Skip to content

Commit

Permalink
View spectrum from single spaxel on hover (spacetelescope#2647)
Browse files Browse the repository at this point in the history
* Working on adding spectrum-from-spaxel on hover

* First working version of spectrum on hover

* Handle NaNs properly

* Update docs and changelog

* Add simple test

Codestyle

Codestyle

* Revert zoom after preview view

* Address review comments

* Only use 3D data

* Fix preview spectrum for uncertainty cube

* Use the same layer selection as coords_info if possible

* Codestyle

* Use selected_obj and get first profile viewer rather than spectrum-viewer specifically

* Remove unused import

* Improve test coverage
  • Loading branch information
rosteen authored Jan 22, 2024
1 parent 9140858 commit 8edbdf0
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Cubeviz

- Moment map plugin now supports linear per-spaxel continuum subtraction. [#2587]

- Single-pixel subset tool now shows spectrum-at-spaxel on hover. [#2647]

Imviz
^^^^^

Expand Down
10 changes: 8 additions & 2 deletions docs/cubeviz/displaycubes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ the bottom of the UI.
Spectrum At Spaxel
==================

This tool allows the user to create a one spaxel subset in an image viewer. This subset will then be
This tool allows the user to create a single-spaxel subset in an image viewer. This subset will then be
visualized in the spectrum viewer by showing the spectrum at that spaxel.
Activate this tool and then left-click to create the new region.
While this tool is active, hovering over a pixel in the image viewer will show a preview of the spectrum
at that spaxel in the spectrum viewer, and left-clicking will create a new subset at that spaxel.
Click again to move the region to a new location under the cursor. Holding down the
alt key (Alt key on Windows, Option key on Mac) while clicking on a spaxel creates a new subset at
that point instead of moving the previously created region.
Expand All @@ -110,6 +111,11 @@ You can also use the subset modes that are explained in the
:ref:`Spatial Regions <imviz_defining_spatial_regions>`
section above in the same way you would with the other subset selection tools.

Note that moving the cursor outside of the image viewer or deactivating the spectrum-at-spaxel tool
will revert the spectrum viewer zoom limits from the zoomed-in preview view to the limits set prior
to using the tool. Thus it may be necessary to reset the zoom to see any single-spaxel subset spectra
created using the tool.

.. _cubeviz-display-settings:

Display Settings
Expand Down
37 changes: 35 additions & 2 deletions jdaviz/configs/cubeviz/plugins/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@


@pytest.mark.filterwarnings('ignore:No observer defined on WCS')
def test_spectrum_at_spaxel(cubeviz_helper, spectrum1d_cube):
cubeviz_helper.load_data(spectrum1d_cube, data_label='test')
def test_spectrum_at_spaxel(cubeviz_helper, spectrum1d_cube_with_uncerts):
cubeviz_helper.load_data(spectrum1d_cube_with_uncerts, data_label='test')

flux_viewer = cubeviz_helper.app.get_viewer("flux-viewer")
uncert_viewer = cubeviz_helper.app.get_viewer("uncert-viewer")
spectrum_viewer = cubeviz_helper.app.get_viewer("spectrum-viewer")

# Set the active tool to spectrumperspaxel
Expand All @@ -18,6 +19,12 @@ def test_spectrum_at_spaxel(cubeviz_helper, spectrum1d_cube):
assert len(flux_viewer.native_marks) == 2
assert len(spectrum_viewer.data()) == 1

# Move to spaxel location
flux_viewer.toolbar.active_tool.on_mouse_move(
{'event': 'mousemove', 'domain': {'x': x, 'y': y}, 'altKey': False})
assert flux_viewer.toolbar.active_tool._mark in spectrum_viewer.figure.marks
assert flux_viewer.toolbar.active_tool._mark.visible is True

# Click on spaxel location
flux_viewer.toolbar.active_tool.on_mouse_event(
{'event': 'click', 'domain': {'x': x, 'y': y}, 'altKey': False})
Expand All @@ -30,10 +37,36 @@ def test_spectrum_at_spaxel(cubeviz_helper, spectrum1d_cube):
assert len(subsets) == 1
assert isinstance(reg, RectanglePixelRegion)

# Move out of bounds
flux_viewer.toolbar.active_tool.on_mouse_move(
{'event': 'mousemove', 'domain': {'x': -1, 'y': -1}, 'altKey': False})
assert flux_viewer.toolbar.active_tool._mark.visible is False

# Mouse leave event
flux_viewer.toolbar.active_tool.on_mouse_move(
{'event': 'mouseleave', 'domain': {'x': x, 'y': y}, 'altKey': False})
assert flux_viewer.toolbar.active_tool._mark.visible is False

# Deselect tool
flux_viewer.toolbar.active_tool = None
assert len(flux_viewer.native_marks) == 3

# Check in uncertainty viewer as well. Set mouseover here
cubeviz_helper.app.session.application._tools['g-coords-info'].dataset.selected = 'none'
uncert_viewer.toolbar.active_tool = uncert_viewer.toolbar.tools['jdaviz:spectrumperspaxel']
uncert_viewer.toolbar.active_tool.on_mouse_move(
{'event': 'mousemove', 'domain': {'x': x, 'y': y}, 'altKey': False})
assert uncert_viewer.toolbar.active_tool._mark in spectrum_viewer.figure.marks
assert uncert_viewer.toolbar.active_tool._mark.visible is True

# Select specific data
cubeviz_helper.app.session.application._tools['g-coords-info'].dataset.selected = 'test[FLUX]'
uncert_viewer.toolbar.active_tool = uncert_viewer.toolbar.tools['jdaviz:spectrumperspaxel']
uncert_viewer.toolbar.active_tool.on_mouse_move(
{'event': 'mousemove', 'domain': {'x': x, 'y': y}, 'altKey': False})
assert uncert_viewer.toolbar.active_tool._mark in spectrum_viewer.figure.marks
assert uncert_viewer.toolbar.active_tool._mark.visible is True


def test_spectrum_at_spaxel_altkey_true(cubeviz_helper, spectrum1d_cube):
cubeviz_helper.load_data(spectrum1d_cube, data_label='test')
Expand Down
91 changes: 91 additions & 0 deletions jdaviz/configs/cubeviz/plugins/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

from glue.config import viewer_tool
from glue_jupyter.bqplot.image import BqplotImageView
from glue_jupyter.bqplot.profile import BqplotProfileView
from glue.viewers.common.tool import CheckableTool
import numpy as np
from specutils import Spectrum1D

from jdaviz.configs.imviz.plugins.tools import _MatchedZoomMixin
from jdaviz.core.events import SliceToolStateMessage
from jdaviz.core.tools import PanZoom, BoxZoom, SinglePixelRegion
from jdaviz.core.marks import PluginLine

__all__ = []

Expand Down Expand Up @@ -83,3 +87,90 @@ class SpectrumPerSpaxel(SinglePixelRegion):
tool_id = 'jdaviz:spectrumperspaxel'
action_text = 'See spectrum at a single spaxel'
tool_tip = 'Click on the viewer and see the spectrum at that spaxel in the spectrum viewer'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._spectrum_viewer = None
self._previous_bounds = None
self._mark = None
self._data = None

def _reset_spectrum_viewer_bounds(self):
sv_state = self._spectrum_viewer.state
sv_state.x_min = self._previous_bounds[0]
sv_state.x_max = self._previous_bounds[1]
sv_state.y_min = self._previous_bounds[2]
sv_state.y_max = self._previous_bounds[3]

def activate(self):
self.viewer.add_event_callback(self.on_mouse_move, events=['mousemove', 'mouseleave'])
if self._spectrum_viewer is None:
# Get first profile viewer
for _, viewer in self.viewer.jdaviz_helper.app._viewer_store.items():
if isinstance(viewer, BqplotProfileView):
self._spectrum_viewer = viewer
break
if self._mark is None:
self._mark = PluginLine(self._spectrum_viewer, visible=False)
self._spectrum_viewer.figure.marks = self._spectrum_viewer.figure.marks + [self._mark,]
# Store these so we can revert to previous user-set zoom after preview view
sv_state = self._spectrum_viewer.state
self._previous_bounds = [sv_state.x_min, sv_state.x_max, sv_state.y_min, sv_state.y_max]
super().activate()

def deactivate(self):
self.viewer.remove_event_callback(self.on_mouse_move)
self._reset_spectrum_viewer_bounds()
super().deactivate()

def on_mouse_move(self, data):
if data['event'] == 'mouseleave':
self._mark.visible = False
self._reset_spectrum_viewer_bounds()
return

x = int(np.round(data['domain']['x']))
y = int(np.round(data['domain']['y']))

# Use the selected layer from coords_info as long as it's 3D
coords_dataset = self.viewer.session.application._tools['g-coords-info'].dataset.selected
if coords_dataset == 'auto':
cube_data = self.viewer.active_image_layer.layer
elif coords_dataset == 'none':
if len(self.viewer.layers):
cube_data = self.viewer.layers[0].layer
else:
return
else:
cube_data = self.viewer.session.application._tools['g-coords-info'].dataset.selected_obj

data_shape = cube_data.ndim if hasattr(cube_data, "ndim") else len(cube_data.shape)
if data_shape != 3:
cube_data = [layer.layer for layer in self.viewer.layers if layer.state.visible
and layer.layer.ndim == 3]
if len(cube_data) == 0:
return
cube_data = cube_data[0]

if isinstance(cube_data, Spectrum1D):
spectrum = cube_data
else:
spectrum = cube_data.get_object(statistic=None)
# Note: change this when Spectrum1D.with_spectral_axis is fixed.
x_unit = self._spectrum_viewer.state.x_display_unit
if spectrum.spectral_axis.unit != x_unit:
new_spectral_axis = spectrum.spectral_axis.to(x_unit)
spectrum = Spectrum1D(spectrum.flux, new_spectral_axis)

if x >= spectrum.flux.shape[0] or x < 0 or y >= spectrum.flux.shape[1] or y < 0:
self._reset_spectrum_viewer_bounds()
self._mark.visible = False
else:
y_values = spectrum.flux[x, y, :]
if np.all(np.isnan(y_values)):
self._mark.visible = False
return
self._mark.update_xy(spectrum.spectral_axis.value, y_values)
self._mark.visible = True
self._spectrum_viewer.state.y_max = np.nanmax(y_values.value) * 1.2
self._spectrum_viewer.state.y_min = np.nanmin(y_values.value) * 0.8

0 comments on commit 8edbdf0

Please sign in to comment.