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

Background window fixes and improvement #86

Merged
merged 3 commits into from
Oct 8, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 59 additions & 26 deletions exspy/signals/eds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand All @@ -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:
Expand All @@ -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"])

Expand Down Expand Up @@ -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.

Expand All @@ -1121,25 +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)
ax = self.axes_manager.signal_axes[0]
segments = []
for bw in windows_position:
# TODO: test to prevent slicing bug. To be removed when fixed
if ax.value2index(bw[0]) == ax.value2index(bw[1]):
y1 = self.isig[bw[0]].data
else:
y1 = self.isig[bw[0] : bw[1]].mean(-1).data
if ax.value2index(bw[2]) == ax.value2index(bw[3]):
y2 = self.isig[bw[2]].data
else:
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")
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.")

# 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)
Comment on lines +1159 to +1164
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this doesnt scale linearly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, maybe it would be good to check if using lazy markers help here, I would assume so but off the top of my head, I don't remember how they work!

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"])

Expand Down
24 changes: 24 additions & 0 deletions exspy/tests/signals/test_eds_tem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -600,6 +601,29 @@ def test_intensity_dtype_uint(self):
rtol=0.03,
)

def test_outside_range_background_windows(self):
s = self.signal
bw = s.estimate_background_windows()
with pytest.raises(ValueError):
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()
Expand Down
4 changes: 4 additions & 0 deletions upcoming_changes/86.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Plot background and integration windows fixes and improvements:

- fix plotting windows with navigation dimension >=2,
- raise improved error message when windows are out of the range of the signal,