From 7f5d45650543f84316980713db6486037684e42a Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Wed, 18 Sep 2024 13:33:14 +0100 Subject: [PATCH] Fix plotting windows with navigation dimension >=2 and simplify setting background and integration windows line markers --- exspy/signals/eds.py | 73 ++++++++++++++++++++++------- exspy/tests/signals/test_eds_tem.py | 18 +++++++ 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/exspy/signals/eds.py b/exspy/signals/eds.py index 4ef3f83c1..db399d9b9 100644 --- a/exspy/signals/eds.py +++ b/exspy/signals/eds.py @@ -987,7 +987,7 @@ def _plot_xray_lines( self.add_xray_lines_markers(xray_lines, render_figure=False) if background_windows is not None: self._add_background_windows_markers( - background_windows, render_figure=False + background_windows, linestyles="--", render_figure=False ) if integration_windows is not None: if integration_windows == "auto": @@ -997,7 +997,7 @@ def _plot_xray_lines( windows_width=integration_windows, xray_lines=xray_lines ) self._add_vertical_lines_groups( - integration_windows, linestyle="--", render_figure=False + integration_windows, render_figure=False ) # Render figure only at the end if render_figure: @@ -1012,16 +1012,22 @@ def _add_vertical_lines_groups(self, position, render_figure=True, **kwargs): position: 2D array of float The position on the signal axis. Each row corresponds to a group. - kwargs + **kwargs : dict keywords argument for :class:`hyperspy.api.plot.markers.VerticalLine` """ - colors = itertools.cycle( + position = np.array(position) + length = position.shape[1] + colors_cycle = itertools.cycle( np.sort(plt.rcParams["axes.prop_cycle"].by_key()["color"]) ) + colors = np.array( + [[c] * length for c, w in zip(colors_cycle, position)] + ).flatten() - for x, color in zip(position, colors): - line = hs.plot.markers.VerticalLines(offsets=x, color=color, **kwargs) - self.add_marker(line, render_figure=False) + line = hs.plot.markers.VerticalLines( + offsets=position.flatten(), color=colors, **kwargs + ) + self.add_marker(line, render_figure=False) if render_figure: self._render_figure(plot=["signal_plot"]) @@ -1102,7 +1108,9 @@ def remove_xray_lines_markers(self, xray_lines, render_figure=True): if render_figure: self._render_figure(plot=["signal_plot"]) - def _add_background_windows_markers(self, windows_position, render_figure=True): + def _add_background_windows_markers( + self, windows_position, render_figure=True, **kwargs + ): """ Plot the background windows associated with each X-ray lines. @@ -1121,21 +1129,50 @@ def _add_background_windows_markers(self, windows_position, render_figure=True): -------- estimate_background_windows, get_lines_intensity """ - self._add_vertical_lines_groups(windows_position) - segments = [] - for bw in windows_position: + self._add_vertical_lines_groups(windows_position, **kwargs) + + # Calculate the start and end of segments for each window + # nav_dim + (number of x-ray lines, vertices positions) + # vertices positions are (x0, y0), (x1, x2) + segments_ = np.zeros( + self.axes_manager.navigation_shape + (len(windows_position), 2, 2) + ) + + for i, bw in enumerate(windows_position): + # Check that all background windows are within the energy range if any(v < self.axes_manager[-1].low_value for v in bw) or any( v > self.axes_manager[-1].high_value for v in bw ): raise ValueError("Background windows is outside of the signal range.") - y1 = self.isig[bw[0] : bw[1]].mean(-1).data - y2 = self.isig[bw[2] : bw[3]].mean(-1).data - x1 = (bw[0] + bw[1]) / 2.0 - x2 = (bw[2] + bw[3]) / 2.0 - segments.append([[x1, y1[0]], [x2, y2[0]]]) - segments = np.array(segments) - lines = hs.plot.markers.Lines(segments=segments, color="black") + + # calculate the position of the segments + y0 = self.isig[bw[0] : bw[1]].mean(-1).data + y1 = self.isig[bw[2] : bw[3]].mean(-1).data + x0 = (bw[0] + bw[1]) / 2.0 + x1 = (bw[2] + bw[3]) / 2.0 + + segments_[..., i, 0, 0] = x0 + segments_[..., i, 0, 1] = y0.T + segments_[..., i, 1, 0] = x1 + segments_[..., i, 1, 1] = y1.T + + # convert to ragged array to comply with requirement for + # navigation position dependent markers + # 2000 x 2000 navigation shape takes ~2s + # 1000 x 1000 navigation shape takes ~0.5s + # 500 x 500 navigation shape takes ~0.01s + segments = np.empty(self.axes_manager.navigation_shape, dtype=object) + for i in np.ndindex(self.axes_manager.navigation_shape): + segments[i] = segments_[i] + + colors_cycle = itertools.cycle( + np.sort(plt.rcParams["axes.prop_cycle"].by_key()["color"]) + ) + colors = np.array([c for c, w in zip(colors_cycle, windows_position)]).flatten() + + lines = hs.plot.markers.Lines(segments=segments, color=colors, **kwargs) self.add_marker(lines, render_figure=False) + if render_figure: self._render_figure(plot=["signal_plot"]) diff --git a/exspy/tests/signals/test_eds_tem.py b/exspy/tests/signals/test_eds_tem.py index 8eb964c3f..aa98e4de9 100644 --- a/exspy/tests/signals/test_eds_tem.py +++ b/exspy/tests/signals/test_eds_tem.py @@ -21,6 +21,7 @@ import numpy as np import pytest +import hyperspy.api as hs from hyperspy.components1d import Gaussian from hyperspy.decorators import lazifyTestClass @@ -607,6 +608,23 @@ def test_outside_range_background_windows(self): s.isig[2.0:].plot(True, background_windows=bw) +def test_plot_windows(): + s = exspy.data.EDS_TEM_FePt_nanoparticles() + + rng = np.random.default_rng() + + [s * v * 10 for v in rng.random((10))] + + s = hs.stack([s * v * 10 for v in rng.random((10))]) + s = hs.stack([s * v * 10 for v in rng.random((5))]) + + bw = s.estimate_background_windows(line_width=[5.0, 2.0]) + iw = s.estimate_integration_windows(windows_width=3) + + s.plot(True, background_windows=bw, integration_windows=iw) + s.axes_manager.indices = (1, 2) + + def test_with_signals_examples(): sig = exspy.data.EDS_TEM_FePt_nanoparticles() for s in (sig, sig.as_lazy()):