Skip to content

Commit

Permalink
Merge pull request #918 from kecnry/mosviz-freeze-state-on-row-click
Browse files Browse the repository at this point in the history
Mosviz: freeze selected plot properties during row change
  • Loading branch information
kecnry authored Oct 8, 2021
2 parents a792f8c + 6aeff7a commit 925a3cb
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Imviz
Mosviz
^^^^^^

- New toggle button to lock/unlock viewer settings (x-limits in 1d and 2d spectrum viewers and
stretch and percentile for 2d spectrum and image viewers). [#918]

Specviz
^^^^^^^

Expand Down
6 changes: 5 additions & 1 deletion jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from echo import CallbackProperty, DictCallbackProperty, ListCallbackProperty
from ipygoldenlayout import GoldenLayout
from ipysplitpanes import SplitPanes
from traitlets import Dict, Bool
from traitlets import Dict, Bool, Unicode
from regions import RectanglePixelRegion, PixCoord
from specutils import Spectrum1D

Expand Down Expand Up @@ -129,6 +129,7 @@ class Application(VuetifyTemplate, HubListener):
sync=True, **w.widget_serialization)

loading = Bool(False).tag(sync=True)
config = Unicode("").tag(sync=True)

def __init__(self, configuration=None, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -1166,6 +1167,7 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None):
'layer_options': "IPY_MODEL_" + viewer.layer_options.model_id,
'viewer_options': "IPY_MODEL_" + viewer.viewer_options.model_id,
'selected_data_items': [],
'config': self.config, # give viewer access to app config/layout
'collapse': True,
'reference': reference}

Expand Down Expand Up @@ -1261,6 +1263,8 @@ def load_configuration(self, path=None, config=None):

# store the loaded config object
self._loaded_configuration = config
# give the vue templates access to the current config/layout
self.config = config['settings'].get('configuration', 'unknown')

self.state.settings.update(config.get('settings'))

Expand Down
6 changes: 6 additions & 0 deletions jdaviz/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<v-app id="web-app" class="jdaviz">
<v-app-bar color="primary" dark :dense="state.settings.dense_toolbar" flat app absolute clipped-right>
<jupyter-widget :widget="item.widget" v-for="item in state.tool_items" :key="item.name"></jupyter-widget>
<v-toolbar-items>
<v-btn v-if="config === 'mosviz'" icon @click="state.settings.freeze_states_on_row_change = !state.settings.freeze_states_on_row_change">
<v-icon v-if="state.settings.freeze_states_on_row_change">mdi-lock</v-icon>
<v-icon v-else>mdi-lock-open-outline</v-icon>
</v-btn>
</v-toolbar-items>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn icon @click="state.drawer = !state.drawer">
Expand Down
56 changes: 54 additions & 2 deletions jdaviz/configs/mosviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ def __init__(self, *args, **kwargs):
spec2d = self.app.get_viewer("spectrum-2d-viewer")
spec2d.scales['x'].observe(self._update_spec1d_x_axis, names=['min', 'max'])

image_viewer = self.app.get_viewer("image-viewer")

# Choose which viewers will have state frozen during a row change.
# This should be a list of tuples, where each entry has the state as the
# first item in the tuple, and a list of frozen attributes as the second.
self._freezable_states = [(spec1d.state, ['x_min', 'x_max']),
(spec2d.state, ['x_min', 'x_max']),
(image_viewer.state, []),
]

self._freezable_layers = [(spec1d.state, ['linewidth']),
(spec2d.state, ['stretch', 'percentile', 'v_min', 'v_max']),
(image_viewer.state, ['stretch', 'percentile', 'v_min', 'v_max'])]
self._frozen_layers_cache = []

# Add callbacks to table-viewer to enable/disable the state freeze
table = self.app.get_viewer("table-viewer")
table._on_row_selected_begin = self._on_row_selected_begin
table._on_row_selected_end = self._on_row_selected_end

# Listen for clicks on the table in case we need to zoom the image
self.app.hub.subscribe(self, TableClickMessage,
handler=self._row_click_message_handler)
Expand All @@ -45,6 +65,38 @@ def __init__(self, *args, **kwargs):

self._update_in_progress = False

def _on_row_selected_begin(self):
if not self.app.state.settings.get('freeze_states_on_row_change'):
return

for state, attrs in self._freezable_states:
state._frozen_state = attrs

# Make a copy of layer attributes (these can't be frozen since it will
# technically be a NEW layer instance). Note: this assumes that
# layers[0] points to the data (and all other indices point to subsets)
self._frozen_layers_cache = [{a: getattr(state.layers[0], a) for a in attrs}
for state, attrs in self._freezable_layers
if len(state.layers)]

