Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stretch bounds tool to plot option histogram viewer #2513

Merged
merged 10 commits into from
Oct 20, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 @@ -403,6 +404,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 @@ -714,7 +721,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 @@
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"]

Check warning on line 358 in jdaviz/core/tools.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/core/tools.py#L357-L358

Added lines #L357 - L358 were not covered by tests

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

Check warning on line 369 in jdaviz/core/tools.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/core/tools.py#L369

Added line #L369 was not covered by tests

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