From 04ccac3400f53ae0407e96985e6a2bd59a8e6de5 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 6 Aug 2024 11:15:45 -0500 Subject: [PATCH 01/18] Refactor: Delete pyfai dependency --- doc/reference/index.rst | 1 - pyxem/__init__.py | 2 - pyxem/detectors/__init__.py | 30 - pyxem/detectors/generic_flat_detector.py | 65 -- pyxem/detectors/medipix_256x256.py | 53 -- pyxem/detectors/medipix_515x515.py | 70 -- pyxem/signals/diffraction2d.py | 668 ++---------------- pyxem/signals/electron_diffraction2d.py | 18 - pyxem/tests/detectors/__init__.py | 0 .../detectors/test_generic_flat_detector.py | 29 - pyxem/tests/detectors/test_medipix_256x256.py | 26 - pyxem/tests/detectors/test_medipix_515x515.py | 36 - pyxem/tests/signals/test_diffraction2d.py | 265 +------ .../signals/test_diffraction_variance2d.py | 1 - .../signals/test_electron_diffraction2d.py | 2 - pyxem/utils/_azimuthal_integrations.py | 2 +- setup.py | 2 - 17 files changed, 57 insertions(+), 1213 deletions(-) delete mode 100644 pyxem/detectors/__init__.py delete mode 100644 pyxem/detectors/generic_flat_detector.py delete mode 100644 pyxem/detectors/medipix_256x256.py delete mode 100644 pyxem/detectors/medipix_515x515.py delete mode 100644 pyxem/tests/detectors/__init__.py delete mode 100644 pyxem/tests/detectors/test_generic_flat_detector.py delete mode 100644 pyxem/tests/detectors/test_medipix_256x256.py delete mode 100644 pyxem/tests/detectors/test_medipix_515x515.py diff --git a/doc/reference/index.rst b/doc/reference/index.rst index 954992f3e..9f7160a61 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -19,7 +19,6 @@ explanations of the functionality. :toctree: generated :template: custom-module-template.rst - detectors data components dummy_data diff --git a/pyxem/__init__.py b/pyxem/__init__.py index 04151cb6c..c0f81f6d8 100644 --- a/pyxem/__init__.py +++ b/pyxem/__init__.py @@ -26,7 +26,6 @@ except ImportError: CUPY_INSTALLED = False from pyxem import components -from pyxem import detectors from pyxem import signals from pyxem import generators from pyxem import data @@ -39,7 +38,6 @@ __all__ = [ "components", - "detectors", "generators", "signals", "data", diff --git a/pyxem/detectors/__init__.py b/pyxem/detectors/__init__.py deleted file mode 100644 index f7a96cb6e..000000000 --- a/pyxem/detectors/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -"""Detectors defined for pyFAI integration""" - -from .generic_flat_detector import GenericFlatDetector -from .medipix_256x256 import Medipix256x256Detector -from .medipix_515x515 import Medipix515x515Detector - - -__all__ = [ - "GenericFlatDetector", - "Medipix256x256Detector", - "Medipix515x515Detector", -] diff --git a/pyxem/detectors/generic_flat_detector.py b/pyxem/detectors/generic_flat_detector.py deleted file mode 100644 index 26d7af438..000000000 --- a/pyxem/detectors/generic_flat_detector.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -""" -A generic flat detector class for azimuthal integration and other similar -operations using the pyFAI AzimuthalIntegrator for a - -""" - -from pyFAI.detectors import Detector -from pyxem.utils._deprecated import deprecated - - -class GenericFlatDetector(Detector): - """ - A PyFAI Detector class for an arbitrarily sized flat detector (i.e. the - calibration is assumed to be constant across the detector plane) - - The detector class is used for get_azimuthal_integral in a Diffraction2D - signal. The data is assumed to be flat in the small angle approximation. - - PyFAI works in real space coordinates, the pixel size is assumed to be 1 (m) - and the remaining parameters, via knowing the calibration and wavelength, - are calculated to have an appropriate physical setup. - - Parameters - ---------- - size_x : int - The size (in pixels) of the detector in the x coordinate. - size_y : int - The size (in pixels) of the detector in the y coordinate. - - Examples - -------- - >>> from pyxem.detectors import GenericFlatDetector - >>> detector = GenericFlatDetector(512,512) - >>> detector - Detector GenericFlatDetector Spline= None - PixelSize= 1.000e+00, 1.000e+00 m - """ - - IS_FLAT = True # this detector is flat - IS_CONTIGUOUS = True # No gaps: all pixels are adjacents - API_VERSION = "1.0" - aliases = ["GenericFlatDetector"] - - @deprecated(since="0.18.0", removal="0.20.0") - def __init__(self, size_x, size_y): - MAX_SHAPE = size_x, size_y - Detector.__init__(self, pixel1=1, pixel2=1, max_shape=MAX_SHAPE) diff --git a/pyxem/detectors/medipix_256x256.py b/pyxem/detectors/medipix_256x256.py deleted file mode 100644 index 659dbada4..000000000 --- a/pyxem/detectors/medipix_256x256.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -""" -256x256 Medipix Direct Electron Detector class for azimuthal integration -and other similar operations using pyFAI azimuthalIntegrator. -""" - -from pyFAI.detectors import Detector -from pyxem.utils._deprecated import deprecated - - -class Medipix256x256Detector(Detector): - """ - A PyFAI Detector class for a 256256 pixel Medipix direct electron detector. - The detector class is used for get_azimuthal_integral in a Diffraction2D - signal. The calibration is not assumed to be constant in scattering vector. - - Examples - -------- - >>> from pyxem.detectors import Medipix256x256Detector - >>> detector = Medipix256x256Detector() - >>> detector - Detector Medipix256x256Detector Spline= None - PixelSize= 5.500e-05, 5.500e-05 m - """ - - IS_FLAT = False # this detector is not flat - IS_CONTIGUOUS = True # No gaps: all pixels are adjacents - API_VERSION = "1.0" - aliases = ["Medipix256x256Detector"] - MAX_SHAPE = 256, 256 - - @deprecated(since="0.18.0", removal="0.20.0") - def __init__(self): - pixel1 = 55e-6 # 55 micron pixel size in x - pixel2 = 55e-6 # 55 micron pixel size in y - Detector.__init__(self, pixel1=pixel1, pixel2=pixel2) diff --git a/pyxem/detectors/medipix_515x515.py b/pyxem/detectors/medipix_515x515.py deleted file mode 100644 index b1110d492..000000000 --- a/pyxem/detectors/medipix_515x515.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -""" -515x515 Medipix Direct Electron Detector class for azimuthal integration -and other similar operations using pyFAI azimuthalIntegrator. -The three central pixels in both orientations are blanks, so those and three -and the surrounding ones are "masked". -""" - -from pyFAI.detectors import Detector -import numpy as np -from pyxem.utils._deprecated import deprecated - - -class Medipix515x515Detector(Detector): - """ - A PyFAI Detector class for a 515x515 pixel Medipix Quad direct electron - detector. A central 5x5 cross is not intepretable, and is stored as a - calc_mask method. - - The detector class is used for get_azimuthal_integral in a Diffraction2D - signal. The calibration is not assumed to be constant in scattering vector. - - Examples - -------- - >>> from pyxem.detectors import Medipix515x515Detector - >>> detector = Medipix515x515Detector() - >>> detector - Detector Medipix515x515Detector Spline= None - PixelSize= 5.500e-05, 5.500e-05 m - """ - - IS_FLAT = False # this detector is not flat - IS_CONTIGUOUS = True # No gaps: all pixels are adjacents - API_VERSION = "1.0" - aliases = ["Medipix515x515Detector"] - MAX_SHAPE = 515, 515 - - @deprecated(since="0.18.0", removal="0.20.0") - def __init__(self): - pixel1 = 55e-6 # 55 micron pixel size in x - pixel2 = 55e-6 # 55 micron pixel size in y - Detector.__init__(self, pixel1=pixel1, pixel2=pixel2) - - def calc_mask(self): - """Defines a function to define a mask of missing and uninterpretable - pixels in the detector plane, following - The missing segment is a 5-wide cross in the middle of the detector. - """ - - mask = np.zeros((515, 515)) - mask[255:260, :] = 1 - mask[:, 255:260] = 1 - return mask.astype(np.int8) diff --git a/pyxem/signals/diffraction2d.py b/pyxem/signals/diffraction2d.py index 1d13f6453..6baf9b636 100644 --- a/pyxem/signals/diffraction2d.py +++ b/pyxem/signals/diffraction2d.py @@ -36,14 +36,8 @@ from pyxem.signals import ( CommonDiffraction, ) -from pyxem.utils.pyfai_utils import ( - get_azimuthal_integrator, - _get_radial_extent, - _get_setup, -) + from pyxem.utils.diffraction import ( - azimuthal_integrate1d, - azimuthal_integrate2d, gain_normalise, remove_bad_pixels, circular_mask, @@ -55,9 +49,6 @@ find_beam_center_interpolate, find_center_of_mass, find_hot_pixels, - integrate_radially, - medfilt_1d, - sigma_clip, ) from pyxem.utils._azimuthal_integrations import ( _slice_radial_integrate, @@ -1652,184 +1643,14 @@ def get_variance( ) return variance - """ Methods associated with radial integration, not pyFAI based """ - - @deprecated( - since="0.17", - alternative="pyxem.signals.diffraction2d.azimuthal_integral2d", - removal="1.0.0", - ) - def angular_slice_radial_average( - self, - angleN=20, - centre_x=None, - centre_y=None, - slice_overlap=None, - show_progressbar=True, - ): - """Do radial average of different angular slices. - Useful for analysing anisotropy in round diffraction features, - such as diffraction rings from polycrystalline materials or - higher order Laue zone rings. - - Parameters - ---------- - angleN : int, default 20 - Number of angular slices. If angleN=4, each slice - will be 90 degrees. The average will start in the top left - corner (0, 0) when plotting using s.plot(), and go clockwise. - centre_x, centre_y : int or NumPy array, optional - If given as int, all the diffraction patterns will have the same - centre position. Each diffraction pattern can also have different - centre position, by passing a NumPy array with the same dimensions - as the navigation axes. - Note: in either case both x and y values must be given. If one is - missing, both will be set from the signal (0., 0.) positions. - If no values are given, the (0., 0.) positions in the signal will - be used. - slice_overlap : float, optional - Amount of overlap between the slices, given in fractions of - angle slice (0 to 1). For angleN=4, each slice will be 90 - degrees. If slice_overlap=0.5, each slice will overlap by 45 - degrees on each side. The range of the slices will then be: - (-45, 135), (45, 225), (135, 315) and (225, 45). - Default off: meaning there is no overlap between the slices. - show_progressbar : bool - Default True - - Returns - ------- - signal : HyperSpy 1D signal - With one more navigation dimensions (the angular slices) compared - to the input signal. - - Examples - -------- - >>> s = pxm.data.dummy_data.get_holz_simple_test_signal() - >>> s_com = s.center_of_mass(show_progressbar=False) - >>> s_ar = s.angular_slice_radial_average( - ... angleN=10, centre_x=s_com.inav[0].data, - ... centre_y=s_com.inav[1].data, slice_overlap=0.2, - ... show_progressbar=False) - >>> s_ar.plot() # doctest: +SKIP - - """ - signal_list = [] - angle_list = [] - if slice_overlap is None: - slice_overlap = 0 - else: - if (slice_overlap < 0) or (slice_overlap > 1): - raise ValueError( - "slice_overlap is {0}. But must be between " - "0 and 1".format(slice_overlap) - ) - angle_step = 2 * np.pi / angleN - for i in range(angleN): - angle0 = (angle_step * i) - (angle_step * slice_overlap) - angle1 = (angle_step * (i + 1)) + (angle_step * slice_overlap) - angle_list.append((angle0, angle1)) - if (centre_x is None) or (centre_y is None): - centre_x, centre_y = pst._make_centre_array_from_signal(self) - elif (not isiterable(centre_x)) or (not isiterable(centre_y)): - centre_x, centre_y = pst._make_centre_array_from_signal( - self, x=centre_x, y=centre_y - ) - - for angle in tqdm(angle_list, disable=(not show_progressbar)): - mask_array = self.angular_mask( - angle[0], angle[1], centre_x_array=centre_x, centre_y_array=centre_y - ) - s_r = self.radial_average( - centre_x=centre_x, - centre_y=centre_y, - mask_array=mask_array, - show_progressbar=show_progressbar, - ) - signal_list.append(s_r) - angle_scale = angle_list[1][1] - angle_list[0][1] - signal = hs.stack(signal_list, new_axis_name="Angle slice") - signal.axes_manager["Angle slice"].offset = angle_scale / 2 - signal.axes_manager["Angle slice"].scale = angle_scale - signal.axes_manager["Angle slice"].units = "Radians" - signal.axes_manager[-1].name = "Scattering angle" - return signal - - """ Methods associated with radial integration, pyFAI based """ - - @property - def ai(self): - try: - return self.metadata.Signal["ai"] - except AttributeError: - raise ValueError("ai property is not currently set") - - @deprecated( - since="0.18", - removal="1.0.0", - alternative="pyxem.signals.diffraction2d.calibration", - ) - def set_ai( - self, center=None, wavelength=None, affine=None, radial_range=None, **kwargs - ): - """This function sets the .ai parameter which stores an ~pyfai.AzimuthalIntegrator object based on - the current calibration applied to the diffraction pattern. - - Parameters - -------- - center: (x,y) or None - The center of the diffraction pattern. If None, the center is the middle of the image. - wavelength: float - The wavelength of the energy in 1/meters. For proper treatment of Ewald Sphere - affine: numpy.Array 3x3 - A 3x3 array which describes the affine distortion of the pattern. This is translated - to a spline interpolation which is used in the pyFAI implementations - radial_range: (start,stop) - The start and stop of the radial range in real units - - Returns - ------- - None : - The metadata item Signal.ai is set - - """ - if wavelength is None and self.unit not in ["2th_deg", "2th_rad"]: - raise ValueError( - "if the unit is not '2th_deg' or '2th_rad' then a wavelength must be given." - ) - - pixel_scale = [ - self.axes_manager.signal_axes[0].scale, - self.axes_manager.signal_axes[1].scale, - ] - - sig_shape = self.axes_manager.signal_shape - setup = _get_setup(wavelength, self.unit, pixel_scale, radial_range) - detector, dist, radial_range = setup - ai = get_azimuthal_integrator( - detector=detector, - detector_distance=dist, - shape=sig_shape, - center=center, - affine=affine, - wavelength=wavelength, - **kwargs, - ) - self.metadata.set_item("Signal.ai", ai) - return None + """ Methods associated with radial integration """ - @deprecated_argument( - name="lazy_result", since="0.14", removal="1.0.0", alternative="lazy_output" - ) def get_azimuthal_integral1d( self, npt, mask=None, radial_range=None, - azimuth_range=None, inplace=False, - method="splitpixel_pyxem", - sum=False, **kwargs, ): """Creates a polar reprojection using pyFAI's azimuthal integrate 2d. This method is designed @@ -1852,13 +1673,7 @@ def get_azimuthal_integral1d( from -pi to pi inplace : bool If the signal is overwritten or copied to a new signal - method : str - Can be “numpy”, “cython”, “BBox” or “splitpixel”, “lut”, “csr”, - “nosplit_csr”, “full_csr”, “lut_ocl” and “csr_ocl” if you want - to go on GPU. To Specify the device: “csr_ocl_1,2” - sum : bool - If true returns the pixel split sum rather than the azimuthal integration which - gives the mean. + Other Parameters ------- @@ -1898,52 +1713,22 @@ def get_azimuthal_integral1d( -------- pyxem.signals.Diffraction2D.get_azimuthal_integral2d """ - usepyfai = method not in ["splitpixel_pyxem"] - if not usepyfai: - # get_slices1d should be sped up in the future by - # getting rid of shapely and using numba on the for loop - indexes, facts, factor_slices, radial_range = self.calibration.get_slices1d( - npt, radial_range=radial_range - ) - if mask is None: - mask = self.calibration.mask - integration = self.map( - _slice_radial_integrate1d, - indexes=indexes, - factors=facts, - factor_slices=factor_slices, - inplace=inplace, - mask=mask, - **kwargs, - ) - else: - if "wavelength" in kwargs: - warnings.warn( - "The wavelength parameter was removed in 0.14. The wavelength " - "can be set using the `set_ai` function or using `s.beam_energy`" - " for `ElectronDiffraction2D` signals" - ) - kwargs.pop("wavelength") - - sig_shape = self.axes_manager.signal_shape - if radial_range is None: - radial_range = _get_radial_extent( - ai=self.ai, shape=sig_shape, unit=self.unit - ) - radial_range[0] = 0 - integration = self.map( - azimuthal_integrate1d, - azimuthal_integrator=self.ai, - npt_rad=npt, - azimuth_range=azimuth_range, - radial_range=radial_range, - method=method, - inplace=inplace, - unit=self.unit, - mask=mask, - sum=sum, - **kwargs, - ) + # get_slices1d should be sped up in the future by + # getting rid of shapely and using numba on the for loop + indexes, facts, factor_slices, radial_range = self.calibration.get_slices1d( + npt, radial_range=radial_range + ) + if mask is None: + mask = self.calibration.mask + integration = self.map( + _slice_radial_integrate1d, + indexes=indexes, + factors=facts, + factor_slices=factor_slices, + inplace=inplace, + mask=mask, + **kwargs, + ) s = self if inplace else integration k_axis = s.axes_manager.signal_axes[0] if not isinstance(k_axis, UniformDataAxis): @@ -1963,9 +1748,6 @@ def get_azimuthal_integral2d( radial_range=None, azimuth_range=None, inplace=False, - method="splitpixel_pyxem", - sum=False, - correctSolidAngle=True, **kwargs, ): """Creates a polar reprojection using pyFAI's azimuthal integrate 2d. This method is designed @@ -1990,11 +1772,6 @@ def get_azimuthal_integral2d( from -pi to pi inplace: bool If the signal is overwritten or copied to a new signal - method: str - Can be “numpy”, “cython”, “BBox” or “splitpixel”, “lut”, “csr”, - “nosplit_csr”, “full_csr”, “lut_ocl” and “csr_ocl” if you want - to go on GPU. To Specify the device: “csr_ocl_1,2”. For pure - pyxem based methods use "splitpixel_pyxem". sum: bool If true the radial integration is returned rather then the Azimuthal Integration. correctSolidAngle: bool @@ -2046,74 +1823,47 @@ def get_azimuthal_integral2d( if azimuth_range is None: azimuth_range = (-np.pi, np.pi) - usepyfai = method not in ["splitpixel_pyxem"] - if not usepyfai: - # get_slices2d should be sped up in the future by - # getting rid of shapely and using numba on the for loop - slices, factors, factors_slice, radial_range = ( - self.calibration.get_slices2d( - npt, - npt_azim, - radial_range=radial_range, - azimuthal_range=azimuth_range, - ) + # get_slices2d should be sped up in the future by + # getting rid of shapely and using numba on the for loop + slices, factors, factors_slice, radial_range = self.calibration.get_slices2d( + npt, + npt_azim, + radial_range=radial_range, + azimuthal_range=azimuth_range, + ) + if self._gpu and CUPY_INSTALLED: # pragma: no cover + from pyxem.utils._azimuthal_integrations import ( + _slice_radial_integrate_cupy, ) - if self._gpu and CUPY_INSTALLED: # pragma: no cover - from pyxem.utils._azimuthal_integrations import ( - _slice_radial_integrate_cupy, - ) - - slices = cp.asarray(slices) - factors = cp.asarray(factors) - factors_slice = cp.asarray(factors_slice) - integration = self._blockwise( - _slice_radial_integrate_cupy, - slices=slices, - factors=factors, - factors_slice=factors_slice, - npt=npt, - npt_azim=npt_azim, - inplace=inplace, - signal_shape=(npt, npt_azim), - mask=mask, - dtype=float, - **kwargs, - ) - else: - if mask is None: - mask = self.calibration.mask - integration = self.map( - _slice_radial_integrate, - slices=slices, - factors=factors, - factors_slice=factors_slice, - npt_rad=npt, - npt_azim=npt_azim, - inplace=inplace, - mask=mask, - **kwargs, - ) + slices = cp.asarray(slices) + factors = cp.asarray(factors) + factors_slice = cp.asarray(factors_slice) + integration = self._blockwise( + _slice_radial_integrate_cupy, + slices=slices, + factors=factors, + factors_slice=factors_slice, + npt=npt, + npt_azim=npt_azim, + inplace=inplace, + signal_shape=(npt, npt_azim), + mask=mask, + dtype=float, + **kwargs, + ) else: - sig_shape = self.axes_manager.signal_shape - if radial_range is None: - radial_range = _get_radial_extent( - ai=self.ai, shape=sig_shape, unit=self.unit - ) - radial_range[0] = 0 + if mask is None: + mask = self.calibration.mask integration = self.map( - azimuthal_integrate2d, - azimuthal_integrator=self.ai, + _slice_radial_integrate, + slices=slices, + factors=factors, + factors_slice=factors_slice, npt_rad=npt, npt_azim=npt_azim, - azimuth_range=azimuth_range, - radial_range=radial_range, - method=method, inplace=inplace, - unit=self.unit, mask=mask, - sum=sum, - correctSolidAngle=correctSolidAngle, **kwargs, ) @@ -2139,320 +1889,6 @@ def get_azimuthal_integral2d( return integration - def get_radial_integral( - self, - npt, - npt_rad, - mask=None, - radial_range=None, - azimuth_range=None, - inplace=False, - method="splitpixel", - sum=False, - correctSolidAngle=True, - **kwargs, - ): - """Calculate the radial integrated profile curve as I = f(chi) - - Parameters - ---------- - npt: int - The number of radial points to calculate - npt_rad: int - number of points in the radial space. Too few points may lead to huge rounding errors. - mask: boolean array or BaseSignal - A boolean mask to apply to the data to exclude some points. - If mask is a BaseSignal then it is iterated over as well. - radial_range: None or (float, float) - The radial range over which to perform the integration. Default is - the full frame - azimuth_range:None or (float, float) - The azimuthal range over which to perform the integration. Default is - from -pi to pi - inplace: bool - If the signal is overwritten or copied to a new signal - method: str - Can be “numpy”, “cython”, “BBox” or “splitpixel”, “lut”, “csr”, - “nosplit_csr”, “full_csr”, “lut_ocl” and “csr_ocl” if you want - to go on GPU. To Specify the device: “csr_ocl_1,2” - sum: bool - If true the radial integration is returned rather then the Azimuthal Integration. - correctSolidAngle: bool - Account for Ewald sphere or not. From PYFAI. - - Other Parameters - ------- - dummy: float - Value for dead/masked pixels - delta_dummy: float - Percision value for dead/masked pixels - correctSolidAngle: bool - Correct for the solid angle of each pixel if True - dark: ndarray - The dark noise image - flat: ndarray - The flat field correction image - safe: bool - Do some extra checks to ensure LUT/CSR is still valid. False is faster. - show_progressbar: bool - If True shows a progress bar for the mapping function - - Returns - ------- - polar: PolarDiffraction2D - A polar diffraction signal - - Examples - -------- - Basic case using "2th_deg" units (no wavelength needed) - - >>> ds.unit = "2th_deg" - >>> ds.set_ai() - >>> ds.get_radial_integral(npt=100, npt_rad=400) - - Basic case using a curved Ewald Sphere approximation and pyXEM units - (wavelength needed) - - >>> ds.unit = "k_nm^-1" # setting units - >>> ds.set_ai(wavelength=2.5e-12) - >>> ds.get_radial_integral(npt=100,npt_rad=400) - - """ - sig_shape = self.axes_manager.signal_shape - if radial_range is None: - radial_range = _get_radial_extent( - ai=self.ai, shape=sig_shape, unit=self.unit - ) - radial_range[0] = 0 - integration = self.map( - integrate_radially, - azimuthal_integrator=self.ai, - npt=npt, - npt_rad=npt_rad, - azimuth_range=azimuth_range, - radial_range=radial_range, - method=method, - inplace=inplace, - radial_unit=self.unit, - mask=mask, - sum=sum, - correctSolidAngle=correctSolidAngle, - **kwargs, - ) - - s = self if inplace else integration - - # Dealing with axis changes - k_axis = s.axes_manager.signal_axes[0] - k_axis.name = "Radius" - k_axis.scale = (radial_range[1] - radial_range[0]) / npt - k_axis.offset = radial_range[0] - - return integration - - def get_medfilt1d( - self, - npt_rad=1028, - npt_azim=512, - mask=None, - inplace=False, - method="splitpixel", - sum=False, - correctSolidAngle=True, - **kwargs, - ): - """Calculate the radial integrated profile curve as I = f(chi) - - Parameters - ---------- - npt_rad: int - The number of radial points. - npt_azim: int - The number of radial points - mask: boolean array or BaseSignal - A boolean mask to apply to the data to exclude some points. - If mask is a BaseSignal then it is iterated over as well. - inplace: bool - If the signal is overwritten or copied to a new signal - method: str - Can be “numpy”, “cython”, “BBox” or “splitpixel”, “lut”, “csr”, - “nosplit_csr”, “full_csr”, “lut_ocl” and “csr_ocl” if you want - to go on GPU. To Specify the device: “csr_ocl_1,2” - sum: bool - If true the radial integration is returned rather then the Azimuthal Integration. - correctSolidAngle: bool - Account for Ewald sphere or not. From PYFAI. - - Other Parameters - ------- - dummy: float - Value for dead/masked pixels - delta_dummy: float - Percision value for dead/masked pixels - correctSolidAngle: bool - Correct for the solid angle of each pixel if True - dark: ndarray - The dark noise image - flat: ndarray - The flat field correction image - safe: bool - Do some extra checks to ensure LUT/CSR is still valid. False is faster. - show_progressbar: bool - If True shows a progress bar for the mapping function - - - Returns - ------- - polar: PolarDiffraction2D - A polar diffraction signal - - Examples - -------- - Basic case using "2th_deg" units (no wavelength needed) - - >>> ds.unit = "2th_deg" - >>> ds.set_ai() - >>> ds.get_radial_integral(npt=100, npt_rad=400) - - Basic case using a curved Ewald Sphere approximation and pyXEM units - (wavelength needed) - - >>> ds.unit = "k_nm^-1" # setting units - >>> ds.set_ai(wavelength=2.5e-12) - >>> ds.get_radial_integral(npt=100,npt_rad=400) - - """ - sig_shape = self.axes_manager.signal_shape - radial_range = _get_radial_extent(ai=self.ai, shape=sig_shape, unit=self.unit) - radial_range[0] = 0 - integration = self.map( - medfilt_1d, - azimuthal_integrator=self.ai, - npt_rad=npt_rad, - npt_azim=npt_azim, - method=method, - inplace=inplace, - unit=self.unit, - mask=mask, - correctSolidAngle=correctSolidAngle, - **kwargs, - ) - - s = self if inplace else integration - - # Dealing with axis changes - k_axis = s.axes_manager.signal_axes[0] - k_axis.name = "Radius" - k_axis.scale = (radial_range[1] - radial_range[0]) / npt_rad - # k_axis.units = unit.unit_symbol - k_axis.offset = radial_range[0] - - return integration - - def sigma_clip( - self, - npt_rad=1028, - npt_azim=512, - mask=None, - thres=3, - max_iter=5, - inplace=False, - method="splitpixel", - sum=False, - correctSolidAngle=True, - **kwargs, - ): - """Perform the 2D integration and perform a sigm-clipping - iterative filter along each row. see the doc of scipy.stats.sigmaclip for the options. - - Parameters - ---------- - npt_rad: int - The number of radial points. - npt_azim: int - The number of radial points - mask: boolean array or BaseSignal - A boolean mask to apply to the data to exclude some points. - If mask is a BaseSignal then it is iterated over as well. - inplace: bool - If the signal is overwritten or copied to a new signal - method: str - Can be “numpy”, “cython”, “BBox” or “splitpixel”, “lut”, “csr”, - “nosplit_csr”, “full_csr”, “lut_ocl” and “csr_ocl” if you want - to go on GPU. To Specify the device: “csr_ocl_1,2” - sum: bool - If true the radial integration is returned rather then the Azimuthal Integration. - correctSolidAngle: bool - Account for Ewald sphere or not. From PYFAI. - - Other Parameters - ------- - dummy: float - Value for dead/masked pixels - delta_dummy: float - Percision value for dead/masked pixels - correctSolidAngle: bool - Correct for the solid angle of each pixel if True - dark: ndarray - The dark noise image - flat: ndarray - The flat field correction image - safe: bool - Do some extra checks to ensure LUT/CSR is still valid. False is faster. - show_progressbar: bool - If True shows a progress bar for the mapping function - - - Returns - ------- - polar: PolarDiffraction2D - A polar diffraction signal - - Examples - -------- - Basic case using "2th_deg" units (no wavelength needed) - - >>> ds.unit = "2th_deg" - >>> ds.set_ai() - >>> ds.get_radial_integral(npt=100, npt_rad=400) - - Basic case using a curved Ewald Sphere approximation and pyXEM units - (wavelength needed) - - >>> ds.unit = "k_nm^-1" # setting units - >>> ds.set_ai(wavelength=2.5e-12) - >>> ds.get_radial_integral(npt=100,npt_rad=400) - - """ - sig_shape = self.axes_manager.signal_shape - radial_range = _get_radial_extent(ai=self.ai, shape=sig_shape, unit=self.unit) - radial_range[0] = 0 - integration = self.map( - sigma_clip, - azimuthal_integrator=self.ai, - npt_rad=npt_rad, - npt_azim=npt_azim, - method=method, - max_iter=max_iter, - thres=thres, - inplace=inplace, - unit=self.unit, - mask=mask, - correctSolidAngle=correctSolidAngle, - **kwargs, - ) - - s = self if inplace else integration - - # Dealing with axis changes - k_axis = s.axes_manager.signal_axes[0] - k_axis.name = "Radius" - k_axis.scale = (radial_range[1] - radial_range[0]) / npt_rad - # k_axis.units = unit.unit_symbol - k_axis.offset = radial_range[0] - - return integration - class LazyDiffraction2D(LazySignal, Diffraction2D): pass diff --git a/pyxem/signals/electron_diffraction2d.py b/pyxem/signals/electron_diffraction2d.py index bdac91587..869bc9e12 100644 --- a/pyxem/signals/electron_diffraction2d.py +++ b/pyxem/signals/electron_diffraction2d.py @@ -61,24 +61,6 @@ def __init__(self, *args, **kwargs): ) del self.metadata.Acquisition_instrument.SEM - def set_ai( - self, center=None, energy=None, affine=None, radial_range=None, **kwargs - ): - if energy is None and self.beam_energy is not None: - energy = self.beam_energy - if energy is not None: - wavelength = get_electron_wavelength(energy) * 1e-10 - else: - wavelength = None - ai = super().set_ai( - center=center, - wavelength=wavelength, - affine=affine, - radial_range=radial_range, - **kwargs - ) - return ai - @property def beam_energy(self): try: diff --git a/pyxem/tests/detectors/__init__.py b/pyxem/tests/detectors/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyxem/tests/detectors/test_generic_flat_detector.py b/pyxem/tests/detectors/test_generic_flat_detector.py deleted file mode 100644 index 69fa2f46f..000000000 --- a/pyxem/tests/detectors/test_generic_flat_detector.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -import pytest -import numpy as np -from pyxem.detectors.generic_flat_detector import GenericFlatDetector -from pyFAI.detectors import Detector - - -def test_generic_flat_detector_init(): - size_x, size_y = 256, 256 - detector = GenericFlatDetector(size_x, size_y) - assert isinstance(detector, Detector) - assert np.array_equal(detector.max_shape, (size_x, size_y)) diff --git a/pyxem/tests/detectors/test_medipix_256x256.py b/pyxem/tests/detectors/test_medipix_256x256.py deleted file mode 100644 index e6646b518..000000000 --- a/pyxem/tests/detectors/test_medipix_256x256.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -import pytest -from pyxem.detectors.medipix_256x256 import Medipix256x256Detector -from pyFAI.detectors import Detector - - -def test_medipix_256x256_init(): - detector = Medipix256x256Detector() - assert isinstance(detector, Detector) diff --git a/pyxem/tests/detectors/test_medipix_515x515.py b/pyxem/tests/detectors/test_medipix_515x515.py deleted file mode 100644 index 36b19f533..000000000 --- a/pyxem/tests/detectors/test_medipix_515x515.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -import pytest -from pyxem.detectors.medipix_515x515 import Medipix515x515Detector -from pyFAI.detectors import Detector -import numpy as np - - -def test_medipix_515x515_init(): - detector = Medipix515x515Detector() - assert isinstance(detector, Detector) - - -def test_medipix_515x515_mask(): - detector = Medipix515x515Detector() - mask = detector.calc_mask() - mask_check = np.zeros((515, 515)) - mask_check[255:260, :] = 1 - mask_check[:, 255:260] = 1 - assert np.array_equal(mask, mask_check) diff --git a/pyxem/tests/signals/test_diffraction2d.py b/pyxem/tests/signals/test_diffraction2d.py index f7ad36280..8d3e30828 100644 --- a/pyxem/tests/signals/test_diffraction2d.py +++ b/pyxem/tests/signals/test_diffraction2d.py @@ -113,17 +113,6 @@ def test_unit(self, ones): def test_unit_set(self, ones): assert ones.unit == "2th_deg" - @pytest.mark.parametrize( - "unit", ["q_nm^-1", "q_A^-1", "k_nm^-1", "k_A^-1", "2th_deg", "2th_rad"] - ) - def test_1d_azimuthal_integral_2th_units(self, ones, unit): - ones.unit = unit - ones.set_ai(wavelength=1e-9) - az = ones.get_azimuthal_integral1d( - npt=10, correctSolidAngle=False, method="bbox" - ) - np.testing.assert_array_equal(az.data[0:8], np.ones(8)) - def test_1d_azimuthal_integral_pyxem(self, ones): ones.calibration.center = None ones.calibration.scale = 0.2 @@ -142,17 +131,15 @@ def test_1d_azimuthal_integral_pyxem(self, ones): ones.calibration.scale = 0.2 az = ones.get_azimuthal_integral1d( npt=10, - method="splitpixel_pyxem", inplace=True, radial_range=[0.0, 0.8], mean=True, ) assert isinstance(ones, Diffraction1D) - np.testing.assert_array_equal(ones.data[0:8], np.ones(8)) + np.testing.assert_array_almost_equal(ones.data[0:8], np.ones(8)) assert az is None def test_1d_azimuthal_integral_inplace(self, ones): - ones.set_ai() az = ones.get_azimuthal_integral1d( npt=10, inplace=True, @@ -162,74 +149,9 @@ def test_1d_azimuthal_integral_inplace(self, ones): def test_1d_azimuthal_integral_slicing(self, ones): ones.unit = "2th_rad" - ones.set_ai(center=(5.5, 5.5)) - az = ones.get_azimuthal_integral1d( - npt=10, - method="BBox", - correctSolidAngle=False, - radial_range=[0.0, 1.0], - ) - np.testing.assert_array_equal(az.data[0:7], np.ones(7)) - - @pytest.mark.parametrize( - "unit", ["q_nm^-1", "q_A^-1", "k_nm^-1", "k_A^-1", "2th_deg", "2th_rad"] - ) - def test_1d_axes_continuity(self, ones, unit): - ones.unit = unit - ones.set_ai(center=(5.5, 5.5), wavelength=1e-9) - az1 = ones.get_azimuthal_integral1d( - npt=10, - radial_range=[0.0, 1.0], - method="splitpixel", - ) - assert np.allclose(az1.axes_manager.signal_axes[0].scale, 0.1) - - @pytest.mark.parametrize("radial_range", [None, [0.0, 1.0]]) - @pytest.mark.parametrize("azimuth_range", [None, [-np.pi, np.pi]]) - @pytest.mark.parametrize("center", [None, [9, 9]]) - @pytest.mark.parametrize("affine", [None, [[1, 0, 0], [0, 1, 0], [0, 0, 1]]]) - def test_1d_integration( - self, - ones, - radial_range, - azimuth_range, - center, - affine, - ): - ones.set_ai(center=center, affine=affine, radial_range=radial_range) - az = ones.get_azimuthal_integral1d( - npt=10, - method="BBox", - radial_range=radial_range, - azimuth_range=azimuth_range, - correctSolidAngle=False, - inplace=False, - ) - assert isinstance(az, Diffraction1D) - - def test_1d_azimuthal_integral_mask(self, ones): - from hyperspy.signals import BaseSignal - - aff = [[1, 1, 0], [0, 1, 0], [0, 0, 1]] - center = [1, 1] - mask = np.zeros((10, 10)) - mask_bs = BaseSignal(data=mask) - ones.set_ai(center=center, affine=aff) - ones.get_azimuthal_integral1d( - npt=10, - method="BBox", - correctSolidAngle=False, - mask=mask_bs, - ) - - @pytest.mark.skip(reason="FAO: M.Nord, skipping to get green for new code") - def test_1d_azimuthal_integral_sum(self, ones): - ones.set_ai() - integration = ones.get_azimuthal_integral1d( - npt=5, radial_range=[0, 0.5], sum=True - ) - # 5^2*pi = 78.5 - np.testing.assert_almost_equal(integration.data.sum(), 78.5, decimal=0) + ones.calibration.center = None + az = ones.get_azimuthal_integral1d(npt=10, radial_range=(0.0, 1.0), mean=True) + np.testing.assert_array_almost_equal(az.data[0:7], np.ones(7)) @pytest.mark.parametrize( "shape", [(20, 16), (3, 20, 16), (4, 3, 20, 16), (6, 4, 3, 20, 16)] @@ -238,7 +160,6 @@ def test_lazy_input_lazy_output_different_shapes(self, shape): chunks = [5] * len(shape) s = LazyDiffraction2D(da.ones(shape, chunks=chunks)) s.unit = "2th_deg" - s.set_ai() npt = 10 s_a = s.get_azimuthal_integral1d(npt=npt) output_signal_shape = s.axes_manager.shape[:-2] + (npt,) @@ -255,7 +176,6 @@ def test_lazy_input_lazy_output_different_shapes(self, shape): def test_non_lazy_input_lazy_output(self, shape): s = Diffraction2D(np.ones(shape)) s.unit = "2th_deg" - s.set_ai() npt = 10 s_a = s.get_azimuthal_integral1d(npt=npt, lazy_output=True) output_signal_shape = s.axes_manager.shape[:-2] + (npt,) @@ -273,7 +193,6 @@ def test_lazy_input_non_lazy_output(self, shape): chunks = [5] * len(shape) s = LazyDiffraction2D(da.ones(shape, chunks=chunks)) s.unit = "2th_deg" - s.set_ai() npt = 10 s_a = s.get_azimuthal_integral1d(npt=npt, lazy_output=False) output_signal_shape = s.axes_manager.shape[:-2] + (npt,) @@ -288,7 +207,6 @@ def test_lazy_input_lazy_result_inplace(self, shape): chunks = [5] * len(shape) s = LazyDiffraction2D(da.ones(shape, chunks=chunks)) s.unit = "2th_deg" - s.set_ai() npt = 10 output_signal_shape = s.axes_manager.shape[:-2] + (npt,) output_data_shape = shape[:-2] + (npt,) @@ -306,7 +224,6 @@ def test_lazy_input_non_lazy_result_inplace(self, shape): chunks = [5] * len(shape) s = LazyDiffraction2D(da.ones(shape, chunks=chunks)) s.unit = "2th_deg" - s.set_ai() npt = 10 output_signal_shape = s.axes_manager.shape[:-2] + (npt,) output_data_shape = shape[:-2] + (npt,) @@ -320,7 +237,6 @@ def test_lazy_input_non_lazy_result_inplace(self, shape): def test_non_lazy_input_lazy_result_inplace(self, shape): s = Diffraction2D(np.ones(shape)) s.unit = "2th_deg" - s.set_ai() npt = 10 output_signal_shape = s.axes_manager.shape[:-2] + (npt,) output_data_shape = shape[:-2] + (npt,) @@ -474,111 +390,6 @@ def ring(self): ring_pattern.unit = "k_nm^-1" return ring_pattern - def test_2d_azimuthal_integral(self, ones): - ones.set_ai() - az = ones.get_azimuthal_integral2d( - npt=10, npt_azim=10, method="BBox", correctSolidAngle=False - ) - np.testing.assert_array_equal(az.data[0:8, :], np.ones((8, 10))) - - def test_2d_azimuthal_integral_scale(self, ring): - ring.set_ai(wavelength=2.5e-12) - az = ring.get_azimuthal_integral2d(npt=500, method="bbox") - peak = np.argmax(az.sum(axis=0)).data * az.axes_manager[1].scale - np.testing.assert_almost_equal(peak[0], 3, decimal=1) - ring.unit = "k_A^-1" - ring.set_ai(wavelength=2.5e-12) - az = ring.get_azimuthal_integral2d(npt=500, method="bbox") - peak = np.argmax(az.sum(axis=0)).data * az.axes_manager[1].scale - np.testing.assert_almost_equal(peak[0], 3, decimal=1) - - def test_2d_azimuthal_integral_fast_slicing(self, ones): - ones.set_ai(center=(5.5, 5.5)) - az1 = ones.get_azimuthal_integral2d( - npt=10, - npt_azim=10, - radial_range=[0.0, 1.0], - method="splitpixel", - correctSolidAngle=False, - ) - np.testing.assert_array_equal(az1.data[8:, :], np.zeros(shape=(2, 10))) - - @pytest.mark.parametrize( - "unit", ["q_nm^-1", "q_A^-1", "k_nm^-1", "k_A^-1", "2th_deg", "2th_rad"] - ) - def test_2d_axes_continuity(self, ones, unit): - ones.unit = unit - ones.set_ai(wavelength=1e-9, center=(5.5, 5.5)) - az1 = ones.get_azimuthal_integral2d( - npt=10, - npt_azim=20, - radial_range=[0.0, 1.0], - method="splitpixel", - ) - assert np.allclose(az1.axes_manager.signal_axes[1].scale, 0.1) - - def test_2d_azimuthal_integral_inplace(self, ones): - ones.set_ai() - az = ones.get_azimuthal_integral2d( - npt=10, - npt_azim=10, - correctSolidAngle=False, - inplace=True, - method="BBox", - ) - assert isinstance(ones, PolarDiffraction2D) - np.testing.assert_array_equal(ones.data[0:8, :], np.ones((8, 10))) - assert az is None - - @pytest.mark.parametrize("radial_range", [None, [0, 1.0]]) - @pytest.mark.parametrize("azimuth_range", [None, [-np.pi, 0]]) - @pytest.mark.parametrize("correctSolidAngle", [True, False]) - @pytest.mark.parametrize("center", [None, [7, 7]]) - @pytest.mark.parametrize("affine", [None, [[1, 0, 0], [0, 1, 0], [0, 0, 1]]]) - def test_2d_azimuthal_integral_params( - self, ones, radial_range, azimuth_range, correctSolidAngle, center, affine - ): - ones.set_ai( - center=center, affine=affine, radial_range=radial_range, wavelength=1e-9 - ) - az = ones.get_azimuthal_integral2d( - npt=10, - npt_azim=10, - radial_range=radial_range, - azimuth_range=azimuth_range, - correctSolidAngle=correctSolidAngle, - method="BBox", - ) - assert isinstance(az, PolarDiffraction2D) - - def test_2d_azimuthal_integral_mask_iterate(self, ones): - from hyperspy.signals import BaseSignal - - aff = [[1, 1, 0], [0, 1, 0], [0, 0, 1]] - center = [1, 1] - ones.set_ai(center=center, affine=aff, wavelength=1e-9) - mask = np.zeros((10, 10)) - mask_bs = BaseSignal(data=mask) - ones.get_azimuthal_integral2d( - npt=10, - npt_azim=10, - method="BBox", - correctSolidAngle=False, - mask=mask_bs, - ) - - def test_2d_azimuthal_integral_sum(self, ones): - ones.set_ai() - integration = ones.get_azimuthal_integral2d( - npt=10, npt_azim=15, radial_range=[0, 0.5], sum=True - ) - # mostly correct except for at the very center where things get weird... - mask = np.ones((10, 10), dtype=bool) - mask[0:5] = False - integration2 = ones.get_azimuthal_integral2d( - npt=10, npt_azim=15, radial_range=[0, 0.5], sum=True, mask=mask - ) - def test_internal_azimuthal_integration(self, ring): ring.calibration(scale=1) az = ring.get_azimuthal_integral2d(npt=40, npt_azim=100, radial_range=(0, 40)) @@ -661,74 +472,6 @@ def test_azimuthal_integration_range( assert np.allclose(quadrant.data[~np.isnan(quadrant.data)], expected_output) -class TestPyFAIIntegration: - @pytest.fixture - def ones(self): - ones_diff = Diffraction2D(data=np.ones(shape=(10, 10))) - ones_diff.axes_manager.signal_axes[0].scale = 0.1 - ones_diff.axes_manager.signal_axes[1].scale = 0.1 - ones_diff.axes_manager.signal_axes[0].name = "kx" - ones_diff.axes_manager.signal_axes[1].name = "ky" - ones_diff.unit = "q_nm^-1" - return ones_diff - - def test_integrate_radial(self, ones): - ones.set_ai(center=(5.5, 5.5), wavelength=1e-9) - assert isinstance(ones, Diffraction2D) - integration = ones.get_radial_integral( - npt=10, - npt_rad=100, - method="BBox", - correctSolidAngle=False, - ) - assert isinstance(integration, Diffraction1D) - np.testing.assert_array_equal(integration, np.ones(10)) - integration = ones.get_radial_integral( - npt=10, - npt_rad=100, - method="BBox", - correctSolidAngle=False, - sum=True, - ) - integration = ones.get_radial_integral( - npt=10, npt_rad=100, method="BBox", correctSolidAngle=False, inplace=True - ) - np.testing.assert_array_equal(ones, np.ones(10)) - assert integration is None - - def test_integrate_med_filter(self, ones): - ones.set_ai(center=(5.5, 5.5), wavelength=1e-9) - integration = ones.get_medfilt1d( - npt_rad=10, npt_azim=100, method="BBox", correctSolidAngle=False - ) - np.testing.assert_array_equal(integration, np.ones(10)) - integration = ones.get_medfilt1d( - npt_rad=10, - npt_azim=100, - method="BBox", - correctSolidAngle=False, - inplace=True, - ) - np.testing.assert_array_equal(ones, np.ones(10)) - assert integration is None - - def test_integrate_sigma_clip(self, ones): - ones.set_ai(center=(5.5, 5.5), wavelength=1e-9) - integration = ones.sigma_clip( - npt_rad=10, npt_azim=100, method="CSR", correctSolidAngle=False - ) # only CSR works - np.testing.assert_array_equal(integration, np.ones(10)) - integration = ones.sigma_clip( - npt_rad=10, - npt_azim=100, - method="CSR", - correctSolidAngle=False, - inplace=True, - ) - np.testing.assert_array_equal(ones, np.ones(10)) - assert integration is None - - class TestVirtualImaging: # Tests that virtual imaging runs without failure diff --git a/pyxem/tests/signals/test_diffraction_variance2d.py b/pyxem/tests/signals/test_diffraction_variance2d.py index ca6911ec2..299daf1b7 100644 --- a/pyxem/tests/signals/test_diffraction_variance2d.py +++ b/pyxem/tests/signals/test_diffraction_variance2d.py @@ -42,7 +42,6 @@ def test_1d_azimuthal_integration(self): ) ) var.unit = "2th_rad" - var.set_ai() integration = var.get_azimuthal_integral1d(npt=10) assert isinstance(integration, DiffractionVariance1D) diff --git a/pyxem/tests/signals/test_electron_diffraction2d.py b/pyxem/tests/signals/test_electron_diffraction2d.py index 4407ca174..6e7bf1c81 100644 --- a/pyxem/tests/signals/test_electron_diffraction2d.py +++ b/pyxem/tests/signals/test_electron_diffraction2d.py @@ -276,14 +276,12 @@ def ones(self): @pytest.mark.parametrize("energy", [None, 200]) def test_1d_azimuthal_integration(self, ones, energy): ones.beam_energy = energy - ones.set_ai() integration = ones.get_azimuthal_integral1d(npt=10) assert isinstance(integration, ElectronDiffraction1D) @pytest.mark.parametrize("energy", [None, 200]) def test_2d_azimuthal_integration(self, ones, energy): ones.beam_energy = energy - ones.set_ai() integration = ones.get_azimuthal_integral2d(npt=10) assert isinstance(integration, PolarDiffraction2D) diff --git a/pyxem/utils/_azimuthal_integrations.py b/pyxem/utils/_azimuthal_integrations.py index 108a55f97..f6455acfe 100644 --- a/pyxem/utils/_azimuthal_integrations.py +++ b/pyxem/utils/_azimuthal_integrations.py @@ -179,7 +179,7 @@ def _slice_radial_integrate1d( for index, fa in zip(ind, f): total = total + img[index[0], index[1]] * fa if mean: - total_f = 0.0 + total_f = np.finfo(np.float32).eps if mask is not None: total_f = total_f + mask[ind[0], ind[1]] * fa else: diff --git a/setup.py b/setup.py index 4ac7943db..726219cbe 100644 --- a/setup.py +++ b/setup.py @@ -94,11 +94,9 @@ "matplotlib >= 3.7.5", "numba", "numpy", - "numexpr != 2.8.6", # bug in 2.8.6 for greek letters need for pyfai "orix >= 0.12.1", "pooch", "psutil", - "pyfai <= 2023.9.0", # breaking changes "scikit-image >= 0.19.0, !=0.21.0", # regression in ellipse fitting" "scikit-learn >= 1.0", "shapely > 2.0.0", # major changes From 5dbf11b48ff8185f4589e64400747352d818a0ff Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 6 Aug 2024 11:33:51 -0500 Subject: [PATCH 02/18] Clean up extra code related to pyfai --- pyxem/tests/utils/test_calibration_utils.py | 26 --- pyxem/tests/utils/test_expt_utils.py | 43 ----- pyxem/tests/utils/test_pyfai_utils.py | 86 --------- pyxem/utils/calibration.py | 48 ----- pyxem/utils/diffraction.py | 1 - pyxem/utils/pyfai_utils.py | 199 -------------------- 6 files changed, 403 deletions(-) delete mode 100644 pyxem/tests/utils/test_pyfai_utils.py delete mode 100644 pyxem/utils/pyfai_utils.py diff --git a/pyxem/tests/utils/test_calibration_utils.py b/pyxem/tests/utils/test_calibration_utils.py index ad9f84a03..1989333b1 100644 --- a/pyxem/tests/utils/test_calibration_utils.py +++ b/pyxem/tests/utils/test_calibration_utils.py @@ -185,29 +185,3 @@ def test_to_string(self, calibration): == "Calibration for , " "Ewald sphere: curved, shape: (10, 10), affine: False, mask: False" ) - - def test_to_pyfai_no_unit(self, calibration): - with pytest.raises(ValueError): - calibration.to_pyfai() - - def test_to_pyfai_flat(self, calibration): - from pyFAI.azimuthalIntegrator import AzimuthalIntegrator - - calibration.units = "k_nm^-1" - calibration.beam_energy = 200 - ai = calibration.to_pyfai() - assert isinstance(ai, AzimuthalIntegrator) - - def test_to_pyfai_curved(self, calibration): - from pyFAI.azimuthalIntegrator import AzimuthalIntegrator - - calibration.beam_energy = 200 - calibration.detector(pixel_size=1, detector_distance=1, units="k_nm^-1") - ai = calibration.to_pyfai() - assert isinstance(ai, AzimuthalIntegrator) - - def test_to_pyfai_failure(self, calibration): - calibration.signal.axes_manager.signal_axes[0].convert_to_non_uniform_axis() - calibration.signal.axes_manager.signal_axes[1].convert_to_non_uniform_axis() - with pytest.raises(ValueError): - ai = calibration.to_pyfai() diff --git a/pyxem/tests/utils/test_expt_utils.py b/pyxem/tests/utils/test_expt_utils.py index cc13e4aa9..d86384eb7 100644 --- a/pyxem/tests/utils/test_expt_utils.py +++ b/pyxem/tests/utils/test_expt_utils.py @@ -21,7 +21,6 @@ from scipy.ndimage import gaussian_filter from matplotlib import pyplot as plt -from pyFAI.detectors import Detector from pyxem.utils.diffraction import ( _index_coords, @@ -194,45 +193,3 @@ def test_find_beam_center_interpolate_2(center_expected, sigma): z = gaussian_filter(z, sigma=sigma) centers = find_beam_center_interpolate(z, sigma=5, upsample_factor=100, kind=3) assert np.allclose(centers, center_expected, atol=0.2) - - -class TestAzimuthalIntegration: - @pytest.fixture - def radial_pattern(self): - x, y = np.ogrid[-5:5, -5:5] - radial = (x**2 + y**2) * np.pi - radial[radial == 0] = 1 - return 100 / radial - - def test_1d_integrate(self, radial_pattern): - from pyxem.utils.pyfai_utils import get_azimuthal_integrator - - dect = Detector(pixel1=1e-4, pixel2=1e-4) - ai = get_azimuthal_integrator( - detector=dect, detector_distance=1, shape=np.shape(radial_pattern) - ) - integration = azimuthal_integrate1d( - radial_pattern, - ai, - npt_rad=100, - method="numpy", - unit="2th_rad", - correctSolidAngle=True, - ) - - def test_2d_integrate(self, radial_pattern): - from pyxem.utils.pyfai_utils import get_azimuthal_integrator - - dect = Detector(pixel1=1e-4, pixel2=1e-4) - ai = get_azimuthal_integrator( - detector=dect, detector_distance=1, shape=np.shape(radial_pattern) - ) - integration = azimuthal_integrate2d( - radial_pattern, - ai, - npt_rad=100, - npt_azim=100, - method="numpy", - unit="2th_rad", - correctSolidAngle=True, - ) diff --git a/pyxem/tests/utils/test_pyfai_utils.py b/pyxem/tests/utils/test_pyfai_utils.py deleted file mode 100644 index 757ce3dfc..000000000 --- a/pyxem/tests/utils/test_pyfai_utils.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -import pytest - -from pyxem.utils.pyfai_utils import ( - _get_radial_extent, - get_azimuthal_integrator, - _get_displacements, - _get_setup, -) -from pyFAI.detectors import Detector -from pyFAI.azimuthalIntegrator import AzimuthalIntegrator -import numpy as np - - -class Test_PyFai_utils: - def test_get_azimuthal_integrator(self): - dect = Detector(pixel1=1e-4, pixel2=1e-4, max_shape=(20, 20)) - ai = get_azimuthal_integrator( - detector=dect, detector_distance=0.001, shape=(20, 20), center=(10.5, 10.5) - ) - assert isinstance(ai, AzimuthalIntegrator) - ai_mask = get_azimuthal_integrator( - detector=dect, - detector_distance=1, - shape=(20, 20), - center=(10.5, 10.5), - mask=np.zeros((20, 20)), - ) - - assert isinstance(ai_mask, AzimuthalIntegrator) - aff = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - ai_affine = get_azimuthal_integrator( - detector=dect, - detector_distance=1, - shape=(20, 20), - center=(10.5, 10.5), - mask=np.zeros((20, 20)), - affine=aff, - ) - assert isinstance(ai_affine, AzimuthalIntegrator) - - def test_get_displacements(self): - aff = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - dis = _get_displacements((10.5, 10.5), shape=(20, 20), affine=aff) - np.testing.assert_array_equal(dis, np.zeros(shape=(2, 21, 21))) - - def test_get_extent(self): - dect = Detector(pixel1=1e-4, pixel2=1e-4) - ai = AzimuthalIntegrator(detector=dect, dist=0.1) - ai.setFit2D(directDist=1000, centerX=50.5, centerY=50.5) - extent = _get_radial_extent(ai=ai, shape=(100, 100), unit="2th_rad") - max_rad = 50 * np.sqrt(2) - calc_extent = np.arctan(max_rad * 1e-4 / 1) - np.testing.assert_almost_equal( - extent[1], - calc_extent, - ) - - @pytest.mark.parametrize("wavelength", [0, 1e-9]) - @pytest.mark.parametrize( - "unit", ["2th_deg", "2th_rad", "q_nm^-1", "q_A^-1", "k_nm^-1", "k_A^-1"] - ) - def test_get_setup(self, wavelength, unit): - curve = _get_setup( - wavelength=wavelength, - pyxem_unit=unit, - pixel_scale=[1, 1], - radial_range=[0, 1], - ) diff --git a/pyxem/utils/calibration.py b/pyxem/utils/calibration.py index 3ad99315d..0218fdfbd 100644 --- a/pyxem/utils/calibration.py +++ b/pyxem/utils/calibration.py @@ -502,54 +502,6 @@ def center(self, center=None): for ax, off in zip(self.signal.axes_manager.signal_axes, center): ax.offset = -off * ax.scale - def to_pyfai(self): - """ - Convert the calibration to a pyfai AzimuthalIntegrator. - - Returns - ------- - """ - from pyFAI.detectors import Detector - from pyxem.utils.pyfai_utils import _get_setup, get_azimuthal_integrator - - if self.flat_ewald: - pixel_scale = self.scale - if self.wavelength is None: - raise ValueError( - "The wavelength must be set before converting to a pyfai AzimuthalIntegrator" - ) - setup = _get_setup( - wavelength=self.wavelength, - pyxem_unit=self.units[0], - pixel_scale=pixel_scale, - ) - detector, dist, radial_range = setup - else: - pixel_size = self.signal.metadata.get_item( - "Acquisition_instrument.TEM.pixel_size" - ) - dist = self.signal.metadata.get_item( - "Acquisition_instrument.TEM.detector_distance" - ) - if pixel_size is None or dist is None: - raise ValueError( - "The dector must be first initialized with the s.calibration.detector method" - ) - detector = Detector( - pixel1=pixel_size, - pixel2=pixel_size, - ) - - ai = get_azimuthal_integrator( - detector=detector, - detector_distance=dist, - shape=self.shape, - center=self.center, - affine=self.affine, - wavelength=self.wavelength, - ) - return ai - @deprecated( since="0.18.0", diff --git a/pyxem/utils/diffraction.py b/pyxem/utils/diffraction.py index f917c19f3..e7008d375 100644 --- a/pyxem/utils/diffraction.py +++ b/pyxem/utils/diffraction.py @@ -35,7 +35,6 @@ from tqdm import tqdm from packaging.version import Version -from pyxem.utils.pyfai_utils import get_azimuthal_integrator from pyxem.utils.cuda_utils import is_cupy_array from pyxem.utils._deprecated import deprecated import pyxem.utils._pixelated_stem_tools as pst diff --git a/pyxem/utils/pyfai_utils.py b/pyxem/utils/pyfai_utils.py deleted file mode 100644 index 2de0d4705..000000000 --- a/pyxem/utils/pyfai_utils.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016-2024 The pyXem developers -# -# This file is part of pyXem. -# -# pyXem 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. -# -# pyXem 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 pyXem. If not, see . - -# TODO: Delete this entire module - -"""Utils for pyfai transformations.""" - -import numpy as np -from pyFAI.azimuthalIntegrator import AzimuthalIntegrator -from pyFAI.detectors import Detector -from pyFAI.units import register_radial_unit, eq_q - -from pyxem.utils._deprecated import deprecated - - -@deprecated(since="0.18.0", removal="1.0.0") -def get_azimuthal_integrator( - detector, - detector_distance, - shape, - center=None, - affine=None, - mask=None, - wavelength=None, - **kwargs -): - """Basic method for creating a azimuthal integrator. - - This helps to deal with taking some of the pyXEM standards and apply them to pyFAI - - Parameters - ---------- - detector: pyFAI.detectors.Detector - The detector to be integrated - detector_distance: - distance sample - detector plan (orthogonal distance, not along the beam), in meter. - shape: (int, int) - The shape of the signal we are operating on. For the - center: (float, float) - The center of the diffraction pattern - affine: (3x3) - The affine transformation to apply to the data - mask: np.array - A boolean array to be added to the integrator. - wavelength: float - The wavelength of the beam in meter. Needed to accounting for the - Ewald sphere. - kwargs: dict - Any additional arguments to the Azimuthal Integrator class - """ - if center is None: - center = np.divide(shape, 2) # Center is middle of the image - if affine is not None: - # create spline representation with (dx,dy) displacements - dx, dy = _get_displacements(center=center, shape=shape, affine=affine) - detector.max_shape = shape - detector.shape = shape - detector.set_dx(dx) - detector.set_dy(dy) - ai = AzimuthalIntegrator( - detector=detector, dist=detector_distance, wavelength=wavelength, **kwargs - ) - if mask is not None: - ai.set_mask(mask) - if wavelength is not None: - ai.wavelength = wavelength - ai.setFit2D( - directDist=detector_distance * 1000, centerX=center[1], centerY=center[0] - ) - return ai - - -@deprecated(since="0.18.0", removal="1.0.0") -def _get_radial_extent(ai, shape=None, unit=None): - """Takes an Azimuthal Integrator and calculates the domain of the output. - - Note: this method isn't perfect. - - Parameters - ---------- - ai: AzimuthalIntegrator - The integrator to operate on - shape: (int, int) - The shape of the detector. - unit: - The unit to calculate the radial extent with. - """ - postions = ai.array_from_unit(shape=shape, unit=unit, typ="center") - return [np.min(postions), np.max(postions)] - - -@deprecated(since="0.18.0", removal="1.0.0") -def _get_displacements(center, shape, affine): - """Gets the displacements for a set of points based on some affine transformation - about some center point. - - Parameters - ---------- - center: (tuple) - The center to preform the affine transformation around - shape: (tuple) - The shape of the array - affine: 3x3 array - The affine transformation to apply to the image - - Returns - ------- - dx: np.array - The displacement in the x direction of shape = shape - dy: np.array - The displacement in the y direction of shape = shape - """ - # all x and y coordinates on the grid - shape_plus = np.add(shape, 1) - x = range(shape_plus[0], 0, -1) - y = range(shape_plus[1], 0, -1) - xx, yy = np.meshgrid(x, y) - xx = np.subtract(xx, center[1]) - yy = np.subtract(yy, center[0]) - coord = np.array( - [xx.flatten(), yy.flatten(), np.ones((shape_plus[0]) * (shape_plus[1]))] - ) - corrected = np.reshape(np.matmul(coord.T, affine), newshape=(*shape_plus, -1)) - dx = xx - corrected[:, :, 0] - dy = yy - corrected[:, :, 1] - return dx, dy - - -@deprecated(since="0.18.0", removal="1.0.0") -def _get_setup(wavelength, pyxem_unit, pixel_scale, radial_range=None): - """Returns a generic set up for a flat detector with accounting for Ewald sphere effects.""" - units_table = { - "2th_deg": None, - "2th_rad": None, - "q_nm^-1": 1e-9, - "q_A^-1": 1e-10, - "k_nm^-1": 1e-9, - "k_A^-1": 1e-10, - } - wavelength_scale = units_table[pyxem_unit] - detector_distance = 1 - if wavelength_scale is None: - if pyxem_unit == "2th_deg": - pixel_1_size = np.tan((pixel_scale[0] / 180) * np.pi) - pixel_2_size = np.tan((pixel_scale[1] / 180) * np.pi) - if pyxem_unit == "2th_rad": - pixel_1_size = np.tan(pixel_scale[0]) - pixel_2_size = np.tan(pixel_scale[1]) - else: - theta0 = pixel_scale[0] * (wavelength / wavelength_scale) - theta1 = pixel_scale[1] * (wavelength / wavelength_scale) - pixel_1_size = np.tan(theta0) * detector_distance - pixel_2_size = np.tan(theta1) * detector_distance - detector = Detector(pixel1=pixel_1_size, pixel2=pixel_2_size) - if radial_range is not None: - radial_range = [radial_range[0], radial_range[1]] - return ( - detector, - detector_distance, - radial_range, - ) - - -register_radial_unit( - "k_A^-1", - center="qArray", - delta="deltaQ", - scale=0.1 / (2 * np.pi), - label=r"Scattering vector $k$ ($\AA^{-1}$)", - equation=eq_q, - short_name="k", - unit_symbol=r"\AA^{-1}", -) - -register_radial_unit( - "k_nm^-1", - center="qArray", - delta="deltaQ", - scale=1 / (2 * np.pi), - label=r"Scattering vector $k$ ($\nm^{-1}$)", - equation=eq_q, - short_name="k", - unit_symbol=r"\nm^{-1}", -) From 4b2e746b9b93dd7d06c8555a2d4537554fdc6d59 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 6 Aug 2024 12:55:30 -0500 Subject: [PATCH 03/18] Fix documentation example --- CHANGELOG.rst | 7 +++++++ examples/processing/determining_ellipticity.py | 2 +- pyxem/tests/utils/test_expt_utils.py | 3 --- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f5cc5dd2f..fe360397e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,13 @@ The format is based on `Keep a Changelog ` and this project adheres to `Semantic Versioning `_. +Version 1.0.0 +=========================== +Removed +------- +- Removed Dependency on pyfai. Azimuthal integration is all handled internally (#1103) + + 2024-06-10 - version 0.19.1 =========================== Fixed diff --git a/examples/processing/determining_ellipticity.py b/examples/processing/determining_ellipticity.py index 573d294ef..640b4517f 100644 --- a/examples/processing/determining_ellipticity.py +++ b/examples/processing/determining_ellipticity.py @@ -83,7 +83,7 @@ s.unit = "k_nm^-1" s.beam_energy = 200 -s.set_ai(affine=affine) +s.calibration.affine = affine az = s.get_azimuthal_integral2d(npt=100, inplace=False) corr = s.apply_affine_transformation(affine, inplace=False) diff --git a/pyxem/tests/utils/test_expt_utils.py b/pyxem/tests/utils/test_expt_utils.py index d86384eb7..a4d4aaa20 100644 --- a/pyxem/tests/utils/test_expt_utils.py +++ b/pyxem/tests/utils/test_expt_utils.py @@ -32,9 +32,6 @@ investigate_dog_background_removal_interactive, find_beam_center_blur, find_beam_center_interpolate, - azimuthal_integrate1d, - azimuthal_integrate2d, - find_hot_pixels, ) From d5bceeda5089c6914ff1b873360ca69fc1812b5b Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 7 Aug 2024 08:25:04 -0500 Subject: [PATCH 04/18] Remove libraries and dummydata from doc --- doc/reference/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/reference/index.rst b/doc/reference/index.rst index 9f7160a61..6b752cb1f 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -21,7 +21,6 @@ explanations of the functionality. data components - dummy_data generators libraries signals From 7fce790402ba74c45a144b9b7666dbb451c9f6dd Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 3 Sep 2024 12:26:41 -0500 Subject: [PATCH 05/18] Documentation: Update CHANGELOG.rst --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fe360397e..ba03256fa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,9 @@ Version 1.0.0 Removed ------- - Removed Dependency on pyfai. Azimuthal integration is all handled internally (#1103) +- Removed Generator classes that were deprecated (#1102) + - Removed IndexationGenerator, CalibrationGenerator, VirtualImageGenerator, + SubpixelRefinementGenerator, VarianceGenerator, PDFGenerator1D, and VirtualDarkFieldGenerator 2024-06-10 - version 0.19.1 From 22db3b719e51b2e3755eefcec4ad27763137c103 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Mon, 14 Oct 2024 12:20:01 -0500 Subject: [PATCH 06/18] Refactor: VisibleDeprecationWarning import based on numpy version. --- pyxem/common.py | 6 ++++++ pyxem/tests/utils/test_deprecation.py | 11 ++++++----- pyxem/utils/_deprecated.py | 10 +++++----- 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 pyxem/common.py diff --git a/pyxem/common.py b/pyxem/common.py new file mode 100644 index 000000000..b86b41e15 --- /dev/null +++ b/pyxem/common.py @@ -0,0 +1,6 @@ +import numpy + +if numpy.__version__ >= "1.25.0": + from numpy.exceptions import VisibleDeprecationWarning +else: + from numpy import VisibleDeprecationWarning diff --git a/pyxem/tests/utils/test_deprecation.py b/pyxem/tests/utils/test_deprecation.py index af4d21f74..05f2a3ebb 100644 --- a/pyxem/tests/utils/test_deprecation.py +++ b/pyxem/tests/utils/test_deprecation.py @@ -22,6 +22,7 @@ import pytest from pyxem.utils._deprecated import deprecated, deprecated_argument +from pyxem.common import VisibleDeprecationWarning class TestDeprecationWarning: @@ -36,7 +37,7 @@ def foo(n): """Some docstring.""" return n + 1 - with pytest.warns(np.VisibleDeprecationWarning) as record: + with pytest.warns(VisibleDeprecationWarning) as record: assert foo(4) == 5 desired_msg = ( "Function `foo()` is deprecated and will be removed in version 0.8. Use " @@ -59,7 +60,7 @@ def foo2(n): """ return n + 2 - with pytest.warns(np.VisibleDeprecationWarning) as record2: + with pytest.warns(VisibleDeprecationWarning) as record2: assert foo2(4) == 6 desired_msg2 = "Function `foo2()` is deprecated." assert str(record2[0].message) == desired_msg2 @@ -76,7 +77,7 @@ def test_deprecation_no_old_doc(self): def foo(n): return n + 1 - with pytest.warns(np.VisibleDeprecationWarning) as record: + with pytest.warns(VisibleDeprecationWarning) as record: assert foo(4) == 5 desired_msg = ( "Function `foo()` is deprecated and will be removed in version 0.8. Use " @@ -115,7 +116,7 @@ def bar_arg_alt(self, **kwargs): assert my_foo.bar_arg(b=1) == {"b": 1} # Warns - with pytest.warns(np.VisibleDeprecationWarning) as record2: + with pytest.warns(VisibleDeprecationWarning) as record2: assert my_foo.bar_arg(a=2) == {"a": 2} assert str(record2[0].message) == ( r"Argument `a` is deprecated and will be removed in version 1.4. " @@ -124,7 +125,7 @@ def bar_arg_alt(self, **kwargs): ) # Warns with alternative - with pytest.warns(np.VisibleDeprecationWarning) as record3: + with pytest.warns(VisibleDeprecationWarning) as record3: assert my_foo.bar_arg_alt(a=3) == {"b": 3} assert str(record3[0].message) == ( r"Argument `a` is deprecated and will be removed in version 1.4. " diff --git a/pyxem/utils/_deprecated.py b/pyxem/utils/_deprecated.py index e089c234e..9bf2ecd20 100644 --- a/pyxem/utils/_deprecated.py +++ b/pyxem/utils/_deprecated.py @@ -30,7 +30,7 @@ from typing import Callable, Optional, Union import warnings -import numpy as np +from pyxem.common import VisibleDeprecationWarning class deprecated: @@ -87,12 +87,12 @@ def __call__(self, func: Callable): @functools.wraps(func) def wrapped(*args, **kwargs): warnings.simplefilter( - action="always", category=np.VisibleDeprecationWarning, append=True + action="always", category=VisibleDeprecationWarning, append=True ) func_code = func.__code__ warnings.warn_explicit( message=msg, - category=np.VisibleDeprecationWarning, + category=VisibleDeprecationWarning, filename=func_code.co_filename, lineno=func_code.co_firstlineno + 1, ) @@ -139,12 +139,12 @@ def wrapped(*args, **kwargs): kwargs[self.alternative] = kwargs.pop(self.name) msg += f"See the documentation of `{func.__name__}()` for more details." warnings.simplefilter( - action="always", category=np.VisibleDeprecationWarning + action="always", category=VisibleDeprecationWarning ) func_code = func.__code__ warnings.warn_explicit( message=msg, - category=np.VisibleDeprecationWarning, + category=VisibleDeprecationWarning, filename=func_code.co_filename, lineno=func_code.co_firstlineno + 1, ) From a2ced0a8cb4a94a477013eee76adb24253ef91ee Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 23 Oct 2024 11:40:37 -0500 Subject: [PATCH 07/18] Bugfix: Fix failing Orientation mapping --- pyxem/tests/signals/test_beam_shift.py | 6 +++--- pyxem/tests/signals/test_indexation_results.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyxem/tests/signals/test_beam_shift.py b/pyxem/tests/signals/test_beam_shift.py index 6d0911ce6..5fa5c3b17 100644 --- a/pyxem/tests/signals/test_beam_shift.py +++ b/pyxem/tests/signals/test_beam_shift.py @@ -35,7 +35,7 @@ def test_simple(self): s.change_dtype("float32") s_orig = s.deepcopy() s.make_linear_plane() - assert s.data == approx(s_orig.data, abs=1e-7) + assert s.data == approx(s_orig.data, abs=1e-6) class TestGetLinearPlane: @@ -47,7 +47,7 @@ def test_simple(self): s = BeamShift(data) s.change_dtype("float32") s_lp = s.get_linear_plane() - assert s_lp.data == approx(s.data, abs=1e-7) + assert s_lp.data == approx(s.data, abs=1e-6) def test_mask(self): data_x, data_y = np.meshgrid( @@ -63,7 +63,7 @@ def test_mask(self): s.data[45:50, 36:41, 0] = 100000 s.data[45:50, 36:41, 1] = -100000 s_lp = s.get_linear_plane(mask=s_mask) - assert s_lp.data == approx(s_orig.data, abs=1e-7) + assert s_lp.data == approx(s_orig.data, abs=1e-6) def test_lazy_input_error(self): s = LazyBeamShift(da.zeros((50, 40, 2))) diff --git a/pyxem/tests/signals/test_indexation_results.py b/pyxem/tests/signals/test_indexation_results.py index ca0b8c23a..99e20bdc4 100644 --- a/pyxem/tests/signals/test_indexation_results.py +++ b/pyxem/tests/signals/test_indexation_results.py @@ -30,6 +30,7 @@ from orix.sampling import get_sample_reduced_fundamental from orix.quaternion import Rotation, Orientation from orix.crystal_map import CrystalMap +from orix.vector import Vector3d from pyxem.generators import TemplateIndexationGenerator from pyxem.signals import VectorMatchingResults, DiffractionVectors, OrientationMap @@ -227,7 +228,7 @@ def simple_multi_rot_orientation_result(self): reciprocal_radius=2, with_direct_beam=True, ) - polar = polar**0.5 + polar = polar**1 orientations = polar.get_orientation(sims) return orientations, r, s @@ -278,10 +279,14 @@ def test_grain_orientation_result(self, simple_multi_rot_orientation_result): assert isinstance(orientations, OrientationMap) orients = orientations.to_single_phase_orientations() + v1 = (orients * Vector3d.zvector()).in_fundamental_sector(orients.symmetry) + v2 = (rotations * Vector3d.zvector()).in_fundamental_sector(rotations.symmetry) + # Check that the orientations are within 2 degrees of the expected value. # Use 2 degrees since that is the angular resolution of the polar dataset - degrees_between = orients.angle_with(rotations, degrees=True) - assert np.all(np.min(degrees_between, axis=2) <= 2) + degrees_between = v1.angle_with(v2, degrees=True) + min_deg = np.min(degrees_between, axis=2) + np.testing.assert_allclose(min_deg, 0, atol=3) def test_to_crystal_map(self, simple_multi_rot_orientation_result): orientations, rotations, s = simple_multi_rot_orientation_result From 1ea3fa96b31335e404d378426fd3d55775315f58 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 23 Oct 2024 15:06:31 -0500 Subject: [PATCH 08/18] Refactor: Remove code mentioning pyfai --- pyxem/generators/calibration_generator.py | 1 - .../generators/test_calibration_generator.py | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/pyxem/generators/calibration_generator.py b/pyxem/generators/calibration_generator.py index d7fb3169d..4a23268cb 100644 --- a/pyxem/generators/calibration_generator.py +++ b/pyxem/generators/calibration_generator.py @@ -31,7 +31,6 @@ generate_ring_pattern, ) -from pyxem.utils.pyfai_utils import get_azimuthal_integrator, _get_setup from pyxem.utils._deprecated import deprecated from pyxem.signals import ElectronDiffraction2D diff --git a/pyxem/tests/generators/test_calibration_generator.py b/pyxem/tests/generators/test_calibration_generator.py index b43e20a8d..a73038d85 100644 --- a/pyxem/tests/generators/test_calibration_generator.py +++ b/pyxem/tests/generators/test_calibration_generator.py @@ -19,7 +19,6 @@ import pytest import numpy as np -from pyFAI.azimuthalIntegrator import AzimuthalIntegrator from hyperspy.signals import Signal2D from hyperspy.roi import Line2DROI from diffsims.utils.ring_pattern_utils import generate_ring_pattern @@ -277,17 +276,3 @@ def test_get_navigation_calibration_no_data(self, empty_calgen): empty_calgen.get_navigation_calibration( line_roi=line, x1=12.0, x2=172.0, n=1, xspace=500.0 ) - - def test_to_ai(self, calgen): - calgen.get_elliptical_distortion( - mask_radius=10, - direct_beam_amplitude=450, - scale=95, - amplitude=1200, - asymmetry=1.5, - spread=2.8, - rotation=10, - ) - calgen.diffraction_calibration = (1, 1) - ai = calgen.to_ai(wavelength=(2.53 * 10**-12)) - assert isinstance(ai, AzimuthalIntegrator) From f469896ced219c76233453216700675de619b1bd Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 23 Oct 2024 15:50:10 -0500 Subject: [PATCH 09/18] Refactor: Remove extra code mentioning pyfai --- pyxem/generators/calibration_generator.py | 16 -- pyxem/tests/signals/test_diffraction2d.py | 13 -- pyxem/utils/diffraction.py | 194 ---------------------- 3 files changed, 223 deletions(-) diff --git a/pyxem/generators/calibration_generator.py b/pyxem/generators/calibration_generator.py index 4a23268cb..c6d89ce67 100644 --- a/pyxem/generators/calibration_generator.py +++ b/pyxem/generators/calibration_generator.py @@ -93,22 +93,6 @@ def __str__(self): ) return information_string - def to_ai(self, wavelength, **kwargs): - sig_shape = np.shape(self.diffraction_pattern) - unit = "k_A^-1" - setup = _get_setup(wavelength, unit, self.diffraction_calibration) - detector, dist, radial_range = setup - ai = get_azimuthal_integrator( - detector=detector, - detector_distance=dist, - shape=sig_shape, - center=self.center, - affine=self.affine_matrix, - wavelength=wavelength, - **kwargs, - ) - return ai - def get_elliptical_distortion( self, mask_radius, diff --git a/pyxem/tests/signals/test_diffraction2d.py b/pyxem/tests/signals/test_diffraction2d.py index 8d3e30828..c28fb6dc3 100644 --- a/pyxem/tests/signals/test_diffraction2d.py +++ b/pyxem/tests/signals/test_diffraction2d.py @@ -113,19 +113,6 @@ def test_unit(self, ones): def test_unit_set(self, ones): assert ones.unit == "2th_deg" - def test_1d_azimuthal_integral_pyxem(self, ones): - ones.calibration.center = None - ones.calibration.scale = 0.2 - az = ones.get_azimuthal_integral1d( - npt=10, - method="splitpixel_pyxem", - inplace=True, - radial_range=[0.0, 0.8], - ) - assert isinstance(ones, Diffraction1D) - np.testing.assert_almost_equal(np.sum(ones.data), np.pi * 4**2, decimal=0) - assert az is None - def test_1d_azimuthal_integral_pyxem(self, ones): ones.calibration.center = None ones.calibration.scale = 0.2 diff --git a/pyxem/utils/diffraction.py b/pyxem/utils/diffraction.py index e7008d375..47bd8c2e5 100644 --- a/pyxem/utils/diffraction.py +++ b/pyxem/utils/diffraction.py @@ -129,200 +129,6 @@ def _polar2cart(r, theta): return x, y -def azimuthal_integrate1d( - z, azimuthal_integrator, npt_rad, mask=None, sum=False, **kwargs -): - """Calculate the azimuthal integral of z around a determined origin. - - This method is used for signals where the origin is constant, compared to - azimuthal_integrate which is used when the origin in the data changes and - is iterated over. - - Parameters - ---------- - z : numpy.ndarray - Two-dimensional data array containing the signal. - azimuthal_integrator : pyFAI.azimuthal_integrator.AzimuthalIntegrator object - An AzimuthalIntegrator that is already initialised and used to calculate - the integral. - npt_rad: - The number of radial points to integrate - mask: Boolean Array - A boolean array with pixels to ignore - sum: bool - Returns the integrated intensity rather than the mean. - **kwargs : - Keyword arguments to be passed to ai.integrate2d - - Returns - ------- - tth : numpy.ndarray - One-dimensional scattering vector axis of z. - I : numpy.ndarray - One-dimensional azimuthal integral of z. - """ - output = azimuthal_integrator.integrate1d(z, npt=npt_rad, mask=mask, **kwargs) - if sum: - return np.transpose(output._sum_signal) - else: - return output[1] - - -def azimuthal_integrate2d( - z, azimuthal_integrator, npt_rad, npt_azim=None, mask=None, sum=False, **kwargs -): - """Calculate the azimuthal integral of z around a determined origin. - - This method is used for signals where the origin is constant, compared to - azimuthal_integrate which is used when the origin in the data changes and - is iterated over. - - Parameters - ---------- - z : numpy.ndarray - Two-dimensional data array containing the signal. - azimuthal_integrator : pyFAI.azimuthal_integrator.AzimuthalIntegrator object - An AzimuthalIntegrator that is already initialised and used to calculate - the integral. - npt_rad: int - The number of radial points to integrate - npt_azim: int - The number of azimuthal points to integrate - mask: Boolean Array - The mask used to ignore points. - sum: bool - If True the sum is returned, otherwise the average is returned. - **kwargs : - Keyword arguments to be passed to ai.integrate2d - - Returns - ------- - I : numpy.ndarray - Two-dimensional azimuthal integral of z. - """ - output = azimuthal_integrator.integrate2d( - z, npt_rad=npt_rad, npt_azim=npt_azim, mask=mask, **kwargs - ) - if sum: - return np.transpose(output._sum_signal) - else: - return np.transpose(output[0]) - - -def integrate_radially( - z, azimuthal_integrator, npt, npt_rad, mask=None, sum=False, **kwargs -): - """Calculate the radial integrated profile curve as I = f(chi) - - Parameters - ---------- - z : numpy.ndarray - Two-dimensional data array containing the signal. - azimuthal_integrator : pyFAI.azimuthal_integrator.AzimuthalIntegrator object - An AzimuthalIntegrator that is already initialised and used to calculate - the integral. - npt: int - The number of points in the output pattern - npt_rad: int - The number of points in the radial space. Too few points may lead to huge rounding errors. - mask: Boolean Array - A boolean array with pixels to ignore - sum: bool - Returns the integrated intensity rather than the mean. - **kwargs : - Keyword arguments to be passed to ai.integrate2d - - Returns - ------- - tth : numpy.ndarray - One-dimensional scattering vector axis of z. - I : numpy.ndarray - One-dimensional azimuthal integral of z. - """ - output = azimuthal_integrator.integrate_radial( - z, npt=npt, npt_rad=npt_rad, mask=mask, **kwargs - ) - if sum: - return np.transpose(output._sum_signal) - else: - return output[1] - - -def medfilt_1d(z, azimuthal_integrator, npt_rad, npt_azim, mask=None, **kwargs): - """Perform the 2D integration and filter along each row using a median filter - - Parameters - ---------- - z : numpy.ndarray - Two-dimensional data array containing the signal. - azimuthal_integrator : pyFAI.azimuthal_integrator.AzimuthalIntegrator object - An AzimuthalIntegrator that is already initialised and used to calculate - the integral. - npt: int - The number of points in the output pattern - npt_rad: int - The number of points in the radial space. Too few points may lead to huge rounding errors. - mask: Boolean Array - A boolean array with pixels to ignore - sum: bool - Returns the integrated intensity rather than the mean. - **kwargs : - Keyword arguments to be passed to ai.integrate2d - - Returns - ------- - tth : numpy.ndarray - One-dimensional scattering vector axis of z. - I : numpy.ndarray - One-dimensional azimuthal integral of z. - """ - output = azimuthal_integrator.medfilt1d( - z, npt_rad=npt_rad, npt_azim=npt_azim, mask=mask, **kwargs - ) - return output[1] - - -def sigma_clip(z, azimuthal_integrator, npt_rad, npt_azim, mask=None, **kwargs): - """Perform the 2D integration and perform a sigm-clipping iterative - filter along each row. see the doc of scipy.stats.sigmaclip for the options. - - - Parameters - ---------- - z : numpy.ndarray - Two-dimensional data array containing the signal. - azimuthal_integrator : pyFAI.azimuthal_integrator.AzimuthalIntegrator object - An AzimuthalIntegrator that is already initialised and used to calculate - the integral. - npt_rad: int - The number of points in the output pattern - npt_azim: int - The number of points in the radial space. Too few points may lead to huge rounding errors. - mask: Boolean Array - A boolean array with pixels to ignore - sum: bool - Returns the integrated intensity rather than the mean. - **kwargs : - Keyword arguments to be passed to ai.integrate2d - - Returns - ------- - tth : numpy.ndarray - One-dimensional scattering vector axis of z. - I : numpy.ndarray - One-dimensional azimuthal integral of z. - """ - from pyFAI._version import version - - if Version(version) >= Version("2023.2.0"): - output = azimuthal_integrator.sigma_clip_ng(z, npt=npt_rad, mask=mask, **kwargs) - else: - output = azimuthal_integrator.sigma_clip( - z, npt_rad=npt_rad, npt_azim=npt_azim, mask=mask, **kwargs - ) - return output[1] - - def gain_normalise(z, dref, bref): """Apply gain normalization to experimentally acquired electron diffraction pattern. From 5802c98ce784a04420143b98f5ca77c3b9f4c45a Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 3 Sep 2024 13:12:52 -0500 Subject: [PATCH 10/18] Documentation: Add Documentation about plotting a diffraction pattern. --- examples/plotting/README.rst | 3 ++ .../plotting_a_diffraction_pattern.py | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/plotting/README.rst create mode 100644 examples/plotting/plotting_a_diffraction_pattern.py diff --git a/examples/plotting/README.rst b/examples/plotting/README.rst new file mode 100644 index 000000000..a24bf9f2c --- /dev/null +++ b/examples/plotting/README.rst @@ -0,0 +1,3 @@ +Plotting +======== +Below is a gallery of examples on how to plot data in pyxem. \ No newline at end of file diff --git a/examples/plotting/plotting_a_diffraction_pattern.py b/examples/plotting/plotting_a_diffraction_pattern.py new file mode 100644 index 000000000..84fd4d2ef --- /dev/null +++ b/examples/plotting/plotting_a_diffraction_pattern.py @@ -0,0 +1,43 @@ +""" +Plotting a Diffraction Pattern +============================== + +This is sometimes not as straightforward as it seems because you might have a zero +beam that is too bright and regions of the diffraction pattern that are too dark. +""" + +from pyxem.data import fe_multi_phase_grains + +mulit_phase = fe_multi_phase_grains() + +# %% +# We can plot easily using the `plot` method. This will show the diffraction pattern +# but the plot is static and not interactive. Additionally, the zero beam is too bright +# and the high k values are too dark. + +mulit_phase.plot() + +# %% +# Plotting the diffraction pattern with a logarithmic scale can help to see the high k values +# But because most of the values are zero, the contrast is not great and is too stretched. + +mulit_phase.plot(norm="log") + +# %% +# You can also add a small value to the data to avoid the log(0) problem. +# This doesn't change the underlying data, only creates a new signal for plotting! + +(mulit_phase + 1).plot(norm="log") + +# %% +# We can also set vmin and vmax to control the contrast. This can be useful to see the high k values. +# A very useful feature is the ability to plot the diffraction pattern with vmax set to the 99th percentile. +# This sets the maximum value to the 99th percentile of the data. In general this works better than setting +# norm='log' if you have zero values in the diffraction pattern. + +mulit_phase.plot(vmax="99th") + +# %% +# Note: that any of the plots are interactive if you add: +# %matplotlib ipympl or %matplotlib qt at the beginning of a Jupyter notebook cell. +# %matplotlib inline will make the plots static. From 0a96d8110e6225a9cad22ccf912c383a0b600fbc Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 3 Sep 2024 13:13:06 -0500 Subject: [PATCH 11/18] Documentation: Add Documentation about fast plotting tricks --- examples/plotting/fast_plotting_tricks.py | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/plotting/fast_plotting_tricks.py diff --git a/examples/plotting/fast_plotting_tricks.py b/examples/plotting/fast_plotting_tricks.py new file mode 100644 index 000000000..5783f81ab --- /dev/null +++ b/examples/plotting/fast_plotting_tricks.py @@ -0,0 +1,63 @@ +""" +Fast Plotting Tricks +==================== + +Sometimes you want to quickly plot a diffraction pattern but things seem __slow__, +this mostly happens with "large" data that is loaded Lazily. + +There are a couple of different ways that plotting in hyperspy/pyxem can be slow: + +1. The data is too large and the navigator is being recalculated every time you plot. (i.e. calling `s.plot()` + takes a long time to render) +2. Dragging the navigator is slow and laggy. +""" + +from pyxem.data import fe_multi_phase_grains +import numpy as np +import hyperspy.api as hs + +s = fe_multi_phase_grains().as_lazy() + +# %% +# To solve the first problem, you can: +# 1. Precompute the navigator using `s.compute_navigator()` which will compute +# the navigator and store it in the signal. This will make plotting faster. + +s.compute_navigator() +print(s.navigator) + +# %% +# 2. You can also set the navigator directly using `s.navigator = ...` if you have a navigator +# that you want to use. This is useful if a virtual image is created along with the signal when +# the data is acquired. + +dummy_navigator = hs.signals.Signal2D(np.ones((20, 20))) # just a dummy navigator +s.navigator = dummy_navigator +# or +s.plot(navigator=dummy_navigator) + +# %% +# 3. You also don't need to plot the navigator every time you plot the signal. You can set +# `navigator = "slider"` to avoid plotting the navigator altogether and just use the sliders. + +s.plot(navigator="slider") + + +# %% +# To solve the second problem, you can: +# 1. Use the Qt backend by running `%matplotlib qt` in a Jupyter notebook cell. This will make the +# navigator much more responsive using "blitting" which only updates the parts of the plot that +# have changed. Note that the QT backend is not available in Google Colab or when running in a +# Jupyter notebook on a remote server. +# +# 2. You can use the Shift + Click feature to "Jump" to a specific location in the navigator. +# This is useful if you want to quickly move to a specific location in the navigator without +# dragging the navigator and loading all the data in between. +# 3. You can also set the navigator point using the `axes_manager.indices` attribute. + +s.axes_manager.indices = (5, 5) # jump to the center of the navigator +s.plot() + +# %% +# 4. Finally, you can always consider saving the data in a more performant format like `.zspy` +# This will make loading the data faster which will in turn make plotting faster! From 8248368c8425754214bd2bd76f80f5a4950d2d88 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 3 Sep 2024 13:13:27 -0500 Subject: [PATCH 12/18] Documentation: Add missing README.rst for DPC --- examples/dpc/README.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/dpc/README.rst diff --git a/examples/dpc/README.rst b/examples/dpc/README.rst new file mode 100644 index 000000000..5ac84e75a --- /dev/null +++ b/examples/dpc/README.rst @@ -0,0 +1,3 @@ +Differential Phase Contrast (DPC) +================================= +Below is a gallery of examples on how to do Differential Phase Contrast (DPC) in pyxem. \ No newline at end of file From 18effd275c568f90b3e8d2084b5841ccc669e771 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Mon, 16 Sep 2024 12:09:10 -0500 Subject: [PATCH 13/18] Documentation: Add link to simulated Stripes Dataset --- pyxem/data/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyxem/data/__init__.py b/pyxem/data/__init__.py index 6f392494a..3933bf34a 100644 --- a/pyxem/data/__init__.py +++ b/pyxem/data/__init__.py @@ -51,6 +51,7 @@ sped_ag, pdcusi_insitu, ) +from pyxem.data.simulated_dpc import simulated_stripes __all__ = [ "au_grating", @@ -65,6 +66,7 @@ "si_grains", "si_grains_simple", "si_rotations_line", + "simulated_stripes", "fe_multi_phase_grains", "fe_fcc_phase", "fe_bcc_phase", From b01bd697694e0b09097a6445a8632ab6f4ac0f3e Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Mon, 16 Sep 2024 13:03:53 -0500 Subject: [PATCH 14/18] Documentation: log --> symlog --- examples/plotting/plotting_a_diffraction_pattern.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/plotting/plotting_a_diffraction_pattern.py b/examples/plotting/plotting_a_diffraction_pattern.py index 84fd4d2ef..493750c22 100644 --- a/examples/plotting/plotting_a_diffraction_pattern.py +++ b/examples/plotting/plotting_a_diffraction_pattern.py @@ -24,10 +24,11 @@ mulit_phase.plot(norm="log") # %% -# You can also add a small value to the data to avoid the log(0) problem. -# This doesn't change the underlying data, only creates a new signal for plotting! +# You can also use the `symlog` norm to plot the diffraction pattern with a logarithmic scale +# but with a linear scale around zero. This can be useful to see the zero beam and the high k values. +# additionally you can visualize negative and positive values as well. -(mulit_phase + 1).plot(norm="log") +mulit_phase.plot(norm="symlog") # %% # We can also set vmin and vmax to control the contrast. This can be useful to see the high k values. From 286c60e446c38dc64af9a631e6ef4d6aef5c5826 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Mon, 16 Sep 2024 13:06:10 -0500 Subject: [PATCH 15/18] Documentation: Add Changelog --- CHANGELOG.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba03256fa..e11db0d4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,15 +8,15 @@ The format is based on `Keep a Changelog ` and this project adheres to `Semantic Versioning `_. -Version 1.0.0 -=========================== +Unreleased +========== +Added +----- +- Added Examples for general plotting functions focusing on plotting diffraction patterns (#1108) + Removed ------- - Removed Dependency on pyfai. Azimuthal integration is all handled internally (#1103) -- Removed Generator classes that were deprecated (#1102) - - Removed IndexationGenerator, CalibrationGenerator, VirtualImageGenerator, - SubpixelRefinementGenerator, VarianceGenerator, PDFGenerator1D, and VirtualDarkFieldGenerator - 2024-06-10 - version 0.19.1 =========================== From 0605682e59fc02e088297632a0ee3732ed495c28 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Thu, 26 Sep 2024 08:33:50 -0400 Subject: [PATCH 16/18] Documentation: Add inter sphinx linking. --- examples/plotting/fast_plotting_tricks.py | 22 ++++++++++++++++--- .../plotting_a_diffraction_pattern.py | 6 +++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/examples/plotting/fast_plotting_tricks.py b/examples/plotting/fast_plotting_tricks.py index 5783f81ab..12e19fb5f 100644 --- a/examples/plotting/fast_plotting_tricks.py +++ b/examples/plotting/fast_plotting_tricks.py @@ -19,17 +19,24 @@ s = fe_multi_phase_grains().as_lazy() # %% +# Pre Computing a Navaigator +# -------------------------- # To solve the first problem, you can: -# 1. Precompute the navigator using `s.compute_navigator()` which will compute -# the navigator and store it in the signal. This will make plotting faster. +# 1. Precompute the navigator using +# :external+hyperspy:meth:`~.api.signals.BaseSignal.plot`:external+hyperspy:meth:`~.api.signals.BaseSignal.plot` +# `s.compute_navigator()` which will compute the navigator and store it in the signal. This will make plotting faster. s.compute_navigator() print(s.navigator) # %% +# Setting a Navigator +# ------------------- # 2. You can also set the navigator directly using `s.navigator = ...` if you have a navigator # that you want to use. This is useful if a virtual image is created along with the signal when -# the data is acquired. +# the data is acquired. This will also save the navigator in the metadata. This is similar to the +# :external+hyperspy:meth:`~.api.signals.LazySignal.compute_navigator` function of the signal and +# will be saved when the signal is saved. dummy_navigator = hs.signals.Signal2D(np.ones((20, 20))) # just a dummy navigator s.navigator = dummy_navigator @@ -37,6 +44,8 @@ s.plot(navigator=dummy_navigator) # %% +# Using a Slider to Navigate +# -------------------------- # 3. You also don't need to plot the navigator every time you plot the signal. You can set # `navigator = "slider"` to avoid plotting the navigator altogether and just use the sliders. @@ -44,12 +53,16 @@ # %% +# Using the QT Backend and Blitting +# --------------------------------- # To solve the second problem, you can: # 1. Use the Qt backend by running `%matplotlib qt` in a Jupyter notebook cell. This will make the # navigator much more responsive using "blitting" which only updates the parts of the plot that # have changed. Note that the QT backend is not available in Google Colab or when running in a # Jupyter notebook on a remote server. # +# Using Shift + Click to Jump +# --------------------------- # 2. You can use the Shift + Click feature to "Jump" to a specific location in the navigator. # This is useful if you want to quickly move to a specific location in the navigator without # dragging the navigator and loading all the data in between. @@ -59,5 +72,8 @@ s.plot() # %% +# Saving the Data +# --------------- # 4. Finally, you can always consider saving the data in a more performant format like `.zspy` # This will make loading the data faster which will in turn make plotting faster! +s.save("fast_and_compressed.zspy") diff --git a/examples/plotting/plotting_a_diffraction_pattern.py b/examples/plotting/plotting_a_diffraction_pattern.py index 493750c22..232cafc33 100644 --- a/examples/plotting/plotting_a_diffraction_pattern.py +++ b/examples/plotting/plotting_a_diffraction_pattern.py @@ -38,6 +38,12 @@ mulit_phase.plot(vmax="99th") +# %% +# We can also use a gamma correction to control and optimize the contrast. + +mulit_phase.plot(norm="power", gamma=0.4) + + # %% # Note: that any of the plots are interactive if you add: # %matplotlib ipympl or %matplotlib qt at the beginning of a Jupyter notebook cell. From 8cfe008c2c2843a6290d7f35228a5459e5a9ad94 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 23 Oct 2024 16:43:46 -0500 Subject: [PATCH 17/18] Documentation: Clean up Formatting --- examples/plotting/fast_plotting_tricks.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/plotting/fast_plotting_tricks.py b/examples/plotting/fast_plotting_tricks.py index 12e19fb5f..0edd7d97a 100644 --- a/examples/plotting/fast_plotting_tricks.py +++ b/examples/plotting/fast_plotting_tricks.py @@ -2,12 +2,12 @@ Fast Plotting Tricks ==================== -Sometimes you want to quickly plot a diffraction pattern but things seem __slow__, +Sometimes you want to quickly plot a diffraction pattern but things seem slow, this mostly happens with "large" data that is loaded Lazily. There are a couple of different ways that plotting in hyperspy/pyxem can be slow: -1. The data is too large and the navigator is being recalculated every time you plot. (i.e. calling `s.plot()` +1. The data is too large and the navigator is being recalculated every time you plot. (i.e. calling s.plot() takes a long time to render) 2. Dragging the navigator is slow and laggy. """ @@ -22,9 +22,10 @@ # Pre Computing a Navaigator # -------------------------- # To solve the first problem, you can: +# # 1. Precompute the navigator using -# :external+hyperspy:meth:`~.api.signals.BaseSignal.plot`:external+hyperspy:meth:`~.api.signals.BaseSignal.plot` -# `s.compute_navigator()` which will compute the navigator and store it in the signal. This will make plotting faster. +# :external+hyperspy:meth:`~.api.signals.BaseSignal.plot` or :external+hyperspy:meth:`_signals.lazy.LazySignal.compute_navigator` +# which will compute the navigator and store it in the signal. This will make plotting faster. s.compute_navigator() print(s.navigator) @@ -35,7 +36,7 @@ # 2. You can also set the navigator directly using `s.navigator = ...` if you have a navigator # that you want to use. This is useful if a virtual image is created along with the signal when # the data is acquired. This will also save the navigator in the metadata. This is similar to the -# :external+hyperspy:meth:`~.api.signals.LazySignal.compute_navigator` function of the signal and +# :external+hyperspy:meth:`_signals.lazy.LazySignal.compute_navigator` function of the signal and # will be saved when the signal is saved. dummy_navigator = hs.signals.Signal2D(np.ones((20, 20))) # just a dummy navigator @@ -56,6 +57,7 @@ # Using the QT Backend and Blitting # --------------------------------- # To solve the second problem, you can: +# # 1. Use the Qt backend by running `%matplotlib qt` in a Jupyter notebook cell. This will make the # navigator much more responsive using "blitting" which only updates the parts of the plot that # have changed. Note that the QT backend is not available in Google Colab or when running in a @@ -66,6 +68,7 @@ # 2. You can use the Shift + Click feature to "Jump" to a specific location in the navigator. # This is useful if you want to quickly move to a specific location in the navigator without # dragging the navigator and loading all the data in between. +# # 3. You can also set the navigator point using the `axes_manager.indices` attribute. s.axes_manager.indices = (5, 5) # jump to the center of the navigator @@ -77,3 +80,5 @@ # 4. Finally, you can always consider saving the data in a more performant format like `.zspy` # This will make loading the data faster which will in turn make plotting faster! s.save("fast_and_compressed.zspy") + +hs.load("fast_and_compressed.zspy").plot() # reload the data and plot it From 1d3bda93c35591bc41f45c65a6044a210e21b3e7 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 23 Oct 2024 16:59:08 -0500 Subject: [PATCH 18/18] Documentation: Fix broken switcher and add announcement --- doc/conf.py | 2 ++ examples/plotting/fast_plotting_tricks.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 30a07f0b8..73122f366 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -107,6 +107,8 @@ "navigation_with_keys": True, "show_toc_level": 2, "use_edit_page_button": True, + "announcement": "Check out the new " + "Examples Gallery! ", "switcher": { "json_url": "https://pyxem.readthedocs.io/en/latest/_static/switcher.json", "version_match": version_match, diff --git a/examples/plotting/fast_plotting_tricks.py b/examples/plotting/fast_plotting_tricks.py index 0edd7d97a..4147957e2 100644 --- a/examples/plotting/fast_plotting_tricks.py +++ b/examples/plotting/fast_plotting_tricks.py @@ -23,8 +23,8 @@ # -------------------------- # To solve the first problem, you can: # -# 1. Precompute the navigator using -# :external+hyperspy:meth:`~.api.signals.BaseSignal.plot` or :external+hyperspy:meth:`_signals.lazy.LazySignal.compute_navigator` +# 1. Precompute the navigator using the :meth:`hyperspy.api.signals.plot` method or +# the :meth:`hyperspy._signals.LazySignal.compute_navigator` method # which will compute the navigator and store it in the signal. This will make plotting faster. s.compute_navigator() @@ -36,7 +36,7 @@ # 2. You can also set the navigator directly using `s.navigator = ...` if you have a navigator # that you want to use. This is useful if a virtual image is created along with the signal when # the data is acquired. This will also save the navigator in the metadata. This is similar to the -# :external+hyperspy:meth:`_signals.lazy.LazySignal.compute_navigator` function of the signal and +# :meth:`hyperspy._signals.LazySignal.compute_navigator`method of the signal and # will be saved when the signal is saved. dummy_navigator = hs.signals.Signal2D(np.ones((20, 20))) # just a dummy navigator