Skip to content

Commit

Permalink
allow user to select layer used in mouseover/markers
Browse files Browse the repository at this point in the history
  • Loading branch information
kecnry committed Feb 14, 2023
1 parent 2e61553 commit aca7483
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 47 deletions.
11 changes: 8 additions & 3 deletions jdaviz/components/layer_viewer_icon.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<template>
<span v-if="icon !== undefined" :class="prevent_invert_if_dark ? '' : 'invert-if-dark'" :style="span_style+'; color: '+color+'; '+borderStyle">
{{String(icon).toUpperCase()}}
</span>
<div v-if="icon !== undefined">
<v-icon v-if="icon.startsWith('mdi-')" size="16">
{{icon}}
</v-icon>
<span v-else :class="prevent_invert_if_dark ? '' : 'invert-if-dark'" :style="span_style+'; color: '+color+'; '+borderStyle">
{{String(icon).toUpperCase()}}
</span>
</div>
</template>

<script>
Expand Down
1 change: 1 addition & 0 deletions jdaviz/components/tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const tooltips = {
'Use the same display parameters for all images and spectra',
'create-image-viewer':
'Create new image viewer',
'coords-info-cycle': 'Cycle selected layer used for mouseover information and markers plugin',
// viewer toolbars
'viewer-toolbar-data': 'Select dataset(s) to display in this viewer',
Expand Down
10 changes: 4 additions & 6 deletions jdaviz/configs/default/plugins/markers/markers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
from traitlets import observe

from glue_jupyter.bqplot.image import BqplotImageView

from jdaviz.configs.imviz.helper import layer_is_image_data
from jdaviz.configs.cubeviz.helper import layer_is_cube_image_data
from jdaviz.core.marks import MarkersMark
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin, ViewerSelectMixin, TableMixin
Expand Down Expand Up @@ -40,7 +36,7 @@ def __init__(self, *args, **kwargs):
'Value', 'viewer']
if self.config in ['specviz', 'specviz2d', 'mosviz']:
# 1d spectrum viewers
headers += ['Spectral Axis', 'Pixel', 'Flux']
headers += ['pixel']
if self.config in ['specviz2d', 'mosviz']:
# 2d spectrum viewers
headers += []
Expand Down Expand Up @@ -93,7 +89,9 @@ def _on_viewer_key_event(self, viewer, data):

self.table.add_item(row_info)

self._get_mark(viewer).append_xy(row_info['x'], row_info['y'])
x, y = row_info['x'], row_info['y']
# TODO: will need to test/update when adding support for display units
self._get_mark(viewer).append_xy(getattr(x, 'value', x), getattr(y, 'value', y))

