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

Marker bugfix #3270

Merged
merged 3 commits into from Nov 29, 2023
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
65 changes: 65 additions & 0 deletions examples/Markers/from_signal.py
@@ -0,0 +1,65 @@
"""
Creating Markers from a signal
==============================

This example shows how to create markers from a signal. This is useful for creating lazy
markers from some operation such as peak finding on a signal. Here we show how to create
markers from a simple map function which finds the maximum value and plots a marker at
that position.
"""
import numpy as np
import hyperspy.api as hs


# Making some artificial data
def find_maxima(data, scale, offset):
ind = np.array(np.unravel_index(np.argmax(data, axis=None), data.shape)).astype(int)
d = data[ind]
ind = ind * scale + offset # convert to physical units
print(ind)
print(d)
return np.array(
[
[ind[0], d[0]],
]
)


def find_maxima_lines(data, scale, offset):
ind = np.array(np.unravel_index(np.argmax(data, axis=None), data.shape)).astype(int)
ind = ind * scale + offset # convert to physical units
return ind


def gaussian(x, mu, sig):
return (
1.0 / (np.sqrt(2.0 * np.pi) * sig) * np.exp(-np.power((x - mu) / sig, 2.0) / 2)
)


data = np.empty((4, 120))
for i in range(4):
x_values = np.linspace(-3 + i * 0.1, 3 + i * 0.1, 120)
data[i] = gaussian(x_values, mu=0, sig=10)

s = hs.signals.Signal1D(data)
s.axes_manager.signal_axes[0].scale = 6 / 120
s.axes_manager.signal_axes[0].offset = -3


scale = s.axes_manager.signal_axes[0].scale
offset = s.axes_manager.signal_axes[0].offset
max_values = s.map(find_maxima, scale=scale, offset=offset, inplace=False, ragged=True)
max_values_lines = s.map(
find_maxima_lines, scale=scale, offset=offset, inplace=False, ragged=True
)

point_markers = hs.plot.markers.Points.from_signal(max_values, signal_axes=None)
line_markers = hs.plot.markers.VerticalLines.from_signal(
max_values_lines, signal_axes=None
)


s.plot()
s.add_marker(point_markers)
s.add_marker(line_markers)
19 changes: 6 additions & 13 deletions hyperspy/drawing/markers.py
Expand Up @@ -541,10 +541,13 @@ def __repr__(self):

text += ", length: "
if self._is_iterating:
lentgh = getattr(self, "__len__ ", "not plotted")
text += "variable (current: %s)" % len(self)
try:
current = len(self)
except RuntimeError:
current = "not plotted"
text += "variable (current: %s)" % current
else:
text += "%s"% len(self)
text += "%s" % len(self)
text += ">"

return text
Expand All @@ -555,7 +558,6 @@ def from_signal(
signal,
key=None,
signal_axes="metadata",
collection=None,
**kwargs,
):
"""
Expand All @@ -569,22 +571,13 @@ def from_signal(
The key used to create a key value pair to create the subclass of
:py:class:`matplotlib.collections.Collection`. If ``None`` (default)
the key is set to ``"offsets"``.
collection: None, str or subclass of :py:class:`matplotlib.collections.Collection`
The collection which is initialized. If ``None``, default to
:py:class:`~.api.plot.markers.Points` markers.
signal_axes: str, tuple of :py:class:`~.axes.UniformDataAxis` or None
If ``"metadata"`` look for signal_axes saved in metadata under
``s.metadata.Peaks.signal_axes`` and convert from pixel positions
to real units before creating the collection. If a ``tuple`` of
signal axes, those axes will be used otherwise (``None``)
no transformation will happen.
"""
if collection is None:
# By default, use `Points` with "display" coordinate system to
# avoid dependence on data coordinates
from hyperspy.utils.markers import Points
cls = Points
kwargs.setdefault('sizes', 10)
if signal_axes is None or (
signal_axes == "metadata"
and not signal.metadata.has_item("Peaks.signal_axes")
Expand Down
13 changes: 11 additions & 2 deletions hyperspy/tests/drawing/test_markers.py
Expand Up @@ -213,6 +213,16 @@ def test_from_signal(self, signal, data, signal_axes):
else:
np.testing.assert_array_equal(col.get_current_kwargs()["offsets"], data[1])

def test_from_signal_lines(self, signal, data):
data = np.empty((3,), dtype=object)
for i in np.ndindex(data.shape):
data[i] = np.ones((10, 2, 2)) * i

signal = BaseSignal(data, ragged=True)
lines = Lines.from_signal(signal,key="segments", signal_axes=None)
s = Signal2D(np.ones((3, 5, 6)))
s.add_marker(lines)

def test_from_signal_not_ragged(self):
s = hs.signals.Signal2D(np.ones((2, 3, 5, 6, 7)))

Expand Down Expand Up @@ -465,8 +475,7 @@ def test_rep_iterating(self, signal):
for i in range(3):
offsets[i] = np.array([[1, 1], [2, 2]])
m = Points(offsets=offsets)
with pytest.raises(RuntimeError):
print(m)
assert m.__repr__() == "<Points, length: variable (current: not plotted)>"

signal.plot()
signal.add_marker(m)
Expand Down
4 changes: 4 additions & 0 deletions upcoming_changes/3270.bugfix.rst
@@ -0,0 +1,4 @@
Fix bugs in new marker implementation:

- Markers str representation fails if the marker isn't added to a signal
- make :meth:`~.plot.markers.Markers.from_signal` to work with all markers - it was only working with :class:`~.plot.markers.Markers.Points`