Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement unit conversion in specviz2d #3253

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Specviz
Specviz2d
^^^^^^^^^

- Implement the Unit Conversion plugin the Specviz2D. [#3253]

API Changes
-----------
- Removed API access to plugins that have passed the deprecation period: Links Control, Canvas Rotation, Export Plot. [#3270]
Expand Down
2 changes: 2 additions & 0 deletions jdaviz/configs/cubeviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None,
Spectral subset applied to data.
cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional
The type that data will be returned as.
use_display_units : bool, optional
Specify whether the returned data is in native units or the current display units.

Returns
-------
Expand Down
2 changes: 2 additions & 0 deletions jdaviz/configs/default/plugins/markers/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ def __init__(self, *args, **kwargs):
elif self.config == 'specviz':
headers = ['spectral_axis', 'spectral_axis:unit',
'index', 'value', 'value:unit']

elif self.config == 'specviz2d':
# TODO: add "index" if/when specviz2d supports plotting spectral_axis
headers = ['spectral_axis', 'spectral_axis:unit',
'pixel_x', 'pixel_y', 'value', 'value:unit', 'viewer']

elif self.config == 'mosviz':
headers = ['spectral_axis', 'spectral_axis:unit',
'pixel_x', 'pixel_y', 'world_ra', 'world_dec', 'index',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from numpy.testing import assert_allclose
from regions import RectanglePixelRegion, PixCoord
from specutils import Spectrum1D, SpectralRegion
from glue.core.roi import XRangeROI

from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit
from jdaviz.core.custom_units_and_equivs import PIX2
Expand Down Expand Up @@ -91,7 +92,7 @@ def test_cubeviz_units(cubeviz_helper, spectrum1d_cube_custom_fluxunit,
is in flux/pix2 and flux/sr, and that the results remain consistant
between translations of the spectral y axis flux<>surface brightness.
"""
cube = spectrum1d_cube_custom_fluxunit(fluxunit=u.Jy / sq_angle_unit,
cube = spectrum1d_cube_custom_fluxunit(fluxunit=u.MJy / sq_angle_unit,
shape=(25, 25, 4), with_uncerts=True)
cubeviz_helper.load_data(cube, data_label="Test Cube")

Expand All @@ -107,9 +108,46 @@ def test_cubeviz_units(cubeviz_helper, spectrum1d_cube_custom_fluxunit,
results = plugin.results
assert results[0]['unit'] == 'W / m2'

viewer = cubeviz_helper.app.get_viewer('spectrum-viewer')
viewer.apply_roi(XRangeROI(4.63e-7, 4.64e-7))

la = cubeviz_helper.plugins['Line Analysis']
la.keep_active = True
la.spectral_subset.selected = 'Subset 1'

marks_before = [la._obj.continuum_marks['left'].y,
la._obj.continuum_marks['right'].y]

# change flux unit and make sure result stays the same after conversion
uc.flux_unit.selected = 'Jy'

marks_after = [la._obj.continuum_marks['left'].y,
la._obj.continuum_marks['right'].y]

# ensure continuum marks update when spectral_y is changed by
# multiply converted continuum marks by expected scale factor (MJy -> Jy)
scaling_factor = 1e6
assert_allclose([mark * scaling_factor for mark in marks_before], marks_after, rtol=1e-5)

# reset to test again after spectral_y_type is changed
marks_before = marks_after

# now change to surface brightness
uc.spectral_y_type = 'Surface Brightness'

if sq_angle_unit == PIX2:
# translation does not alter spectral_y values in viewer
scaling_factor = 1
else:
scaling_factor = cube.meta.get('PIXAR_SR')

marks_after = [la._obj.continuum_marks['left'].y,
la._obj.continuum_marks['right'].y]

# ensure continuum marks update when spectral_y_type is changed
# multiply converted continuum marks by expected pixel scale factor
assert_allclose([mark / scaling_factor for mark in marks_before], marks_after, rtol=1e-5)

results = plugin.results
line_flux_before_unit_conversion = results[0]
# convert back and forth between unit<>str for string format consistency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,40 @@ def test_flux_unit_choices(specviz_helper, flux_unit, expected_choices):

assert uc_plg.flux_unit.selected == flux_unit.to_string()
assert uc_plg.flux_unit.choices == expected_choices


def test_mosviz_profile_view_mouseover(specviz2d_helper, spectrum2d):
data = np.zeros((5, 10))
data[3] = np.arange(10)
spectrum2d = Spectrum1D(flux=data*u.MJy, spectral_axis=data[3]*u.um)

specviz2d_helper.load_data(spectrum2d)
viewer = specviz2d_helper.app.get_viewer("spectrum-viewer")
plg = specviz2d_helper.plugins["Unit Conversion"]

# make sure we don't expose angle, sb, nor spectral-y units when native
# units are in flux
assert hasattr(plg, 'flux_unit')
assert not hasattr(plg, 'angle_unit')
assert not hasattr(plg, 'sb_unit')
assert not hasattr(plg, 'spectral_y_type')

label_mouseover = specviz2d_helper.app.session.application._tools['g-coords-info']
label_mouseover._viewer_mouse_event(viewer,
{'event': 'mousemove',
'domain': {'x': 5, 'y': 3}})

assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00',
'Wave 5.00000e+00 um (5 pix)',
'Flux 5.00000e+00 MJy')

plg._obj.flux_unit_selected = 'Jy'
assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00',
'Wave 5.00000e+00 um (5 pix)',
'Flux 5.00000e+06 Jy')

# test mouseover when spectral density equivalencies are required for conversion
plg._obj.flux_unit_selected = 'erg / (Angstrom s cm2)'
assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00',
'Wave 5.00000e+00 um (5 pix)',
'Flux 5.99585e-08 erg / (Angstrom s cm2)')
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(self, *args, **kwargs):

self._cached_properties = ['image_layers']

if self.config not in ['specviz', 'cubeviz']:
if self.config not in ['specviz', 'specviz2d', 'cubeviz']:
# TODO [specviz2d, mosviz] x_display_unit is not implemented in glue for image viewer
# used by spectrum-2d-viewer
# TODO [mosviz]: add to yaml file
Expand Down Expand Up @@ -141,7 +141,7 @@ def __init__(self, *args, **kwargs):
# initialize flux choices to empty list, will be populated when data is loaded
self.flux_unit.choices = []

self.has_angle = self.config in ('cubeviz', 'specviz', 'mosviz')
self.has_angle = self.config in ('cubeviz', 'specviz', 'mosviz', 'specviz2d')
self.angle_unit = UnitSelectPluginComponent(self,
items='angle_unit_items',
selected='angle_unit_selected')
Expand Down Expand Up @@ -211,7 +211,6 @@ def _on_add_data_to_viewer(self, msg):
or not len(self.flux_unit_selected)
or not len(self.angle_unit_selected)
or (self.config == 'cubeviz' and not len(self.spectral_y_type_selected))):

data_obj = msg.data.get_object()
if isinstance(data_obj, Spectrum1D):

Expand All @@ -237,9 +236,13 @@ def _on_add_data_to_viewer(self, msg):

try:
if angle_unit is None:
# default to sr if input spectrum is not in surface brightness units
# TODO: for cubeviz, should we check the cube itself?
self.angle_unit.selected = 'sr'
if self.config in ['specviz', 'specviz2d']:
self.has_angle = False
self.has_sb = False
else:
# default to pix2 if input data is not in surface brightness units
# TODO: for cubeviz, should we check the cube itself?
self.angle_unit.selected = 'pix2'
else:
self.angle_unit.selected = str(angle_unit)
except ValueError:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
hint="Solid angle unit."
/>

<v-row v-if="has_spectral">
<v-row v-if="has_sb">
<v-text-field
v-model="sb_unit_selected"
:label="api_hints_enabled ? 'plg.sb_unit' : 'Surface Brightness Unit'"
Expand Down
8 changes: 6 additions & 2 deletions jdaviz/configs/specviz2d/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ def load_trace(self, trace, data_label, show_in_viewer=True):
self._default_spectrum_2d_viewer_reference_name, data_label
)

def get_data(self, data_label=None, spectral_subset=None, cls=None):
def get_data(self, data_label=None, spectral_subset=None,
cls=None, use_display_units=False):
"""
Returns data with name equal to data_label of type cls with subsets applied from
spectral_subset.
Expand All @@ -184,11 +185,14 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None):
Spectral subset applied to data.
cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional
The type that data will be returned as.
use_display_units : bool, optional
Specify whether the returned data is in native units or the current display units.

Returns
-------
data : cls
Data is returned as type cls with subsets applied.

"""
return self._get_data(data_label=data_label, spectral_subset=spectral_subset, cls=cls)
return self._get_data(data_label=data_label, spectral_subset=spectral_subset,
cls=cls, use_display_units=use_display_units)
1 change: 1 addition & 0 deletions jdaviz/configs/specviz2d/specviz2d.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tray:
- g-plot-options
- g-subset-tools
- g-markers
- g-unit-conversion
- spectral-extraction
- g-gaussian-smooth
- g-model-fitting
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def _create_spectrum1d_cube_with_fluxunit(fluxunit=u.Jy, shape=(2, 2, 4), with_u
wcs_dict = {"CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CTYPE3": "WAVE-LOG",
"CRVAL1": 205, "CRVAL2": 27, "CRVAL3": 4.622e-7,
"CDELT1": -0.0001, "CDELT2": 0.0001, "CDELT3": 8e-11,
"CRPIX1": 0, "CRPIX2": 0, "CRPIX3": 0,
"CRPIX1": 0, "CRPIX2": 0, "CRPIX3": 0, "PIXAR_SR": 10,
# Need these for aperture photometry test.
"TELESCOP": "JWST", "BUNIT": fluxunit.to_string(), "PIXAR_A2": 0.01}
w = WCS(wcs_dict)
Expand Down
10 changes: 3 additions & 7 deletions jdaviz/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,16 +480,12 @@ def _handle_display_units(self, data, use_display_units=True):
new_uncert.unit, y_unit, spec=data)
new_uncert = StdDevUncertainty(new_uncert_converted, unit=y_unit)
else:
new_uncert = StdDevUncertainty(new_uncert, unit=data.flux.unit)
new_uncert = StdDevUncertainty(new_uncert, unit=y_unit)

else:
new_uncert = None
if ('_pixel_scale_factor' in data.meta):
new_y = flux_conversion(data.flux.value, data.flux.unit,
y_unit, data) * u.Unit(y_unit)
else:
new_y = flux_conversion(data.flux.value, data.flux.unit,
data.flux.unit, spec=data) * u.Unit(data.flux.unit)
new_y = flux_conversion(data.flux.value, data.flux.unit,
y_unit, spec=data) * u.Unit(y_unit)
new_spec = (spectral_axis_conversion(data.spectral_axis.value,
data.spectral_axis.unit,
spectral_unit)
Expand Down
21 changes: 18 additions & 3 deletions jdaviz/core/marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
SpectralMarksChangedMessage,
RedshiftMessage)
from jdaviz.core.unit_conversion_utils import (all_flux_unit_conversion_equivs,
flux_conversion_general)
flux_conversion_general,
check_if_unit_is_per_solid_angle)


__all__ = ['OffscreenLinesMarks', 'BaseSpectrumVerticalLine', 'SpectralLine',
Expand Down Expand Up @@ -129,15 +130,21 @@ def set_y_unit(self, unit=None):
unit = self.viewer.state.y_display_unit
unit = u.Unit(unit)

# spectrum y-values in viewer have already been converted, don't convert again
# if a spectral_y_type is changed, just update the unit
if self.yunit is not None and check_if_unit_is_per_solid_angle(self.yunit) != check_if_unit_is_per_solid_angle(unit): # noqa
self.yunit = unit
return

if self.yunit is not None and not np.all([s == 0 for s in self.y.shape]):

if self.viewer.default_class is Spectrum1D:

spec = self.viewer.state.reference_data.get_object(cls=Spectrum1D)

pixar_sr = spec.meta.get('PIXAR_SR', 1)
cube_wave = self.x * self.xunit
equivs = all_flux_unit_conversion_equivs(pixar_sr, cube_wave)

y = flux_conversion_general(self.y, self.yunit, unit, equivs,
with_unit=False)

Expand All @@ -148,14 +155,22 @@ def set_y_unit(self, unit=None):
def _on_global_display_unit_changed(self, msg):
if not self.auto_update_units:
return
if self.viewer.__class__.__name__ in ['SpecvizProfileView', 'CubevizProfileView']:
if self.viewer.__class__.__name__ in ['SpecvizProfileView',
'CubevizProfileView',
'MosvizProfileView']:
axis_map = {'spectral': 'x', 'spectral_y': 'y'}
elif self.viewer.__class__.__name__ == 'MosvizProfile2DView':
axis_map = {'spectral': 'x'}
else:
return
axis = axis_map.get(msg.axis, None)
if axis is not None:
scale = self.scales.get(axis, None)
# if PluginMark mark is LinearScale(0, 1), prevent it from entering unit conversion
# machinery so it maintains it's position in viewer.
if isinstance(scale, LinearScale) and (scale.min, scale.max) == (0, 1):
return

getattr(self, f'set_{axis}_unit')(msg.unit)

def clear(self):
Expand Down
Loading