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

Fix triggering event range widget #2952

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions hyperspy/drawing/_widgets/range.py
Expand Up @@ -144,6 +144,7 @@ def _span_changed(self, *args, **kwargs):
extents = self.span.extents
self._pos = np.array([extents[0]])
self._size = np.array([extents[1] - extents[0]])
self.events.changed.trigger(self)

def _get_range(self):
p = self._pos[0]
Expand Down
47 changes: 31 additions & 16 deletions hyperspy/drawing/signal1d.py
Expand Up @@ -177,13 +177,20 @@ def plot(self, data_function_kwargs={}, **kwargs):
self.ax.set_title(self.title)
x_axis_upper_lims = []
x_axis_lower_lims = []

for line in self.ax_lines:
line.plot(data_function_kwargs=data_function_kwargs, **kwargs)
x_axis_lower_lims.append(line.axis.axis[0])
x_axis_upper_lims.append(line.axis.axis[-1])
if len(line.axis.axis) > 1:
x_axis_lower_lims.append(line.axis.axis[0])
x_axis_upper_lims.append(line.axis.axis[-1])

for marker in self.ax_markers:
marker.plot(render_figure=False)
plt.xlim(np.min(x_axis_lower_lims), np.max(x_axis_upper_lims))

plt.xlim(min(x_axis_lower_lims, default=None),
max(x_axis_upper_lims, default=None)
)