def clear_table(self):
"""
Expand Down
82 changes: 65 additions & 17 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math
import numpy as np
from traitlets import Bool, Unicode
from traitlets import Bool, Unicode, observe

from astropy import units as u
from glue.core import BaseData
Expand All @@ -14,16 +14,18 @@
from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView
from jdaviz.core.events import ViewerAddedMessage
from jdaviz.core.registries import tool_registry
from jdaviz.core.template_mixin import TemplateMixin
from jdaviz.core.template_mixin import TemplateMixin, LayerSelectMultiviewerMixin
from jdaviz.core.marks import PluginScatter

__all__ = ['CoordsInfo']


@tool_registry('g-coords-info')
class CoordsInfo(TemplateMixin):
class CoordsInfo(TemplateMixin, LayerSelectMultiviewerMixin):
template_file = __file__, "coords_info.vue"
icon = Unicode("").tag(sync=True)

layer_icon = Unicode("").tag(sync=True) # option for layer (auto, none, or specific layer)
icon = Unicode("").tag(sync=True) # currently exposed layer

row1a_title = Unicode("").tag(sync=True)
row1a_text = Unicode("").tag(sync=True)
Expand All @@ -49,6 +51,14 @@ def __init__(self, *args, **kwargs):
for viewer in self.app._viewer_store.values():
self._create_viewer_callbacks(viewer)

if self.config in ['specviz', 'specviz2d', 'mosviz']:
# has a spectrum viewer, so we want None as an option to disable locking to any layer
# and ONLY showing cursor information
self.layer._manual_options = ['auto', 'none']
else:
self.layer._manual_options = ['auto']
self.layer.viewer = self.app.get_viewer_reference_names()

# subscribe to mouse events on any new viewers
self.hub.subscribe(self, ViewerAddedMessage, handler=self._on_viewer_added)

Expand All @@ -66,6 +76,7 @@ def _create_viewer_callbacks(self, viewer):

def _on_viewer_added(self, msg):
self._create_viewer_callbacks(self.app.get_viewer_by_id(msg.viewer_id))
self.layer.viewer = self.app.get_viewer_reference_names()

@property
def marks(self):
Expand Down Expand Up @@ -150,6 +161,18 @@ def _layers_changed(self, viewer):
# cursor position
self.update_display(viewer, self._x, self._y)

@observe('layer_selected')
def _selected_layer_changed(self, *args):
if self.layer_selected == 'auto':
self.layer_icon = 'mdi-auto-fix'
elif self.layer_selected == 'none':
self.layer_icon = 'mdi-cursor-default'
else:
self.layer_icon = self.app.state.layer_icons.get(self.layer_selected, '')

def vue_next_layer(self, *args, **kwargs):
self.layer.select_next()

def update_display(self, viewer, x, y):
if isinstance(viewer, SpecvizProfileView):
self._spectrum_viewer_update(viewer, x, y)
Expand All @@ -169,7 +192,17 @@ def _image_viewer_update(self, viewer, x, y):
self._viewer_mouse_clear_event(viewer)
return

image = active_layer.layer
if self.layer.selected == 'auto':
image = active_layer.layer
else:
for layer in viewer.layers:
if layer.layer.label == self.layer.selected:
image = layer.layer
break
else:
self._viewer_mouse_clear_event(viewer)
return

self.icon = self.app.state.layer_icons.get(image.label, '') # noqa
self._dict['data_label'] = image.label

Expand Down Expand Up @@ -303,30 +336,39 @@ def _spectrum_viewer_update(self, viewer, x, y):
self.row1a_title = 'Cursor'
self.row1a_text = f'{x:10.5e}, {y:10.5e}'

self._dict['x'] = x
self._dict['y'] = y

# show the locked marker/coords only if either no tool or the default tool is active
locking_active = viewer.toolbar.active_tool_id in viewer.toolbar.default_tool_priority + [None] # noqa
if not locking_active:
if not locking_active or self.layer.selected == 'none':
self.row2_title = '\u00A0'
self.row2_text = ''
self.row3_title = '\u00A0'
self.row3_text = ''
self.icon = ''
self.icon = 'mdi-cursor-default'
self.marks[viewer._reference_id].visible = False
# get the units from the first layer
# TODO: replace with display units once implemented
statistic = getattr(viewer.state, 'function', None)
cache_key = (viewer.state.layers[0].layer.label, statistic)
sp = self.app._get_object_cache[cache_key]
self._dict['x'] = x * sp.spectral_axis.unit
self._dict['y'] = y * sp.flux.unit
self._dict['pixel'] = np.nan
self._dict['data_label'] = ''
return

# Snap to the closest data point, not the actual mouse location.
sp = None
closest_i = None
closest_wave = None
closest_flux = None
closest_icon = ''
closest_icon = 'mdi-cursor-default'
closest_distance = None
for lyr in viewer.state.layers:
if not lyr.visible:
if self.layer.selected == 'auto' and not lyr.visible:
continue
if self.layer.selected != 'auto' and self.layer.selected != lyr.layer.label:
continue

if isinstance(lyr.layer, GroupedSubset):
if not isinstance(lyr.layer.subset_state, RoiSubsetState):
# then this is a SPECTRAL subset
Expand All @@ -347,7 +389,8 @@ def _spectrum_viewer_update(self, viewer, x, y):
self.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():
if (self.layer.selected != lyr.layer.label and
(x < sp.spectral_axis.value.min() or x > sp.spectral_axis.value.max())):
continue

cur_i = np.argmin(abs(sp.spectral_axis.value - x))
Expand All @@ -369,19 +412,24 @@ def _spectrum_viewer_update(self, viewer, x, y):
continue

if closest_wave is None:
self._viewer_mouse_clear_event(viewer)
self.row2_title = '\u00A0'
self.row2_text = ''
self.row3_title = '\u00A0'
self.row3_text = ''
self.icon = 'mdi-cursor-default'
self.marks[viewer._reference_id].visible = False
return

self.row2_title = 'Wave'
self.row2_text = f'{closest_wave.value:10.5e} {closest_wave.unit.to_string()}'
self._dict['Spectral Axis'] = closest_wave
self._dict['x'] = closest_wave
if closest_wave.unit != u.pix:
self.row2_text += f' ({int(closest_i)} pix)'
self._dict['Pixel'] = closest_i
self._dict['pixel'] = float(closest_i) # float to be compatible with nan

self.row3_title = 'Flux'
self.row3_text = f'{closest_flux.value:10.5e} {closest_flux.unit.to_string()}'
self._dict['Flux'] = closest_flux
self._dict['y'] = closest_flux

self.icon = closest_icon

Expand Down
55 changes: 35 additions & 20 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
<template>
<div>
<span v-if="icon" style="position: absolute; top: 50%; transform: translateY(-50%); -ms-transform: translateY(-50%);">
<j-layer-viewer-icon :icon="icon" color="white" :prevent_invert_if_dark="true"></j-layer-viewer-icon>
</span>
<div style="display: inline-block; white-space: nowrap; line-height: 14pt; margin: 0; position: absolute; margin-left: 26px; top: 50%; transform: translateY(-50%); -ms-transform: translateY(-50%);">
<table>
<tr>
<td colspan="4" :style="row1_unreliable ? 'color: #B8B8B8' : ''">
<b>{{ row1a_title }} </b>{{ row1a_text }}&nbsp;&nbsp;
<b>{{ row1b_title }} </b>{{ row1b_text }}
</td>
</tr>
<tr :style="row2_unreliable ? 'color: #B8B8B8' : ''">
<td width="42"><b>{{ row2_title }}</b></td>
<td>{{ row2_text }}</td>
</tr>
<tr :style="row3_unreliable ? 'color: #B8B8B8' : ''">
<td width="42"><b>{{ row3_title }}</b></td>
<td>{{ row3_text }}</td>
</tr>
</table>
<div v-if="icon" style="position: absolute; top: 50%; transform: translateY(-50%); -ms-transform: translateY(-50%);">
<span>
<j-layer-viewer-icon :icon="icon" color="white" :prevent_invert_if_dark="true"></j-layer-viewer-icon>
</span>
<div style="display: inline-block; white-space: nowrap; line-height: 14pt; margin: 0; position: absolute; margin-left: 22px; top: 50%; transform: translateY(-50%); -ms-transform: translateY(-50%);">
<table>
<tr>
<td colspan="4" :style="row1_unreliable ? 'color: #B8B8B8' : ''">
<b>{{ row1a_title }} </b>{{ row1a_text }}&nbsp;&nbsp;
<b>{{ row1b_title }} </b>{{ row1b_text }}
</td>
</tr>
<tr :style="row2_unreliable ? 'color: #B8B8B8' : ''">
<td width="42"><b>{{ row2_title }}</b></td>
<td>{{ row2_text }}</td>
</tr>
<tr :style="row3_unreliable ? 'color: #B8B8B8' : ''">
<td width="42"><b>{{ row3_title }}</b></td>
<td>{{ row3_text }}</td>
</tr>
</table>
</div>
</div>
<div v-else style="height: 100%">
<v-toolbar-items>
<j-tooltip tipid='coords-info-cycle'>
<v-btn icon tile @click="next_layer()">
<j-layer-viewer-icon :icon="layer_icon" color="white" :prevent_invert_if_dark="true"></j-layer-viewer-icon>
</v-btn>
</j-tooltip>
<span style="padding-top: 22px">
{{layer_selected}}
</span>
</v-toolbar-items>

</div>
</div>
</template>
57 changes: 56 additions & 1 deletion jdaviz/core/template_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
'SubsetSelect', 'SpatialSubsetSelectMixin', 'SpectralSubsetSelectMixin',
'DatasetSpectralSubsetValidMixin',
'ViewerSelect', 'ViewerSelectMixin',
'LayerSelect', 'LayerSelectMixin',
'LayerSelect', 'LayerSelectMixin', 'LayerSelectMultiviewerMixin',
'DatasetSelect', 'DatasetSelectMixin',
'Table', 'TableMixin',
'AutoTextField', 'AutoTextFieldMixin',
Expand Down Expand Up @@ -482,6 +482,21 @@ def select_none(self):
self.selected = []
return self.selected

def select_next(self):
"""
Select next entry in the choices, wrapping when reaching the end. Raises an error if
:meth:`is_multiselect`
"""
if self.is_multiselect:
raise ValueError("currently in multiselect mode")

cycle = self.choices
curr_ind = cycle.index(self.selected)
if curr_ind >= len(cycle) - 1:
curr_ind = -1
self.selected = cycle[curr_ind + 1]
return self.selected

@property
def default_text(self):
return self._default_text
Expand Down Expand Up @@ -809,6 +824,44 @@ def __init__(self, *args, **kwargs):
'layer_multiselect')


class LayerSelectMultiviewerMixin(VuetifyTemplate, HubListener):
"""
Applies the LayerSelect component as a mixin in the base plugin. This
automatically adds traitlets as well as new properties to the plugin with minimal
extra code. For multiple instances or custom traitlet names/defaults, use the
component instead.
To use in a plugin:
* add ``LayerSelectMultiviewerMixin`` as a mixin to the class
* use the traitlets available from the plugin or properties/methods available from
``plugin.layer``.
Example template (label and hint are optional)::
<plugin-layer-select
:items="layer_items"
:selected.sync="layer_selected"
:show_if_single_entry="true"
label="Layer"
hint="Select layer."
/>
"""
layer_items = List().tag(sync=True)
layer_selected = Any().tag(sync=True)
layer_viewer = List().tag(sync=True)
layer_multiselect = Bool(False).tag(sync=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layer = LayerSelect(self,
'layer_items',
'layer_selected',
'layer_viewer',
'layer_multiselect')


class SubsetSelect(SelectPluginComponent):
"""
Plugin select for subsets, with support for single or multi-selection.
Expand Down Expand Up @@ -2269,6 +2322,8 @@ def add_item(self, item):
def json_safe(item):
if hasattr(item, 'to_string'):
return item.to_string()
if isinstance(item, float) and np.isnan(item):
return ''
return item

if isinstance(item, QTable):
Expand Down

0 comments on commit aca7483

Please sign in to comment.