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

Implement plot span map #3224

Merged
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7052d73
port of lumispy implementation
0Hughman0 Sep 6, 2023
e7ebd52
allow passing of list of SpanROI as spans
0Hughman0 Sep 6, 2023
918197d
# -*- coding: utf-8 -*-
0Hughman0 Sep 6, 2023
f3dd007
improved doc description
0Hughman0 Sep 6, 2023
2211be6
added tests for passing ROIs
0Hughman0 Sep 6, 2023
5a433d3
added changelog entry
0Hughman0 Sep 6, 2023
2337bbe
Update doc/user_guide/visualisation.rst
0Hughman0 Sep 7, 2023
cf338cc
Update hyperspy/drawing/utils.py
0Hughman0 Sep 7, 2023
4e51e13
fixed line lengths
0Hughman0 Sep 7, 2023
df32119
switched sig to signal
0Hughman0 Sep 7, 2023
13fe33d
fixed capitalisation
0Hughman0 Sep 7, 2023
d07f05b
generalising for more shapes
0Hughman0 Sep 10, 2023
dacb52c
updating docs/ comments
0Hughman0 Sep 10, 2023
1946b0f
updated docs and docstrings
0Hughman0 Sep 12, 2023
9420698
updated test name
0Hughman0 Sep 12, 2023
650c3c5
improved and updated tests
0Hughman0 Sep 13, 2023
615b055
updated changelog message
0Hughman0 Sep 13, 2023
6a1ada0
updated pytest-mpl baselines
0Hughman0 Sep 13, 2023
55faa15
updated utils.plot docstring
0Hughman0 Sep 13, 2023
c189790
rebuild pytest baselines with matplotlib 3.5.1
0Hughman0 Sep 13, 2023
29d8465
actually using matplotlib 3.5.1
0Hughman0 Sep 13, 2023
4504744
rebuild baselines with freetype=2.10.4
0Hughman0 Sep 13, 2023
d650f69
Merge remote-tracking branch 'upsteam/RELEASE_next_major' into implem…
ericpre Nov 30, 2023
3a9ee27
Avoid figure flickering
ericpre Nov 30, 2023
711b9b5
Fix issue with "sticky" SpanROI widget at first click outside
ericpre Nov 30, 2023
17af368
Improve docstring and user guide
ericpre Nov 30, 2023
2446381
Apply suggestions from code review
jlaehne Dec 1, 2023
39b7a2b
minor doc improvements
jlaehne Dec 1, 2023
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 doc/reference/api.plot/index.rst
Expand Up @@ -5,6 +5,7 @@
hyperspy.api.plot.markers
hyperspy.api.plot.plot_histograms
hyperspy.api.plot.plot_images
hyperspy.api.plot.plot_roi_map
hyperspy.api.plot.plot_signals
hyperspy.api.plot.plot_spectra

Expand Down
Binary file added doc/user_guide/images/plot_roi_map_demo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions doc/user_guide/region_of_interest.rst
Expand Up @@ -223,3 +223,41 @@ Handily, we can pass a :py:class:`~.roi.RectangularROI` ROI instead.
>>> tuple(roi)
(2.0, 10.0, 0.0, 5.0)
>>> im.align2D(roi=roi)


Interactively Slicing Signal Dimensions
---------------------------------------

:func:`~.api.plot.plot_roi_map` is a function that allows you to
interactively see the spatial variation of intensity in a Signal
within a ROI of its signal axes.
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

To allow selection of the signal ROIs, a plot of the mean signal over all
spatial positions is generated. Interactive ROIs can then be adjusted to the
desired regions within this plot.

For each ROI, a plot reflecting how the intensity of signal within this ROI
varies over the spatial dimensions of the Signal object is also plotted.

For Signal objects with 1 signal dimension :py:class:`~.roi.SpanROI`\ s are used
and for 2 signal dimensions, :py:class:`~.roi.RectangularROI`\ s are used.

For example, below, for a hyperspectral map with 2 navigation dimensions and
1 signal dimension i.e. a spectrum at each position in a 2D map,
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
:py:class:`~.roi.SpanROI`\ s are used to select spectral regions of interest.
For each spectral region of interest a plot is generated displaying the
intensity within this region at each position in the map.

.. code-block:: python

>>> import hyperpsy.api as hs
>>> from hyperspy.utils.plot import plot_roi_map
>>> sig = hs.load('mydata.sur')
>>> sig
<Signal1D, dimensions: (128, 128|1024)>
>>> plot_roi_map(sig, rois=2)


