Skip to content

Commit

Permalink
Add stretch bounds tool to plot option histogram viewer (#2513)
Browse files Browse the repository at this point in the history
* Add stretch bounds tool to plot option histogram viewer
* Add changelog, test
* Change color based on tool active state
* Add short sleep to test to avoid throttle
* stretch function curve as inactive blue

---------

Co-authored-by: Kyle Conroy <[email protected]>
  • Loading branch information
rosteen and kecnry authored Oct 20, 2023
1 parent 58c6528 commit d9cacaf
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ New Features
- Plots in plugins now include basic zoom/pan tools for Plot Options,
Imviz Line Profiles, and Imviz's aperture photometry. [#2498]

- Histogram plot in Plot Options now includes tool to set stretch vmin and vmax. [#2513]

Cubeviz
^^^^^^^

Expand Down
9 changes: 8 additions & 1 deletion jdaviz/configs/default/plugins/plot_options/plot_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from glue_jupyter.bqplot.image.state import BqplotImageLayerState
from glue_jupyter.common.toolbar_vuetify import read_icon

from jdaviz.components.toolbar_nested import NestedJupyterToolbar
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import (PluginTemplateMixin, ViewerSelect, LayerSelect,
PlotOptionsSyncState, Plot,
Expand Down Expand Up @@ -398,6 +399,12 @@ def state_attr_for_line_visible(state):
state_filter=is_image)

self.stretch_histogram = Plot(self, viewer_type='histogram')
# Add the stretch bounds tool to the default Plot viewer.
self.stretch_histogram.tools_nested.append(["jdaviz:stretch_bounds"])
self.stretch_histogram.toolbar = NestedJupyterToolbar(self.stretch_histogram.viewer,
self.stretch_histogram.tools_nested,
["jdaviz:stretch_bounds"])

# NOTE: this is a current workaround so the histogram viewer doesn't crash when replacing
# data. Note also that passing x=[0] fails on SOME machines, so we'll pass [0, 1] instead
self.stretch_histogram._add_data('ref', x=[0, 1])
Expand Down Expand Up @@ -709,7 +716,7 @@ def _update_stretch_curve(self, msg=None):
x=curve_x,
y=curve_y,
ynorm=True,
color='#c75d2c',
color="#007BA1", # "inactive" blue
opacities=[0.5],
)

Expand Down
3 changes: 3 additions & 0 deletions jdaviz/core/template_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3280,7 +3280,9 @@ def __init__(self, plugin, viewer_type='scatter', app=None, *args, **kwargs):
app = jglue()

self._app = app
self._plugin = plugin
self.viewer = app.new_data_viewer(viewer_type, show=False)
self.viewer._plugin = plugin
self._viewer_type = viewer_type
if viewer_type == 'histogram':
self._viewer_components = ('x',)
Expand Down Expand Up @@ -3422,6 +3424,7 @@ def _add_mark(self, cls, label, xnorm=False, ynorm=False, **kwargs):
raise ValueError(f"mark with label '{label}' already exists")
mark = cls(scales={'x': bqplot.LinearScale() if xnorm else self.figure.axes[0].scale,
'y': bqplot.LinearScale() if ynorm else self.figure.axes[1].scale},
labels=[label],
**kwargs)
self.figure.marks = self.figure.marks + [mark]
self._marks[label] = mark
Expand Down
28 changes: 28 additions & 0 deletions jdaviz/core/tests/test_tools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import time

import numpy as np
from numpy.testing import assert_allclose


Expand Down Expand Up @@ -41,3 +44,28 @@ def test_rangezoom(specviz_helper, spectrum1d):
t.interact.selected = [14, 15]
t.on_update_zoom()
assert_allclose(_get_lims(sv), [6500, 7000, 14, 15])


def test_stretch_bounds(imviz_helper):
imviz_helper.load_data(np.ones((2, 2)))

plot_options = imviz_helper.plugins['Plot Options']._obj
stretch_tool = plot_options.stretch_histogram.toolbar.tools["jdaviz:stretch_bounds"]
plot_options.stretch_histogram.toolbar.active_tool = stretch_tool

min_msg = {'event': 'click', 'pixel': {'x': 40, 'y': 322},
'domain': {'x': 0.1, 'y': 342},
'button': 0, 'altKey': False, 'ctrlKey': False, 'metaKey': False}

max_msg = {'event': 'click', 'pixel': {'x': 40, 'y': 322},
'domain': {'x': 1.3, 'y': 342},
'button': 0, 'altKey': False, 'ctrlKey': False, 'metaKey': False}

stretch_tool.on_mouse_event(min_msg)
time.sleep(0.3)
stretch_tool.on_mouse_event(max_msg)

assert plot_options.stretch_vmin_value == 0.1
assert plot_options.stretch_vmax_value == 1.3

plot_options.stretch_histogram.toolbar.active_tool = None
42 changes: 42 additions & 0 deletions jdaviz/core/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import time

import numpy as np
from echo import delay_callback
Expand Down Expand Up @@ -338,6 +339,47 @@ def is_visible(self):
return len([m for m in self.viewer.figure.marks if isinstance(m, SpectralLine)]) > 0


@viewer_tool
class StretchBounds(CheckableTool):
icon = os.path.join(ICON_DIR, 'line_select.svg')
tool_id = 'jdaviz:stretch_bounds'
action_text = 'Set Stretch VMin and VMax'
tool_tip = 'Set closest stretch bound (VMin/VMax) with click or click+drag'

def __init__(self, viewer, **kwargs):
self._time_last = 0
super().__init__(viewer, **kwargs)

def activate(self):
self.viewer.add_event_callback(self.on_mouse_event,
events=['dragmove', 'click'])
for mark in self.viewer.figure.marks:
if np.any([x in mark.labels for x in ('vmin', 'vmax')]):
mark.colors = ["#c75d2c"]

def deactivate(self):
self.viewer.remove_event_callback(self.on_mouse_event)
for mark in self.viewer.figure.marks:
if np.any([x in mark.labels for x in ('vmin', 'vmax')]):
mark.colors = ["#007BA1"]

def on_mouse_event(self, data):
if (time.time() - self._time_last) <= 0.05:
# throttle to 200ms
return

event_x = data['domain']['x']
current_bounds = [self.viewer._plugin.stretch_vmin_value,
self.viewer._plugin.stretch_vmax_value,]
att_names = ["stretch_vmin_value", "stretch_vmax_value"]
closest_bound_ind = np.argmin([abs(current_bounds[0] - event_x),
abs(current_bounds[1] - event_x)])

setattr(self.viewer._plugin, att_names[closest_bound_ind], event_x)

self._time_last = time.time()


class _BaseSidebarShortcut(Tool):
plugin_name = None # define in subclass
viewer_attr = 'viewer'
Expand Down

0 comments on commit d9cacaf

Please sign in to comment.