def _on_row_selected_end(self):
if not self.app.state.settings.get('freeze_states_on_row_change'):
return

for state, attrs in self._freezable_states:
state._frozen_state = []

# Restore data-layer states from cache, then reset cache
for (state, attrs), cache in zip(self._freezable_layers, self._frozen_layers_cache):
state.layers[0].update_from_dict(cache)

self._frozen_layers_cache = []

# Make sure world flipping has been handled correctly, as internal
# callbacks may have been made while limits were frozen. This is
# especially important for NIRISS data.
self._update_spec2d_x_axis()

def _extend_world(self, spec1d, ext):
# Extend 1D spectrum world axis to enable panning (within reason) past
# the bounds of data
Expand All @@ -56,7 +108,7 @@ def _extend_world(self, spec1d, ext):
world = np.hstack((prepend, world, append))
return world

def _update_spec2d_x_axis(self, change):
def _update_spec2d_x_axis(self, change=None):
# This assumes the two spectrum viewers have the same x-axis shape and
# wavelength solution, which should always hold
table_viewer = self.app.get_viewer('table-viewer')
Expand Down Expand Up @@ -93,7 +145,7 @@ def _update_spec2d_x_axis(self, change):

self._update_in_progress = False

def _update_spec1d_x_axis(self, change):
def _update_spec1d_x_axis(self, change=None):
# This assumes the two spectrum viewers have the same x-axis shape and
# wavelength solution, which should always hold
table_viewer = self.app.get_viewer('table-viewer')
Expand Down
1 change: 1 addition & 0 deletions jdaviz/configs/mosviz/mosviz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ settings:
context:
notebook:
max_height: 860px
freeze_states_on_row_change: False
toolbar:
- g-data-tools
- g-subset-tools
Expand Down
12 changes: 12 additions & 0 deletions jdaviz/configs/mosviz/plugins/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
RemoveDataFromViewerMessage,
TableClickMessage)
from jdaviz.core.registries import viewer_registry
from jdaviz.core.freezable_state import FreezableBqplotImageViewerState

__all__ = ['MosvizProfileView', 'MosvizImageView', 'MosvizProfile2DView',
'MosvizTableViewer']
Expand Down Expand Up @@ -83,6 +84,8 @@ class MosvizProfile2DView(BqplotImageView):

tools = ['bqplot:panzoom_x']

_state_cls = FreezableBqplotImageViewerState

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Setup viewer option defaults
Expand Down Expand Up @@ -116,6 +119,9 @@ def __init__(self, session, *args, **kwargs):
self._shared_image = False
self.row_selection_in_progress = False

self._on_row_selected_begin = None
self._on_row_selected_end = None

def redraw(self):

# Overload to hide components - we do this via overloading instead of
Expand All @@ -135,6 +141,9 @@ def redraw(self):
super().redraw()

def _on_row_selected(self, event):
if self._on_row_selected_begin:
self._on_row_selected_begin()

self.row_selection_in_progress = True
# Grab the index of the latest selected row
selected_index = event['new']
Expand Down Expand Up @@ -200,5 +209,8 @@ def _on_row_selected(self, event):

self.row_selection_in_progress = False

if self._on_row_selected_end:
self._on_row_selected_end()

def set_plot_axes(self, *args, **kwargs):
return
2 changes: 2 additions & 0 deletions jdaviz/configs/specviz/plugins/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from jdaviz.core.registries import viewer_registry
from jdaviz.core.spectral_line import SpectralLine
from jdaviz.core.linelists import load_preset_linelist, get_available_linelists
from jdaviz.core.freezable_state import FreezableProfileViewerState

__all__ = ['SpecvizProfileView']

Expand All @@ -27,6 +28,7 @@
class SpecvizProfileView(BqplotProfileView):
default_class = Spectrum1D
spectral_lines = None
_state_cls = FreezableProfileViewerState

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
21 changes: 21 additions & 0 deletions jdaviz/core/freezable_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from glue.viewers.profile.state import ProfileViewerState
from glue_jupyter.bqplot.image.state import BqplotImageViewerState


class FreezableState():
_frozen_state = []

def __setattr__(self, k, v):
if k[0] == '_' or k not in self._frozen_state:
super().__setattr__(k, v)
elif getattr(self, k) is None:
# still allow Nones to be updated to intial values
super().__setattr__(k, v)


class FreezableProfileViewerState(ProfileViewerState, FreezableState):
pass


class FreezableBqplotImageViewerState(BqplotImageViewerState, FreezableState):
pass

0 comments on commit 925a3cb

Please sign in to comment.