diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6a0913979..53155b579 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,6 +15,7 @@ Changed
for integrity check as `it replaces LGTM
`_
- Added a centroid/center of mass functionality to analyse peak position of a spectrum (both in `utils`` and in `LumiSpectrum``)
+- `s.crop_edges` moved to utils. It also now can take 1,2 or 4 values as input (can crop each axis independently) and also accepts percentages. The use of it under the `CommonLumiSpectrum` class is deprecated.
Maintenance
-----------
diff --git a/doc/source/user_guide/utilities.rst b/doc/source/user_guide/utilities.rst
index 5f78e6fe7..a80013bd5 100644
--- a/doc/source/user_guide/utilities.rst
+++ b/doc/source/user_guide/utilities.rst
@@ -29,19 +29,34 @@ signals in the range of +/- 50 pixels around the centre of the overlapping regio
.. _spectral_map_utils:
-Utilities for spectral maps
-===========================
-
-The function :py:meth:`~.signals.common_luminescence.CommonLumi.crop_edges`
-removes the specified number of pixels from all four edges of a spectral map.
-It is a convenience wrapper for the ``inav`` :external+hyperspy:ref:`method in
-HyperSpy `.
+Cropping multiple signals in the navigation axis
+================================================
+
+The function :py:meth:`~.utils.axes.crop_edges`
+removes the specified number of pixels or % from the four edges of a spectral map,
+from the edges inwards. It takes a list of `Signals` and cropping can happen
+uniformly on all sides or by specifying the cropping range for each axis or each
+side. If the navigation axes shape across the list of signals is different, all
+signals can be rebinned to match the shape of the first signal in the list.
+It is a convenience wrapper for the ``inav`` `method in HyperSpy
+`_.
.. code-block:: python
+ >>> signals = [cl_map, sem_image]
+ >>> signals
+ [CLSpectrum <256,256|1024>, Signal2D <128,128|1>]
+ >>> signals_cropped = lum.utils.crop_edges(signals, crop_range=5, crop_units="%", rebin_nav=True)
+ >>> signals_cropped
+ [CLSpectrum <243,243|1024>, Signal2D <243,243|1>]
+.. Note::
+
+ Many scanning luminescence techniques result in edge defects at the edges of the scanned region.
+ This function enables the same cropping of the navigation axis for a list of signals in the same
+ region to correct for such defect.
- >>> s.crop_edges(crop_px=2)
+.. Note::
-*[TODO: add possibility to crop different amounts of pixels on different sides]*
+ Before version `0.2.2` this function belonged to the class `CommonLumi` as :py:meth:`~.signals.common_luminescence.CommonLumi.crop_edges`. This use is now deprecated.
.. _unit_conversion:
diff --git a/lumispy/__init__.py b/lumispy/__init__.py
index ad5ce7999..dc0538c6a 100644
--- a/lumispy/__init__.py
+++ b/lumispy/__init__.py
@@ -23,6 +23,7 @@
from lumispy.utils.axes import nm2eV, eV2nm, nm2invcm, invcm2nm, join_spectra
from lumispy.utils.io import to_array, savetxt
+from lumispy.utils import crop_edges
from lumispy import signals
from lumispy import components
diff --git a/lumispy/signals/common_luminescence.py b/lumispy/signals/common_luminescence.py
index ca6331cca..fd38890f7 100644
--- a/lumispy/signals/common_luminescence.py
+++ b/lumispy/signals/common_luminescence.py
@@ -23,47 +23,18 @@
from numpy import isnan
from warnings import warn
+from lumispy.utils.signals import crop_edges
class CommonLumi:
"""**General luminescence signal class (dimensionless)**"""
def crop_edges(self, crop_px):
- """Crop the amount of pixels from the four edges of the scanning
- region, from out the edges inwards.
-
- Parameters
- ----------
- crop_px : int
- Amount of pixels to be cropped on each side individually.
-
- Returns
- -------
- signal_cropped : CommonLuminescence
- A smaller cropped CL signal object. If inplace is True, the original
- object is modified and no LumiSpectrum is returned.
- """
-
- width = self.axes_manager.shape[0]
- height = self.axes_manager.shape[1]
-
- if crop_px * 2 > width or crop_px * 2 > height:
- raise ValueError(
- "The pixels to be cropped cannot be larger than half the width or the length!"
- )
- else:
- signal_cropped = self.inav[
- crop_px + 1 : width - crop_px + 1, crop_px + 1 : height - crop_px + 1
- ]
-
- # Store transformation in metadata (or update the value if already previously transformed)
-
- try:
- signal_cropped.metadata.Signal.cropped_edges += crop_px
- except AttributeError:
- signal_cropped.metadata.set_item("Signal.cropped_edges", crop_px)
-
- return signal_cropped
+ warn(
+ "This function is deprecated and will be deleted in v1.0. Please use ``sc = lum.utils.crop_edges(s)`` instead with the ``crop_range`` parameter instead.",
+ DeprecationWarning,
+ )
+ return crop_edges(self, crop_px=crop_px)
def remove_negative(self, basevalue=1, inplace=False):
"""Sets all negative values to 'basevalue', e.g. for logarithmic scale
diff --git a/lumispy/tests/signals/test_common_luminescence.py b/lumispy/tests/signals/test_common_luminescence.py
index 1fdcea849..e51616693 100644
--- a/lumispy/tests/signals/test_common_luminescence.py
+++ b/lumispy/tests/signals/test_common_luminescence.py
@@ -23,18 +23,12 @@
class TestCommonLumi:
- def test_crop_edges(self):
+ def test_crop_edges_deprecated(self):
s1 = LumiSpectrum(np.ones((10, 10, 10)))
- s2 = LumiTransientSpectrum(np.ones((10, 10, 10, 10)))
- s3 = LumiSpectrum(np.ones((3, 3, 10)))
- s1 = s1.crop_edges(crop_px=2)
- s2 = s2.crop_edges(crop_px=2)
- assert s1.axes_manager.navigation_shape[0] == 6
- assert s1.axes_manager.navigation_shape[1] == 6
- assert s2.axes_manager.navigation_shape[0] == 6
- assert s2.axes_manager.navigation_shape[1] == 6
- with pytest.raises(ValueError):
- s3.crop_edges(crop_px=2)
+ with pytest.warns(DeprecationWarning, match="This function is deprecated"):
+ s2 = s1.crop_edges(crop_px=1)
+ assert s2.axes_manager.navigation_shape[0] == 8
+ assert s2.axes_manager.navigation_shape[1] == 8
def test_remove_negative(self):
s1 = LumiSpectrum(np.random.random((10, 10, 10))) - 0.3
diff --git a/lumispy/tests/utils/test_signals.py b/lumispy/tests/utils/test_signals.py
index 53f021bae..d52dae043 100644
--- a/lumispy/tests/utils/test_signals.py
+++ b/lumispy/tests/utils/test_signals.py
@@ -1,11 +1,31 @@
-import numpy as np
-import pytest
-from numpy.testing import assert_allclose
-from lumispy.utils.signals import com
+# -*- coding: utf-8 -*-
+# Copyright 2019-2023 The LumiSpy developers
+#
+# This file is part of LumiSpy.
+#
+# LumiSpy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the license, or
+# (at your option) any later version.
+#
+# LumiSpy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LumiSpy. If not, see .
+
+from lumispy.utils.signals import com, crop_edges
from hyperspy.axes import FunctionalDataAxis, DataAxis, UniformDataAxis
+from numpy import ones, array
+from numpy.testing import assert_allclose
+from pytest import raises, mark, warns
+from hyperspy.signals import Signal1D, Signal2D
+from lumispy.signals import LumiSpectrum
-@pytest.mark.parametrize(
+@mark.parametrize(
"axis, output",
[
(
@@ -24,7 +44,7 @@
],
)
def test_com_axes(axis, output):
- intensities = np.array([1, 1])
+ intensities = array([1, 1])
centroid = com(intensities, axis)
assert_allclose(centroid, output, atol=0.1)
@@ -33,7 +53,7 @@ def test_com_axes(axis, output):
def test_com_list():
# Float without decimals as index for centroid
wavelengths = [200, 300, 400, 500, 600, 700]
- intensities = np.array([1, 2, 3, 2, 1, 0])
+ intensities = array([1, 2, 3, 2, 1, 0])
centroid = com(intensities, wavelengths)
assert_allclose(centroid, 400.0, atol=0.1)
@@ -42,7 +62,7 @@ def test_com_list():
200,
300,
]
- intensities = np.array(
+ intensities = array(
[
1,
1,
@@ -53,11 +73,217 @@ def test_com_list():
def test_com_inputs():
- with pytest.raises(ValueError, match="The length of the spectrum array"):
- com(np.ones(2), np.ones(3))
- with pytest.raises(ValueError):
- com(np.ones(3), np.ones(2))
- with pytest.raises(
+ with raises(ValueError, match="The length of the spectrum array"):
+ com(ones(2), ones(3))
+ with raises(ValueError):
+ com(ones(3), ones(2))
+ with raises(
ValueError, match="The parmeter `signal_axis` must be a HyperSpy Axis object."
):
- com(np.ones(3), "string")
+ com(ones(3), "string")
+
+
+#
+# Test navigation axis utils
+#
+
+
+@mark.parametrize(
+ "range, output",
+ [
+ (2, (6, 6)),
+ (2.0, (6, 6)),
+ ((2, 4), (6, 2)),
+ ((2.0, 4.0), (6, 2)),
+ ((1, 2, 3, 4), (6, 4)),
+ ((1.0, 8.0, 7.0, 4.0), (6, 4)),
+ ((1, 2, 3), ()),
+ ((1, 2, 3, 4, 5), ()),
+ (True, ()),
+ ((1, 0, 0, 3), (9, 7)),
+ ((None), (10, 10)),
+ ],
+)
+def test_crop_edges_s(range, output):
+ s1 = [LumiSpectrum(ones((10, 10, 10)))]
+
+ # Check for bad input range
+ if type(range) not in (int, float, tuple, str, type(None)):
+ with raises(ValueError, match="value must be a number,"):
+ crop_edges(s1, range)
+
+ elif type(range) == tuple and len(range) not in (1, 2, 4):
+ with raises(ValueError, match="tuple must be either a"):
+ crop_edges(s1, range)
+
+ else:
+ s1 = crop_edges(s1, range)
+ assert s1[0].axes_manager.navigation_shape[0] == output[0]
+ assert s1[0].axes_manager.navigation_shape[1] == output[1]
+
+
+@mark.parametrize(
+ "error, range, output",
+ [
+ (False, "1nm", (8, 8)),
+ (False, "rel0.1", (8, 8)),
+ (False, ("rel0.2", "rel0.4"), (6, 2)),
+ (False, ("2nm", "4nm"), (6, 2)),
+ (False, ("rel0.1", "rel0.8", "rel0.3", "rel0.2"), (2, 5)),
+ (False, ("1nm", "8nm", "7nm", "4nm"), (6, 4)),
+ (True, "a", ()),
+ (True, "11", ()),
+ ],
+)
+def test_crop_edges_fancy_str(error, range, output):
+ s1 = [LumiSpectrum(ones((10, 10, 10)))]
+ s1[0].axes_manager.navigation_axes[0].units = "nm"
+ s1[0].axes_manager.navigation_axes[1].units = "nm"
+
+ # Check for bad input range
+ if error:
+ with raises(ValueError):
+ crop_edges(s1, range)
+
+ else:
+ s1 = crop_edges(s1, range)
+ assert s1[0].axes_manager.navigation_shape[0] == output[0]
+ assert s1[0].axes_manager.navigation_shape[1] == output[1]
+
+
+def test_crop_single_spectrum():
+ s1 = LumiSpectrum(ones((10, 10, 10)))
+ s2 = crop_edges(
+ s1,
+ crop_range=1.0,
+ )
+ assert s2.axes_manager.navigation_shape[0] == 8
+ assert s2.axes_manager.navigation_shape[1] == 8
+ s2 = crop_edges(
+ s1,
+ crop_range=1,
+ )
+ assert s2.axes_manager.navigation_shape[0] == 8
+ assert s2.axes_manager.navigation_shape[1] == 8
+
+
+def test_crop_edges_metadata():
+ s1 = LumiSpectrum(ones((10, 10, 10)))
+ s2 = crop_edges(s1, crop_range=2)
+ assert (s2.metadata.Signal.cropped_edges == array([2, -2, -2, 2])).all()
+ s2 = crop_edges(s1, crop_range="rel0.1")
+ assert (s2.metadata.Signal.cropped_edges == array([1, -1, -1, 1])).all()
+ s3 = crop_edges(s2, crop_range=1)
+ assert (
+ s3.metadata.Signal.cropped_edges == array([[1, -1, -1, 1], [1, -1, -1, 1]])
+ ).all()
+
+
+def test_crop_edges_too_far():
+ s1 = LumiSpectrum(ones((10, 10, 10)))
+ with raises(IndexError, match="The pixels to be cropped"):
+ crop_edges(s1, crop_range=6)
+
+
+@mark.parametrize(
+ "range, output",
+ [
+ (2, (6)),
+ ((2, 4), (4)),
+ ((1, 2, 3, 4), ()),
+ ((1, 2, 3), ()),
+ ("2nm", (6)),
+ (("2nm", "4nm"), (2)),
+ ],
+)
+def test_crop_edges_linescan(range, output):
+ s1 = [LumiSpectrum(ones((10, 10)))]
+ s1[0].axes_manager.navigation_axes[0].units = "nm"
+
+ if type(range) == tuple and len(range) not in (1, 2):
+ with raises(ValueError, match="tuple must be either a"):
+ crop_edges(s1, range)
+
+ else:
+ s1 = crop_edges(s1, range)
+ assert s1[0].axes_manager.navigation_shape[0] == output
+
+
+@mark.parametrize("data", [((10,) * 4), ((10,) * 5)])
+def test_crop_edges_multidim(data):
+ s1 = [LumiSpectrum(ones((data)))]
+ with raises(NotImplementedError, match="navigation axes with more than 2"):
+ crop_edges(s1, 2)
+
+
+def test_crop_edges_deprecated():
+ s1 = LumiSpectrum(ones((10, 10, 10)))
+ with warns(DeprecationWarning, match="is deprecated"):
+ s2 = crop_edges(s1, crop_px=1)
+ assert s2.axes_manager.navigation_shape[0] == 8
+ assert s2.axes_manager.navigation_shape[1] == 8
+ with warns(DeprecationWarning, match="Both"):
+ s2 = crop_edges(s1, crop_range=1, crop_px=2)
+ assert s2.axes_manager.navigation_shape[0] == 8
+ assert s2.axes_manager.navigation_shape[1] == 8
+
+
+def test_crop_edges_multiple_no_rebin():
+ s1 = [LumiSpectrum(ones((10, 10, 10)))] * 2
+ s2 = crop_edges(s1, crop_range=1, rebin_nav=False)
+ for s in s2:
+ assert s.axes_manager.navigation_shape[0] == 8
+ assert s.axes_manager.navigation_shape[1] == 8
+ s1 = [
+ LumiSpectrum(ones((10, 10, 10))),
+ ] * 3
+ s2 = crop_edges(s1, crop_range=1, rebin_nav=False)
+ for s in s2:
+ assert s.axes_manager.navigation_shape[0] == 8
+ assert s.axes_manager.navigation_shape[1] == 8
+
+ s1 = [
+ LumiSpectrum(ones((10, 10, 10))),
+ Signal1D(ones((10, 10, 10))),
+ Signal2D(ones((10, 10, 10, 10))),
+ ]
+ s2 = crop_edges(s1, crop_range=1, rebin_nav=False)
+ for s in s2:
+ assert s.axes_manager.navigation_shape[0] == 8
+ assert s.axes_manager.navigation_shape[1] == 8
+
+ s1 = [
+ LumiSpectrum(ones((10, 10, 10))),
+ LumiSpectrum(ones((20, 5, 10))),
+ ]
+ s2 = crop_edges(s1, crop_range=1, rebin_nav=False)
+ assert s2[0].axes_manager.navigation_shape[0] == 8
+ assert s2[0].axes_manager.navigation_shape[1] == 8
+ assert s2[1].axes_manager.navigation_shape[0] == 3
+ assert s2[1].axes_manager.navigation_shape[1] == 18
+
+
+def test_crop_edges_multiple_error():
+ s1 = [
+ LumiSpectrum(ones((10, 10, 10))),
+ LumiSpectrum(ones((10, 10))),
+ ]
+ with raises(ValueError, match="mix of navigation axes"):
+ crop_edges(s1, 1)
+
+
+def test_crop_edges_multiple_rebin():
+ s1 = [
+ LumiSpectrum(ones((10, 10, 10))),
+ LumiSpectrum(ones((20, 20, 10))),
+ LumiSpectrum(ones((5, 5, 10))),
+ LumiSpectrum(ones((13, 7, 10))),
+ LumiSpectrum(ones((5, 20, 5))),
+ ]
+ s2 = crop_edges(s1, crop_range=1, rebin_nav=True)
+ for s in s2:
+ assert s.axes_manager.navigation_shape[0] == 8
+ assert s.axes_manager.navigation_shape[1] == 8
+ # Check signal axis has not been changed
+ assert s2[0].axes_manager.signal_shape[0] == 10
+ assert s2[-1].axes_manager.signal_shape[0] == 5
diff --git a/lumispy/utils/__init__.py b/lumispy/utils/__init__.py
index 45b5b60eb..7fd34827e 100644
--- a/lumispy/utils/__init__.py
+++ b/lumispy/utils/__init__.py
@@ -30,3 +30,8 @@
join_spectra,
solve_grating_equation,
)
+
+from .signals import (
+ com,
+ crop_edges,
+)
diff --git a/lumispy/utils/signals.py b/lumispy/utils/signals.py
index 8467b3eac..c83e31dde 100644
--- a/lumispy/utils/signals.py
+++ b/lumispy/utils/signals.py
@@ -1,7 +1,26 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019-2023 The LumiSpy developers
+#
+# This file is part of LumiSpy.
+#
+# LumiSpy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the license, or
+# (at your option) any later version.
+#
+# LumiSpy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LumiSpy. If not, see .
+
import numpy as np
from hyperspy.axes import FunctionalDataAxis
from scipy.ndimage import center_of_mass
from scipy.interpolate import interp1d
+from warnings import warn
def com(spectrum_intensities, signal_axis, **kwargs):
@@ -79,3 +98,172 @@ def _interpolate_signal(axis_array, index, **kwargs):
raise ValueError("The parmeter `signal_axis` must be a HyperSpy Axis object.")
return com_val
+
+
+#
+# navigation axis manipulation
+#
+
+
+def crop_edges(
+ S,
+ crop_range=None,
+ rebin_nav=False,
+ **kwargs,
+):
+ """
+ Cropping along the navigation axes of a list of signal objects.
+ Crop the amount of pixels from the four edges of the scanning
+ region, from the edges inwards. Cropping can happen uniformly on all
+ sides or by specifying the cropping range for each axis or each side. If the navigation axes shape is different, all signals can be rebinned to match the shape of the first signal in the list.
+ Parameters
+ ----------
+ S : list of HyperSpy Signal objects or a single HyperSpy Signal object.
+ crop_range : {int | float | str} or tuple of {ints | floats | strs}
+ If int the values are taken as indices. If float the values are converted to indices. If str HyperSpy fancy indexing is used (e.g. ``rel0.1`` will crop 10% on each side, or ``100 nm`` will crop 100 nm on each side).
+ If a number or a tuple of size 1 is passed, all sides are cropped by the same amount. If a tuple of size 2 is passed (``crop_x``, ``crop_y``), a different
+ amount is cropped from the x and y directions, respectively. If a tuple of size 4 is passed (``crop_left``, ``crop_bottom``, ``crop_right``, ``crop_top``), a different amount is cropped from each edge individually.
+ rebin_nav : bool
+ If the navigation axes shape is different between signals in the list S, all signals will be rebinned to match the shape of the first signal in the list. Note this does not take into account the calibration values of the navigation axes.
+ kwrgs
+ To account for the deprecated ``crop_px`` parameter.
+ Returns
+ -------
+ S_cropped : Signal or list of Signals
+ A list of smaller, cropped Signal objects or a cropped single Signal if only one signal object is passed as input.
+ """
+
+ def range_formatting(str_list):
+ if len(str_list) == 2:
+ px = S[0].inav[: str_list[0]].axes_manager.navigation_shape[0]
+ px_list = [px, -px]
+ else:
+ # Pairwaise formatting with [top, left, right, bottom] when len(str_list) == 4
+ px_x = S[0].inav[: str_list[0], :].axes_manager.navigation_shape[0]
+ px_y = S[0].inav[:, : str_list[1]].axes_manager.navigation_shape[1]
+ px_list = [px_x, -px_y, -px_x, px_y]
+ return np.array(px_list, dtype=int)
+
+ # Deprecation warning (for compatibility with ``crop_px``)
+ if "crop_px" in kwargs and crop_range is not None:
+ warn(
+ "Both ``crop_range`` and the deprecated ``crop_px`` were passed. Only ``crop_range`` is being used.",
+ DeprecationWarning,
+ 2,
+ )
+ elif "crop_px" in kwargs:
+ warn(
+ "``crop_px`` is deprecated; use ``crop_range`` instead.",
+ DeprecationWarning,
+ 2,
+ )
+ crop_range = int(kwargs["crop_px"])
+ elif crop_range is None:
+ crop_range = 0
+
+ # Check that S is a list
+ no_list = False
+ if type(S) is not list:
+ no_list = True
+ S = [S]
+
+ # Check all signals in list are compatible (same range) and rebin
+ nav_shape = S[0].axes_manager.navigation_shape
+ for i, s in enumerate(S):
+ if i == 0:
+ continue
+ if len(nav_shape) != len(s.axes_manager.navigation_shape):
+ raise ValueError(
+ "The signal list contains a mix of navigation axes dimensions which cannot be broadcasted."
+ )
+ if nav_shape != s.axes_manager.navigation_shape:
+ if not rebin_nav:
+ warn(
+ f"The navivigation axes of the first signal in index = 0 and in index = {i} have different shapes of {nav_shape} and {s.axes_manager.navigation_shape} respectively. This may cause errors during cropping. You can turn `rebin_nav` to True to rebin navigation axes.",
+ UserWarning,
+ )
+ if rebin_nav:
+ scale = np.array(s.axes_manager.navigation_shape) / np.array(nav_shape)
+ signal_dim = len(s.axes_manager.signal_shape)
+ scale = np.append(scale, [1] * signal_dim)
+ S[i] = s.rebin(scale=scale)
+
+ # Check for the size of the navigation axis
+ line_scan = False
+ nav_shape = s.axes_manager.navigation_shape
+ if len(nav_shape) == 1:
+ line_scan = True
+ elif len(nav_shape) > 2:
+ raise NotImplementedError(
+ "`crop_edges` is not supported for navigation axes with more than 2 dimensions."
+ )
+
+ crop_range_type = type(crop_range)
+ # Create a list of [top, left, right, bottom] or for line_scan only [left, right]
+ n = 2 if line_scan else 4
+ if crop_range_type in (int, float, str):
+ crop_vals = [crop_range] * n
+ crop_range = [crop_range]
+ elif crop_range_type is tuple:
+ if len(crop_range) == 2:
+ crop_vals = (list(crop_range) * (n // 2),)
+ crop_vals = crop_vals[0]
+ elif len(crop_range) == 4 and not line_scan:
+ crop_vals = list(crop_range)
+ else:
+ raise ValueError(
+ f"The ``crop_range`` tuple must be either a 2-tuple (x,y) or a 4-tuple (left, bottom, right, top). You provided a {len(crop_range)}-tuple. For line scans, the tuple must be a 2-tuple (left, right)."
+ )
+ else:
+ raise ValueError(
+ f"The crop_range value must be a number, a string, or a tuple, not a {crop_range_type}"
+ )
+
+ # Negative means reverse indexing
+ if type(crop_vals[0]) is int:
+ if line_scan:
+ crop_vals = np.array(crop_vals) * [1, -1]
+ else:
+ crop_vals = np.array(crop_vals) * [1, -1, -1, 1]
+ else:
+ # Check if input was already fine or if str/float needs to be reformatted
+ if (len(crop_range) == 4) or (len(crop_range) == 2 and line_scan):
+ # Shuffle order from [left, bottom, right, top] to [top, left, right, bottom]
+ # crop_vals = [crop_vals[3], crop_vals[0], crop_vals[2], crop_vals[1]]
+ pass
+ else:
+ crop_vals = range_formatting(crop_vals)
+
+ S_cropped = []
+ for s in S:
+
+ # Remove 0 for None
+ crop_ids = [x if x != 0 else None for x in crop_vals]
+
+ # Crop accordingly
+ if line_scan:
+ signal_cropped = s.inav[crop_ids[0] : crop_ids[1]]
+ else:
+ signal_cropped = s.inav[
+ crop_ids[0] : crop_ids[2], crop_ids[3] : crop_ids[1]
+ ]
+
+ # Check if cropping went too far
+ if 0 in signal_cropped.axes_manager.navigation_shape:
+ raise IndexError(
+ "The pixels to be cropped surpassed the width/height of the signal navigation axes."
+ )
+
+ # Store transformation in metadata (or update the value if already previously transformed)
+ if signal_cropped.metadata.has_item("Signal.cropped_edges"):
+ signal_cropped.metadata.Signal.cropped_edges = np.vstack(
+ (signal_cropped.metadata.Signal.cropped_edges, crop_vals)
+ )
+ else:
+ signal_cropped.metadata.set_item("Signal.cropped_edges", crop_vals)
+ S_cropped.append(signal_cropped)
+
+ if no_list:
+ return S_cropped[0]
+ else:
+ return S_cropped