diff --git a/CHANGES.rst b/CHANGES.rst index 81d9bf952d..1b83432bdf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -36,6 +36,8 @@ Specviz Specviz2d ^^^^^^^^^ +- Implement the Unit Conversion plugin in Specviz2D. [#3253] + API Changes ----------- diff --git a/jdaviz/configs/cubeviz/helper.py b/jdaviz/configs/cubeviz/helper.py index 4c610506b7..f9579514f1 100644 --- a/jdaviz/configs/cubeviz/helper.py +++ b/jdaviz/configs/cubeviz/helper.py @@ -141,6 +141,8 @@ def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, Spectral subset applied to data. cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. + use_display_units : bool, optional + Specify whether the returned data is in native units or the current display units. Returns ------- diff --git a/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py b/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py index 9893fd5420..aac421dd91 100644 --- a/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py +++ b/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py @@ -520,9 +520,18 @@ def test_spectral_extraction_with_correct_sum_units(cubeviz_helper, cubeviz_helper.load_data(spectrum1d_cube_fluxunit_jy_per_steradian) spec_extr_plugin = cubeviz_helper.plugins['Spectral Extraction']._obj collapsed = spec_extr_plugin.extract() + + assert '_pixel_scale_factor' in collapsed.meta + + # Original units in Jy / sr + # After collapsing, sr is removed via the scale factor and the extracted spectrum is in Jy + expected_flux_values = (np.array([190., 590., 990., 1390., 1790., + 2190., 2590., 2990., 3390., 3790.]) * + collapsed.meta.get('_pixel_scale_factor')) + np.testing.assert_allclose( collapsed.flux.value, - [190., 590., 990., 1390., 1790., 2190., 2590., 2990., 3390., 3790.] + expected_flux_values ) assert collapsed.flux.unit == u.Jy assert collapsed.uncertainty.unit == u.Jy diff --git a/jdaviz/configs/default/plugins/markers/markers.py b/jdaviz/configs/default/plugins/markers/markers.py index 7379a1c3ff..960ede6a40 100644 --- a/jdaviz/configs/default/plugins/markers/markers.py +++ b/jdaviz/configs/default/plugins/markers/markers.py @@ -64,10 +64,12 @@ def __init__(self, *args, **kwargs): elif self.config == 'specviz': headers = ['spectral_axis', 'spectral_axis:unit', 'index', 'value', 'value:unit'] + elif self.config == 'specviz2d': # TODO: add "index" if/when specviz2d supports plotting spectral_axis headers = ['spectral_axis', 'spectral_axis:unit', 'pixel_x', 'pixel_y', 'value', 'value:unit', 'viewer'] + elif self.config == 'mosviz': headers = ['spectral_axis', 'spectral_axis:unit', 'pixel_x', 'pixel_y', 'world_ra', 'world_dec', 'index', @@ -223,7 +225,6 @@ def _on_is_active_changed(self, *args): def _on_viewer_key_event(self, viewer, data): if data['event'] == 'keydown' and data['key'] == 'm': row_info = self.coords_info.as_dict() - if 'viewer' in self.table.headers_avail: row_info['viewer'] = viewer.reference if viewer.reference is not None else viewer.reference_id # noqa diff --git a/jdaviz/configs/default/plugins/markers/tests/test_markers_plugin.py b/jdaviz/configs/default/plugins/markers/tests/test_markers_plugin.py index f98644bd44..b096931473 100644 --- a/jdaviz/configs/default/plugins/markers/tests/test_markers_plugin.py +++ b/jdaviz/configs/default/plugins/markers/tests/test_markers_plugin.py @@ -4,6 +4,7 @@ import numpy as np from numpy.testing import assert_allclose import pytest +from specutils import Spectrum1D from jdaviz.core.custom_units_and_equivs import PIX2, SPEC_PHOTON_FLUX_DENSITY_UNITS from jdaviz.core.marks import MarkersMark @@ -242,6 +243,61 @@ def test_markers_cubeviz_flux_unit_conversion(cubeviz_helper, assert last_row['value:unit'] == new_cube_unit_str +def test_markers_specviz2d_unit_conversion(specviz2d_helper, spectrum2d): + data = np.zeros((5, 10)) + data[3] = np.arange(10) + spectrum2d = Spectrum1D(flux=data*u.MJy, spectral_axis=data[3]*u.AA) + specviz2d_helper.load_data(spectrum2d) + + uc = specviz2d_helper.plugins['Unit Conversion'] + uc.open_in_tray() + mp = specviz2d_helper.plugins['Markers'] + mp.keep_active = True + + label_mouseover = specviz2d_helper.app.session.application._tools["g-coords-info"] + viewer2d = specviz2d_helper.app.get_viewer("spectrum-2d-viewer") + label_mouseover._viewer_mouse_event(viewer2d, {"event": "mousemove", + "domain": {"x": 6, "y": 3}}) + assert label_mouseover.as_text() == ('Pixel x=06.0 y=03.0 Value +6.00000e+00 MJy', + 'Wave 6.00000e+00 Angstrom', + '') + mp._obj._on_viewer_key_event(viewer2d, {'event': 'keydown', + 'key': 'm'}) + + # make sure last marker added to table reflects new unit selection + last_row = mp.export_table()[-1] + assert last_row['value:unit'] == uc.flux_unit + assert last_row['spectral_axis:unit'] == uc.spectral_unit + + # ensure marks work with flux conversion where spectral axis is required and + # spectral axis conversion + uc.flux_unit = 'erg / (Angstrom s cm2)' + uc.spectral_unit = 'Ry' + label_mouseover._viewer_mouse_event(viewer2d, {"event": "mousemove", + "domain": {"x": 4, "y": 3}}) + assert label_mouseover.as_text() == ('Pixel x=04.0 y=03.0 Value +7.49481e+00 erg / (Angstrom s cm2)', # noqa + 'Wave 2.27817e+02 Ry', + '') + mp._obj._on_viewer_key_event(viewer2d, {'event': 'keydown', + 'key': 'm'}) + + # make sure last marker added to table reflects new unit selection + last_row = mp.export_table()[-1] + assert last_row['value:unit'] == uc.flux_unit + assert last_row['spectral_axis:unit'] == uc.spectral_unit + + second_marker_flux_unit = uc.flux_unit.selected + second_marker_spectral_unit = uc.spectral_unit.selected + + # test edge case two non-native spectral axis required conversions + uc.flux_unit = 'ph / (Angstrom s cm2)' + uc.spectral_unit = 'eV' + + # make sure table flux and spectral unit doesn't update + assert last_row['value:unit'] == second_marker_flux_unit + assert last_row['spectral_axis:unit'] == second_marker_spectral_unit + + class TestImvizMultiLayer(BaseImviz_WCS_NoWCS): def test_markers_layer_cycle(self): label_mouseover = self.imviz.app.session.application._tools['g-coords-info'] diff --git a/jdaviz/configs/default/plugins/model_fitting/model_fitting.py b/jdaviz/configs/default/plugins/model_fitting/model_fitting.py index 40313afbde..8c0b9f0540 100644 --- a/jdaviz/configs/default/plugins/model_fitting/model_fitting.py +++ b/jdaviz/configs/default/plugins/model_fitting/model_fitting.py @@ -1113,8 +1113,11 @@ def _fit_model_to_spectrum(self, add_data): return models_to_fit = self._reinitialize_with_fixed() - masked_spectrum = self._apply_subset_masks(self.dataset.selected_spectrum, + spec = self.dataset.get_selected_spectrum(use_display_units=True) + + masked_spectrum = self._apply_subset_masks(spec, self.spectral_subset) + try: fitted_model, fitted_spectrum = fit_model_to_spectrum( masked_spectrum, 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 2ad1baf4db..7f2047d473 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py @@ -312,6 +312,8 @@ def test_results_table(specviz_helper, spectrum1d): mf = specviz_helper.plugins['Model Fitting'] mf.create_model_component('Linear1D') + uc = specviz_helper.plugins['Unit Conversion'] + mf.add_results.label = 'linear model' with warnings.catch_warnings(): warnings.filterwarnings('ignore', message='Model is linear in parameters.*') @@ -325,6 +327,9 @@ def test_results_table(specviz_helper, spectrum1d): 'L:intercept_0', 'L:intercept_0:unit', 'L:intercept_0:fixed', 'L:intercept_0:std'] + # verify units in table match the current display unit + assert mf_table['L:intercept_0:unit'][-1] == uc.flux_unit + mf.create_model_component('Gaussian1D') mf.add_results.label = 'composite model' with warnings.catch_warnings(): @@ -346,6 +351,19 @@ def test_results_table(specviz_helper, spectrum1d): 'G:stddev_1', 'G:stddev_1:unit', 'G:stddev_1:fixed', 'G:stddev_1:std'] + mf.remove_model_component('G') + assert len(mf_table) == 2 + + # verify Spectrum1D model fitting plugin and table can handle spectral density conversions + uc.flux_unit = 'erg / (Angstrom s cm2)' + mf.reestimate_model_parameters() + + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='Model is linear in parameters.*') + mf.calculate_fit(add_data=True) + + assert mf_table['L:intercept_0:unit'][-1] == uc.flux_unit + def test_equation_validation(specviz_helper, spectrum1d): data_label = 'test' diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index bec01c238c..849febcc93 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -22,6 +22,7 @@ from jdaviz.core.unit_conversion_utils import (all_flux_unit_conversion_equivs, check_if_unit_is_per_solid_angle, flux_conversion_general) +from jdaviz.utils import flux_conversion __all__ = ['CoordsInfo'] @@ -433,6 +434,12 @@ def _image_viewer_update(self, viewer, x, y): # use WCS to expose the wavelength for a 2d spectrum shown in pixel space try: wave, pixel = image.coords.pixel_to_world(x, y) + if wave is not None: + equivalencies = all_flux_unit_conversion_equivs(cube_wave=wave) + wave = wave.to(self.app._get_display_unit('spectral'), + equivalencies=equivalencies) + self._dict['spectral_axis'] = wave.value + self._dict['spectral_axis:unit'] = wave.unit.to_string() except Exception: # WCS might not be valid # pragma: no cover coords_status = False else: @@ -483,12 +490,23 @@ def _image_viewer_update(self, viewer, x, y): if isinstance(viewer, (ImvizImageView, MosvizImageView, MosvizProfile2DView)): value = image.get_data(attribute)[int(round(y)), int(round(x))] + if associated_dq_layers is not None: associated_dq_layer = associated_dq_layers[0] dq_attribute = associated_dq_layer.state.attribute dq_data = associated_dq_layer.layer.get_data(dq_attribute) dq_value = dq_data[int(round(y)), int(round(x))] + unit = u.Unit(image.get_component(attribute).units) + if (isinstance(viewer, MosvizProfile2DView) and unit != '' + and u.Unit(self.app._get_display_unit(attribute)).physical_type + not in ['frequency', 'wavelength', 'length'] + and unit != self.app._get_display_unit(attribute)): + equivalencies = all_flux_unit_conversion_equivs(cube_wave=wave) + value = flux_conversion(value, unit, self.app._get_display_unit(attribute), + eqv=equivalencies) + unit = self.app._get_display_unit(attribute) + elif isinstance(viewer, (CubevizImageView, RampvizImageView)): arr = image.get_component(attribute).data unit = u.Unit(image.get_component(attribute).units) @@ -543,7 +561,10 @@ def _image_viewer_update(self, viewer, x, y): else: dq_text = '' self.row1b_text = f'{value:+10.5e} {unit}{dq_text}' - self._dict['value'] = float(value) + if not isinstance(value, (float, np.floating)): + self._dict['value'] = float(value) + else: + self._dict['value'] = value self._dict['value:unit'] = str(unit) self._dict['value:unreliable'] = unreliable_pixel else: diff --git a/jdaviz/configs/mosviz/plugins/tools.py b/jdaviz/configs/mosviz/plugins/tools.py index bde5a25f82..a99ab5098c 100644 --- a/jdaviz/configs/mosviz/plugins/tools.py +++ b/jdaviz/configs/mosviz/plugins/tools.py @@ -1,6 +1,7 @@ import os from glue.config import viewer_tool +from astropy import units as u from jdaviz.configs.mosviz.plugins.viewers import MosvizProfileView, MosvizProfile2DView from jdaviz.core.tools import _MatchedZoomMixin, HomeZoom, BoxZoom, XRangeZoom, PanZoom, PanZoomX @@ -18,12 +19,43 @@ def _is_matched_viewer(self, viewer): return isinstance(viewer, (MosvizProfile2DView, MosvizProfileView)) def _map_limits(self, from_viewer, to_viewer, limits={}): + components = self.viewer.state.data_collection[0]._components + # Determine cid for spectral axis + cid = None + for key in components.keys(): + if 'Wavelength' in str(key): + cid = str(key) + break + elif 'Wave' in str(key): + cid = str(key) + break + + if cid is not None: + native_unit = u.Unit(self.viewer.state.data_collection[0].get_component(cid).units) + else: + native_unit = '' + current_display_unit = u.Unit(self.viewer.jdaviz_helper.app._get_display_unit('spectral')) + if isinstance(from_viewer, MosvizProfileView) and isinstance(to_viewer, MosvizProfile2DView): # noqa + if native_unit != current_display_unit and native_unit != '': + limits['x_min'] = (limits['x_min'] * native_unit).to_value( + current_display_unit, equivalencies=u.spectral() + ) + limits['x_max'] = (limits['x_max'] * native_unit).to_value( + current_display_unit, equivalencies=u.spectral() + ) limits['x_min'], limits['x_max'] = to_viewer.world_to_pixel_limits((limits['x_min'], limits['x_max'])) elif isinstance(from_viewer, MosvizProfile2DView) and isinstance(to_viewer, MosvizProfileView): # noqa limits['x_min'], limits['x_max'] = from_viewer.pixel_to_world_limits((limits['x_min'], limits['x_max'])) + if native_unit != current_display_unit and native_unit != '': + limits['x_min'] = (limits['x_min'] * native_unit).to_value( + current_display_unit, equivalencies=u.spectral() + ) + limits['x_max'] = (limits['x_max'] * native_unit).to_value( + current_display_unit, equivalencies=u.spectral() + ) return limits diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index 21b0865bc9..398494e729 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -222,8 +222,11 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_ label_mouseover._viewer_mouse_event(spec2d_viewer, {'event': 'mousemove', 'domain': {'x': 10, 'y': 100}}) - assert label_mouseover.as_text() == ('Pixel x=00010.0 y=00100.0 Value +8.12986e-01', - 'Wave 1.10000e-05 m', '') + + # Note: spectra2d Wave loaded in meters, but we respect one spectral unit, so the meters in + # converted to Angstrom (the spectra1d spectral unit). + assert label_mouseover.as_text() == ('Pixel x=00010.0 y=00100.0 Value +8.12986e-01 Jy', + 'Wave 1.10000e+05 Angstrom', '') assert label_mouseover.icon == 'c' # need to trigger a mouseleave or mouseover to reset the traitlets 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 6b077a14ca..7c055378d0 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 @@ -6,6 +6,7 @@ from numpy.testing import assert_allclose from regions import RectanglePixelRegion, PixCoord from specutils import Spectrum1D, SpectralRegion +from glue.core.roi import XRangeROI from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit from jdaviz.core.custom_units_and_equivs import PIX2 @@ -91,7 +92,7 @@ def test_cubeviz_units(cubeviz_helper, spectrum1d_cube_custom_fluxunit, is in flux/pix2 and flux/sr, and that the results remain consistant between translations of the spectral y axis flux<>surface brightness. """ - cube = spectrum1d_cube_custom_fluxunit(fluxunit=u.Jy / sq_angle_unit, + cube = spectrum1d_cube_custom_fluxunit(fluxunit=u.MJy / sq_angle_unit, shape=(25, 25, 4), with_uncerts=True) cubeviz_helper.load_data(cube, data_label="Test Cube") @@ -107,9 +108,46 @@ def test_cubeviz_units(cubeviz_helper, spectrum1d_cube_custom_fluxunit, results = plugin.results assert results[0]['unit'] == 'W / m2' + viewer = cubeviz_helper.app.get_viewer('spectrum-viewer') + viewer.apply_roi(XRangeROI(4.63e-7, 4.64e-7)) + + la = cubeviz_helper.plugins['Line Analysis'] + la.keep_active = True + la.spectral_subset.selected = 'Subset 1' + + marks_before = [la._obj.continuum_marks['left'].y, + la._obj.continuum_marks['right'].y] + + # change flux unit and make sure result stays the same after conversion + uc.flux_unit.selected = 'Jy' + + marks_after = [la._obj.continuum_marks['left'].y, + la._obj.continuum_marks['right'].y] + + # ensure continuum marks update when spectral_y is changed by + # multiply converted continuum marks by expected scale factor (MJy -> Jy) + scaling_factor = 1e6 + assert_allclose([mark * scaling_factor for mark in marks_before], marks_after, rtol=1e-5) + + # reset to test again after spectral_y_type is changed + marks_before = marks_after + # now change to surface brightness uc.spectral_y_type = 'Surface Brightness' + if sq_angle_unit == PIX2: + # translation does not alter spectral_y values in viewer + scaling_factor = 1 + else: + scaling_factor = cube.meta.get('PIXAR_SR') + + marks_after = [la._obj.continuum_marks['left'].y, + la._obj.continuum_marks['right'].y] + + # ensure continuum marks update when spectral_y_type is changed + # multiply converted continuum marks by expected pixel scale factor + assert_allclose([mark / scaling_factor for mark in marks_before], marks_after, rtol=1e-5) + results = plugin.results line_flux_before_unit_conversion = results[0] # convert back and forth between unit<>str for string format consistency 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 9dbc4dd800..971d99b811 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 @@ -154,3 +154,40 @@ def test_flux_unit_choices(specviz_helper, flux_unit, expected_choices): assert uc_plg.flux_unit.selected == flux_unit.to_string() assert uc_plg.flux_unit.choices == expected_choices + + +def test_mosviz_profile_view_mouseover(specviz2d_helper, spectrum2d): + data = np.zeros((5, 10)) + data[3] = np.arange(10) + spectrum2d = Spectrum1D(flux=data*u.MJy, spectral_axis=data[3]*u.um) + + specviz2d_helper.load_data(spectrum2d) + viewer = specviz2d_helper.app.get_viewer("spectrum-viewer") + plg = specviz2d_helper.plugins["Unit Conversion"] + + # make sure we don't expose angle, sb, nor spectral-y units when native + # units are in flux + assert hasattr(plg, 'flux_unit') + assert not hasattr(plg, 'angle_unit') + assert not hasattr(plg, 'sb_unit') + assert not hasattr(plg, 'spectral_y_type') + + label_mouseover = specviz2d_helper.app.session.application._tools['g-coords-info'] + label_mouseover._viewer_mouse_event(viewer, + {'event': 'mousemove', + 'domain': {'x': 5, 'y': 3}}) + + assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00', + 'Wave 5.00000e+00 um (5 pix)', + 'Flux 5.00000e+00 MJy') + + plg._obj.flux_unit_selected = 'Jy' + assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00', + 'Wave 5.00000e+00 um (5 pix)', + 'Flux 5.00000e+06 Jy') + + # test mouseover when spectral density equivalencies are required for conversion + plg._obj.flux_unit_selected = 'erg / (Angstrom s cm2)' + assert label_mouseover.as_text() == ('Cursor 5.00000e+00, 3.00000e+00', + 'Wave 5.00000e+00 um (5 pix)', + 'Flux 5.99585e-08 erg / (Angstrom s cm2)') diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index c9b6372885..ad4288cc36 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -113,9 +113,8 @@ def __init__(self, *args, **kwargs): self._cached_properties = ['image_layers'] - if self.config not in ['specviz', 'cubeviz']: - # TODO [specviz2d, mosviz] x_display_unit is not implemented in glue for image viewer - # used by spectrum-2d-viewer + if self.config not in ['specviz', 'specviz2d', 'cubeviz']: + # TODO [mosviz] x_display_unit is not implemented in glue for image viewer # TODO [mosviz]: add to yaml file # TODO [cubeviz, slice]: slice indicator broken after changing spectral_unit # TODO: support for multiple viewers and handling of mixed state from glue (or does @@ -141,7 +140,7 @@ def __init__(self, *args, **kwargs): # initialize flux choices to empty list, will be populated when data is loaded self.flux_unit.choices = [] - self.has_angle = self.config in ('cubeviz', 'specviz', 'mosviz') + self.has_angle = self.config in ('cubeviz', 'specviz', 'mosviz', 'specviz2d') self.angle_unit = UnitSelectPluginComponent(self, items='angle_unit_items', selected='angle_unit_selected') @@ -211,7 +210,6 @@ def _on_add_data_to_viewer(self, msg): or not len(self.flux_unit_selected) or not len(self.angle_unit_selected) or (self.config == 'cubeviz' and not len(self.spectral_y_type_selected))): - data_obj = msg.data.get_object() if isinstance(data_obj, Spectrum1D): @@ -237,9 +235,13 @@ def _on_add_data_to_viewer(self, msg): try: if angle_unit is None: - # default to sr if input spectrum is not in surface brightness units - # TODO: for cubeviz, should we check the cube itself? - self.angle_unit.selected = 'sr' + if self.config in ['specviz', 'specviz2d']: + self.has_angle = False + self.has_sb = False + else: + # default to pix2 if input data is not in surface brightness units + # TODO: for cubeviz, should we check the cube itself? + self.angle_unit.selected = 'pix2' else: self.angle_unit.selected = str(angle_unit) except ValueError: diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue index 2e8b6ed89a..0454e11089 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.vue @@ -49,7 +49,7 @@ hint="Solid angle unit." /> - +