Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for a toggle setting for mouseover marker #9

Closed
wants to merge 9 commits into from
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------

Expand Down
10 changes: 10 additions & 0 deletions docs/specviz/displaying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
====

Expand Down
18 changes: 18 additions & 0 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -134,6 +135,7 @@ class ApplicationState(State):
'tab_headers': True,
},
'viewer_labels': True,
'mouseover_marker': True,
'dense_toolbar': True,
'context': {
'notebook': {
Expand Down Expand Up @@ -258,6 +260,12 @@ 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 = {}
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
new_cms = (['Rainbow', cm.rainbow],
Expand Down Expand Up @@ -1478,6 +1486,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``.
Expand All @@ -1493,6 +1509,8 @@ def _on_data_deleted(self, msg):
if data_item['name'] == msg.data.label:
self.state.data_items.remove(data_item)

self._clear_object_cache(msg.data.label)

@staticmethod
def _create_data_item(data):
ndims = len(data.shape)
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -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.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'
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.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'
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.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 == ''
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 shows nothing.
spec_viewer.on_mouse_or_key_event({'event': 'mousemove', 'domain': {'x': 5500, 'y': 120}})
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 == ''
4 changes: 4 additions & 0 deletions jdaviz/configs/default/plugins/plot_options/plot_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down
8 changes: 8 additions & 0 deletions jdaviz/configs/default/plugins/plot_options/plot_options.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
persistent-hint
></v-switch>
</v-row>
<v-row>
<v-switch v-if="config=='specviz'"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cubeviz and mosviz also have spectral viewers, so I think we want the switch there as well.

Suggested change
<v-switch v-if="config=='specviz'"
<v-switch v-if="['specviz', 'cubeviz', 'mosviz'].indexOf(config) !== -1"

v-model="show_mouseover_marker"
label="Show mouseover marker"
hint="Show a marker at the mouse position"
persistent-hint
></v-switch>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
Expand Down
29 changes: 29 additions & 0 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
@@ -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']

Expand All @@ -13,6 +15,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)
Expand All @@ -22,8 +25,30 @@ 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'
self.world_label_icrs = '\u00A0'
self.world_label_deg = '\u00A0'
self.world_ra = ''
Expand Down Expand Up @@ -53,3 +78,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'
6 changes: 3 additions & 3 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
</tr>
<tr :style="unreliable_world ? 'color: #B8B8B8' : ''">
<td width="42"><b>{{ world_label_prefix }}</b></td>
<td width="115">{{ world_ra }}</td>
<td :width="world_label_prefix == 'Wave' ? 85 : 115">{{ world_ra }}</td>
<td width="120">{{ world_dec }}</td>
<td>{{ world_label_icrs }}</td>
</tr>
<tr :style="unreliable_world ? 'color: #B8B8B8' : ''">
<td width="42">{{ unreliable_world ? '(est.)' : '' }}</td>
<td width="115">{{ world_ra_deg }}</td>
<td width="42" :style="unreliable_world ? '' : 'font-weight: bold'">{{ world_label_prefix_2 }}</td>
<td :width="world_label_prefix == 'Wave' ? 85 : 115">{{ world_ra_deg }}</td>
<td width="120">{{ world_dec_deg }}</td>
<td>{{ world_label_deg }}</td>
</tr>
Expand Down
1 change: 1 addition & 0 deletions jdaviz/configs/imviz/tests/test_linking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading