From 127d29528fe17bb08b48bb454221ec989ecd5a61 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 26 Sep 2024 14:51:27 -0400 Subject: [PATCH 01/17] defined pix2 unit --- jdaviz/configs/cubeviz/plugins/parsers.py | 9 +++++---- .../cubeviz/plugins/tests/test_cubeviz_aperphot.py | 14 ++++++++------ .../plugins/model_fitting/tests/test_fitting.py | 3 ++- .../plugins/aper_phot_simple/aper_phot_simple.py | 3 +-- .../imviz/plugins/coords_info/coords_info.py | 5 +++-- jdaviz/configs/imviz/tests/test_parser.py | 6 ++++-- .../configs/imviz/tests/test_simple_aper_phot.py | 7 ++++--- .../line_analysis/tests/test_line_analysis.py | 3 ++- .../unit_conversion/tests/test_unit_conversion.py | 10 ++++++---- .../plugins/unit_conversion/unit_conversion.py | 4 ++-- jdaviz/core/custom_units.py | 4 ++++ jdaviz/core/validunits.py | 6 ++++-- jdaviz/tests/test_utils.py | 4 ++-- jdaviz/utils.py | 14 ++++++-------- 14 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 jdaviz/core/custom_units.py diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py index 40b0236c2f..21f8f45aa0 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 * 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 43b170c3e6..2be1942e5a 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 * + 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.load_regions(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/default/plugins/model_fitting/tests/test_fitting.py b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py index 69ed0251a5..74555c3503 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 * 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..be4fff05a4 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 * 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..e54d54ad04 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 * 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..5d6f17eaf0 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -16,6 +16,8 @@ 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 * + @pytest.mark.parametrize( @@ -287,7 +289,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 +415,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 2f00530d21..4978714e18 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 * 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) @@ -130,7 +131,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]' @@ -154,7 +155,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 b5e40f0aed..ea798a9117 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 @@ -10,6 +10,7 @@ from specutils import Spectrum1D from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit +from jdaviz.core.custom_units import * from jdaviz.core.events import LineIdentifyMessage from jdaviz.core.marks import LineAnalysisContinuum @@ -79,7 +80,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 51fc06537b..e63d8a3bb8 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 @@ -6,6 +6,8 @@ from regions import PixCoord, CirclePixelRegion from specutils import Spectrum1D +from jdaviz.core.custom_units import * + # On failure, should not crash; essentially a no-op. @pytest.mark.parametrize( @@ -147,7 +149,7 @@ def cubeviz_wcs_dict(): return w, wcs_dict -@pytest.mark.parametrize("angle_unit", [u.sr, u.pix*u.pix]) +@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() @@ -190,7 +192,7 @@ def test_unit_translation(cubeviz_helper, 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]) +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) def test_sb_unit_conversion(cubeviz_helper, angle_unit): angle_str = angle_unit.to_string() @@ -238,7 +240,7 @@ def test_sb_unit_conversion(cubeviz_helper, angle_unit): # 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 + 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)' @@ -304,7 +306,7 @@ def test_contour_unit_conversion(cubeviz_helper, spectrum1d_cube_fluxunit_jy_per assert np.allclose(po_plg.contour_max.value, 1.99e-4) -@pytest.mark.parametrize("angle_unit", [u.sr, u.pix*u.pix]) +@pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) def test_cubeviz_flux_sb_translation_counts(cubeviz_helper, angle_unit): """ diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index e7474f14b0..557e438192 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 * from jdaviz.core.events import GlobalDisplayUnitChanged, AddDataMessage from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import (PluginTemplateMixin, UnitSelectPluginComponent, @@ -228,9 +229,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..f453736ea9 --- /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 \ No newline at end of file diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 394a81194b..de0dbae73d 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -1,12 +1,14 @@ from astropy import units as u import itertools +from jdaviz.core.custom_units import * + __all__ = ['supported_sq_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] + return [PIX2, u.sr] def units_to_strings(unit_list): @@ -196,7 +198,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..2b87a66c55 100644 --- a/jdaviz/tests/test_utils.py +++ b/jdaviz/tests/test_utils.py @@ -10,12 +10,12 @@ from numpy.testing import assert_allclose from specutils import Spectrum1D +from jdaviz.core.custom_units import * 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(): @@ -53,7 +53,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..e19a0fd428 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 * 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, From b8d5a824ab6db984370483b2a3e6319584165c47 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 26 Sep 2024 15:47:21 -0400 Subject: [PATCH 02/17] define supported units in one place --- jdaviz/app.py | 37 +++++--------- .../unit_conversion/unit_conversion.py | 5 +- jdaviz/core/custom_units.py | 2 +- jdaviz/core/validunits.py | 49 ++++++++++++++----- 4 files changed, 52 insertions(+), 41 deletions(-) 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/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index 557e438192..dff224734b 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -14,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'] @@ -36,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(')') diff --git a/jdaviz/core/custom_units.py b/jdaviz/core/custom_units.py index f453736ea9..1675969961 100644 --- a/jdaviz/core/custom_units.py +++ b/jdaviz/core/custom_units.py @@ -1,4 +1,4 @@ import astropy.units as u # define custom composite units here -PIX2 = u.pix * u.pix \ No newline at end of file +PIX2 = u.pix * u.pix diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index de0dbae73d..dec9b86f48 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -3,12 +3,45 @@ from jdaviz.core.custom_units import * -__all__ = ['supported_sq_angle_units', 'units_to_strings', 'create_spectral_equivalencies_list', +__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 [PIX2, 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 that, + combined with the u.spectral_density equivalency for some conversions, + are translatable between one another. These units + can be combined with units in 'supported_sq_angle_units' to form supported + surface brightness units. If data is loaded with a flux unit (or equivalent + flux unit e.g nJy) in this list, conversions between all other units in the + list should be supported. Units like 'counts' and 'e / s' would not + """ + 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): @@ -78,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) From 5945959844b623adb05ae3934d15aaae7b4bac72 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 26 Sep 2024 19:11:35 -0400 Subject: [PATCH 03/17] code style --- jdaviz/configs/cubeviz/plugins/parsers.py | 2 +- .../configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py | 2 +- .../default/plugins/model_fitting/tests/test_fitting.py | 2 +- .../imviz/plugins/aper_phot_simple/aper_phot_simple.py | 2 +- jdaviz/configs/imviz/plugins/coords_info/coords_info.py | 2 +- jdaviz/configs/imviz/tests/test_parser.py | 3 +-- jdaviz/configs/imviz/tests/test_simple_aper_phot.py | 2 +- .../plugins/line_analysis/tests/test_line_analysis.py | 2 +- .../plugins/unit_conversion/tests/test_unit_conversion.py | 2 +- .../specviz/plugins/unit_conversion/unit_conversion.py | 2 +- jdaviz/core/validunits.py | 6 ++++-- jdaviz/tests/test_utils.py | 3 +-- jdaviz/utils.py | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py index 21f8f45aa0..6e8f58c938 100644 --- a/jdaviz/configs/cubeviz/plugins/parsers.py +++ b/jdaviz/configs/cubeviz/plugins/parsers.py @@ -10,7 +10,7 @@ from astropy.wcs import WCS from specutils import Spectrum1D, SpectralAxis -from jdaviz.core.custom_units import * +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 diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py index 2be1942e5a..276e0ff635 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_aperphot.py @@ -7,7 +7,7 @@ from numpy.testing import assert_allclose from regions import RectanglePixelRegion, PixCoord -from jdaviz.core.custom_units import * +from jdaviz.core.custom_units import PIX2 def test_cubeviz_aperphot_cube_orig_flux(cubeviz_helper, image_cube_hdu_obj_microns): 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 74555c3503..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,7 +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 * +from jdaviz.core.custom_units import PIX2 SPECTRUM_SIZE = 200 # length of spectrum 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 be4fff05a4..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,7 +15,7 @@ from traitlets import Any, Bool, Integer, List, Unicode, observe from jdaviz.core.custom_traitlets import FloatHandleEmpty -from jdaviz.core.custom_units import * +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 diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index e54d54ad04..8a22c57b8c 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -13,7 +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 * +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 diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 5d6f17eaf0..d84a24b8e3 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -16,8 +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 * - +from jdaviz.core.custom_units import PIX2 @pytest.mark.parametrize( diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index 4978714e18..43d806c08d 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -13,7 +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 * +from jdaviz.core.custom_units import PIX2 from jdaviz.tests.test_utils import PHOTUTILS_LT_1_12_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 ea798a9117..abd645e1a9 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 @@ -10,7 +10,7 @@ from specutils import Spectrum1D from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit -from jdaviz.core.custom_units import * +from jdaviz.core.custom_units import PIX2 from jdaviz.core.events import LineIdentifyMessage from jdaviz.core.marks import LineAnalysisContinuum 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 e63d8a3bb8..0b274edd4e 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 @@ -6,7 +6,7 @@ from regions import PixCoord, CirclePixelRegion from specutils import Spectrum1D -from jdaviz.core.custom_units import * +from jdaviz.core.custom_units import PIX2 # On failure, should not crash; essentially a no-op. diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index dff224734b..5bc6c71624 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -6,7 +6,7 @@ from traitlets import List, Unicode, observe, Bool from jdaviz.configs.default.plugins.viewers import JdavizProfileView -from jdaviz.core.custom_units import * +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, diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index dec9b86f48..318491308f 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -1,7 +1,7 @@ from astropy import units as u import itertools -from jdaviz.core.custom_units import * +from jdaviz.core.custom_units import PIX2 __all__ = ['supported_sq_angle_units', 'locally_defined_flux_units', 'combine_flux_and_angle_units', 'units_to_strings', @@ -15,6 +15,7 @@ def supported_sq_angle_units(as_strings=False): units = units_to_strings(units) return units + def locally_defined_flux_units(): """ This function returns a list of string representations of flux units that, @@ -23,13 +24,14 @@ def locally_defined_flux_units(): can be combined with units in 'supported_sq_angle_units' to form supported surface brightness units. If data is loaded with a flux unit (or equivalent flux unit e.g nJy) in this list, conversions between all other units in the - list should be supported. Units like 'counts' and 'e / s' would not + list should be supported. """ 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 diff --git a/jdaviz/tests/test_utils.py b/jdaviz/tests/test_utils.py index 2b87a66c55..7e6d914124 100644 --- a/jdaviz/tests/test_utils.py +++ b/jdaviz/tests/test_utils.py @@ -10,14 +10,13 @@ from numpy.testing import assert_allclose from specutils import Spectrum1D -from jdaviz.core.custom_units import * +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") - def test_spec_sb_flux_conversion(): # Actual spectrum content does not matter, just the meta is used here. spec = Spectrum1D(flux=[1, 1, 1] * u.Jy, spectral_axis=[1, 2, 3] * u.um) diff --git a/jdaviz/utils.py b/jdaviz/utils.py index e19a0fd428..f90cf1282a 100644 --- a/jdaviz/utils.py +++ b/jdaviz/utils.py @@ -22,7 +22,7 @@ from glue_astronomy.spectral_coordinates import SpectralCoordinates from ipyvue import watch -from jdaviz.core.custom_units import * +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', From c805453de8c987f9dbc77c219194f71516a8c3df Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 26 Sep 2024 22:38:13 -0400 Subject: [PATCH 04/17] new test file --- .../tests/test_cubeviz_unit_conversion.py | 230 ++++++++++++++++++ .../tests/test_unit_conversion.py | 226 ----------------- jdaviz/core/validunits.py | 12 +- 3 files changed, 235 insertions(+), 233 deletions(-) create mode 100644 jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py 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..0c9b3ad409 --- /dev/null +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -0,0 +1,230 @@ +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 + + +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, 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/specviz/plugins/unit_conversion/tests/test_unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py index 0b274edd4e..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,12 +2,8 @@ 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 -from jdaviz.core.custom_units import PIX2 - # On failure, should not crash; essentially a no-op. @pytest.mark.parametrize( @@ -137,225 +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, 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/core/validunits.py b/jdaviz/core/validunits.py index 318491308f..39d30bb4dc 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -18,13 +18,11 @@ def supported_sq_angle_units(as_strings=False): def locally_defined_flux_units(): """ - This function returns a list of string representations of flux units that, - combined with the u.spectral_density equivalency for some conversions, - are translatable between one another. These units - can be combined with units in 'supported_sq_angle_units' to form supported - surface brightness units. If data is loaded with a flux unit (or equivalent - flux unit e.g nJy) in this list, conversions between all other units in the - list should be supported. + 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)', From 3250230dfd313ae2177893cd87cc80d94aa159d3 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 26 Sep 2024 23:17:50 -0400 Subject: [PATCH 05/17] added test --- .../tests/test_cubeviz_unit_conversion.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py index 0c9b3ad409..289cd85541 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -6,10 +6,11 @@ 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 here + # 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, @@ -18,6 +19,38 @@ def cubeviz_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 From caa5501409828bc681475cc52c4a33d602141ed1 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Fri, 27 Sep 2024 21:30:36 -0400 Subject: [PATCH 06/17] simplify flux choice setting --- .../unit_conversion/unit_conversion.py | 9 +-- jdaviz/core/validunits.py | 70 +++++++++++-------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index 5bc6c71624..e1911e0b11 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -133,7 +133,7 @@ def __init__(self, *args, **kwargs): items='flux_unit_items', selected='flux_unit_selected') # NOTE: will switch to count only if first data loaded into viewer in in counts - self.flux_unit.choices = create_flux_equivalencies_list(u.Jy, u.Hz) + self.flux_unit.choices = create_flux_equivalencies_list(u.Jy) self.has_angle = self.config in ('cubeviz', 'specviz', 'mosviz') self.angle_unit = UnitSelectPluginComponent(self, @@ -219,11 +219,8 @@ def _on_add_data_to_viewer(self, msg): flux_unit = data_obj.flux.unit if angle_unit is None else data_obj.flux.unit * angle_unit # noqa if not self.flux_unit_selected: - if flux_unit in (u.count, u.DN, u.electron / u.s): - self.flux_unit.choices = [flux_unit] - elif flux_unit not in self.flux_unit.choices: - # ensure that the native units are in the list of choices - self.flux_unit.choices += [flux_unit] + self.flux_unit.choices = create_flux_equivalencies_list(flux_unit) + try: self.flux_unit.selected = str(flux_unit) except ValueError: diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 39d30bb4dc..2f7199751b 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -1,5 +1,6 @@ from astropy import units as u import itertools +import numpy as np from jdaviz.core.custom_units import PIX2 @@ -16,18 +17,24 @@ def supported_sq_angle_units(as_strings=False): return units -def locally_defined_flux_units(): +def spectral_and_photon_flux_density_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)). + This function returns an alphabetically sorted list of string representations + of spectral and photon flux density 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 + + return sorted(flux_units) + +def locally_defined_flux_units(): + return spectral_and_photon_flux_density_units() + def combine_flux_and_angle_units(flux_units, angle_units): @@ -92,36 +99,39 @@ def create_spectral_equivalencies_list(spectral_axis_unit, return sorted(units_to_strings(local_units)) + spectral_axis_unit_equivalencies_titles -def create_flux_equivalencies_list(flux_unit, spectral_axis_unit): - """Get all possible conversions for flux from current flux units.""" - if ((flux_unit in (u.count, u.dimensionless_unscaled)) - or (spectral_axis_unit in (u.pix, u.dimensionless_unscaled))): - return [] +def create_flux_equivalencies_list(flux_unit): + """ + Get all possible conversions for flux from flux_unit, to populate 'flux' + dropdown menu in the unit conversion plugin. - # Get unit equivalencies. Value passed into u.spectral_density() is irrelevant. - try: - curr_flux_unit_equivalencies = flux_unit.find_equivalent_units( - equivalencies=u.spectral_density(1 * spectral_axis_unit), - include_prefix_units=False) - except u.core.UnitConversionError: - return [] + If flux_unit is a spectral or photon density (i.e convertable to units in + spectral_and_photon_flux_density_units), then the loaded unit and all of the + units in spectral_and_photon_flux_density_units. - mag_units = ['bol', 'AB', 'ST'] - # remove magnitude units from list - 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 + If the loaded flux unit is count, dimensionless_unscaled, DN, e/s, then + there will be no additional items available for unit conversion and the + only item in the dropdown will be the native unit - # Get local 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) - - set(local_units)) + flux_unit_str = flux_unit.to_string() - # Convert equivalencies into readable versions of the units and sort them alphabetically. - flux_unit_equivalencies_titles = sorted(units_to_strings(curr_flux_unit_equivalencies)) + # if flux_unit is a spectral or photon flux density unit, then the flux unit + # dropdown options should be the loaded unit (which may have a different + # prefix e.g nJy) in addition to items in spectral_and_photon_flux_density_units + spec_photon_density_flux = spectral_and_photon_flux_density_units() + equiv = u.spectral_density(1 * u.m) # spec. unit doesn't matter here, we're not evaluating + if np.any([flux_unit.is_equivalent(un, equiv) for un in spec_photon_density_flux]): + if flux_unit_str not in spec_photon_density_flux: + return spec_photon_density_flux + [flux_unit_str] + return spec_photon_density_flux - # Concatenate both lists with the local units coming first. - return sorted(units_to_strings(local_units)) + flux_unit_equivalencies_titles + else: + # for any other units, including counts, DN, e/s, DN /s, etc, + # no other conversions between flux units available as we only support + # conversions to and from spectral and photon flux density flux unit. + # dropdown will only contain one item (the input unit) + return [flux_unit_str] def create_angle_equivalencies_list(solid_angle_unit): From 25f58bec0a5e8268e591610f87c064d6aa0f1df8 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Mon, 30 Sep 2024 13:39:41 -0400 Subject: [PATCH 07/17] . --- .../tests/test_cubeviz_unit_conversion.py | 33 ++++++++++++++++--- .../unit_conversion/unit_conversion.py | 4 +-- jdaviz/core/validunits.py | 3 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py index 289cd85541..22f1ac9bd2 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -6,7 +6,9 @@ from specutils import Spectrum1D from jdaviz.core.custom_units import PIX2 -from jdaviz.core.validunits import locally_defined_flux_units +from jdaviz.core.validunits import spectral_and_photon_flux_density_units + +ALL_FLUX_UNITS = spectral_and_photon_flux_density_units() def cubeviz_wcs_dict(): @@ -41,15 +43,36 @@ def test_basic_unit_conversions(cubeviz_helper, angle_unit): 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() + # in Jy, this will be all items in 'spectral_and_photon_flux_density_units' uc_plg = cubeviz_helper.plugins['Unit Conversion'] - for flux_unit in all_flux_units: + for flux_unit in ALL_FLUX_UNITS: uc_plg.flux_unit = flux_unit +@pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), + (u.Jy, ALL_FLUX_UNITS), + (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) +def test_flux_unit_choices(cubeviz_helper, flux_unit, expected_choices): + """ + Test that cubes loaded with various flux units have the expected default + flux unit selection in the unit conversion plugin, and that the list of + convertable flux units in the dropdown is correct. + """ + + w, wcs_dict = cubeviz_wcs_dict() + flux = np.zeros((30, 20, 3001), dtype=np.float32) + # load cube in flux_unit, will become cube in flux_unit / pix2 + cube = Spectrum1D(flux=flux * flux_unit, wcs=w, meta=wcs_dict) + cubeviz_helper.load_data(cube) + + uc_plg = cubeviz_helper.plugins['Unit Conversion'] + + assert uc_plg.angle_unit.selected == 'pix2' # will always be pix2 + + assert uc_plg.flux_unit.selected == flux_unit.to_string() + assert uc_plg.flux_unit.choices == expected_choices + @pytest.mark.parametrize("angle_unit", [u.sr, PIX2]) def test_unit_translation(cubeviz_helper, angle_unit): diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index e1911e0b11..b74616c5d6 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -220,15 +220,13 @@ def _on_add_data_to_viewer(self, msg): if not self.flux_unit_selected: self.flux_unit.choices = create_flux_equivalencies_list(flux_unit) - try: self.flux_unit.selected = str(flux_unit) except ValueError: self.flux_unit.selected = '' if not self.angle_unit_selected: - if angle_unit == PIX2: - self.angle_unit.choices = ['pix2'] + self.angle_unit.choices = create_angle_equivalencies_list(angle_unit) try: if angle_unit is None: # default to sr if input spectrum is not in surface brightness units diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 2f7199751b..78b70a0c52 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -36,7 +36,6 @@ def locally_defined_flux_units(): return spectral_and_photon_flux_density_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 @@ -156,7 +155,7 @@ def create_angle_equivalencies_list(solid_angle_unit): """ - if solid_angle_unit is None: + if solid_angle_unit is None or solid_angle_unit is PIX2: # if there was no solid angle in the unit when calling this function # can only represent that unit as per square pixel return ['pix^2'] From cbc1895b611c56c46d8e29b17d2dffe4345b76e1 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Mon, 30 Sep 2024 14:21:00 -0400 Subject: [PATCH 08/17] . --- jdaviz/app.py | 4 ++-- .../cubeviz/plugins/tests/test_cubeviz_unit_conversion.py | 5 +++-- jdaviz/configs/default/plugins/viewers.py | 2 +- .../specviz/plugins/unit_conversion/unit_conversion.py | 1 - jdaviz/core/validunits.py | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/jdaviz/app.py b/jdaviz/app.py index 9d5ea971d4..50355a6f38 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -55,7 +55,7 @@ spectral_axis_conversion) from jdaviz.core.validunits import (check_if_unit_is_per_solid_angle, combine_flux_and_angle_units, - locally_defined_flux_units, + spectral_and_photon_flux_density_units, supported_sq_angle_units) __all__ = ['Application', 'ALL_JDAVIZ_CONFIGS', 'UnitConverterWithSpectral'] @@ -75,7 +75,7 @@ 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. - all_flux_units = locally_defined_flux_units() + ['ct'] + all_flux_units = spectral_and_photon_flux_density_units() + ['ct'] angle_units = supported_sq_angle_units() all_sb_units = combine_flux_and_angle_units(all_flux_units, angle_units) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py index 22f1ac9bd2..897eb43109 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -50,11 +50,12 @@ def test_basic_unit_conversions(cubeviz_helper, angle_unit): for flux_unit in ALL_FLUX_UNITS: uc_plg.flux_unit = flux_unit + @pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), (u.Jy, ALL_FLUX_UNITS), (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) def test_flux_unit_choices(cubeviz_helper, flux_unit, expected_choices): - """ + """ Test that cubes loaded with various flux units have the expected default flux unit selection in the unit conversion plugin, and that the list of convertable flux units in the dropdown is correct. @@ -68,7 +69,7 @@ def test_flux_unit_choices(cubeviz_helper, flux_unit, expected_choices): uc_plg = cubeviz_helper.plugins['Unit Conversion'] - assert uc_plg.angle_unit.selected == 'pix2' # will always be pix2 + assert uc_plg.angle_unit.selected == 'pix2' # will always be pix2 assert uc_plg.flux_unit.selected == flux_unit.to_string() assert uc_plg.flux_unit.choices == expected_choices diff --git a/jdaviz/configs/default/plugins/viewers.py b/jdaviz/configs/default/plugins/viewers.py index aab17b8e5d..5edbca8544 100644 --- a/jdaviz/configs/default/plugins/viewers.py +++ b/jdaviz/configs/default/plugins/viewers.py @@ -736,7 +736,7 @@ def _plot_uncertainties(self): self.figure.marks = list(self.figure.marks) + [error_line_mark] def set_plot_axes(self): - # Set y axes labels for the spectrum viewer + # Set x and y axes labels for the spectrum viewer y_display_unit = self.state.y_display_unit y_unit = ( u.Unit(y_display_unit) if y_display_unit and y_display_unit != 'None' diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index 448b3466ed..cb970aace6 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -6,7 +6,6 @@ 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, diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 63536633b8..f0f512d752 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -31,6 +31,7 @@ def spectral_and_photon_flux_density_units(): return sorted(flux_units) + def locally_defined_flux_units(): return spectral_and_photon_flux_density_units() From 5d9f6e96956bf38b23a553301bb500bdc169dc16 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Mon, 30 Sep 2024 14:55:51 -0400 Subject: [PATCH 09/17] . --- jdaviz/core/validunits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index f0f512d752..a3fef228ec 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -100,7 +100,7 @@ def create_spectral_equivalencies_list(spectral_axis_unit, def create_flux_equivalencies_list(flux_unit): """ - Get all possible conversions for flux from flux_unit, to populate 'flux' + Get all possible conversions for flux from flux_unit, to populate 'flux' dropdown menu in the unit conversion plugin. If flux_unit is a spectral or photon density (i.e convertable to units in From 43715efa8e660030aa71b3ceaf0a8976d590a1e6 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Mon, 30 Sep 2024 15:21:08 -0400 Subject: [PATCH 10/17] specviz test --- .../tests/test_unit_conversion.py | 23 +++++++++++++++++++ jdaviz/core/validunits.py | 6 +---- 2 files changed, 24 insertions(+), 5 deletions(-) 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 faefecd885..7f08b4d153 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 @@ -4,6 +4,10 @@ from astropy.nddata import InverseVariance from specutils import Spectrum1D +from jdaviz.core.validunits import spectral_and_photon_flux_density_units + +ALL_FLUX_UNITS = spectral_and_photon_flux_density_units() + # On failure, should not crash; essentially a no-op. @pytest.mark.parametrize( @@ -133,3 +137,22 @@ def test_non_stddev_uncertainty(specviz_helper): np.abs(viewer.figure.marks[-1].y - viewer.figure.marks[-1].y.mean(0)), stddev ) + + +@pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), + (u.Jy, ALL_FLUX_UNITS), + (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) +def test_flux_unit_choices(specviz_helper, spectrum1d, flux_unit, expected_choices): + """ + Test that cubes loaded with various flux units have the expected default + flux unit selection in the unit conversion plugin, and that the list of + convertable flux units in the dropdown is correct. + """ + + spec = Spectrum1D(spectrum1d.flux.value * flux_unit, spectrum1d.spectral_axis) + specviz_helper.load_data(spec) + + uc_plg = specviz_helper.plugins['Unit Conversion'] + + assert uc_plg.flux_unit.selected == flux_unit.to_string() + assert uc_plg.flux_unit.choices == expected_choices diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index a3fef228ec..7bb7ce77fe 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -4,7 +4,7 @@ from jdaviz.core.custom_units import PIX2 -__all__ = ['supported_sq_angle_units', 'locally_defined_flux_units', +__all__ = ['supported_sq_angle_units', 'spectral_and_photon_flux_density_units', 'combine_flux_and_angle_units', 'units_to_strings', 'create_spectral_equivalencies_list', 'create_flux_equivalencies_list', 'check_if_unit_is_per_solid_angle'] @@ -32,10 +32,6 @@ def spectral_and_photon_flux_density_units(): return sorted(flux_units) -def locally_defined_flux_units(): - return spectral_and_photon_flux_density_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 From 58124a0a6274b70d62419be21bf58cd9b9d845c5 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Mon, 30 Sep 2024 15:27:35 -0400 Subject: [PATCH 11/17] . --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4f8438f6ed..299cdde945 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ New Features - Added flux/surface brightness translation and surface brightness unit conversion in Cubeviz and Specviz. [#2781, #2940, #3088, #3111, #3113, #3129, - #3139, #3149, #3155, #3178, #3185, #3187, #3190, #3156, #3200, #3192] + #3139, #3149, #3155, #3178, #3185, #3187, #3190, #3156, #3200, #3192, #3206] - Plugin tray is now open by default. [#2892] From b80bfbcfee460de4c51c5228fbb5b69ce5d4ff60 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 10:05:49 -0400 Subject: [PATCH 12/17] review comments --- .../cubeviz/plugins/tests/test_cubeviz_unit_conversion.py | 4 ++-- .../plugins/unit_conversion/tests/test_unit_conversion.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py index 897eb43109..fe855e4d61 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -38,7 +38,7 @@ def test_basic_unit_conversions(cubeviz_helper, angle_unit): # load cube with flux units of MJy w, wcs_dict = cubeviz_wcs_dict() - flux = np.zeros((30, 20, 3001), dtype=np.float32) + flux = np.zeros((3, 4, 5), dtype=np.float32) cube = Spectrum1D(flux=flux * u.MJy / angle_unit, wcs=w, meta=wcs_dict) cubeviz_helper.load_data(cube, data_label="test") @@ -62,7 +62,7 @@ def test_flux_unit_choices(cubeviz_helper, flux_unit, expected_choices): """ w, wcs_dict = cubeviz_wcs_dict() - flux = np.zeros((30, 20, 3001), dtype=np.float32) + flux = np.zeros((3, 4, 5), dtype=np.float32) # load cube in flux_unit, will become cube in flux_unit / pix2 cube = Spectrum1D(flux=flux * flux_unit, wcs=w, meta=wcs_dict) cubeviz_helper.load_data(cube) 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 7f08b4d153..e782512e98 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 @@ -142,14 +142,14 @@ def test_non_stddev_uncertainty(specviz_helper): @pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), (u.Jy, ALL_FLUX_UNITS), (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) -def test_flux_unit_choices(specviz_helper, spectrum1d, flux_unit, expected_choices): +def test_flux_unit_choices(specviz_helper, flux_unit, expected_choices): """ Test that cubes loaded with various flux units have the expected default flux unit selection in the unit conversion plugin, and that the list of convertable flux units in the dropdown is correct. """ - spec = Spectrum1D(spectrum1d.flux.value * flux_unit, spectrum1d.spectral_axis) + spec = Spectrum1D([1, 2, 3] * flux_unit, [4, 5, 6] * u.um) specviz_helper.load_data(spec) uc_plg = specviz_helper.plugins['Unit Conversion'] From 5fc912fe16892fff69ffddf5a473471c359f30e1 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 15:23:49 -0400 Subject: [PATCH 13/17] review comments --- jdaviz/app.py | 4 +-- .../tests/test_cubeviz_unit_conversion.py | 12 +++---- .../tests/test_unit_conversion.py | 8 ++--- jdaviz/core/custom_units.py | 18 ++++++++++ jdaviz/core/validunits.py | 34 +++++-------------- 5 files changed, 37 insertions(+), 39 deletions(-) diff --git a/jdaviz/app.py b/jdaviz/app.py index 50355a6f38..3430d6aba0 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -53,9 +53,9 @@ 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.custom_units import SPEC_PHOTON_FLUX_DENSITY_UNITS from jdaviz.core.validunits import (check_if_unit_is_per_solid_angle, combine_flux_and_angle_units, - spectral_and_photon_flux_density_units, supported_sq_angle_units) __all__ = ['Application', 'ALL_JDAVIZ_CONFIGS', 'UnitConverterWithSpectral'] @@ -75,7 +75,7 @@ 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. - all_flux_units = spectral_and_photon_flux_density_units() + ['ct'] + all_flux_units = SPEC_PHOTON_FLUX_DENSITY_UNITS + ['ct'] angle_units = supported_sq_angle_units() all_sb_units = combine_flux_and_angle_units(all_flux_units, angle_units) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py index fe855e4d61..09c52f53f8 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_cubeviz_unit_conversion.py @@ -5,10 +5,7 @@ from regions import PixCoord, CirclePixelRegion from specutils import Spectrum1D -from jdaviz.core.custom_units import PIX2 -from jdaviz.core.validunits import spectral_and_photon_flux_density_units - -ALL_FLUX_UNITS = spectral_and_photon_flux_density_units() +from jdaviz.core.custom_units import PIX2, SPEC_PHOTON_FLUX_DENSITY_UNITS def cubeviz_wcs_dict(): @@ -47,13 +44,14 @@ def test_basic_unit_conversions(cubeviz_helper, angle_unit): uc_plg = cubeviz_helper.plugins['Unit Conversion'] - for flux_unit in ALL_FLUX_UNITS: + for flux_unit in SPEC_PHOTON_FLUX_DENSITY_UNITS: uc_plg.flux_unit = flux_unit + assert cubeviz_helper.app._get_display_unit('spectral_y') == flux_unit @pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), - (u.Jy, ALL_FLUX_UNITS), - (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) + (u.Jy, SPEC_PHOTON_FLUX_DENSITY_UNITS), + (u.nJy, SPEC_PHOTON_FLUX_DENSITY_UNITS + ['nJy'])]) # noqa def test_flux_unit_choices(cubeviz_helper, flux_unit, expected_choices): """ Test that cubes loaded with various flux units have the expected default 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 e782512e98..1fff4d838d 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 @@ -4,9 +4,7 @@ from astropy.nddata import InverseVariance from specutils import Spectrum1D -from jdaviz.core.validunits import spectral_and_photon_flux_density_units - -ALL_FLUX_UNITS = spectral_and_photon_flux_density_units() +from jdaviz.core.custom_units import SPEC_PHOTON_FLUX_DENSITY_UNITS # On failure, should not crash; essentially a no-op. @@ -140,8 +138,8 @@ def test_non_stddev_uncertainty(specviz_helper): @pytest.mark.parametrize("flux_unit, expected_choices", [(u.count, ['ct']), - (u.Jy, ALL_FLUX_UNITS), - (u.nJy, ALL_FLUX_UNITS + ['nJy'])]) + (u.Jy, SPEC_PHOTON_FLUX_DENSITY_UNITS), + (u.nJy, SPEC_PHOTON_FLUX_DENSITY_UNITS + ['nJy'])]) # noqa def test_flux_unit_choices(specviz_helper, flux_unit, expected_choices): """ Test that cubes loaded with various flux units have the expected default diff --git a/jdaviz/core/custom_units.py b/jdaviz/core/custom_units.py index 1675969961..53c7a8a1a1 100644 --- a/jdaviz/core/custom_units.py +++ b/jdaviz/core/custom_units.py @@ -2,3 +2,21 @@ # define custom composite units here PIX2 = u.pix * u.pix + + +def _spectral_and_photon_flux_density_units(): + """ + This function returns an alphabetically sorted list of string representations + of spectral and photon flux density 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 sorted(flux_units) + + +SPEC_PHOTON_FLUX_DENSITY_UNITS = _spectral_and_photon_flux_density_units() diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 7bb7ce77fe..5e975c6587 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -2,9 +2,9 @@ import itertools import numpy as np -from jdaviz.core.custom_units import PIX2 +from jdaviz.core.custom_units import PIX2, SPEC_PHOTON_FLUX_DENSITY_UNITS -__all__ = ['supported_sq_angle_units', 'spectral_and_photon_flux_density_units', +__all__ = ['supported_sq_angle_units', 'combine_flux_and_angle_units', 'units_to_strings', 'create_spectral_equivalencies_list', 'create_flux_equivalencies_list', 'check_if_unit_is_per_solid_angle'] @@ -17,21 +17,6 @@ def supported_sq_angle_units(as_strings=False): return units -def spectral_and_photon_flux_density_units(): - """ - This function returns an alphabetically sorted list of string representations - of spectral and photon flux density 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 sorted(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 @@ -100,8 +85,8 @@ def create_flux_equivalencies_list(flux_unit): dropdown menu in the unit conversion plugin. If flux_unit is a spectral or photon density (i.e convertable to units in - spectral_and_photon_flux_density_units), then the loaded unit and all of the - units in spectral_and_photon_flux_density_units. + SPEC_PHOTON_FLUX_DENSITY_UNITS), then the loaded unit and all of the + units in SPEC_PHOTON_FLUX_DENSITY_UNITS. If the loaded flux unit is count, dimensionless_unscaled, DN, e/s, then there will be no additional items available for unit conversion and the @@ -112,13 +97,12 @@ def create_flux_equivalencies_list(flux_unit): # if flux_unit is a spectral or photon flux density unit, then the flux unit # dropdown options should be the loaded unit (which may have a different - # prefix e.g nJy) in addition to items in spectral_and_photon_flux_density_units - spec_photon_density_flux = spectral_and_photon_flux_density_units() + # prefix e.g nJy) in addition to items in SPEC_PHOTON_FLUX_DENSITY_UNITS equiv = u.spectral_density(1 * u.m) # spec. unit doesn't matter here, we're not evaluating - if np.any([flux_unit.is_equivalent(un, equiv) for un in spec_photon_density_flux]): - if flux_unit_str not in spec_photon_density_flux: - return spec_photon_density_flux + [flux_unit_str] - return spec_photon_density_flux + if np.any([flux_unit.is_equivalent(un, equiv) for un in SPEC_PHOTON_FLUX_DENSITY_UNITS]): + if flux_unit_str not in SPEC_PHOTON_FLUX_DENSITY_UNITS: + return SPEC_PHOTON_FLUX_DENSITY_UNITS + [flux_unit_str] + return SPEC_PHOTON_FLUX_DENSITY_UNITS else: # for any other units, including counts, DN, e/s, DN /s, etc, From 7b6af6ff8c3ed35d2131d3c831f660b6ddadbdca Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 16:05:10 -0400 Subject: [PATCH 14/17] initialize flux and angle choices to an empty list before data is loaded --- .../specviz/plugins/unit_conversion/unit_conversion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index cb970aace6..d7881eec63 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -132,14 +132,16 @@ def __init__(self, *args, **kwargs): items='flux_unit_items', selected='flux_unit_selected') # NOTE: will switch to count only if first data loaded into viewer in in counts - self.flux_unit.choices = create_flux_equivalencies_list(u.Jy) + # 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.angle_unit = UnitSelectPluginComponent(self, items='angle_unit_items', selected='angle_unit_selected') # NOTE: will switch to pix2 only if first data loaded into viewer is in pix2 units - self.angle_unit.choices = create_angle_equivalencies_list(u.sr) + # initialize flux choices to empty list, will be populated when data is loaded + self.angle_unit.choices = [] self.has_sb = self.has_angle or self.config in ('imviz',) # NOTE: sb_unit is read_only, exposed through sb_unit property From 7052dcba5c9f372d86ce85848cea13072fd83cc5 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 16:06:17 -0400 Subject: [PATCH 15/17] . --- jdaviz/core/custom_units.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jdaviz/core/custom_units.py b/jdaviz/core/custom_units.py index 53c7a8a1a1..9ba1c42d46 100644 --- a/jdaviz/core/custom_units.py +++ b/jdaviz/core/custom_units.py @@ -1,5 +1,7 @@ import astropy.units as u +___all__ = ["PIX2", "SPEC_PHOTON_FLUX_DENSITY_UNITS"] + # define custom composite units here PIX2 = u.pix * u.pix From 919e28a8e642ef437d6b7e4897c045d881e10865 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 16:09:28 -0400 Subject: [PATCH 16/17] . --- jdaviz/core/validunits.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jdaviz/core/validunits.py b/jdaviz/core/validunits.py index 5e975c6587..75869a4942 100644 --- a/jdaviz/core/validunits.py +++ b/jdaviz/core/validunits.py @@ -1,6 +1,5 @@ from astropy import units as u import itertools -import numpy as np from jdaviz.core.custom_units import PIX2, SPEC_PHOTON_FLUX_DENSITY_UNITS @@ -99,10 +98,12 @@ def create_flux_equivalencies_list(flux_unit): # dropdown options should be the loaded unit (which may have a different # prefix e.g nJy) in addition to items in SPEC_PHOTON_FLUX_DENSITY_UNITS equiv = u.spectral_density(1 * u.m) # spec. unit doesn't matter here, we're not evaluating - if np.any([flux_unit.is_equivalent(un, equiv) for un in SPEC_PHOTON_FLUX_DENSITY_UNITS]): - if flux_unit_str not in SPEC_PHOTON_FLUX_DENSITY_UNITS: - return SPEC_PHOTON_FLUX_DENSITY_UNITS + [flux_unit_str] - return SPEC_PHOTON_FLUX_DENSITY_UNITS + for un in SPEC_PHOTON_FLUX_DENSITY_UNITS: + if flux_unit.is_equivalent(un, equiv): + if flux_unit_str not in SPEC_PHOTON_FLUX_DENSITY_UNITS: + return SPEC_PHOTON_FLUX_DENSITY_UNITS + [flux_unit_str] + else: + return SPEC_PHOTON_FLUX_DENSITY_UNITS else: # for any other units, including counts, DN, e/s, DN /s, etc, From c330a93b8bda911a14a228af945ba90acc7a95f5 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 3 Oct 2024 22:04:44 -0400 Subject: [PATCH 17/17] . --- jdaviz/core/custom_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/core/custom_units.py b/jdaviz/core/custom_units.py index 9ba1c42d46..7b3b6acba9 100644 --- a/jdaviz/core/custom_units.py +++ b/jdaviz/core/custom_units.py @@ -1,6 +1,6 @@ import astropy.units as u -___all__ = ["PIX2", "SPEC_PHOTON_FLUX_DENSITY_UNITS"] +__all__ = ["PIX2", "SPEC_PHOTON_FLUX_DENSITY_UNITS"] # define custom composite units here PIX2 = u.pix * u.pix