Skip to content

Commit

Permalink
Spectrum info panel for spectrum viewers
Browse files Browse the repository at this point in the history
in all the configurations supported by Jdaviz.
  • Loading branch information
pllim committed Dec 21, 2022
1 parent ecc2658 commit 61058aa
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 42 deletions.
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
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.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'
6 changes: 6 additions & 0 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 = ''
Expand Down Expand Up @@ -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'
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
24 changes: 22 additions & 2 deletions jdaviz/configs/mosviz/tests/test_data_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'})
Expand All @@ -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 == ''
Expand All @@ -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):
'''
Expand Down
73 changes: 60 additions & 13 deletions jdaviz/configs/specviz/plugins/viewers.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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']
Expand All @@ -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 = ""
Expand Down
Loading

0 comments on commit 61058aa

Please sign in to comment.