.. image:: images/plot_roi_map_demo.gif
:width: 100%
:alt: Demo of plot_roi_map functionality.
6 changes: 6 additions & 0 deletions hyperspy/drawing/_widgets/range.py
Expand Up @@ -142,6 +142,12 @@

def _set_span_extents(self, left, right):
self.span.extents = (left, right)
try:
# For https://github.com/matplotlib/matplotlib/pull/27409
self.span._selection_completed = True
except AttributeError:

Check warning on line 148 in hyperspy/drawing/_widgets/range.py

View check run for this annotation

Codecov / codecov/patch

hyperspy/drawing/_widgets/range.py#L148

Added line #L148 was not covered by tests
# Remove when minimum matplotlib is > 3.8.2
pass

Check warning on line 150 in hyperspy/drawing/_widgets/range.py

View check run for this annotation

Codecov / codecov/patch

hyperspy/drawing/_widgets/range.py#L150

Added line #L150 was not covered by tests
# update internal state range widget
self._span_changed()

Expand Down
195 changes: 195 additions & 0 deletions hyperspy/drawing/utils.py
Expand Up @@ -35,6 +35,8 @@
import matplotlib.pyplot as plt
from rsciio.utils import rgb_tools


import hyperspy.api as hs
from hyperspy.defaults_parser import preferences
from hyperspy.misc.utils import to_numpy

Expand Down Expand Up @@ -1698,3 +1700,196 @@ def picker_kwargs(value, kwargs=None):
kwargs['picker'] = value

return kwargs


def _create_span_roi_group(sig_ax, N):
"""
Creates a set of `N` of :py:class:`~.roi.SpanROI`\\ s that sit along axis at sensible positions.

Arguments
---------
sig_ax: DataAxis
The axis over which the ROI will be placed
N: int
The number of ROIs
"""
axis = sig_ax.axis
ax_range = axis[-1] - axis[0]
span_width = ax_range / (2 * N)

spans = []

for i in range(N):
# create a span that has a unique range
span = hs.roi.SpanROI(
i * span_width + axis[0], (i + 1) * span_width + axis[0]
)

spans.append(span)

return spans


def _create_rect_roi_group(sig_wax, sig_hax, N):
"""
Creates a set of `N` :py:class:`~roi.RectangularROI`\\ s that sit along `waxis` and `haxis` at sensible positions.

Arguments
---------
sig_wax: DataAxis
The width axis over which the ROI will be placed
sig_hax: DataAxis
The height axis over which the ROI will be placed
N: int
The number of ROIs
"""
waxis = sig_wax.axis
haxis = sig_hax.axis

w_range = waxis[-1] - waxis[0]
h_range = haxis[-1] - haxis[0]

span_w_width = w_range / (2 * N)
span_h_width = h_range / (2 * N)

rects = []

for i in range(N):
# create a span that has a unique range
rect = hs.roi.RectangularROI(
left=i * span_w_width + waxis[0],
top=i * span_h_width + haxis[0],
right=(i + 1) * span_w_width + waxis[0],
bottom=(i + 1) * span_h_width + haxis[0]
)

rects.append(rect)

return rects


def plot_roi_map(signal, rois=1):
"""
Plot a ROI map of ``signal``.
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

Uses ROIs to select a regions of ``signal``'s signal axes.
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

For each ROI, a plot is generated of the sum within this signal ROI
at each point in ``signal``'s navigator.
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

The ROI can be moved interactively and the corresponding plots will
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
update automatically.

Arguments
---------
signal: :class:`~.api.signals.BaseSignal`
The signal to inspect.
rois: int, list of :class:`~.api.roi.SpanROI` or :class:`~.api.roi.RectangularROI`
ROIs that represent colour channels in map. Can either pass a list of
ROI objects, or an ``int``, ``N``, in which case ``N`` ROIs will
be created. Currently limited to a maximum of 3 ROIs.

Returns
-------
all_sum : :class:`~.api.signals.BaseSignal`
Sum over all positions of the signal, the 'navigator'on which the ROIs
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
are added.
rois : list of :class:`~.api.roi.SpanROI` or :class:`~.api.roi.RectangularROI`
The ROIs that slice signal
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
roi_signals : :class:`~.api.signals.BaseSignal`
Slices of ``signal`` according to each roi
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
roi_sums : :class:`~.api.signals.BaseSignal`
The summed ``roi_signals``
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

Examples
--------
3D hyperspectral data:
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

For 3D hyperspectral data, the rois used will be instances of
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
:class:`~.api.roi.SpanROI`. Therefore these ROIs can be used to select
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
particular spectral ranges, e.g. a particular peak.

The map generated for a given ROI is therefore the sum of this spectral
region at each point in the hyperspectral map. Therefore regions of the
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
sample where this peak is bright will be bright in this map.

4D STEM:
jlaehne marked this conversation as resolved.
Show resolved Hide resolved

For 4D STEM data, the ROIs used will be instances of
:class:`~.api.roi.RectangularROI`. These ROIs can be used to select particular
regions in reciprocal space, e.g. a particular diffraction spot.

The map generated for a given ROI is therefore the intensity of this
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
region at each point in the scan. Therefore regions of the
jlaehne marked this conversation as resolved.
Show resolved Hide resolved
scan where a particular spot is intense will appear bright.
"""
sig_dims = len(signal.axes_manager.signal_axes)
nav_dims = len(signal.axes_manager.navigation_axes)

