diff --git a/jdaviz/app.py b/jdaviz/app.py index 895a0e90b6..9d5ea971d4 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -53,7 +53,10 @@ from jdaviz.utils import (SnackbarQueue, alpha_index, data_has_valid_wcs, layer_is_table_data, MultiMaskSubsetState, _wcs_only_label, flux_conversion, spectral_axis_conversion) -from jdaviz.core.validunits import check_if_unit_is_per_solid_angle +from jdaviz.core.validunits import (check_if_unit_is_per_solid_angle, + combine_flux_and_angle_units, + locally_defined_flux_units, + supported_sq_angle_units) __all__ = ['Application', 'ALL_JDAVIZ_CONFIGS', 'UnitConverterWithSpectral'] @@ -72,32 +75,14 @@ class UnitConverterWithSpectral: def equivalent_units(self, data, cid, units): if cid.label == "flux": eqv = u.spectral_density(1 * u.m) # Value does not matter here. - list_of_units = set(list(map(str, u.Unit(units).find_equivalent_units( - include_prefix_units=True, equivalencies=eqv))) - + [ - 'Jy', 'mJy', 'uJy', 'MJy', - 'W / (Hz m2)', 'eV / (Hz s m2)', - 'erg / (Hz s cm2)', 'erg / (Angstrom s cm2)', - 'ph / (Angstrom s cm2)', 'ph / (Hz s cm2)', - 'ct' - ] - + [ - 'Jy / sr', 'mJy / sr', 'uJy / sr', 'MJy / sr', - '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)', - 'ct / sr' - ] - + [ - 'Jy / pix2', 'mJy / pix2', 'uJy / pix2', 'MJy / pix2', - 'W / (Hz m2 pix2)', 'eV / (Hz s m2 pix2)', - 'erg / (s cm2 pix2)', 'erg / (Hz s cm2 pix2)', - 'erg / (Angstrom s cm2 pix2)', - 'ph / (Angstrom s cm2 pix2)', 'ph / (Hz s cm2 pix2)', - 'ct / pix2' - ] + all_flux_units = locally_defined_flux_units() + ['ct'] + angle_units = supported_sq_angle_units() + all_sb_units = combine_flux_and_angle_units(all_flux_units, angle_units) + # list of all possible units for spectral y axis, independent of data loaded + # + list_of_units = set(list(map(str, u.Unit(units).find_equivalent_units( + include_prefix_units=True, equivalencies=eqv))) + all_flux_units + all_sb_units ) else: # spectral axis # prefer Hz over Bq and um over micron diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py index 40b0236c2f..6e8f58c938 100644 --- a/jdaviz/configs/cubeviz/plugins/parsers.py +++ b/jdaviz/configs/cubeviz/plugins/parsers.py @@ -10,6 +10,7 @@ from astropy.wcs import WCS from specutils import Spectrum1D, SpectralAxis +from jdaviz.core.custom_units import PIX2 from jdaviz.core.registries import data_parser_registry from jdaviz.core.validunits import check_if_unit_is_per_solid_angle from jdaviz.utils import standardize_metadata, PRIHDR_KEY, download_uri_to_path @@ -195,9 +196,9 @@ def _return_spectrum_with_correct_units(flux, wcs, metadata, data_type=None, # convert flux and uncertainty to per-pix2 if input is not a surface brightness if apply_pix2: if not check_if_unit_is_per_solid_angle(flux.unit): - flux = flux / (u.pix * u.pix) + flux = flux / PIX2 if uncertainty is not None: - uncertainty = uncertainty / (u.pix * u.pix) + uncertainty = uncertainty / PIX2 # handle scale factors when they are included in the unit if not np.isclose(flux.unit.scale, 1.0, rtol=1e-5): @@ -606,14 +607,14 @@ def convert_spectrum1d_from_flux_to_flux_per_pixel(spectrum): # convert flux, which is always populated flux = getattr(spectrum, 'flux') - flux = flux / (u.pix * u.pix) + flux = flux / PIX2 # and uncerts, if present uncerts = getattr(spectrum, 'uncertainty') if uncerts is not None: # enforce common uncert type. uncerts = uncerts.represent_as(StdDevUncertainty) - uncerts = StdDevUncertainty(uncerts.quantity / (u.pix * u.pix)) + uncerts = StdDevUncertainty(uncerts.quantity / PIX2) # create a new spectrum 1d with all the info from the input spectrum 1d, # and the flux / uncerts converted from flux to SB per square pixel diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py index 59916c9619..e4ab814724 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py @@ -7,11 +7,13 @@ from numpy.testing import assert_allclose from regions import RectanglePixelRegion, PixCoord +from jdaviz.core.custom_units import PIX2 + def test_cubeviz_aperphot_cube_orig_flux(cubeviz_helper, image_cube_hdu_obj_microns): cubeviz_helper.load_data(image_cube_hdu_obj_microns, data_label="test") flux_unit = u.Unit("1E-17 erg*s^-1*cm^-2*Angstrom^-1*pix^-2") # actually a sb - solid_angle_unit = u.pix * u.pix + solid_angle_unit = PIX2 aper = RectanglePixelRegion(center=PixCoord(x=1, y=2), width=3, height=5) cubeviz_helper.plugins['Subset Tools']._obj.import_region(aper) @@ -99,7 +101,7 @@ def test_cubeviz_aperphot_cube_orig_flux(cubeviz_helper, image_cube_hdu_obj_micr def test_cubeviz_aperphot_generated_3d_gaussian_smooth(cubeviz_helper, image_cube_hdu_obj_microns): cubeviz_helper.load_data(image_cube_hdu_obj_microns, data_label="test") flux_unit = u.Unit("1E-17 erg*s^-1*cm^-2*Angstrom^-1*pix^-2") # actually a sb - solid_angle_unit = u.pix * u.pix + solid_angle_unit = PIX2 gauss_plg = cubeviz_helper.plugins["Gaussian Smooth"]._obj gauss_plg.mode_selected = "Spatial" @@ -133,7 +135,7 @@ def test_cubeviz_aperphot_generated_3d_gaussian_smooth(cubeviz_helper, image_cub assert_quantity_allclose(row["slice_wave"], 4.894499866699333 * u.um) -@pytest.mark.parametrize("cube_unit", [u.MJy / u.sr, u.MJy, u.MJy / (u.pix*u.pix)]) +@pytest.mark.parametrize("cube_unit", [u.MJy / u.sr, u.MJy, u.MJy / PIX2]) def test_cubeviz_aperphot_cube_sr_and_pix2(cubeviz_helper, spectrum1d_cube_custom_fluxunit, cube_unit): @@ -173,7 +175,7 @@ def test_cubeviz_aperphot_cube_sr_and_pix2(cubeviz_helper, # so we can directly compare. this shouldn't be populated automatically, # which is checked above plg.flux_scaling = 0.003631 - solid_angle_unit = u.pix * u.pix + solid_angle_unit = PIX2 cube_unit = u.MJy / solid_angle_unit # cube unit in app is now per pix2 plg.vue_do_aper_phot() @@ -185,7 +187,7 @@ def test_cubeviz_aperphot_cube_sr_and_pix2(cubeviz_helper, # (15 - 10) MJy/sr x 1 sr, will always be MJy since solid angle is multiplied out assert_allclose(row["sum"], 5.0 * u.MJy) - assert_allclose(row["sum_aper_area"], 1 * (u.pix * u.pix)) + assert_allclose(row["sum_aper_area"], 1 * PIX2) # we forced area to be one sr so MJy / sr and MJy / pix2 gave the same result assert_allclose(row["pixarea_tot"], 1.0 * solid_angle_unit) @@ -226,7 +228,7 @@ def test_cubeviz_aperphot_cube_orig_flux_mjysr(cubeviz_helper, spectrum1d_cube_c assert_allclose(row["xcenter"], 3 * u.pix) assert_allclose(row["ycenter"], 1 * u.pix) assert_allclose(row["sum"], 1.1752215e-12 * u.MJy) # (15 - 10) MJy/sr x 2.3504431e-13 sr - assert_allclose(row["sum_aper_area"], 1 * (u.pix * u.pix)) + assert_allclose(row["sum_aper_area"], 1 * PIX2) assert_allclose(row["pixarea_tot"], 2.350443053909789e-13 * u.sr) assert_allclose(row["aperture_sum_mag"], 23.72476627732448 * u.mag) assert_allclose(row["mean"], 5 * (u.MJy / u.sr)) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py new file mode 100644 index 0000000000..289cd85541 --- /dev/null +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -0,0 +1,263 @@ +import numpy as np +import pytest +from astropy import units as u +from astropy.wcs import WCS +from regions import PixCoord, CirclePixelRegion +from specutils import Spectrum1D + +from jdaviz.core.custom_units import PIX2 +from jdaviz.core.validunits import locally_defined_flux_units + + +def cubeviz_wcs_dict(): + # returns a WCS obj and dictionary used for cubeviz tests + wcs_dict = {"CTYPE1": "WAVE-LOG", "CTYPE2": "DEC--TAN", "CTYPE3": "RA---TAN", + "CRVAL1": 4.622e-7, "CRVAL2": 27, "CRVAL3": 205, + "CDELT1": 8e-11, "CDELT2": 0.0001, "CDELT3": -0.0001, + "CRPIX1": 0, "CRPIX2": 0, "CRPIX3": 0, "PIXAR_SR": 8e-11} + w = WCS(wcs_dict) + return w, wcs_dict + + +@pytest.mark.skip(reason="Unskip after JDAT 4785 resolved.") +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) +def test_basic_unit_conversions(cubeviz_helper, angle_unit): + """ + Basic test for changing flux units for a cube loaded in Jy to + all available flux units. Checks that the the conversion does + not produce any tracebacks. Tests conversions between all units + in : + ['Jy', 'mJy', 'uJy', 'MJy', 'W / (Hz m2)', 'eV / (Hz s m2)', + 'erg / (Hz s cm2)', 'erg / (Angstrom s cm2)', + 'ph / (Angstrom s cm2)', 'ph / (Hz s cm2)'] + + Parametrized over both available solid angle units (pix2 and sr). + """ + + # load cube with flux units of MJy + w, wcs_dict = cubeviz_wcs_dict() + flux = np.zeros((30, 20, 3001), dtype=np.float32) + cube = Spectrum1D(flux=flux * u.MJy / angle_unit, wcs=w, meta=wcs_dict) + cubeviz_helper.load_data(cube, data_label="test") + + # get all available flux units for translation. Since cube is loaded + # in Jy, this will be all items in 'locally_defined_flux_units' + + all_flux_units = locally_defined_flux_units() + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + + for flux_unit in all_flux_units: + uc_plg.flux_unit = flux_unit + + +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) +def test_unit_translation(cubeviz_helper, angle_unit): + # custom cube so PIXAR_SR is in metadata, and Flux units, and in MJy + w, wcs_dict = cubeviz_wcs_dict() + flux = np.zeros((30, 20, 3001), dtype=np.float32) + flux[5:15, 1:11, :] = 1 + cube = Spectrum1D(flux=flux * u.MJy / angle_unit, wcs=w, meta=wcs_dict) + cubeviz_helper.load_data(cube, data_label="test") + + center = PixCoord(5, 10) + cubeviz_helper.load_regions(CirclePixelRegion(center, radius=2.5)) + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + + # test that the scale factor was set + assert np.all(cubeviz_helper.app.data_collection['Spectrum (sum)'].meta['_pixel_scale_factor'] != 1) # noqa + + # When the dropdown is displayed, this ensures the loaded + # data collection item units will be used for translations. + assert uc_plg._obj.spectral_y_type_selected == 'Flux' + + # accessing from get_data(use_display_units=True) should return flux-like units + assert cubeviz_helper.app._get_display_unit('spectral_y') == u.MJy + assert cubeviz_helper.get_data('Spectrum (sum)', use_display_units=True).unit == u.MJy + + # to have access to display units + viewer_1d = cubeviz_helper.app.get_viewer( + cubeviz_helper._default_spectrum_viewer_reference_name) + + # change global y-units from Flux -> Surface Brightness + uc_plg._obj.spectral_y_type_selected = 'Surface Brightness' + + assert uc_plg._obj.spectral_y_type_selected == 'Surface Brightness' + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + + # check if units translated + assert y_display_unit == u.MJy / angle_unit + + # get_data(use_display_units=True) should return surface brightness-like units + assert cubeviz_helper.app._get_display_unit('spectral_y') == u.MJy / angle_unit + assert cubeviz_helper.get_data('Spectrum (sum)', use_display_units=True).unit == u.MJy / angle_unit # noqa + + +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) +def test_sb_unit_conversion(cubeviz_helper, angle_unit): + + angle_str = angle_unit.to_string() + + # custom cube to have Surface Brightness units + w, wcs_dict = cubeviz_wcs_dict() + flux = np.zeros((30, 20, 3001), dtype=np.float32) + flux[5:15, 1:11, :] = 1 + cube = Spectrum1D(flux=flux * (u.MJy / angle_unit), wcs=w, meta=wcs_dict) + cubeviz_helper.load_data(cube, data_label="test") + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + uc_plg.open_in_tray() + + # ensure that per solid angle cube defaults to Flux spectrum + assert uc_plg.spectral_y_type == 'Flux' + # flux choices is populated with flux units + assert uc_plg.flux_unit.choices + # and angle choices should be the only the input angle + assert len(uc_plg.angle_unit.choices) == 1 + assert angle_str in uc_plg.angle_unit.choices + + # to have access to display units + viewer_1d = cubeviz_helper.app.get_viewer( + cubeviz_helper._default_spectrum_viewer_reference_name) + + uc_plg.spectral_y_type.selected = 'Surface Brightness' + + # Surface Brightness conversion + uc_plg.flux_unit = 'Jy' + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + assert y_display_unit == u.Jy / angle_unit + label_mouseover = cubeviz_helper.app.session.application._tools["g-coords-info"] + flux_viewer = cubeviz_helper.app.get_viewer( + cubeviz_helper._default_flux_viewer_reference_name + ) + label_mouseover._viewer_mouse_event( + flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} + ) + assert label_mouseover.as_text() == ( + f"Pixel x=00010.0 y=00008.0 Value +1.00000e+06 Jy / {angle_str}", + "World 13h39m59.7037s +27d00m03.2400s (ICRS)", + "204.9987654313 27.0008999946 (deg)") + + # Try a second conversion + uc_plg.flux_unit = 'W / Hz m2' + + if angle_unit == PIX2: # unit string order is different for pix2 vs sr + str_unit = 'W / (Hz m2 pix2)' + elif angle_unit == u.sr: + str_unit = 'W / (Hz sr m2)' + + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + assert y_display_unit == u.Unit(str_unit) + + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + label_mouseover._viewer_mouse_event( + flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} + ) + + assert label_mouseover.as_text() == ( + f"Pixel x=00010.0 y=00008.0 Value +1.00000e-20 {str_unit}", + "World 13h39m59.7037s +27d00m03.2400s (ICRS)", + "204.9987654313 27.0008999946 (deg)") + + # really a translation test, test_unit_translation loads a Flux + # cube, this test load a Surface Brightness Cube, this ensures + # two-way translation + uc_plg.flux_unit = 'MJy' + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + label_mouseover._viewer_mouse_event( + flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} + ) + assert label_mouseover.as_text() == ( + f"Pixel x=00010.0 y=00008.0 Value +1.00000e+00 MJy / {angle_str}", + "World 13h39m59.7037s +27d00m03.2400s (ICRS)", + "204.9987654313 27.0008999946 (deg)") + + uc_plg._obj.spectral_y_type_selected = 'Flux' + uc_plg.flux_unit = 'Jy' + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + + assert y_display_unit == u.Jy + + la = cubeviz_helper.plugins['Line Analysis']._obj + assert la.dataset.get_selected_spectrum(use_display_units=True) + + +def test_contour_unit_conversion(cubeviz_helper, spectrum1d_cube_fluxunit_jy_per_steradian): + # custom cube to have Surface Brightness units + cubeviz_helper.load_data(spectrum1d_cube_fluxunit_jy_per_steradian, data_label="test") + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + uc_plg.open_in_tray() + + po_plg = cubeviz_helper.plugins['Plot Options'] + # Make sure that the contour values get updated + po_plg.contour_visible = True + + assert uc_plg.spectral_y_type == 'Flux' + assert uc_plg.flux_unit == 'Jy' + assert uc_plg.sb_unit == "Jy / sr" + assert cubeviz_helper.viewers['flux-viewer']._obj.layers[0].state.attribute_display_unit == "Jy / sr" # noqa + assert np.allclose(po_plg.contour_max.value, 199) + + uc_plg.spectral_y_type = 'Surface Brightness' + uc_plg.flux_unit = 'MJy' + + assert uc_plg.sb_unit == "MJy / sr" + assert cubeviz_helper.viewers['flux-viewer']._obj.layers[0].state.attribute_display_unit == "MJy / sr" # noqa + assert np.allclose(po_plg.contour_max.value, 1.99e-4) + + +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) +def test_cubeviz_flux_sb_translation_counts(cubeviz_helper, angle_unit): + + """ + When a cube is loaded in counts, 'count' should be the only + available option for flux unit. The y axis can be translated + between flux and sb. Test a flux cube which will be converted + to ct/pix2, and a sb cube ct/sr. + """ + + angle_str = angle_unit.to_string() + + # custom cube to have Surface Brightness units + w, wcs_dict = cubeviz_wcs_dict() + flux = np.zeros((30, 20, 3001), dtype=np.float32) + flux[5:15, 1:11, :] = 1 + cube = Spectrum1D(flux=flux * (u.ct / angle_unit), wcs=w, meta=wcs_dict) + cubeviz_helper.load_data(cube, data_label="test") + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + uc_plg.open_in_tray() + + # ensure that per solid angle cube defaults to Flux spectrum + assert uc_plg.spectral_y_type == 'Flux' + # flux choices is populated with only one choice, counts + assert len(uc_plg.flux_unit.choices) == 1 + assert 'ct' in uc_plg.flux_unit.choices + # and angle choices should be the only the input angle + assert len(uc_plg.angle_unit.choices) == 1 + assert angle_str in uc_plg.angle_unit.choices + + # to have access to display units + viewer_1d = cubeviz_helper.app.get_viewer( + cubeviz_helper._default_spectrum_viewer_reference_name) + + # do a spectral y axis translation from Flux to Surface Brightness + uc_plg.spectral_y_type.selected = 'Surface Brightness' + + y_display_unit = u.Unit(viewer_1d.state.y_display_unit) + assert y_display_unit == u.ct / angle_unit + + # and test mouseover info + label_mouseover = cubeviz_helper.app.session.application._tools["g-coords-info"] + flux_viewer = cubeviz_helper.app.get_viewer( + cubeviz_helper._default_flux_viewer_reference_name + ) + label_mouseover._viewer_mouse_event( + flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} + ) + assert label_mouseover.as_text() == ( + f"Pixel x=00010.0 y=00008.0 Value +1.00000e+00 ct / {angle_str}", + "World 13h39m59.7037s +27d00m03.2400s (ICRS)", + "204.9987654313 27.0008999946 (deg)") diff --git a/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py index 69ed0251a5..c0aa37370b 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py @@ -14,6 +14,7 @@ from jdaviz.configs.default.plugins.model_fitting import fitting_backend as fb from jdaviz.configs.default.plugins.model_fitting import initializers +from jdaviz.core.custom_units import PIX2 SPECTRUM_SIZE = 200 # length of spectrum @@ -79,7 +80,7 @@ def test_model_ids(cubeviz_helper, spectral_cube_wcs): def test_parameter_retrieval(cubeviz_helper, spectral_cube_wcs): flux_unit = u.nJy - sb_unit = flux_unit / (u.pix * u.pix) + sb_unit = flux_unit / PIX2 wav_unit = u.Hz flux = np.ones((3, 4, 5)) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index fc2636762c..451dcf834d 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -15,6 +15,7 @@ from traitlets import Any, Bool, Integer, List, Unicode, observe from jdaviz.core.custom_traitlets import FloatHandleEmpty +from jdaviz.core.custom_units import PIX2 from jdaviz.core.events import (GlobalDisplayUnitChanged, SnackbarMessage, LinkUpdatedMessage, SliceValueUpdatedMessage) from jdaviz.core.region_translators import regions2aperture, _get_region_from_spatial_subset @@ -27,8 +28,6 @@ __all__ = ['SimpleAperturePhotometry'] -PIX2 = u.pix * u.pix # define square pixel unit which is used around the plugin - @tray_registry('imviz-aper-phot-simple', label="Aperture Photometry") class SimpleAperturePhotometry(PluginTemplateMixin, ApertureSubsetSelectMixin, diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index 8ab0874571..8a22c57b8c 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -13,6 +13,7 @@ MosvizProfile2DView) from jdaviz.configs.rampviz.plugins.viewers import RampvizImageView, RampvizProfileView from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView +from jdaviz.core.custom_units import PIX2 from jdaviz.core.events import ViewerAddedMessage, GlobalDisplayUnitChanged from jdaviz.core.helpers import data_has_valid_wcs from jdaviz.core.marks import PluginScatter, PluginLine @@ -491,8 +492,8 @@ def _image_viewer_update(self, viewer, x, y): # If unit is flux per pix2, the type will be 'unknown' rather # than surface brightness, so have to multiply the pix2 part out # and check if the numerator is a spectral flux density - if check_if_unit_is_per_solid_angle(unit, return_unit=True) == u.pix*u.pix: - un = u.Unit(unit) * u.pix*u.pix + if check_if_unit_is_per_solid_angle(unit, return_unit=True) == PIX2: + un = u.Unit(unit) * PIX2 physical_type = un.physical_type else: physical_type = u.Unit(unit).physical_type diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 51618d934e..d84a24b8e3 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -16,6 +16,7 @@ from jdaviz.configs.imviz.helper import split_filename_with_fits_ext from jdaviz.configs.imviz.plugins.parsers import ( parse_data, _validate_fits_image2d, _validate_bunit, _parse_image, HAS_ROMAN_DATAMODELS) +from jdaviz.core.custom_units import PIX2 @pytest.mark.parametrize( @@ -287,7 +288,7 @@ def test_parse_jwst_nircam_level2(self, imviz_helper): data_unit = u.MJy / u.sr assert_quantity_allclose(tbl[0]['background'], 0.1741226315498352 * data_unit) assert_quantity_allclose(tbl[0]['sum'], 4.486487e-11 * u.MJy, rtol=1e-6) - assert_quantity_allclose(tbl[0]['sum_aper_area'], 111.220234 * (u.pix * u.pix)) + assert_quantity_allclose(tbl[0]['sum_aper_area'], 111.220234 * PIX2) assert_quantity_allclose(tbl[0]['pixarea_tot'], 9.33677e-14 * u.sr, rtol=1e-6) assert_quantity_allclose(tbl[0]['aperture_sum_counts'], 132061.576643 * u.count, rtol=1e-6) assert_quantity_allclose(tbl[0]['aperture_sum_counts_err'], 363.402775 * u.count) @@ -413,7 +414,7 @@ def test_parse_hst_drz(self, imviz_helper): data_unit = u.electron / u.s assert_quantity_allclose(tbl[0]['background'], 0.0014 * data_unit) assert_quantity_allclose(tbl[0]['sum'], 115.944737 * data_unit, rtol=1e-3) - assert_quantity_allclose(tbl[0]['sum_aper_area'], 2583.959958 * (u.pix * u.pix), rtol=1e-3) + assert_quantity_allclose(tbl[0]['sum_aper_area'], 2583.959958 * PIX2, rtol=1e-3) assert_array_equal(tbl[0]['pixarea_tot'], None) assert_array_equal(tbl[0]['aperture_sum_counts'], None) assert_array_equal(tbl[0]['aperture_sum_counts_err'], None) diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index aeabae08d6..685012264a 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -13,6 +13,7 @@ from jdaviz.configs.imviz.plugins.aper_phot_simple.aper_phot_simple import ( _curve_of_growth, _radial_profile) from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_WCS, BaseImviz_WCS_NoWCS +from jdaviz.core.custom_units import PIX2 from jdaviz.tests.test_utils import PHOTUTILS_LT_1_12_1 @@ -79,7 +80,7 @@ def test_plugin_wcs_dithered(self): 'data_label', 'subset_label', 'timestamp'] assert_array_equal(tbl['id'], [1, 2]) assert_allclose(tbl['background'], 0) - assert_quantity_allclose(tbl['sum_aper_area'], [63.617251, 62.22684693104279] * (u.pix * u.pix), rtol=1e-4) # noqa + assert_quantity_allclose(tbl['sum_aper_area'], [63.617251, 62.22684693104279] * PIX2, rtol=1e-4) # noqa assert_array_equal(tbl['pixarea_tot'], None) assert_array_equal(tbl['aperture_sum_counts'], None) assert_array_equal(tbl['aperture_sum_counts_err'], None) @@ -131,7 +132,7 @@ def test_plugin_wcs_dithered(self): sky = tbl[-1]['sky_center'] assert_allclose(sky.ra.deg, 337.51894336144454, rtol=1e-4) assert_allclose(sky.dec.deg, -20.832777499255897, rtol=1e-4) - assert_quantity_allclose(tbl[-1]['sum_aper_area'], 28.274334 * (u.pix * u.pix), rtol=1e-4) + assert_quantity_allclose(tbl[-1]['sum_aper_area'], 28.274334 * PIX2, rtol=1e-4) assert_allclose(tbl[-1]['sum'], 28.274334, rtol=1e-4) assert_allclose(tbl[-1]['mean'], 1, rtol=1e-4) assert tbl[-1]['data_label'] == 'has_wcs_1[SCI,1]' @@ -156,7 +157,7 @@ def test_plugin_wcs_dithered(self): sky = tbl[-1]['sky_center'] assert_allclose(sky.ra.deg, 337.51894336144454, rtol=1e-4) assert_allclose(sky.dec.deg, -20.832083, rtol=1e-4) - assert_quantity_allclose(tbl[-1]['sum_aper_area'], 81 * (u.pix * u.pix)) + assert_quantity_allclose(tbl[-1]['sum_aper_area'], 81 * PIX2) assert_allclose(tbl[-1]['sum'], 0) assert_allclose(tbl[-1]['mean'], 0) assert tbl[-1]['data_label'] == 'has_wcs_1[SCI,1]' diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py index 7643709b82..8f54ea8afe 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py @@ -8,6 +8,7 @@ from specutils import Spectrum1D, SpectralRegion from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit +from jdaviz.core.custom_units import PIX2 from jdaviz.core.events import LineIdentifyMessage from jdaviz.core.marks import LineAnalysisContinuum @@ -82,7 +83,7 @@ def test_spatial_subset(cubeviz_helper, image_cube_hdu_obj): assert u.Unit(result['unit']) != u.dimensionless_unscaled -@pytest.mark.parametrize('sq_angle_unit', [u.sr, u.pix*u.pix]) +@pytest.mark.parametrize('sq_angle_unit', [u.sr, PIX2]) def test_cubeviz_units(cubeviz_helper, spectrum1d_cube_custom_fluxunit, sq_angle_unit): """ diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py index 0ace7db172..faefecd885 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py @@ -2,8 +2,6 @@ import pytest from astropy import units as u from astropy.nddata import InverseVariance -from astropy.wcs import WCS -from regions import PixCoord, CirclePixelRegion from specutils import Spectrum1D @@ -135,226 +133,3 @@ def test_non_stddev_uncertainty(specviz_helper): np.abs(viewer.figure.marks[-1].y - viewer.figure.marks[-1].y.mean(0)), stddev ) - - -def cubeviz_wcs_dict(): - # returns a WCS obj and dictionary used for cubeviz tests here - wcs_dict = {"CTYPE1": "WAVE-LOG", "CTYPE2": "DEC--TAN", "CTYPE3": "RA---TAN", - "CRVAL1": 4.622e-7, "CRVAL2": 27, "CRVAL3": 205, - "CDELT1": 8e-11, "CDELT2": 0.0001, "CDELT3": -0.0001, - "CRPIX1": 0, "CRPIX2": 0, "CRPIX3": 0, "PIXAR_SR": 8e-11} - w = WCS(wcs_dict) - return w, wcs_dict - - -@pytest.mark.parametrize("angle_unit", [u.sr, u.pix*u.pix]) -def test_unit_translation(cubeviz_helper, angle_unit): - # custom cube so PIXAR_SR is in metadata, and Flux units, and in MJy - w, wcs_dict = cubeviz_wcs_dict() - flux = np.zeros((30, 20, 3001), dtype=np.float32) - flux[5:15, 1:11, :] = 1 - cube = Spectrum1D(flux=flux * u.MJy / angle_unit, wcs=w, meta=wcs_dict) - cubeviz_helper.load_data(cube, data_label="test") - - center = PixCoord(5, 10) - cubeviz_helper.plugins['Subset Tools']._obj.import_region( - CirclePixelRegion(center, radius=2.5)) - - uc_plg = cubeviz_helper.plugins['Unit Conversion'] - - # test that the scale factor was set - assert np.all(cubeviz_helper.app.data_collection['Spectrum (sum)'].meta['_pixel_scale_factor'] != 1) # noqa - - # When the dropdown is displayed, this ensures the loaded - # data collection item units will be used for translations. - assert uc_plg._obj.spectral_y_type_selected == 'Flux' - - # accessing from get_data(use_display_units=True) should return flux-like units - assert cubeviz_helper.app._get_display_unit('spectral_y') == u.MJy - assert cubeviz_helper.get_data('Spectrum (sum)', use_display_units=True).unit == u.MJy - - # to have access to display units - viewer_1d = cubeviz_helper.app.get_viewer( - cubeviz_helper._default_spectrum_viewer_reference_name) - - # change global y-units from Flux -> Surface Brightness - uc_plg._obj.spectral_y_type_selected = 'Surface Brightness' - - assert uc_plg._obj.spectral_y_type_selected == 'Surface Brightness' - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - - # check if units translated - assert y_display_unit == u.MJy / angle_unit - - # get_data(use_display_units=True) should return surface brightness-like units - assert cubeviz_helper.app._get_display_unit('spectral_y') == u.MJy / angle_unit - assert cubeviz_helper.get_data('Spectrum (sum)', use_display_units=True).unit == u.MJy / angle_unit # noqa - - -@pytest.mark.parametrize("angle_unit", [u.sr, u.pix*u.pix]) -def test_sb_unit_conversion(cubeviz_helper, angle_unit): - - angle_str = angle_unit.to_string() - - # custom cube to have Surface Brightness units - w, wcs_dict = cubeviz_wcs_dict() - flux = np.zeros((30, 20, 3001), dtype=np.float32) - flux[5:15, 1:11, :] = 1 - cube = Spectrum1D(flux=flux * (u.MJy / angle_unit), wcs=w, meta=wcs_dict) - cubeviz_helper.load_data(cube, data_label="test") - - uc_plg = cubeviz_helper.plugins['Unit Conversion'] - uc_plg.open_in_tray() - - # ensure that per solid angle cube defaults to Flux spectrum - assert uc_plg.spectral_y_type == 'Flux' - # flux choices is populated with flux units - assert uc_plg.flux_unit.choices - # and angle choices should be the only the input angle - assert len(uc_plg.angle_unit.choices) == 1 - assert angle_str in uc_plg.angle_unit.choices - - # to have access to display units - viewer_1d = cubeviz_helper.app.get_viewer( - cubeviz_helper._default_spectrum_viewer_reference_name) - - uc_plg.spectral_y_type.selected = 'Surface Brightness' - - # Surface Brightness conversion - uc_plg.flux_unit = 'Jy' - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - assert y_display_unit == u.Jy / angle_unit - label_mouseover = cubeviz_helper.app.session.application._tools["g-coords-info"] - flux_viewer = cubeviz_helper.app.get_viewer( - cubeviz_helper._default_flux_viewer_reference_name - ) - label_mouseover._viewer_mouse_event( - flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} - ) - assert label_mouseover.as_text() == ( - f"Pixel x=00010.0 y=00008.0 Value +1.00000e+06 Jy / {angle_str}", - "World 13h39m59.7037s +27d00m03.2400s (ICRS)", - "204.9987654313 27.0008999946 (deg)") - - # Try a second conversion - uc_plg.flux_unit = 'W / Hz m2' - - if angle_unit == u.pix * u.pix: # unit string order is different for pix2 vs sr - str_unit = 'W / (Hz m2 pix2)' - elif angle_unit == u.sr: - str_unit = 'W / (Hz sr m2)' - - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - assert y_display_unit == u.Unit(str_unit) - - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - label_mouseover._viewer_mouse_event( - flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} - ) - - assert label_mouseover.as_text() == ( - f"Pixel x=00010.0 y=00008.0 Value +1.00000e-20 {str_unit}", - "World 13h39m59.7037s +27d00m03.2400s (ICRS)", - "204.9987654313 27.0008999946 (deg)") - - # really a translation test, test_unit_translation loads a Flux - # cube, this test load a Surface Brightness Cube, this ensures - # two-way translation - uc_plg.flux_unit = 'MJy' - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - label_mouseover._viewer_mouse_event( - flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} - ) - assert label_mouseover.as_text() == ( - f"Pixel x=00010.0 y=00008.0 Value +1.00000e+00 MJy / {angle_str}", - "World 13h39m59.7037s +27d00m03.2400s (ICRS)", - "204.9987654313 27.0008999946 (deg)") - - uc_plg._obj.spectral_y_type_selected = 'Flux' - uc_plg.flux_unit = 'Jy' - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - - assert y_display_unit == u.Jy - - la = cubeviz_helper.plugins['Line Analysis']._obj - assert la.dataset.get_selected_spectrum(use_display_units=True) - - -def test_contour_unit_conversion(cubeviz_helper, spectrum1d_cube_fluxunit_jy_per_steradian): - # custom cube to have Surface Brightness units - cubeviz_helper.load_data(spectrum1d_cube_fluxunit_jy_per_steradian, data_label="test") - - uc_plg = cubeviz_helper.plugins['Unit Conversion'] - uc_plg.open_in_tray() - - po_plg = cubeviz_helper.plugins['Plot Options'] - # Make sure that the contour values get updated - po_plg.contour_visible = True - - assert uc_plg.spectral_y_type == 'Flux' - assert uc_plg.flux_unit == 'Jy' - assert uc_plg.sb_unit == "Jy / sr" - assert cubeviz_helper.viewers['flux-viewer']._obj.layers[0].state.attribute_display_unit == "Jy / sr" # noqa - assert np.allclose(po_plg.contour_max.value, 199) - - uc_plg.spectral_y_type = 'Surface Brightness' - uc_plg.flux_unit = 'MJy' - - assert uc_plg.sb_unit == "MJy / sr" - assert cubeviz_helper.viewers['flux-viewer']._obj.layers[0].state.attribute_display_unit == "MJy / sr" # noqa - assert np.allclose(po_plg.contour_max.value, 1.99e-4) - - -@pytest.mark.parametrize("angle_unit", [u.sr, u.pix*u.pix]) -def test_cubeviz_flux_sb_translation_counts(cubeviz_helper, angle_unit): - - """ - When a cube is loaded in counts, 'count' should be the only - available option for flux unit. The y axis can be translated - between flux and sb. Test a flux cube which will be converted - to ct/pix2, and a sb cube ct/sr. - """ - - angle_str = angle_unit.to_string() - - # custom cube to have Surface Brightness units - w, wcs_dict = cubeviz_wcs_dict() - flux = np.zeros((30, 20, 3001), dtype=np.float32) - flux[5:15, 1:11, :] = 1 - cube = Spectrum1D(flux=flux * (u.ct / angle_unit), wcs=w, meta=wcs_dict) - cubeviz_helper.load_data(cube, data_label="test") - - uc_plg = cubeviz_helper.plugins['Unit Conversion'] - uc_plg.open_in_tray() - - # ensure that per solid angle cube defaults to Flux spectrum - assert uc_plg.spectral_y_type == 'Flux' - # flux choices is populated with only one choice, counts - assert len(uc_plg.flux_unit.choices) == 1 - assert 'ct' in uc_plg.flux_unit.choices - # and angle choices should be the only the input angle - assert len(uc_plg.angle_unit.choices) == 1 - assert angle_str in uc_plg.angle_unit.choices - - # to have access to display units - viewer_1d = cubeviz_helper.app.get_viewer( - cubeviz_helper._default_spectrum_viewer_reference_name) - - # do a spectral y axis translation from Flux to Surface Brightness - uc_plg.spectral_y_type.selected = 'Surface Brightness' - - y_display_unit = u.Unit(viewer_1d.state.y_display_unit) - assert y_display_unit == u.ct / angle_unit - - # and test mouseover info - label_mouseover = cubeviz_helper.app.session.application._tools["g-coords-info"] - flux_viewer = cubeviz_helper.app.get_viewer( - cubeviz_helper._default_flux_viewer_reference_name - ) - label_mouseover._viewer_mouse_event( - flux_viewer, {"event": "mousemove", "domain": {"x": 10, "y": 8}} - ) - assert label_mouseover.as_text() == ( - f"Pixel x=00010.0 y=00008.0 Value +1.00000e+00 ct / {angle_str}", - "World 13h39m59.7037s +27d00m03.2400s (ICRS)", - "204.9987654313 27.0008999946 (deg)") diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index e7474f14b0..5bc6c71624 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -6,6 +6,7 @@ from traitlets import List, Unicode, observe, Bool from jdaviz.configs.default.plugins.viewers import JdavizProfileView +from jdaviz.core.custom_units import PIX2 from jdaviz.core.events import GlobalDisplayUnitChanged, AddDataMessage from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import (PluginTemplateMixin, UnitSelectPluginComponent, @@ -13,7 +14,8 @@ from jdaviz.core.validunits import (create_spectral_equivalencies_list, create_flux_equivalencies_list, check_if_unit_is_per_solid_angle, - create_angle_equivalencies_list) + create_angle_equivalencies_list, + supported_sq_angle_units) __all__ = ['UnitConversion'] @@ -35,7 +37,7 @@ def _valid_glue_display_unit(unit_str, sv, axis='x'): def _flux_to_sb_unit(flux_unit, angle_unit): - if angle_unit not in ['pix2', 'sr']: + if angle_unit not in supported_sq_angle_units(as_strings=True): sb_unit = flux_unit elif '(' in flux_unit: pos = flux_unit.rfind(')') @@ -228,9 +230,8 @@ def _on_add_data_to_viewer(self, msg): self.flux_unit.selected = '' if not self.angle_unit_selected: - if angle_unit == u.pix**2: + if angle_unit == PIX2: self.angle_unit.choices = ['pix2'] - try: if angle_unit is None: # default to sr if input spectrum is not in surface brightness units diff --git a/jdaviz/core/custom_units.py b/jdaviz/core/custom_units.py new file mode 100644 index 0000000000..1675969961 --- /dev/null +++ b/jdaviz/core/custom_units.py @@ -0,0 +1,4 @@ +import astropy.units as u + +# define custom composite units here +PIX2 = u.pix * u.pix diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 394a81194b..39d30bb4dc 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -1,12 +1,47 @@ from astropy import units as u import itertools -__all__ = ['supported_sq_angle_units', 'units_to_strings', 'create_spectral_equivalencies_list', +from jdaviz.core.custom_units import PIX2 + +__all__ = ['supported_sq_angle_units', 'locally_defined_flux_units', + 'combine_flux_and_angle_units', 'units_to_strings', + 'create_spectral_equivalencies_list', 'create_flux_equivalencies_list', 'check_if_unit_is_per_solid_angle'] -def supported_sq_angle_units(): - return [u.pix*u.pix, u.sr] +def supported_sq_angle_units(as_strings=False): + units = [PIX2, u.sr] + if as_strings: + units = units_to_strings(units) + return units + + +def locally_defined_flux_units(): + """ + This function returns a list of string representations of flux units. This + list represents flux units that the unit conversion plugin supports + conversion to and from if the input data unit is compatible with items in the + list (i.e is equivalent directly or with u.spectral_density(cube_wave)). + + """ + flux_units = ['Jy', 'mJy', 'uJy', 'MJy', 'W / (Hz m2)', 'eV / (Hz s m2)', + 'erg / (Hz s cm2)', 'erg / (Angstrom s cm2)', + 'ph / (Angstrom s cm2)', 'ph / (Hz s cm2)'] + return flux_units + + +def combine_flux_and_angle_units(flux_units, angle_units): + """ + Combine (list of) flux_units and angle_units to create a list of string + representations of surface brightness units. The returned strings will be in + the same format as the astropy unit to_string() of the unit, for consistency. + """ + if not isinstance(flux_units, list): + flux_units = [flux_units] + if not isinstance(angle_units, list): + angle_units = [angle_units] + + return [(u.Unit(flux) / u.Unit(angle)).to_string() for flux in flux_units for angle in angle_units] # noqa def units_to_strings(unit_list): @@ -76,15 +111,7 @@ def create_flux_equivalencies_list(flux_unit, spectral_axis_unit): curr_flux_unit_equivalencies = [unit for unit in curr_flux_unit_equivalencies if not any(mag in unit.name for mag in mag_units)] # noqa # 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)', - ] - local_units = [u.Unit(unit) for unit in locally_defined_flux_units] + local_units = [u.Unit(unit) for unit in locally_defined_flux_units()] # Remove overlap units. curr_flux_unit_equivalencies = list(set(curr_flux_unit_equivalencies) @@ -196,7 +223,7 @@ def check_if_unit_is_per_solid_angle(unit, return_unit=False): return new_unit return True # area units present but not requested to be returned # square pixel should be considered a square angle unit - if new_unit == u.pix * u.pix: + if new_unit == PIX2: if return_unit: return new_unit return True diff --git a/jdaviz/tests/test_utils.py b/jdaviz/tests/test_utils.py index a099854b22..7e6d914124 100644 --- a/jdaviz/tests/test_utils.py +++ b/jdaviz/tests/test_utils.py @@ -10,13 +10,12 @@ from numpy.testing import assert_allclose from specutils import Spectrum1D +from jdaviz.core.custom_units import PIX2 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") -PIX2 = u.pix * u.pix - def test_spec_sb_flux_conversion(): # Actual spectrum content does not matter, just the meta is used here. @@ -53,7 +52,7 @@ def test_spec_sb_flux_conversion(): # 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)) - for solid_angle in [u.sr, u.pix*u.pix]: + for solid_angle in [u.sr, PIX2]: 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 * solid_angle)), # noqa diff --git a/jdaviz/utils.py b/jdaviz/utils.py index fb0997872b..f90cf1282a 100644 --- a/jdaviz/utils.py +++ b/jdaviz/utils.py @@ -22,6 +22,7 @@ from glue_astronomy.spectral_coordinates import SpectralCoordinates from ipyvue import watch +from jdaviz.core.custom_units import PIX2 from jdaviz.core.validunits import check_if_unit_is_per_solid_angle __all__ = ['SnackbarQueue', 'enable_hot_reloading', 'bqplot_clear_figure', @@ -451,15 +452,15 @@ def flux_conversion(values, original_units, target_units, spec=None, eqv=None, s values=values, orig_units=orig_units, targ_units=targ_units, eqv=eqv, image_data=image_data ) - elif solid_angle_in_orig == solid_angle_in_targ == u.pix * u.pix: + elif solid_angle_in_orig == solid_angle_in_targ == PIX2: # in the case where we have 2 SBs per solid pixel that need # u.spectral_density equivalency, they can't be directly converted # for whatever reason (i.e 'Jy / pix2' and 'erg / (Angstrom s cm2 pix2)' # are not convertible). In this case, multiply out the factor of pix2 for # conversion (same kind of thing _indirect_conversion is # doing but we already know the exact angle units. - orig_units *= u.pix * u.pix - targ_units *= u.pix * u.pix + orig_units *= PIX2 + targ_units *= PIX2 return (values * orig_units).to_value(targ_units, equivalencies=eqv) @@ -543,14 +544,12 @@ def _eqv_flux_to_sb_pixel(): e.g MJy <> MJy / pix2 """ - pix2 = u.pix * u.pix - # generate an equivalency for each flux type that would need # another equivalency for converting to/from flux_units = [u.MJy, u.erg / (u.s * u.cm**2 * u.Angstrom), u.ph / (u.Angstrom * u.s * u.cm**2), u.ph / (u.Hz * u.s * u.cm**2)] - return [(flux_unit, flux_unit / pix2, lambda x: x, lambda x: x) + return [(flux_unit, flux_unit / PIX2, lambda x: x, lambda x: x) for flux_unit in flux_units] @@ -569,11 +568,10 @@ def _eqv_sb_per_pixel_to_per_angle(flux_unit, scale_factor=1): (one solution being creating this equivalency for each equivalent flux-type.) """ - pix2 = u.pix * u.pix # the two types of units we want to define a conversion between flux_solid_ang = flux_unit / u.sr - flux_sq_pix = flux_unit / pix2 + flux_sq_pix = flux_unit / PIX2 pix_to_solid_angle_equiv = [(flux_solid_ang, flux_sq_pix, lambda x: x * scale_factor,