self.axes_manager.events.indices_changed.connect(self.update, [])
self.events.closed.connect(
lambda: self.axes_manager.events.indices_changed.disconnect(
Expand Down Expand Up @@ -484,28 +491,36 @@ def update(self, force_replot=False, render_figure=True,
else:
self.line.set_ydata(ydata)

if 'x' in self.autoscale:
self.ax.set_xlim(axis[0], axis[-1])
# Don't change xlim if axis has 0 length (unnecessary)
if 'x' in self.autoscale and len(axis) > 0:
x_min, x_max = axis[0], axis[-1]
if x_min == x_max:
# To avoid matplotlib UserWarning when calling `set_ylim`
x_min, x_max = (x_min - 0.1, x_min + 0.1)
self.ax.set_xlim(x_min, x_max)

if 'v' in self.autoscale:
# Don't change ymin if data has 0 length (unnecessary)
if 'v' in self.autoscale and len(ydata) > 0:
self.ax.relim()
y1, y2 = np.searchsorted(axis, self.ax.get_xbound())
y2 += 2
y1, y2 = np.clip((y1, y2), 0, len(ydata - 1))
clipped_ydata = ydata[y1:y2]
# Based on the current zoom of the x axis, find the corresponding
# y range of data and calculate the y_min, y_max accordingly
i1, i2 = np.searchsorted(axis, self.ax.get_xbound())
# Make interval wider on both side and clip to allowed range
i1, i2 = np.clip((i1-1, i2+1), 0, len(ydata - 1))
ydata = ydata[i1:i2]

with ignore_warning(category=RuntimeWarning):
# In case of "All-NaN slices"
y_max, y_min = (np.nanmax(clipped_ydata),
np.nanmin(clipped_ydata))
y_max, y_min = np.nanmax(ydata), np.nanmin(ydata)

if self._plot_imag:
# Add real plot
yreal = self._get_data(real_part=True)
clipped_yreal = yreal[y1:y2]
yreal = self._get_data(real_part=True)[i1:i2]
with ignore_warning(category=RuntimeWarning):
# In case of "All-NaN slices"
y_min = min(y_min, np.nanmin(clipped_yreal))
y_max = max(y_max, np.nanmin(clipped_yreal))
y_min = min(y_min, np.nanmin(yreal))
y_max = max(y_max, np.nanmin(yreal))

if y_min == y_max:
ericpre marked this conversation as resolved.
Show resolved Hide resolved
# To avoid matplotlib UserWarning when calling `set_ylim`
y_min, y_max = y_min - 0.1, y_max + 0.1
Expand Down
12 changes: 11 additions & 1 deletion hyperspy/interactive.py
Expand Up @@ -127,8 +127,18 @@ def update(self):


def interactive(f, event="auto", recompute_out_event="auto", *args, **kwargs):
"""
%s

Returns
-------
:py:class:`~hyperspy.signal.BaseSignal` or one of its subclass
Signal updated with the operation result when a given event is
triggered.

"""
cls = Interactive(f, event, recompute_out_event, *args, **kwargs)
return cls.out


interactive.__doc__ = Interactive.__init__.__doc__
interactive.__doc__ %= Interactive.__init__.__doc__
24 changes: 17 additions & 7 deletions hyperspy/roi.py
Expand Up @@ -334,8 +334,8 @@ def _update_widgets(self, exclude=None):
"""Internal function for updating the associated widgets to the
geometry contained in the ROI.

Arguments
---------
Parameters
----------
exclude : set()
A set of widgets to exclude from the update. Useful e.g. if a
widget has triggered a change in the ROI: Then all widgets,
Expand Down Expand Up @@ -379,10 +379,10 @@ def _set_from_widget(self, widget):
def interactive(self, signal, navigation_signal="same", out=None,
color="green", snap=True, **kwargs):
"""Creates an interactively sliced Signal (sliced by this ROI) via
hyperspy.interactive.
:py:func:`~.interactive.interactive`.

Arguments
---------
Parameters
----------
signal : Signal
The source signal to slice.
navigation_signal : Signal, None or "same" (default)
Expand All @@ -406,6 +406,12 @@ def interactive(self, signal, navigation_signal="same", out=None,
All kwargs are passed to the roi __call__ method which is called
interactively on any roi parameter change.

Returns
-------
:py:class:`~hyperspy.signal.BaseSignal` or one of its subclass
Signal updated with the current ROI selection
when the ROI is changed.

"""
if hasattr(signal, '_plot_kwargs'):
kwargs.update({'_plot_kwargs': signal._plot_kwargs})
Expand Down Expand Up @@ -460,8 +466,8 @@ def add_widget(self, signal, axes=None, widget=None, color='green',
changes in either are reflected in the other. Note that only one
widget can be added per signal/axes combination.

Arguments
---------
Parameters
----------
signal : Signal
The signal to which the widget is added. This is used to determine
which plot to add the widget to, and it supplies the axes_manager
Expand All @@ -479,6 +485,10 @@ def add_widget(self, signal, axes=None, widget=None, color='green',
True.
kwargs:
All keyword arguments are passed to the widget constructor.

Returns
-------
The widget of the ROI.
"""

axes = self._parse_axes(axes, signal.axes_manager,)
Expand Down
15 changes: 15 additions & 0 deletions hyperspy/tests/drawing/test_plot_roi_widgets.py
Expand Up @@ -231,3 +231,18 @@ def test_snapping_axis_values(snap):
r = roi.Line2DROI(x1=6, y1=0, x2=12, y2=4, linewidth=0)
s.plot()
_ = r.interactive(s, snap=snap)


def test_plot_span_roi_changed_event():
s = Signal1D(np.arange(100))
s.plot()
r = roi.SpanROI()
s_span = r.interactive(s)
np.testing.assert_allclose(s_span.data, np.arange(25, 74))

w = list(r.widgets)[0]
assert w._pos == (24.5, )
assert w._size == (50., )
w._set_span_extents(10, 20)
np.testing.assert_allclose(s_span.data, np.arange(9, 19))

3 changes: 3 additions & 0 deletions hyperspy/tests/drawing/test_plot_signal.py
Expand Up @@ -321,6 +321,9 @@ def test_plot_complex_representation():
imag_ref = np.arange(9).reshape((3, 3)) + 9
comp_ref = real_ref + 1j * imag_ref
s = hs.signals.ComplexSignal1D(comp_ref)
s.plot()
# change indices to trigger update
s.axes_manager.indices = (1, )
s.plot(representation='polar', same_axes=True)
s.plot(representation='polar', same_axes=False)
with pytest.raises(ValueError):
Expand Down
16 changes: 16 additions & 0 deletions hyperspy/tests/drawing/test_plot_signal1d.py
Expand Up @@ -402,3 +402,19 @@ def test_plot_spectra_linestyle_error():

with pytest.raises(ValueError):
hs.plot.plot_spectra(s, linestyle='invalid')


def test_plot_empty_slice_autoscale():
s = hs.signals.Signal1D(np.arange(100))
s.plot()
r = hs.roi.SpanROI()
s_span = r.interactive(s)
s_span.plot(autoscale='x')
# change span selector to an "empty" slice and trigger update
r.left = 24
r.right = 24.1

s_span.plot(autoscale='v')
# change span selector to an "empty" slice and trigger update
r.left = 23
r.right = 23.1
1 change: 1 addition & 0 deletions upcoming_changes/2952.bugfix.rst
@@ -0,0 +1 @@
Fix :py:class:`~.roi.SpanROI` regression: the output of :py:meth:`~.roi.BaseInteractiveROI.interactive` was not updated when the ROI was changed. Fix errors with updating limits when plotting empty slice of data. Improve docstrings and test coverage.