diff --git a/CHANGES.rst b/CHANGES.rst
index e0469340fc..fbbdb5b603 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,7 +5,7 @@ New Features
------------
- Added flux/surface brightness translation and surface brightness
- unit conversion in Cubeviz and Specviz. [#2781, #2940, #3088, #3111, #3113, #3129, #3155]
+ unit conversion in Cubeviz and Specviz. [#2781, #2940, #3088, #3111, #3113, #3129, #3139, #3155]
- Plugin tray is now open by default. [#2892]
diff --git a/jdaviz/app.py b/jdaviz/app.py
index 7674ec5c51..b552fc018e 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -75,18 +75,16 @@ def equivalent_units(self, data, cid, units):
include_prefix_units=True, equivalencies=eqv)))
+ [
'Jy', 'mJy', 'uJy', 'MJy',
- 'W / (m2 Hz)', 'W / (Hz m2)', # Order is different in astropy v5.3
- 'eV / (s m2 Hz)', 'eV / (Hz s m2)',
- 'erg / (s cm2 Angstrom)', 'erg / (s cm2 Angstrom)',
- 'erg / (s cm2 Hz)', 'erg / (Hz s cm2)',
- 'ph / (Angstrom s cm2)',
- 'ph / (Hz s cm2)', 'ph / (Hz s cm2)'
+ 'W / (Hz m2)', 'eV / (Hz s m2)',
+ 'erg / (Hz s cm2)', 'erg / (Angstrom s cm2)',
+ 'ph / (Angstrom s cm2)', 'ph / (Hz s cm2)'
]
+ [
'Jy / sr', 'mJy / sr', 'uJy / sr', 'MJy / sr',
- 'W / (Hz sr m2)',
- 'eV / (Hz s sr m2)',
- 'erg / (s sr cm2)',
+ 'W / (Hz sr m2)', 'eV / (Hz s sr m2)',
+ 'erg / (s sr cm2)', 'erg / (Hz s sr cm2)',
+ 'erg / (Angstrom s sr cm2)',
+ 'ph / (Angstrom s sr cm2)', 'ph / (Hz s sr cm2)'
])
else: # spectral axis
# prefer Hz over Bq and um over micron
@@ -108,7 +106,7 @@ def to_unit(self, data, cid, values, original_units, target_units):
except RuntimeError:
data = data.get_object(cls=NDDataArray)
spec = Spectrum1D(flux=data.data * u.Unit(original_units))
- return flux_conversion(spec, values, original_units, target_units)
+ return flux_conversion(values, original_units, target_units, spec)
else: # spectral axis
return spectral_axis_conversion(values, original_units, target_units)
diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
index 27115b92f4..1e0b82983b 100644
--- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
+++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
@@ -17,7 +17,7 @@
from jdaviz.core.marks import PluginScatter, PluginLine
from jdaviz.core.registries import tool_registry
from jdaviz.core.template_mixin import TemplateMixin, DatasetSelectMixin
-from jdaviz.utils import _eqv_pixar_sr, _convert_surface_brightness_units
+from jdaviz.utils import flux_conversion, _eqv_pixar_sr
__all__ = ['CoordsInfo']
@@ -482,11 +482,21 @@ def _image_viewer_update(self, viewer, x, y):
value = self._get_cube_value(
image, arr, x, y, viewer
)
- if self.image_unit is not None and self.image_unit.is_equivalent(unit):
- value = _convert_surface_brightness_units(
- value, unit, self.image_unit
- )
- unit = self.image_unit
+ if self.image_unit is not None:
+ if 'PIXAR_SR' in self.app.data_collection[0].meta:
+ # Need current slice value and associated unit to use to compute
+ # spectral density equivalencies that enable Flux to Flux conversions.
+ # This is needed for units that are not directly convertible/translatable.
+ slice = viewer.slice_value * u.Unit(self.app._get_display_unit('spectral'))
+
+ value = flux_conversion(value, unit, self.image_unit,
+ eqv=_eqv_pixar_sr(self.app.data_collection[0].meta['PIXAR_SR']), # noqa: E501
+ slice=slice)
+ unit = self.image_unit
+
+ elif self.image_unit.is_equivalent(unit):
+ value = (value * u.Unit(unit)).to_value(u.Unit(self.image_unit))
+ unit = self.image_unit
if associated_dq_layers is not None:
associated_dq_layer = associated_dq_layers[0]
@@ -590,8 +600,7 @@ def _copy_axes_to_spectral():
# temporarily here, may be removed after upstream units handling
# or will be generalized for any sb <-> flux
if '_pixel_scale_factor' in sp.meta:
- eqv = u.spectral_density(sp.spectral_axis) + _eqv_pixar_sr(sp.meta['_pixel_scale_factor']) # noqa
- disp_flux = sp.flux.to_value(viewer.state.y_display_unit, eqv)
+ disp_flux = flux_conversion(sp.flux.value, sp.flux.unit, viewer.state.y_display_unit, spec=sp) # noqa: E501
else:
disp_flux = sp.flux.to_value(viewer.state.y_display_unit,
u.spectral_density(sp.spectral_axis))
diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py
index 4d1b90870c..34e19145ff 100644
--- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py
+++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py
@@ -9,7 +9,6 @@
from jdaviz.core.validunits import (create_spectral_equivalencies_list,
create_flux_equivalencies_list,
check_if_unit_is_per_solid_angle,
- units_to_strings,
create_angle_equivalencies_list)
__all__ = ['UnitConversion']
@@ -69,7 +68,6 @@ class UnitConversion(PluginTemplateMixin):
flux_or_sb_items = List().tag(sync=True)
flux_or_sb_selected = Unicode().tag(sync=True)
- can_translate = Bool(True).tag(sync=True)
# This is used a warning message if False. This can be changed from
# bool to unicode when we eventually handle inputing this value if it
# doesn't exist in the FITS header
@@ -170,9 +168,8 @@ def _on_glue_y_display_unit_changed(self, y_unit_str):
# if the y-axis is set to surface brightness,
# untranslatable units need to be removed from the flux choices
if check_if_unit_is_per_solid_angle(y_unit_str):
- updated_flux_choices = list(set(create_flux_equivalencies_list(y_unit * u.sr, x_unit))
- - set(units_to_strings(self._untranslatable_units)))
- self.flux_unit.choices = updated_flux_choices
+ flux_choices = create_flux_equivalencies_list(y_unit * u.sr, x_unit)
+ self.flux_unit.choices = flux_choices
# sets the angle unit drop down and the surface brightness read-only text
if self.app.data_collection[0]:
@@ -183,6 +180,19 @@ def _on_glue_y_display_unit_changed(self, y_unit_str):
self.flux_unit.selected,
self.angle_unit.selected
)
+ if self.angle_unit.selected == 'pix':
+ mouseover_unit = self.flux_unit.selected
+ else:
+ mouseover_unit = self.sb_unit_selected
+ self.hub.broadcast(GlobalDisplayUnitChanged("sb", mouseover_unit, sender=self))
+
+ else:
+ # if cube was loaded in flux units, we still need to broadcast
+ # a 'sb' message for mouseover info. this should be removed when
+ # unit change messaging is improved and is a temporary fix
+ self.hub.broadcast(GlobalDisplayUnitChanged('sb',
+ self.flux_unit.selected,
+ sender=self))
if not self.flux_unit.selected:
y_display_unit = self.spectrum_viewer.state.y_display_unit
@@ -242,15 +252,6 @@ def _on_flux_unit_changed(self, msg):
spectral_y = sb_unit if self.flux_or_sb == 'Surface Brightness' else flux_unit
- untranslatable_units = self._untranslatable_units
- # disable translator if flux unit is untranslatable,
- # still can convert flux units, this just disables flux
- # to surface brightness translation for units in list.
- if spectral_y in untranslatable_units:
- self.can_translate = False
- else:
- self.can_translate = True
-
yunit = _valid_glue_display_unit(spectral_y, self.spectrum_viewer, 'y')
# update spectrum viewer with new y display unit
@@ -280,18 +281,6 @@ def _translate(self, flux_or_sb=None):
if self.app.config == 'specviz':
return
- # we want to raise an error if a user tries to translate with an
- # untranslated Flux unit using the API
- untranslatable_units = units_to_strings(self._untranslatable_units)
-
- if hasattr(self, 'flux_unit'):
- if ((self.flux_unit.selected in untranslatable_units)
- and (flux_or_sb == 'Surface Brightness')):
- raise ValueError(
- "Selected flux unit is not translatable. Please choose a flux unit "
- f"that is not in the following list: {untranslatable_units}."
- )
-
if self.spectrum_viewer.state.y_display_unit:
spec_units = u.Unit(self.spectrum_viewer.state.y_display_unit)
else:
@@ -325,17 +314,6 @@ def _translate(self, flux_or_sb=None):
sender=self))
self.spectrum_viewer.reset_limits()
- @property
- def _untranslatable_units(self):
- return [
- u.erg / (u.s * u.cm**2),
- u.erg / (u.s * u.cm**2 * u.Angstrom),
- u.erg / (u.s * u.cm**2 * u.Hz),
- u.ph / (u.Angstrom * u.s * u.cm**2),
- u.ph / (u.s * u.cm**2 * u.Hz),
- u.ST, u.bol
- ]
-
def _append_angle_correctly(self, flux_unit, angle_unit):
if angle_unit not in ['pix', 'sr']:
self.sb_unit_selected = flux_unit
diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue
index d0c6bbdcf9..09fed8b73f 100644
--- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue
+++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue
@@ -74,9 +74,7 @@
:class="api_hints_enabled ? 'api-hint' : null"
hint="Select the y-axis physical type for the spectrum-viewer."
persistent-hint
- :disabled="!can_translate"
>
- Translation is not available due to current unit selection.
diff --git a/jdaviz/core/helpers.py b/jdaviz/core/helpers.py
index dd03156ecd..194a3dfc80 100644
--- a/jdaviz/core/helpers.py
+++ b/jdaviz/core/helpers.py
@@ -487,8 +487,8 @@ def _handle_display_units(self, data, use_display_units=True):
# if not specified as NDUncertainty, assume stddev:
new_uncert = uncertainty
if ('_pixel_scale_factor' in data.meta):
- new_uncert_converted = flux_conversion(data, new_uncert.quantity.value,
- new_uncert.unit, y_unit)
+ new_uncert_converted = flux_conversion(new_uncert.quantity.value,
+ 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)
@@ -496,11 +496,11 @@ def _handle_display_units(self, data, use_display_units=True):
else:
new_uncert = None
if ('_pixel_scale_factor' in data.meta):
- new_y = flux_conversion(data, data.flux.value, data.flux.unit,
- y_unit) * u.Unit(y_unit)
+ new_y = flux_conversion(data.flux.value, data.flux.unit,
+ y_unit, data) * u.Unit(y_unit)
else:
- new_y = flux_conversion(data, data.flux.value, data.flux.unit,
- data.flux.unit) * u.Unit(data.flux.unit)
+ new_y = flux_conversion(data.flux.value, data.flux.unit,
+ data.flux.unit, spec=data) * u.Unit(data.flux.unit)
new_spec = (spectral_axis_conversion(data.spectral_axis.value,
data.spectral_axis.unit,
spectral_unit)
diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py
index 8aad68ac63..578c94f175 100644
--- a/jdaviz/core/validunits.py
+++ b/jdaviz/core/validunits.py
@@ -73,12 +73,9 @@ def create_flux_equivalencies_list(flux_unit, spectral_axis_unit):
# Get local flux units.
locally_defined_flux_units = ['Jy', 'mJy', 'uJy', 'MJy',
- 'W / (Hz m2)',
- 'eV / (s m2 Hz)',
- 'erg / (s cm2 Hz)',
- 'erg / (s cm2 Angstrom)',
- 'ph / (Angstrom s cm2)',
- 'ph / (Hz s cm2)',
+ 'W / (Hz m2)', 'eV / (Hz s m2)',
+ 'erg / (s cm2 Angstrom)', 'erg / (Hz s cm2)',
+ 'ph / (Angstrom s cm2)', 'ph / (Hz s cm2)'
]
local_units = [u.Unit(unit) for unit in locally_defined_flux_units]
diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py
index e2d3ca11bb..42fd5541a5 100644
--- a/jdaviz/tests/test_app.py
+++ b/jdaviz/tests/test_app.py
@@ -227,8 +227,8 @@ def test_to_unit(cubeviz_helper):
original_units = u.MJy / u.sr
target_units = u.MJy
- value = flux_conversion(data.get_object(cls=Spectrum1D),
- values, original_units, target_units)
+ value = flux_conversion(values, original_units,
+ target_units, data.get_object(cls=Spectrum1D))
# will be a uniform array since not wavelength dependent
# so test first value in array
@@ -240,8 +240,8 @@ def test_to_unit(cubeviz_helper):
original_units = u.MJy
target_units = u.erg / u.cm**2 / u.s / u.AA
- new_values = flux_conversion(data.get_object(cls=Spectrum1D), values,
- original_units, target_units)
+ new_values = flux_conversion(values, original_units,
+ target_units, data.get_object(cls=Spectrum1D))
assert np.allclose(new_values,
(values * original_units)
@@ -255,8 +255,8 @@ def test_to_unit(cubeviz_helper):
original_units = u.MJy
target_units = u.erg / u.cm**2 / u.s / u.AA
- new_values = flux_conversion(data.get_object(cls=Spectrum1D), values,
- original_units, target_units)
+ new_values = flux_conversion(values, original_units,
+ target_units, data.get_object(cls=Spectrum1D))
# In this case we do a regular spectral density conversion, but using the
# first value in the spectral axis for the equivalency
diff --git a/jdaviz/tests/test_utils.py b/jdaviz/tests/test_utils.py
index bf4f7ceb2d..86294bcd12 100644
--- a/jdaviz/tests/test_utils.py
+++ b/jdaviz/tests/test_utils.py
@@ -10,7 +10,8 @@
from numpy.testing import assert_allclose
from specutils import Spectrum1D
-from jdaviz.utils import alpha_index, download_uri_to_path, flux_conversion
+from jdaviz.utils import (alpha_index, download_uri_to_path, flux_conversion,
+ _indirect_conversion, _eqv_pixar_sr)
PHOTUTILS_LT_1_12_1 = not minversion(photutils, "1.12.1.dev")
@@ -24,50 +25,120 @@ def test_spec_sb_flux_conversion():
# Float scalar pixel scale factor
spec.meta["_pixel_scale_factor"] = 0.1
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 2, 3])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), [100, 200, 300])
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec), [1, 2, 3])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec), [100, 200, 300])
+
+ # complex translation Jy / sr -> erg / (Angstrom s cm2 sr)
+ targ = [2.99792458e-12, 1.49896229e-12, 9.99308193e-13] * (u.erg / (u.Angstrom * u.s * u.cm**2 * u.sr)) # noqa: E501
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.erg / (u.Angstrom * u.s * u.cm**2 * u.sr), spec), targ.value) # noqa: E501
+
+ # complex translation erg / (Angstrom s cm2 sr) -> Jy / sr
+ targ = [3.33564095e+13, 2.66851276e+14, 9.00623057e+14] * (u.Jy / u.sr)
+ assert_allclose(flux_conversion(values, u.erg / (u.Angstrom * u.s * u.cm**2 * u.sr), u.Jy / u.sr, spec), targ.value) # noqa: E501
+
+ spectral_values = spec.spectral_axis
+ spec_unit = u.MJy
+ eqv = u.spectral_density(spectral_values) + _eqv_pixar_sr(spec.meta["_pixel_scale_factor"])
+
+ # test spectrum when target unit in untranslatable unit list
+ target_values = [5.03411657e-05, 2.01364663e-04, 4.53070491e-04]
+ expected_units = (u.ph / (u.Hz * u.s * u.cm**2))
+ returned_values, return_units, unit_flag = _indirect_conversion(
+ values=values, orig_units=(u.MJy),
+ targ_units=(u.ph / (u.s * u.cm**2 * u.Hz * u.sr)), # noqa
+ eqv=eqv, spec_unit=spec_unit, image_data=None
+ )
+ assert_allclose(returned_values, target_values)
+ assert (return_units == expected_units)
+ assert (unit_flag == 'orig')
+
+ # test spectrum when original unit in untranslatable unit list
+ target_values = [1., 2., 3.]
+ expected_units = (u.ph / (u.Angstrom * u.s * u.cm**2))
+ returned_values, return_units, unit_flag = _indirect_conversion(
+ values=values,
+ orig_units=(u.ph / (u.Angstrom * u.s * u.cm**2 * u.sr)), # noqa
+ targ_units=(u.MJy), eqv=eqv,
+ spec_unit=spec_unit, image_data=None
+ )
+ assert_allclose(returned_values, target_values)
+ assert (return_units == expected_units)
+ assert (unit_flag == 'targ')
+
+ # test the default case where units are translatable
+ target_values = [10, 20, 30]
+ expected_units = (u.MJy)
+ returned_values, return_units, unit_flag = _indirect_conversion(
+ values=values, orig_units=(u.Jy/u.sr),
+ targ_units=(u.MJy), eqv=eqv,
+ spec_unit=spec_unit, image_data=None
+ )
+ assert_allclose(returned_values, target_values)
+ assert (return_units == expected_units)
+ assert (unit_flag == 'targ')
+
+ # test image viewer data units are untranslatable
+ target_value = 1.e-18
+ expected_units = (u.erg / (u.s * u.cm**2 * u.Hz))
+ returned_values, return_units = _indirect_conversion(
+ values=1, orig_units=(u.MJy/u.sr),
+ targ_units=(u.erg / (u.s * u.cm**2 * u.Hz * u.sr)),
+ eqv=eqv, spec_unit=None, image_data=True
+ )
+ assert_allclose(returned_values, target_value)
+ assert return_units == expected_units
+
+ # test image viewer data units are translatable
+ target_value = 10
+ expected_units = (u.MJy / u.sr)
+ returned_values, return_units = _indirect_conversion(
+ values=10, orig_units=(u.MJy/u.sr), targ_units=(u.Jy/u.sr),
+ eqv=eqv, spec_unit=None, image_data=True
+ )
+ assert_allclose(returned_values, target_value)
+ assert return_units == expected_units
# Quantity scalar pixel scale factor
spec.meta["_pixel_scale_factor"] = 0.1 * (u.sr / u.pix)
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 2, 3])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), [100, 200, 300])
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec), [1, 2, 3])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec), [100, 200, 300])
# values == 2
values = [10, 20]
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 2])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), [100, 200])
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec), [1, 2])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec), [100, 200])
# float array pixel scale factor
spec.meta["_pixel_scale_factor"] = [0.1, 0.2, 0.3] # min_max = [0.1, 0.3]
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 6])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), [100, 66.66666666666667])
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec), [1, 6])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec), [100, 66.66666666666667])
# Quantity array pixel scale factor
spec.meta["_pixel_scale_factor"] = [0.1, 0.2, 0.3] * (u.sr / u.pix) # min_max = [0.1, 0.3]
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 6])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), [100, 66.66666666666667])
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec), [1, 6])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec), [100, 66.66666666666667])
# values != 2
values = [10, 20, 30]
spec.meta["_pixel_scale_factor"] = [0.1, 0.2, 0.3]
- assert_allclose(flux_conversion(spec, values, u.Jy / u.sr, u.Jy), [1, 4, 9])
- assert_allclose(flux_conversion(spec, values, u.Jy, u.Jy / u.sr), 100)
+ assert_allclose(flux_conversion(values, u.Jy / u.sr, u.Jy, spec=spec), [1, 4, 9])
+ assert_allclose(flux_conversion(values, u.Jy, u.Jy / u.sr, spec=spec), 100)
# values != 2 but _pixel_scale_factor size mismatch
with pytest.raises(ValueError, match="operands could not be broadcast together"):
spec.meta["_pixel_scale_factor"] = [0.1, 0.2, 0.3, 0.4]
- flux_conversion(spec, values, u.Jy / u.sr, u.Jy)
+ flux_conversion(values, u.Jy / u.sr, u.Jy, spec=spec)
# Other kind of flux conversion unrelated to _pixel_scale_factor.
# The answer was obtained from synphot unit conversion.
spec.meta["_pixel_scale_factor"] = 0.1
targ = [2.99792458e-12, 1.49896229e-12, 9.99308193e-13] * (u.erg / (u.AA * u.cm * u.cm * u.s)) # FLAM # noqa: E501
- assert_allclose(flux_conversion(spec, values, u.Jy, targ.unit), targ.value)
+ assert_allclose(flux_conversion(values, u.Jy, targ.unit, spec=spec), targ.value)
# values == 2 (only used spec.spectral_axis[0] for some reason)
values = [10, 20]
targ = [2.99792458e-12, 5.99584916e-12] * (u.erg / (u.AA * u.cm * u.cm * u.s)) # FLAM
- assert_allclose(flux_conversion(spec, values, u.Jy, targ.unit), targ.value)
+ assert_allclose(flux_conversion(values, u.Jy, targ.unit, spec=spec), targ.value)
@pytest.mark.parametrize("test_input,expected", [(0, 'a'), (1, 'b'), (25, 'z'), (26, 'aa'),
diff --git a/jdaviz/utils.py b/jdaviz/utils.py
index a9d0d517f6..ff19929662 100644
--- a/jdaviz/utils.py
+++ b/jdaviz/utils.py
@@ -21,6 +21,8 @@
from glue.core.subset import SubsetState, RangeSubsetState, RoiSubsetState
from ipyvue import watch
+from jdaviz.core.validunits import check_if_unit_is_per_solid_angle
+
__all__ = ['SnackbarQueue', 'enable_hot_reloading', 'bqplot_clear_figure',
'standardize_metadata', 'ColorCycler', 'alpha_index', 'get_subset_type',
'download_uri_to_path', 'flux_conversion', 'spectral_axis_conversion',
@@ -286,31 +288,48 @@ def standardize_metadata(metadata):
return out_meta
-def flux_conversion(spec, values, original_units, target_units):
+def indirect_units():
+ return [
+ u.erg / (u.s * u.cm**2 * u.Angstrom * u.sr),
+ u.erg / (u.s * u.cm**2 * u.Hz * u.sr),
+ u.ph / (u.Angstrom * u.s * u.cm**2 * u.sr), u.ph / (u.Angstrom * u.s * u.sr * u.cm**2),
+ u.ph / (u.s * u.cm**2 * u.Hz * u.sr)
+ ]
+
+
+def flux_conversion(values, original_units, target_units, spec=None, eqv=None, slice=None):
"""
- Given a Spectrum1D object, flux values, original flux units, and target units,
- this method will return the flux values in the converted units. This conversion
- takes into account the possible surface brightness to flux or vice versa change that
- may happen between units.
+ Convert flux or surface brightness values from original units to target units.
+
+ This function handles the conversion of flux or surface brightness values between different
+ units, taking into account changes between flux and surface brightness. It supports complex
+ conversions for Spectrum1D objects or cube image data.
Parameters
----------
- spec : `~specutils.Spectrum1D` object
- The Spectrum1D object that will have converted flux units.
-
values : float array
- Flux values in the original units.
+ Flux or surface brightness values in the original units.
original_units : str
- The flux units of the spec object.
+ The flux or surface brightness units of the spec object or cube image.
target_units : str
- The units the flux will be converted to.
+ The units the flux or surface brightness will be converted to.
+
+ spec : `~specutils.Spectrum1D`, optional
+ The Spectrum1D object that will have converted flux or surface brightness units.
+
+ eqv : list of `astropy.units.equivalencies`, optional
+ A list of Astropy equivalencies necessary for complex unit conversions/translations.
+
+ slice : `astropy.units.Quantity`, optional
+ The current slice of a data cube, with units. Necessary for complex unit
+ conversions/translations that require spectral density equivalencies.
Returns
-------
result : float array
- Flux values in the target units.
+ Flux or surface brightness values in the target units.
"""
# we set surface brightness choices and selection before flux, which can
# cause a dimensionless translation attempt at instantiation
@@ -319,13 +338,22 @@ def flux_conversion(spec, values, original_units, target_units):
# If there are only two values, this is likely the limits being converted, so then
# in case we need to use the spectral density equivalency, we need to provide only
# to spectral axis values. If there is only one value
- if not np.isscalar(values) and len(values) == 2:
- spectral_values = spec.spectral_axis[0]
- else:
- spectral_values = spec.spectral_axis
+ image_data = False
+ if spec:
+ if not np.isscalar(values) and len(values) == 2:
+ spectral_values = spec.spectral_axis[0]
+ else:
+ spectral_values = spec.spectral_axis
+
+ # the unit of the data collection item object, could be flux or surface brightness
+ spec_unit = str(spec.flux.unit)
- # Need this for setting the y-limits
- eqv = u.spectral_density(spectral_values)
+ # Need this for setting the y-limits
+ eqv = u.spectral_density(spectral_values)
+ elif slice is not None and eqv:
+ image_data = True
+ # Need this to convert Flux to Flux for complex conversions/translations of cube image data
+ eqv += u.spectral_density(slice)
orig_units = u.Unit(original_units)
orig_bases = orig_units.bases
@@ -333,7 +361,7 @@ def flux_conversion(spec, values, original_units, target_units):
targ_bases = targ_units.bases
# Ensure a spectrum passed through Spectral Extraction plugin
- if (('_pixel_scale_factor' in spec.meta) and
+ if (((spec and ('_pixel_scale_factor' in spec.meta))) and
(((u.sr in orig_bases) and (u.sr not in targ_bases)) or
((u.sr not in orig_bases) and (u.sr in targ_bases)))):
# Data item in data collection does not update from conversion/translation.
@@ -351,15 +379,72 @@ def flux_conversion(spec, values, original_units, target_units):
eqv_in = [min(fac), max(fac)]
else:
eqv_in = fac
-
eqv += _eqv_pixar_sr(np.array(eqv_in))
+ # indirect units cannot be directly converted, and require
+ # additional conversions to reach the desired end unit.
+ # if spec_unit in [original_units, target_units]:
+ result = _indirect_conversion(
+ values=values, orig_units=orig_units, targ_units=targ_units,
+ eqv=eqv, spec_unit=spec_unit
+ )
+
+ if result and len(result) == 2:
+ values, updated_units = result
+ orig_units = updated_units
+ else:
+ values, updated_units, selected_unit_updated = result
+ if selected_unit_updated == 'targ':
+ targ_units = updated_units
+ elif selected_unit_updated == 'orig':
+ orig_units = updated_units
+
+ elif image_data:
+ values, orig_units = _indirect_conversion(
+ values=values, orig_units=orig_units, targ_units=targ_units,
+ eqv=eqv, image_data=image_data
+ )
+
return (values * orig_units).to_value(targ_units, equivalencies=eqv)
-def _convert_surface_brightness_units(data, from_unit, to_unit):
- quantity = data * u.Unit(from_unit)
- return quantity.to_value(u.Unit(to_unit))
+def _indirect_conversion(values, orig_units, targ_units, eqv,
+ spec_unit=None, image_data=None):
+ # indirect units cannot be directly converted, and require
+ # additional conversions to reach the desired end unit.
+ if (spec_unit and spec_unit in [orig_units, targ_units]
+ and not check_if_unit_is_per_solid_angle(spec_unit)):
+ if u.Unit(targ_units) in indirect_units():
+ temp_targ = targ_units * u.sr
+ values = (values * orig_units).to_value(temp_targ, equivalencies=eqv)
+ orig_units = u.Unit(temp_targ)
+ return values, orig_units, 'orig'
+ elif u.Unit(orig_units) in indirect_units():
+ temp_orig = orig_units * u.sr
+ values = (values * orig_units).to_value(temp_orig, equivalencies=eqv)
+ targ_units = u.Unit(temp_orig)
+ return values, targ_units, 'targ'
+
+ return values, targ_units, 'targ'
+
+ elif image_data or (spec_unit and check_if_unit_is_per_solid_angle(spec_unit)):
+ if not check_if_unit_is_per_solid_angle(targ_units):
+ targ_units /= u.sr
+ if ((u.Unit(targ_units) in indirect_units()) or
+ (u.Unit(orig_units) in indirect_units())):
+ # SB -> Flux -> Flux -> SB
+ temp_orig = orig_units * u.sr
+ temp_targ = targ_units * u.sr
+
+ # Convert Surface Brightness to Flux, then Flux to Flux
+ values = (values * orig_units).to_value(temp_orig, equivalencies=eqv)
+ values = (values * temp_orig).to_value(temp_targ, equivalencies=eqv)
+ # Lastly a Flux to Surface Brightness translation in the return statement
+ orig_units = temp_targ
+
+ return values, orig_units
+
+ return values, orig_units
def _eqv_pixar_sr(pixar_sr):
@@ -369,7 +454,12 @@ def converter_flux(x): # Surface Brightness -> Flux
def iconverter_flux(x): # Flux -> Surface Brightness
return x / pixar_sr
- return [(u.MJy / u.sr, u.MJy, converter_flux, iconverter_flux)]
+ return [
+ (u.MJy / u.sr, u.MJy, converter_flux, iconverter_flux),
+ (u.erg / (u.s * u.cm**2 * u.Angstrom * u.sr), u.erg / (u.s * u.cm**2 * u.Angstrom), converter_flux, iconverter_flux), # noqa
+ (u.ph / (u.Angstrom * u.s * u.cm**2 * u.sr), u.ph / (u.Angstrom * u.s * u.cm**2), converter_flux, iconverter_flux), # noqa
+ (u.ph / (u.Hz * u.s * u.cm**2 * u.sr), u.ph / (u.Hz * u.s * u.cm**2), converter_flux, iconverter_flux) # noqa
+ ]
def spectral_axis_conversion(values, original_units, target_units):