if sig_dims not in [1, 2] or nav_dims not in [1, 2]:
warnings.warn(("This function is only tested for signals with 1 or 2 "
"signal and navigation dimensions, not"
f" {sig_dims} signal and {nav_dims} navigation."))

if isinstance(rois, int):
if sig_dims == 1:
rois = _create_span_roi_group(
signal.axes_manager.signal_axes[0], rois
)
elif sig_dims == 2:
rois = _create_rect_roi_group(
*signal.axes_manager.signal_axes, rois
)
else:
raise ValueError(("Can only generate default ROIs for signals "
f"with 1 or 2 signal dimensions, not {sig_dims}"
", try providing an explicit `rois` argument"))

if len(rois) > 3:
raise ValueError("Maximum number of spans is 3")

colors = ["red", "green", "blue"]
roi_signals = []
roi_sums = []

all_sum = signal.nansum()
all_sum.plot()

for i, roi in enumerate(rois):
color = colors[i]

# add it to the sum over all positions
roi.add_widget(all_sum, color=colors[i])

# create a signal that is the spectral slice of sig
roi_signal = hs.interactive(
roi,
signal=signal,
event=roi.events.changed,
axes=signal.axes_manager.signal_axes,
)
roi_signals.append(roi_signal)

# add up the roi sliced signals to one point per nav position
roi_sum = roi_signal.nansum(roi_signal.axes_manager.signal_axes)

# transpose shape to swap these points per nav into signal points
roi_sum = roi_sum.transpose(
signal_axes=roi_sum.axes_manager.navigation_dimension
)

roi_sums.append(roi_sum)

roi_sum.plot(cmap=f"{color.capitalize()}s")

# connect the span signal changing range to the value of span_sum
hs.interactive(
roi_signal.nansum,
event=roi_signal.axes_manager.events.any_axis_changed,
axis=roi_signal.axes_manager.signal_axes,
out=roi_sum,
recompute_out_event=None,
)

# return all ya bits for future messing around.
return all_sum, rois, roi_signals, roi_sums
8 changes: 6 additions & 2 deletions hyperspy/external/matplotlib/widgets.py
Expand Up @@ -510,7 +510,7 @@ def _press(self, event):
# visibility to False and extents to (v, v)
# update will be called when setting the extents
self.visible = False
self.extents = v, v
self._set_extents((v, v))
# We need to set the visibility back, so the span selector will be
# drawn when necessary (span width > 0)
self.visible = True
Expand Down Expand Up @@ -609,7 +609,7 @@ def _onmove(self, event):
if vmin > vmax:
vmin, vmax = vmax, vmin

self.extents = vmin, vmax
self._set_extents((vmin, vmax))

if self.onmove_callback is not None:
self.onmove_callback(vmin, vmax)
Expand Down Expand Up @@ -679,6 +679,10 @@ def extents(self):

@extents.setter
def extents(self, extents):
self._set_extents(extents)
self._selection_completed = True

def _set_extents(self, extents):
# Update displayed shape
if self.snap_values is not None:
extents = tuple(self._snap(extents, self.snap_values))
Expand Down
3 changes: 2 additions & 1 deletion hyperspy/tests/test_import.py
Expand Up @@ -195,8 +195,9 @@ def test_dir_utils_plot():
'markers',
'plot_histograms',
'plot_images',
'plot_roi_map',
'plot_signals',
'plot_spectra',
'plot_spectra'
]


Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.