From 3ca2d279868a59569d212891f573071ea2292446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:33:53 +0200 Subject: [PATCH 01/12] Remove support for Python 3.8 and 3.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .github/workflows/tests.yml | 2 +- CHANGELOG.rst | 2 ++ README.rst | 2 +- doc/user/installation.rst | 2 +- pyproject.toml | 4 +--- tests/test_indexing/test_ebsd_refinement.py | 3 +-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4af3f005..0f578591 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,7 @@ jobs: python-version: ['3.11', '3.12'] include: - os: ubuntu-latest - python-version: 3.8 + python-version: 3.10 DEPENDENCIES: dask==2021.8.1 diffsims==0.5.2 hyperspy==1.7.3 matplotlib==3.5 numba==0.57 numpy==1.23.0 orix==0.12.1 pooch==1.3.0 pyebsdindex==0.2.0 scikit-image==0.21.0 LABEL: -oldest - os: ubuntu-latest diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 257cbddd..35d3029f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,8 @@ Added Changed ------- +- Minimum Python version is now 3.10. + (`#? `_) Removed ------- diff --git a/README.rst b/README.rst index ce3023c6..09be475a 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ library. .. |tests_status| image:: https://github.com/pyxem/kikuchipy/actions/workflows/tests.yml/badge.svg :target: https://github.com/pyxem/kikuchipy/actions/workflows/tests.yml -.. |python| image:: https://img.shields.io/badge/python-3.8+-blue.svg +.. |python| image:: https://img.shields.io/badge/python-3.10+-blue.svg :target: https://www.python.org/downloads/ .. |Coveralls| image:: https://coveralls.io/repos/github/pyxem/kikuchipy/badge.svg?branch=develop diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 07aeec64..be19d15c 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -4,7 +4,7 @@ Installation kikuchipy can be installed with `pip `__, `conda `__, the -:ref:`hyperspy:hyperspy-bundle`, or from source, and supports Python >= 3.8. +:ref:`hyperspy:hyperspy-bundle`, or from source, and supports Python >= 3.10. All alternatives are available on Windows, macOS and Linux. .. _install-with-pip: diff --git a/pyproject.toml b/pyproject.toml index cb300638..01f8e041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,9 @@ description = "Processing, simulating, and indexing of electron backscatter diff license = {file = "LICENSE"} readme = {file = "README.rst", content-type = "text/x-rst"} dynamic = ["version"] -requires-python = ">= 3.8" +requires-python = ">= 3.10" classifiers = [ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/tests/test_indexing/test_ebsd_refinement.py b/tests/test_indexing/test_ebsd_refinement.py index 5bcdda78..68a6f1b7 100644 --- a/tests/test_indexing/test_ebsd_refinement.py +++ b/tests/test_indexing/test_ebsd_refinement.py @@ -832,8 +832,7 @@ def test_refine_projection_center_local( assert det_ref.pc.shape == nav_shape + (3,) assert num_evals_ref.shape == nav_shape - # TODO: Change to == 10 once Python 3.7 is unsopprted. - assert num_evals_ref.max() < 50 + assert num_evals_ref.max() == 10 @pytest.mark.parametrize( ( From dd1a32ad616d5c6d951f6524c1f4fd009e6b6bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:39:55 +0200 Subject: [PATCH 02/12] Update type hints in private modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/_rotation/__init__.py | 2 +- src/kikuchipy/_util/_deprecated.py | 24 +++++++++++++++--------- src/kikuchipy/_util/_transfer_axes.py | 11 ++++++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/kikuchipy/_rotation/__init__.py b/src/kikuchipy/_rotation/__init__.py index fb158aa0..b527aad5 100644 --- a/src/kikuchipy/_rotation/__init__.py +++ b/src/kikuchipy/_rotation/__init__.py @@ -70,7 +70,7 @@ def _rotation_from_rodrigues(rx: float, ry: float, rz: float) -> np.ndarray: d = s * rz / norm rot = np.array([a, b, c, d], dtype="float64") - if rot[0] < 0: # pragma: no cover + if rot[0] < 0: rot = -rot return rot diff --git a/src/kikuchipy/_util/_deprecated.py b/src/kikuchipy/_util/_deprecated.py index ffd94df0..0676ee68 100644 --- a/src/kikuchipy/_util/_deprecated.py +++ b/src/kikuchipy/_util/_deprecated.py @@ -28,7 +28,7 @@ import functools import inspect -from typing import Callable, Optional, Union +from typing import Callable import warnings import numpy as np @@ -47,11 +47,11 @@ class deprecated: def __init__( self, - since: Union[str, int, float], - alternative: Optional[str] = None, + since: str | int | float, + alternative: str | None = None, alternative_is_function: bool = True, - removal: Union[str, int, float, None] = None, - ): + removal: str | int | float | None = None, + ) -> None: """Visible deprecation warning. Parameters @@ -71,7 +71,7 @@ def __init__( self.alternative_is_function = alternative_is_function self.removal = removal - def __call__(self, func: Callable): + def __call__(self, func: Callable) -> None: # Wrap function to raise warning when called, and add warning to # docstring if self.alternative is not None: @@ -91,7 +91,7 @@ def __call__(self, func: Callable): msg = f"Attribute `{func.__name__}` is deprecated{rm_msg}.{alt_msg}" @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args, **kwargs) -> Callable: warnings.simplefilter( action="always", category=np.VisibleDeprecationWarning, append=True ) @@ -126,13 +126,19 @@ class deprecated_argument: `_. """ - def __init__(self, name, since, removal, alternative=None): + def __init__( + self, + name: str, + since: str | float, + removal: str | float, + alternative: str | None = None, + ) -> None: self.name = name self.since = since self.removal = removal self.alternative = alternative - def __call__(self, func): + def __call__(self, func: Callable) -> Callable: @functools.wraps(func) def wrapped(*args, **kwargs): if self.name in kwargs.keys(): diff --git a/src/kikuchipy/_util/_transfer_axes.py b/src/kikuchipy/_util/_transfer_axes.py index 93855f55..cc3a0cc4 100644 --- a/src/kikuchipy/_util/_transfer_axes.py +++ b/src/kikuchipy/_util/_transfer_axes.py @@ -15,11 +15,16 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +from hyperspy.axes import AxesManager -def _transfer_navigation_axes_to_signal_axes(new_axes, old_axes): + +def _transfer_navigation_axes_to_signal_axes( + new_axes: AxesManager, old_axes: AxesManager +) -> AxesManager: """Transfer navigation axis calibrations from an old signal to the - signal axes of a new signal produced from it by a generator. Used - from methods that generate a signal with a single value at each + signal axes of a new signal produced from it by a generator. + + Used from methods that generate a signal with a single value at each navigation position. Adapted from the pyxem package. From 198f080804c0ec86ad04b12ad1c6baf4e855cb09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:40:15 +0200 Subject: [PATCH 03/12] Update type hints in data module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/data/_data.py | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/kikuchipy/data/_data.py b/src/kikuchipy/data/_data.py index f1bc80b2..4facb048 100644 --- a/src/kikuchipy/data/_data.py +++ b/src/kikuchipy/data/_data.py @@ -17,7 +17,6 @@ import os from pathlib import Path -from typing import Optional, Union import hyperspy.api as hs import pooch @@ -75,7 +74,7 @@ def nickel_ebsd_small(**kwargs) -> EBSD: def nickel_ebsd_large( - allow_download: bool = False, show_progressbar: Optional[bool] = None, **kwargs + allow_download: bool = False, show_progressbar: bool | None = None, **kwargs ) -> EBSD: """4125 EBSD patterns in a (55, 75) navigation shape of (60, 60) pixels from nickel, acquired on a NORDIF UF-1100 detector @@ -123,7 +122,7 @@ def nickel_ebsd_large( def ni_gain( number: int = 1, allow_download: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, **kwargs, ) -> EBSD: """EBSD dataset of (149, 200) patterns of (60, 60) pixels from @@ -189,7 +188,7 @@ def ni_gain( def ni_gain_calibration( number: int = 1, allow_download: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, **kwargs, ) -> EBSD: """A few EBSD calibration patterns of (480, 480) pixels from @@ -251,13 +250,13 @@ def ni_gain_calibration( ) file_path = NiGainCalibration.fetch_file_path(allow_download, show_progressbar) - return load(file_path, **kwargs) # pragma: no cover + return load(file_path, **kwargs) def si_ebsd_moving_screen( distance: int = 0, allow_download: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, **kwargs, ) -> EBSD: """One EBSD pattern of (480, 480) pixels from a single crystal @@ -322,7 +321,7 @@ def si_ebsd_moving_screen( def si_wafer( - allow_download: bool = False, show_progressbar: Optional[bool] = None, **kwargs + allow_download: bool = False, show_progressbar: bool | None = None, **kwargs ) -> EBSD: """EBSD dataset of (50, 50) patterns of (480, 480) pixels from a single crystal silicon wafer, acquired on a NORDIF UF-420 detector @@ -375,7 +374,7 @@ def si_wafer( """ SiWafer = Dataset("si_wafer/Pattern.dat", collection_name="ebsd_si_wafer.zip") file_path = SiWafer.fetch_file_path(allow_download, show_progressbar) - return load(file_path, **kwargs) # pragma: no cover + return load(file_path, **kwargs) # ---------------------------- Simulations --------------------------- # @@ -446,7 +445,7 @@ def nickel_ebsd_master_pattern_small(**kwargs) -> EBSDMasterPattern: def ebsd_master_pattern( phase: str, allow_download: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, **kwargs, ) -> EBSDMasterPattern: r"""EBSD master pattern of an available phase of (1001, 1001) pixel @@ -522,7 +521,7 @@ def ebsd_master_pattern( dset = Dataset("ebsd_master_pattern/" + datasets[phase.lower()]) file_path = dset.fetch_file_path(allow_download, show_progressbar) - return load(file_path, **kwargs) # pragma: no cover + return load(file_path, **kwargs) class Dataset: @@ -530,12 +529,12 @@ class Dataset: file_package_path: Path file_cache_path: Path expected_md5_hash: str = "" - collection_name: Optional[str] = None + collection_name: str | None = None def __init__( self, - file_relpath: Union[Path, str], - collection_name: Optional[str] = None, + file_relpath: Path | str, + collection_name: str | None = None, ) -> None: if isinstance(file_relpath, str): file_relpath = Path(file_relpath) @@ -581,28 +580,28 @@ def file_path_str(self) -> str: return self.file_path.as_posix() @property - def md5_hash(self) -> Union[str, None]: + def md5_hash(self) -> str | None: if self.file_path.exists(): return pooch.file_hash(self.file_path_str, alg="md5") else: - return None + return @property def has_correct_hash(self) -> bool: return self.md5_hash == self.expected_md5_hash.split(":")[1] @property - def url(self) -> Union[str, None]: + def url(self) -> str | None: if self.file_relpath_str in registry_urls: return registry_urls[self.file_relpath_str] elif self.is_in_collection and "data/" + self.collection_name in registry_urls: return registry_urls["data/" + self.collection_name] else: - return None + return def fetch_file_path_from_collection( self, downloader: pooch.HTTPDownloader - ) -> file_path: # pragma: no cover + ) -> file_path: file_paths = marshall.fetch( "data/" + self.collection_name, downloader=downloader, @@ -627,7 +626,7 @@ def fetch_file_path_from_collection( return self.file_relpath_str def fetch_file_path( - self, allow_download: bool = False, show_progressbar: Optional[bool] = None + self, allow_download: bool = False, show_progressbar: bool | None = None ) -> str: if show_progressbar is None: show_progressbar = hs.preferences.General.show_progressbar @@ -637,7 +636,7 @@ def fetch_file_path( if self.has_correct_hash: # Bypass pooch since the file is not in the cache return self.file_path_str - else: # pragma: no cover + else: raise AttributeError( f"File {self.file_path_str} has incorrect MD5 hash {self.md5_hash}" f", while {self.expected_md5_hash.split(':')[1]} was expected. This" @@ -647,19 +646,19 @@ def fetch_file_path( elif self.is_in_cache: if self.has_correct_hash: file_path = self.file_relpath_str - elif allow_download: # pragma: no cover + elif allow_download: if self.is_in_collection: file_path = self.fetch_file_path_from_collection(downloader) else: file_path = self.file_relpath_str - else: # pragma: no cover + else: raise ValueError( f"File {self.file_path_str} must be re-downloaded from the " f"repository file {self.url} to your local cache {marshall.path}. " "Pass `allow_download=True` to allow this re-download." ) else: - if allow_download: # pragma: no cover + if allow_download: if self.is_in_collection: file_path = self.fetch_file_path_from_collection(downloader) else: From 0a08a8a445510909ed94838bfa7fe85b1e0753c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:40:30 +0200 Subject: [PATCH 04/12] Update type hints in detectors module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/detectors/calibration.py | 37 ++++--- src/kikuchipy/detectors/ebsd_detector.py | 135 ++++++++++++----------- 2 files changed, 92 insertions(+), 80 deletions(-) diff --git a/src/kikuchipy/detectors/calibration.py b/src/kikuchipy/detectors/calibration.py index 38820d1b..3cbade87 100644 --- a/src/kikuchipy/detectors/calibration.py +++ b/src/kikuchipy/detectors/calibration.py @@ -18,7 +18,6 @@ """Calibration of the EBSD projection/pattern center.""" from itertools import combinations -from typing import List, Optional, Tuple, Union import matplotlib.pyplot as plt import numpy as np @@ -77,13 +76,13 @@ def __init__( self, pattern_in: np.ndarray, pattern_out: np.ndarray, - points_in: Union[np.ndarray, List[Tuple[float]]], - points_out: Union[np.ndarray, List[Tuple[float]]], + points_in: np.ndarray | list[tuple[float]], + points_out: np.ndarray | list[tuple[float]], delta_z: float = 1.0, - px_size: Optional[float] = None, + px_size: float | None = None, binning: int = 1, convention: str = "tsl", - ): + ) -> None: """Create an instance storing the PC estimates, the average PC, and other parameters relevant for the estimation. """ @@ -98,7 +97,7 @@ def __init__( self.make_lines() @property - def shape(self) -> Tuple[int, int]: + def shape(self) -> tuple[int, int]: """Return the detector shape, (nrows, ncols).""" return self.patterns[0].shape @@ -253,13 +252,13 @@ def make_lines(self): def plot( self, - pattern_kwargs: dict = dict(cmap="gray"), - line_kwargs: dict = dict(linewidth=2, zorder=1), - scatter_kwargs: dict = dict(zorder=2), - pc_kwargs: dict = dict(marker="*", s=300, facecolor="gold", edgecolor="k"), + pattern_kwargs: dict | None = None, + line_kwargs: dict | None = None, + scatter_kwargs: dict | None = None, + pc_kwargs: dict | None = None, return_figure: bool = False, **kwargs: dict, - ) -> Union[None, Tuple[plt.Figure, List[plt.Axes]]]: + ) -> None | tuple[plt.Figure, list[plt.Axes]]: """A convenience method of three images, the first two with the patterns with points and lines annotated, and the third with the calibration results. @@ -289,6 +288,15 @@ def plot( fig Figure, returned if ``return_figure=True``. """ + if pattern_kwargs is None: + pattern_kwargs = {"cmap": "gray"} + if line_kwargs is None: + line_kwargs = {"linewidth": 2, "zorder": 1} + if scatter_kwargs is None: + scatter_kwargs = {"zorder": 2} + if pc_kwargs is None: + pc_kwargs = {"marker": "*", "s": 300, "facecolor": "gold", "edgecolor": "k"} + pat1, pat2 = self.patterns points1, points2 = self.points px = self.pxy[0] @@ -335,7 +343,7 @@ def plot( if return_figure: return fig - def __repr__(self): + def __repr__(self) -> str: name = self.__class__.__name__ points = np.array_str(self.points, precision=0) pcx, pcy, pcz = self.pc @@ -346,9 +354,8 @@ def __repr__(self): def _get_intersection_from_lines( - line1: Union[List[int], np.ndarray], - line2: Union[List[int], np.ndarray], -) -> Tuple[float, float]: + line1: list[int] | np.ndarray, line2: list[int] | np.ndarray +) -> tuple[float, float]: """line: [x1, y1, x2, y2]""" x1, y1, x2, y2 = line1 x3, y3, x4, y4 = line2 diff --git a/src/kikuchipy/detectors/ebsd_detector.py b/src/kikuchipy/detectors/ebsd_detector.py index 9be61fde..f208e388 100644 --- a/src/kikuchipy/detectors/ebsd_detector.py +++ b/src/kikuchipy/detectors/ebsd_detector.py @@ -22,8 +22,9 @@ import logging from pathlib import Path import re -from typing import TYPE_CHECKING, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Self +from diffsims.crystallography import ReciprocalLatticeVector from matplotlib.figure import Figure from matplotlib.markers import MarkerStyle import matplotlib.patches as mpatches @@ -42,18 +43,22 @@ if TYPE_CHECKING: # pragma: no cover from diffsims.crystallography import ReciprocalLatticeVector - from pyebsdindex import EBSDIndexer + + from kikuchipy.constants import installed + + if installed["pyebsdindex"]: + from pyebsdindex.ebsd_index import EBSDIndexer _logger = logging.getLogger(__name__) -CONVENTION_ALIAS = { +CONVENTION_ALIAS: dict[str, list[str]] = { "bruker": ["bruker"], "tsl": ["edax", "tsl", "amatek"], "oxford": ["oxford", "aztec"], "emsoft": ["emsoft", "emsoft4", "emsoft5"], } -CONVENTION_ALIAS_ALL = list(np.concatenate(list(CONVENTION_ALIAS.values()))) +CONVENTION_ALIAS_ALL: list = list(np.concatenate(list(CONVENTION_ALIAS.values()))) class EBSDDetector: @@ -194,14 +199,14 @@ class EBSDDetector: def __init__( self, - shape: Tuple[int, int] = (1, 1), - px_size: float = 1, + shape: tuple[int, int] = (1, 1), + px_size: float = 1.0, binning: int = 1, - tilt: float = 0, - azimuthal: float = 0, - sample_tilt: float = 70, - pc: Union[np.ndarray, list, tuple] = (0.5, 0.5, 0.5), - convention: Optional[str] = None, + tilt: float = 0.0, + azimuthal: float = 0.0, + sample_tilt: float = 70.0, + pc: np.ndarray | list | tuple = (0.5, 0.5, 0.5), + convention: str | None = None, ) -> None: """Create an EBSD detector with a shape, pixel size, binning factor, sample and detector tilt about the detector X axis, @@ -210,7 +215,7 @@ def __init__( """ self.shape = shape self.px_size = float(px_size) - self.binning = float(binning) + self.binning: float = float(binning) self.tilt = float(tilt) self.azimuthal = float(azimuthal) self.sample_tilt = float(sample_tilt) @@ -238,7 +243,7 @@ def __repr__(self) -> str: ) @property - def specimen_scintillator_distance(self) -> float: + def specimen_scintillator_distance(self) -> np.ndarray: """Return the specimen to scintillator distance, known in EMsoft as :math:`L`. """ @@ -275,7 +280,7 @@ def aspect_ratio(self) -> float: return self.ncols / self.nrows @property - def unbinned_shape(self) -> Tuple[int, int]: + def unbinned_shape(self) -> tuple[int, int]: """Return the unbinned detector shape in pixels.""" return tuple(np.array(self.shape) * self.binning) @@ -298,7 +303,7 @@ def pc(self) -> np.ndarray: return self._pc @pc.setter - def pc(self, value: Union[np.ndarray, List, Tuple]): + def pc(self, value: np.ndarray | list | tuple) -> None: """Set all projection center coordinates, assuming Bruker's convention. """ @@ -325,7 +330,7 @@ def pcx(self) -> np.ndarray: return self.pc[..., 0] @pcx.setter - def pcx(self, value: Union[np.ndarray, list, tuple, float]): + def pcx(self, value: np.ndarray | list | tuple | float): """Set the x projection center coordinates.""" self._pc[..., 0] = np.atleast_2d(value).astype(float) @@ -343,7 +348,7 @@ def pcy(self) -> np.ndarray: return self.pc[..., 1] @pcy.setter - def pcy(self, value: Union[np.ndarray, list, tuple, float]): + def pcy(self, value: np.ndarray | list | tuple | float): """Set y projection center coordinates.""" self._pc[..., 1] = np.atleast_2d(value).astype(float) @@ -361,7 +366,7 @@ def pcz(self) -> np.ndarray: return self.pc[..., 2] @pcz.setter - def pcz(self, value: Union[np.ndarray, list, tuple, float]): + def pcz(self, value: np.ndarray | list | tuple | float): """Set z projection center coordinates.""" self._pc[..., 2] = np.atleast_2d(value).astype(float) @@ -417,12 +422,12 @@ def bounds(self) -> np.ndarray: return np.array([0, self.ncols - 1, 0, self.nrows - 1]) @property - def x_min(self) -> Union[np.ndarray, float]: + def x_min(self) -> np.ndarray | float: """Return the left bound of detector in gnomonic coordinates.""" return -self.aspect_ratio * (self.pcx / self.pcz) @property - def x_max(self) -> Union[np.ndarray, float]: + def x_max(self) -> np.ndarray | float: """Return the right bound of detector in gnomonic coordinates.""" return self.aspect_ratio * (1 - self.pcx) / self.pcz @@ -432,12 +437,12 @@ def x_range(self) -> np.ndarray: return np.dstack((self.x_min, self.x_max)).reshape(self.navigation_shape + (2,)) @property - def y_min(self) -> Union[np.ndarray, float]: + def y_min(self) -> np.ndarray | float: """Return the top bound of detector in gnomonic coordinates.""" return -(1 - self.pcy) / self.pcz @property - def y_max(self) -> Union[np.ndarray, float]: + def y_max(self) -> np.ndarray | float: """Return the bottom bound of detector in gnomonic coordinates.""" return self.pcy / self.pcz @@ -490,7 +495,7 @@ def r_max(self) -> np.ndarray: return np.atleast_2d(np.sqrt(np.max(corners, axis=-1))) @classmethod - def load(cls, fname: Union[Path, str]) -> EBSDDetector: + def load(cls, fname: Path | str) -> Self: """Return an EBSD detector loaded from a text file saved with :meth:`save`. @@ -517,7 +522,7 @@ def load(cls, fname: Union[Path, str]) -> EBSDDetector: "navigation_shape", ] - detector_kw = dict(zip(keys, [None] * len(keys))) + detector_kw: dict = dict(zip(keys, [None] * len(keys))) with open(fname, mode="r") as f: header = [] for line in f.readlines(): @@ -552,7 +557,7 @@ def load(cls, fname: Union[Path, str]) -> EBSDDetector: return cls(pc=pc, **detector_kw) - def crop(self, extent: Union[Tuple[int, int, int, int], List[int]]) -> EBSDDetector: + def crop(self, extent: tuple[int, int, int, int] | list[int]) -> Self: """Return a new detector with its :attr:`shape` cropped and :attr:`pc` values updated accordingly. @@ -607,17 +612,17 @@ def crop(self, extent: Union[Tuple[int, int, int, int], List[int]]) -> EBSDDetec pcy_new = (self.pcy * ny - top) / ny_new pcz_new = self.pcz * ny / ny_new - return EBSDDetector( + return self.__class__( shape=(ny_new, nx_new), pc=np.dstack((pcx_new, pcy_new, pcz_new)), tilt=self.tilt, sample_tilt=self.sample_tilt, - binning=self.binning, + binning=int(self.binning), px_size=self.px_size, azimuthal=self.azimuthal, ) - def deepcopy(self) -> EBSDDetector: + def deepcopy(self) -> Self: """Return a deep copy using :func:`copy.deepcopy`. Returns @@ -634,13 +639,13 @@ def estimate_xtilt( degrees: bool = False, return_figure: bool = False, return_outliers: bool = False, - figure_kwargs: Optional[dict] = None, - ) -> Union[ - float, - Tuple[float, np.ndarray], - Tuple[float, plt.Figure], - Tuple[float, np.ndarray, plt.Figure], - ]: + figure_kwargs: dict | None = None, + ) -> ( + float + | tuple[float, np.ndarray] + | tuple[float, Figure] + | tuple[float, np.ndarray, Figure] + ): r"""Estimate the tilt about the detector :math:`X_d` axis. This tilt is assumed to bring the sample plane normal into @@ -772,8 +777,8 @@ def estimate_xtilt( def estimate_xtilt_ztilt( self, degrees: bool = False, - is_outlier: Optional[Union[list, tuple, np.ndarray]] = None, - ) -> Union[float, Tuple[float, float]]: + is_outlier: list | tuple | np.ndarray | None = None, + ) -> float | tuple[float, float]: r"""Estimate the tilts about the detector :math:`X_d` and :math:`Z_d` axes. @@ -840,14 +845,14 @@ def estimate_xtilt_ztilt( def extrapolate_pc( self, - pc_indices: Union[tuple, list, np.ndarray], + pc_indices: tuple | list | np.ndarray, navigation_shape: tuple, step_sizes: tuple, - shape: Optional[tuple] = None, - px_size: float = None, - binning: int = None, - is_outlier: Optional[Union[tuple, list, np.ndarray]] = None, - ): + shape: tuple | None = None, + px_size: float | None = None, + binning: int | None = None, + is_outlier: tuple | list | np.ndarray | None = None, + ) -> Self: r"""Return a new detector with projection centers (PCs) in a 2D map extrapolated from an average PC. @@ -947,14 +952,14 @@ def extrapolate_pc( def fit_pc( self, - pc_indices: Union[list, tuple, np.ndarray], - map_indices: Union[list, tuple, np.ndarray], + pc_indices: list | tuple | np.ndarray, + map_indices: list | tuple | np.ndarray, transformation: str = "projective", - is_outlier: Optional[np.ndarray] = None, + is_outlier: np.ndarray | None = None, plot: bool = True, return_figure: bool = False, - figure_kwargs: Optional[dict] = None, - ) -> Union[EBSDDetector, Tuple[EBSDDetector, plt.Figure]]: + figure_kwargs: dict | None = None, + ) -> Self | tuple[EBSDDetector, Figure]: """Return a new detector with interpolated projection centers (PCs) for all points in a map by fitting a plane to :attr:`pc` :cite:`winkelmann2020refined`. @@ -1121,9 +1126,9 @@ def fit_pc( def get_indexer( self, phase_list: PhaseList, - reflectors: Optional[ - List[Union["ReciprocalLatticeVector", np.ndarray, list, tuple, None]] - ] = None, + reflectors: ( + list[ReciprocalLatticeVector | np.ndarray | list | tuple | None] | None + ) = None, **kwargs, ) -> "EBSDIndexer": r"""Return a PyEBSDIndex EBSD indexer. @@ -1291,15 +1296,15 @@ def plot( self, coordinates: str = "detector", show_pc: bool = True, - pc_kwargs: Optional[dict] = None, - pattern: Optional[np.ndarray] = None, - pattern_kwargs: Optional[dict] = None, + pc_kwargs: dict | None = None, + pattern: np.ndarray | None = None, + pattern_kwargs: dict | None = None, draw_gnomonic_circles: bool = False, - gnomonic_angles: Union[None, list, np.ndarray] = None, - gnomonic_circles_kwargs: Optional[dict] = None, - zoom: float = 1, + gnomonic_angles: np.ndarray | list | None = None, + gnomonic_circles_kwargs: dict | None = None, + zoom: float = 1.0, return_figure: bool = False, - ) -> Union[None, Figure]: + ) -> None | Figure: """Plot the detector screen viewed from the detector towards the sample. @@ -1460,9 +1465,9 @@ def plot_pc( return_figure: bool = False, orientation: str = "horizontal", annotate: bool = False, - figure_kwargs: Optional[dict] = None, + figure_kwargs: dict | None = None, **kwargs, - ) -> Union[None, plt.Figure]: + ) -> None | Figure: """Plot all projection centers (PCs). Parameters @@ -1618,7 +1623,7 @@ def plot_pc( if return_figure: return fig - def save(self, filename: str, convention: str = "Bruker", **kwargs) -> None: + def save(self, filename: str | Path, convention: str = "Bruker", **kwargs) -> None: """Save detector in a text file with projection centers (PCs) in the given convention. @@ -1781,7 +1786,7 @@ def _pc_bruker2oxford(self) -> np.ndarray: def _fit_hyperplane( pc_centered: np.ndarray, -) -> Tuple[float, float, Rotation, Rotation, np.ndarray]: +) -> tuple[float, float, Rotation, Rotation, np.ndarray]: # Hyperplane fit pc_trim_mean = scs.trim_mean(pc_centered, proportiontocut=0.1) pc_trim_centered = pc_centered - pc_trim_mean[np.newaxis, :] @@ -1822,7 +1827,7 @@ def _fit_pc_projective( pc_centered_flat: np.ndarray, pc_indices_flat: np.ndarray, map_indices_flat: np.ndarray, -) -> Tuple[np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: *_, rot_xtilt, rot_ztilt, pc_trim_mean = _fit_hyperplane(pc_centered_flat) v_pc_centered = Vector3d(pc_centered_flat) @@ -1854,7 +1859,7 @@ def _fit_pc_projective( def _fit_pc_affine( pc_flat: np.ndarray, pc_indices_flat: np.ndarray, map_indices_flat: np.ndarray -) -> Tuple[np.array, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: # Solve the least squares problem X * A = Y # Source: https://stackoverflow.com/a/20555267/3228100 matrix, res, *_ = np.linalg.lstsq(pc_indices_flat, pc_flat, rcond=None) @@ -1873,7 +1878,7 @@ def _plot_pc_fit( fit_slope: float, figure_kwargs: dict, return_figure: bool = False, -) -> Union[None, plt.Figure]: +) -> None | Figure: pcx, pcy, pcz = pc.T pcx_fit_2d, pcy_fit_2d, pcz_fit_2d = pc_fit.T pcx_fit = pcx_fit_2d.ravel() From 85094512fc2328e5bf87aabb163d5cef811dfd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:40:46 +0200 Subject: [PATCH 05/12] Update type hints in draw module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/draw/_navigators.py | 4 +--- .../draw/_plot_pattern_positions_in_map.py | 18 +++++++++--------- src/kikuchipy/draw/colors.py | 2 +- src/kikuchipy/draw/markers.py | 14 ++++++-------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/kikuchipy/draw/_navigators.py b/src/kikuchipy/draw/_navigators.py index bd722228..c7127b40 100644 --- a/src/kikuchipy/draw/_navigators.py +++ b/src/kikuchipy/draw/_navigators.py @@ -19,15 +19,13 @@ navigators with :meth:`~hyperspy.signals.Signal2D.plot`. """ -from typing import Union - import hyperspy.api as hs import numpy as np from skimage.exposure import rescale_intensity def get_rgb_navigator( - image: np.ndarray, dtype: Union[str, np.dtype, type] = "uint16" + image: np.ndarray, dtype: str | np.dtype | type = "uint16" ) -> hs.signals.Signal2D: """Create an RGB navigator signal which is suitable to pass to :meth:`~hyperspy._signals.signal2d.Signal2D.plot` as the diff --git a/src/kikuchipy/draw/_plot_pattern_positions_in_map.py b/src/kikuchipy/draw/_plot_pattern_positions_in_map.py index 08483c30..3e6dc0dc 100644 --- a/src/kikuchipy/draw/_plot_pattern_positions_in_map.py +++ b/src/kikuchipy/draw/_plot_pattern_positions_in_map.py @@ -22,8 +22,8 @@ interest within a larger area. """ -from typing import Optional - +import matplotlib.axes as maxes +import matplotlib.figure as mfigure import matplotlib.patches as mpatches import matplotlib.pyplot as plt import numpy as np @@ -33,13 +33,13 @@ def plot_pattern_positions_in_map( rc: np.ndarray, roi_shape: tuple, roi_origin: tuple = (0, 0), - area_shape: Optional[tuple] = None, - roi_image: Optional[np.ndarray] = None, - area_image: Optional[np.ndarray] = None, - axis: Optional[plt.Axes] = None, + area_shape: tuple | None = None, + roi_image: np.ndarray | None = None, + area_image: np.ndarray | None = None, + axis: maxes.Axes | None = None, return_figure: bool = False, - color: Optional[str] = "k", -) -> Optional[plt.Figure]: + color: str | None = "k", +) -> mfigure.Figure | mfigure.SubFigure | None: """Plot pattern positions in a 2D map within a region of interest (ROI), the ROI potentially within a larger area. @@ -85,7 +85,7 @@ def plot_pattern_positions_in_map( roi_rect_kw = dict(fc="none", lw=2, clip_on=False, zorder=5) roi_ny, roi_nx = roi_shape - if isinstance(axis, plt.Axes): + if isinstance(axis, maxes.Axes): new_axis = False ax = axis fig = ax.figure diff --git a/src/kikuchipy/draw/colors.py b/src/kikuchipy/draw/colors.py index fa20f802..18ce066e 100644 --- a/src/kikuchipy/draw/colors.py +++ b/src/kikuchipy/draw/colors.py @@ -30,7 +30,7 @@ DARK_GREEN = (0, 0.5, 0) DARKER_GREEN = (0, 0.5, 0.5) DARK_BLUE = (0, 0, 0.5) -TSL_COLORS = [ +TSL_COLORS: list[tuple] = [ RED, YELLOW, GREEN, diff --git a/src/kikuchipy/draw/markers.py b/src/kikuchipy/draw/markers.py index 7f9dc3d9..83051e7d 100644 --- a/src/kikuchipy/draw/markers.py +++ b/src/kikuchipy/draw/markers.py @@ -17,13 +17,11 @@ """Creation of lists of HyperSpy markers.""" -from typing import Union - from hyperspy.utils.markers import line_segment, point, text import numpy as np -def get_line_segment_list(lines: Union[list, np.ndarray], **kwargs) -> list: +def get_line_segment_list(lines: np.ndarray | list, **kwargs) -> list: """Return a list of line segment markers. Parameters @@ -35,7 +33,7 @@ def get_line_segment_list(lines: Union[list, np.ndarray], **kwargs) -> list: Returns ------- - marker_list : list + marker_list List of :class:`hyperspy.drawing._markers.line_segment.LineSegment`. """ @@ -54,7 +52,7 @@ def get_line_segment_list(lines: Union[list, np.ndarray], **kwargs) -> list: return marker_list -def get_point_list(points: Union[list, np.ndarray], **kwargs) -> list: +def get_point_list(points: np.ndarray | list, **kwargs) -> list: """Return a list of point markers. Parameters @@ -66,7 +64,7 @@ def get_point_list(points: Union[list, np.ndarray], **kwargs) -> list: Returns ------- - marker_list : list + marker_list List of :class:`hyperspy.drawing._markers.point.Point`. """ points = np.asarray(points) @@ -83,7 +81,7 @@ def get_point_list(points: Union[list, np.ndarray], **kwargs) -> list: def get_text_list( - texts: Union[list, np.ndarray], coordinates: Union[np.ndarray, list], **kwargs + texts: np.ndarray | list, coordinates: np.ndarray | list, **kwargs ) -> list: """Return a list of text markers. @@ -98,7 +96,7 @@ def get_text_list( Returns ------- - marker_list : list + marker_list List of :class:`hyperspy.drawing._markers.text.Text`. """ coordinates = np.asarray(coordinates) From 9b1f4235fe97ccf62ecbdab9913191adbdb4ca11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:40:57 +0200 Subject: [PATCH 06/12] Update type hints in filters module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/filters/fft_barnes.py | 39 ++++++++------------ src/kikuchipy/filters/window.py | 56 ++++++++++++++--------------- 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/kikuchipy/filters/fft_barnes.py b/src/kikuchipy/filters/fft_barnes.py index 8bfff8ae..891a2a59 100644 --- a/src/kikuchipy/filters/fft_barnes.py +++ b/src/kikuchipy/filters/fft_barnes.py @@ -19,8 +19,6 @@ via FFT written by Connelly Barnes (public domain, 2007). """ -from typing import List, Tuple, Union - import numba as nb import numpy as np from scipy.fft import irfft2, next_fast_len, rfft2 @@ -29,9 +27,9 @@ def _fft_filter_setup( - image_shape: Tuple[int, int], - window: Union[np.ndarray, Window], -) -> Tuple[Tuple[int, int], np.ndarray, Tuple[int, int], Tuple[int, int]]: + image_shape: tuple[int, int], + window: np.ndarray | Window, +) -> tuple[tuple[int, int], np.ndarray, tuple[int, int], tuple[int, int]]: window_shape = window.shape # Optimal FFT shape @@ -53,10 +51,7 @@ def _fft_filter_setup( return fft_shape, transfer_function, offset_before, offset_after -def fft_filter( - image: np.ndarray, - window: Union[np.ndarray, Window], -) -> np.ndarray: +def fft_filter(image: np.ndarray, window: np.ndarray | Window) -> np.ndarray: """Filter a 2D image in the frequency domain with a window defined in the spatial domain. @@ -100,8 +95,8 @@ def fft_filter( def _pad_window( - window: Union[np.ndarray, Window], - fft_shape: Union[Tuple[int, ...], List[int], np.ndarray], + window: np.ndarray | Window, + fft_shape: tuple[int, ...] | list[int] | np.ndarray, ) -> np.ndarray: wy, wx = window.shape window_pad = np.zeros(fft_shape, dtype=np.float32) @@ -109,17 +104,13 @@ def _pad_window( return window_pad -def _offset_before_fft( - window_shape: Union[Tuple[int, int], np.ndarray] -) -> Tuple[int, int]: +def _offset_before_fft(window_shape: tuple[int, int] | np.ndarray) -> tuple[int, int]: wy, wx = window_shape offset = (wy - ((wy - 1) // 2) - 1, wx - ((wx - 1) // 2) - 1) return offset -def _offset_after_ifft( - window_shape: Union[Tuple[int, int], np.ndarray] -) -> Tuple[int, int]: +def _offset_after_ifft(window_shape: tuple[int, int] | np.ndarray) -> tuple[int, int]: wy, wx = window_shape offset = ((wy - 1) // 2, (wx - 1) // 2) return offset @@ -128,9 +119,9 @@ def _offset_after_ifft( @nb.njit(cache=True, fastmath=True, nogil=True) def _pad_image( image: np.ndarray, - fft_shape: Tuple[int, ...], - window_shape: Tuple[int, int], - offset_before_fft: Tuple[int, int], + fft_shape: tuple[int, ...], + window_shape: tuple[int, int], + offset_before_fft: tuple[int, int], ) -> np.ndarray: iy, ix = image.shape wy, wx = window_shape @@ -164,10 +155,10 @@ def _pad_image( def _fft_filter( image: np.ndarray, transfer_function: np.ndarray, - fft_shape: Tuple[int, int], - window_shape: Tuple[int, int], - offset_before_fft: Tuple[int, int], - offset_after_ifft: Tuple[int, int], + fft_shape: tuple[int, int], + window_shape: tuple[int, int], + offset_before_fft: tuple[int, int], + offset_after_ifft: tuple[int, int], ) -> np.ndarray: # Create new image array to pad with the image in the top left # corner diff --git a/src/kikuchipy/filters/window.py b/src/kikuchipy/filters/window.py index 8455e050..2b81785c 100644 --- a/src/kikuchipy/filters/window.py +++ b/src/kikuchipy/filters/window.py @@ -16,10 +16,10 @@ # along with kikuchipy. If not, see . from copy import copy -from typing import List, Optional, Sequence, Tuple, Union +from typing import Self, Sequence from dask.array import Array -from matplotlib.figure import Figure +import matplotlib.figure as mfigure from matplotlib.pyplot import subplots from numba import njit import numpy as np @@ -109,15 +109,15 @@ class Window(np.ndarray): [0.7788 0.8825 0.7788]] """ - _name: str = None + _name: str | None = None _circular: bool = False def __new__( cls, - window: Union[None, str, np.ndarray, Array] = None, - shape: Optional[Sequence[int]] = None, + window: str | np.ndarray | Array | None = None, + shape: Sequence[int] | None = None, **kwargs, - ): + ) -> Self: if window is None: window = "circular" @@ -191,13 +191,13 @@ def __new__( return obj - def __array_finalize__(self, obj): + def __array_finalize__(self, obj: Self | None) -> None: if obj is None: return self._name = getattr(obj, "_name", None) self._circular = getattr(obj, "_circular", False) - def __array_wrap__(self, obj): + def __array_wrap__(self, obj: Self) -> Self | np.ndarray: if obj.shape == (): return obj[()] else: @@ -250,7 +250,7 @@ def origin(self) -> tuple: """Return the window origin.""" return tuple(i // 2 for i in self.shape) - def make_circular(self): + def make_circular(self) -> None: """Make the window circular. The data of window elements who's radial distance to the @@ -272,7 +272,7 @@ def make_circular(self): if self.name in ["rectangular", "boxcar"]: self._name = "circular" - def shape_compatible(self, shape: Tuple[int]) -> bool: + def shape_compatible(self, shape: tuple[int, ...]) -> bool: """Return whether the window shape is compatible with another shape. @@ -302,41 +302,41 @@ def plot( self, grid: bool = True, show_values: bool = True, - textcolors: Optional[List[str]] = None, + textcolors: list[str] | None = None, cmap: str = "viridis", cmap_label: str = "Value", colorbar: bool = True, return_figure: bool = False, - ) -> Figure: + ) -> mfigure.Figure | None: """Plot window values with indices relative to the origin. Parameters ---------- grid Whether to separate each value with a white spacing in a - grid. Default is ``True``. + grid. Default is True. show_values Whether to show values as text in centre of element. Default - is ``True``. + is True. textcolors A list of two color specifications. The first is used for values below a threshold, the second for those above. If - not given (default), this is set to ``["white", "black"]``. + not given (default), this is set to ["white", "black"]. cmap A colormap to color data with, available in :class:`matplotlib.colors.ListedColormap`. Default is - ``"viridis"``. + "viridis". cmap_label - Colormap label. Default is ``"Value"``. + Colormap label. Default is "Value". colorbar - Whether to show the colorbar. Default is ``True``. + Whether to show the colorbar. Default is True. return_figure - Whether to return the figure. Default is ``False``. + Whether to return the figure. Default is False. Returns ------- fig - Figure returned if ``return_figure=True``. + Figure returned if *return_figure* is True. Examples -------- @@ -399,8 +399,8 @@ def plot( def distance_to_origin( - shape: Union[Tuple[int], Tuple[int, int]], - origin: Union[None, Tuple[int], Tuple[int, int]] = None, + shape: tuple[int] | tuple[int, int], + origin: tuple[int] | tuple[int, int] | None = None, ) -> np.ndarray: """Return the distance to the window origin in pixels. @@ -468,9 +468,9 @@ def modified_hann(Nx: int) -> np.ndarray: def lowpass_fft_filter( - shape: Tuple[int, int], - cutoff: Union[int, float], - cutoff_width: Union[None, int, float] = None, + shape: tuple[int, int], + cutoff: int | float, + cutoff_width: int | float | None = None, ) -> np.ndarray: r"""Return a frequency domain low-pass filter transfer function in 2D. @@ -535,9 +535,9 @@ def lowpass_fft_filter( def highpass_fft_filter( - shape: Tuple[int, int], - cutoff: Union[int, float], - cutoff_width: Union[None, int, float] = None, + shape: tuple[int, int], + cutoff: int | float, + cutoff_width: int | float | None = None, ) -> np.ndarray: r"""Return a frequency domain high-pass filter transfer function in 2D. From bbf942c4bf825021e943deffc767f3b968996c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:41:10 +0200 Subject: [PATCH 07/12] Update type hints in imaging module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/imaging/vbse.py | 38 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/kikuchipy/imaging/vbse.py b/src/kikuchipy/imaging/vbse.py index 6112fd9a..bb9f6f72 100644 --- a/src/kikuchipy/imaging/vbse.py +++ b/src/kikuchipy/imaging/vbse.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from typing import List, Optional, Tuple, Union - from dask.array import Array from hyperspy._signals.signal2d import Signal2D from hyperspy.drawing._markers.horizontal_line import HorizontalLine @@ -48,11 +46,11 @@ class VirtualBSEImager: kikuchipy.signals.EBSD.get_virtual_bse_intensity """ - def __init__(self, signal: Union[EBSD, LazyEBSD]): + def __init__(self, signal: EBSD | LazyEBSD) -> None: self.signal = signal self._grid_shape = (5, 5) - def __repr__(self): + def __repr__(self) -> str: return self.__class__.__name__ + " for " + repr(self.signal) @property @@ -83,19 +81,19 @@ def grid_shape(self) -> tuple: return self._grid_shape @grid_shape.setter - def grid_shape(self, shape: Union[Tuple[int, int], List[int]]): + def grid_shape(self, shape: tuple[int, int] | list[int]) -> None: """Set the generator grid shape.""" self._grid_shape = tuple(shape) def get_rgb_image( self, - r: Union[BaseInteractiveROI, Tuple, List[BaseInteractiveROI], List[Tuple]], - g: Union[BaseInteractiveROI, Tuple, List[BaseInteractiveROI], List[Tuple]], - b: Union[BaseInteractiveROI, Tuple, List[BaseInteractiveROI], List[Tuple]], - percentiles: Optional[Tuple] = None, + r: BaseInteractiveROI | tuple | list[BaseInteractiveROI] | list[tuple], + g: BaseInteractiveROI | tuple | list[BaseInteractiveROI] | list[tuple], + b: BaseInteractiveROI | tuple | list[BaseInteractiveROI] | list[tuple], + percentiles: tuple | None = None, normalize: bool = True, - alpha: Union[None, np.ndarray, VirtualBSEImage] = None, - dtype_out: Union[str, np.dtype, type] = "uint8", + alpha: np.ndarray | VirtualBSEImage | None = None, + dtype_out: str | np.dtype | type = "uint8", add_bright: int = 0, contrast: float = 1.0, ) -> VirtualBSEImage: @@ -192,7 +190,7 @@ def get_rgb_image( return vbse_rgb_image def get_images_from_grid( - self, dtype_out: Union[str, np.dtype, type] = "float32" + self, dtype_out: str | np.dtype | type = "float32" ) -> VirtualBSEImage: """Return an in-memory signal with a stack of virtual backscatter electron (BSE) images by integrating the intensities @@ -237,7 +235,7 @@ def get_images_from_grid( return vbse_images - def roi_from_grid(self, index: Union[Tuple, List[Tuple]]) -> RectangularROI: + def roi_from_grid(self, index: tuple | list[tuple]) -> RectangularROI: """Return a rectangular region of interest (ROI) on the EBSD detector from one or multiple grid tile indices as row(s) and column(s). @@ -270,8 +268,8 @@ def roi_from_grid(self, index: Union[Tuple, List[Tuple]]) -> RectangularROI: def plot_grid( self, - pattern_idx: Optional[Tuple[int, ...]] = None, - rgb_channels: Union[None, List[Tuple], List[List[Tuple]]] = None, + pattern_idx: tuple[int, ...] | None = None, + rgb_channels: list[tuple] | list[list[tuple]] | None = None, visible_indices: bool = True, **kwargs, ) -> EBSD: @@ -357,7 +355,7 @@ def _normalize_image( image: np.ndarray, add_bright: int = 0, contrast: float = 1.0, - dtype_out: Union[str, np.dtype, type] = "uint8", + dtype_out: str | np.dtype | type = "uint8", ) -> np.ndarray: """Normalize an image's intensities to a mean of 0 and a standard deviation of 1, with the possibility to also scale by a contrast @@ -396,11 +394,11 @@ def _normalize_image( def _get_rgb_image( - channels: List[np.ndarray], - percentiles: Optional[Tuple] = None, + channels: list[np.ndarray], + percentiles: tuple | None = None, normalize: bool = True, - alpha: Optional[np.ndarray] = None, - dtype_out: Union[str, np.dtype, type] = "uint8", + alpha: np.ndarray | None = None, + dtype_out: str | np.dtype | type = "uint8", add_bright: int = 0, contrast: float = 1.0, ) -> np.ndarray: From 9e9c51804992604dafd2bdcbe1ca13760c5b11dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:41:23 +0200 Subject: [PATCH 08/12] Update type hints in indexing module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../indexing/_dictionary_indexing.py | 13 +- src/kikuchipy/indexing/_hough_indexing.py | 45 +++-- src/kikuchipy/indexing/_merge_crystal_maps.py | 9 +- .../indexing/_orientation_similarity_map.py | 14 +- .../indexing/_refinement/_refinement.py | 183 +++++++++--------- .../indexing/_refinement/_solvers.py | 84 ++++---- .../_normalized_cross_correlation.py | 28 ++- .../_normalized_dot_product.py | 28 ++- .../similarity_metrics/_similarity_metric.py | 35 ++-- 9 files changed, 221 insertions(+), 218 deletions(-) diff --git a/src/kikuchipy/indexing/_dictionary_indexing.py b/src/kikuchipy/indexing/_dictionary_indexing.py index b7bc2390..81a772bd 100644 --- a/src/kikuchipy/indexing/_dictionary_indexing.py +++ b/src/kikuchipy/indexing/_dictionary_indexing.py @@ -20,7 +20,6 @@ """ from time import sleep, time -from typing import Optional, Tuple, Union import dask.array as da from dask.diagnostics import ProgressBar @@ -33,9 +32,9 @@ def _dictionary_indexing( - experimental: Union[np.ndarray, da.Array], + experimental: np.ndarray | da.Array, experimental_nav_shape: tuple, - dictionary: Union[np.ndarray, da.Array], + dictionary: np.ndarray | da.Array, step_sizes: tuple, dictionary_xmap: CrystalMap, metric: SimilarityMetric, @@ -169,11 +168,11 @@ def _dictionary_indexing( def _match_chunk( - experimental: Union[np.ndarray, da.Array], - simulated: Union[np.ndarray, da.Array], + experimental: np.ndarray | da.Array, + simulated: np.ndarray | da.Array, keep_n: int, metric: SimilarityMetric, -) -> Tuple[da.Array, da.Array]: +) -> tuple[da.Array, da.Array]: """Match all experimental patterns to part of or the entire dictionary of simulated patterns. @@ -207,7 +206,7 @@ def _dictionary_indexing_info_message( n_experimental_all: int, dictionary_size: int, phase_name: str, - n_experimental: Optional[int] = None, + n_experimental: int | None = None, ) -> str: """Return a message with useful dictionary indexing information. diff --git a/src/kikuchipy/indexing/_hough_indexing.py b/src/kikuchipy/indexing/_hough_indexing.py index 6dec1612..a8555492 100644 --- a/src/kikuchipy/indexing/_hough_indexing.py +++ b/src/kikuchipy/indexing/_hough_indexing.py @@ -22,7 +22,7 @@ """ from time import time -from typing import List, Optional, Tuple, Union +from typing import TYPE_CHECKING import dask.array as da from diffsims.crystallography import ReciprocalLatticeVector @@ -32,13 +32,20 @@ from kikuchipy.constants import installed +if TYPE_CHECKING: + from kikuchipy.constants import installed + + if installed["pyebsdindex"]: + from pyebsdindex.ebsd_index import EBSDIndexer + from pyebsdindex.tripletvote import BandIndexer + def xmap_from_hough_indexing_data( data: np.ndarray, phase_list: PhaseList, data_index: int = -1, - navigation_shape: Optional[tuple] = None, - step_sizes: Optional[tuple] = None, + navigation_shape: tuple | None = None, + step_sizes: tuple | None = None, scan_unit: str = "px", ) -> CrystalMap: """Convert Hough indexing result array from :mod:`pyebsdindex` to a @@ -117,9 +124,9 @@ def _get_indexer_from_detector( pc: np.ndarray, sample_tilt: float, tilt: float, - reflectors: Optional[ - List[Union[ReciprocalLatticeVector, np.ndarray, list, tuple, None]] - ] = None, + reflectors: ( + list[ReciprocalLatticeVector | np.ndarray | list | tuple | None] | None + ) = None, **kwargs, ) -> "EBSDIndexer": r"""Return a PyEBSDIndex EBSD indexer. @@ -182,14 +189,14 @@ def _get_indexer_from_detector( def _hough_indexing( - patterns: Union[np.ndarray, da.Array], + patterns: np.ndarray | da.Array, phase_list: PhaseList, nav_shape: tuple, step_sizes: tuple, - indexer, + indexer: "EBSDIndexer", chunksize: int, verbose: int, -) -> Tuple[CrystalMap, np.ndarray, np.ndarray]: +) -> tuple[CrystalMap, np.ndarray, np.ndarray]: """Perform Hough indexing with PyEBSDIndex. Requires PyEBSDIndex to be installed. @@ -205,7 +212,7 @@ def _hough_indexing( Navigation shape. step_sizes Navigation step sizes. - indexer : pyebsdindex.ebsd_index.EBSDIndexer + indexer Indexer instance. verbose Whether to print indexing information. 0 - no output, 1 - @@ -250,10 +257,10 @@ def _hough_indexing( def _get_pyebsdindex_phaselist( phase_list: PhaseList, - reflectors: Optional[ - List[Union[ReciprocalLatticeVector, np.ndarray, list, tuple, None]] - ] = None, -) -> List["BandIndexer"]: + reflectors: ( + list[ReciprocalLatticeVector | np.ndarray | list | tuple | None] | None + ) = None, +) -> list["BandIndexer"]: r"""Return a phase list compatible with PyEBSDIndex from an orix phase list. @@ -336,7 +343,7 @@ def _get_pyebsdindex_phaselist( def _indexer_is_compatible_with_kikuchipy( indexer, sig_shape: tuple, - nav_size: Optional[int] = None, + nav_size: int | None = None, check_pc: bool = True, raise_if_not: bool = False, ) -> bool: @@ -405,7 +412,7 @@ def _indexer_is_compatible_with_kikuchipy( def _phase_lists_are_compatible( phase_list: PhaseList, - indexer, + indexer: "EBSDIndexer", raise_if_not: bool = False, ) -> bool: """Check whether phase lists made with orix and PyEBSDIndex are @@ -421,7 +428,7 @@ def _phase_lists_are_compatible( ---------- phase_list Phase list made with orix. - indexer : pyebsdindex.ebsd_index.EBSDIndexer + indexer EBSD indexer with a phase list from PyEBSDIndex. raise_if_not Whether to raise a ``ValueError`` if the phase lists are @@ -510,8 +517,8 @@ def _get_info_message(nav_size: int, chunksize: int, indexer: "EBSDIndexer") -> def _optimize_pc( - pc0: List[float], - patterns: Union[np.ndarray, da.Array], + pc0: list[float], + patterns: np.ndarray | da.Array, indexer: "EBSDIndexer", batch: bool, method: str, diff --git a/src/kikuchipy/indexing/_merge_crystal_maps.py b/src/kikuchipy/indexing/_merge_crystal_maps.py index 3d427197..ce0f03af 100644 --- a/src/kikuchipy/indexing/_merge_crystal_maps.py +++ b/src/kikuchipy/indexing/_merge_crystal_maps.py @@ -16,7 +16,6 @@ # along with kikuchipy. If not, see . from math import copysign -from typing import List, Optional, Union import warnings import numpy as np @@ -27,12 +26,12 @@ def merge_crystal_maps( - crystal_maps: List[CrystalMap], + crystal_maps: list[CrystalMap], mean_n_best: int = 1, - greater_is_better: Optional[int] = None, + greater_is_better: int | None = None, scores_prop: str = "scores", - simulation_indices_prop: Optional[str] = None, - navigation_masks: Optional[List[Union[None, np.ndarray]]] = None, + simulation_indices_prop: str | None = None, + navigation_masks: list[np.ndarray | None] | None = None, ) -> CrystalMap: """Return a multi phase :class:`~orix.crystal_map.CrystalMap` by merging maps of 1D or 2D navigation shape based on scores. diff --git a/src/kikuchipy/indexing/_orientation_similarity_map.py b/src/kikuchipy/indexing/_orientation_similarity_map.py index a5899deb..ba3d8cd4 100644 --- a/src/kikuchipy/indexing/_orientation_similarity_map.py +++ b/src/kikuchipy/indexing/_orientation_similarity_map.py @@ -22,8 +22,6 @@ # TODO: Consider moving to orix. -from typing import Optional - import numpy as np from orix.crystal_map import CrystalMap from scipy.ndimage import generic_filter @@ -31,11 +29,11 @@ def orientation_similarity_map( xmap: CrystalMap, - n_best: Optional[int] = None, + n_best: int | None = None, simulation_indices_prop: str = "simulation_indices", normalize: bool = False, - from_n_best: Optional[int] = None, - footprint: Optional[np.ndarray] = None, + from_n_best: int | None = None, + footprint: np.ndarray | None = None, center_index: int = 2, ) -> np.ndarray: r"""Compute an orientation similarity map (OSM) where the ranked @@ -146,9 +144,9 @@ def _orientation_similarity_per_pixel( for mi in match_indices[neighbours] ] - os = np.nanmean(number_of_equal_matches_to_its_neighbours) + os_i = np.nanmean(number_of_equal_matches_to_its_neighbours) if normalize: - os /= n + os_i /= n - return os + return os_i diff --git a/src/kikuchipy/indexing/_refinement/_refinement.py b/src/kikuchipy/indexing/_refinement/_refinement.py index 2352ba92..d88c0334 100644 --- a/src/kikuchipy/indexing/_refinement/_refinement.py +++ b/src/kikuchipy/indexing/_refinement/_refinement.py @@ -22,7 +22,7 @@ import sys from time import time -from typing import Callable, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable import dask.array as da from dask.diagnostics import ProgressBar @@ -44,12 +44,20 @@ from kikuchipy.signals.util._crystal_map import _get_indexed_points_in_data_in_xmap from kikuchipy.signals.util._master_pattern import _get_direction_cosines_from_detector +if TYPE_CHECKING: # pragma: no cover + from kikuchipy.constants import installed + from kikuchipy.detectors.ebsd_detector import EBSDDetector + from kikuchipy.signals.ebsd_master_pattern import EBSDMasterPattern + + if installed["nlopt"]: + import nlopt + def compute_refine_orientation_results( results: da.Array, xmap: CrystalMap, master_pattern: "EBSDMasterPattern", - navigation_mask: Optional[np.ndarray] = None, + navigation_mask: np.ndarray | None = None, pseudo_symmetry_checked: bool = False, ) -> CrystalMap: """Compute the results from @@ -124,8 +132,8 @@ def compute_refine_projection_center_results( results: da.Array, detector: "EBSDDetector", xmap: CrystalMap, - navigation_mask: Optional[np.ndarray] = None, -) -> Tuple[np.ndarray, "EBSDDetector", np.ndarray]: + navigation_mask: np.ndarray | None = None, +) -> tuple[np.ndarray, "EBSDDetector", np.ndarray]: """Compute the results from :meth:`~kikuchipy.signals.EBSD.refine_projection_center` and return the score array, :class:`~kikuchipy.detectors.EBSDDetector` and @@ -191,9 +199,9 @@ def compute_refine_orientation_projection_center_results( detector: "EBSDDetector", xmap: CrystalMap, master_pattern: "EBSDMasterPattern", - navigation_mask: Optional[np.ndarray] = None, + navigation_mask: np.ndarray | None = None, pseudo_symmetry_checked: bool = False, -) -> Tuple[CrystalMap, "EBSDDetector"]: +) -> tuple[CrystalMap, "EBSDDetector"]: """Compute the results from :meth:`~kikuchipy.signals.EBSD.refine_orientation_projection_center` and return the :class:`~orix.crystal_map.CrystalMap` and @@ -331,20 +339,20 @@ def _refine_orientation( xmap: CrystalMap, detector, master_pattern, - energy: Union[int, float], - patterns: Union[np.ndarray, da.Array], + energy: int | float, + patterns: np.ndarray | da.Array, points_to_refine: np.ndarray, signal_mask: np.ndarray, - trust_region: Union[tuple, list, np.ndarray, None], + trust_region: np.ndarray | list | tuple | None, rtol: float, - pseudo_symmetry_ops: Optional[Rotation] = None, - method: Optional[str] = None, - method_kwargs: Optional[dict] = None, - initial_step: Optional[float] = None, - maxeval: Optional[int] = None, + pseudo_symmetry_ops: Rotation | None = None, + method: str | None = None, + method_kwargs: dict | None = None, + initial_step: float | None = None, + maxeval: int | None = None, compute: bool = True, - navigation_mask: Optional[np.ndarray] = None, -): + navigation_mask: np.ndarray | None = None, +) -> CrystalMap: ref = _RefinementSetup( mode="ori", xmap=xmap, @@ -435,17 +443,17 @@ def _refine_orientation_chunk_scipy( rotations: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray, - pcx: Optional[np.ndarray] = None, - pcy: Optional[np.ndarray] = None, - pcz: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, - solver_kwargs: Optional[dict] = None, - direction_cosines: Optional[np.ndarray] = None, - nrows: Optional[int] = None, - ncols: Optional[int] = None, - tilt: Optional[float] = None, - azimuthal: Optional[float] = None, - sample_tilt: Optional[float] = None, + pcx: np.ndarray | None = None, + pcy: np.ndarray | None = None, + pcz: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, + solver_kwargs: dict | None = None, + direction_cosines: np.ndarray | None = None, + nrows: int | None = None, + ncols: int | None = None, + tilt: float | None = None, + azimuthal: float | None = None, + sample_tilt: float | None = None, n_pseudo_symmetry_ops: int = 0, ): """Refine orientations from patterns in one dask array chunk using @@ -502,18 +510,18 @@ def _refine_orientation_chunk_nlopt( rotations: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray, - pcx: Optional[np.ndarray] = None, - pcy: Optional[np.ndarray] = None, - pcz: Optional[np.ndarray] = None, - opt: "nlopt.opt" = None, - signal_mask: Optional[np.ndarray] = None, - solver_kwargs: Optional[dict] = None, - direction_cosines: Optional[np.ndarray] = None, - nrows: Optional[int] = None, - ncols: Optional[int] = None, - tilt: Optional[float] = None, - azimuthal: Optional[float] = None, - sample_tilt: Optional[float] = None, + pcx: np.ndarray | None = None, + pcy: np.ndarray | None = None, + pcz: np.ndarray | None = None, + opt: "nlopt.opt | None" = None, + signal_mask: np.ndarray | None = None, + solver_kwargs: dict | None = None, + direction_cosines: np.ndarray | None = None, + nrows: int | None = None, + ncols: int | None = None, + tilt: float | None = None, + azimuthal: float | None = None, + sample_tilt: float | None = None, n_pseudo_symmetry_ops: int = 0, ): """Refine orientations from patterns in one dask array chunk using @@ -578,20 +586,20 @@ def _refine_pc( xmap: CrystalMap, detector, master_pattern, - energy: Union[int, float], - patterns: Union[np.ndarray, da.Array], + energy: int | float, + patterns: np.ndarray | da.Array, points_to_refine: np.ndarray, signal_mask: np.ndarray, - trust_region: Union[tuple, list, np.ndarray, None], + trust_region: np.ndarray | list | tuple | None, rtol: float, - rotations_ps: Optional[Rotation] = None, - method: Optional[str] = None, - method_kwargs: Optional[dict] = None, - initial_step: Optional[float] = None, - maxeval: Optional[int] = None, + rotations_ps: Rotation | None = None, + method: str | None = None, + method_kwargs: dict | None = None, + initial_step: float | None = None, + maxeval: int | None = None, compute: bool = True, - navigation_mask: Optional[np.ndarray] = None, -): + navigation_mask: np.ndarray | None = None, +) -> tuple[np.ndarray, "EBSDDetector", np.ndarray]: ref = _RefinementSetup( mode="pc", xmap=xmap, @@ -646,7 +654,7 @@ def _refine_pc_chunk_scipy( lower_bounds: np.ndarray, upper_bounds: np.ndarray, solver_kwargs: dict, -): +) -> np.ndarray: """Refine projection centers using patterns in one dask array chunk.""" nav_size = patterns.shape[0] @@ -674,8 +682,8 @@ def _refine_pc_chunk_nlopt( lower_bounds: np.ndarray, upper_bounds: np.ndarray, solver_kwargs: dict, - opt: "nlopt.opt" = None, -): + opt: "nlopt.opt | None" = None, +) -> np.ndarray: """Refine projection centers using patterns in one dask array chunk.""" # Copy optimizer import nlopt @@ -706,20 +714,20 @@ def _refine_orientation_pc( xmap: CrystalMap, detector, master_pattern, - energy: Union[int, float], - patterns: Union[np.ndarray, da.Array], + energy: int | float, + patterns: np.ndarray | da.Array, points_to_refine: np.ndarray, signal_mask: np.ndarray, - trust_region: Union[tuple, list, np.ndarray, None], + trust_region: np.ndarray | list | tuple | None, rtol: float, - pseudo_symmetry_ops: Optional[Rotation] = None, - method: Optional[str] = None, - method_kwargs: Optional[dict] = None, - initial_step: Union[tuple, list, np.ndarray, None] = None, - maxeval: Optional[int] = None, + pseudo_symmetry_ops: Rotation | None = None, + method: str | None = None, + method_kwargs: dict | None = None, + initial_step: np.ndarray | list | tuple | None = None, + maxeval: int | None = None, compute: bool = True, - navigation_mask: Optional[np.ndarray] = None, -) -> tuple: + navigation_mask: np.ndarray | None = None, +) -> tuple[CrystalMap, "EBSDDetector"]: """See the docstring of :meth:`kikuchipy.signals.EBSD.refine_orientation_projection_center`. """ @@ -781,9 +789,9 @@ def _refine_orientation_pc_chunk_scipy( rot_pc: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray, - solver_kwargs: Optional[dict] = None, + solver_kwargs: dict | None = None, n_pseudo_symmetry_ops: int = 0, -): +) -> np.ndarray: """Refine orientations and projection centers using all patterns in one dask array chunk using *SciPy*. """ @@ -813,10 +821,10 @@ def _refine_orientation_pc_chunk_nlopt( rot_pc: np.ndarray, lower_bounds: np.ndarray, upper_bounds: np.ndarray, - solver_kwargs: Optional[dict] = None, - opt: "nlopt.opt" = None, + solver_kwargs: dict | None = None, + opt: "nlopt.opt | None" = None, n_pseudo_symmetry_ops: int = 0, -): +) -> np.ndarray: """Refine orientations and projection centers using all patterns in one dask array chunk using *NLopt*. """ @@ -903,10 +911,10 @@ class _RefinementSetup: nav_size: int n_pseudo_symmetry_ops: int = 0 rotations_array: da.Array - rotations_pc_array: Optional[da.Array] = None + rotations_pc_array: da.Array | None = None # Optimization parameters - initial_step: Optional[list] = None - maxeval: Optional[int] = None + initial_step: list | None = None + maxeval: int | None = None method_name: str optimization_type: str package: str @@ -924,16 +932,16 @@ def __init__( xmap: CrystalMap, detector: "EBSDDetector", master_pattern: "EBSDMasterPattern", - energy: Union[int, float], - patterns: Union[np.ndarray, da.Array], + energy: int | float, + patterns: np.ndarray | da.Array, points_to_refine: np.ndarray, rtol: float, method: str, - method_kwargs: Optional[dict] = None, - initial_step: Optional[float] = None, - maxeval: Optional[int] = None, - signal_mask: Optional[np.ndarray] = None, - pseudo_symmetry_ops: Optional[Rotation] = None, + method_kwargs: dict | None = None, + initial_step: float | None = None, + maxeval: int | None = None, + signal_mask: np.ndarray | None = None, + pseudo_symmetry_ops: Rotation | None = None, ): """Set up EBSD refinement.""" self.mode = mode @@ -1054,9 +1062,9 @@ def set_optimization_parameters( self, rtol: float, method: str, - method_kwargs: Optional[dict] = None, - initial_step: Union[float, int, Tuple[float, float], None] = None, - maxeval: Optional[int] = None, + method_kwargs: dict | None = None, + initial_step: float | int | tuple[float, float] | None = None, + maxeval: int | None = None, ) -> None: """Set *NLopt* or *SciPy* optimization parameters. @@ -1148,8 +1156,8 @@ def set_fixed_parameters( self, detector: "EBSDDetector", master_pattern: "EBSDMasterPattern", - energy: Union[int, float], - signal_mask: Optional[np.ndarray] = None, + energy: int | float, + signal_mask: np.ndarray | None = None, ) -> None: """Set fixed parameters to pass to the objective function. @@ -1184,8 +1192,9 @@ def set_fixed_parameters( self.fixed_parameters = params def get_bound_constraints( - self, trust_region: Union[tuple, list, np.ndarray, None] - ) -> Tuple[da.Array, da.Array]: + self, + trust_region: np.ndarray | list | tuple | None, + ) -> tuple[da.Array, da.Array]: """Return the bound constraints on the control variables. Parameters @@ -1248,7 +1257,7 @@ def get_bound_constraints( return lower_bounds, upper_bounds - def get_info_message(self, trust_region) -> str: + def get_info_message(self, trust_region: np.ndarray | list | tuple | None) -> str: """Return a string with important refinement information to display to the user. @@ -1293,8 +1302,8 @@ def get_info_message(self, trust_region) -> str: def _get_master_pattern_data( - master_pattern: "EBSDMasterPattern", energy: Union[int, float] -) -> Tuple[np.ndarray, np.ndarray, int, int, float]: + master_pattern: "EBSDMasterPattern", energy: int | float +) -> tuple[np.ndarray, np.ndarray, int, int, float]: """Return the upper and lower hemispheres along with their shape. Parameters diff --git a/src/kikuchipy/indexing/_refinement/_solvers.py b/src/kikuchipy/indexing/_refinement/_solvers.py index 8b5c6ac6..9fe346b3 100644 --- a/src/kikuchipy/indexing/_refinement/_solvers.py +++ b/src/kikuchipy/indexing/_refinement/_solvers.py @@ -20,7 +20,7 @@ patterns. """ -from typing import Callable, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable from numba import njit import numpy as np @@ -37,9 +37,15 @@ ) from kikuchipy.signals.util._master_pattern import _get_direction_cosines_for_fixed_pc +if TYPE_CHECKING: # pragma: no cover + from kikuchipy.constants import installed + + if installed["nlopt"]: + import nlopt + @njit(cache=True, nogil=True, fastmath=True) -def _prepare_pattern(pattern: np.ndarray, rescale: bool) -> Tuple[np.ndarray, float]: +def _prepare_pattern(pattern: np.ndarray, rescale: bool) -> tuple[np.ndarray, float]: """Prepare experimental pattern. Parameters @@ -75,21 +81,20 @@ def _refine_orientation_solver_scipy( method: Callable, method_kwargs: dict, trust_region_passed: bool, - fixed_parameters: Tuple[np.ndarray, np.ndarray, int, int, float], - direction_cosines: Optional[np.ndarray] = None, - pcx: Optional[float] = None, - pcy: Optional[float] = None, - pcz: Optional[float] = None, - nrows: Optional[int] = None, - ncols: Optional[int] = None, - tilt: Optional[float] = None, - azimuthal: Optional[float] = None, - sample_tilt: Optional[float] = None, + fixed_parameters: tuple[np.ndarray, np.ndarray, int, int, float], + direction_cosines: np.ndarray | None = None, + pcx: float | None = None, + pcy: float | None = None, + pcz: float | None = None, + nrows: int | None = None, + ncols: int | None = None, + tilt: float | None = None, + azimuthal: float | None = None, + sample_tilt: float | None = None, n_pseudo_symmetry_ops: int = 0, -) -> Union[ - Tuple[float, int, float, float, float], - Tuple[float, int, int, float, float, float], -]: +) -> ( + tuple[float, int, float, float, float] | tuple[float, int, int, float, float, float] +): """Maximize the similarity between an experimental pattern and a projected simulated pattern by optimizing the orientation (Rodrigues-Frank vector) used in the projection. @@ -253,7 +258,7 @@ def _refine_pc_solver_scipy( method_kwargs: dict, fixed_parameters: tuple, trust_region_passed: bool, -) -> Tuple[float, int, float, float, float]: +) -> tuple[float, int, float, float, float]: """Maximize the similarity between an experimental pattern and a projected simulated pattern by optimizing the projection center (PC) parameters used in the projection. @@ -336,10 +341,10 @@ def _refine_orientation_pc_solver_scipy( fixed_parameters: tuple, trust_region_passed: bool, n_pseudo_symmetry_ops: int = 0, -) -> Union[ - Tuple[float, int, float, float, float, float, float, float], - Tuple[float, int, int, float, float, float, float, float, float], -]: +) -> ( + tuple[float, int, float, float, float, float, float, float] + | tuple[float, int, int, float, float, float, float, float, float] +): """Maximize the similarity between an experimental pattern and a projected simulated pattern by optimizing the orientation and projection center (PC) parameters used in the projection. @@ -466,21 +471,20 @@ def _refine_orientation_solver_nlopt( signal_mask: np.ndarray, rescale: bool, trust_region_passed: bool, - fixed_parameters: Tuple[np.ndarray, np.ndarray, int, int, float], - direction_cosines: Optional[np.ndarray] = None, - pcx: Optional[float] = None, - pcy: Optional[float] = None, - pcz: Optional[float] = None, - nrows: Optional[int] = None, - ncols: Optional[int] = None, - tilt: Optional[float] = None, - azimuthal: Optional[float] = None, - sample_tilt: Optional[float] = None, + fixed_parameters: tuple[np.ndarray, np.ndarray, int, int, float], + direction_cosines: np.ndarray | None = None, + pcx: float | None = None, + pcy: float | None = None, + pcz: float | None = None, + nrows: int | None = None, + ncols: int | None = None, + tilt: float | None = None, + azimuthal: float | None = None, + sample_tilt: float | None = None, n_pseudo_symmetry_ops: int = 0, -) -> Union[ - Tuple[float, int, float, float, float], - Tuple[float, int, int, float, float, float], -]: +) -> ( + tuple[float, int, float, float, float] | tuple[float, int, int, float, float, float] +): pattern, squared_norm = _prepare_pattern(pattern, rescale) # Get direction cosines if a unique PC per pattern is used @@ -546,7 +550,7 @@ def _refine_pc_solver_nlopt( rescale: bool, fixed_parameters: tuple, trust_region_passed: bool, -) -> Tuple[float, int, float, float, float]: +) -> tuple[float, int, float, float, float]: pattern, squared_norm = _prepare_pattern(pattern, rescale) # Combine tuple of fixed parameters passed to the objective function @@ -577,10 +581,10 @@ def _refine_orientation_pc_solver_nlopt( fixed_parameters: tuple, trust_region_passed: bool, n_pseudo_symmetry_ops: int = 0, -) -> Union[ - Tuple[float, int, float, float, float, float, float, float], - Tuple[float, int, int, float, float, float, float, float, float], -]: +) -> ( + tuple[float, int, float, float, float, float, float, float] + | tuple[float, int, int, float, float, float, float, float, float] +): pattern, squared_norm = _prepare_pattern(pattern, rescale) # Combine tuple of fixed parameters passed to the objective function diff --git a/src/kikuchipy/indexing/similarity_metrics/_normalized_cross_correlation.py b/src/kikuchipy/indexing/similarity_metrics/_normalized_cross_correlation.py index 29d2c913..2377f951 100644 --- a/src/kikuchipy/indexing/similarity_metrics/_normalized_cross_correlation.py +++ b/src/kikuchipy/indexing/similarity_metrics/_normalized_cross_correlation.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from typing import List, Union - import dask import dask.array as da from numba import njit @@ -55,13 +53,11 @@ class NormalizedCrossCorrelationMetric(SimilarityMetric): attributes. """ - _allowed_dtypes: List[type] = [np.float32, np.float64] + _allowed_dtypes: list[type] = [np.float32, np.float64] _sign: int = 1 def __call__( - self, - experimental: Union[da.Array, np.ndarray], - dictionary: Union[da.Array, np.ndarray], + self, experimental: da.Array | np.ndarray, dictionary: da.Array | np.ndarray ) -> da.Array: """Compute the similarities between experimental patterns and simulated dictionary patterns. @@ -90,8 +86,8 @@ def __call__( return self.match(experimental, dictionary) def prepare_experimental( - self, patterns: Union[np.ndarray, da.Array] - ) -> Union[np.ndarray, da.Array]: + self, patterns: np.ndarray | da.Array + ) -> np.ndarray | da.Array: """Prepare experimental patterns before matching to dictionary patterns in :meth:`match`. @@ -132,8 +128,8 @@ def prepare_experimental( return prepared_patterns def prepare_dictionary( - self, patterns: Union[np.ndarray, da.Array] - ) -> Union[np.ndarray, da.Array]: + self, patterns: np.ndarray | da.Array + ) -> np.ndarray | da.Array: """Prepare dictionary patterns before matching to experimental patterns in :meth:`match`. @@ -164,8 +160,8 @@ def prepare_dictionary( def match( self, - experimental: Union[np.ndarray, da.Array], - dictionary: Union[np.ndarray, da.Array], + experimental: np.ndarray | da.Array, + dictionary: np.ndarray | da.Array, ) -> da.Array: """Match all experimental patterns to all dictionary patterns and return their similarities. @@ -186,17 +182,15 @@ def match( "ik,mk->im", experimental, dictionary, optimize=True, dtype=self.dtype ) - def _mask_patterns( - self, patterns: Union[da.Array, np.ndarray] - ) -> Union[da.Array, np.ndarray]: + def _mask_patterns(self, patterns: da.Array | np.ndarray) -> da.Array | np.ndarray: with dask.config.set(**{"array.slicing.split_large_chunks": False}): patterns = patterns[:, ~self.signal_mask.ravel()] return patterns @staticmethod def _zero_mean_normalize_patterns( - patterns: Union[da.Array, np.ndarray] - ) -> Union[da.Array, np.ndarray]: + patterns: da.Array | np.ndarray, + ) -> da.Array | np.ndarray: if isinstance(patterns, np.ndarray): return _zero_mean_normalize_patterns_numpy(patterns) else: diff --git a/src/kikuchipy/indexing/similarity_metrics/_normalized_dot_product.py b/src/kikuchipy/indexing/similarity_metrics/_normalized_dot_product.py index 7ec33927..85720dcd 100644 --- a/src/kikuchipy/indexing/similarity_metrics/_normalized_dot_product.py +++ b/src/kikuchipy/indexing/similarity_metrics/_normalized_dot_product.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from typing import List, Union - import dask import dask.array as da import numpy as np @@ -45,13 +43,13 @@ class NormalizedDotProductMetric(SimilarityMetric): attributes. """ - _allowed_dtypes: List[type] = [np.float32, np.float64] + _allowed_dtypes: list[type] = [np.float32, np.float64] _sign: int = 1 def __call__( self, - experimental: Union[da.Array, np.ndarray], - dictionary: Union[da.Array, np.ndarray], + experimental: da.Array | np.ndarray, + dictionary: da.Array | np.ndarray, ) -> da.Array: """Compute the similarities between experimental patterns and simulated dictionary patterns. @@ -80,8 +78,8 @@ def __call__( return self.match(experimental, dictionary) def prepare_experimental( - self, patterns: Union[np.ndarray, da.Array] - ) -> Union[np.ndarray, da.Array]: + self, patterns: np.ndarray | da.Array + ) -> np.ndarray | da.Array: """Prepare experimental patterns before matching to dictionary patterns in :meth:`match`. @@ -122,8 +120,8 @@ def prepare_experimental( return patterns def prepare_dictionary( - self, patterns: Union[np.ndarray, da.Array] - ) -> Union[np.ndarray, da.Array]: + self, patterns: np.ndarray | da.Array + ) -> np.ndarray | da.Array: """Prepare dictionary patterns before matching to experimental patterns in :meth:`match`. @@ -153,8 +151,8 @@ def prepare_dictionary( def match( self, - experimental: Union[np.ndarray, da.Array], - dictionary: Union[np.ndarray, da.Array], + experimental: np.ndarray | da.Array, + dictionary: np.ndarray | da.Array, ) -> da.Array: """Match all experimental patterns to all dictionary patterns and return their similarities. @@ -175,17 +173,13 @@ def match( "ik,mk->im", experimental, dictionary, optimize=True, dtype=self.dtype ) - def _mask_patterns( - self, patterns: Union[da.Array, np.ndarray] - ) -> Union[da.Array, np.ndarray]: + def _mask_patterns(self, patterns: da.Array | np.ndarray) -> da.Array | np.ndarray: with dask.config.set(**{"array.slicing.split_large_chunks": False}): patterns = patterns[:, ~self.signal_mask.ravel()] return patterns @staticmethod - def _normalize_patterns( - patterns: Union[da.Array, np.ndarray] - ) -> Union[da.Array, np.ndarray]: + def _normalize_patterns(patterns: da.Array | np.ndarray) -> da.Array | np.ndarray: if isinstance(patterns, da.Array): dispatcher = da else: diff --git a/src/kikuchipy/indexing/similarity_metrics/_similarity_metric.py b/src/kikuchipy/indexing/similarity_metrics/_similarity_metric.py index e170973f..5aa5612d 100644 --- a/src/kikuchipy/indexing/similarity_metrics/_similarity_metric.py +++ b/src/kikuchipy/indexing/similarity_metrics/_similarity_metric.py @@ -16,7 +16,6 @@ # along with kikuchipy. If not, see . import abc -from typing import List, Optional, Union import numpy as np @@ -64,18 +63,18 @@ class SimilarityMetric(abc.ABC): Default is ``False``. """ - _allowed_dtypes: List[type] = [] - _sign: Optional[int] = None + _allowed_dtypes: list[type] = [] + _sign: int | None = None def __init__( self, - n_experimental_patterns: Optional[int] = None, - n_dictionary_patterns: Optional[int] = None, - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, - dtype: Union[str, np.dtype, type] = "float32", + n_experimental_patterns: int | None = None, + n_dictionary_patterns: int | None = None, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, + dtype: str | np.dtype | type = "float32", rechunk: bool = False, - ): + ) -> None: """Create a similarity metric matching experimental and simulated EBSD patterns in a dictionary. """ @@ -86,7 +85,7 @@ def __init__( self._dtype = np.dtype(dtype) self._rechunk = rechunk - def __repr__(self): + def __repr__(self) -> str: string = f"{self.__class__.__name__}: {np.dtype(self.dtype).name}, " sign_string = {1: "greater is better", -1: "lower is better"} string += sign_string[self.sign] @@ -96,7 +95,7 @@ def __repr__(self): return string @property - def allowed_dtypes(self) -> List[type]: + def allowed_dtypes(self) -> list[type]: """Return the list of allowed array data types used during matching. """ @@ -115,7 +114,7 @@ def dtype(self) -> np.dtype: return self._dtype @dtype.setter - def dtype(self, value: Union[str, np.dtype, type]): + def dtype(self, value: str | np.dtype | type) -> None: """Set which data type to cast the patterns to before matching. """ @@ -136,7 +135,7 @@ def n_dictionary_patterns(self) -> int: return self._n_dictionary_patterns @n_dictionary_patterns.setter - def n_dictionary_patterns(self, value: int): + def n_dictionary_patterns(self, value: int) -> None: """Set the number of dictionary patterns to match.""" self._n_dictionary_patterns = value @@ -155,7 +154,7 @@ def n_experimental_patterns(self) -> int: return self._n_experimental_patterns @n_experimental_patterns.setter - def n_experimental_patterns(self, value: int): + def n_experimental_patterns(self, value: int) -> None: """Set the number of experimental patterns to match.""" self._n_experimental_patterns = value @@ -172,7 +171,7 @@ def navigation_mask(self) -> np.ndarray: return self._navigation_mask @navigation_mask.setter - def navigation_mask(self, value: np.ndarray): + def navigation_mask(self, value: np.ndarray) -> None: """Set the boolean mask of patterns to match, equal to the navigation (map) shape. """ @@ -191,7 +190,7 @@ def signal_mask(self) -> np.ndarray: return self._signal_mask @signal_mask.setter - def signal_mask(self, value: np.ndarray): + def signal_mask(self, value: np.ndarray) -> None: """Set the boolean mask equal to the experimental patterns' detector shape ``(s rows, s columns)``. """ @@ -217,7 +216,7 @@ def rechunk(self) -> bool: return self._rechunk @rechunk.setter - def rechunk(self, value: bool): + def rechunk(self, value: bool) -> None: """Set whether to allow rechunking of arrays before matching.""" self._rechunk = value @@ -242,7 +241,7 @@ def match(self, *args, **kwargs): """ return NotImplemented # pragma: no cover - def raise_error_if_invalid(self): + def raise_error_if_invalid(self) -> None: """Raise a ValueError if :attr:`dtype` is not among :attr:`allowed_dtypes` and the latter is not an empty list. """ From 1f16b1f69be29a8e9fbdcb32617e37859f82e8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 2 Oct 2024 21:52:29 +0200 Subject: [PATCH 09/12] Fix Python version in tests CI YAML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f578591..70e86065 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,11 +48,11 @@ jobs: python-version: ['3.11', '3.12'] include: - os: ubuntu-latest - python-version: 3.10 + python-version: '3.10' DEPENDENCIES: dask==2021.8.1 diffsims==0.5.2 hyperspy==1.7.3 matplotlib==3.5 numba==0.57 numpy==1.23.0 orix==0.12.1 pooch==1.3.0 pyebsdindex==0.2.0 scikit-image==0.21.0 LABEL: -oldest - os: ubuntu-latest - python-version: 3.12 + python-version: '3.12' LABEL: -minimum_requirement steps: - uses: actions/checkout@v4 From 59ed1ad694c6b8d41f9596ecc3b173051c3b315d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 5 Oct 2024 13:21:24 +0200 Subject: [PATCH 10/12] Complete updating of type hints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 7 +- doc/dev/code_style.rst | 2 +- doc/dev/handling_deprecations.rst | 2 +- src/kikuchipy/constants.py | 5 +- src/kikuchipy/io/_io.py | 74 ++--- src/kikuchipy/io/_util.py | 8 +- .../io/plugins/_emsoft_master_pattern.py | 29 +- src/kikuchipy/io/plugins/_h5ebsd.py | 42 +-- src/kikuchipy/io/plugins/bruker_h5ebsd.py | 13 +- src/kikuchipy/io/plugins/ebsd_directory.py | 9 +- src/kikuchipy/io/plugins/edax_binary.py | 14 +- src/kikuchipy/io/plugins/edax_h5ebsd.py | 9 +- src/kikuchipy/io/plugins/emsoft_ebsd.py | 13 +- .../io/plugins/emsoft_ebsd_master_pattern.py | 21 +- .../io/plugins/emsoft_ecp_master_pattern.py | 21 +- .../io/plugins/emsoft_tkd_master_pattern.py | 21 +- src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py | 39 ++- src/kikuchipy/io/plugins/nordif.py | 23 +- .../io/plugins/nordif_calibration_patterns.py | 5 +- src/kikuchipy/io/plugins/oxford_binary.py | 17 +- src/kikuchipy/io/plugins/oxford_h5ebsd.py | 9 +- src/kikuchipy/logging.py | 3 +- src/kikuchipy/pattern/_pattern.py | 90 +++--- src/kikuchipy/pattern/chunk.py | 23 +- .../signals/_kikuchi_master_pattern.py | 24 +- src/kikuchipy/signals/_kikuchipy_signal.py | 42 +-- src/kikuchipy/signals/ebsd.py | 281 +++++++++--------- src/kikuchipy/signals/ebsd_master_pattern.py | 65 ++-- src/kikuchipy/signals/ecp_master_pattern.py | 57 ++-- src/kikuchipy/signals/util/_crystal_map.py | 7 +- src/kikuchipy/signals/util/_dask.py | 36 ++- src/kikuchipy/signals/util/_map_helper.py | 14 +- src/kikuchipy/signals/util/_master_pattern.py | 24 +- .../util/_overwrite_hyperspy_methods.py | 12 +- src/kikuchipy/signals/util/array_tools.py | 8 +- src/kikuchipy/signals/virtual_bse_image.py | 38 ++- .../simulations/_kikuchi_pattern_features.py | 22 +- .../_kikuchi_pattern_simulation.py | 51 ++-- .../simulations/kikuchi_pattern_simulator.py | 33 +- 39 files changed, 616 insertions(+), 597 deletions(-) diff --git a/conftest.py b/conftest.py index d98a62db..4bb2f9ba 100644 --- a/conftest.py +++ b/conftest.py @@ -24,7 +24,7 @@ import os from pathlib import Path import tempfile -from typing import Callable, Generator, List +from typing import Callable, Generator import dask.array as da from diffpy.structure import Atom, Lattice, Structure @@ -39,10 +39,11 @@ import skimage.color as skc import kikuchipy as kp +from kikuchipy import constants from kikuchipy.data._data import marshall from kikuchipy.io.plugins._h5ebsd import _dict2hdf5group -if kp.constants.installed["pyvista"]: +if constants.installed["pyvista"]: import pyvista as pv pv.OFF_SCREEN = True @@ -227,7 +228,7 @@ def nickel_phase(nickel_structure) -> Generator[Phase, None, None]: @pytest.fixture -def pc1() -> Generator[List[float], None, None]: +def pc1() -> Generator[list[float], None, None]: """One projection center (PC) in TSL convention.""" yield [0.4210, 0.7794, 0.5049] diff --git a/doc/dev/code_style.rst b/doc/dev/code_style.rst index d166db0c..ef9ca1ff 100644 --- a/doc/dev/code_style.rst +++ b/doc/dev/code_style.rst @@ -34,7 +34,7 @@ Package imports should be structured into three blocks with blank lines between We use type hints in the function definition without type duplication in the function docstring, for example:: - def my_function(a: int, b: Optional[bool] = None) -> Tuple[float, np.ndarray]: + def my_function(a: int, b: bool | None = None) -> tuple[float, np.ndarray]: """This is a new function. Parameters diff --git a/doc/dev/handling_deprecations.rst b/doc/dev/handling_deprecations.rst index e7d1ed76..90ac3db6 100644 --- a/doc/dev/handling_deprecations.rst +++ b/doc/dev/handling_deprecations.rst @@ -24,7 +24,7 @@ The decorator should be placed right above the object signature to be deprecated Parameters can be deprecated as well:: @deprecate_argument(name="n", since=0.8, removal=0.9, alternative="m") - def foo(self, n: Optional[int] = None, m: int: Optional[int] = None) -> int: + def foo(self, n: int | None = None, m: int: int | None = None) -> int: if m is None: m = n return m + 1 diff --git a/src/kikuchipy/constants.py b/src/kikuchipy/constants.py index 92077d2a..71041b46 100644 --- a/src/kikuchipy/constants.py +++ b/src/kikuchipy/constants.py @@ -18,15 +18,14 @@ """Constants and such useful across modules.""" from importlib.metadata import version -from typing import Dict, List # NB! Update project config file if this list is updated! -optional_deps: List[str] = [ +optional_deps: list[str] = [ "pyvista", "nlopt", "pyebsdindex", ] -installed: Dict[str, bool] = {} +installed: dict[str, bool] = {} for pkg in optional_deps: try: _ = version(pkg) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 3d5d6963..4abd031a 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -18,9 +18,9 @@ import glob import os from pathlib import Path -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING -from h5py import File, Group, is_hdf5 +import h5py from hyperspy.io_plugins import hspy from hyperspy.misc.io.tools import overwrite as overwrite_method from hyperspy.misc.utils import find_subclasses, strlist2enumeration @@ -45,7 +45,7 @@ ) import kikuchipy.signals -plugins = [ +plugins: list = [ bruker_h5ebsd, ebsd_directory, edax_binary, @@ -63,8 +63,11 @@ ] if TYPE_CHECKING: # pragma: no cover - from kikuchipy.signals.ebsd import EBSD - from kikuchipy.signals.ebsd_master_pattern import EBSDMasterPattern + from kikuchipy.signals.ebsd import EBSD, LazyEBSD + from kikuchipy.signals.ebsd_master_pattern import ( + EBSDMasterPattern, + LazyEBSDMasterPattern, + ) from kikuchipy.signals.ecp_master_pattern import ECPMasterPattern default_write_ext = set() @@ -73,14 +76,9 @@ default_write_ext.add(plugin.file_extensions[plugin.default_extension]) -def load(filename: Union[str, Path], lazy: bool = False, **kwargs) -> Union[ - "EBSD", - "EBSDMasterPattern", - "ECPMasterPattern", - List["EBSD"], - List["EBSDMasterPattern"], - List["ECPMasterPattern"], -]: +def load( + filename: str | Path, lazy: bool = False, **kwargs +) -> "EBSD | EBSDMasterPattern | ECPMasterPattern | list[EBSD] | list[EBSDMasterPattern] | list[ECPMasterPattern]": """Load an :class:`~kikuchipy.signals.EBSD`, :class:`~kikuchipy.signals.EBSDMasterPattern` or :class:`~kikuchipy.signals.ECPMasterPattern` signal from one of the @@ -127,7 +125,7 @@ def load(filename: Union[str, Path], lazy: bool = False, **kwargs) -> Union[ if len(filenames) > 0: is_wildcard = True if not is_wildcard: - raise IOError(f"No filename matches '{filename}'.") + raise IOError(f"No filename matches {filename!r}") # Find matching reader for file extension extension = os.path.splitext(filename)[1][1:] @@ -137,10 +135,10 @@ def load(filename: Union[str, Path], lazy: bool = False, **kwargs) -> Union[ readers.append(plugin) if len(readers) == 0: raise IOError( - f"Could not read '{filename}'. If the file format is supported, please " + f"Could not read {filename!r}. If the file format is supported, please " "report this error" ) - elif len(readers) > 1 and is_hdf5(filename): + elif len(readers) > 1 and h5py.is_hdf5(filename): reader = _plugin_from_footprints(filename, plugins=readers) else: reader = readers[0] @@ -163,7 +161,9 @@ def load(filename: Union[str, Path], lazy: bool = False, **kwargs) -> Union[ return out -def _dict2signal(signal_dict: dict, lazy: bool = False): +def _dict2signal( + signal_dict: dict, lazy: bool = False +) -> "EBSD | LazyEBSD | EBSDMasterPattern | LazyEBSDMasterPattern": """Create a signal instance from a dictionary. This function is a modified version :func:`hyperspy.io.dict2signal`. @@ -180,7 +180,7 @@ def _dict2signal(signal_dict: dict, lazy: bool = False): Returns ------- - signal : EBSD, LazyEBSD, EBSDMasterPattern or LazyEBSDMasterPattern + signal Signal instance with ``data``, ``metadata`` and ``original_metadata`` from dictionary. """ @@ -210,7 +210,7 @@ def _dict2signal(signal_dict: dict, lazy: bool = False): return signal -def _plugin_from_footprints(filename: str, plugins) -> Optional[object]: +def _plugin_from_footprints(filename: str, plugins) -> object: """Get HDF5 correct plugin from a list of potential plugins based on their unique footprints. @@ -239,7 +239,7 @@ def _hdf5group2dict(group): d = {} for key, val in group.items(): key_lower = key.lstrip().lower() - if isinstance(val, Group): + if isinstance(val, h5py.Group): d[key_lower] = _hdf5group2dict(val) elif key_lower == "manufacturer": d[key_lower] = key @@ -252,7 +252,7 @@ def _exists(obj, chain): if key in obj: return _exists(obj[key], chain) if chain else obj[key] - with File(filename) as f: + with h5py.File(filename) as f: d = _hdf5group2dict(f["/"]) plugins_with_footprints = [p for p in plugins if hasattr(p, "footprint")] @@ -296,7 +296,7 @@ def _assign_signal_subclass( signal_dimension: int, signal_type: str = "", lazy: bool = False, -): +) -> "EBSD | EBSDMasterPattern | ECPMasterPattern": """Given ``record_by`` and ``signal_type`` return the matching signal subclass. @@ -330,10 +330,10 @@ def _assign_signal_subclass( ): dtype = "real" else: - raise ValueError(f"Data type '{dtype.name}' not understood") + raise ValueError(f"Data type {dtype.name!r} not understood") if not isinstance(signal_dimension, int) or signal_dimension < 0: raise ValueError( - f"Signal dimension must be a positive integer and not '{signal_dimension}'" + f"Signal dimension must be a positive integer and not {signal_dimension!r}" ) # Get possible signal classes @@ -357,20 +357,20 @@ def _assign_signal_subclass( matches = dtype_dim_type_matches else: raise ValueError( - f"No kikuchipy signals match dtype '{dtype}', signal dimension " - f"'{signal_dimension}' and signal_type '{signal_type}'." + f"No kikuchipy signals match dtype {dtype!r}, signal dimension " + f"'{signal_dimension}' and signal_type {signal_type!r}" ) return matches[0] def _save( - filename: Union[str, Path], - signal, - overwrite: Optional[bool] = None, - add_scan: Optional[bool] = None, + filename: str | Path, + signal: "EBSD | LazyEBSD", + overwrite: bool | None = None, + add_scan: bool | None = None, **kwargs, -): +) -> None: """Write signal to a file in a supported format. This function is a modified version of :func:`hyperspy.io.save`. @@ -379,7 +379,7 @@ def _save( ---------- filename File path including name of new file. - signal : EBSD or LazyEBSD + signal Signal instance. overwrite Whether to overwrite file or not if it already exists. @@ -387,7 +387,7 @@ def _save( Whether to add the signal to an already existing h5ebsd file or not. If the file does not exist the signal is written to a new file. - **kwargs : + **kwargs Keyword arguments passed to the writer. """ filename = str(filename) @@ -405,8 +405,8 @@ def _save( if writer is None: raise ValueError( - f"'{ext}' does not correspond to any supported format. Supported file " - f"extensions are: '{strlist2enumeration(default_write_ext)}'" + f"{ext!r} does not correspond to any supported format. Supported file " + f"extensions are: {strlist2enumeration(default_write_ext)!r}" ) else: sd = signal.axes_manager.signal_dimension @@ -423,7 +423,7 @@ def _save( writing_plugins.append(plugin) raise ValueError( "This file format cannot write this data. The following formats can: " - f"{strlist2enumeration(writing_plugins)}" + f"{strlist2enumeration(writing_plugins)!r}" ) _ensure_directory(filename) @@ -436,7 +436,7 @@ def _save( and is_file ): if add_scan is None: - q = "Add scan to '{}' (y/n)?\n".format(filename) + q = f"Add scan to {filename!r} (y/n)?\n" add_scan = _get_input_bool(q) if add_scan: overwrite = True # So that the 2nd statement below triggers diff --git a/src/kikuchipy/io/_util.py b/src/kikuchipy/io/_util.py index 74fa483c..30633166 100644 --- a/src/kikuchipy/io/_util.py +++ b/src/kikuchipy/io/_util.py @@ -16,11 +16,11 @@ # along with kikuchipy. If not, see . import os -from typing import Any, Union +from typing import Any import warnings -def _get_input_bool(question: str) -> bool: +def _get_input_bool(question: str) -> bool | None: """Get input from user on boolean choice, returning the answer. Parameters @@ -46,7 +46,7 @@ def _get_input_bool(question: str) -> bool: return False -def _get_input_variable(question: str, var_type: Any) -> Union[None, Any]: +def _get_input_variable(question: str, var_type: Any) -> Any | None: """Get variable input from user, returning the variable. Parameters @@ -73,7 +73,7 @@ def _get_input_variable(question: str, var_type: Any) -> Union[None, Any]: return None -def _ensure_directory(filename: str): +def _ensure_directory(filename: str) -> None: """Check if the filename path exists, create it if not.""" directory = os.path.split(filename)[0] if directory and not os.path.exists(directory): diff --git a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py index fcfcd412..43bf0fcb 100644 --- a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py +++ b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py @@ -18,10 +18,9 @@ import abc import os from pathlib import Path -from typing import List, Optional, Tuple import dask.array as da -from h5py import Dataset, File, Group +import h5py import numpy as np from kikuchipy.io.plugins._h5ebsd import _hdf5group2dict @@ -35,12 +34,12 @@ class EMsoftMasterPatternReader(abc.ABC): def __init__( self, - filename: str, - energy: Optional[range] = None, + filename: str | Path, + energy: range | None = None, projection: str = "stereographic", hemisphere: "str" = "upper", lazy: bool = False, - ): + ) -> None: self.filename = filename self.energy = energy self.projection = projection.lower() @@ -49,20 +48,20 @@ def __init__( @property @abc.abstractmethod - def diffraction_type(self): + def diffraction_type(self) -> str: return NotImplemented # pragma: no cover @property @abc.abstractmethod - def cl_parameters_group_name(self): + def cl_parameters_group_name(self) -> str: return NotImplemented # pragma: no cover @property @abc.abstractmethod - def energy_string(self): + def energy_string(self) -> str: return NotImplemented # pragma: no cover - def read(self, **kwargs) -> List[dict]: + def read(self, **kwargs) -> list[dict]: """Read kikuchi diffraction master patterns. Parameters @@ -78,7 +77,7 @@ def read(self, **kwargs) -> List[dict]: fpath = Path(self.filename) mode = kwargs.pop("mode", "r") - f = File(fpath, mode=mode, **kwargs) + f = h5py.File(fpath, mode=mode, **kwargs) _check_file_format(f, self.diffraction_type) @@ -221,7 +220,7 @@ def read(self, **kwargs) -> List[dict]: return [output] -def _check_file_format(file: File, diffraction_type: str): +def _check_file_format(file: h5py.File, diffraction_type: str) -> None: """Raise an error if the HDF5 file is not in EMsoft's master pattern file format. @@ -252,8 +251,8 @@ def _check_file_format(file: File, diffraction_type: str): def _get_data_shape_slices( npx: int, energies: np.ndarray, - energy: Optional[tuple] = None, -) -> Tuple[Tuple, Tuple[slice, ...]]: + energy: tuple | None = None, +) -> tuple[tuple, tuple[slice, ...]]: """Return data shape from half the master pattern side length, number of asymmetric positions if the square Lambert projection is to be read, and an energy or energy range. @@ -294,7 +293,9 @@ def _get_data_shape_slices( return data_shape, data_slices -def _get_datasets(data_group: Group, projection: str, hemisphere: str) -> List[Dataset]: +def _get_datasets( + data_group: h5py.Group, projection: str, hemisphere: str +) -> list[h5py.Dataset]: """Return datasets from projection and hemisphere. Parameters diff --git a/src/kikuchipy/io/plugins/_h5ebsd.py b/src/kikuchipy/io/plugins/_h5ebsd.py index df48f24a..1de92d3c 100644 --- a/src/kikuchipy/io/plugins/_h5ebsd.py +++ b/src/kikuchipy/io/plugins/_h5ebsd.py @@ -19,7 +19,7 @@ import abc import os -from typing import List, Optional, Tuple, Union +from pathlib import Path import warnings import dask.array as da @@ -31,9 +31,9 @@ def _hdf5group2dict( group: h5py.Group, - dictionary: Union[None, dict] = None, + dictionary: dict | None = None, recursive: bool = False, - data_dset_names: Optional[list] = None, + data_dset_names: list | None = None, ) -> dict: """Return a dictionary with values from datasets in a group. @@ -83,7 +83,7 @@ def _hdf5group2dict( return dictionary -def _dict2hdf5group(dictionary: dict, group: h5py.Group, **kwargs): +def _dict2hdf5group(dictionary: dict, group: h5py.Group, **kwargs) -> None: """Write a dictionary to datasets in a new group in an opened HDF5 file format. @@ -144,23 +144,23 @@ class H5EBSDReader(abc.ABC): "oxford instruments": "Processed Patterns", } - def __init__(self, filename: str, **kwargs): - self.filename = filename + def __init__(self, filename: str | Path, **kwargs) -> None: + self.filename = str(filename) self.file = h5py.File(filename, **kwargs) self.scan_groups = self.get_scan_groups() self.manufacturer, self.version = self.get_manufacturer_version() self.check_file() self.patterns_name = self.manufacturer_patterns[self.manufacturer] - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__} ({self.version}): {self.filename}" @property - def scan_group_names(self) -> List[str]: + def scan_group_names(self) -> list[str]: """Return a list of available scan group names.""" return [group.name.lstrip("/") for group in self.scan_groups] - def check_file(self): + def check_file(self) -> None: """Check if the file is a valid h5ebsd file by searching for datasets containing manufacturer, version and scans in the top group. @@ -185,7 +185,7 @@ def check_file(self): "no top groups with subgroup name 'EBSD' with subgroups 'Data' and " "'Header' were found" ) - man, ver = self.get_manufacturer_version() + man, _ = self.get_manufacturer_version() man = man.lower() supported_manufacturers = list(self.manufacturer_patterns.keys()) if man not in supported_manufacturers and error is None: @@ -196,7 +196,7 @@ def check_file(self): if error is not None: raise IOError(f"{self.filename} is not a supported h5ebsd file, as {error}") - def get_manufacturer_version(self) -> Tuple[str, str]: + def get_manufacturer_version(self) -> tuple[str | None, str | None]: """Get manufacturer and version from the top group. Returns @@ -215,7 +215,7 @@ def get_manufacturer_version(self) -> Tuple[str, str]: version = val.lower() return manufacturer, version - def get_scan_groups(self) -> List[h5py.Group]: + def get_scan_groups(self) -> list[h5py.Group]: """Return a list of the groups with scans. Assumes all top groups contain a scan. @@ -232,8 +232,8 @@ def get_scan_groups(self) -> List[h5py.Group]: return scan_groups def get_desired_scan_groups( - self, group_names: Union[None, str, List[str]] = None - ) -> List[h5py.Group]: + self, group_names: str | list[str] | None = None + ) -> list[h5py.Group]: """Return a list of the desired group(s) with scan(s). Parameters @@ -277,8 +277,8 @@ def get_data( group: h5py.Group, data_shape: tuple, lazy: bool = False, - indices: Optional[np.ndarray] = None, - ) -> Union[np.ndarray, da.Array]: + indices: np.ndarray | None = None, + ) -> np.ndarray | da.Array: """Read and return patterns from file as a NumPy or Dask array. Parameters @@ -353,7 +353,9 @@ def get_data( return data @staticmethod - def get_axes_list(data_shape: tuple, data_scale: tuple) -> List[dict]: + def get_axes_list( + data_shape: tuple[int, ...], data_scale: tuple[int, ...] + ) -> list[dict]: """Return a description of each data axis. Parameters @@ -414,7 +416,7 @@ def get_axes_list(data_shape: tuple, data_scale: tuple) -> List[dict]: return axes_list - def get_metadata_filename_title(self, group_name: str) -> Tuple[str, str]: + def get_metadata_filename_title(self, group_name: str) -> tuple[str, str]: """Return filename without full path and a scan title for the signal metadata. @@ -438,9 +440,9 @@ def get_metadata_filename_title(self, group_name: str) -> Tuple[str, str]: def read( self, - group_names: Union[None, str, List[str]] = None, + group_names: str | list[str] | None = None, lazy: bool = False, - ) -> List[dict]: + ) -> list[dict]: """Return a list of dictionaries which can be used to create :class:`~kikuchipy.signals.EBSD` signals. diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd.py b/src/kikuchipy/io/plugins/bruker_h5ebsd.py index b9369d63..c49d3937 100644 --- a/src/kikuchipy/io/plugins/bruker_h5ebsd.py +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd.py @@ -18,7 +18,6 @@ """Reader of EBSD data from a Bruker Nano h5ebsd file.""" from pathlib import Path -from typing import List, Union import h5py import numpy as np @@ -67,7 +66,7 @@ class BrukerH5EBSDReader(H5EBSDReader): Keyword arguments passed to :class:`h5py.File`. """ - def __init__(self, filename: str, **kwargs): + def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: @@ -198,10 +197,10 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: return scan_dict -def _bruker_roi_is_rectangular(iy, ix): +def _bruker_roi_is_rectangular(iy: int, ix: int) -> tuple[int, int, bool]: iy_unique, iy_unique_counts = np.unique(iy, return_counts=True) ix_unique, ix_unique_counts = np.unique(ix, return_counts=True) - is_rectangular = ( + is_rectangular = bool( np.all(np.diff(np.sort(iy_unique)) == 1) and np.all(np.diff(np.sort(ix_unique)) == 1) and np.unique(iy_unique_counts).size == 1 @@ -213,11 +212,11 @@ def _bruker_roi_is_rectangular(iy, ix): def file_reader( - filename: Union[str, Path], - scan_group_names: Union[None, str, List[str]] = None, + filename: str | Path, + scan_group_names: str | list[str] | None = None, lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read electron backscatter diffraction patterns, a crystal map, and an EBSD detector from a Bruker h5ebsd file :cite:`jackson2014h5ebsd`. diff --git a/src/kikuchipy/io/plugins/ebsd_directory.py b/src/kikuchipy/io/plugins/ebsd_directory.py index 5d12f494..6a35e211 100644 --- a/src/kikuchipy/io/plugins/ebsd_directory.py +++ b/src/kikuchipy/io/plugins/ebsd_directory.py @@ -22,7 +22,6 @@ import os from pathlib import Path import re -from typing import Dict, List, Optional, Union import warnings import dask @@ -50,11 +49,11 @@ def file_reader( - filename: Union[str, Path], - xy_pattern: Optional[str] = None, - show_progressbar: Optional[bool] = None, + filename: str | Path, + xy_pattern: str | None = None, + show_progressbar: bool | None = None, lazy: bool = False, -) -> List[Dict]: +) -> list[dict]: r"""Read all images in a directory, assuming they are electron backscatter diffraction (EBSD) patterns of equal shape and data type. diff --git a/src/kikuchipy/io/plugins/edax_binary.py b/src/kikuchipy/io/plugins/edax_binary.py index 67c26f46..cc3fa843 100644 --- a/src/kikuchipy/io/plugins/edax_binary.py +++ b/src/kikuchipy/io/plugins/edax_binary.py @@ -21,7 +21,7 @@ """ from pathlib import Path -from typing import BinaryIO, Dict, List, Optional, Tuple, Union +from typing import BinaryIO import warnings import dask.array as da @@ -49,10 +49,10 @@ def file_reader( - filename: Union[str, Path], - nav_shape: Optional[Tuple[int, int]] = None, + filename: str | Path, + nav_shape: tuple[int, int] | None = None, lazy: bool = False, -) -> List[dict]: +) -> list[dict]: """Read EBSD patterns from an EDAX binary UP1/2 file. Not meant to be used directly; use :func:`~kikuchipy.load`. @@ -113,7 +113,7 @@ class EDAXBinaryFileReader: Open EDAX binary UP1/2 file with uncompressed patterns. """ - def __init__(self, file: BinaryIO): + def __init__(self, file: BinaryIO) -> None: """Prepare to read EBSD patterns from an open EDAX UP1/2 file.""" self.file = file @@ -125,7 +125,7 @@ def __init__(self, file: BinaryIO): if self.version == 2: raise ValueError("Only files with version 1 or >= 3, not 2, can be read") - def read_header(self) -> Dict[str, int]: + def read_header(self) -> dict[str, int]: """Read and return header information. Returns @@ -175,7 +175,7 @@ def read_header(self) -> Dict[str, int]: } def read_scan( - self, nav_shape: Optional[Tuple[int, int]] = None, lazy: bool = False + self, nav_shape: tuple[int, int] | None = None, lazy: bool = False ) -> dict: """Return a dictionary with scan information and patterns. diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd.py b/src/kikuchipy/io/plugins/edax_h5ebsd.py index c2ca2e5d..48e2dc51 100644 --- a/src/kikuchipy/io/plugins/edax_h5ebsd.py +++ b/src/kikuchipy/io/plugins/edax_h5ebsd.py @@ -18,7 +18,6 @@ """Reader of EBSD data from an EDAX TSL h5ebsd file.""" from pathlib import Path -from typing import List, Union import h5py from orix.crystal_map import CrystalMap @@ -66,7 +65,7 @@ class EDAXH5EBSDReader(H5EBSDReader): Keyword arguments passed to :class:`h5py.File`. """ - def __init__(self, filename: str, **kwargs): + def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: @@ -162,11 +161,11 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: def file_reader( - filename: Union[str, Path], - scan_group_names: Union[None, str, List[str]] = None, + filename: str | Path, + scan_group_names: str | list[str] | None = None, lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read electron backscatter diffraction patterns, a crystal map, and an EBSD detector from an EDAX h5ebsd file :cite:`jackson2014h5ebsd`. diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd.py b/src/kikuchipy/io/plugins/emsoft_ebsd.py index 4b60bc8e..ee123dc7 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd.py @@ -19,11 +19,10 @@ import os from pathlib import Path -from typing import List, Tuple, Union import dask.array as da from diffpy.structure import Atom, Lattice, Structure -from h5py import File +import h5py import numpy as np from orix.crystal_map import CrystalMap, Phase, PhaseList from orix.quaternion import Rotation @@ -54,11 +53,11 @@ def file_reader( - filename: Union[str, Path], - scan_size: Union[None, int, Tuple[int, ...]] = None, + filename: str | Path, + scan_size: int | tuple[int, ...] | None = None, lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read dynamically simulated electron backscatter diffraction patterns from EMsoft's format produced by their EMEBSD.f90 program. @@ -83,7 +82,7 @@ def file_reader( Data, axes, metadata and original metadata. """ mode = kwargs.pop("mode", "r") - f = File(filename, mode=mode, **kwargs) + f = h5py.File(filename, mode=mode, **kwargs) _check_file_format(f) @@ -166,7 +165,7 @@ def file_reader( return [scan] -def _check_file_format(file: File): +def _check_file_format(file: h5py.File) -> None: """Return whether the HDF file is in EMsoft's format. Parameters diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py index 7966ac67..a876289b 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py @@ -19,7 +19,6 @@ """ from pathlib import Path -from typing import List, Optional, Union from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader @@ -45,19 +44,27 @@ class EMsoftEBSDMasterPatternReader(EMsoftMasterPatternReader): - diffraction_type = "EBSD" - cl_parameters_group_name = "MCCL" # Monte Carlo openCL - energy_string = "EkeVs" + @property + def diffraction_type(self) -> str: + return "EBSD" + + @property + def cl_parameters_group_name(self) -> str: + return "MCCL" # Monte Carlo openCL + + @property + def energy_string(self) -> str: + return "EkeVs" def file_reader( - filename: Union[str, Path], - energy: Optional[range] = None, + filename: str | Path, + energy: range | None = None, projection: str = "stereographic", hemisphere: str = "upper", lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read simulated electron backscatter diffraction master patterns from EMsoft's HDF5 file format :cite:`callahan2013dynamical`. diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py index abff92fc..9e002e11 100644 --- a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py @@ -19,7 +19,6 @@ """ from pathlib import Path -from typing import List, Optional, Union from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader @@ -45,19 +44,27 @@ class EMsoftECPMasterPatternReader(EMsoftMasterPatternReader): - diffraction_type = "ECP" - cl_parameters_group_name = "MCCL" # Monte Carlo openCL - energy_string = "EkeV" + @property + def diffraction_type(self) -> str: + return "ECP" + + @property + def cl_parameters_group_name(self) -> str: + return "MCCL" # Monte Carlo openCL + + @property + def energy_string(self) -> str: + return "EkeV" def file_reader( - filename: Union[str, Path], - energy: Optional[range] = None, + filename: str | Path, + energy: range | None = None, projection: str = "stereographic", hemisphere: str = "upper", lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read simulated electron channeling pattern (ECP) master patterns from EMsoft's HDF5 file format :cite:`callahan2013dynamical`. diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py index 2f22bec5..318ab173 100644 --- a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py @@ -19,7 +19,6 @@ """ from pathlib import Path -from typing import List, Optional, Union from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader @@ -45,19 +44,27 @@ class EMsoftTKDMasterPatternReader(EMsoftMasterPatternReader): - diffraction_type = "TKD" - cl_parameters_group_name = "MCCLfoil" # Monte Carlo openCL - energy_string = "EkeVs" + @property + def diffraction_type(self) -> str: + return "TKD" + + @property + def cl_parameters_group_name(self) -> str: + return "MCCLfoil" # Monte Carlo openCL + + @property + def energy_string(self) -> str: + return "EkeVs" def file_reader( - filename: Union[str, Path], - energy: Optional[range] = None, + filename: str | Path, + energy: range | None = None, projection: str = "stereographic", hemisphere: str = "upper", lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read simulated transmission kikuchi diffraction master patterns from EMsoft's HDF5 file format :cite:`callahan2013dynamical`. diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py index 877df4dd..02edc417 100644 --- a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py @@ -19,7 +19,7 @@ import os from pathlib import Path -from typing import List, Optional, Tuple, Union +from typing import TYPE_CHECKING import warnings import h5py @@ -37,6 +37,8 @@ __all__ = ["file_reader", "file_writer"] +if TYPE_CHECKING: # pragma: no cover + from kikuchipy.signals.ebsd import EBSD # Plugin characteristics # ---------------------- @@ -75,7 +77,7 @@ class KikuchipyH5EBSDReader(H5EBSDReader): Keyword arguments passed to :class:`h5py.File`. """ - def __init__(self, filename: str, **kwargs): + def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: @@ -138,12 +140,7 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: xmap_dict = _hdf5group2dict( group["EBSD/CrystalMap/crystal_map"], recursive=True ) - # TODO: Remove once orix v0.11.0 is released - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "Argument `z`", np.VisibleDeprecationWarning - ) - xmap = dict2crystalmap(xmap_dict) + xmap = dict2crystalmap(xmap_dict) else: xmap = CrystalMap.empty(shape=(ny, nx), step_sizes=(dy, dx)) scan_dict["xmap"] = xmap @@ -193,11 +190,11 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: def file_reader( - filename: Union[str, Path], - scan_group_names: Union[None, str, List[str]] = None, + filename: str | Path, + scan_group_names: str | list[str] | None = None, lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read electron backscatter diffraction patterns, a crystal map, and an EBSD detector from a kikuchipy h5ebsd file :cite:`jackson2014h5ebsd`. @@ -241,7 +238,7 @@ class KikuchipyH5EBSDWriter: ---------- filename Full file path of the HDF5 file. - signal : kikuchipy.signals.EBSD + signal EBSD signal. add_scan Whether to add the signal to the file if it exists and is @@ -250,8 +247,8 @@ class KikuchipyH5EBSDWriter: """ def __init__( - self, filename: Union[str, Path], signal, add_scan: Optional[bool] = None - ): + self, filename: str | Path, signal: "EBSD", add_scan: bool | None = None + ) -> None: self.filename = filename self.signal = signal self.file_exists = os.path.isfile(filename) @@ -272,11 +269,11 @@ def __init__( if self.file_exists: self.check_file() - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}: {self.filename}" @property - def data_shape_scale(self) -> Tuple[tuple, tuple]: + def data_shape_scale(self) -> tuple[tuple, tuple]: """Return the shape and scale of the data to write to file.""" data_shape = [1] * 4 # (ny, nx, sy, sx) data_scales = [1] * 3 # (dy, dx, px_size) @@ -299,11 +296,11 @@ def data_shape_scale(self) -> Tuple[tuple, tuple]: return tuple(data_shape), tuple(data_scales) @property - def scan_group_names(self) -> List[str]: + def scan_group_names(self) -> list[str]: """Return a list of available scan group names.""" return [group.name.lstrip("/") for group in self.scan_groups] - def check_file(self): + def check_file(self) -> None: """Check if the file, if it is old, is a valid kikuchipy h5ebsd file. @@ -331,7 +328,7 @@ def check_file(self): f"{self.filename} is not a supported kikuchipy h5ebsd file, as {error}" ) - def get_scan_groups(self) -> list: + def get_scan_groups(self) -> list["EBSD"]: """Return a list of groups with scans.""" scan_groups = [] if self.file_exists: @@ -381,7 +378,7 @@ def get_xmap_dict(self) -> dict: xmap = CrystalMap.empty(shape=(ny, nx), step_sizes=(dy, dx)) return crystalmap2dict(xmap) - def write(self, scan_number: int = 1, **kwargs): + def write(self, scan_number: int = 1, **kwargs) -> None: """Write an :class:`~kikuchipy.signals.EBSD` to file. The file is closed after writing. @@ -479,7 +476,7 @@ def write(self, scan_number: int = 1, **kwargs): def file_writer( filename: str, signal, - add_scan: Optional[bool] = None, + add_scan: bool | None = None, scan_number: int = 1, **kwargs, ): diff --git a/src/kikuchipy/io/plugins/nordif.py b/src/kikuchipy/io/plugins/nordif.py index 2b3faaa1..693a3b57 100644 --- a/src/kikuchipy/io/plugins/nordif.py +++ b/src/kikuchipy/io/plugins/nordif.py @@ -21,7 +21,7 @@ import os from pathlib import Path import re -from typing import Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING import warnings from matplotlib.pyplot import imread @@ -30,6 +30,9 @@ from kikuchipy.detectors.ebsd_detector import EBSDDetector +if TYPE_CHECKING: # pragma: no cover + from kikuchipy.signals.ebsd import EBSD, LazyEBSD + __all__ = ["file_reader", "file_writer"] _logger = logging.getLogger(__name__) @@ -47,13 +50,13 @@ def file_reader( - filename: Union[str, Path], - mmap_mode: Optional[str] = None, - scan_size: Union[None, int, Tuple[int, ...]] = None, - pattern_size: Optional[Tuple[int, ...]] = None, - setting_file: Optional[str] = None, + filename: str | Path, + mmap_mode: str | None = None, + scan_size: int | tuple[int, ...] | None = None, + pattern_size: tuple[int, ...] | None = None, + setting_file: str | None = None, lazy: bool = False, -) -> List[Dict]: +) -> list[dict]: """Read electron backscatter patterns from a NORDIF data file. Not meant to be used directly; use :func:`~kikuchipy.load`. @@ -213,7 +216,7 @@ def file_reader( def _get_settings_from_file( filename: str, pattern_type: str = "acquisition" -) -> Tuple[dict, dict, dict, dict]: +) -> tuple[dict, dict, dict, dict]: """Return metadata with parameters from NORDIF setting file. Parameters @@ -235,7 +238,7 @@ def _get_settings_from_file( detector Dictionary for creating an EBSD detector. """ - f = open(filename, "r", encoding="latin-1") # Avoid byte strings + f = open(filename, encoding="latin-1") # Avoid byte strings content = f.read().splitlines() # Get line numbers of setting blocks @@ -439,7 +442,7 @@ def scale(x, return_tuple: bool = False): return omd -def file_writer(filename: str, signal: Union["EBSD", "LazyEBSD"]): +def file_writer(filename: str, signal: "EBSD | LazyEBSD") -> None: """Write an :class:`~kikuchipy.signals.EBSD` or :class:`~kikuchipy.signals.LazyEBSD` instance to a NORDIF binary file. diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns.py b/src/kikuchipy/io/plugins/nordif_calibration_patterns.py index 83083f76..c829318d 100644 --- a/src/kikuchipy/io/plugins/nordif_calibration_patterns.py +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns.py @@ -19,7 +19,6 @@ import os from pathlib import Path -from typing import List, Tuple, Union import warnings from matplotlib.pyplot import imread @@ -43,7 +42,7 @@ writes = False -def file_reader(filename: Union[str, Path], lazy: bool = False) -> List[dict]: +def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: """Reader electron backscatter patterns from .bmp files stored in a NORDIF project directory, their filenames listed in a text file. @@ -116,7 +115,7 @@ def file_reader(filename: Union[str, Path], lazy: bool = False) -> List[dict]: return [scan] -def _get_patterns(dirname: str, coordinates: List[Tuple[int]]) -> np.ndarray: +def _get_patterns(dirname: str, coordinates: list[tuple[int, int]]) -> np.ndarray: patterns = [] for y, x in coordinates: fname_pattern = f"Calibration ({x},{y}).bmp" diff --git a/src/kikuchipy/io/plugins/oxford_binary.py b/src/kikuchipy/io/plugins/oxford_binary.py index fdd8d149..2bf4c49b 100644 --- a/src/kikuchipy/io/plugins/oxford_binary.py +++ b/src/kikuchipy/io/plugins/oxford_binary.py @@ -26,7 +26,7 @@ import os from pathlib import Path import struct -from typing import BinaryIO, List, Tuple, Union +from typing import BinaryIO import dask import dask.array as da @@ -36,7 +36,6 @@ __all__ = ["file_reader"] - _logger = logging.getLogger(__name__) # Plugin characteristics @@ -51,7 +50,7 @@ writes = False -def file_reader(filename: Union[str, Path], lazy: bool = False) -> List[dict]: +def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: """Read EBSD patterns from an Oxford Instruments' binary .ebsp file. Only uncompressed patterns can be read. If only non-indexed patterns @@ -111,7 +110,7 @@ class OxfordBinaryFileReader: ("n_bytes", np.int32, (1,)), ] - def __init__(self, file: BinaryIO): + def __init__(self, file: BinaryIO) -> None: """Prepare to read EBSD patterns from an open Oxford Instruments' binary .ebsp file. """ @@ -177,7 +176,7 @@ def all_patterns_present(self) -> bool: """Whether all or only non-indexed patterns are stored in the file. """ - return self.pattern_is_present.all() + return bool(self.pattern_is_present.all()) @property def data_shape(self) -> tuple: @@ -269,7 +268,7 @@ def get_memmap(self) -> np.memmap: offset=self.first_pattern_position, ) - def get_navigation_shape_and_step_size(self) -> Tuple[int, int, int]: + def get_navigation_shape_and_step_size(self) -> tuple[int, int, int]: """Return the navigation shape and step size. An equal step size between rows and columns is assumed. @@ -322,7 +321,7 @@ def get_pattern_starts(self) -> np.ndarray: self.file.seek(self.pattern_starts_byte_position) return np.fromfile(self.file, dtype=np.int64, count=self.n_patterns) - def get_pattern_footer_dtype(self, offset: int) -> List[tuple]: + def get_pattern_footer_dtype(self, offset: int) -> list[tuple]: """Return the pattern footer data types to be used when memory mapping. @@ -359,7 +358,7 @@ def get_pattern_footer_dtype(self, offset: int) -> List[tuple]: self.pattern_footer_size = 0 return list(footer_dtype) - def get_patterns(self, lazy: bool) -> Union[np.ndarray, da.Array]: + def get_patterns(self, lazy: bool) -> np.ndarray | da.Array: """Return the EBSD patterns in the file. The patterns are read from the memory map. They are sorted into @@ -416,7 +415,7 @@ def get_single_pattern_footer(self, offset: int) -> np.ndarray: self.file.seek(offset + self.pattern_header_size + self.n_bytes) return np.fromfile(self.file, dtype=self.pattern_footer_dtype, count=1) - def get_single_pattern_header(self, offset: int) -> Tuple[bool, int, int, int]: + def get_single_pattern_header(self, offset: int) -> tuple[bool, int, int, int]: """Return a single pattern header. Parameters diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd.py b/src/kikuchipy/io/plugins/oxford_h5ebsd.py index daf4242b..1a19500f 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd.py @@ -20,7 +20,6 @@ import logging from pathlib import Path -from typing import List, Union import h5py import numpy as np @@ -68,7 +67,7 @@ class OxfordH5EBSDReader(H5EBSDReader): Keyword arguments passed to :class:`h5py.File`. """ - def __init__(self, filename: str, **kwargs): + def __init__(self, filename: str | Path, **kwargs) -> None: super().__init__(filename, **kwargs) def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: @@ -173,11 +172,11 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: def file_reader( - filename: Union[str, Path], - scan_group_names: Union[None, str, List[str]] = None, + filename: str | Path, + scan_group_names: str | list[str] | None = None, lazy: bool = False, **kwargs, -) -> List[dict]: +) -> list[dict]: """Read electron backscatter diffraction patterns, a crystal map, and an EBSD detector from an Oxford Instruments h5ebsd (H5OINA) file :cite:`jackson2014h5ebsd`. diff --git a/src/kikuchipy/logging.py b/src/kikuchipy/logging.py index 90715254..5e3bb37e 100644 --- a/src/kikuchipy/logging.py +++ b/src/kikuchipy/logging.py @@ -16,10 +16,9 @@ # along with kikuchipy. If not, see . import logging -from typing import Union -def set_log_level(level: Union[int, str]): # pragma: no cover +def set_log_level(level: int | str): # pragma: no cover """Set level of kikuchipy logging messages. Parameters diff --git a/src/kikuchipy/pattern/_pattern.py b/src/kikuchipy/pattern/_pattern.py index d51eb1ec..515aa42c 100644 --- a/src/kikuchipy/pattern/_pattern.py +++ b/src/kikuchipy/pattern/_pattern.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable from numba import njit import numpy as np @@ -30,10 +30,10 @@ def rescale_intensity( pattern: np.ndarray, - in_range: Optional[Tuple[Union[int, float], ...]] = None, - out_range: Optional[Tuple[Union[int, float], ...]] = None, - dtype_out: Union[str, np.dtype, type, None] = None, - percentiles: Union[None, Tuple[int, int], Tuple[float, float]] = None, + in_range: tuple[int | float, ...] | None = None, + out_range: tuple[int | float, ...] | None = None, + dtype_out: str | np.dtype | type | None = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, ) -> np.ndarray: """Rescale intensities in an EBSD pattern. @@ -96,10 +96,10 @@ def rescale_intensity( @njit(cache=True, nogil=True, fastmath=True) def _rescale_with_min_max( pattern: np.ndarray, - imin: Union[int, float], - imax: Union[int, float], - omin: Union[int, float], - omax: Union[int, float], + imin: int | float, + imax: int | float, + omin: int | float, + omax: int | float, ) -> np.ndarray: """Rescale a pattern to a certain intensity range. @@ -134,17 +134,17 @@ def _rescale_without_min_max_1d_float32(pattern: np.ndarray) -> np.ndarray: @njit("Tuple((float32[:], float32))(float32[:])", cache=True, nogil=True, fastmath=True) -def _zero_mean_sum_square_1d_float32(pattern: np.ndarray) -> Tuple[np.ndarray, float]: +def _zero_mean_sum_square_1d_float32(pattern: np.ndarray) -> tuple[np.ndarray, float]: pattern -= np.mean(pattern) return pattern, np.square(pattern).sum() -def _zero_mean(patterns: np.ndarray, axis: Union[int, tuple]) -> np.ndarray: +def _zero_mean(patterns: np.ndarray, axis: int | tuple[int, ...]) -> np.ndarray: patterns_mean = np.nanmean(patterns, axis=axis, keepdims=True) return patterns - patterns_mean -def _normalize(patterns: np.ndarray, axis: Union[int, tuple]) -> np.ndarray: +def _normalize(patterns: np.ndarray, axis: int | tuple[int, ...]) -> np.ndarray: patterns_squared = patterns**2 patterns_norm = np.nansum(patterns_squared, axis=axis, keepdims=True) patterns_norm_squared = patterns_norm**0.5 @@ -155,7 +155,7 @@ def normalize_intensity( pattern: np.ndarray, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[type, None] = None, + dtype_out: type | None = None, ) -> np.ndarray: """Normalize image intensities to a mean of zero and a given standard deviation. @@ -212,7 +212,7 @@ def _normalize_intensity( def fft( pattern: np.ndarray, - apodization_window: Union[None, np.ndarray, Window] = None, + apodization_window: np.ndarray | Window | None = None, shift: bool = False, real_fft_only: bool = False, **kwargs, @@ -311,8 +311,8 @@ def ifft( def fft_filter( pattern: np.ndarray, - transfer_function: Union[np.ndarray, Window], - apodization_window: Union[None, np.ndarray, Window] = None, + transfer_function: np.ndarray | Window, + apodization_window: np.ndarray | Window | None = None, shift: bool = False, ) -> np.ndarray: """Filter an EBSD patterns in the frequency domain. @@ -362,7 +362,7 @@ def fft_spectrum(fft_pattern: np.ndarray) -> np.ndarray: return np.sqrt(fft_pattern.real**2 + fft_pattern.imag**2) -def fft_frequency_vectors(shape: Tuple[int, int]) -> np.ndarray: +def fft_frequency_vectors(shape: tuple[int, int]) -> np.ndarray: """Get the frequency vectors in a Fourier Transform spectrum. Parameters @@ -394,8 +394,8 @@ def _remove_static_background_subtract( pattern: np.ndarray, static_bg: np.ndarray, dtype_out: np.dtype, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, scale_bg: bool, ) -> np.ndarray: """Remove static background from a pattern by subtraction.""" @@ -417,8 +417,8 @@ def _remove_static_background_divide( pattern: np.ndarray, static_bg: np.ndarray, dtype_out: np.dtype, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, scale_bg: bool, ) -> np.ndarray: """Remove static background from a pattern by division.""" @@ -440,8 +440,8 @@ def _remove_dynamic_background( filter_func: Callable, operation: str, dtype_out: np.dtype, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, **kwargs, ) -> np.ndarray: """Remove dynamic background from a pattern. @@ -485,8 +485,8 @@ def _remove_dynamic_background( def _remove_background_subtract( pattern: np.ndarray, background: np.ndarray, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, ) -> np.ndarray: """Remove background from pattern by subtraction and rescale.""" pattern -= background @@ -499,8 +499,8 @@ def _remove_background_subtract( def _remove_background_divide( pattern: np.ndarray, background: np.ndarray, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, ) -> np.ndarray: """Remove background from pattern by division and rescale.""" pattern /= background @@ -513,11 +513,11 @@ def remove_dynamic_background( pattern: np.ndarray, operation: str = "subtract", filter_domain: str = "frequency", - std: Union[None, int, float] = None, - truncate: Union[int, float] = 4.0, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, + std: int | float | None = None, + truncate: int | float = 4.0, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, ) -> np.ndarray: """Remove the dynamic background in an EBSD pattern. @@ -602,11 +602,11 @@ def remove_dynamic_background( def _dynamic_background_frequency_space_setup( - pattern_shape: Union[List[int], Tuple[int, int]], - std: Union[int, float], - truncate: Union[int, float], -) -> Tuple[ - Tuple[int, int], Tuple[int, int], np.ndarray, Tuple[int, int], Tuple[int, int] + pattern_shape: list[int] | tuple[int, int], + std: int | float, + truncate: int | float, +) -> tuple[ + tuple[int, int], tuple[int, int], np.ndarray, tuple[int, int], tuple[int, int] ]: # Get Gaussian filtering window shape = (int(truncate * std),) * 2 @@ -634,8 +634,8 @@ def _dynamic_background_frequency_space_setup( def get_dynamic_background( pattern: np.ndarray, filter_domain: str = "frequency", - std: Union[None, int, float] = None, - truncate: Union[int, float] = 4.0, + std: int | float | None = None, + truncate: int | float = 4.0, ) -> np.ndarray: """Get the dynamic background in an EBSD pattern. @@ -698,8 +698,8 @@ def get_dynamic_background( def get_image_quality( pattern: np.ndarray, normalize: bool = True, - frequency_vectors: Optional[np.ndarray] = None, - inertia_max: Union[None, int, float] = None, + frequency_vectors: np.ndarray | None = None, + inertia_max: int | float | None = None, ) -> float: """Return the image quality of an EBSD pattern. @@ -795,8 +795,8 @@ def _bin2d(pattern: np.ndarray, factor: int) -> np.ndarray: def _downsample2d( pattern: np.ndarray, factor: int, - omin: Union[int, float], - omax: Union[int, float], + omin: int | float, + omax: int | float, dtype_out: np.dtype, ) -> np.ndarray: pattern = pattern.astype(np.float32) @@ -809,8 +809,8 @@ def _downsample2d( def _adaptive_histogram_equalization( image: np.ndarray, - kernel_size: Union[Tuple[int, int], List[int]], - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int], + clip_limit: int | float = 0, nbins: int = 128, ) -> np.ndarray: """Local contrast enhancement with adaptive histogram equalization. diff --git a/src/kikuchipy/pattern/chunk.py b/src/kikuchipy/pattern/chunk.py index 5c89be2d..6aa88f1f 100644 --- a/src/kikuchipy/pattern/chunk.py +++ b/src/kikuchipy/pattern/chunk.py @@ -19,24 +19,21 @@ :class:`dask.array.Array` chunks of EBSD patterns. """ -from typing import Union +from typing import Callable import dask.array as da from numba import njit import numpy as np -from scipy.ndimage import correlate, gaussian_filter +from scipy.ndimage import correlate -from kikuchipy.filters.fft_barnes import fft_filter as fft_filter_barnes from kikuchipy.filters.window import Window -from kikuchipy.pattern._pattern import _rescale_with_min_max -from kikuchipy.pattern._pattern import fft_filter as fft_filter_pattern -from kikuchipy.pattern._pattern import rescale_intensity +from kikuchipy.pattern._pattern import _rescale_with_min_max, rescale_intensity def get_dynamic_background( - patterns: Union[np.ndarray, da.Array], - filter_func: Union[gaussian_filter, fft_filter_barnes], - dtype_out: Union[str, np.dtype, type, None] = None, + patterns: np.ndarray | da.Array, + filter_func: Callable, + dtype_out: str | np.dtype | type | None = None, **kwargs, ) -> np.ndarray: """Obtain the dynamic background in a chunk of EBSD patterns. @@ -77,9 +74,9 @@ def get_dynamic_background( def fft_filter( patterns: np.ndarray, - filter_func: Union[fft_filter_pattern, fft_filter_barnes], - transfer_function: Union[np.ndarray, Window], - dtype_out: Union[str, np.dtype, type, None] = None, + filter_func: Callable, + transfer_function: np.ndarray | Window, + dtype_out: str | np.dtype | type | None = None, **kwargs, ) -> np.ndarray: """Filter a chunk of EBSD patterns in the frequency domain. @@ -133,7 +130,7 @@ def fft_filter( def _average_neighbour_patterns( patterns: np.ndarray, window_sums: np.ndarray, - window: Union[np.ndarray, Window], + window: np.ndarray | Window, dtype_out: np.dtype, omin: float, omax: float, diff --git a/src/kikuchipy/signals/_kikuchi_master_pattern.py b/src/kikuchipy/signals/_kikuchi_master_pattern.py index 72c86243..ad3b5e49 100644 --- a/src/kikuchipy/signals/_kikuchi_master_pattern.py +++ b/src/kikuchipy/signals/_kikuchi_master_pattern.py @@ -17,7 +17,7 @@ from copy import deepcopy import logging -from typing import TYPE_CHECKING, Any, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any from warnings import warn import hyperspy.api as hs @@ -64,7 +64,7 @@ class KikuchiMasterPattern(KikuchipySignal2D, hs.signals.Signal2D): _custom_attributes = ["hemisphere", "phase", "projection"] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._hemisphere = kwargs.get("hemisphere") self._phase = kwargs.get("phase", Phase()) @@ -88,7 +88,7 @@ def hemisphere(self) -> str: return self._hemisphere @hemisphere.setter - def hemisphere(self, value: str): + def hemisphere(self, value: str) -> None: self._hemisphere = value @property @@ -104,7 +104,7 @@ def phase(self) -> Phase: return self._phase @phase.setter - def phase(self, value: Phase): + def phase(self, value: Phase) -> None: self._phase = value @property @@ -120,10 +120,10 @@ def projection(self) -> str: return self._projection @projection.setter - def projection(self, value: str): + def projection(self, value: str) -> None: self._projection = value - def as_lambert(self, show_progressbar: Optional[bool] = None) -> Any: + def as_lambert(self, show_progressbar: bool | None = None) -> Any: """Return a new master pattern in the Lambert projection :cite:`callahan2013dynamical`. @@ -205,12 +205,12 @@ def as_lambert(self, show_progressbar: Optional[bool] = None) -> Any: def plot_spherical( self, - energy: Union[int, float, None] = None, + energy: int | float | None = None, return_figure: bool = False, style: str = "surface", - plotter_kwargs: Union[dict] = None, - show_kwargs: Union[dict] = None, - ) -> "Plotter": + plotter_kwargs: dict | None = None, + show_kwargs: dict | None = None, + ) -> "Plotter | None": """Plot the master pattern sphere. This requires the master pattern to be in the stereographic @@ -310,8 +310,8 @@ def plot_spherical( pl.show(**show_kwargs) def _get_master_pattern_arrays_from_energy( - self, energy: Union[int, float, None] = None - ) -> Tuple[np.ndarray, np.ndarray]: + self, energy: int | float | None = None + ) -> tuple[np.ndarray, np.ndarray]: """Return upper and lower master patterns created with a single, given energy. diff --git a/src/kikuchipy/signals/_kikuchipy_signal.py b/src/kikuchipy/signals/_kikuchipy_signal.py index fdebcbce..a8c234fd 100644 --- a/src/kikuchipy/signals/_kikuchipy_signal.py +++ b/src/kikuchipy/signals/_kikuchipy_signal.py @@ -20,7 +20,7 @@ import logging import numbers import os -from typing import Any, List, Optional, Tuple, Union +from typing import Any import warnings import dask.array as da @@ -78,16 +78,16 @@ def _navigation_shape_rc(self) -> tuple: def rescale_intensity( self, relative: bool = False, - in_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - out_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, - percentiles: Union[Tuple[int, int], Tuple[float, float], None] = None, - show_progressbar: Optional[bool] = None, + in_range: tuple[int, int] | tuple[float, float] | None = None, + out_range: tuple[int, int] | tuple[float, float] | None = None, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, Any]: + lazy_output: bool | None = None, + ) -> Any | None: """Rescale image intensities. Output min./max. intensity is determined from ``out_range`` or @@ -237,11 +237,11 @@ def normalize_intensity( self, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, Any]: + lazy_output: bool | None = None, + ) -> Any | None: """Normalize image intensities to a mean of zero with a given standard deviation. @@ -329,13 +329,13 @@ def normalize_intensity( def adaptive_histogram_equalization( self, - kernel_size: Optional[Union[Tuple[int, int], List[int]]] = None, - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int] | None = None, + clip_limit: int | float = 0.0, nbins: int = 128, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, Any]: + lazy_output: bool | None = None, + ) -> Any | None: """Enhance the local contrast using adaptive histogram equalization. @@ -504,7 +504,7 @@ def _set_custom_attributes( make_deepcopy: bool = False, make_lazy: bool = False, unmake_lazy: bool = False, - ): + ) -> None: """Set custom attributes not in ``Signal2D``. This is a quick way to set all custom attributes of a class @@ -578,7 +578,7 @@ def deepcopy(self) -> Any: return s_new - def _assign_subclass(self): + def _assign_subclass(self) -> None: attrs = self._custom_attributes super()._assign_subclass() diff --git a/src/kikuchipy/signals/ebsd.py b/src/kikuchipy/signals/ebsd.py index d0a9da9b..b59387dc 100644 --- a/src/kikuchipy/signals/ebsd.py +++ b/src/kikuchipy/signals/ebsd.py @@ -22,7 +22,7 @@ import gc import logging import os -from typing import Iterable, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Iterable import warnings import dask @@ -102,6 +102,11 @@ from kikuchipy.signals.util.array_tools import grid_indices from kikuchipy.signals.virtual_bse_image import VirtualBSEImage +if TYPE_CHECKING: # pragma: no cover + from pyebsdindex.ebsd_index import EBSDIndexer + + from kikuchipy.signals.ebsd_master_pattern import EBSDMasterPattern + _logger = logging.getLogger(__name__) @@ -171,7 +176,7 @@ class EBSD(KikuchipySignal2D): _alias_signal_types = ["electron_backscatter_diffraction"] _custom_attributes = ["detector", "static_background", "xmap"] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._detector = kwargs.get( @@ -199,7 +204,7 @@ def detector(self) -> EBSDDetector: return self._detector @detector.setter - def detector(self, value: EBSDDetector): + def detector(self, value: EBSDDetector) -> None: if _detector_is_compatible_with_signal( detector=value, nav_shape=self._navigation_shape_rc, @@ -209,7 +214,7 @@ def detector(self, value: EBSDDetector): self._detector = value @property - def xmap(self) -> Union[CrystalMap, None]: + def xmap(self) -> CrystalMap | None: """Return or set the crystal map containing the phases, unit cell rotations and auxiliary properties of the EBSD dataset. @@ -222,14 +227,14 @@ def xmap(self) -> Union[CrystalMap, None]: return self._xmap @xmap.setter - def xmap(self, value: CrystalMap): + def xmap(self, value: CrystalMap) -> None: if _xmap_is_compatible_with_signal( value, self.axes_manager.navigation_axes[::-1], raise_if_not=True ): self._xmap = value @property - def static_background(self) -> Union[np.ndarray, da.Array, None]: + def static_background(self) -> np.ndarray | da.Array | None: """Return or set the static background pattern. Parameters @@ -241,7 +246,7 @@ def static_background(self) -> Union[np.ndarray, da.Array, None]: return self._static_background @static_background.setter - def static_background(self, value: Union[np.ndarray, da.Array]): + def static_background(self, value: np.ndarray | da.Array) -> None: if value.dtype != self.data.dtype: warnings.warn("Background pattern has different data type from patterns") if value.shape != self._signal_shape_rc: @@ -251,8 +256,8 @@ def static_background(self, value: Union[np.ndarray, da.Array]): # ------------------------ Custom methods ------------------------ # def extract_grid( - self, grid_shape: Union[Tuple[int, int], int], return_indices: bool = False - ) -> Union[Union[EBSD, LazyEBSD], Tuple[Union[EBSD, LazyEBSD], np.ndarray]]: + self, grid_shape: tuple[int, int] | int, return_indices: bool = False + ) -> EBSD | LazyEBSD | tuple[EBSD | LazyEBSD, np.ndarray]: """Return a new signal with patterns from positions in a grid of shape ``grid_shape`` evenly spaced in navigation space. @@ -364,7 +369,7 @@ def extract_grid( return out def set_scan_calibration( - self, step_x: Union[int, float] = 1.0, step_y: Union[int, float] = 1.0 + self, step_x: int | float = 1.0, step_y: int | float = 1.0 ) -> None: """Set the step size in microns. @@ -394,7 +399,7 @@ def set_scan_calibration( x.scale, y.scale = (step_x, step_y) x.units, y.units = ["um"] * 2 - def set_detector_calibration(self, delta: Union[int, float]) -> None: + def set_detector_calibration(self, delta: int | float) -> None: """Set detector pixel size in microns. The offset is set to the the detector center. @@ -426,12 +431,12 @@ def set_detector_calibration(self, delta: Union[int, float]) -> None: def remove_static_background( self, operation: str = "subtract", - static_bg: Union[np.ndarray, da.Array, None] = None, + static_bg: np.ndarray | da.Array | None = None, scale_bg: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: """Remove the static background. The removal is performed by subtracting or dividing by a static @@ -561,13 +566,13 @@ def remove_dynamic_background( self, operation: str = "subtract", filter_domain: str = "frequency", - std: Union[int, float, None] = None, - truncate: Union[int, float] = 4.0, - show_progressbar: Optional[bool] = None, + std: int | float | None = None, + truncate: int | float = 4.0, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, + lazy_output: bool | None = None, **kwargs, - ) -> Union[None, EBSD, LazyEBSD]: + ) -> EBSD | LazyEBSD | None: """Remove the dynamic background. The removal is performed by subtracting or dividing by a @@ -684,13 +689,13 @@ def remove_dynamic_background( def get_dynamic_background( self, filter_domain: str = "frequency", - std: Union[int, float, None] = None, - truncate: Union[int, float] = 4.0, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, - lazy_output: Optional[bool] = None, + std: int | float | None = None, + truncate: int | float = 4.0, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, + lazy_output: bool | None = None, **kwargs, - ) -> Union[EBSD, LazyEBSD]: + ) -> EBSD | LazyEBSD: """Return the dynamic background per pattern in a new signal. Parameters @@ -790,13 +795,13 @@ def get_dynamic_background( def fft_filter( self, - transfer_function: Union[np.ndarray, Window], + transfer_function: np.ndarray | Window, function_domain: str, shift: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: """Filter patterns in the frequency domain. Patterns are transformed via the Fast Fourier Transform (FFT) to @@ -928,13 +933,13 @@ def fft_filter( def average_neighbour_patterns( self, - window: Union[str, np.ndarray, da.Array, Window] = "circular", - window_shape: Tuple[int, ...] = (3, 3), - show_progressbar: Optional[bool] = None, + window: str | np.ndarray | da.Array | Window = "circular", + window_shape: tuple[int, ...] = (3, 3), + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, + lazy_output: bool | None = None, **kwargs, - ) -> Union[None, EBSD, LazyEBSD]: + ) -> EBSD | LazyEBSD | None: """Average patterns with its neighbours within a window. The amount of averaging is specified by the window coefficients. @@ -1099,11 +1104,11 @@ def average_neighbour_patterns( def downsample( self, factor: int, - dtype_out: Optional[str] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: r"""Downsample the pattern shape by an integer factor and rescale intensities to fill the data type range. @@ -1205,12 +1210,12 @@ def downsample( def get_neighbour_dot_product_matrices( self, - window: Optional[Window] = None, + window: Window | None = None, zero_mean: bool = True, normalize: bool = True, - dtype_out: Union[str, np.dtype, type] = "float32", - show_progressbar: Optional[bool] = None, - ) -> Union[np.ndarray, da.Array]: + dtype_out: str | np.dtype | type = "float32", + show_progressbar: bool | None = None, + ) -> np.ndarray | da.Array: """Get an array with dot products of a pattern and its neighbours within a window. @@ -1297,8 +1302,8 @@ def get_neighbour_dot_product_matrices( def get_image_quality( self, normalize: bool = True, - show_progressbar: Optional[bool] = None, - ) -> Union[np.ndarray, da.Array]: + show_progressbar: bool | None = None, + ) -> np.ndarray | da.Array: """Compute the image quality map of patterns in an EBSD scan. The image quality :math:`Q` is calculated based on the procedure @@ -1362,13 +1367,13 @@ def get_image_quality( def get_average_neighbour_dot_product_map( self, - window: Optional[Window] = None, + window: Window | None = None, zero_mean: bool = True, normalize: bool = True, - dtype_out: Union[str, np.dtype, type] = "float32", - dp_matrices: Optional[np.ndarray] = None, - show_progressbar: Optional[bool] = None, - ) -> Union[np.ndarray, da.Array]: + dtype_out: str | np.dtype | type = "float32", + dp_matrices: np.ndarray | None = None, + show_progressbar: bool | None = None, + ) -> np.ndarray | da.Array: """Get a map of the average dot product between patterns and their neighbours within an averaging window. @@ -1479,7 +1484,7 @@ def get_average_neighbour_dot_product_map( def plot_virtual_bse_intensity( self, roi: BaseInteractiveROI, - out_signal_axes: Union[Iterable[int], Iterable[str], None] = None, + out_signal_axes: Iterable[int] | Iterable[str] | None = None, **kwargs, ) -> None: """Plot an interactive virtual backscatter electron (VBSE) @@ -1541,7 +1546,7 @@ def plot_virtual_bse_intensity( def get_virtual_bse_intensity( self, roi: BaseInteractiveROI, - out_signal_axes: Union[Iterable[int], Iterable[str], None] = None, + out_signal_axes: Iterable[int] | Iterable[str] | None = None, ) -> VirtualBSEImage: """Get a virtual backscatter electron (VBSE) image formed from intensities within a region of interest (ROI) on the detector. @@ -1591,11 +1596,11 @@ def hough_indexing( verbose: int = 1, return_index_data: bool = False, return_band_data: bool = False, - ) -> Union[ - CrystalMap, - Tuple[CrystalMap, np.ndarray], - Tuple[CrystalMap, np.ndarray, np.ndarray], - ]: + ) -> ( + CrystalMap + | tuple[CrystalMap, np.ndarray] + | tuple[CrystalMap, np.ndarray, np.ndarray] + ): """Index patterns by Hough indexing using :mod:`pyebsdindex`. See :class:`~pyebsdindex.ebsd_index.EBSDIndexer` and @@ -1710,7 +1715,7 @@ def hough_indexing( def hough_indexing_optimize_pc( self, - pc0: Union[list, tuple, np.ndarray], + pc0: np.ndarray | list | tuple, indexer: "EBSDIndexer", batch: bool = False, method: str = "Nelder-Mead", @@ -1819,13 +1824,13 @@ def hough_indexing_optimize_pc( def dictionary_indexing( self, dictionary: EBSD, - metric: Union[SimilarityMetric, str] = "ncc", + metric: SimilarityMetric | str = "ncc", keep_n: int = 20, - n_per_iteration: Optional[int] = None, - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, + n_per_iteration: int | None = None, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, rechunk: bool = False, - dtype: Union[str, np.dtype, type, None] = None, + dtype: str | np.dtype | type | None = None, ) -> CrystalMap: """Index patterns by matching each pattern to a dictionary of simulated patterns of known orientations @@ -1980,20 +1985,20 @@ def refine_orientation( xmap: CrystalMap, detector: EBSDDetector, master_pattern: "EBSDMasterPattern", - energy: Union[int, float], - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, - pseudo_symmetry_ops: Optional[Rotation] = None, - method: Optional[str] = "minimize", - method_kwargs: Optional[dict] = None, - trust_region: Union[tuple, list, np.ndarray, None] = None, - initial_step: Union[float] = None, + energy: int | float, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, + pseudo_symmetry_ops: Rotation | None = None, + method: str | None = "minimize", + method_kwargs: dict | None = None, + trust_region: tuple | list | np.ndarray | None = None, + initial_step: float | None = None, rtol: float = 1e-4, - maxeval: Optional[int] = None, + maxeval: int | None = None, compute: bool = True, rechunk: bool = True, - chunk_kwargs: Optional[dict] = None, - ) -> Union[CrystalMap, da.Array]: + chunk_kwargs: dict | None = None, + ) -> CrystalMap | da.Array: r"""Refine orientations by searching orientation space around the best indexed solution using fixed projection centers. @@ -2181,19 +2186,19 @@ def refine_projection_center( xmap: CrystalMap, detector: EBSDDetector, master_pattern: "EBSDMasterPattern", - energy: Union[int, float], - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, - method: Optional[str] = "minimize", - method_kwargs: Optional[dict] = None, - trust_region: Union[tuple, list, np.ndarray, None] = None, - initial_step: Union[float] = None, + energy: int | float, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, + method: str | None = "minimize", + method_kwargs: dict | None = None, + trust_region: tuple | list | np.ndarray | None = None, + initial_step: float | None = None, rtol: float = 1e-4, - maxeval: Optional[int] = None, + maxeval: int | None = None, compute: bool = True, rechunk: bool = True, - chunk_kwargs: Optional[dict] = None, - ) -> Union[Tuple[np.ndarray, EBSDDetector, np.ndarray], da.Array]: + chunk_kwargs: dict | None = None, + ) -> tuple[np.ndarray, EBSDDetector, np.ndarray] | da.Array: """Refine projection centers by searching the parameter space using fixed orientations. @@ -2370,20 +2375,20 @@ def refine_orientation_projection_center( xmap: CrystalMap, detector: EBSDDetector, master_pattern: "EBSDMasterPattern", - energy: Union[int, float], - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, - pseudo_symmetry_ops: Optional[Rotation] = None, - method: Optional[str] = "minimize", - method_kwargs: Optional[dict] = None, - trust_region: Union[tuple, list, np.ndarray, None] = None, - initial_step: Union[tuple, list, np.ndarray, None] = None, - rtol: Optional[float] = 1e-4, - maxeval: Optional[int] = None, + energy: int | float, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, + pseudo_symmetry_ops: Rotation | None = None, + method: str | None = "minimize", + method_kwargs: dict | None = None, + trust_region: tuple | list | np.ndarray | None = None, + initial_step: tuple | list | np.ndarray | None = None, + rtol: float | None = 1e-4, + maxeval: int | None = None, compute: bool = True, rechunk: bool = True, - chunk_kwargs: Optional[dict] = None, - ) -> Union[Tuple[CrystalMap, EBSDDetector], da.Array]: + chunk_kwargs: dict | None = None, + ) -> tuple[CrystalMap, EBSDDetector] | da.Array: r"""Refine orientations and projection centers simultaneously by searching the orientation and PC parameter space. @@ -2587,9 +2592,9 @@ def refine_orientation_projection_center( def save( self, - filename: Optional[str] = None, - overwrite: Optional[bool] = None, - extension: Optional[str] = None, + filename: str | None = None, + overwrite: bool | None = None, + extension: str | None = None, **kwargs, ) -> None: """Write the signal to file in the specified format. @@ -2647,9 +2652,9 @@ def save( def get_decomposition_model( self, - components: Union[int, List[int], None] = None, - dtype_out: Union[str, np.dtype, type] = "float32", - ) -> Union[EBSD, LazyEBSD]: + components: int | list[int] | None = None, + dtype_out: str | np.dtype | type = "float32", + ) -> EBSD | LazyEBSD: """Get the model signal generated with the selected number of principal components from a decomposition. @@ -2864,8 +2869,8 @@ def _check_refinement_parameters( xmap: CrystalMap, detector: EBSDDetector, master_pattern: "EBSDMasterPattern", - navigation_mask: Optional[np.ndarray] = None, - signal_mask: Optional[np.ndarray] = None, + navigation_mask: np.ndarray | None = None, + signal_mask: np.ndarray | None = None, ) -> np.ndarray: """Check compatibility of refinement parameters with refinement. @@ -2954,10 +2959,10 @@ def _check_refinement_parameters( def _prepare_patterns_for_refinement( self, points_to_refine: np.ndarray, - signal_mask: Union[np.ndarray, None], + signal_mask: np.ndarray | None, rechunk: bool, - chunk_kwargs: Optional[dict] = None, - ) -> Tuple[da.Array, np.ndarray]: + chunk_kwargs: dict | None = None, + ) -> tuple[da.Array, np.ndarray]: """Prepare pattern array and mask for refinement. Parameters @@ -3030,10 +3035,10 @@ def _prepare_patterns_for_refinement( def _prepare_metric( self, - metric: Union[SimilarityMetric, str], - navigation_mask: Union[np.ndarray, None], - signal_mask: Union[np.ndarray, None], - dtype: Union[str, np.dtype, type, None], + metric: SimilarityMetric | str, + navigation_mask: np.ndarray | None, + signal_mask: np.ndarray | None, + dtype: str | np.dtype | type | None, rechunk: bool, n_dictionary_patterns: int, ) -> SimilarityMetric: @@ -3071,7 +3076,7 @@ def _prepare_metric( @staticmethod def _get_sum_signal( - signal, out_signal_axes: Optional[List] = None + signal, out_signal_axes: list | None = None ) -> hs.signals.Signal2D: out = signal.nansum(signal.axes_manager.signal_axes) if out_signal_axes is None: @@ -3093,16 +3098,16 @@ def _get_sum_signal( def rescale_intensity( self, relative: bool = False, - in_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - out_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, - percentiles: Union[Tuple[int, int], Tuple[float, float], None] = None, - show_progressbar: Optional[bool] = None, + in_range: tuple[int, int] | tuple[float, float] | None = None, + out_range: tuple[int, int] | tuple[float, float] | None = None, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: return super().rescale_intensity( relative, in_range, @@ -3118,11 +3123,11 @@ def normalize_intensity( self, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: return super().normalize_intensity( num_std, divide_by_square_root, @@ -3134,13 +3139,13 @@ def normalize_intensity( def adaptive_histogram_equalization( self, - kernel_size: Optional[Union[Tuple[int, int], List[int]]] = None, - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int] | None = None, + clip_limit: int | float = 0.0, nbins: int = 128, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSD, LazyEBSD]: + lazy_output: bool | None = None, + ) -> EBSD | LazyEBSD | None: return super().adaptive_histogram_equalization( kernel_size, clip_limit, @@ -3181,11 +3186,11 @@ def compute(self, *args, **kwargs) -> None: def get_decomposition_model_write( self, - components: Union[int, List[int], None] = None, - dtype_learn: Union[str, np.dtype, type] = "float32", + components: int | list[int] | None = None, + dtype_learn: str | np.dtype | type = "float32", mbytes_chunk: int = 100, - dir_out: Optional[str] = None, - fname_out: Optional[str] = None, + dir_out: str | None = None, + fname_out: str | None = None, ) -> None: """Write the model signal generated from the selected number of principal components directly to an ``.hspy`` file. @@ -3275,10 +3280,10 @@ def get_decomposition_model_write( def _update_custom_attributes( attributes: dict, - nav_slices: Union[slice, tuple, None] = None, - sig_slices: Union[slice, tuple, None] = None, - new_nav_shape: Optional[tuple] = None, - new_sig_shape: Optional[tuple] = None, + nav_slices: slice | tuple | None = None, + sig_slices: slice | tuple | None = None, + new_nav_shape: tuple[int, ...] | None = None, + new_sig_shape: tuple[int, ...] | None = None, ) -> dict: """Update dictionary of custom attributes after slicing the signal data. diff --git a/src/kikuchipy/signals/ebsd_master_pattern.py b/src/kikuchipy/signals/ebsd_master_pattern.py index ac151c0f..4c98b444 100644 --- a/src/kikuchipy/signals/ebsd_master_pattern.py +++ b/src/kikuchipy/signals/ebsd_master_pattern.py @@ -17,7 +17,7 @@ from __future__ import annotations -from typing import List, Optional, Tuple, Union +from typing import TYPE_CHECKING import dask import dask.array as da @@ -39,6 +39,9 @@ _project_patterns_from_master_pattern_with_varying_pc, ) +if TYPE_CHECKING: # pragma: no cover + from pyvista import Plotter + class EBSDMasterPattern(KikuchiMasterPattern): """Simulated Electron Backscatter Diffraction (EBSD) master pattern. @@ -93,12 +96,12 @@ def get_patterns( self, rotations: Rotation, detector: EBSDDetector, - energy: Union[int, float, None] = None, - dtype_out: Union[str, np.dtype, type] = "float32", + energy: int | float | None = None, + dtype_out: str | np.dtype | type = "float32", compute: bool = False, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, **kwargs, - ) -> Union[EBSD, LazyEBSD]: + ) -> EBSD | LazyEBSD: """Return one or more EBSD patterns projected onto a detector from a master pattern in the square Lambert projection for rotation(s) relative to the EDAX TSL sample reference frame (RD, @@ -384,7 +387,7 @@ def hemisphere(self) -> str: return super().hemisphere @hemisphere.setter - def hemisphere(self, value: str): + def hemisphere(self, value: str) -> None: super(EBSDMasterPattern, type(self)).hemisphere.fset(self, value) @property @@ -392,7 +395,7 @@ def phase(self) -> Phase: return super().phase @phase.setter - def phase(self, value: Phase): + def phase(self, value: Phase) -> None: super(EBSDMasterPattern, type(self)).phase.fset(self, value) @property @@ -400,20 +403,20 @@ def projection(self) -> str: return super().projection @projection.setter - def projection(self, value: str): + def projection(self, value: str) -> None: super(EBSDMasterPattern, type(self)).projection.fset(self, value) - def as_lambert(self, show_progressbar: Optional[bool] = None) -> EBSDMasterPattern: + def as_lambert(self, show_progressbar: bool | None = None) -> EBSDMasterPattern: return super().as_lambert(show_progressbar=show_progressbar) def plot_spherical( self, - energy: Union[int, float, None] = None, + energy: int | float | None = None, return_figure: bool = False, style: str = "surface", - plotter_kwargs: Union[dict] = None, - show_kwargs: Union[dict] = None, - ) -> "pyvista.Plotter": + plotter_kwargs: dict | None = None, + show_kwargs: dict | None = None, + ) -> "Plotter | None": return super().plot_spherical( energy=energy, return_figure=return_figure, @@ -430,11 +433,11 @@ def normalize_intensity( self, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSDMasterPattern, LazyEBSDMasterPattern]: + lazy_output: bool | None = None, + ) -> EBSDMasterPattern | LazyEBSDMasterPattern | None: return super().normalize_intensity( num_std=num_std, divide_by_square_root=divide_by_square_root, @@ -447,16 +450,16 @@ def normalize_intensity( def rescale_intensity( self, relative: bool = False, - in_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - out_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, - percentiles: Union[Tuple[int, int], Tuple[float, float], None] = None, - show_progressbar: Optional[bool] = None, + in_range: tuple[int, int] | tuple[float, float] | None = None, + out_range: tuple[int, int] | tuple[float, float] | None = None, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSDMasterPattern, LazyEBSDMasterPattern]: + lazy_output: bool | None = None, + ) -> EBSDMasterPattern | LazyEBSDMasterPattern | None: return super().rescale_intensity( relative=relative, in_range=in_range, @@ -470,13 +473,13 @@ def rescale_intensity( def adaptive_histogram_equalization( self, - kernel_size: Optional[Union[Tuple[int, int], List[int]]] = None, - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int] | None = None, + clip_limit: int | float = 0.0, nbins: int = 128, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, EBSDMasterPattern, LazyEBSDMasterPattern]: + lazy_output: bool | None = None, + ) -> EBSDMasterPattern | LazyEBSDMasterPattern | None: return super().adaptive_histogram_equalization( kernel_size, clip_limit, diff --git a/src/kikuchipy/signals/ecp_master_pattern.py b/src/kikuchipy/signals/ecp_master_pattern.py index 54f8ee25..101fe935 100644 --- a/src/kikuchipy/signals/ecp_master_pattern.py +++ b/src/kikuchipy/signals/ecp_master_pattern.py @@ -17,7 +17,7 @@ from __future__ import annotations -from typing import List, Optional, Tuple, Union +from typing import TYPE_CHECKING import numpy as np from orix.crystal_map import Phase @@ -25,6 +25,9 @@ from kikuchipy.signals._kikuchi_master_pattern import KikuchiMasterPattern from kikuchipy.signals._kikuchipy_signal import LazyKikuchipySignal2D +if TYPE_CHECKING: # pragma: no cover + from pyvista import Plotter + class ECPMasterPattern(KikuchiMasterPattern): """Simulated Electron Channeling Pattern (ECP) master pattern. @@ -70,7 +73,7 @@ def hemisphere(self) -> str: return super().hemisphere @hemisphere.setter - def hemisphere(self, value: str): + def hemisphere(self, value: str) -> None: super(ECPMasterPattern, type(self)).hemisphere.fset(self, value) @property @@ -78,7 +81,7 @@ def phase(self) -> Phase: return super().phase @phase.setter - def phase(self, value: Phase): + def phase(self, value: Phase) -> None: super(ECPMasterPattern, type(self)).phase.fset(self, value) @property @@ -86,20 +89,20 @@ def projection(self) -> str: return super().projection @projection.setter - def projection(self, value: str): + def projection(self, value: str) -> None: super(ECPMasterPattern, type(self)).projection.fset(self, value) - def as_lambert(self, show_progressbar: Optional[bool] = None) -> ECPMasterPattern: + def as_lambert(self, show_progressbar: bool | None = None) -> ECPMasterPattern: return super().as_lambert(show_progressbar=show_progressbar) def plot_spherical( self, - energy: Union[int, float, None] = None, + energy: int | float | None = None, return_figure: bool = False, style: str = "surface", - plotter_kwargs: Union[dict] = None, - show_kwargs: Union[dict] = None, - ) -> "pyvista.Plotter": + plotter_kwargs: dict | None = None, + show_kwargs: dict | None = None, + ) -> "Plotter | None": return super().plot_spherical( energy=energy, return_figure=return_figure, @@ -116,11 +119,11 @@ def normalize_intensity( self, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, ECPMasterPattern, LazyECPMasterPattern]: + lazy_output: bool | None = None, + ) -> ECPMasterPattern | LazyECPMasterPattern | None: return super().normalize_intensity( num_std=num_std, divide_by_square_root=divide_by_square_root, @@ -133,16 +136,16 @@ def normalize_intensity( def rescale_intensity( self, relative: bool = False, - in_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - out_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, - percentiles: Union[Tuple[int, int], Tuple[float, float], None] = None, - show_progressbar: Optional[bool] = None, + in_range: tuple[int, int] | tuple[float, float] | None = None, + out_range: tuple[int, int] | tuple[float, float] | None = None, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, ECPMasterPattern, LazyECPMasterPattern]: + lazy_output: bool | None = None, + ) -> ECPMasterPattern | LazyECPMasterPattern | None: return super().rescale_intensity( relative=relative, in_range=in_range, @@ -156,13 +159,13 @@ def rescale_intensity( def adaptive_histogram_equalization( self, - kernel_size: Optional[Union[Tuple[int, int], List[int]]] = None, - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int] | None = None, + clip_limit: int | float = 0.0, nbins: int = 128, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, ECPMasterPattern, LazyECPMasterPattern]: + lazy_output: bool | None = None, + ) -> ECPMasterPattern | LazyECPMasterPattern | None: return super().adaptive_histogram_equalization( kernel_size, clip_limit, diff --git a/src/kikuchipy/signals/util/_crystal_map.py b/src/kikuchipy/signals/util/_crystal_map.py index a9538804..92cd307d 100644 --- a/src/kikuchipy/signals/util/_crystal_map.py +++ b/src/kikuchipy/signals/util/_crystal_map.py @@ -19,7 +19,6 @@ and an :class:`~kikuchipy.signals.EBSD` signal. """ -from typing import Optional, Tuple, Union import warnings import numpy as np @@ -62,7 +61,7 @@ def _xmap_is_compatible_with_signal( # TODO: Move to orix' Phase.__eq__ -def _equal_phase(phase1: Phase, phase2: Phase) -> Tuple[bool, Union[str, None]]: +def _equal_phase(phase1: Phase, phase2: Phase) -> tuple[bool, str | None]: if phase1.name != phase2.name: return False, "names" @@ -110,8 +109,8 @@ def _equal_phase(phase1: Phase, phase2: Phase) -> Tuple[bool, Union[str, None]]: def _get_indexed_points_in_data_in_xmap( xmap: CrystalMap, - navigation_mask: Optional[np.ndarray] = None, -) -> Tuple[np.ndarray, np.ndarray, int, Union[Tuple[int], Tuple[int, int], None]]: + navigation_mask: np.ndarray | None = None, +) -> tuple[np.ndarray, np.ndarray, int, tuple[int] | tuple[int, int] | None]: in_data = xmap.is_in_data.copy() if navigation_mask is not None: diff --git a/src/kikuchipy/signals/util/_dask.py b/src/kikuchipy/signals/util/_dask.py index 6bb3d157..bc2b5f70 100644 --- a/src/kikuchipy/signals/util/_dask.py +++ b/src/kikuchipy/signals/util/_dask.py @@ -16,7 +16,7 @@ # along with kikuchipy. If not, see . import logging -from typing import TYPE_CHECKING, List, Optional, Tuple, Union +from typing import TYPE_CHECKING import dask.array as da import numpy as np @@ -28,13 +28,13 @@ def get_chunking( - signal: Optional[Union["EBSD", "LazyEBSD"]] = None, - data_shape: Optional[tuple] = None, - nav_dim: Optional[int] = None, - sig_dim: Optional[int] = None, - chunk_shape: Optional[int] = None, - chunk_bytes: Union[int, float, str, None] = 30e6, - dtype: Union[str, np.dtype, type, None] = None, + signal: "EBSD | LazyEBSD | None" = None, + data_shape: tuple[int, ...] | None = None, + nav_dim: int | None = None, + sig_dim: int | None = None, + chunk_shape: int | None = None, + chunk_bytes: int | float | str | None = 30e6, + dtype: str | np.dtype | type | None = None, ) -> tuple: """Get a chunk tuple based on the shape of the signal data. @@ -109,9 +109,7 @@ def get_chunking( def get_dask_array( - signal: Union["EBSD", "LazyEBSD"], - dtype: Union[str, np.dtype, type, None] = None, - **kwargs, + signal: "EBSD |LazyEBSD", dtype: str | np.dtype | type | None = None, **kwargs ) -> da.Array: """Return dask array of patterns with appropriate chunking. @@ -161,8 +159,8 @@ def get_dask_array( def _reduce_chunks( dask_array: da.Array, - chunk_bytes: Union[int, float] = 8e6, - dtype_out: Union[str, np.dtype, type] = "float32", + chunk_bytes: int | float = 8e6, + dtype_out: str | np.dtype | type = "float32", ) -> tuple: dtype_out = np.dtype(dtype_out) @@ -216,9 +214,9 @@ def _get_chunk_overlap_depth(window, axes_manager, chunksize: tuple) -> dict: def _rechunk_learning_results( - factors: Union[np.ndarray, da.Array], - loadings: Union[np.ndarray, da.Array], - mbytes_chunk: Union[int, float] = 100, + factors: np.ndarray | da.Array, + loadings: np.ndarray | da.Array, + mbytes_chunk: int | float = 100, ) -> list: """Return suggested data chunks for learning results. @@ -281,9 +279,9 @@ def _rechunk_learning_results( def _update_learning_results( learning_results, - components: Union[None, int, List[int]], - dtype_out: Union[str, np.dtype, type], -) -> Tuple[Union[np.ndarray, da.Array], Union[np.ndarray, da.Array]]: + components: int | list[int] | None, + dtype_out: str | np.dtype | type, +) -> tuple[np.ndarray | da.Array, np.ndarray | da.Array]: """Update learning results before calling :meth:`hyperspy.learn.mva.MVA.get_decomposition_model` by changing data type, keeping only desired components and rechunking diff --git a/src/kikuchipy/signals/util/_map_helper.py b/src/kikuchipy/signals/util/_map_helper.py index dc339e21..54e0d81f 100644 --- a/src/kikuchipy/signals/util/_map_helper.py +++ b/src/kikuchipy/signals/util/_map_helper.py @@ -19,7 +19,7 @@ and their neighbours in a map of a 1D or 2D navigation shape. """ -from typing import Callable, Optional, Tuple, Union +from typing import Callable import numpy as np from scipy.ndimage import generic_filter @@ -35,9 +35,9 @@ def _map_helper( patterns: np.ndarray, map_function: Callable, - window: Union[np.ndarray, Window], + window: np.ndarray | Window, nav_shape: tuple, - dtype_out: np.dtype = np.float32, + dtype_out: np.dtype | type = np.float32, **kwargs, ) -> np.ndarray: """Return output of :func:`scipy.ndimage.generic_filter` after @@ -102,9 +102,9 @@ def _neighbour_dot_products( center_index: int, zero_mean: bool, normalize: bool, - flat_window_truthy_indices: Optional[np.ndarray] = None, - output: Optional[np.ndarray] = None, -) -> Union[float, int, np.ndarray]: + flat_window_truthy_indices: np.ndarray | None = None, + output: np.ndarray | None = None, +) -> float | int | np.ndarray: """Return either an average of a dot product matrix between a pattern and it's neighbours, or the matrix. @@ -308,7 +308,7 @@ def _get_average_dot_product_map( return adp -def _setup_window_indices(window: Window) -> Tuple[np.ndarray, np.ndarray, int]: +def _setup_window_indices(window: Window) -> tuple[np.ndarray, np.ndarray, int]: # Index of window origin in flattened window flat_window_origin = np.ravel_multi_index(window.origin, window.shape) diff --git a/src/kikuchipy/signals/util/_master_pattern.py b/src/kikuchipy/signals/util/_master_pattern.py index a6b2515c..d1445a4f 100644 --- a/src/kikuchipy/signals/util/_master_pattern.py +++ b/src/kikuchipy/signals/util/_master_pattern.py @@ -58,7 +58,7 @@ patterns into a detector. """ -from typing import TYPE_CHECKING, Optional, Tuple, Union +from typing import TYPE_CHECKING import numba as nb from numba import njit @@ -79,7 +79,7 @@ def _get_direction_cosines_from_detector( - detector: "EBSDDetector", signal_mask: Optional[np.ndarray] = None + detector: "EBSDDetector", signal_mask: np.ndarray | None = None ) -> np.ndarray: """Return direction cosines for one or more projection centers (PCs). @@ -131,7 +131,7 @@ def _get_direction_cosines_from_detector( ) def _get_cosine_sine_of_alpha_and_azimuthal( sample_tilt: float, tilt: float, azimuthal: float -) -> Tuple[float, float, float, float]: +) -> tuple[float, float, float, float]: alpha = (np.pi / 2) - np.deg2rad(sample_tilt) + np.deg2rad(tilt) azimuthal = np.deg2rad(azimuthal) return np.cos(alpha), np.sin(alpha), np.cos(azimuthal), np.sin(azimuthal) @@ -352,9 +352,9 @@ def _project_patterns_from_master_pattern_with_fixed_pc( npy: int, scale: float, rescale: bool, - out_min: Union[int, float], - out_max: Union[int, float], - dtype_out: Optional[type] = np.float32, + out_min: int | float, + out_max: int | float, + dtype_out: np.dtype | type | None = np.float32, ) -> np.ndarray: """Return one or more simulated EBSD patterns projected from a master pattern with a fixed projection center (PC). @@ -427,9 +427,9 @@ def _project_patterns_from_master_pattern_with_varying_pc( npy: int, scale: float, rescale: bool, - out_min: Union[int, float], - out_max: Union[int, float], - dtype_out: Optional[type] = np.float32, + out_min: int | float, + out_max: int | float, + dtype_out: np.dtype | type | None = np.float32, ) -> np.ndarray: """Return simulated EBSD patterns projected from a master pattern with varying projection centers (PCs). @@ -502,8 +502,8 @@ def _project_single_pattern_from_master_pattern( npy: int, scale: float, rescale: bool, - out_min: Union[int, float], - out_max: Union[int, float], + out_min: int | float, + out_max: int | float, dtype_out: type, ) -> np.ndarray: """Return a single 1D EBSD pattern projected from a master pattern. @@ -629,7 +629,7 @@ def _get_lambert_interpolation_parameters( npx: int, npy: int, scale: float, -) -> Tuple[ +) -> tuple[ np.ndarray, np.ndarray, np.ndarray, diff --git a/src/kikuchipy/signals/util/_overwrite_hyperspy_methods.py b/src/kikuchipy/signals/util/_overwrite_hyperspy_methods.py index 02ba8e11..c24be5dc 100644 --- a/src/kikuchipy/signals/util/_overwrite_hyperspy_methods.py +++ b/src/kikuchipy/signals/util/_overwrite_hyperspy_methods.py @@ -20,12 +20,12 @@ import functools import inspect import re -from typing import Callable, List, Union +from typing import Callable def get_parameters( - method: Callable, params_of_interest: List[str], args: tuple, kwargs: dict -) -> Union[dict, None]: + method: Callable, params_of_interest: list[str], args: tuple, kwargs: dict +) -> dict | None: sig = inspect.signature(method) params = {} @@ -73,12 +73,12 @@ class insert_doc_disclaimer: Method, e.g. rebin. """ - def __init__(self, cls, meth): + def __init__(self, cls, meth: Callable) -> None: self.cls_name = cls.__name__ self.doc = meth.__doc__ self.name = meth.__name__ - def __call__(self, func): + def __call__(self, func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) @@ -86,7 +86,7 @@ def wrapper(*args, **kwargs): wrapper.__doc__ = self._insert_doc_disclaimer() return wrapper - def _insert_doc_disclaimer(self): + def _insert_doc_disclaimer(self) -> str | None: doc = self.doc if doc is None: return doc diff --git a/src/kikuchipy/signals/util/array_tools.py b/src/kikuchipy/signals/util/array_tools.py index f9811d6b..6fd19b0c 100644 --- a/src/kikuchipy/signals/util/array_tools.py +++ b/src/kikuchipy/signals/util/array_tools.py @@ -15,16 +15,14 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from typing import Tuple, Union - import numpy as np def grid_indices( - grid_shape: Union[Tuple[int, int], int], - nav_shape: Union[Tuple[int, int], int], + grid_shape: tuple[int, int] | int, + nav_shape: tuple[int, int] | int, return_spacing: bool = False, -) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: +) -> np.ndarray | tuple[np.ndarray, np.ndarray]: """Return indices of a grid evenly spaced in a larger grid of max. two dimensions. diff --git a/src/kikuchipy/signals/virtual_bse_image.py b/src/kikuchipy/signals/virtual_bse_image.py index 35bdd9d2..a5ccd300 100644 --- a/src/kikuchipy/signals/virtual_bse_image.py +++ b/src/kikuchipy/signals/virtual_bse_image.py @@ -17,8 +17,6 @@ from __future__ import annotations -from typing import List, Optional, Tuple, Union - import numpy as np from kikuchipy.signals._kikuchipy_signal import KikuchipySignal2D, LazyKikuchipySignal2D @@ -42,16 +40,16 @@ class VirtualBSEImage(KikuchipySignal2D): def rescale_intensity( self, relative: bool = False, - in_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - out_range: Union[Tuple[int, int], Tuple[float, float], None] = None, - dtype_out: Union[ - str, np.dtype, type, Tuple[int, int], Tuple[float, float], None - ] = None, - percentiles: Union[Tuple[int, int], Tuple[float, float], None] = None, - show_progressbar: Optional[bool] = None, + in_range: tuple[int, int] | tuple[float, float] | None = None, + out_range: tuple[int, int] | tuple[float, float] | None = None, + dtype_out: ( + str | np.dtype | type | tuple[int, int] | tuple[float, float] | None + ) = None, + percentiles: tuple[int, int] | tuple[float, float] | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, VirtualBSEImage, LazyVirtualBSEImage]: + lazy_output: bool | None = None, + ) -> VirtualBSEImage | LazyVirtualBSEImage | None: return super().rescale_intensity( relative, in_range, @@ -67,11 +65,11 @@ def normalize_intensity( self, num_std: int = 1, divide_by_square_root: bool = False, - dtype_out: Union[str, np.dtype, type, None] = None, - show_progressbar: Optional[bool] = None, + dtype_out: str | np.dtype | type | None = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, VirtualBSEImage, LazyVirtualBSEImage]: + lazy_output: bool | None = None, + ) -> VirtualBSEImage | LazyVirtualBSEImage | None: return super().normalize_intensity( num_std, divide_by_square_root, @@ -83,13 +81,13 @@ def normalize_intensity( def adaptive_histogram_equalization( self, - kernel_size: Optional[Union[Tuple[int, int], List[int]]] = None, - clip_limit: Union[int, float] = 0, + kernel_size: tuple[int, int] | list[int] | None = None, + clip_limit: int | float = 0.0, nbins: int = 128, - show_progressbar: Optional[bool] = None, + show_progressbar: bool | None = None, inplace: bool = True, - lazy_output: Optional[bool] = None, - ) -> Union[None, VirtualBSEImage, LazyVirtualBSEImage]: + lazy_output: bool | None = None, + ) -> VirtualBSEImage | LazyVirtualBSEImage | None: return super().adaptive_histogram_equalization( kernel_size, clip_limit, diff --git a/src/kikuchipy/simulations/_kikuchi_pattern_features.py b/src/kikuchipy/simulations/_kikuchi_pattern_features.py index 5810a065..13b8b4cd 100644 --- a/src/kikuchipy/simulations/_kikuchi_pattern_features.py +++ b/src/kikuchipy/simulations/_kikuchi_pattern_features.py @@ -25,8 +25,8 @@ def __init__( vector: Miller, vector_detector: Vector3d, in_pattern: np.ndarray, - max_r_gnomonic: float = 10, - ): + max_r_gnomonic: float = 10.0, + ) -> None: self.vector = vector self.vector_detector = vector_detector self.in_pattern = np.atleast_2d(in_pattern) @@ -57,8 +57,8 @@ def __init__( hkl: Miller, hkl_detector: Vector3d, in_pattern: np.ndarray, - max_r_gnomonic: float = 10, - ): + max_r_gnomonic: float = 10.0, + ) -> None: super().__init__(hkl, hkl_detector, in_pattern, max_r_gnomonic) self._set_hesse_distance() self._set_within_r_gnomonic(np.abs(self.hesse_distance)) @@ -78,16 +78,16 @@ def plane_trace_coordinates(self) -> np.ndarray: """x0, y0, x1, y1""" return self._plane_trace_coordinates - def _set_hesse_distance(self): + def _set_hesse_distance(self) -> None: hesse_distance = np.tan(0.5 * np.pi - self.vector_detector.polar) self._hesse_distance = np.atleast_2d(hesse_distance) - def _set_hesse_alpha(self): + def _set_hesse_alpha(self) -> None: hesse_distance = self.hesse_distance hesse_distance[~self.within_r_gnomonic] = np.nan self._hesse_alpha = np.arccos(hesse_distance / self.max_r_gnomonic) - def _set_plane_trace_coordinates(self): + def _set_plane_trace_coordinates(self) -> None: # Get alpha1 and alpha2 angles (NaN for bands outside gnomonic radius) azimuth = np.atleast_2d(self.vector_detector.azimuth) hesse_alpha = self.hesse_alpha @@ -108,8 +108,8 @@ def __init__( uvw: Miller, uvw_detector: Vector3d, in_pattern: np.ndarray, - max_r_gnomonic: float = 10, - ): + max_r_gnomonic: float = 10.0, + ) -> None: super().__init__(uvw, uvw_detector, in_pattern, max_r_gnomonic) self._set_r_gnomonic() self._set_within_r_gnomonic(self.r_gnomonic) @@ -119,10 +119,10 @@ def __init__( def r_gnomonic(self) -> np.ndarray: return self._r_gnomonic - def _set_r_gnomonic(self): + def _set_r_gnomonic(self) -> None: self._r_gnomonic = np.sqrt(self.x_gnomonic**2 + self.y_gnomonic**2) - def _set_xy_within_r_gnomonic(self): + def _set_xy_within_r_gnomonic(self) -> None: xy = np.stack((self.x_gnomonic, self.y_gnomonic)) xy = np.moveaxis(xy, 0, -1) xy[~self.within_r_gnomonic] = np.nan diff --git a/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py b/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py index ec879234..b40e2b56 100644 --- a/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py +++ b/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py @@ -17,14 +17,13 @@ from copy import deepcopy import re -from typing import List, Optional, Union from diffsims.crystallography import ReciprocalLatticeVector from hyperspy.drawing.marker import MarkerBase from hyperspy.utils.markers import line_segment, point, text import matplotlib.collections as mcollections +import matplotlib.figure as mfigure import matplotlib.path as mpath -import matplotlib.pyplot as plt import matplotlib.text as mtext import numpy as np from orix.quaternion import Rotation @@ -67,7 +66,7 @@ def __init__( reflectors: ReciprocalLatticeVector, lines: KikuchiPatternLine, zone_axes: KikuchiPatternZoneAxis, - ): + ) -> None: self._detector = detector.deepcopy() self._rotations = deepcopy(rotations) self._reflectors = reflectors.deepcopy() @@ -113,14 +112,14 @@ def __repr__(self) -> str: def as_collections( self, - index: Union[int, tuple, None] = None, + index: int | tuple[int, ...] | None = None, coordinates: str = "detector", lines: bool = True, zone_axes: bool = False, zone_axes_labels: bool = False, - lines_kwargs: dict = None, - zone_axes_kwargs: dict = None, - zone_axes_labels_kwargs: dict = None, + lines_kwargs: dict | None = None, + zone_axes_kwargs: dict | None = None, + zone_axes_labels_kwargs: dict | None = None, ) -> list: """Get a single simulation as a list of Matplotlib objects. @@ -196,11 +195,11 @@ def as_markers( zone_axes: bool = False, zone_axes_labels: bool = False, pc: bool = False, - lines_kwargs: Optional[dict] = None, - zone_axes_kwargs: Optional[dict] = None, - zone_axes_labels_kwargs: Optional[dict] = None, - pc_kwargs: Optional[dict] = None, - ) -> List[MarkerBase]: + lines_kwargs: dict | None = None, + zone_axes_kwargs: dict | None = None, + zone_axes_labels_kwargs: dict | None = None, + pc_kwargs: dict | None = None, + ) -> list[MarkerBase]: """Return a list of simulation markers. Parameters @@ -258,7 +257,7 @@ def as_markers( def lines_coordinates( self, - index: Union[int, tuple, None] = None, + index: int | tuple | None = None, coordinates: str = "detector", exclude_nan: bool = True, ) -> np.ndarray: @@ -299,20 +298,20 @@ def lines_coordinates( def plot( self, - index: Union[int, tuple, None] = None, + index: int | tuple | None = None, coordinates: str = "detector", - pattern: Optional[np.ndarray] = None, + pattern: np.ndarray | None = None, lines: bool = True, zone_axes: bool = True, zone_axes_labels: bool = True, pc: bool = True, - pattern_kwargs: Optional[dict] = None, - lines_kwargs: Optional[dict] = None, - zone_axes_kwargs: Optional[dict] = None, - zone_axes_labels_kwargs: Optional[dict] = None, - pc_kwargs: Optional[dict] = None, + pattern_kwargs: dict | None = None, + lines_kwargs: dict | None = None, + zone_axes_kwargs: dict | None = None, + zone_axes_labels_kwargs: dict | None = None, + pc_kwargs: dict | None = None, return_figure: bool = False, - ) -> plt.Figure: + ) -> mfigure.Figure | None: """Plot a single simulation on the detector. Parameters @@ -396,7 +395,7 @@ def plot( def zone_axes_coordinates( self, - index: Union[int, tuple, None] = None, + index: int | tuple | None = None, coordinates: str = "detector", exclude_nan: bool = True, ) -> np.ndarray: @@ -436,7 +435,7 @@ def zone_axes_coordinates( return coords.copy() def _lines_as_collection( - self, index: Union[int, tuple], coordinates: str, **kwargs + self, index: int | tuple[int, ...], coordinates: str, **kwargs ) -> mcollections.LineCollection: """Get Kikuchi lines as a Matplotlib collection. @@ -470,7 +469,7 @@ def _lines_as_collection( kw.update(kwargs) return mcollections.LineCollection(segments=list(coords), **kw) - def _lines_as_markers(self, **kwargs) -> List[line_segment]: + def _lines_as_markers(self, **kwargs) -> list[line_segment]: """Get Kikuchi lines as a list of HyperSpy markers. Parameters @@ -611,7 +610,7 @@ def _set_zone_axes_detector_coordinates(self) -> None: self._zone_axes_detector_coordinates = coords_d def _zone_axes_as_collection( - self, index: Union[int, tuple], coordinates: str, **kwargs + self, index: int | tuple[int, ...], coordinates: str, **kwargs ) -> mcollections.PathCollection: """Get zone axes as a Matplotlib collection. @@ -646,7 +645,7 @@ def _zone_axes_as_collection( return mcollections.PathCollection(circles, **kw) def _zone_axes_labels_as_list( - self, index: Union[int, tuple], coordinates: str, **kwargs + self, index: int | tuple[int, ...], coordinates: str, **kwargs ) -> list: """Get zone axes labels as a list of texts. diff --git a/src/kikuchipy/simulations/kikuchi_pattern_simulator.py b/src/kikuchipy/simulations/kikuchi_pattern_simulator.py index a34cc9e8..ddf03588 100644 --- a/src/kikuchipy/simulations/kikuchi_pattern_simulator.py +++ b/src/kikuchipy/simulations/kikuchi_pattern_simulator.py @@ -55,12 +55,13 @@ # ###################################################################### import sys -from typing import Optional, Union +from typing import TYPE_CHECKING import dask.array as da from dask.diagnostics import ProgressBar from diffsims.crystallography import ReciprocalLatticeVector import matplotlib.colors as mcolors +import matplotlib.figure as mfigure import matplotlib.pyplot as plt import numpy as np from orix import projections @@ -80,6 +81,9 @@ GeometricalKikuchiPatternSimulation, ) +if TYPE_CHECKING: # pragma: no cover + from pyvista import Plotter + class KikuchiPatternSimulator: """Setup and calculation of geometrical or kinematical Kikuchi @@ -92,7 +96,7 @@ class KikuchiPatternSimulator: dimension. """ - def __init__(self, reflectors: ReciprocalLatticeVector): + def __init__(self, reflectors: ReciprocalLatticeVector) -> None: self._reflectors = reflectors.deepcopy().flatten() @property @@ -111,9 +115,9 @@ def __repr__(self) -> str: def calculate_master_pattern( self, - half_size: Optional[int] = 500, - hemisphere: Optional[str] = "upper", - scaling: Optional[str] = "linear", + half_size: int = 500, + hemisphere: str = "upper", + scaling: str | None = "linear", ) -> EBSDMasterPattern: r"""Calculate a kinematical master pattern in the stereographic projection. @@ -137,8 +141,7 @@ def calculate_master_pattern( scaling Intensity scaling of the band kinematical intensities, either ``"linear"`` (default), :math:`|F|`, ``"square"``, - :math:`|F|^2`, or ``"None"``, giving all bands an intensity - of ``1``. + :math:`|F|^2`, or None, giving all bands an intensity of 1. Returns ------- @@ -418,17 +421,17 @@ def on_detector( def plot( self, - projection: Optional[str] = "stereographic", - mode: Optional[str] = "lines", - hemisphere: Optional[str] = "upper", - scaling: Optional[str] = "linear", - figure: Union[None, plt.Figure, "pyvista.Plotter"] = None, + projection: str | None = "stereographic", + mode: str | None = "lines", + hemisphere: str | None = "upper", + scaling: str | None = "linear", + figure: "mfigure.Figure | Plotter | None" = None, return_figure: bool = False, backend: str = "matplotlib", show_plotter: bool = True, color: str = "k", **kwargs, - ) -> Union[plt.Figure, "pyvista.Plotter"]: + ) -> "mfigure.Figure | Plotter | None": """Plot reflectors as lines or bands in the stereographic or spherical projection. @@ -631,11 +634,11 @@ def _plot_spherical( ref: ReciprocalLatticeVector, color: np.ndarray, backend: str, - figure, + figure: "mfigure.Figure | Plotter | None", show_plotter: bool, scaling_title: str, intensity: np.ndarray, -): +) -> None: v = Vector3d(ref).unit steps = 101 From f8e56f052fb3a8278ae21a1ec0799435109cf14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 5 Oct 2024 13:49:22 +0200 Subject: [PATCH 11/12] Temporarily restrict to NumPy <2 due to dependency incompats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01f8e041..c836efa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,8 @@ dependencies = [ "lazy_loader", "matplotlib >= 3.5", "numba >= 0.57", - "numpy >= 1.23.0", + # TODO: Remove pinning of NumPy <2 once HyperSpy 2.0 is supported + "numpy >= 1.23.0, <2", "orix >= 0.12.1", "pooch >= 1.3.0", "pyyaml", From 4ab1d6f1b2d27299a7c54abda98743cfabb86ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 5 Oct 2024 14:09:49 +0200 Subject: [PATCH 12/12] Wait on using typing.Self until Python >= 3.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/detectors/ebsd_detector.py | 12 ++++++------ src/kikuchipy/filters/window.py | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/kikuchipy/detectors/ebsd_detector.py b/src/kikuchipy/detectors/ebsd_detector.py index f208e388..fc411af7 100644 --- a/src/kikuchipy/detectors/ebsd_detector.py +++ b/src/kikuchipy/detectors/ebsd_detector.py @@ -22,7 +22,7 @@ import logging from pathlib import Path import re -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING from diffsims.crystallography import ReciprocalLatticeVector from matplotlib.figure import Figure @@ -495,7 +495,7 @@ def r_max(self) -> np.ndarray: return np.atleast_2d(np.sqrt(np.max(corners, axis=-1))) @classmethod - def load(cls, fname: Path | str) -> Self: + def load(cls, fname: Path | str) -> EBSDDetector: """Return an EBSD detector loaded from a text file saved with :meth:`save`. @@ -557,7 +557,7 @@ def load(cls, fname: Path | str) -> Self: return cls(pc=pc, **detector_kw) - def crop(self, extent: tuple[int, int, int, int] | list[int]) -> Self: + def crop(self, extent: tuple[int, int, int, int] | list[int]) -> EBSDDetector: """Return a new detector with its :attr:`shape` cropped and :attr:`pc` values updated accordingly. @@ -622,7 +622,7 @@ def crop(self, extent: tuple[int, int, int, int] | list[int]) -> Self: azimuthal=self.azimuthal, ) - def deepcopy(self) -> Self: + def deepcopy(self) -> EBSDDetector: """Return a deep copy using :func:`copy.deepcopy`. Returns @@ -852,7 +852,7 @@ def extrapolate_pc( px_size: float | None = None, binning: int | None = None, is_outlier: tuple | list | np.ndarray | None = None, - ) -> Self: + ) -> EBSDDetector: r"""Return a new detector with projection centers (PCs) in a 2D map extrapolated from an average PC. @@ -959,7 +959,7 @@ def fit_pc( plot: bool = True, return_figure: bool = False, figure_kwargs: dict | None = None, - ) -> Self | tuple[EBSDDetector, Figure]: + ) -> EBSDDetector | tuple[EBSDDetector, Figure]: """Return a new detector with interpolated projection centers (PCs) for all points in a map by fitting a plane to :attr:`pc` :cite:`winkelmann2020refined`. diff --git a/src/kikuchipy/filters/window.py b/src/kikuchipy/filters/window.py index 2b81785c..49ef02fa 100644 --- a/src/kikuchipy/filters/window.py +++ b/src/kikuchipy/filters/window.py @@ -15,8 +15,10 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +from __future__ import annotations + from copy import copy -from typing import Self, Sequence +from typing import Sequence from dask.array import Array import matplotlib.figure as mfigure @@ -117,7 +119,7 @@ def __new__( window: str | np.ndarray | Array | None = None, shape: Sequence[int] | None = None, **kwargs, - ) -> Self: + ) -> Window: if window is None: window = "circular" @@ -197,7 +199,7 @@ def __array_finalize__(self, obj: Self | None) -> None: self._name = getattr(obj, "_name", None) self._circular = getattr(obj, "_circular", False) - def __array_wrap__(self, obj: Self) -> Self | np.ndarray: + def __array_wrap__(self, obj: Self) -> Window | np.ndarray: if obj.shape == (): return obj[()] else: