From cb6c1f8c2a82fceb88b84a2fdd3c00d51f479574 Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Mon, 5 Dec 2022 18:46:52 -0500
Subject: [PATCH 1/9] Improved spectrum info panel for spectrum viewers in all
the configurations supported by Jdaviz.
---
CHANGES.rst | 8 ++
docs/specviz/displaying.rst | 10 ++
.../tests/test_gaussian_smooth.py | 123 +++++++++++++++---
.../imviz/plugins/coords_info/coords_info.py | 6 +
.../imviz/plugins/coords_info/coords_info.vue | 6 +-
jdaviz/configs/imviz/tests/test_linking.py | 1 +
.../configs/mosviz/tests/test_data_loading.py | 24 +++-
jdaviz/configs/specviz/plugins/viewers.py | 73 +++++++++--
jdaviz/configs/specviz/tests/test_helper.py | 49 ++++++-
.../configs/specviz2d/tests/test_parsers.py | 15 ++-
10 files changed, 273 insertions(+), 42 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 3da545acab..e111553568 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -110,6 +110,8 @@ Cubeviz
- Added Slice plugin player control buttons. [#1848]
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Imviz
^^^^^
@@ -130,6 +132,8 @@ Mosviz
- ``load_data`` method can now load JWST NIRCam and NIRSpec level 2 data. [#1835]
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Specviz
^^^^^^^
@@ -137,6 +141,8 @@ Specviz
- Switch to opt-in concatenation for multi-order x1d spectra. [#1659]
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Specviz2d
^^^^^^^^^
@@ -145,6 +151,8 @@ Specviz2d
- Add dropdown for choosing background statistic (average or median). [#1922]
+- Improved mouseover info display for spectrum viewer. [#1894]
+
API Changes
-----------
diff --git a/docs/specviz/displaying.rst b/docs/specviz/displaying.rst
index fd6d6c628a..08bf91879b 100644
--- a/docs/specviz/displaying.rst
+++ b/docs/specviz/displaying.rst
@@ -41,6 +41,16 @@ data menu.
.. image:: img/data_tab.png
+.. _specviz_cursor_info:
+
+Cursor Information
+==================
+
+By moving your cursor along the spectrum viewer, you will be able to see information on the
+index, spectral axis value, and flux value of the closest data point to the cursor
+(not to be confused with the actual cursor position).
+This information is displayed in the top bar of the UI, on the middle-right side.
+
Home
====
diff --git a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
index 4f9ad23781..3b887145a5 100644
--- a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
+++ b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
@@ -1,21 +1,18 @@
import pytest
-from jdaviz import Application
+from astropy.utils.exceptions import AstropyUserWarning
from specutils import Spectrum1D
-from jdaviz.configs.default.plugins.gaussian_smooth.gaussian_smooth import GaussianSmooth
-
-def test_linking_after_spectral_smooth(spectrum1d_cube):
-
- app = Application(configuration="cubeviz")
+def test_linking_after_spectral_smooth(cubeviz_helper, spectrum1d_cube):
+ app = cubeviz_helper.app
dc = app.data_collection
- app.add_data(spectrum1d_cube, 'test')
- app.add_data_to_viewer('flux-viewer', 'test')
+ cubeviz_helper.load_data(spectrum1d_cube, data_label='test')
+ spec_viewer = cubeviz_helper.app.get_viewer('spectrum-viewer')
assert len(dc) == 1
- gs = GaussianSmooth(app=app)
- gs.dataset_selected = 'test'
+ gs = cubeviz_helper.plugins['Gaussian Smooth']._obj
+ gs.dataset_selected = 'test[FLUX]'
gs.mode_selected = 'Spectral'
gs.stddev = 3.2
gs.add_to_viewer_selected = 'None'
@@ -40,8 +37,8 @@ def test_linking_after_spectral_smooth(spectrum1d_cube):
# itself is prepended to the default label, and there is no longer
# an overwrite warning.
assert len(gs.dataset_items) == 2
- assert gs.dataset_selected == 'test'
- assert gs.results_label == 'test spectral-smooth stddev-3.2'
+ assert gs.dataset_selected == 'test[FLUX]'
+ assert gs.results_label == 'test[FLUX] spectral-smooth stddev-3.2'
assert gs.results_label_overwrite is False
assert len(dc) == 2
@@ -68,21 +65,109 @@ def test_linking_after_spectral_smooth(spectrum1d_cube):
assert dc.external_links[2].cids1[0] == dc[0].pixel_component_ids[2]
assert dc.external_links[2].cids2[0] == dc[-1].pixel_component_ids[2]
+ # Mouseover should automatically jump from one spectrum
+ # to another, depending on which one is closer.
+
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 60}})
+ assert spec_viewer.label_mouseover.pixel == 'x=01.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '4.62360e-07'
+ assert spec_viewer.label_mouseover.world_dec == 'm'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '9.20000e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'a'
+
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 20}})
+ assert spec_viewer.label_mouseover.pixel == 'x=01.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '4.62360e-07'
+ assert spec_viewer.label_mouseover.world_dec == 'm'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '1.47943e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'b'
+
+ # Check mouseover behavior when we hide everything.
+ for lyr in spec_viewer.layers:
+ lyr.visible = False
+
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 60}})
+ assert spec_viewer.label_mouseover.pixel == ''
+ assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra == ''
+ assert spec_viewer.label_mouseover.world_dec == ''
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra_deg == ''
+ assert spec_viewer.label_mouseover.world_dec_deg == ''
+ assert spec_viewer.label_mouseover.icon == ''
+
-@pytest.mark.filterwarnings("ignore::UserWarning")
def test_spatial_convolution(cubeviz_helper, spectrum1d_cube):
dc = cubeviz_helper.app.data_collection
- cubeviz_helper.app.add_data(spectrum1d_cube, 'test')
- cubeviz_helper.app.add_data_to_viewer('flux-viewer', 'test')
+ cubeviz_helper.load_data(spectrum1d_cube, data_label='test')
- gs = GaussianSmooth(app=cubeviz_helper.app)
- gs.dataset_selected = 'test'
+ gs = cubeviz_helper.plugins['Gaussian Smooth']._obj
+ gs.dataset_selected = 'test[FLUX]'
gs.mode_selected = 'Spatial'
gs.stddev = 3
assert gs.results_label == 'spatial-smooth stddev-3.0'
- gs.vue_apply()
+ with pytest.warns(
+ AstropyUserWarning,
+ match='The following attributes were set on the data object, but will be ignored'):
+ gs.vue_apply()
assert len(dc) == 2
assert dc[1].label == "spatial-smooth stddev-3.0"
+ assert dc[1].shape == (2, 4, 2) # specutils moved spectral axis to last
assert (dc["spatial-smooth stddev-3.0"].get_object(cls=Spectrum1D, statistic=None).shape
- == (4, 2, 2))
+ == (2, 4, 2))
+
+
+def test_spectrum1d_smooth(specviz_helper, spectrum1d):
+ dc = specviz_helper.app.data_collection
+ specviz_helper.load_data(spectrum1d, data_label='test')
+ spec_viewer = specviz_helper.app.get_viewer('spectrum-viewer')
+
+ gs = specviz_helper.plugins['Gaussian Smooth']._obj
+ gs.dataset_selected = 'test'
+ gs.mode_selected = 'Spectral'
+ gs.stddev = 10
+ gs.vue_apply()
+
+ assert len(dc) == 2
+ assert dc[1].label == 'smooth stddev-10.0'
+
+ # Mouseover should automatically jump from one spectrum
+ # to another, depending on which one is closer.
+
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 6400, 'y': 120}})
+ assert spec_viewer.label_mouseover.pixel == 'x=02.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '6.44444e+03'
+ assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '1.35366e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'a'
+
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 6400, 'y': 5}})
+ assert spec_viewer.label_mouseover.pixel == 'x=02.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '6.44444e+03'
+ assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '5.34688e+00'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'b'
+
+ # Out-of-bounds should lock to closest edge value.
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 5500, 'y': 120}})
+ assert spec_viewer.label_mouseover.pixel == 'x=00.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '6.00000e+03'
+ assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '1.24967e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'a'
diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
index 46a0c0cfdb..b90d22a38d 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 @@ class CoordsInfo(TemplateMixin):
pixel = Unicode("").tag(sync=True)
value = Unicode("").tag(sync=True)
world_label_prefix = Unicode("\u00A0").tag(sync=True)
+ world_label_prefix_2 = Unicode("\u00A0").tag(sync=True)
world_label_icrs = Unicode("\u00A0").tag(sync=True)
world_label_deg = Unicode("\u00A0").tag(sync=True)
world_ra = Unicode("").tag(sync=True)
@@ -24,6 +25,7 @@ class CoordsInfo(TemplateMixin):
def reset_coords_display(self):
self.world_label_prefix = '\u00A0'
+ self.world_label_prefix_2 = '\u00A0'
self.world_label_icrs = '\u00A0'
self.world_label_deg = '\u00A0'
self.world_ra = ''
@@ -53,3 +55,7 @@ def set_coords(self, sky, unreliable_world=False, unreliable_pixel=False):
self.world_dec_deg = world_dec_deg
self.unreliable_world = unreliable_world
self.unreliable_pixel = unreliable_pixel
+ if unreliable_world:
+ self.world_label_prefix_2 = '(est.)'
+ else:
+ self.world_label_prefix_2 = '\u00A0'
diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.vue b/jdaviz/configs/imviz/plugins/coords_info/coords_info.vue
index d875d80a78..40f917425c 100644
--- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.vue
+++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.vue
@@ -13,13 +13,13 @@
{{ world_label_prefix }} |
- {{ world_ra }} |
+ {{ world_ra }} |
{{ world_dec }} |
{{ world_label_icrs }} |
- {{ unreliable_world ? '(est.)' : '' }} |
- {{ world_ra_deg }} |
+ {{ world_label_prefix_2 }} |
+ {{ world_ra_deg }} |
{{ world_dec_deg }} |
{{ world_label_deg }} |
diff --git a/jdaviz/configs/imviz/tests/test_linking.py b/jdaviz/configs/imviz/tests/test_linking.py
index 096d56b0f6..2ba0ed5a44 100644
--- a/jdaviz/configs/imviz/tests/test_linking.py
+++ b/jdaviz/configs/imviz/tests/test_linking.py
@@ -271,6 +271,7 @@ def test_wcslink_rotated(self):
# but cursor is outside GWCS bounding box
assert self.viewer.label_mouseover.unreliable_world
assert self.viewer.label_mouseover.unreliable_pixel
+ assert self.viewer.label_mouseover.world_label_prefix_2 == '(est.)'
class TestLink_GWCS_GWCS(BaseImviz_GWCS_GWCS):
diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py
index cb8809142c..9327e0c394 100644
--- a/jdaviz/configs/mosviz/tests/test_data_loading.py
+++ b/jdaviz/configs/mosviz/tests/test_data_loading.py
@@ -152,6 +152,8 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
spectra2d = [mos_spectrum2d] * 3
image_viewer = mosviz_helper.app.get_viewer('image-viewer')
+ spec1d_viewer = mosviz_helper.app.get_viewer('spectrum-viewer')
+ spec2d_viewer = mosviz_helper.app.get_viewer('spectrum-2d-viewer')
# Coordinates info panel should not crash even when nothing is loaded.
image_viewer.on_mouse_or_key_event({'event': 'mouseover'})
@@ -176,14 +178,15 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
assert len(qtable) == 3
# Also check coordinates info panels for Mosviz image viewer.
- # 1D spectrum viewer panel is already tested in Specviz.
- # 2D spectrum viewer panel is already tested in Specviz2d.
+ # 1D spectrum viewer panel is also tested in Specviz.
+ # 2D spectrum viewer panel is also tested in Specviz2d.
image_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 0, 'y': 0}})
assert image_viewer.label_mouseover.pixel == 'x=000.0 y=000.0'
assert image_viewer.label_mouseover.value == '+3.74540e-01 Jy'
assert image_viewer.label_mouseover.world_ra_deg == '5.0297844783'
assert image_viewer.label_mouseover.world_dec_deg == '4.9918991917'
+ assert image_viewer.label_mouseover.icon == 'a'
image_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': None, 'y': 0}})
assert image_viewer.label_mouseover.pixel == ''
@@ -203,6 +206,23 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
assert image_viewer.label_mouseover.world_ra_deg == ''
assert image_viewer.label_mouseover.world_dec_deg == ''
+ spec2d_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 10, 'y': 100}})
+ assert spec2d_viewer.label_mouseover.pixel == 'x=00010.0 y=00100.0'
+ assert spec2d_viewer.label_mouseover.value == '+8.12986e-01 '
+ assert spec2d_viewer.label_mouseover.world_ra_deg == ''
+ assert spec2d_viewer.label_mouseover.world_dec_deg == ''
+ assert spec2d_viewer.label_mouseover.icon == 'b'
+
+ spec1d_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 7000, 'y': 170}})
+ assert spec1d_viewer.label_mouseover.pixel == 'x=04.0'
+ assert spec1d_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec1d_viewer.label_mouseover.world_ra == '6.88889e+03'
+ assert spec1d_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec1d_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec1d_viewer.label_mouseover.world_ra_deg == '1.35436e+01'
+ assert spec1d_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec1d_viewer.label_mouseover.icon == 'c'
+
def test_zip_error(mosviz_helper, tmp_path):
'''
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index 5a68c99b61..92d94e640c 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -1,17 +1,16 @@
-import numpy as np
+import math
import warnings
+import numpy as np
+from astropy import table
+from astropy import units as u
from glue.core import BaseData
from glue.core.subset import Subset
from glue.config import data_translator
from glue_jupyter.bqplot.profile import BqplotProfileView
from glue.core.exceptions import IncompatibleAttribute
-
-from astropy import table
-from specutils import Spectrum1D
from matplotlib.colors import cnames
-from astropy import units as u
-
+from specutils import Spectrum1D
from jdaviz.core.events import SpectralMarksChangedMessage, LineIdentifyMessage
from jdaviz.core.registries import viewer_registry
@@ -66,6 +65,9 @@ def on_mouse_or_key_event(self, data):
return
if data['event'] == 'mousemove':
+ if len(self.jdaviz_app.data_collection) < 1:
+ return
+
# Extract data coordinates - these are pixels in the reference image
x = data['domain']['x']
y = data['domain']['y']
@@ -76,16 +78,61 @@ def on_mouse_or_key_event(self, data):
self.label_mouseover.value = ""
return
- fmt = 'x={:+10.5e} y={:+10.5e}'
- self.label_mouseover.pixel = fmt.format(x, y)
+ # Snap to the closest data point, not the actual mouse location.
+ sp = None
+ closest_i = 0
+ closest_wave = 0
+ closest_flux = 0
+ closest_maxsize = 0
+ closest_label = ''
+ closest_distance = None
+ for lyr in self.state.layers:
+ if ((not isinstance(lyr.layer, BaseData)) or (lyr.layer.ndim not in (1, 3))
+ or (not lyr.visible)):
+ continue
+
+ try:
+ # TODO: Is there a way to cache this?
+ sp = lyr.layer.get_object(
+ cls=Spectrum1D, statistic=getattr(self.state, 'function', None))
+
+ cur_i = np.argmin(abs(sp.spectral_axis.value - x))
+ cur_wave = sp.spectral_axis[cur_i]
+ cur_flux = sp.flux[cur_i]
+
+ dx = cur_wave.value - x
+ dy = cur_flux.value - y
+ cur_distance = math.sqrt(dx * dx + dy * dy)
+ if (closest_distance is None) or (cur_distance < closest_distance):
+ closest_distance = cur_distance
+ closest_i = cur_i
+ closest_wave = cur_wave
+ closest_flux = cur_flux
+ closest_maxsize = int(np.ceil(np.log10(sp.spectral_axis.size))) + 3
+ closest_label = self.jdaviz_app.state.layer_icons.get(lyr.layer.label)
+ except Exception:
+ sp = None
+
+ if sp is None: # Something is loaded but not the right thing
+ self.label_mouseover.icon = ""
+ self.label_mouseover.pixel = ""
+ self.label_mouseover.reset_coords_display()
+ self.label_mouseover.value = ""
+ return
- # We just want cursor position, so these are not used.
- self.label_mouseover.icon = ''
- self.label_mouseover.reset_coords_display()
- self.label_mouseover.value = ''
+ fmt = 'x={:0' + str(closest_maxsize) + '.1f}'
+ self.label_mouseover.pixel = fmt.format(closest_i)
+ self.label_mouseover.world_label_prefix = 'Wave'
+ self.label_mouseover.world_ra = f'{closest_wave.value:10.5e}'
+ self.label_mouseover.world_dec = closest_wave.unit.to_string()
+ self.label_mouseover.world_label_prefix_2 = 'Flux'
+ self.label_mouseover.world_ra_deg = f'{closest_flux.value:10.5e}'
+ self.label_mouseover.world_dec_deg = closest_flux.unit.to_string()
+ self.label_mouseover.icon = closest_label
+ self.label_mouseover.value = "" # Not used
elif data['event'] == 'mouseleave' or data['event'] == 'mouseenter':
-
+ self.label_mouseover.icon = ""
self.label_mouseover.pixel = ""
self.label_mouseover.reset_coords_display()
self.label_mouseover.value = ""
diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py
index abfbe8708e..e871862b9c 100644
--- a/jdaviz/configs/specviz/tests/test_helper.py
+++ b/jdaviz/configs/specviz/tests/test_helper.py
@@ -204,16 +204,43 @@ def test_get_spectral_regions_unit(specviz_helper, spectrum1d):
def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d):
+ spec_viewer = specviz_helper.app.get_viewer('spectrum-viewer')
+
+ # Mouseover without data should not crash.
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 6100, 'y': 12.5}})
+ assert spec_viewer.label_mouseover.pixel == ''
+ assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra == ''
+ assert spec_viewer.label_mouseover.world_dec == ''
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra_deg == ''
+ assert spec_viewer.label_mouseover.world_dec_deg == ''
+ assert spec_viewer.label_mouseover.icon == ''
+
# If the reference (visible) data changes via unit conversion,
# check that the region's units convert too
specviz_helper.load_spectrum(spectrum1d)
- # Also check coordinates info panel
- spec_viewer = specviz_helper.app.get_viewer('spectrum-viewer')
+ # Also check coordinates info panel.
+ # x=0 -> 6000 A, x=1 -> 6222.222 A
spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 6100, 'y': 12.5}})
- assert spec_viewer.label_mouseover.pixel == 'x=+6.10000e+03 y=+1.25000e+01'
+ assert spec_viewer.label_mouseover.pixel == 'x=00.0' # Actual: x=00.4
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '6.00000e+03'
+ assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '1.24967e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'a'
spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': None, 'y': 12.5}})
assert spec_viewer.label_mouseover.pixel == ''
+ assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra == ''
+ assert spec_viewer.label_mouseover.world_dec == ''
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra_deg == ''
+ assert spec_viewer.label_mouseover.world_dec_deg == ''
+ assert spec_viewer.label_mouseover.icon == 'a'
# Convert the wavelength axis to microns
new_spectral_axis = "micron"
@@ -238,9 +265,23 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d):
# Coordinates info panel should show new unit
spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 0.61, 'y': 12.5}})
- assert spec_viewer.label_mouseover.pixel == 'x=+6.10000e-01 y=+1.25000e+01'
+ assert spec_viewer.label_mouseover.pixel == 'x=00.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '6.00000e-01'
+ assert spec_viewer.label_mouseover.world_dec == 'micron'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '1.24967e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
+ assert spec_viewer.label_mouseover.icon == 'b'
spec_viewer.on_mouse_or_key_event({'event': 'mouseleave'})
assert spec_viewer.label_mouseover.pixel == ''
+ assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra == ''
+ assert spec_viewer.label_mouseover.world_dec == ''
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra_deg == ''
+ assert spec_viewer.label_mouseover.world_dec_deg == ''
+ assert spec_viewer.label_mouseover.icon == ''
def test_subset_default_thickness(specviz_helper, spectrum1d):
diff --git a/jdaviz/configs/specviz2d/tests/test_parsers.py b/jdaviz/configs/specviz2d/tests/test_parsers.py
index 122a10fa29..90b8fe147f 100644
--- a/jdaviz/configs/specviz2d/tests/test_parsers.py
+++ b/jdaviz/configs/specviz2d/tests/test_parsers.py
@@ -68,13 +68,26 @@ def test_2d_parser_no_unit(specviz2d_helper, mos_spectrum2d):
assert dc_1.label == 'Spectrum 1D'
assert dc_1.get_component('flux').units == dc_0.get_component('flux').units
- # Also check the coordinates info panel.
+ # Also check the coordinates info panels.
+
viewer_2d = specviz2d_helper.app.get_viewer('spectrum-2d-viewer')
viewer_2d.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 0, 'y': 0}})
assert viewer_2d.label_mouseover.pixel == 'x=00000.0 y=00000.0'
assert viewer_2d.label_mouseover.value == '+3.74540e-01 '
assert viewer_2d.label_mouseover.world_ra_deg == ''
assert viewer_2d.label_mouseover.world_dec_deg == ''
+ assert viewer_2d.label_mouseover.icon == 'a'
+
+ viewer_1d = specviz2d_helper.app.get_viewer('spectrum-viewer')
+ viewer_1d.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 6.5, 'y': 3}})
+ assert viewer_1d.label_mouseover.pixel == 'x=006.0'
+ assert viewer_1d.label_mouseover.world_label_prefix == 'Wave'
+ assert viewer_1d.label_mouseover.world_ra == '6.00000e+00'
+ assert viewer_1d.label_mouseover.world_dec == 'pix'
+ assert viewer_1d.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert viewer_1d.label_mouseover.world_ra_deg == '-3.59571e+00'
+ assert viewer_1d.label_mouseover.world_dec_deg == ''
+ assert viewer_1d.label_mouseover.icon == 'b'
def test_1d_parser(specviz2d_helper, spectrum1d):
From 5750d6ffbdad0ab0c3d32b4463882a9b4d386fc7 Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Thu, 29 Dec 2022 18:08:36 -0500
Subject: [PATCH 2/9] BUG: Fix spectrum mouseover when out of bounds.
---
.../tests/test_gaussian_smooth.py | 24 ++++++++--------
jdaviz/configs/specviz/plugins/viewers.py | 14 ++++++----
jdaviz/configs/specviz/tests/test_helper.py | 28 +++++++++++++++++++
3 files changed, 49 insertions(+), 17 deletions(-)
diff --git a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
index 3b887145a5..be711c342a 100644
--- a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
+++ b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py
@@ -68,7 +68,7 @@ def test_linking_after_spectral_smooth(cubeviz_helper, spectrum1d_cube):
# Mouseover should automatically jump from one spectrum
# to another, depending on which one is closer.
- spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 60}})
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.6236e-7, 'y': 60}})
assert spec_viewer.label_mouseover.pixel == 'x=01.0'
assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
assert spec_viewer.label_mouseover.world_ra == '4.62360e-07'
@@ -78,7 +78,7 @@ def test_linking_after_spectral_smooth(cubeviz_helper, spectrum1d_cube):
assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
assert spec_viewer.label_mouseover.icon == 'a'
- spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 20}})
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.6236e-7, 'y': 20}})
assert spec_viewer.label_mouseover.pixel == 'x=01.0'
assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
assert spec_viewer.label_mouseover.world_ra == '4.62360e-07'
@@ -92,7 +92,7 @@ def test_linking_after_spectral_smooth(cubeviz_helper, spectrum1d_cube):
for lyr in spec_viewer.layers:
lyr.visible = False
- spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.633e-7, 'y': 60}})
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 4.6236e-7, 'y': 60}})
assert spec_viewer.label_mouseover.pixel == ''
assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
assert spec_viewer.label_mouseover.world_ra == ''
@@ -161,13 +161,13 @@ def test_spectrum1d_smooth(specviz_helper, spectrum1d):
assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
assert spec_viewer.label_mouseover.icon == 'b'
- # Out-of-bounds should lock to closest edge value.
+ # Out-of-bounds shows nothing.
spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 5500, 'y': 120}})
- assert spec_viewer.label_mouseover.pixel == 'x=00.0'
- assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
- assert spec_viewer.label_mouseover.world_ra == '6.00000e+03'
- assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
- assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
- assert spec_viewer.label_mouseover.world_ra_deg == '1.24967e+01'
- assert spec_viewer.label_mouseover.world_dec_deg == 'Jy'
- assert spec_viewer.label_mouseover.icon == 'a'
+ assert spec_viewer.label_mouseover.pixel == ''
+ assert spec_viewer.label_mouseover.world_label_prefix == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra == ''
+ assert spec_viewer.label_mouseover.world_dec == ''
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == '\xa0'
+ assert spec_viewer.label_mouseover.world_ra_deg == ''
+ assert spec_viewer.label_mouseover.world_dec_deg == ''
+ assert spec_viewer.label_mouseover.icon == ''
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index 92d94e640c..25edd790e8 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -80,9 +80,9 @@ def on_mouse_or_key_event(self, data):
# Snap to the closest data point, not the actual mouse location.
sp = None
- closest_i = 0
- closest_wave = 0
- closest_flux = 0
+ closest_i = None
+ closest_wave = None
+ closest_flux = None
closest_maxsize = 0
closest_label = ''
closest_distance = None
@@ -96,6 +96,10 @@ def on_mouse_or_key_event(self, data):
sp = lyr.layer.get_object(
cls=Spectrum1D, statistic=getattr(self.state, 'function', None))
+ # Out of range in spectral axis.
+ if x < sp.spectral_axis.value.min() or x > sp.spectral_axis.value.max():
+ continue
+
cur_i = np.argmin(abs(sp.spectral_axis.value - x))
cur_wave = sp.spectral_axis[cur_i]
cur_flux = sp.flux[cur_i]
@@ -110,10 +114,10 @@ def on_mouse_or_key_event(self, data):
closest_flux = cur_flux
closest_maxsize = int(np.ceil(np.log10(sp.spectral_axis.size))) + 3
closest_label = self.jdaviz_app.state.layer_icons.get(lyr.layer.label)
- except Exception:
+ except Exception: # Something is loaded but not the right thing # pragma: no cover
sp = None
- if sp is None: # Something is loaded but not the right thing
+ if sp is None or closest_wave is None:
self.label_mouseover.icon = ""
self.label_mouseover.pixel = ""
self.label_mouseover.reset_coords_display()
diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py
index e871862b9c..dbcd138efe 100644
--- a/jdaviz/configs/specviz/tests/test_helper.py
+++ b/jdaviz/configs/specviz/tests/test_helper.py
@@ -1,5 +1,6 @@
from zipfile import ZipFile
+import numpy as np
import pytest
from astropy import units as u
from astropy.tests.helper import assert_quantity_allclose
@@ -381,3 +382,30 @@ def test_data_label_as_posarg(specviz_helper, spectrum1d):
# Passing in data_label keyword as posarg.
specviz_helper.load_spectrum(spectrum1d, 'my_spec')
assert specviz_helper.app.data_collection[0].label == 'my_spec'
+
+
+def test_spectra_partial_overlap(specviz_helper):
+ spec_viewer = specviz_helper.app.get_viewer('spectrum-viewer')
+
+ wave_1 = np.linspace(6000, 7000, 10) * u.AA
+ flux_1 = ([1200] * wave_1.size) * u.nJy
+ sp_1 = Spectrum1D(flux=flux_1, spectral_axis=wave_1)
+
+ wave_2 = wave_1 + (800 * u.AA)
+ flux_2 = ([60] * wave_2.size) * u.nJy
+ sp_2 = Spectrum1D(flux=flux_2, spectral_axis=wave_2)
+
+ specviz_helper.load_spectrum(sp_1, data_label='left')
+ specviz_helper.load_spectrum(sp_2, data_label='right')
+
+ # Test mouseover outside of left but in range for right.
+ # Should show right spectrum even when mouse is near left flux.
+ spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 7022, 'y': 1000}})
+ assert spec_viewer.label_mouseover.pixel == 'x=02.0'
+ assert spec_viewer.label_mouseover.world_label_prefix == 'Wave'
+ assert spec_viewer.label_mouseover.world_ra == '7.02222e+03'
+ assert spec_viewer.label_mouseover.world_dec == 'Angstrom'
+ assert spec_viewer.label_mouseover.world_label_prefix_2 == 'Flux'
+ assert spec_viewer.label_mouseover.world_ra_deg == '6.00000e+01'
+ assert spec_viewer.label_mouseover.world_dec_deg == 'nJy'
+ assert spec_viewer.label_mouseover.icon == 'b'
From 1fdb0770907665aac2500a31e4cf6da83583a290 Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Fri, 30 Dec 2022 12:54:25 -0500
Subject: [PATCH 3/9] PERF: Decrease lag for Cubeviz spec1d mouseover. Also
remove unnecessary re-computation in the internals. A new internal cache on
app level is introduced.
---
jdaviz/app.py | 7 +++
jdaviz/configs/specviz/plugins/viewers.py | 73 +++++++++++++----------
2 files changed, 48 insertions(+), 32 deletions(-)
diff --git a/jdaviz/app.py b/jdaviz/app.py
index ca89b66dba..fcc426deef 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -258,6 +258,10 @@ def __init__(self, configuration=None, *args, **kwargs):
# Add a fitted_models dictionary that the helpers (or user) can access
self.fitted_models = {}
+ # Internal cache so we don't have to keep calling get_object for the same Data.
+ # Key should be (data_label, statistic) and value the translated object.
+ self._get_object_cache = {}
+
# Add new and inverse colormaps to Glue global state. Also see ColormapRegistry in
# https://github.com/glue-viz/glue/blob/main/glue/config.py
new_cms = (['Rainbow', cm.rainbow],
@@ -1493,6 +1497,9 @@ def _on_data_deleted(self, msg):
if data_item['name'] == msg.data.label:
self.state.data_items.remove(data_item)
+ # Hard to know what is associated with what, so just clear everything.
+ self._get_object_cache.clear()
+
@staticmethod
def _create_data_item(data):
ndims = len(data.shape)
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index 25edd790e8..f6868212d6 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -92,9 +92,15 @@ def on_mouse_or_key_event(self, data):
continue
try:
- # TODO: Is there a way to cache this?
- sp = lyr.layer.get_object(
- cls=Spectrum1D, statistic=getattr(self.state, 'function', None))
+ # Cache should have been populated when spectrum was first plotted.
+ # But if not (maybe user changed statistic), we cache it here too.
+ statistic = getattr(self.state, 'function', None)
+ cache_key = (lyr.layer.label, statistic)
+ if cache_key in self.jdaviz_app._get_object_cache:
+ sp = self.jdaviz_app._get_object_cache[cache_key]
+ else:
+ sp = lyr.layer.get_object(cls=Spectrum1D, statistic=statistic)
+ self.jdaviz_app._get_object_cache[cache_key] = sp
# Out of range in spectral axis.
if x < sp.spectral_axis.value.min() or x > sp.spectral_axis.value.max():
@@ -148,40 +154,39 @@ def _expected_subset_layer_default(self, layer_state):
def data(self, cls=None):
# Grab the user's chosen statistic for collapsing data
- if hasattr(self.state, 'function'):
- statistic = self.state.function
- else:
- statistic = None
-
+ statistic = getattr(self.state, 'function', None)
data = []
for layer_state in self.state.layers:
if hasattr(layer_state, 'layer'):
+ lyr = layer_state.layer
# For raw data, just include the data itself
- if isinstance(layer_state.layer, BaseData):
+ if isinstance(lyr, BaseData):
_class = cls or self.default_class
if _class is not None:
- # If spectrum, collapse via the defined statistic
- if _class == Spectrum1D:
- layer_data = layer_state.layer.get_object(cls=_class,
- statistic=statistic)
+ cache_key = (lyr.label, statistic)
+ if cache_key in self.jdaviz_app._get_object_cache:
+ layer_data = self.jdaviz_app._get_object_cache[cache_key]
else:
- layer_data = layer_state.layer.get_object(cls=_class)
+ # If spectrum, collapse via the defined statistic
+ if _class == Spectrum1D:
+ layer_data = lyr.get_object(cls=_class, statistic=statistic)
+ else:
+ layer_data = lyr.get_object(cls=_class)
+ self.jdaviz_app._get_object_cache[cache_key] = layer_data
data.append(layer_data)
- # For subsets, make sure to apply the subset mask to the
- # layer data first
- elif isinstance(layer_state.layer, Subset):
- layer_data = layer_state.layer
+ # For subsets, make sure to apply the subset mask to the layer data first
+ elif isinstance(lyr, Subset):
+ layer_data = lyr
if _class is not None:
handler, _ = data_translator.get_handler_for(_class)
try:
- layer_data = handler.to_object(layer_data,
- statistic=statistic)
+ layer_data = handler.to_object(layer_data, statistic=statistic)
except IncompatibleAttribute:
continue
data.append(layer_data)
@@ -465,18 +470,20 @@ def _plot_mask(self):
# Loop through all active data in the viewer
for index, layer_state in enumerate(self.state.layers):
- comps = [str(component) for component in layer_state.layer.components]
+ lyr = layer_state.layer
+ comps = [str(component) for component in lyr.components]
# Skip subsets
- if hasattr(layer_state.layer, "subset_state"):
+ if hasattr(lyr, "subset_state"):
continue
# Ignore data that does not have a mask component
if "mask" in comps:
- mask = np.array(layer_state.layer['mask'].data)
+ mask = np.array(lyr['mask'].data)
- data_x = layer_state.layer.data.get_object().spectral_axis
- data_y = layer_state.layer.data.get_object().flux.value
+ data_obj = lyr.data.get_object()
+ data_x = data_obj.spectral_axis.value
+ data_y = data_obj.flux.value
# For plotting markers only for the masked data
# points, erase un-masked data from trace.
@@ -507,24 +514,26 @@ def _plot_uncertainties(self):
# Loop through all active data in the viewer
for index, layer_state in enumerate(self.state.layers):
+ lyr = layer_state.layer
# Skip subsets
- if hasattr(layer_state.layer, "subset_state"):
+ if hasattr(lyr, "subset_state"):
continue
- comps = [str(component) for component in layer_state.layer.components]
+ comps = [str(component) for component in lyr.components]
# Ignore data that does not have an uncertainty component
if "uncertainty" in comps: # noqa
- error = np.array(layer_state.layer['uncertainty'].data)
+ error = np.array(lyr['uncertainty'].data)
- data_x = layer_state.layer.data.get_object().spectral_axis
- data_y = layer_state.layer.data.get_object().flux.value
+ data_obj = lyr.data.get_object()
+ data_x = data_obj.spectral_axis.value
+ data_y = data_obj.flux.value
# The shaded band around the spectrum trace is bounded by
# two lines, above and below the spectrum trace itself.
- x = [np.ndarray.tolist(data_x),
- np.ndarray.tolist(data_x)]
+ data_x_list = np.ndarray.tolist(data_x)
+ x = [data_x_list, data_x_list]
y = [np.ndarray.tolist(data_y - error),
np.ndarray.tolist(data_y + error)]
From c6b3912f8e801649a3d4afe72038ced749f6fd59 Mon Sep 17 00:00:00 2001
From: Kyle Conroy
Date: Thu, 29 Dec 2022 15:10:41 -0500
Subject: [PATCH 4/9] basic implementation of mouseover marker
* only used in spectrum profile viewers
* currently ALWAYS on
---
.../imviz/plugins/coords_info/coords_info.py | 23 ++++++++++++++++++
jdaviz/configs/specviz/plugins/viewers.py | 4 ++++
jdaviz/core/marks.py | 24 ++++++++++++++-----
3 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
index b90d22a38d..032c0c0271 100644
--- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
+++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py
@@ -1,7 +1,9 @@
from traitlets import Bool, Unicode
+from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView
from jdaviz.core.registries import tool_registry
from jdaviz.core.template_mixin import TemplateMixin
+from jdaviz.core.marks import PluginScatter
__all__ = ['CoordsInfo']
@@ -23,6 +25,27 @@ class CoordsInfo(TemplateMixin):
unreliable_world = Bool(False).tag(sync=True)
unreliable_pixel = Bool(False).tag(sync=True)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._marks = {}
+
+ @property
+ def marks(self):
+ """
+ Access the marks created by this plugin.
+ """
+ if self._marks:
+ # TODO: replace with cache property?
+ return self._marks
+
+ # create marks for each of the spectral viewers (will need a listener event to create marks
+ # for new viewers if dynamic creation of spectral viewers is ever supported)
+ for id, viewer in self.app._viewer_store.items():
+ if isinstance(viewer, SpecvizProfileView):
+ self._marks[id] = PluginScatter(viewer, visible=False)
+ viewer.figure.marks = viewer.figure.marks + [self._marks[id]]
+ return self._marks
+
def reset_coords_display(self):
self.world_label_prefix = '\u00A0'
self.world_label_prefix_2 = '\u00A0'
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index f6868212d6..5e8011c052 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -128,6 +128,7 @@ def on_mouse_or_key_event(self, data):
self.label_mouseover.pixel = ""
self.label_mouseover.reset_coords_display()
self.label_mouseover.value = ""
+ self.label_mouseover.marks[self._reference_id].visible = False
return
fmt = 'x={:0' + str(closest_maxsize) + '.1f}'
@@ -140,12 +141,15 @@ def on_mouse_or_key_event(self, data):
self.label_mouseover.world_dec_deg = closest_flux.unit.to_string()
self.label_mouseover.icon = closest_label
self.label_mouseover.value = "" # Not used
+ self.label_mouseover.marks[self._reference_id].update_xy([closest_wave.value], [closest_flux.value])
+ self.label_mouseover.marks[self._reference_id].visible = True
elif data['event'] == 'mouseleave' or data['event'] == 'mouseenter':
self.label_mouseover.icon = ""
self.label_mouseover.pixel = ""
self.label_mouseover.reset_coords_display()
self.label_mouseover.value = ""
+ self.label_mouseover.marks[self._reference_id].visible = False
def _expected_subset_layer_default(self, layer_state):
super()._expected_subset_layer_default(layer_state)
diff --git a/jdaviz/core/marks.py b/jdaviz/core/marks.py
index 110cc87deb..955850e54b 100644
--- a/jdaviz/core/marks.py
+++ b/jdaviz/core/marks.py
@@ -13,7 +13,7 @@
__all__ = ['OffscreenLinesMarks', 'BaseSpectrumVerticalLine', 'SpectralLine',
'SliceIndicatorMarks', 'ShadowMixin', 'ShadowLine', 'ShadowLabelFixedY',
- 'PluginLine',
+ 'PluginMark', 'PluginLine', 'PluginScatter',
'LineAnalysisContinuum', 'LineAnalysisContinuumCenter',
'LineAnalysisContinuumLeft', 'LineAnalysisContinuumRight',
'LineUncertainties', 'ScatterMask', 'SelectedSpaxel']
@@ -484,19 +484,31 @@ def _on_shadowing_changed(self, change):
self._update_align()
-class PluginLine(Lines, HubListener):
- def __init__(self, viewer, x=[], y=[], **kwargs):
- # color is same blue as import button
- super().__init__(x=x, y=y, colors=["#007BA1"], scales=viewer.scales, **kwargs)
-
+class PluginMark():
def update_xy(self, x, y):
self.x = np.asarray(x)
self.y = np.asarray(y)
+ def append_xy(self, x, y):
+ self.x = np.append(self.x, x)
+ self.y = np.append(self.y, y)
+
def clear(self):
self.update_xy([], [])
+class PluginLine(Lines, PluginMark, HubListener):
+ def __init__(self, viewer, x=[], y=[], **kwargs):
+ # color is same blue as import button
+ super().__init__(x=x, y=y, colors=["#007BA1"], scales=viewer.scales, **kwargs)
+
+
+class PluginScatter(Scatter, PluginMark, HubListener):
+ def __init__(self, viewer, x=[], y=[], **kwargs):
+ # color is same blue as import button
+ super().__init__(x=x, y=y, colors=["#007BA1"], scales=viewer.scales, **kwargs)
+
+
class LineAnalysisContinuum(PluginLine):
pass
From 111746dd9cf9bc839a0423486d7e92ef38686d09 Mon Sep 17 00:00:00 2001
From: Kyle Conroy
Date: Fri, 30 Dec 2022 09:54:32 -0500
Subject: [PATCH 5/9] only show marker (and nearest spectrum details) if no
tool selected
(or default tool - slice in cubeviz, for example)
---
jdaviz/configs/specviz/plugins/viewers.py | 33 ++++++++++++++---------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index 5e8011c052..7606cfa262 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -131,18 +131,27 @@ def on_mouse_or_key_event(self, data):
self.label_mouseover.marks[self._reference_id].visible = False
return
- fmt = 'x={:0' + str(closest_maxsize) + '.1f}'
- self.label_mouseover.pixel = fmt.format(closest_i)
- self.label_mouseover.world_label_prefix = 'Wave'
- self.label_mouseover.world_ra = f'{closest_wave.value:10.5e}'
- self.label_mouseover.world_dec = closest_wave.unit.to_string()
- self.label_mouseover.world_label_prefix_2 = 'Flux'
- self.label_mouseover.world_ra_deg = f'{closest_flux.value:10.5e}'
- self.label_mouseover.world_dec_deg = closest_flux.unit.to_string()
- self.label_mouseover.icon = closest_label
- self.label_mouseover.value = "" # Not used
- self.label_mouseover.marks[self._reference_id].update_xy([closest_wave.value], [closest_flux.value])
- self.label_mouseover.marks[self._reference_id].visible = True
+ # show the locked marker/coords only if either no tool or the default tool is active
+ locking_active = self.toolbar.active_tool_id in self.toolbar.default_tool_priority + [None] # noqa
+ if locking_active:
+ fmt = 'x={:0' + str(closest_maxsize) + '.1f}'
+ self.label_mouseover.pixel = fmt.format(closest_i)
+ self.label_mouseover.world_label_prefix = 'Wave'
+ self.label_mouseover.world_ra = f'{closest_wave.value:10.5e}'
+ self.label_mouseover.world_dec = closest_wave.unit.to_string()
+ self.label_mouseover.world_label_prefix_2 = 'Flux'
+ self.label_mouseover.world_ra_deg = f'{closest_flux.value:10.5e}'
+ self.label_mouseover.world_dec_deg = closest_flux.unit.to_string()
+ self.label_mouseover.icon = closest_label
+ self.label_mouseover.value = "" # Not used
+ self.label_mouseover.marks[self._reference_id].update_xy([closest_wave.value], [closest_flux.value]) # noqa
+ self.label_mouseover.marks[self._reference_id].visible = True
+ else:
+ # show exact plot coordinates (useful for drawing spectral subsets or zoom ranges)
+ fmt = 'x={:+10.5e} y={:+10.5e}'
+ self.label_mouseover.icon = ""
+ self.label_mouseover.pixel = fmt.format(x, y)
+ self.label_mouseover.marks[self._reference_id].visible = False
elif data['event'] == 'mouseleave' or data['event'] == 'mouseenter':
self.label_mouseover.icon = ""
From ae22338481fc61ecab69ba71fe8b724669cf2dbc Mon Sep 17 00:00:00 2001
From: Kyle Conroy
Date: Wed, 4 Jan 2023 11:27:29 -0500
Subject: [PATCH 6/9] support for mouseover of spatial subsets in cubeviz
* including clearing cache when subset is changed and ignoring hidden layers
---
jdaviz/app.py | 14 ++++++++++++--
jdaviz/configs/specviz/plugins/viewers.py | 21 +++++++++++++++------
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/jdaviz/app.py b/jdaviz/app.py
index fcc426deef..cf51194fb5 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -28,6 +28,7 @@
from glue.core.message import (DataCollectionAddMessage,
DataCollectionDeleteMessage,
SubsetCreateMessage,
+ SubsetUpdateMessage,
SubsetDeleteMessage)
from glue.core.state_objects import State
from glue.core.subset import Subset, RangeSubsetState, RoiSubsetState
@@ -261,6 +262,8 @@ def __init__(self, configuration=None, *args, **kwargs):
# Internal cache so we don't have to keep calling get_object for the same Data.
# Key should be (data_label, statistic) and value the translated object.
self._get_object_cache = {}
+ self.hub.subscribe(self, SubsetUpdateMessage,
+ handler=lambda msg: self._clear_object_cache(msg.subset.label))
# Add new and inverse colormaps to Glue global state. Also see ColormapRegistry in
# https://github.com/glue-viz/glue/blob/main/glue/config.py
@@ -1482,6 +1485,14 @@ def _on_data_added(self, msg):
data_item = self._create_data_item(msg.data)
self.state.data_items.append(data_item)
+ def _clear_object_cache(self, data_label=None):
+ if data_label is None:
+ self._get_object_cache.clear()
+ else:
+ # keys are (data_label, statistic) tuples
+ self._get_object_cache = {k: v for k, v in self._get_object_cache.items()
+ if k[0] != data_label}
+
def _on_data_deleted(self, msg):
"""
Callback for when data is removed from the internal ``DataCollection``.
@@ -1497,8 +1508,7 @@ def _on_data_deleted(self, msg):
if data_item['name'] == msg.data.label:
self.state.data_items.remove(data_item)
- # Hard to know what is associated with what, so just clear everything.
- self._get_object_cache.clear()
+ self._clear_object_cache(msg.data.label)
@staticmethod
def _create_data_item(data):
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index 7606cfa262..fd68528f43 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -5,7 +5,8 @@
from astropy import table
from astropy import units as u
from glue.core import BaseData
-from glue.core.subset import Subset
+from glue.core.subset import Subset, RoiSubsetState
+from glue.core.subset_group import GroupedSubset
from glue.config import data_translator
from glue_jupyter.bqplot.profile import BqplotProfileView
from glue.core.exceptions import IncompatibleAttribute
@@ -87,7 +88,13 @@ def on_mouse_or_key_event(self, data):
closest_label = ''
closest_distance = None
for lyr in self.state.layers:
- if ((not isinstance(lyr.layer, BaseData)) or (lyr.layer.ndim not in (1, 3))
+ if not lyr.visible:
+ continue
+ if isinstance(lyr.layer, GroupedSubset):
+ if not isinstance(lyr.layer.subset_state, RoiSubsetState):
+ # then this is a SPECTRAL subset
+ continue
+ elif ((not isinstance(lyr.layer, BaseData)) or (lyr.layer.ndim not in (1, 3))
or (not lyr.visible)):
continue
@@ -99,7 +106,8 @@ def on_mouse_or_key_event(self, data):
if cache_key in self.jdaviz_app._get_object_cache:
sp = self.jdaviz_app._get_object_cache[cache_key]
else:
- sp = lyr.layer.get_object(cls=Spectrum1D, statistic=statistic)
+ sp = self.jdaviz_app.get_data_from_viewer('spectrum-viewer',
+ lyr.layer.label)
self.jdaviz_app._get_object_cache[cache_key] = sp
# Out of range in spectral axis.
@@ -120,10 +128,11 @@ def on_mouse_or_key_event(self, data):
closest_flux = cur_flux
closest_maxsize = int(np.ceil(np.log10(sp.spectral_axis.size))) + 3
closest_label = self.jdaviz_app.state.layer_icons.get(lyr.layer.label)
- except Exception: # Something is loaded but not the right thing # pragma: no cover
- sp = None
+ except Exception: # nosec
+ # Something is loaded but not the right thing
+ continue
- if sp is None or closest_wave is None:
+ if closest_wave is None:
self.label_mouseover.icon = ""
self.label_mouseover.pixel = ""
self.label_mouseover.reset_coords_display()
From 463bae985f0f68dc4b8ea69f47373a6b8b4c3647 Mon Sep 17 00:00:00 2001
From: Kyle Conroy
Date: Thu, 5 Jan 2023 09:26:54 -0500
Subject: [PATCH 7/9] remilestone changelog to 3.3
---
CHANGES.rst | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index e111553568..e34136fc60 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,18 +7,26 @@ New Features
Cubeviz
^^^^^^^
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Imviz
^^^^^
Mosviz
^^^^^^
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Specviz
^^^^^^^
+- Improved mouseover info display for spectrum viewer. [#1894]
+
Specviz2d
^^^^^^^^^
+- Improved mouseover info display for spectrum viewer. [#1894]
+
API Changes
-----------
@@ -110,8 +118,6 @@ Cubeviz
- Added Slice plugin player control buttons. [#1848]
-- Improved mouseover info display for spectrum viewer. [#1894]
-
Imviz
^^^^^
@@ -132,8 +138,6 @@ Mosviz
- ``load_data`` method can now load JWST NIRCam and NIRSpec level 2 data. [#1835]
-- Improved mouseover info display for spectrum viewer. [#1894]
-
Specviz
^^^^^^^
@@ -141,8 +145,6 @@ Specviz
- Switch to opt-in concatenation for multi-order x1d spectra. [#1659]
-- Improved mouseover info display for spectrum viewer. [#1894]
-
Specviz2d
^^^^^^^^^
@@ -151,8 +153,6 @@ Specviz2d
- Add dropdown for choosing background statistic (average or median). [#1922]
-- Improved mouseover info display for spectrum viewer. [#1894]
-
API Changes
-----------
From 6cb7ff26e5421ad860008dfe0b36d5a8f483c6e2 Mon Sep 17 00:00:00 2001
From: "Brett M. Morris"
Date: Tue, 17 Jan 2023 16:33:06 -0500
Subject: [PATCH 8/9] creating new setting for showing mouseover marker
---
jdaviz/app.py | 1 +
.../configs/default/plugins/plot_options/plot_options.py | 4 ++++
.../configs/default/plugins/plot_options/plot_options.vue | 8 ++++++++
3 files changed, 13 insertions(+)
diff --git a/jdaviz/app.py b/jdaviz/app.py
index cf51194fb5..c1e2ebc1a0 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -135,6 +135,7 @@ class ApplicationState(State):
'tab_headers': True,
},
'viewer_labels': True,
+ 'mouseover_marker': True,
'dense_toolbar': True,
'context': {
'notebook': {
diff --git a/jdaviz/configs/default/plugins/plot_options/plot_options.py b/jdaviz/configs/default/plugins/plot_options/plot_options.py
index 862fbd8a4f..9343795e82 100644
--- a/jdaviz/configs/default/plugins/plot_options/plot_options.py
+++ b/jdaviz/configs/default/plugins/plot_options/plot_options.py
@@ -188,6 +188,8 @@ class PlotOptions(PluginTemplateMixin):
show_viewer_labels = Bool(True).tag(sync=True)
+ show_mouseover_marker = Bool(True).tag(sync=True)
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.viewer = ViewerSelect(self, 'viewer_items', 'viewer_selected', 'multiselect')
@@ -304,6 +306,7 @@ def line_visible(state):
# display_units
self.show_viewer_labels = self.app.state.settings['viewer_labels']
+ self.show_mouseover_marker = self.app.state.settings['mouseover_marker']
self.app.state.add_callback('settings', self._on_app_settings_changed)
@property
@@ -331,6 +334,7 @@ def _on_show_viewer_labels_changed(self, event):
def _on_app_settings_changed(self, value):
self.show_viewer_labels = value['viewer_labels']
+ self.show_mouseover_marker = value['mouseover_marker']
def select_all(self, viewers=True, layers=True):
"""
diff --git a/jdaviz/configs/default/plugins/plot_options/plot_options.vue b/jdaviz/configs/default/plugins/plot_options/plot_options.vue
index bfad881c3c..746af73376 100644
--- a/jdaviz/configs/default/plugins/plot_options/plot_options.vue
+++ b/jdaviz/configs/default/plugins/plot_options/plot_options.vue
@@ -19,6 +19,14 @@
persistent-hint
>
+
+
+
From da4a704e29861ca6f1553d1cbf663b4601905fd6 Mon Sep 17 00:00:00 2001
From: "Brett M. Morris"
Date: Tue, 17 Jan 2023 20:37:06 -0500
Subject: [PATCH 9/9] proposal for mouseover marker toggle
---
jdaviz/configs/default/plugins/plot_options/plot_options.vue | 2 +-
jdaviz/configs/specviz/plugins/viewers.py | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/jdaviz/configs/default/plugins/plot_options/plot_options.vue b/jdaviz/configs/default/plugins/plot_options/plot_options.vue
index 746af73376..91dd82ab39 100644
--- a/jdaviz/configs/default/plugins/plot_options/plot_options.vue
+++ b/jdaviz/configs/default/plugins/plot_options/plot_options.vue
@@ -23,7 +23,7 @@
diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py
index fd68528f43..d0bd439599 100644
--- a/jdaviz/configs/specviz/plugins/viewers.py
+++ b/jdaviz/configs/specviz/plugins/viewers.py
@@ -65,6 +65,11 @@ def on_mouse_or_key_event(self, data):
else: # pragma: no cover
return
+ # only handle mouseover markers if the setting is selected:
+ plot_options_plugin = self.jdaviz_app.get_tray_item_from_name('g-plot-options')
+ if not plot_options_plugin.show_mouseover_marker:
+ return
+
if data['event'] == 'mousemove':
if len(self.jdaviz_app.data_collection) < 1:
return