diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35d3029f..c67ee3ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,11 +18,13 @@ Unreleased Added ----- +- Dependency on RosettaSciIO for read/write of some file formats. + (`#? `_) Changed ------- - Minimum Python version is now 3.10. - (`#? `_) + (`#689 `_) Removed ------- diff --git a/conftest.py b/conftest.py index 4bb2f9ba..38b5fc36 100644 --- a/conftest.py +++ b/conftest.py @@ -298,6 +298,11 @@ def save_path_hdf5(request, tmpdir) -> Generator[Path, None, None]: yield Path(tmpdir / f"patterns.{ext}") +@pytest.fixture() +def save_path_nordif(tmpdir) -> Generator[Path, None, None]: + yield Path(tmpdir / "nordif/save_temp.dat") + + @pytest.fixture def ni_small_axes_manager() -> Generator[dict, None, None]: """Axes manager for :func:`kikuchipy.data.nickel_ebsd_small`.""" diff --git a/doc/conf.py b/doc/conf.py index 5e85394f..083caef8 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -79,6 +79,7 @@ "pyvista": ("https://docs.pyvista.org", None), "pyxem": ("https://pyxem.readthedocs.io/en/latest", None), "readthedocs": ("https://docs.readthedocs.io/en/stable", None), + "rosettasciio": ("https://rosettasciio.readthedocs.io/en/stable", None), "scipy": ("https://docs.scipy.org/doc/scipy", None), "skimage": ("https://scikit-image.org/docs/stable", None), "sklearn": ("https://scikit-learn.org/stable", None), diff --git a/doc/environment.yml b/doc/environment.yml index f72b6022..a08a04dc 100644 --- a/doc/environment.yml +++ b/doc/environment.yml @@ -3,4 +3,5 @@ channels: dependencies: - pip - python=3.11 - - pandoc>=2.14.2, <4.0.0 \ No newline at end of file + - pandoc>=2.14.2, <4.0.0 + - ipykernel \ No newline at end of file diff --git a/doc/user/installation.rst b/doc/user/installation.rst index be19d15c..b3915834 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -106,11 +106,11 @@ This is a list of core package dependencies: * :doc:`orix `: Handling of rotations and vectors using crystal symmetry * :doc:`pooch `: Downloading and caching of datasets * `pyyaml `__: Parcing of YAML files -* `tqdm `__: Progressbars * :doc:`scikit-image `: Image processing like adaptive histogram equalization * `scikit-learn `__: Multivariate analysis * :doc:`scipy `: Optimization algorithms, filtering and more +* `tqdm `__: Progressbars .. _lazy_loader: https://scientific-python.org/specs/spec-0001/#lazy_loader diff --git a/examples/selecting_data/crop_signal_axes.py b/examples/selecting_data/crop_signal_axes.py index b13a7321..ea4db775 100644 --- a/examples/selecting_data/crop_signal_axes.py +++ b/examples/selecting_data/crop_signal_axes.py @@ -5,7 +5,7 @@ This example shows various ways to crop the signal axes of an :class:`~kikuchipy.signals.EBSD` signal using HyperSpy's ``isig`` slicer and the :meth:`~kikuchipy.signals.EBSD.crop` and -:meth:`~hyperspy._signals.signal2d.Signal2D.crop_image` methods (see +:meth:`~hyperspy._signals.signal2d.Signal2D.crop_signal` methods (see :ref:`hyperspy:signal.indexing` for details). """ @@ -49,10 +49,10 @@ print(s3.detector) # %% -# Do the same inplace using ``crop_image()`` +# Do the same inplace using ``crop_signal()`` s4 = s.deepcopy() -s4.crop_image(top=10, bottom=50, left=5, right=55) +s4.crop_signal(top=10, bottom=50, left=5, right=55) _ = hs.plot.plot_images(s4, **plot_kwds) print(s4) diff --git a/hyperspy_extension.yaml b/hyperspy_extension.yaml deleted file mode 100644 index 0a366982..00000000 --- a/hyperspy_extension.yaml +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2019-2024 The kikuchipy developers -# -# This file is part of kikuchipy. -# -# kikuchipy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# kikuchipy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with kikuchipy. If not, see . - -signals: - EBSD: - signal_type: EBSD - signal_type_aliases: - - electron_backscatter_diffraction - signal_dimension: 2 - dtype: real - lazy: False - module: kikuchipy.signals.ebsd - LazyEBSD: - signal_type: EBSD - signal_dimension: 2 - dtype: real - lazy: True - module: kikuchipy.signals.ebsd - EBSDMasterPattern: - signal_type: EBSDMasterPattern - signal_type_aliases: - - ebsd_master_pattern - - master_pattern - signal_dimension: 2 - dtype: real - lazy: False - module: kikuchipy.signals.ebsd_master_pattern - LazyEBSDMasterPattern: - signal_type: EBSDMasterPattern - signal_dimension: 2 - dtype: real - lazy: True - module: kikuchipy.signals.ebsd_master_pattern - ECPMasterPattern: - signal_type: ECPMasterPattern - signal_type_aliases: - - ecp_master_pattern - signal_dimension: 2 - dtype: real - lazy: False - module: kikuchipy.signals.ecp_master_pattern - LazyECPMasterPattern: - signal_type: ECPMasterPattern - signal_dimension: 2 - dtype: real - lazy: True - module: kikuchipy.signals.ecp_master_pattern - LazyVirtualBSEImage: - signal_type: VirtualBSEImage - signal_dimension: 2 - dtype: real - lazy: True - module: kikuchipy.signals.virtual_bse_image - VirtualBSEImage: - signal_type: VirtualBSEImage - signal_type_aliases: - - virtual_backscatter_electron_image - signal_dimension: 2 - dtype: real - lazy: False - module: kikuchipy.signals.virtual_bse_image diff --git a/pyproject.toml b/pyproject.toml index fc208df3..75015376 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,8 @@ requires = ["hatchling"] build-backend = "hatchling.build" +# ----------------------------- Project ------------------------------ # + [project] name = "kikuchipy" authors = [{name = "kikuchipy developers"}] @@ -40,33 +42,23 @@ dependencies = [ "dask[array] >= 2021.8.1", "diffpy.structure >= 3", "diffsims >= 0.5.2", - "hyperspy >= 2", + # TODO: Replace with HyperSpy 2.2 once it's out + "hyperspy @ git+https://github.com/hyperspy/hyperspy.git@RELEASE_next_patch", "h5py >= 2.10", "imageio", "lazy_loader", "matplotlib >= 3.5", "numba >= 0.57", - # TODO: Remove pinning of NumPy <2 once HyperSpy 2.0 is supported - "numpy >= 1.23.0, <2", + "numpy >= 1.23.0", "orix >= 0.12.1", "pooch >= 1.3.0", "pyyaml", - "tqdm >= 0.5.2", "scikit-image >= 0.16.2", "scikit-learn", "scipy >= 1.7", + "tqdm >= 0.5.2", ] -[project.entry-points."hyperspy.extensions"] -kikuchipy = "kikuchipy" - -[project.urls] -Documentation = "https://kikuchipy.org" -Download = "https://pypi.python.org/pypi/kikuchipy" -"Bug Tracker" = "https://github.com/pyxem/kikuchipy/issues" -"Source Code" = "https://github.com/pyxem/kikuchipy" -Changelog = "https://kikuchipy.org/en/stable/changelog.html" - [project.optional-dependencies] all = [ "nlopt", @@ -108,22 +100,34 @@ dev = [ "kikuchipy[doc,tests,coverage]", ] +[project.entry-points."hyperspy.extensions"] +kikuchipy = "kikuchipy" + +[project.urls] +Documentation = "https://kikuchipy.org" +Download = "https://pypi.python.org/pypi/kikuchipy" +"Bug Tracker" = "https://github.com/pyxem/kikuchipy/issues" +"Source Code" = "https://github.com/pyxem/kikuchipy" +Changelog = "https://kikuchipy.org/en/stable/changelog.html" + +# ------------------------------- Tool ------------------------------- # + [tool.hatch.version] path = "src/kikuchipy/__init__.py" -# TODO: Remove this section and the duplicated file when HS 2.0 is -# supported -[tool.hatch.build.force-include] -"hyperspy_extension.yaml" = "hyperspy_extension.yaml" - # https://github.com/pypa/hatch/discussions/427 # https://github.com/pypa/hatch/issues/492 +[tool.hatch.build.targets.wheel.sources] +src = "" + [tool.hatch.build.targets.wheel] include = [ "src/kikuchipy", ] -[tool.hatch.build.targets.wheel.sources] -"src" = "" + +# TODO: Remove once HyperSpy is not installed from a direct reference (GitHub) +[tool.hatch.metadata] +allow-direct-references = true [tool.coverage.report] precision = 2 diff --git a/src/kikuchipy/_util/_deprecated.py b/src/kikuchipy/_util/_deprecated.py index 0676ee68..f3bea08c 100644 --- a/src/kikuchipy/_util/_deprecated.py +++ b/src/kikuchipy/_util/_deprecated.py @@ -31,7 +31,7 @@ from typing import Callable import warnings -import numpy as np +from kikuchipy.constants import VisibleDeprecationWarning class deprecated: @@ -93,12 +93,12 @@ def __call__(self, func: Callable) -> None: @functools.wraps(func) def wrapped(*args, **kwargs) -> Callable: warnings.simplefilter( - action="always", category=np.VisibleDeprecationWarning, append=True + action="always", category=VisibleDeprecationWarning, append=True ) func_code = func.__code__ warnings.warn_explicit( message=msg, - category=np.VisibleDeprecationWarning, + category=VisibleDeprecationWarning, filename=func_code.co_filename, lineno=func_code.co_firstlineno + 1, ) @@ -151,12 +151,12 @@ def wrapped(*args, **kwargs): msg += f"Use `{self.alternative}` instead. " msg += f"See the documentation of `{func.__name__}()` for more details." warnings.simplefilter( - action="always", category=np.VisibleDeprecationWarning + action="always", category=VisibleDeprecationWarning ) func_code = func.__code__ warnings.warn_explicit( message=msg, - category=np.VisibleDeprecationWarning, + category=VisibleDeprecationWarning, filename=func_code.co_filename, lineno=func_code.co_firstlineno + 1, ) diff --git a/src/kikuchipy/constants.py b/src/kikuchipy/constants.py index 71041b46..6cd6f049 100644 --- a/src/kikuchipy/constants.py +++ b/src/kikuchipy/constants.py @@ -51,3 +51,15 @@ # Have to use bare except because PyOpenCL might raise its own # LogicError, but we also want to catch import errors here pyopencl_context_available = False + + +# TODO: Remove and use numpy.exceptions.VisibleDeprecationWarning once +# NumPy 1.25 is minimal supported version +try: + # Added in NumPy 1.25.0 + from numpy.exceptions import VisibleDeprecationWarning +except ImportError: # pragma: no cover + # Removed in NumPy 2.0.0 + from numpy import VisibleDeprecationWarning + +del optional_deps, version diff --git a/src/kikuchipy/detectors/ebsd_detector.py b/src/kikuchipy/detectors/ebsd_detector.py index fc411af7..99ddf8d7 100644 --- a/src/kikuchipy/detectors/ebsd_detector.py +++ b/src/kikuchipy/detectors/ebsd_detector.py @@ -226,7 +226,7 @@ def __init__( def __repr__(self) -> str: decimals = 3 - pc_average = tuple(self.pc_average.round(decimals)) + pc_average = tuple(map(float, self.pc_average.round(decimals))) sample_tilt = np.round(self.sample_tilt, decimals) tilt = np.round(self.tilt, decimals) azimuthal = np.round(self.azimuthal, decimals) @@ -234,7 +234,7 @@ def __repr__(self) -> str: return ( f"{type(self).__name__}" f"(shape={self.shape}, " - f"pc={pc_average!r}, " + f"pc={pc_average}, " f"sample_tilt={sample_tilt}, " f"tilt={tilt}, " f"azimuthal={azimuthal}, " diff --git a/src/kikuchipy/draw/markers.py b/src/kikuchipy/draw/markers.py deleted file mode 100644 index 83051e7d..00000000 --- a/src/kikuchipy/draw/markers.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2019-2024 The kikuchipy developers -# -# This file is part of kikuchipy. -# -# kikuchipy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# kikuchipy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with kikuchipy. If not, see . - -"""Creation of lists of HyperSpy markers.""" - -from hyperspy.utils.markers import line_segment, point, text -import numpy as np - - -def get_line_segment_list(lines: np.ndarray | list, **kwargs) -> list: - """Return a list of line segment markers. - - Parameters - ---------- - lines - On the form [[x00, y00, x01, y01], [x10, y10, x11, y11], ...]. - kwargs - Keyword arguments allowed by :func:`matplotlib.pyplot.axvline`. - - Returns - ------- - marker_list - List of - :class:`hyperspy.drawing._markers.line_segment.LineSegment`. - """ - lines = np.asarray(lines) - if lines.ndim == 1: # No navigation shape, one line - lines = lines[np.newaxis, ...] - - marker_list = [] - for i in range(lines.shape[-2]): # Iterate over bands - if not np.allclose(lines[..., i, :], np.nan, equal_nan=True): - x1 = lines[..., i, 0] - y1 = lines[..., i, 1] - x2 = lines[..., i, 2] - y2 = lines[..., i, 3] - marker_list.append(line_segment(x1=x1, y1=y1, x2=x2, y2=y2, **kwargs)) - return marker_list - - -def get_point_list(points: np.ndarray | list, **kwargs) -> list: - """Return a list of point markers. - - Parameters - ---------- - points - On the form [[x0, y0], [x1, y1], ...]. - kwargs - Keyword arguments allowed by :func:`matplotlib.pyplot.scatter`. - - Returns - ------- - marker_list - List of :class:`hyperspy.drawing._markers.point.Point`. - """ - points = np.asarray(points) - if points.ndim == 1: - points = points[np.newaxis, ...] - - marker_list = [] - for i in range(points.shape[-2]): # Iterate over zone axes - if not np.allclose(points[..., i, :], np.nan, equal_nan=True): - marker_list.append( - point(x=points[..., i, 0], y=points[..., i, 1], **kwargs) - ) - return marker_list - - -def get_text_list( - texts: np.ndarray | list, coordinates: np.ndarray | list, **kwargs -) -> list: - """Return a list of text markers. - - Parameters - ---------- - texts - A list of texts. - coordinates - On the form [[x0, y0], [x1, y1], ...]. - kwargs - Keyword arguments allowed by :func:`matplotlib.pyplot.axvline.` - - Returns - ------- - marker_list - List of :class:`hyperspy.drawing._markers.text.Text`. - """ - coordinates = np.asarray(coordinates) - if coordinates.ndim == 1: - coordinates = coordinates[np.newaxis, ...] - - marker_list = [] - is_finite = np.isfinite(coordinates)[..., 0] - coordinates[~is_finite] = -1 - for i in range(coordinates.shape[-2]): # Iterate over zone axes - if not np.allclose(coordinates[..., i, :], -1): # All NaNs - x = coordinates[..., i, 0] - y = coordinates[..., i, 1] - x[~is_finite[..., i]] = np.nan - y[~is_finite[..., i]] = np.nan - text_marker = text(x=x, y=y, text=texts[i], **kwargs) - marker_list.append(text_marker) - return marker_list diff --git a/src/kikuchipy/imaging/vbse.py b/src/kikuchipy/imaging/vbse.py index bb9f6f72..fda6ca2d 100644 --- a/src/kikuchipy/imaging/vbse.py +++ b/src/kikuchipy/imaging/vbse.py @@ -17,10 +17,6 @@ from dask.array import Array from hyperspy._signals.signal2d import Signal2D -from hyperspy.drawing._markers.horizontal_line import HorizontalLine -from hyperspy.drawing._markers.rectangle import Rectangle -from hyperspy.drawing._markers.text import Text -from hyperspy.drawing._markers.vertical_line import VerticalLine from hyperspy.roi import BaseInteractiveROI, RectangularROI import numpy as np @@ -48,7 +44,11 @@ class VirtualBSEImager: def __init__(self, signal: EBSD | LazyEBSD) -> None: self.signal = signal - self._grid_shape = (5, 5) + sig_shape = signal._signal_shape_rc + grid_shape = () + for axis_length in sig_shape: + grid_shape += (min(5, axis_length),) + self.grid_shape = grid_shape def __repr__(self) -> str: return self.__class__.__name__ + " for " + repr(self.signal) @@ -70,20 +70,36 @@ def grid_cols(self) -> np.ndarray: return np.linspace(0, sx, gx + 1) @property - def grid_shape(self) -> tuple: + def grid_shape(self) -> tuple[int, int]: """Return or set the generator grid shape. Parameters ---------- shape : tuple or list of int - Generator grid shape. + Generator grid shape (n rows, n cols). Must correspond to a + tuple of length equal to the number of signal dimensions of + :attr:`signal`. Cannot be greater than signal shape of + :attr:`signal`. """ return self._grid_shape @grid_shape.setter def grid_shape(self, shape: tuple[int, int] | list[int]) -> None: """Set the generator grid shape.""" - self._grid_shape = tuple(shape) + shape = tuple(shape) + ndim_sig = self.signal.axes_manager.signal_dimension + if len(shape) != ndim_sig: + raise ValueError( + "Grid shape must have the same length as number of signal dimensions " + f"{ndim_sig}" + ) + sig_shape_rc = self.signal._signal_shape_rc + if any(i > j for i, j in zip(shape, self.signal._signal_shape_rc)): + raise ValueError( + f"Grid shape (n rows, n cols) = {shape} cannot be greater than signal " + f"shape {sig_shape_rc}" + ) + self._grid_shape = shape def get_rgb_image( self, diff --git a/src/kikuchipy/indexing/_hough_indexing.py b/src/kikuchipy/indexing/_hough_indexing.py index a8555492..09906b1e 100644 --- a/src/kikuchipy/indexing/_hough_indexing.py +++ b/src/kikuchipy/indexing/_hough_indexing.py @@ -378,10 +378,10 @@ def _indexer_is_compatible_with_kikuchipy( # PC and orientation convention if indexer.vendor.lower() != "kikuchipy": compatible = False - error_msg = f"`indexer.vendor` must be 'kikuchipy', but was {indexer.vendor}." + error_msg = f"'indexer.vendor' must be 'kikuchipy', but was {indexer.vendor}" # Detector shape - sig_shape_indexer = tuple(indexer.bandDetectPlan.patDim) + sig_shape_indexer = tuple(map(int, indexer.bandDetectPlan.patDim)) if compatible and sig_shape != sig_shape_indexer: compatible = False error_msg = ( @@ -508,10 +508,8 @@ def _get_info_message(nav_size: int, chunksize: int, indexer: "EBSDIndexer") -> if pc.size > 3: pc = pc.mean(0) info += ", mean" - pc = pc.round(4) - info += ( - f"): {tuple(pc)}\n" f" Indexing {nav_size} pattern(s) in {n_chunks} chunk(s)" - ) + pc = tuple(map(float, pc.round(4))) + info += f"): {pc}\n" f" Indexing {nav_size} pattern(s) in {n_chunks} chunk(s)" return info diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 4abd031a..55e083d5 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -16,51 +16,32 @@ # along with kikuchipy. If not, see . import glob +import importlib import os from pathlib import Path from typing import TYPE_CHECKING 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 +from hyperspy.misc.utils import find_subclasses from hyperspy.signal import BaseSignal import numpy as np +import yaml -from kikuchipy.io._util import _ensure_directory, _get_input_bool -from kikuchipy.io.plugins import ( - bruker_h5ebsd, - ebsd_directory, - edax_binary, - edax_h5ebsd, - emsoft_ebsd, - emsoft_ebsd_master_pattern, - emsoft_ecp_master_pattern, - emsoft_tkd_master_pattern, - kikuchipy_h5ebsd, - nordif, - nordif_calibration_patterns, - oxford_binary, - oxford_h5ebsd, -) +from kikuchipy.io._util import _ensure_directory, _get_input_bool, _overwrite import kikuchipy.signals -plugins: list = [ - bruker_h5ebsd, - ebsd_directory, - edax_binary, - edax_h5ebsd, - emsoft_ebsd, - emsoft_ebsd_master_pattern, - emsoft_ecp_master_pattern, - emsoft_tkd_master_pattern, - hspy, - kikuchipy_h5ebsd, - nordif, - nordif_calibration_patterns, - oxford_binary, - oxford_h5ebsd, -] +PLUGINS: list = [] +WRITE_EXTENSIONS = [] +specification_paths = list(Path(__file__).parent.rglob("specification.yaml")) +for path in specification_paths: + with open(path) as file: + spec = yaml.safe_load(file) + spec["api"] = ".".join(path.parts[-5:-1]) + PLUGINS.append(spec) + if spec["writes"]: + for ext in spec["file_extensions"]: + WRITE_EXTENSIONS.append(ext) + if TYPE_CHECKING: # pragma: no cover from kikuchipy.signals.ebsd import EBSD, LazyEBSD @@ -70,22 +51,13 @@ ) from kikuchipy.signals.ecp_master_pattern import ECPMasterPattern -default_write_ext = set() -for plugin in plugins: - if plugin.writes: - default_write_ext.add(plugin.file_extensions[plugin.default_extension]) - 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 + """Load a supported signal from one of the :ref:`/tutorials/load_save_data.ipynb#Supported-file-formats`. - This function is a modified version of :func:`hyperspy.io.load`. - Parameters ---------- filename @@ -93,7 +65,7 @@ def load( lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default - is ``False``. + is False. **kwargs Keyword arguments passed to the corresponding kikuchipy reader. See their individual documentation for available options. @@ -108,9 +80,13 @@ def load( IOError If the file was not found or could not be read. + Notes + ----- + This function is a modified version of :func:`hyperspy.io.load`. + Examples -------- - Import nine patterns from an HDF5 file in a directory ``DATA_PATH`` + Import nine patterns from an HDF5 file in a directory DATA_PATH >>> import kikuchipy as kp >>> s = kp.load(DATA_PATH / "patterns.h5") @@ -130,22 +106,26 @@ def load( # Find matching reader for file extension extension = os.path.splitext(filename)[1][1:] readers = [] - for plugin in plugins: - if extension.lower() in plugin.file_extensions: + for plugin in PLUGINS: + if extension.lower() in plugin["file_extensions"]: readers.append(plugin) - if len(readers) == 0: + + reader = None + if len(readers) == 1: + reader = readers[0] + elif len(readers) > 1 and h5py.is_hdf5(filename): + reader = _plugin_from_footprints(filename, plugins=readers) + + if len(readers) == 0 or reader is None: raise IOError( f"Could not read {filename!r}. If the file format is supported, please " "report this error" ) - elif len(readers) > 1 and h5py.is_hdf5(filename): - reader = _plugin_from_footprints(filename, plugins=readers) - else: - reader = readers[0] - # Get data and metadata (from potentially multiple signals if an h5ebsd - # file) - signal_dicts = reader.file_reader(filename, lazy=lazy, **kwargs) + # Get data and metadata (from potentially multiple signals if we're + # reading from an h5ebsd file) + file_reader = importlib.import_module(reader["api"]).file_reader + signal_dicts = file_reader(filename, lazy=lazy, **kwargs) out = [] for signal in signal_dicts: out.append(_dict2signal(signal, lazy=lazy)) @@ -166,13 +146,11 @@ def _dict2signal( ) -> "EBSD | LazyEBSD | EBSDMasterPattern | LazyEBSDMasterPattern": """Create a signal instance from a dictionary. - This function is a modified version :func:`hyperspy.io.dict2signal`. - Parameters ---------- signal_dict - Signal dictionary with ``data``, ``metadata`` and - ``original_metadata``. + Signal dictionary with "data", "metadata", and + "original_metadata" keys. lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default @@ -181,8 +159,13 @@ def _dict2signal( Returns ------- signal - Signal instance with ``data``, ``metadata`` and - ``original_metadata`` from dictionary. + Signal instance with "data", "metadata", and "original_metadata" + keys. + + Notes + ----- + This function is a modified version of + :func:`hyperspy.io.dict2signal`. """ signal_type = "" if "metadata" in signal_dict: @@ -191,7 +174,7 @@ def _dict2signal( record_by = md["Signal"]["record_by"] if record_by != "image": raise ValueError( - "kikuchipy only supports `record_by = image`, not " f"{record_by}." + "kikuchipy only supports `record_by = image`, not " f"{record_by}" ) del md["Signal"]["record_by"] if "Signal" in md and "signal_type" in md["Signal"]: @@ -211,15 +194,16 @@ def _dict2signal( def _plugin_from_footprints(filename: str, plugins) -> object: - """Get HDF5 correct plugin from a list of potential plugins based on - their unique footprints. + """Return correct HDF5 plugin from a list of potential plugins based + on their unique footprints. The unique footprint is a list of strings that can take on either of two formats: - * group/dataset names separated by "/", indicating nested - groups/datasets - * single group/dataset name indicating that the groups/datasets - are in the top group + + * group/dataset names separated by "/", indicating nested + groups/datasets + * single group/dataset name indicating that the groups/datasets are + in the top group Parameters ---------- @@ -231,8 +215,7 @@ def _plugin_from_footprints(filename: str, plugins) -> object: Returns ------- plugin - One of the potential plugins, or ``None`` if no footprint was - found. + One of the potential plugins, or None if no footprint was found. """ def _hdf5group2dict(group): @@ -247,46 +230,46 @@ def _hdf5group2dict(group): d[key_lower] = 1 return d - def _exists(obj, chain): + def _exists(obj: dict, chain: list[str]) -> dict | None: key = chain.pop(0) if key in obj: return _exists(obj[key], chain) if chain else obj[key] - with h5py.File(filename) as f: - d = _hdf5group2dict(f["/"]) - - plugins_with_footprints = [p for p in plugins if hasattr(p, "footprint")] - plugins_with_manufacturer = [ - p for p in plugins_with_footprints if hasattr(p, "manufacturer") - ] + with h5py.File(filename) as file: + top_group_keys = _hdf5group2dict(file["/"]) matching_plugin = None - # Check manufacturer if possible (all h5ebsd files have this) - for key, val in d.items(): + + plugins_matching_manufacturer = [] + # Find plugins matching the manufacturer dataset in the file + for key, val in top_group_keys.items(): if key == "manufacturer": - # Extracting the manufacturer is finicky - man = f[val][()] - if isinstance(man, np.ndarray) and len(man) == 1: - man = man[0] - if isinstance(man, bytes): - man = man.decode("latin-1") - for p in plugins_with_manufacturer: - if man.lower() == p.manufacturer: - matching_plugin = p - break - - # If no match found, continue searching - if matching_plugin is None: - for p in plugins_with_footprints: + manufacturer = file[val][()] + if isinstance(manufacturer, np.ndarray) and len(manufacturer) == 1: + manufacturer = manufacturer[0] + if isinstance(manufacturer, bytes): + manufacturer = manufacturer.decode("latin-1") + for plugin in plugins: + if manufacturer.lower() == plugin["manufacturer"]: + plugins_matching_manufacturer.append(plugin) + break + + if len(plugins_matching_manufacturer) == 1: + matching_plugin = plugins_matching_manufacturer[0] + else: + # Search for a unique footprint + plugins_matching_footprints = [] + for plugin in plugins: n_matches = 0 - n_desired_matches = len(p.footprint) - for fp in p.footprint: - fp = fp.lower().split("/") - if _exists(d, fp) is not None: + n_desired_matches = len(plugin["footprints"]) + for footprint in plugin["footprints"]: + footprint = footprint.lower().split("/") + if _exists(top_group_keys, footprint) is not None: n_matches += 1 - if n_matches == n_desired_matches: - matching_plugin = p - break + if n_matches > 0 and n_matches == n_desired_matches: + plugins_matching_footprints.append(plugin) + if len(plugins_matching_footprints) == 1: + matching_plugin = plugins_matching_footprints[0] return matching_plugin @@ -297,16 +280,13 @@ def _assign_signal_subclass( signal_type: str = "", lazy: bool = False, ) -> "EBSD | EBSDMasterPattern | ECPMasterPattern": - """Given ``record_by`` and ``signal_type`` return the matching - signal subclass. - - This function is a modified version of - :func:`hyperspy.io.assign_signal_subclass`. + """Return matching signal subclass given by *record_by* and + *signal_type*. Parameters ---------- dtype - Data type of signal data. + Data type of the signal data. signal_dimension Number of signal dimensions. signal_type @@ -319,6 +299,11 @@ def _assign_signal_subclass( Returns ------- Signal or subclass + + Notes + ----- + This function is a modified version of + :func:`hyperspy.io.assign_signal_subclass`. """ # Check if parameter values are allowed if ( @@ -344,14 +329,14 @@ def _assign_signal_subclass( # Get signals matching both input signal's dtype and signal dimension dtype_matches = [s for s in signals.values() if s._dtype == dtype] - dtype_dim_matches = [ - s for s in dtype_matches if s._signal_dimension == signal_dimension - ] - dtype_dim_type_matches = [ - s - for s in dtype_dim_matches - if signal_type == s._signal_type or signal_type in s._alias_signal_types - ] + dtype_dim_matches = [] + for s in dtype_matches: + if s._signal_dimension == signal_dimension: + dtype_dim_matches.append(s) + dtype_dim_type_matches = [] + for s in dtype_dim_matches: + if signal_type == s._signal_type or signal_type in s._alias_signal_types: + dtype_dim_type_matches.append(s) if len(dtype_dim_type_matches) == 1: matches = dtype_dim_type_matches @@ -371,9 +356,7 @@ def _save( 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`. + """Write a signal to file in a supported format. Parameters ---------- @@ -389,75 +372,89 @@ def _save( file. **kwargs Keyword arguments passed to the writer. + + Raises + ------ + ValueError + If file extension does not correspond to any supported format. + ValueError + If the file format cannot write the signal data. + ValueError + If the overwrite parameter is invalid. + + Notes + ----- + This function is a modified version of :func:`hyperspy.io.save`. """ filename = str(filename) ext = os.path.splitext(filename)[1][1:] if ext == "": # Will write to kikuchipy's h5ebsd format ext = "h5" - filename = filename + "." + ext + filename += "." + ext writer = None - for plugin in plugins: - if ext.lower() in plugin.file_extensions and plugin.writes: + for plugin in PLUGINS: + if ext.lower() in plugin["file_extensions"] and plugin["writes"]: writer = plugin break if writer is None: raise ValueError( f"{ext!r} does not correspond to any supported format. Supported file " - f"extensions are: {strlist2enumeration(default_write_ext)!r}" + f"extensions are: {WRITE_EXTENSIONS!r}" ) else: - sd = signal.axes_manager.signal_dimension - nd = signal.axes_manager.navigation_dimension - if writer.writes is not True and (sd, nd) not in writer.writes: - # Get writers that can write this data - writing_plugins = [] - for plugin in plugins: + sig_dim = signal.axes_manager.signal_dimension + nav_dim = signal.axes_manager.navigation_dimension + if writer["writes"] is not True and [sig_dim, nav_dim] not in writer["writes"]: + compatible_plugin_names = [] + for plugin in PLUGINS: if ( - plugin.writes is True - or plugin.writes is not False - and (sd, nd) in plugin.writes + plugin["writes"] is True + or plugin["writes"] is not False + and [sig_dim, nav_dim] in plugin["writes"] ): - writing_plugins.append(plugin) + compatible_plugin_names.append(plugin["name"]) raise ValueError( - "This file format cannot write this data. The following formats can: " - f"{strlist2enumeration(writing_plugins)!r}" + f"Chosen IO plugin {writer['name']!r} cannot write this data. The " + f"following plugins can: {compatible_plugin_names}" ) _ensure_directory(filename) is_file = os.path.isfile(filename) # Check if we are to add signal to an already existing h5ebsd file - if ( - writer.format_name == "kikuchipy_h5ebsd" - and overwrite is not True - and is_file - ): + if writer["name"] == "kikuchipy_h5ebsd" and overwrite is not True and is_file: if add_scan is None: - q = f"Add scan to {filename!r} (y/n)?\n" - add_scan = _get_input_bool(q) + add_scan = _get_input_bool( + f"Add scan to {filename!r} (y/n)?\n", + ( + "Your terminal does not support raw input. Not adding scan. To " + "add the scan, pass 'add_scan=True'" + ), + ) if add_scan: - overwrite = True # So that the 2nd statement below triggers + # So that the 2nd statement below triggers + overwrite = True kwargs["add_scan"] = add_scan # Determine if signal is to be written to file or not if overwrite is None: - write = overwrite_method(filename) # Ask what to do + write = _overwrite(filename) # Ask what to do elif overwrite is True or (overwrite is False and not is_file): - write = True # Write the file + write = True elif overwrite is False and is_file: - write = False # Don't write the file + write = False else: raise ValueError( - "overwrite parameter can only be None, True or False, and not " + "overwrite parameter can only be None, True, or False, and not " f"{overwrite}" ) - # Finally, write file if write: - writer.file_writer(filename, signal, **kwargs) + file_writer = importlib.import_module(writer["api"]).file_writer + file_writer(filename, signal, **kwargs) directory, filename = os.path.split(os.path.abspath(filename)) signal.tmp_parameters.set_item("folder", directory) signal.tmp_parameters.set_item("filename", os.path.splitext(filename)[0]) diff --git a/src/kikuchipy/io/_util.py b/src/kikuchipy/io/_util.py index 30633166..43f0480e 100644 --- a/src/kikuchipy/io/_util.py +++ b/src/kikuchipy/io/_util.py @@ -16,17 +16,20 @@ # along with kikuchipy. If not, see . import os +from pathlib import Path from typing import Any import warnings -def _get_input_bool(question: str) -> bool | None: +def _get_input_bool(question: str, warning: str) -> bool: """Get input from user on boolean choice, returning the answer. Parameters ---------- question Question to ask user. + warning + Warning to display if no good answer was given. """ try: answer = input(question) @@ -39,11 +42,21 @@ def _get_input_bool(question: str) -> bool | None: elif answer.lower() == "n": return False except OSError: - warnings.warn( - "Your terminal does not support raw input. Not adding scan. To add the scan" - " use `add_scan=True`" + warnings.warn(warning) + return False + + +def _overwrite(fname: Path | str) -> bool: + if Path(fname).is_file(): + return _get_input_bool( + f"Overwrite {fname!r} (y/n)?\n", + ( + "Your terminal does not support raw input. Not overwriting. To " + "overwrite the file, pass overwrite=True." + ), ) - return False + else: + return True def _get_input_variable(question: str, var_type: Any) -> Any | None: diff --git a/src/kikuchipy/io/plugins/__init__.pyi b/src/kikuchipy/io/plugins/__init__.pyi index b6c69ec8..8a6e3a21 100644 --- a/src/kikuchipy/io/plugins/__init__.pyi +++ b/src/kikuchipy/io/plugins/__init__.pyi @@ -15,34 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from . import ( - bruker_h5ebsd, - ebsd_directory, - edax_binary, - edax_h5ebsd, - emsoft_ebsd, - emsoft_ebsd_master_pattern, - emsoft_ecp_master_pattern, - emsoft_tkd_master_pattern, - kikuchipy_h5ebsd, - nordif, - nordif_calibration_patterns, - oxford_binary, - oxford_h5ebsd, -) +from . import plugins -__all__ = [ - "bruker_h5ebsd", - "ebsd_directory", - "edax_binary", - "edax_h5ebsd", - "emsoft_ebsd", - "emsoft_ebsd_master_pattern", - "emsoft_ecp_master_pattern", - "emsoft_tkd_master_pattern", - "kikuchipy_h5ebsd", - "nordif", - "nordif_calibration_patterns", - "oxford_binary", - "oxford_h5ebsd", -] +__all__ = ["plugins"] diff --git a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py index 43bf0fcb..3a9b783d 100644 --- a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py +++ b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py @@ -24,7 +24,7 @@ import numpy as np from kikuchipy.io.plugins._h5ebsd import _hdf5group2dict -from kikuchipy.io.plugins.emsoft_ebsd import _crystaldata2phase +from kikuchipy.io.plugins.emsoft_ebsd._api import _crystaldata2phase class EMsoftMasterPatternReader(abc.ABC): @@ -77,9 +77,9 @@ def read(self, **kwargs) -> list[dict]: fpath = Path(self.filename) mode = kwargs.pop("mode", "r") - f = h5py.File(fpath, mode=mode, **kwargs) + file = h5py.File(fpath, mode, **kwargs) - _check_file_format(f, self.diffraction_type) + _check_file_format(file, self.diffraction_type) # Set data group names diff_type = self.diffraction_type @@ -97,11 +97,11 @@ def read(self, **kwargs) -> list[dict]: "original_filename": fpath.name, }, } - nml_params = _hdf5group2dict(f["NMLparameters"], recursive=True) + nml_params = _hdf5group2dict(file["NMLparameters"], recursive=True) # Get phase information and add it to both the original metadata # and a Phase object - crystal_data = _hdf5group2dict(f["CrystalData"]) + crystal_data = _hdf5group2dict(file["CrystalData"]) nml_params["CrystalData"] = crystal_data phase = _crystaldata2phase(crystal_data) @@ -114,7 +114,7 @@ def read(self, **kwargs) -> list[dict]: phase.name = None # Get data shape and slices - data_group = f[data_group_path] + data_group = file[data_group_path] energies = data_group[self.energy_string][()] data_shape, data_slices = _get_data_shape_slices( npx=nml_params[name_list_name]["npx"], energies=energies, energy=self.energy @@ -192,8 +192,9 @@ def read(self, **kwargs) -> list[dict]: dim_idx += [2, 3] # Create axis object - axes = [ - { + axes = [] + for i, j in zip(range(data.ndim), dim_idx): + axis = { "size": data.shape[i], "index_in_array": i, "name": names[j], @@ -201,8 +202,7 @@ def read(self, **kwargs) -> list[dict]: "offset": offsets[j], "units": units[j], } - for i, j in zip(range(data.ndim), dim_idx) - ] + axes.append(axis) output = { "axes": axes, @@ -215,7 +215,7 @@ def read(self, **kwargs) -> list[dict]: } if not self.lazy: - f.close() + file.close() return [output] @@ -229,13 +229,13 @@ def _check_file_format(file: h5py.File, diffraction_type: str) -> None: file HDF5 file. diffraction_type - ``"EBSD"``, ``"TKD"`` or ``"ECP"``. + "EBSD", "TKD", or "ECP". Raises ------ KeyError If the program that created the file is not named - ``"EMmaster.f90"``. + "EMmaster.f90". IOError If the file is not in the EMsoft master pattern file format. """ @@ -245,7 +245,7 @@ def _check_file_format(file: h5py.File, diffraction_type: str) -> None: if program_name != f"EM{diffraction_type}master.f90": raise KeyError except KeyError: - raise IOError(f"'{file.filename}' is not in EMsoft's master pattern format") + raise IOError(f"{file.filename!r} is not in EMsoft's master pattern format") def _get_data_shape_slices( @@ -273,7 +273,7 @@ def _get_data_shape_slices( data_shape Shape of data. data_slices - Data to get, determined from ``energy``. + Data to get, determined from *energy*. """ data_shape = (npx * 2 + 1,) * 2 data_slices = (slice(None, None),) * 2 @@ -303,37 +303,41 @@ def _get_datasets( data_group HDF5 data group with data sets. projection - ``"stereographic"`` or ``"lambert"`` projection. + "stereographic" or "lambert" projection. hemisphere - ``"upper"``, ``"lower"`` or ``"both"`` hemisphere(s). + "upper", "lower", or "both" hemisphere(s). Returns ------- datasets List of HDF5 data sets. + + Raises + ------ + ValueError + If *projection* or *hemisphere* is not among the options. """ - hemisphere = hemisphere.lower() projection = projection.lower() - projections = {"stereographic": "masterSP", "lambert": "mLP"} - hemispheres = {"upper": "NH", "lower": "SH"} - - if projection not in projections.keys(): + if projection not in projections: raise ValueError( - f"'projection' value {projection} must be one of {list(projections.keys())}" + f"'projection' value {projection!r} must be one of {list(projections)}" ) + hemisphere = hemisphere.lower() + hemispheres = {"upper": "NH", "lower": "SH"} + + projection_label = projections[projection] if hemisphere == "both": - dset_names = [ - projections[projection] + hemispheres[h] for h in hemispheres.keys() - ] - datasets = [data_group[n] for n in dset_names] + datasets = [] + for hemi_label in hemispheres.values(): + datasets.append(data_group[projection_label + hemi_label]) elif hemisphere in hemispheres: - dset_name = projections[projection] + hemispheres[hemisphere] + dset_name = projection_label + hemispheres[hemisphere] datasets = [data_group[dset_name]] else: raise ValueError( - f"'hemisphere' value {hemisphere} must be one of {list(hemispheres.keys())}" + f"'hemisphere' value {hemisphere!r} must be one of {list(hemispheres)}" ) return datasets diff --git a/src/kikuchipy/io/plugins/_h5ebsd.py b/src/kikuchipy/io/plugins/_h5ebsd.py index 1de92d3c..d583e258 100644 --- a/src/kikuchipy/io/plugins/_h5ebsd.py +++ b/src/kikuchipy/io/plugins/_h5ebsd.py @@ -33,7 +33,7 @@ def _hdf5group2dict( group: h5py.Group, dictionary: dict | None = None, recursive: bool = False, - data_dset_names: list | None = None, + data_dset_names: list[str] | None = None, ) -> dict: """Return a dictionary with values from datasets in a group. @@ -44,14 +44,14 @@ def _hdf5group2dict( dictionary To fill dataset values into. recursive - Whether to add subgroups to ``dictionary`` (default is False). + Whether to add subgroups to *dictionary*. Default is False. data_dset_names List of names of HDF5 data sets with data to not read. Returns ------- dictionary - Dataset values in group (and subgroups if ``recursive=True``). + Dataset values in group (and subgroups if *recursive* is True). """ if data_dset_names is None: data_dset_names = [] @@ -137,7 +137,7 @@ class H5EBSDReader(abc.ABC): Keyword arguments passed to :class:`h5py.File`. """ - manufacturer_patterns = { + manufacturer_patterns: dict[str, str] = { "bruker nano": "RawPatterns", "edax": "Pattern", "kikuchipy": "patterns", @@ -158,7 +158,12 @@ def __repr__(self) -> str: @property 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] + names = [] + for group in self.scan_groups: + # Fails if group is anynomous, with None as name + name = group.name.lstrip("/") + names.append(name) + return names def check_file(self) -> None: """Check if the file is a valid h5ebsd file by searching for @@ -196,8 +201,8 @@ def check_file(self) -> None: 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 | None, str | None]: - """Get manufacturer and version from the top group. + def get_manufacturer_version(self) -> tuple[str, str]: + """Return manufacturer and version, read from the top group. Returns ------- @@ -205,6 +210,12 @@ def get_manufacturer_version(self) -> tuple[str | None, str | None]: File manufacturer. version File version. + + Raises + ------ + IOError + If either 'manufacturer' or 'version' datasets could not be + found in the top group. """ manufacturer = None version = None @@ -213,6 +224,10 @@ def get_manufacturer_version(self) -> tuple[str | None, str | None]: manufacturer = val.lower() elif key.lower() in ["version", "format version"]: version = val.lower() + if manufacturer is None: + raise IOError(f"Could not find 'manufacturer' key in {self.filename!r}") + elif version is None: + raise IOError(f"Could not find 'version' key in {self.filename!r}") return manufacturer, version def get_scan_groups(self) -> list[h5py.Group]: @@ -246,6 +261,11 @@ def get_desired_scan_groups( ------- scan_groups A list of the desired scan group(s). + + Raises + ------ + IOError + If the desired scan group is not among the available scans. """ # Get desired scan groups scan_groups = [] @@ -263,8 +283,8 @@ def get_desired_scan_groups( break if not scan_is_here: error_str = ( - f"Scan '{desired_name}' is not among the available scans " - f"{self.scan_group_names} in '{self.filename}'." + f"Scan {desired_name!r} is not among the available scans " + f"{self.scan_group_names} in {self.filename!r}" ) if len(group_names) == 1: raise IOError(error_str) @@ -446,7 +466,7 @@ def read( """Return a list of dictionaries which can be used to create :class:`~kikuchipy.signals.EBSD` signals. - The file is closed after reading if ``lazy=False``. + The file is closed after reading if *lazy* is False. Parameters ---------- @@ -454,8 +474,8 @@ def read( Name or a list of names of the desired top HDF5 group(s). If not given, the first scan group is returned. lazy - Read dataset lazily (default is False). If False, the file - is closed after reading. + Read dataset lazily. Default is False. If False, the file is + closed after reading. Returns ------- diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.pyi b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd.py b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py similarity index 68% rename from src/kikuchipy/io/plugins/bruker_h5ebsd.py rename to src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py index c49d3937..8eaf220a 100644 --- a/src/kikuchipy/io/plugins/bruker_h5ebsd.py +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py @@ -26,44 +26,21 @@ from kikuchipy.detectors.ebsd_detector import EBSDDetector from kikuchipy.io.plugins._h5ebsd import H5EBSDReader, _hdf5group2dict -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "bruker_h5ebsd" -description = ( - "Read support for electron backscatter diffraction patterns stored " - "in an HDF5 file formatted in Bruker Nano's h5ebsd format, similar " - "to the format described in Jackson et al.: h5ebsd: an archival " - "data format for electron back-scatter diffraction data sets. " - "Integrating Materials and Manufacturing Innovation 2014 3:4, doi: " - "https://dx.doi.org/10.1186/2193-9772-3-4." -) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5", "h5ebsd"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - -# Unique HDF5 footprint -footprint = ["manufacturer", "version"] -manufacturer = "bruker nano" - class BrukerH5EBSDReader(H5EBSDReader): """Bruker Nano h5ebsd file reader. - The file contents are ment to be used for initializing a - :class:`~kikuchipy.signals.EBSD` signal. - Parameters ---------- filename Full file path of the HDF5 file. **kwargs Keyword arguments passed to :class:`h5py.File`. + + Notes + ----- + The file contents are meant to be used for initializing a + :class:`~kikuchipy.signals.EBSD` signal. """ def __init__(self, filename: str, **kwargs) -> None: @@ -77,16 +54,15 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: group Group with patterns. lazy - Whether to read dataset lazily (default is ``False``). + Whether to read dataset lazily. Default is False. Returns ------- scan_dict - Dictionary with keys ``"axes"``, ``"data"``, ``"metadata"``, - ``"original_metadata"``, ``"detector"``, - ``"static_background"``, and ``"xmap"``. This dictionary can - be passed as keyword arguments to create an - :class:`~kikuchipy.signals.EBSD` signal. + Dictionary with keys "axes", "data", "metadata", + "original_metadata", "detector", "static_background", and + "xmap". This dictionary can be passed as keyword arguments + to create an :class:`~kikuchipy.signals.EBSD` signal. Raises ------ @@ -103,25 +79,27 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: If pattern array is smaller than the data shape determined from other datasets in the file. """ - hd = _hdf5group2dict(group["EBSD/Header"], recursive=True) - dd = _hdf5group2dict(group["EBSD/Data"], data_dset_names=self.patterns_name) + header_dset = _hdf5group2dict(group["EBSD/Header"], recursive=True) + data_dset = _hdf5group2dict( + group["EBSD/Data"], data_dset_names=[self.patterns_name] + ) # Ensure file can be read - grid_type = hd.get("Grid Type") + grid_type = header_dset.get("Grid Type") if grid_type != "isometric": - raise IOError(f"Only square grids are supported, not {grid_type}") + raise IOError(f"Only square grids are supported, not {grid_type!r}") # Get region of interest (ROI, only rectangular shape supported) indices = None - roi = False try: sd = _hdf5group2dict(group["EBSD/SEM"]) iy = sd["IY"][()] ix = sd["IX"][()] roi = True except KeyError: - ny = hd["NROWS"] - nx = hd["NCOLS"] + ny = header_dset["NROWS"] + nx = header_dset["NCOLS"] + roi = False if roi: ny_roi, nx_roi, is_rectangular = _bruker_roi_is_rectangular(iy, ix) if is_rectangular: @@ -134,9 +112,9 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: raise ValueError("Only a rectangular region of interest is supported") # Get other data shapes - sy, sx = hd["PatternHeight"], hd["PatternWidth"] - dy, dx = hd["YSTEP"], hd["XSTEP"] - px_size = hd.get("DetectorFullHeightMicrons", 1) / hd.get( + sy, sx = header_dset["PatternHeight"], header_dset["PatternWidth"] + dy, dx = header_dset["YSTEP"], header_dset["XSTEP"] + px_size = header_dset.get("DetectorFullHeightMicrons", 1) / header_dset.get( "UnClippedPatternHeight", 1 ) @@ -145,20 +123,19 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: metadata = { "Acquisition_instrument": { "SEM": { - "beam_energy": hd.get("KV"), - "magnification": hd.get("Magnification"), - "working_distance": hd.get("WD"), + "beam_energy": header_dset.get("KV"), + "magnification": header_dset.get("Magnification"), + "working_distance": header_dset.get("WD"), }, }, "General": { - "original_filename": hd.get("OriginalFile", fname), + "original_filename": header_dset.get("OriginalFile", fname), "title": title, }, "Signal": {"signal_type": "EBSD", "record_by": "image"}, } scan_dict = {"metadata": metadata} - # --- Data data = self.get_data( group, data_shape=(ny, nx, sy, sx), @@ -173,24 +150,28 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: "manufacturer": self.manufacturer, "version": self.version, } - scan_dict["original_metadata"].update(hd) + scan_dict["original_metadata"].update(header_dset) # TODO: Use reader from orix xmap = CrystalMap.empty(shape=(ny, nx), step_sizes=(dy, dx)) scan_dict["xmap"] = xmap - scan_dict["static_background"] = hd.get("StaticBackground") + scan_dict["static_background"] = header_dset.get("StaticBackground") pc = np.column_stack( - (dd.get("PCX", 0.5), dd.get("PCY", 0.5), dd.get("DD", 0.5)) + ( + data_dset.get("PCX", 0.5), + data_dset.get("PCY", 0.5), + data_dset.get("DD", 0.5), + ) ) if pc.size > 3: pc = pc.reshape((ny, nx, 3)) scan_dict["detector"] = EBSDDetector( shape=(sy, sx), px_size=px_size, - tilt=hd.get("CameraTilt", 0.0), - sample_tilt=hd.get("Sample Tilt", 0.0), + tilt=header_dset.get("CameraTilt", 0.0), + sample_tilt=header_dset.get("Sample Tilt", 0.0), pc=pc, ) @@ -217,11 +198,11 @@ def file_reader( lazy: bool = False, **kwargs, ) -> list[dict]: - """Read electron backscatter diffraction patterns, a crystal map, + """Return electron backscatter diffraction patterns, a crystal map, and an EBSD detector from a Bruker h5ebsd file :cite:`jackson2014h5ebsd`. - Not ment to be used directly; use :func:`~kikuchipy.load`. + Not meant to be used directly; use :func:`~kikuchipy.load` instead. Parameters ---------- @@ -234,17 +215,17 @@ def file_reader( lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default - is ``False``. + is False. **kwargs Keyword arguments passed to :class:`h5py.File`. Returns ------- scan_dict_list - List of one or more dictionaries with the keys ``"axes"``, - ``"data"``, ``"metadata"``, ``"original_metadata"``, - ``"detector"``, ``"static_background"``, and ``"xmap"``. This - dictionary can be passed as keyword arguments to create an + List of one or more dictionaries with the keys "axes", "data", + "metadata", "original_metadata", "detector", + "static_background", and "xmap". This dictionary can be passed + as keyword arguments to create an :class:`~kikuchipy.signals.EBSD` signal. """ reader = BrukerH5EBSDReader(filename, **kwargs) diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/specification.yaml b/src/kikuchipy/io/plugins/bruker_h5ebsd/specification.yaml new file mode 100644 index 00000000..ecc958b9 --- /dev/null +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/specification.yaml @@ -0,0 +1,13 @@ +name: bruker_h5ebsd +description: > + Read support for electron backscatter diffraction patterns stored in + an HDF5 file formatted in Bruker Nano's h5ebsd format, similar to the + format described in Jackson et al.: h5ebsd: an archival data format + for electron back-scatter diffraction data sets. Integrating Materials + and Manufacturing Innovation 2014 3:4, + doi: https://dx.doi.org/10.1186/2193-9772-3-4. +file_extensions: ['h5', 'hdf5', 'h5ebsd'] +default_extension: 0 +writes: False +manufacturer: bruker nano +footprints: ['manufacturer', 'version'] diff --git a/src/kikuchipy/io/plugins/ebsd_directory/__init__.py b/src/kikuchipy/io/plugins/ebsd_directory/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsd_directory/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/ebsd_directory/__init__.pyi b/src/kikuchipy/io/plugins/ebsd_directory/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsd_directory/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/ebsd_directory.py b/src/kikuchipy/io/plugins/ebsd_directory/_api.py similarity index 87% rename from src/kikuchipy/io/plugins/ebsd_directory.py rename to src/kikuchipy/io/plugins/ebsd_directory/_api.py index 6a35e211..65e1e391 100644 --- a/src/kikuchipy/io/plugins/ebsd_directory.py +++ b/src/kikuchipy/io/plugins/ebsd_directory/_api.py @@ -30,24 +30,9 @@ import imageio.v3 as iio import numpy as np -__all__ = ["file_reader"] - - _logger = logging.getLogger(__name__) -# Plugin characteristics -# ---------------------- -format_name = "Directory of EBSD patterns" -description = "Read support for patterns in image files in a directory" -full_support = False -# Recognised file extension -file_extensions = ["tif", "tiff", "bmp", "png"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - - def file_reader( filename: str | Path, xy_pattern: str | None = None, @@ -68,17 +53,17 @@ def file_reader( Regular expression to extract map coordinates from the filenames. If not given, two regular expressions will be tried: assuming (x, y) = (5, 10), "_x5y10.tif" or "-5-10.bmp". - Valid ``xy_pattern`` equal to these are ``r"_x(\d+)y(\d+).tif"`` + Valid *xy_pattern* equal to these are ``r"_x(\d+)y(\d+).tif"`` and ``r"-(\d+)-(\d+).bmp"``, respectively. If none of these expressions match the first file's name in the directory, a warning is printed and the returned signal will have only one navigation dimension. show_progressbar Whether to show a progressbar when reading the signal into - memory when ``lazy=False``. + memory when *lazy* is False. lazy Read the patterns lazily without actually reading them from disk - until required. Default is ``False``. + until required. Default is False. Returns ------- @@ -119,7 +104,7 @@ def file_reader( nav_shape = (n_patterns,) else: # Read coordinates of each file - fn_idx_sets = dict() + fn_idx_sets = {} xy_coords = np.zeros((n_patterns, 2), dtype=int) for j, fn in enumerate(filenames): for i, idx in enumerate(re.search(xy_pattern, fn).groups()): @@ -135,7 +120,7 @@ def file_reader( warnings.warn( "Returned signal will have one navigation dimension as the number of " f"patterns found in the directory, {n_patterns}, does not match the " - f"navigation shape determined from the filenames, {nav_shape}." + f"navigation shape determined from the filenames, {nav_shape}" ) nav_shape = (n_patterns,) @@ -180,8 +165,9 @@ def file_reader( units = ["um"] * ndim scales = [1] * ndim axes_names = ["y", "x"][-nav_dim:] + ["dy", "dx"] - axes = [ - { + axes = [] + for i in range(ndim): + axis = { "size": data.shape[i], "index_in_array": i, "name": axes_names[i], @@ -189,10 +175,9 @@ def file_reader( "offset": 0.0, "units": units[i], } - for i in range(ndim) - ] - metadata = dict(Signal=dict(signal_type="EBSD", record_by="image")) + axes.append(axis) + metadata = {"Signal": {"signal_type": "EBSD", "record_by": "image"}} - scan = dict(axes=axes, data=data, metadata=metadata) + scan = {"axes": axes, "data": data, "metadata": metadata} return [scan] diff --git a/src/kikuchipy/io/plugins/ebsd_directory/specification.yaml b/src/kikuchipy/io/plugins/ebsd_directory/specification.yaml new file mode 100644 index 00000000..5b812d58 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsd_directory/specification.yaml @@ -0,0 +1,7 @@ +name: ebsd_directory +description: Read support for patterns in image files in a directory +file_extensions: ['tif', 'tiff', 'bmp', 'png'] +default_extension: 0 +writes: False +manufacturer: None +footprints: [] diff --git a/src/kikuchipy/io/plugins/edax_binary/__init__.py b/src/kikuchipy/io/plugins/edax_binary/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_binary/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/edax_binary/__init__.pyi b/src/kikuchipy/io/plugins/edax_binary/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_binary/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/edax_binary.py b/src/kikuchipy/io/plugins/edax_binary/_api.py similarity index 88% rename from src/kikuchipy/io/plugins/edax_binary.py rename to src/kikuchipy/io/plugins/edax_binary/_api.py index cc3fa843..a7f5f04f 100644 --- a/src/kikuchipy/io/plugins/edax_binary.py +++ b/src/kikuchipy/io/plugins/edax_binary/_api.py @@ -29,24 +29,6 @@ from kikuchipy.signals.util._dask import get_chunking -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "EDAX binary" -description = ( - "Read support for electron backscatter diffraction patterns stored " - "in a binary EDAX TSL's UP1/UP2 file extension '.up1' or '.up2'. " - "The reader is adapted from the EDAX UP1/2 reader in PyEBSDIndex." -) -full_support = False -# Recognised file extension -file_extensions = ["up1", "up2"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - def file_reader( filename: str | Path, @@ -60,7 +42,7 @@ def file_reader( Parameters ---------- filename - File path to UP1/2 file with ``"up1"`` or ``"up2"`` extension. + File path to UP1/2 file with .up1 or .up2 extension. nav_shape Navigation shape, as (n map rows, n map columns), of the returned :class:`~kikuchipy.signals.EBSD` signal, matching the @@ -84,15 +66,15 @@ def file_reader( ValueError If file version is 2, since only version 1 or >= 3 is supported. ValueError - If ``nav_shape`` does not match the number of patterns in - the file. + If *nav_shape* does not match the number of patterns in the + file. Warns ----- UserWarning If patterns were acquired in an hexagonal grid, since then the returned signal will have only one navigation dimension, even - though ``nav_shape`` is given. + though *nav_shape* is given. Notes ----- @@ -126,12 +108,7 @@ def __init__(self, file: BinaryIO) -> None: raise ValueError("Only files with version 1 or >= 3, not 2, can be read") def read_header(self) -> dict[str, int]: - """Read and return header information. - - Returns - ------- - dictionary - """ + """Return read header information.""" self.file.seek(4) sx, sy, pattern_offset = np.fromfile(self.file, "uint32", count=3) file_size = Path(self.file.name).stat().st_size @@ -230,8 +207,9 @@ def read_scan( units = ["um"] * ndim scales = [header["dy"], header["dx"]] + [1, 1][:nav_dim] axes_names = ["y", "x"][-nav_dim:] + ["dy", "dx"] - axes = [ - { + axes: list[dict] = [] + for i in range(ndim): + axis = { "size": data.shape[i], "index_in_array": i, "name": axes_names[i], @@ -239,8 +217,7 @@ def read_scan( "offset": 0.0, "units": units[i], } - for i in range(ndim) - ] + axes.append(axis) fname = self.file.name metadata = { "General": { diff --git a/src/kikuchipy/io/plugins/edax_binary/specification.yaml b/src/kikuchipy/io/plugins/edax_binary/specification.yaml new file mode 100644 index 00000000..1ec2d9a3 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_binary/specification.yaml @@ -0,0 +1,10 @@ +name: edax_binary +description: > + Read support for electron backscatter diffraction patterns stored in a + binary EDAX TSL's UP1/UP2 file extension '.up1' or '.up2'. The reader + is adapted from the EDAX UP1/2 reader in PyEBSDIndex. +file_extensions: ['up1', 'up2'] +default_extension: 0 +writes: False +manufacturer: edax +footprints: [] diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.pyi b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd.py b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py similarity index 77% rename from src/kikuchipy/io/plugins/edax_h5ebsd.py rename to src/kikuchipy/io/plugins/edax_h5ebsd/_api.py index 48e2dc51..50aa4bac 100644 --- a/src/kikuchipy/io/plugins/edax_h5ebsd.py +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py @@ -25,31 +25,6 @@ from kikuchipy.detectors.ebsd_detector import EBSDDetector from kikuchipy.io.plugins._h5ebsd import H5EBSDReader, _hdf5group2dict -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "edax_h5ebsd" -description = ( - "Read support for electron backscatter diffraction patterns stored " - "in an HDF5 file formatted in EDAX TSL's h5ebsd format, similar to " - "the format described in Jackson et al.: h5ebsd: an archival data " - "format for electron back-scatter diffraction data sets. " - "Integrating Materials and Manufacturing Innovation 2014 3:4, doi: " - "https://dx.doi.org/10.1186/2193-9772-3-4." -) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5", "h5ebsd"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - -# Unique HDF5 footprint -footprint = ["manufacturer", "version"] -manufacturer = "edax" - class EDAXH5EBSDReader(H5EBSDReader): """EDAX TSL h5ebsd file reader. @@ -76,16 +51,15 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: group Group with patterns. lazy - Whether to read dataset lazily (default is ``False``). + Whether to read dataset lazily. Default is False. Returns ------- scan_dict - Dictionary with keys ``"axes"``, ``"data"``, ``"metadata"``, - ``"original_metadata"``, ``"detector"``, - ``"static_background"``, and ``"xmap"``. This dictionary can - be passed as keyword arguments to create an - :class:`~kikuchipy.signals.EBSD` signal. + Dictionary with keys "axes", "data", "metadata", + "original_metadata", "detector", "static_background", and + "xmap". This dictionary can be passed as keyword arguments + to create an :class:`~kikuchipy.signals.EBSD` signal. Raises ------ @@ -146,9 +120,9 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: scan_dict["detector"] = EBSDDetector( shape=(sy, sx), px_size=px_size, - tilt=hd.get("Camera Elevation Angle", 0), - azimuthal=hd.get("Camera Azimuthal Angle", 0), - sample_tilt=hd.get("Sample Tilt", 70), + tilt=hd.get("Camera Elevation Angle", 0.0), + azimuthal=hd.get("Camera Azimuthal Angle", 0.0), + sample_tilt=hd.get("Sample Tilt", 70.0), pc=( hd.get("Pattern Center Calibration", {}).get("x-star", 0.5), hd.get("Pattern Center Calibration", {}).get("y-star", 0.5), @@ -183,16 +157,15 @@ def file_reader( lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default - is ``False``. + is False. **kwargs Keyword arguments passed to :class:`h5py.File`. Returns ------- scan_dict_list - List of one or more dictionaries with the keys ``"axes"``, - ``"data"``, ``"metadata"``, ``"original_metadata"``, - ``"detector"``, and ``"xmap"``. This + List of one or more dictionaries with the keys "axes", "data", + "metadata", "original_metadata", "detector", and "xmap". This dictionary can be passed as keyword arguments to create an :class:`~kikuchipy.signals.EBSD` signal. """ diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/specification.yaml b/src/kikuchipy/io/plugins/edax_h5ebsd/specification.yaml new file mode 100644 index 00000000..a6b80f51 --- /dev/null +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/specification.yaml @@ -0,0 +1,13 @@ +name: edax_h5ebsd +description: > + Read support for electron backscatter diffraction patterns stored in + an HDF5 file formatted in EDAX TSL's h5ebsd format, similar to the + format described in Jackson et al.: h5ebsd: an archival data format + for electron back-scatter diffraction data sets. Integrating Materials + and Manufacturing Innovation 2014 3:4, + doi: https://dx.doi.org/10.1186/2193-9772-3-4. +file_extensions: ['h5', 'hdf5', 'h5ebsd'] +default_extension: 0 +writes: False +manufacturer: edax +footprints: ['manufacturer', 'version'] diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.pyi b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd.py b/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py similarity index 77% rename from src/kikuchipy/io/plugins/emsoft_ebsd.py rename to src/kikuchipy/io/plugins/emsoft_ebsd/_api.py index ee123dc7..20f6cc51 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py @@ -30,27 +30,6 @@ from kikuchipy.detectors.ebsd_detector import EBSDDetector from kikuchipy.io.plugins._h5ebsd import _hdf5group2dict -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "emsoft_ebsd" -description = ( - "Read support for dynamically simulated electron backscatter " - "diffraction patterns stored in EMsoft's HDF5 file format produced " - "by their EMEBSD.f90 program." -) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5"] -default_extension = 0 -# Writing capabilities -writes = False - -# Unique HDF5 footprint -footprint = ["emdata/ebsd/ebsdpatterns"] - def file_reader( filename: str | Path, @@ -72,23 +51,23 @@ def file_reader( lazy Open the data lazily without actually reading the data from disk until requested. Allows opening datasets larger than available - memory. Default is ``False``. + memory. Default is False. **kwargs Keyword arguments passed to :class:`h5py.File`. Returns ------- signal_dict_list - Data, axes, metadata and original metadata. + Data, axes, metadata, and original metadata. """ mode = kwargs.pop("mode", "r") - f = h5py.File(filename, mode=mode, **kwargs) + file = h5py.File(filename, mode=mode, **kwargs) - _check_file_format(f) + _check_file_format(file) - group = f["/"] - hd = _hdf5group2dict(group, data_dset_names=["EBSDPatterns"], recursive=True) - nml_dict = hd["NMLparameters"]["EBSDNameList"] + group = file["/"] + header = _hdf5group2dict(group, data_dset_names=["EBSDPatterns"], recursive=True) + nml_dict = header["NMLparameters"]["EBSDNameList"] # --- Metadata fname = os.path.basename(filename).split(".")[0] @@ -99,10 +78,10 @@ def file_reader( "General": {"original_filename": fname, "title": fname}, "Signal": {"signal_type": "EBSD", "record_by": "image"}, } - scan = {"metadata": metadata, "original_metadata": hd} + scan = {"metadata": metadata, "original_metadata": header} # --- Data - dataset = f["EMData/EBSD/EBSDPatterns"] + dataset = file["EMData/EBSD/EBSDPatterns"] if lazy: chunks = "auto" if dataset.chunks is None else dataset.chunks data = da.from_array(dataset, chunks=chunks) @@ -128,8 +107,9 @@ def file_reader( units = ["px"] + units names = ["y"] + names scales = np.append([1], scales) - scan["axes"] = [ - { + axes = [] + for i in range(data.ndim): + axis = { "size": data.shape[i], "index_in_array": i, "name": names[i], @@ -137,15 +117,15 @@ def file_reader( "offset": 0, "units": units[i], } - for i in range(data.ndim) - ] + axes.append(axis) + scan["axes"] = axes # --- Crystal map - phase = _crystaldata2phase(_hdf5group2dict(f["CrystalData"])) - xtal_fname = f["EMData/EBSD/xtalname"][()][0].decode().split("/")[-1] + phase = _crystaldata2phase(_hdf5group2dict(file["CrystalData"])) + xtal_fname = file["EMData/EBSD/xtalname"][()][0].decode().split("/")[-1] phase.name, _ = os.path.splitext(xtal_fname) scan["xmap"] = CrystalMap( - rotations=Rotation.from_euler(f["EMData/EBSD/EulerAngles"][()]), + rotations=Rotation.from_euler(file["EMData/EBSD/EulerAngles"][()]), phase_list=PhaseList(phase), ) @@ -160,32 +140,27 @@ def file_reader( ) if not lazy: - f.close() + file.close() return [scan] def _check_file_format(file: h5py.File) -> None: - """Return whether the HDF file is in EMsoft's format. - - Parameters - ---------- - file: h5py:File - """ + """Return whether the HDF file is in EMsoft's format.""" try: program_name = file["EMheader/EBSD/ProgramName"][:][0].decode() if program_name != "EMEBSD.f90": raise KeyError except KeyError: raise IOError( - f"'{file.filename}' is not in EMsoft's format returned by their EMEBSD.f90 " + f"{file.filename!r} is not in EMsoft's format returned by their EMEBSD.f90 " "program." ) def _crystaldata2phase(dictionary: dict) -> Phase: - """Return a :class:`~orix.crystal_map.Phase` object from a - dictionary with EMsoft CrystalData group content. + """Return a phase from a dictionary with EMsoft CrystalData group + content. Parameters ---------- diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd/specification.yaml b/src/kikuchipy/io/plugins/emsoft_ebsd/specification.yaml new file mode 100644 index 00000000..15e64e5a --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/specification.yaml @@ -0,0 +1,10 @@ +name: emsoft_ebsd +description: > + Read support for dynamically simulated electron backscatter + diffraction patterns stored in EMsoft's HDF5 file format produced by + their EMEBSD.f90 program." +file_extensions: ['h5', 'hdf5'] +default_extension: 0 +writes: False +manufacturer: emsoft +footprints: ['emdata/ebsd/ebsdpatterns'] diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.pyi b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py similarity index 68% rename from src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py rename to src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py index a876289b..95f5f83b 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py @@ -22,25 +22,16 @@ from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "emsoft_ebsd_master_pattern" -description = ( - "Read support for simulated electron backscatter diffraction (EBSD)" - "master patterns stored in an EMsoft HDF5 file." -) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5"] -default_extension = 0 -# Writing capabilities -writes = False - -# Unique HDF5 footprint -footprint = ["emdata/ebsdmaster"] +ENERGY_ARG = """Desired beam energy or energy range. If not given (default), all + available energies are read. +""" +PROJECTION_ARG = """Projection(s) to read. Options are "stereographic" (default) or + "lambert". +""" +HEMISPHERE_ARG = """Projection hemisphere(s) to read. Options are "upper" (default), + "lower", or "both". If "both", these will be stacked in the + vertical navigation axis. +""" class EMsoftEBSDMasterPatternReader(EMsoftMasterPatternReader): @@ -50,7 +41,7 @@ def diffraction_type(self) -> str: @property def cl_parameters_group_name(self) -> str: - return "MCCL" # Monte Carlo openCL + return "MCCL" # Monte Carlo OpenCL @property def energy_string(self) -> str: @@ -73,28 +64,24 @@ def file_reader( Parameters ---------- filename - Full file path of the HDF file. + Full file path of the HDF5 file. energy - Desired beam energy or energy range. If not given (default), all - available energies are read. + %s projection - Projection(s) to read. Options are ``"stereographic"`` (default) - or ``"lambert"``. + %s hemisphere - Projection hemisphere(s) to read. Options are ``"upper"`` - (default), ``"lower"`` or ``"both"``. If ``"both"``, these will - be stacked in the vertical navigation axis. + %s lazy Open the data lazily without actually reading the data from disk until requested. Allows opening datasets larger than available - memory. Default is ``False``. + memory. Default is False. **kwargs Keyword arguments passed to :class:`h5py.File`. Returns ------- signal_dict_list - Data, axes, metadata and original metadata. + Data, axes, metadata, and original metadata. """ reader = EMsoftEBSDMasterPatternReader( filename=filename, @@ -104,3 +91,6 @@ def file_reader( lazy=lazy, ) return reader.read(**kwargs) + + +file_reader.__doc__ %= (ENERGY_ARG, PROJECTION_ARG, HEMISPHERE_ARG) diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/specification.yaml b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/specification.yaml new file mode 100644 index 00000000..1373c83e --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/specification.yaml @@ -0,0 +1,9 @@ +name: emsoft_ebsd_master_pattern +description: > + Read support for simulated electron backscatter diffraction (EBSD) + master patterns stored in an EMsoft HDF5 file. +file_extensions: ['h5', 'hdf5'] +default_extension: 0 +writes: False +manufacturer: emsoft +footprints: ['emdata/ebsdmaster'] diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.pyi b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py similarity index 70% rename from src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py rename to src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py index 9e002e11..8643bb83 100644 --- a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py @@ -21,26 +21,11 @@ from pathlib import Path from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader - -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "emsoft_ecp_master_pattern" -description = ( - "Read support for simulated electron channeling pattern (ECP)" - "master patterns stored in an EMsoft HDF5 file." +from kikuchipy.io.plugins.emsoft_ebsd_master_pattern._api import ( + ENERGY_ARG, + HEMISPHERE_ARG, + PROJECTION_ARG, ) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5"] -default_extension = 0 -# Writing capabilities -writes = False - -# Unique HDF5 footprint -footprint = ["emdata/ecpmaster"] class EMsoftECPMasterPatternReader(EMsoftMasterPatternReader): @@ -50,7 +35,7 @@ def diffraction_type(self) -> str: @property def cl_parameters_group_name(self) -> str: - return "MCCL" # Monte Carlo openCL + return "MCCL" # Monte Carlo OpenCL @property def energy_string(self) -> str: @@ -75,19 +60,15 @@ def file_reader( filename Full file path of the HDF file. energy - Desired beam energy or energy range. If not given (default), all - available energies are read. + %s projection - Projection(s) to read. Options are ``"stereographic"`` (default) - or ``"lambert"``. + %s hemisphere - Projection hemisphere(s) to read. Options are ``"upper"`` - (default), ``"lower"`` or ``"both"``. If ``"both"``, these will - be stacked in the vertical navigation axis. + %s lazy Open the data lazily without actually reading the data from disk until requested. Allows opening datasets larger than available - memory. Default is ``False``. + memory. Default is False. **kwargs Keyword arguments passed to :class:`h5py.File`. @@ -104,3 +85,6 @@ def file_reader( lazy=lazy, ) return reader.read(**kwargs) + + +file_reader.__doc__ %= (ENERGY_ARG, PROJECTION_ARG, HEMISPHERE_ARG) diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/specification.yaml b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/specification.yaml new file mode 100644 index 00000000..96fa8617 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/specification.yaml @@ -0,0 +1,9 @@ +name: emsoft_ecp_master_pattern +description: > + Read support for simulated electron channeling pattern (ECP) master + patterns stored in an EMsoft HDF5 file." +file_extensions: ['h5', 'hdf5'] +default_extension: 0 +writes: False +manufacturer: emsoft +footprints: ['emdata/ecpmaster'] diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.pyi b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py similarity index 70% rename from src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py rename to src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py index 318ab173..a64d3490 100644 --- a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern.py +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py @@ -21,26 +21,11 @@ from pathlib import Path from kikuchipy.io.plugins._emsoft_master_pattern import EMsoftMasterPatternReader - -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "emsoft_tkd_master_pattern" -description = ( - "Read support for simulated transmission kikuchi diffraction (TKD)" - "master patterns stored in an EMsoft HDF5 file." +from kikuchipy.io.plugins.emsoft_ebsd_master_pattern._api import ( + ENERGY_ARG, + HEMISPHERE_ARG, + PROJECTION_ARG, ) -full_support = False -# Recognised file extension -file_extensions = ["h5", "hdf5"] -default_extension = 0 -# Writing capabilities -writes = False - -# Unique HDF5 footprint -footprint = ["emdata/tkdmaster"] class EMsoftTKDMasterPatternReader(EMsoftMasterPatternReader): @@ -50,7 +35,7 @@ def diffraction_type(self) -> str: @property def cl_parameters_group_name(self) -> str: - return "MCCLfoil" # Monte Carlo openCL + return "MCCLfoil" # Monte Carlo OpenCL @property def energy_string(self) -> str: @@ -75,19 +60,15 @@ def file_reader( filename Full file path of the HDF file. energy - Desired beam energy or energy range. If not given (default), all - available energies are read. + %s projection - Projection(s) to read. Options are ``"stereographic"`` (default) - or ``"lambert"``. + %s hemisphere - Projection hemisphere(s) to read. Options are ``"upper"`` - (default), ``"lower"`` or ``"both"``. If ``"both"``, these will - be stacked in the vertical navigation axis. + %s lazy Open the data lazily without actually reading the data from disk until requested. Allows opening datasets larger than available - memory. Default is ``False``. + memory. Default is False. **kwargs Keyword arguments passed to :class:`h5py.File`. @@ -104,3 +85,6 @@ def file_reader( lazy=lazy, ) return reader.read(**kwargs) + + +file_reader.__doc__ %= (ENERGY_ARG, PROJECTION_ARG, HEMISPHERE_ARG) diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/specification.yaml b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/specification.yaml new file mode 100644 index 00000000..90bdf463 --- /dev/null +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/specification.yaml @@ -0,0 +1,9 @@ +name: emsoft_tkd_master_pattern +description: > + Read support for simulated transmission kikuchi diffraction (TKD) + master patterns stored in an EMsoft HDF5 file. +file_extensions: ['h5', 'hdf5'] +default_extension: 0 +writes: False +manufacturer: emsoft +footprints: ['emdata/tkdmaster'] diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.pyi b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.pyi new file mode 100644 index 00000000..c9131db6 --- /dev/null +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader, file_writer + +__all__ = ["file_reader", "file_writer"] diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py similarity index 83% rename from src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py rename to src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py index 02edc417..6512be8e 100644 --- a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd.py +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py @@ -23,11 +23,11 @@ import warnings import h5py -from hyperspy.io_plugins.hspy import overwrite_dataset import numpy as np from orix import __version__ as orix_version from orix.crystal_map import CrystalMap from orix.io.plugins.orix_hdf5 import crystalmap2dict, dict2crystalmap +from rsciio.hspy._api import overwrite_dataset from kikuchipy import __version__ as kikuchipy_version from kikuchipy.detectors.ebsd_detector import EBSDDetector @@ -35,38 +35,14 @@ from kikuchipy.io.plugins._h5ebsd import H5EBSDReader, _dict2hdf5group, _hdf5group2dict from kikuchipy.signals.util._crystal_map import _xmap_is_compatible_with_signal -__all__ = ["file_reader", "file_writer"] - if TYPE_CHECKING: # pragma: no cover - from kikuchipy.signals.ebsd import EBSD - -# Plugin characteristics -# ---------------------- -format_name = "kikuchipy_h5ebsd" -description = ( - "Read/write support for electron backscatter diffraction patterns " - "stored in an HDF5 file formatted in kikuchipy's h5ebsd format, " - "similar to the format described in Jackson et al.: h5ebsd: an " - "archival data format for electron back-scatter diffraction data " - "sets. Integrating Materials and Manufacturing Innovation 2014 3:4," - " doi: https://dx.doi.org/10.1186/2193-9772-3-4." -) -full_support = True -# Recognised file extension -file_extensions = ["h5", "hdf5", "h5ebsd"] -default_extension = 1 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = [(2, 2), (2, 1), (2, 0)] - -# Unique HDF5 footprint -footprint = ["manufacturer", "version"] -manufacturer = "kikuchipy" + from kikuchipy.signals.ebsd import EBSD, LazyEBSD class KikuchipyH5EBSDReader(H5EBSDReader): """kikuchipy h5ebsd file reader. - The file contents are ment to be used for initializing a + The file contents are meant to be used for initializing a :class:`~kikuchipy.signals.EBSD` signal. Parameters @@ -88,25 +64,24 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: group Group with patterns. lazy - Whether to read dataset lazily (default is ``False``). + Whether to read dataset lazily. Default is False. Returns ------- scan_dict - Dictionary with keys ``"axes"``, ``"data"``, ``"metadata"``, - ``"original_metadata"``, ``"detector"``, - ``"static_background"``, and ``"xmap"``. This dictionary can - be passed as keyword arguments to create an - :class:`~kikuchipy.signals.EBSD` signal. + Dictionary with keys "axes", "data", "metadata", + "original_metadata", "detector", "static_background", and + "xmap". This dictionary can be passed as keyword arguments + to create an :class:`~kikuchipy.signals.EBSD` signal. """ - hd = _hdf5group2dict(group["EBSD/Header"], recursive=True) + header = _hdf5group2dict(group["EBSD/Header"], recursive=True) # Note: When written, these were obtained from the # `axes_manager` attribute, not the `xmap` one - ny, nx = hd["n_rows"], hd["n_columns"] - sy, sx = hd["pattern_height"], hd["pattern_width"] - dy, dx = hd.get("step_y", 1), hd.get("step_x", 1) - px_size = hd.get("detector_pixel_size", 1) + ny, nx = header["n_rows"], header["n_columns"] + sy, sx = header["pattern_height"], header["pattern_width"] + dy, dx = header.get("step_y", 1), header.get("step_x", 1) + px_size = header.get("detector_pixel_size", 1) # --- Metadata fname, title = self.get_metadata_filename_title(group.name) @@ -133,7 +108,7 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: "manufacturer": self.manufacturer, "version": self.version, } - scan_dict["original_metadata"].update(hd) + scan_dict["original_metadata"].update(header) # --- Crystal map if "CrystalMap" in group["EBSD"]: @@ -146,10 +121,12 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: scan_dict["xmap"] = xmap # --- Static background - scan_dict["static_background"] = hd.get("static_background") + scan_dict["static_background"] = header.get("static_background") # --- Detector - pc = np.dstack((hd.get("pcx", 0.5), hd.get("pcy", 0.5), hd.get("pcz", 0.5))) + pc = np.dstack( + (header.get("pcx", 0.5), header.get("pcy", 0.5), header.get("pcz", 0.5)) + ) if pc.size > 3: try: pc = pc.reshape((ny, nx, 3)) @@ -179,10 +156,10 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: scan_dict["detector"] = EBSDDetector( shape=(sy, sx), px_size=px_size, - binning=hd.get("binning", 1), - tilt=hd.get("elevation_angle", 0), - azimuthal=hd.get("azimuth_angle", 0), - sample_tilt=hd.get("sample_tilt", 70), + binning=header.get("binning", 1), + tilt=header.get("elevation_angle", 0.0), + azimuthal=header.get("azimuth_angle", 0.0), + sample_tilt=header.get("sample_tilt", 70.0), pc=pc, ) @@ -199,9 +176,9 @@ def file_reader( and an EBSD detector from a kikuchipy h5ebsd file :cite:`jackson2014h5ebsd`. - Not ment to be used directly; use :func:`~kikuchipy.load`. + Not meant to be used directly; use :func:`~kikuchipy.load`. - The file is closed after reading if ``lazy=False``. + The file is closed after reading if *lazy* False. Parameters ---------- @@ -214,17 +191,17 @@ def file_reader( lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default - is ``False``. + is False. **kwargs Keyword arguments passed to :class:`h5py.File`. Returns ------- scan_dict_list - List of one or more dictionaries with the keys ``"axes"``, - ``"data"``, ``"metadata"``, ``"original_metadata"``, - ``"detector"``, ``"static_background"``, and ``"xmap"``. This - dictionary can be passed as keyword arguments to create an + List of one or more dictionaries with the keys "axes", "data", + "metadata", "original_metadata", "detector", + "static_background", and "xmap". This dictionary can be passed + as keyword arguments to create an :class:`~kikuchipy.signals.EBSD` signal. """ reader = KikuchipyH5EBSDReader(filename, **kwargs) @@ -242,8 +219,13 @@ class KikuchipyH5EBSDWriter: EBSD signal. add_scan Whether to add the signal to the file if it exists and is - closed. If the file exists but this is not ``True``, the file + closed. If the file exists but this is not True, the file will be overwritten. + + Raises + ------ + OSError + If the file is already open. """ def __init__( @@ -309,7 +291,7 @@ def check_file(self) -> None: IOError If the file was not created with kikuchipy, or if there are no groups in the top group containing the datasets - ``"EBSD/Data"`` and ``"EBSD/Header"``. + "EBSD/Data" and "EBSD/Header". """ error = None top_groups = _hdf5group2dict(self.file["/"]) @@ -348,7 +330,7 @@ def get_valid_scan_number(self, scan_number: int = 1) -> int: Returns ------- valid_scan_number - ``scan_number`` or a new valid number if an existing scan in + *scan_number* or a new valid number if an existing scan in the file has this number. Raises @@ -367,8 +349,8 @@ def get_valid_scan_number(self, scan_number: int = 1) -> int: return scan_number def get_xmap_dict(self) -> dict: - """Return a dictionary produced from :attr:`signal.xmap` or an - empty one. + """Return a dictionary produced from :attr:`signal`'s *xmap* + attribute, or an empty one. """ (ny, nx, *_), (dy, dx, _) = self.data_shape_scale xmap = self.signal.xmap @@ -394,8 +376,7 @@ def write(self, scan_number: int = 1, **kwargs) -> None: Raises ------ ValueError - If the file exists but ``add_scan`` is ``None`` or - ``False``. + If the file exists but *scan_number* is *None* or *False*. """ if self.file_exists: valid_scan_number = self.get_valid_scan_number(scan_number) @@ -475,11 +456,11 @@ def write(self, scan_number: int = 1, **kwargs) -> None: def file_writer( filename: str, - signal, + signal: "EBSD | LazyEBSD", add_scan: bool | None = None, scan_number: int = 1, **kwargs, -): +) -> None: """Write an :class:`~kikuchipy.signals.EBSD` or :class:`~kikuchipy.signals.LazyEBSD` signal to an existing but not open or new h5ebsd file. @@ -493,7 +474,7 @@ def file_writer( ---------- filename Full path of HDF5 file. - signal : kikuchipy.signals.EBSD or kikuchipy.signals.LazyEBSD + signal Signal instance. add_scan Add signal to an existing, but not open, h5ebsd file. If it does diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/specification.yaml b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/specification.yaml new file mode 100644 index 00000000..8d3f4840 --- /dev/null +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/specification.yaml @@ -0,0 +1,13 @@ +name: kikuchipy_h5ebsd +description: > + Read/write support for electron backscatter diffraction patterns + stored in an HDF5 file formatted in kikuchipy's h5ebsd format, similar + to the format described in Jackson et al.: h5ebsd: an archival data + format for electron back-scatter diffraction data sets. Integrating + Materials and Manufacturing Innovation 2014 3:4, + doi: https://dx.doi.org/10.1186/2193-9772-3-4. +file_extensions: ['h5', 'hdf5', 'h5ebsd'] +default_extension: 1 +writes: [[2, 2], [2, 1], [2, 0]] +manufacturer: kikuchipy +footprints: ['manufacturer', 'version'] diff --git a/src/kikuchipy/io/plugins/nordif/__init__.py b/src/kikuchipy/io/plugins/nordif/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/nordif/__init__.pyi b/src/kikuchipy/io/plugins/nordif/__init__.pyi new file mode 100644 index 00000000..c9131db6 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader, file_writer + +__all__ = ["file_reader", "file_writer"] diff --git a/src/kikuchipy/io/plugins/nordif.py b/src/kikuchipy/io/plugins/nordif/_api.py similarity index 87% rename from src/kikuchipy/io/plugins/nordif.py rename to src/kikuchipy/io/plugins/nordif/_api.py index 693a3b57..7e025db0 100644 --- a/src/kikuchipy/io/plugins/nordif.py +++ b/src/kikuchipy/io/plugins/nordif/_api.py @@ -17,6 +17,7 @@ """Reader and writer of EBSD patterns from a NORDIF binary file.""" +from io import TextIOWrapper import logging import os from pathlib import Path @@ -28,26 +29,14 @@ import numpy as np from orix.crystal_map import CrystalMap +from kikuchipy.constants import VisibleDeprecationWarning 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__) -# Plugin characteristics -# ---------------------- -format_name = "NORDIF" -description = "Read/write support for NORDIF pattern and setting files" -full_support = False -# Recognised file extension -file_extensions = ["dat"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = [(2, 2), (2, 1), (2, 0)] - def file_reader( filename: str | Path, @@ -59,26 +48,26 @@ def file_reader( ) -> list[dict]: """Read electron backscatter patterns from a NORDIF data file. - Not meant to be used directly; use :func:`~kikuchipy.load`. + Not meant to be used directly; use :func:`~kikuchipy.load` instead. Parameters ---------- filename File path to NORDIF data file. mmap_mode - Memory map mode. If not given, ``"r"`` is used unless - ``lazy=True``, in which case ``"c"`` is used. + Memory map mode. If not given, "r" is used unless *lazy* is + True, in which case "c" is used. scan_size Scan size in number of patterns in width and height. pattern_size Pattern size in detector pixels in width and height. setting_file - File path to NORDIF setting file (default is `Setting.txt` in - same directory as ``filename``). + File path to NORDIF setting file (default is "Setting.txt" in + same directory as *filename*). lazy Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default - is ``False``. + is False. Returns ------- @@ -86,7 +75,10 @@ def file_reader( Data, axes, metadata and original metadata. """ if mmap_mode is None: - mmap_mode = "r" if lazy else "c" + if lazy: + mmap_mode = "r" + else: + mmap_mode = "c" scan = {} @@ -94,9 +86,9 @@ def file_reader( if "+" in mmap_mode or ("write" in mmap_mode and "copyonwrite" != mmap_mode): if lazy: raise ValueError("Lazy loading does not support in-place writing") - f = open(filename, mode="r+b") + file = open(filename, mode="r+b") else: - f = open(filename, mode="rb") + file = open(filename, mode="rb") # Get metadata from setting file folder, _ = os.path.split(filename) @@ -115,7 +107,7 @@ def file_reader( "No setting file found and no scan_size or pattern_size detected in " "input arguments. These must be set if no setting file is provided" ) - warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning) + warnings.filterwarnings("ignore", category=VisibleDeprecationWarning) md = {} omd = {} detector_dict = None @@ -127,7 +119,7 @@ def file_reader( except FileNotFoundError: scan["static_background"] = None warnings.warn( - f"Could not read static background pattern '{static_bg_file}', however it " + f"Could not read static background pattern {static_bg_file!r}, however it " "can be set as 'EBSD.static_background'" ) @@ -155,10 +147,10 @@ def file_reader( # Read data from file data_size = ny * nx * sy * sx if not lazy: - f.seek(0) - data = np.fromfile(f, dtype="uint8", count=data_size) + file.seek(0) + data = np.fromfile(file, dtype="uint8", count=data_size) else: - data = np.memmap(f.name, mode=mmap_mode, dtype="uint8") + data = np.memmap(file.name, mode=mmap_mode, dtype="uint8") try: data = data.reshape((ny, nx, sy, sx)).squeeze() @@ -209,7 +201,7 @@ def file_reader( # --- Crystal map scan["xmap"] = CrystalMap.empty(shape=(ny, nx), step_sizes=(dy, dx)) - f.close() + file.close() return [scan] @@ -224,8 +216,8 @@ def _get_settings_from_file( filename File path of NORDIF setting file. pattern_type - Whether to read the ``"acquisition"`` (default) or - ``"calibration"`` settings. + Whether to read the "acquisition" (default) or "calibration" + settings. Returns ------- @@ -317,7 +309,9 @@ def _get_settings_from_file( return md, omd, scan_size, detector -def _get_string(content: list, expression: str, line_no: int, file) -> str: +def _get_string( + content: list, expression: str, line_no: int, file: TextIOWrapper +) -> str: """Get relevant part of string using regular expression. Parameters @@ -328,7 +322,7 @@ def _get_string(content: list, expression: str, line_no: int, file) -> str: Regular expression. line_no Line number to search in. - file : file instance + file File handle of open setting file. Returns @@ -339,8 +333,8 @@ def _get_string(content: list, expression: str, line_no: int, file) -> str: match = re.search(expression, content[line_no]) if match is None: warnings.warn( - f"Failed to read line {line_no - 1} in settings file '{file.name}' using " - f"regular expression '{expression}'" + f"Failed to read line {line_no - 1} in settings file {file.name!r} using " + f"regular expression {expression!r}" ) return "" else: @@ -427,9 +421,10 @@ def scale(x, return_tuple: bool = False): omd["area"]["shape_scaled"] = scale(omd["area"]["shape"], True) if omd["roi"]["shape_scaled"] != (ny, nx): + n_samples_calculated = tuple(int(i) for i in omd["roi"]["shape_scaled"]) _logger.debug( f"Number of samples {(ny, nx)} differs from the one calculated from " - f"area/ROI shapes {omd['roi']['shape_scaled']}" + f"area/ROI shapes {n_samples_calculated}" ) # Try to read area overview image @@ -443,9 +438,7 @@ def scale(x, return_tuple: bool = False): 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. + """Write an EBSD signal to a NORDIF binary file. Parameters ---------- @@ -454,10 +447,10 @@ def file_writer(filename: str, signal: "EBSD | LazyEBSD") -> None: signal Signal instance. """ - with open(filename, "wb") as f: + with open(filename, "wb") as file: if signal._lazy: - for pattern in signal._iterate_signal(): - np.array(pattern.flatten()).tofile(f) + for pattern in signal._iterate_signal(iterpath="flyback"): + np.array(pattern.flatten()).tofile(file) else: - for pattern in signal._iterate_signal(): - pattern.flatten().tofile(f) + for pattern in signal._iterate_signal(iterpath="flyback"): + pattern.flatten().tofile(file) diff --git a/src/kikuchipy/io/plugins/nordif/specification.yaml b/src/kikuchipy/io/plugins/nordif/specification.yaml new file mode 100644 index 00000000..813feb68 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif/specification.yaml @@ -0,0 +1,7 @@ +name: nordif +description: Read/write support for NORDIF's binary pattern file +file_extensions: ['dat'] +default_extension: 0 +writes: [[2, 2], [2, 1], [2, 0]] +manufacturer: nordif +footprints: [] diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.pyi b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns.py b/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py similarity index 67% rename from src/kikuchipy/io/plugins/nordif_calibration_patterns.py rename to src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py index c829318d..16639ba0 100644 --- a/src/kikuchipy/io/plugins/nordif_calibration_patterns.py +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py @@ -25,28 +25,14 @@ import numpy as np from kikuchipy.detectors.ebsd_detector import EBSDDetector -from kikuchipy.io.plugins.nordif import _get_settings_from_file - -__all__ = ["file_reader"] - - -# Plugin characteristics -# ---------------------- -format_name = "NORDIF calibration patterns" -description = "Read support for NORDIF's calibration patterns" -full_support = False -# Recognised file extension -file_extensions = ["txt"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False +from kikuchipy.io.plugins.nordif._api import _get_settings_from_file 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. + """Return NORDIF calibration electron backscatter diffraction + patterns in a directory with a settings text file. - Not meant to be used directly; use :func:`~kikuchipy.load`. + Not meant to be used directly; use :func:`~kikuchipy.load` instead. Parameters ---------- @@ -58,10 +44,12 @@ def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: Returns ------- scan - Data, axes, metadata and original metadata. + Data, axes, metadata, and original metadata. """ # Get metadata from setting file - md, omd, _, detector = _get_settings_from_file(filename, pattern_type="calibration") + metadata, orig_metadata, _, detector = _get_settings_from_file( + str(filename), pattern_type="calibration" + ) dirname = os.path.dirname(filename) scan = {} @@ -73,12 +61,12 @@ def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: except FileNotFoundError: scan["static_background"] = None warnings.warn( - f"Could not read static background pattern '{static_bg_file}', however it " + f"Could not read static background pattern {static_bg_file!r}, however it " "can be set as 'EBSD.static_background'" ) # Set required and other parameters in metadata - md.update( + metadata.update( { "General": { "original_filename": filename, @@ -87,12 +75,12 @@ def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: "Signal": {"signal_type": "EBSD", "record_by": "image"}, } ) - scan["metadata"] = md - scan["original_metadata"] = omd + scan["metadata"] = metadata + scan["original_metadata"] = orig_metadata scan["detector"] = EBSDDetector(**detector) - yx = omd["calibration_patterns"]["indices"] + yx = orig_metadata["calibration_patterns"]["indices"] data = _get_patterns(dirname=dirname, coordinates=yx) scan["data"] = data @@ -100,17 +88,19 @@ def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: units = ["um"] * 3 names = ["x", "dy", "dx"] scales = np.ones(3) - scan["axes"] = [ - { - "size": data.shape[i], - "index_in_array": i, - "name": names[i], - "scale": scales[i], - "offset": 0, - "units": units[i], - } - for i in range(data.ndim) - ] + axes = [] + for i in range(data.ndim): + axes.append( + { + "size": data.shape[i], + "index_in_array": i, + "name": names[i], + "scale": scales[i], + "offset": 0, + "units": units[i], + } + ) + scan["axes"] = axes return [scan] diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns/specification.yaml b/src/kikuchipy/io/plugins/nordif_calibration_patterns/specification.yaml new file mode 100644 index 00000000..1bb678e0 --- /dev/null +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/specification.yaml @@ -0,0 +1,7 @@ +name: nordif_calibration_patterns +description: Read support for NORDIF's calibration patterns. +file_extensions: ['txt'] +default_extension: 0 +writes: False +manufacturer: nordif +footprints: [] diff --git a/src/kikuchipy/io/plugins/oxford_binary/__init__.py b/src/kikuchipy/io/plugins/oxford_binary/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_binary/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/oxford_binary/__init__.pyi b/src/kikuchipy/io/plugins/oxford_binary/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_binary/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/oxford_binary.py b/src/kikuchipy/io/plugins/oxford_binary/_api.py similarity index 97% rename from src/kikuchipy/io/plugins/oxford_binary.py rename to src/kikuchipy/io/plugins/oxford_binary/_api.py index 2bf4c49b..bf79b13a 100644 --- a/src/kikuchipy/io/plugins/oxford_binary.py +++ b/src/kikuchipy/io/plugins/oxford_binary/_api.py @@ -34,21 +34,8 @@ from kikuchipy.signals.util._dask import get_chunking -__all__ = ["file_reader"] - _logger = logging.getLogger(__name__) -# Plugin characteristics -# ---------------------- -format_name = "Oxford binary" -description = "Read support for Oxford Instruments' binary .ebsp file." -full_support = False -# Recognised file extension -file_extensions = ["ebsp"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: """Read EBSD patterns from an Oxford Instruments' binary .ebsp file. @@ -64,7 +51,7 @@ def file_reader(filename: str | Path, lazy: bool = False) -> list[dict]: File path to .ebsp file. lazy Read the data lazily without actually reading the data from disk - until required. Default is ``False``. + until required. Default is False. Returns ------- @@ -192,7 +179,7 @@ def first_pattern_position(self) -> int: def pattern_is_present(self) -> np.ndarray: """Boolean array indicating whether a pattern listed in the file header is present in the file or not. If not, its - `pattern_starts` entry is zero. + :attr:`pattern_starts` entry is zero. """ return self.pattern_starts != 0 @@ -335,7 +322,7 @@ def get_pattern_footer_dtype(self, offset: int) -> list[tuple]: footer_dtype Format of each pattern footer as a list of tuples with a field name, data type and size. The format depends on the - :attr:`~self.version`. + :attr:`version`. """ self.file.seek(offset + self.pattern_header_size + self.n_bytes) footer_dtype = () diff --git a/src/kikuchipy/io/plugins/oxford_binary/specification.yaml b/src/kikuchipy/io/plugins/oxford_binary/specification.yaml new file mode 100644 index 00000000..11098250 --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_binary/specification.yaml @@ -0,0 +1,7 @@ +name: oxford_binary +description: Read support for Oxford Instruments' binary .ebsp file. +file_extensions: ['ebsp'] +default_extension: 0 +writes: False +manufacturer: oxford +footprints: [] diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py new file mode 100644 index 00000000..2f857066 --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.pyi b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.pyi new file mode 100644 index 00000000..65a3a663 --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.pyi @@ -0,0 +1,20 @@ +# Copyright 2019-2024 The kikuchipy developers +# +# This file is part of kikuchipy. +# +# kikuchipy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kikuchipy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kikuchipy. If not, see . + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py similarity index 74% rename from src/kikuchipy/io/plugins/oxford_h5ebsd.py rename to src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py index 1a19500f..dce3463b 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py @@ -28,35 +28,13 @@ from kikuchipy.detectors.ebsd_detector import EBSDDetector from kikuchipy.io.plugins._h5ebsd import H5EBSDReader, _hdf5group2dict -__all__ = ["file_reader"] - _logger = logging.getLogger(__name__) -# Plugin characteristics -# ---------------------- -format_name = "oxford_h5ebsd" -description = ( - "Read support for electron backscatter diffraction patterns stored " - "in an HDF5 file formatted in Oxford Instruments' h5ebsd format, " - "named H5OINA. The format is similar to the format described in " - "Jackson et al.: h5ebsd: an archival data format for electron " - "back-scatter diffraction data sets. Integrating Materials and " - "Manufacturing Innovation 2014 3:4, doi: " - "https://dx.doi.org/10.1186/2193-9772-3-4." -) -full_support = False -# Recognised file extension -file_extensions = ["h5oina"] -default_extension = 0 -# Writing capabilities (signal dimensions, navigation dimensions) -writes = False - - class OxfordH5EBSDReader(H5EBSDReader): """Oxford Instruments h5ebsd (H5OINA) file reader. - The file contents are ment to be used for initializing a + The file contents are meant to be used for initializing a :class:`~kikuchipy.signals.EBSD` signal. Parameters @@ -78,7 +56,7 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: group Group with patterns. lazy - Whether to read dataset lazily (default is False). + Whether to read dataset lazily. Default is False. Returns ------- @@ -93,13 +71,15 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: IOError If patterns are not acquired in a square grid. """ - hd = _hdf5group2dict(group["EBSD/Header"], recursive=True) - dd = _hdf5group2dict(group["EBSD/Data"], data_dset_names=self.patterns_name) + header_group = _hdf5group2dict(group["EBSD/Header"], recursive=True) + data_group = _hdf5group2dict( + group["EBSD/Data"], data_dset_names=[self.patterns_name] + ) # Get data shapes - ny, nx = hd["Y Cells"], hd["X Cells"] - sy, sx = hd["Pattern Height"], hd["Pattern Width"] - dy, dx = hd.get("Y Step", 1), hd.get("X Step", 1) + ny, nx = header_group["Y Cells"], header_group["X Cells"] + sy, sx = header_group["Pattern Height"], header_group["Pattern Width"] + dy, dx = header_group.get("Y Step", 1), header_group.get("X Step", 1) px_size = 1.0 # --- Metadata @@ -107,9 +87,9 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: metadata = { "Acquisition_instrument": { "SEM": { - "beam_energy": hd.get("Beam Voltage"), - "magnification": hd.get("Magnification"), - "working_distance": hd.get("Working Distance"), + "beam_energy": header_group.get("Beam Voltage"), + "magnification": header_group.get("Magnification"), + "working_distance": header_group.get("Working Distance"), }, }, "General": {"original_filename": fname, "title": title}, @@ -129,7 +109,7 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: "manufacturer": self.manufacturer, "version": self.version, } - scan_dict["original_metadata"].update(hd) + scan_dict["original_metadata"].update(header_group) # --- Crystal map # TODO: Implement reader of Oxford Instruments h5ebsd crystal @@ -138,14 +118,14 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: scan_dict["xmap"] = xmap # --- Static background - scan_dict["static_background"] = hd.get("Processed Static Background") + scan_dict["static_background"] = header_group.get("Processed Static Background") # --- Detector pc = np.column_stack( ( - dd.get("Pattern Center X", 0.5), - dd.get("Pattern Center Y", 0.5), - dd.get("Detector Distance", 0.5), + data_group.get("Pattern Center X", 0.5), + data_group.get("Pattern Center Y", 0.5), + data_group.get("Detector Distance", 0.5), ) ) if pc.size > 3: @@ -153,15 +133,15 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: detector_kw = dict( shape=(sy, sx), pc=pc, - sample_tilt=np.rad2deg(hd.get("Tilt Angle", np.deg2rad(70))), + sample_tilt=np.rad2deg(header_group.get("Tilt Angle", np.deg2rad(70))), convention="oxford", ) - detector_tilt_euler = hd.get("Detector Orientation Euler") + detector_tilt_euler = header_group.get("Detector Orientation Euler") try: detector_kw["tilt"] = np.rad2deg(detector_tilt_euler[1]) - 90 except (IndexError, TypeError): # pragma: no cover _logger.debug("Could not read detector tilt") - binning_str = hd.get("Camera Binning Mode") + binning_str = header_group.get("Camera Binning Mode") try: detector_kw["binning"] = int(binning_str.split("x")[0]) except (IndexError, ValueError): # pragma: no cover diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/specification.yaml b/src/kikuchipy/io/plugins/oxford_h5ebsd/specification.yaml new file mode 100644 index 00000000..7a22a61e --- /dev/null +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/specification.yaml @@ -0,0 +1,14 @@ +name: oxford_h5ebsd +description: > + Read support for electron backscatter diffraction patterns stored in + an HDF5 file formatted in Oxford Instruments' h5ebsd format, named + H5OINA. The format is similar to the format described in Jackson et + al.: h5ebsd: an archival data format for electron back-scatter + diffraction data sets. Integrating Materials and Manufacturing + Innovation 2014 3:4, + doi: https://dx.doi.org/10.1186/2193-9772-3-4. +file_extensions: ['h5oina'] +default_extension: 0 +writes: False +manufacturer: oxford +footprints: [] diff --git a/src/kikuchipy/signals/_kikuchipy_signal.py b/src/kikuchipy/signals/_kikuchipy_signal.py index a8c234fd..97106717 100644 --- a/src/kikuchipy/signals/_kikuchipy_signal.py +++ b/src/kikuchipy/signals/_kikuchipy_signal.py @@ -25,9 +25,9 @@ import dask.array as da from hyperspy._lazy_signals import LazySignal2D -from hyperspy.misc.rgb_tools import rgb_dtypes from hyperspy.signals import Signal2D import numpy as np +from rsciio.utils.rgb_tools import rgb_dtypes from skimage.util.dtype import dtype_range import yaml @@ -57,7 +57,7 @@ class KikuchipySignal2D(Signal2D): """General class for image signals in kikuchipy, extending HyperSpy's Signal2D class with some methods for carrying over custom - properties and some methods for intensity manipulation. + properties and others for manipulation of image intensities. Not meant to be used directly, see derived classes like :class:`~kikuchipy.signals.EBSD`. @@ -90,60 +90,57 @@ def rescale_intensity( ) -> Any | None: """Rescale image intensities. - Output min./max. intensity is determined from ``out_range`` or + Output min./max. intensity is determined from *out_range* or the data type range of the :class:`numpy.dtype` passed to - ``dtype_out`` if ``out_range`` is ``None``. - - This method is based on - :func:`skimage.exposure.rescale_intensity`. + *dtype_out* if *out_range* is None. Parameters ---------- relative - Whether to keep relative intensities between images (default - is ``False``). If ``True``, ``in_range`` must be ``None``, - because ``in_range`` is in this case set to the global - min./max. intensity. Use with care, as this requires the - computation of the min./max. intensity of the signal before - rescaling. + Whether to keep relative intensities between images. Default + is False. If True, *in_range* must be None, because + *in_range* is in this case set to the global min./max. + intensity. Use with care, as this requires the computation + of the min./max. intensity of the signal before rescaling. in_range Min./max. intensity of input images. If not given, - ``in_range`` is set to pattern min./max intensity. Contrast - stretching is performed when ``in_range`` is set to a - narrower intensity range than the input patterns. Must be - ``None`` if ``relative=True`` or ``percentiles`` are passed. + *in_range* is set to pattern min./max intensity. Contrast + stretching is performed when *in_range* is set to a narrower + intensity range than the input patterns. Must be None if + *relative* is True or *percentiles* are given. out_range Min./max. intensity of output images. If not given, - ``out_range`` is set to ``dtype_out`` min./max according to - ``skimage.util.dtype.dtype_range``. + *out_range* is set to *dtype_out* min./max according to + :func:`skimage.util.dtype.dtype_range`. dtype_out - Data type of rescaled images, default is input images' data + Data type of rescaled images. Default is input images' data type. percentiles Disregard intensities outside these percentiles. Calculated - per image. Must be ``None`` if ``in_range`` or ``relative`` - is passed. Default is ``None``. + per image. Must be None if *in_range* or *relative* + is passed. Default is None. show_progressbar Whether to show a progressbar. If not given, the value of :obj:`hyperspy.api.preferences.General.show_progressbar` is used. inplace Whether to operate on the current signal or return a new - one. Default is ``True``. + one. Default is True. lazy_output Whether the returned signal is lazy. If not given this - follows from the current signal. Can only be ``True`` if - ``inplace=False``. + follows from the current signal. Can only be True if + *inplace* is False. Returns ------- s_out - Rescaled signal, returned if ``inplace=False``. Whether - it is lazy is determined from ``lazy_output``. + Rescaled signal, returned if *inplace* is False. Whether + it is lazy is determined from *lazy_output*. See Also -------- - :func:`skimage.exposure.rescale_intensity` + :func:`skimage.exposure.rescale_intensity` : + This method is based on this function. Notes ----- @@ -158,8 +155,8 @@ def rescale_intensity( Image intensities are stretched to fill the available grey levels in the input images' data type range or any data type - range passed to ``dtype_out``, either keeping relative - intensities between images or not + range passed to *dtype_out*, either keeping relative intensities + between images or not >>> print( ... s.data.dtype, s.data.min(), s.data.max(), @@ -186,21 +183,21 @@ def rescale_intensity( Here, the darkest and brightest pixels within the 1% percentile are set to the ends of the data type range, e.g. 0 and 255 - respectively for images of ``uint8`` data type. + respectively for images of "uint8" data type. """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") if self.data.dtype in rgb_dtypes.values(): raise NotImplementedError( - "Use RGB channel normalization when creating the image instead." + "Use RGB channel normalization when creating the image instead" ) # Determine min./max. intensity of input image to rescale to if in_range is not None and percentiles is not None: - raise ValueError("'percentiles' must be None if 'in_range' is not None.") + raise ValueError("'percentiles' must be None if 'in_range' is not None") elif relative is True and in_range is not None: - raise ValueError("'in_range' must be None if 'relative' is True.") + raise ValueError("'in_range' must be None if 'relative' is True") elif relative: # Scale relative to min./max. intensity in images in_range = (self.data.min(), self.data.max()) in_range = tuple(da.compute(in_range)[0]) @@ -213,15 +210,14 @@ def rescale_intensity( if out_range is None: out_range = dtype_range[dtype_out.type] - map_kw = dict( - show_progressbar=show_progressbar, - parallel=True, - output_dtype=dtype_out, - in_range=in_range, - out_range=out_range, - dtype_out=dtype_out, - percentiles=percentiles, - ) + map_kw = { + "show_progressbar": show_progressbar, + "output_dtype": dtype_out, + "in_range": in_range, + "out_range": out_range, + "dtype_out": dtype_out, + "percentiles": percentiles, + } attrs = self._get_custom_attributes() if inplace: self.map(rescale_intensity, inplace=True, **map_kw) @@ -249,10 +245,10 @@ def normalize_intensity( ---------- num_std Number of standard deviations of the output intensities. - Default is ``1``. + Default is 1. divide_by_square_root Whether to divide output intensities by the square root of - the signal dimension size. Default is ``False``. + the signal dimension size. Default is False. dtype_out Data type of normalized images. If not given, the input images' data type is used. @@ -262,24 +258,23 @@ def normalize_intensity( is used. inplace Whether to operate on the current signal or return a new - one. Default is ``True``. + one. Default is True. lazy_output Whether the returned signal is lazy. If not given this - follows from the current signal. Can only be ``True`` if - ``inplace=False``. + follows from the current signal. Can only be True if + *inplace* is False. Returns ------- s_out - Normalized signal, returned if ``inplace=False``. Whether - it is lazy is determined from ``lazy_output``. + Normalized signal, returned if *inplace* is False. Whether + it is lazy is determined from *lazy_output*. Notes ----- Data type should always be changed to floating point, e.g. - ``float32`` with - :meth:`~hyperspy.signal.BaseSignal.change_dtype`, before - normalizing the intensities. + "float32" with :meth:`~hyperspy.signal.BaseSignal.change_dtype`, + before normalizing the intensities. Rescaling RGB images is not possible. Use RGB channel normalization when creating the image instead. @@ -296,11 +291,11 @@ def normalize_intensity( 0.0 """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") if self.data.dtype in rgb_dtypes.values(): raise NotImplementedError( - "Use RGB channel normalization when creating the image instead." + "Use RGB channel normalization when creating the image instead" ) if dtype_out is None: @@ -308,14 +303,13 @@ def normalize_intensity( else: dtype_out = np.dtype(dtype_out) - map_kw = dict( - show_progressbar=show_progressbar, - parallel=True, - output_dtype=dtype_out, - num_std=num_std, - divide_by_square_root=divide_by_square_root, - dtype_out=dtype_out, - ) + map_kw = { + "show_progressbar": show_progressbar, + "output_dtype": dtype_out, + "num_std": num_std, + "divide_by_square_root": divide_by_square_root, + "dtype_out": dtype_out, + } attrs = self._get_custom_attributes() if inplace: self.map(normalize_intensity, inplace=True, **map_kw) @@ -349,27 +343,27 @@ def adaptive_histogram_equalization( image width. clip_limit Clipping limit, normalized between 0 and 1 (higher values - give more contrast). Default is ``0``. + give more contrast). Default is 0. nbins Number of gray bins for histogram ("data range"), default is - ``128``. + 128. show_progressbar Whether to show a progressbar. If not given, the value of :obj:`hyperspy.api.preferences.General.show_progressbar` is used. inplace Whether to operate on the current signal or return a new - one. Default is ``True``. + one. Default is True. lazy_output Whether the returned signal is lazy. If not given this - follows from the current signal. Can only be ``True`` if - ``inplace=False``. + follows from the current signal. Can only be True if + *inplace* is False. Returns ------- s_out - Equalized signal, returned if ``inplace=False``. Whether it - is lazy is determined from ``lazy_output``. + Equalized signal, returned if *inplace* is False. Whether it + is lazy is determined from *lazy_output*. See Also -------- @@ -413,7 +407,7 @@ def adaptive_histogram_equalization( >>> _ = ax3.plot(hist2) """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") dtype_out = self.data.dtype if np.issubdtype(dtype_out, np.floating): @@ -444,14 +438,13 @@ def adaptive_histogram_equalization( raise ValueError(f"Incorrect value of `shape`: {kernel_size}") kernel_size = [int(k) for k in kernel_size] - map_kw = dict( - show_progressbar=show_progressbar, - parallel=True, - output_dtype=dtype_out, - kernel_size=kernel_size, - clip_limit=clip_limit, - nbins=nbins, - ) + map_kw = { + "show_progressbar": show_progressbar, + "output_dtype": dtype_out, + "kernel_size": kernel_size, + "clip_limit": clip_limit, + "nbins": nbins, + } attrs = self._get_custom_attributes() if inplace: self.map(_adaptive_histogram_equalization, inplace=True, **map_kw) @@ -478,7 +471,7 @@ def _get_custom_attributes(self, make_deepcopy: bool = False) -> dict: ---------- make_deepcopy Whether the returned dictionary should contain deep copies - of each attribute. Default is ``False``. + of each attribute. Default is False. Returns ------- @@ -518,16 +511,16 @@ def _set_custom_attributes( Dictionary of custom attributes. make_deepcopy Whether to make a deepcopy of all attributes before setting - them in this instance. Default is ``False``. + them in this instance. Default is False. make_lazy Whether to cast attributes which are :class:`~numpy.ndarray` to :class:`~dask.array.Array` before - setting them. Default is ``False``. + setting them. Default is False. unmake_lazy Whether to cast attributes which are :class:`~dask.array.Array` to :class:`~numpy.ndarray` before - setting them. Default is ``False``. Ignored if both this and - ``make_lazy`` are ``True``. + setting them. Default is False. Ignored if both this and + *make_lazy* are True. """ for name, value in attributes.items(): if name in self._custom_attributes: diff --git a/src/kikuchipy/signals/ebsd.py b/src/kikuchipy/signals/ebsd.py index b59387dc..395f6e49 100644 --- a/src/kikuchipy/signals/ebsd.py +++ b/src/kikuchipy/signals/ebsd.py @@ -22,6 +22,7 @@ import gc import logging import os +from pathlib import Path from typing import TYPE_CHECKING, Iterable import warnings @@ -505,7 +506,7 @@ def remove_static_background( array. """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") dtype = np.float32 # During processing dtype_out = self.data.dtype.type @@ -541,16 +542,15 @@ def remove_static_background( else: operation_func = _remove_static_background_divide - map_kw = dict( - show_progressbar=show_progressbar, - parallel=True, - output_dtype=dtype_out, - static_bg=static_bg, - dtype_out=dtype_out, - omin=omin, - omax=omax, - scale_bg=scale_bg, - ) + map_kw = { + "show_progressbar": show_progressbar, + "output_dtype": dtype_out, + "static_bg": static_bg, + "dtype_out": dtype_out, + "omin": omin, + "omax": omax, + "scale_bg": scale_bg, + } attrs = self._get_custom_attributes() if inplace: self.map(operation_func, inplace=True, **map_kw) @@ -633,7 +633,7 @@ def remove_dynamic_background( >>> s.remove_dynamic_background(operation="divide", std=5) """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") if std is None: std = self.axes_manager.signal_shape[0] / 8 @@ -666,17 +666,16 @@ def remove_dynamic_background( dtype_out = self.data.dtype.type omin, omax = dtype_range[dtype_out] - map_kw = dict( - show_progressbar=show_progressbar, - parallel=True, - output_dtype=dtype_out, - filter_func=filter_func, - operation=operation, - dtype_out=dtype_out, - omin=omin, - omax=omax, + map_kw = { + "show_progressbar": show_progressbar, + "output_dtype": dtype_out, + "filter_func": filter_func, + "operation": operation, + "dtype_out": dtype_out, + "omin": omin, + "omax": omax, **kwargs, - ) + } attrs = self._get_custom_attributes() if inplace: self.map(map_func, inplace=True, **map_kw) @@ -869,7 +868,7 @@ def fft_filter( ... ) """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") dtype_out = self.data.dtype.type dtype = np.float32 @@ -996,7 +995,7 @@ def average_neighbour_patterns( scipy.ndimage.correlate """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") if isinstance(window, Window) and window.is_valid: averaging_window = copy.copy(window) @@ -1153,7 +1152,7 @@ def downsample( rescaling is undesirable, use :meth:`rebin` instead. """ if lazy_output and inplace: - raise ValueError("`lazy_output=True` requires `inplace=False`") + raise ValueError("'lazy_output=True' requires 'inplace=False'") if not isinstance(factor, int) or factor <= 1: raise ValueError(f"Binning `factor` {factor} must be an integer > 1") @@ -1191,7 +1190,6 @@ def downsample( map_kw = { "show_progressbar": show_progressbar, - "parallel": True, "output_dtype": dtype_out, "factor": factor, "omin": omin, @@ -1355,7 +1353,6 @@ def get_image_quality( image_quality_map = self.map( _get_image_quality, show_progressbar=show_progressbar, - parallel=True, inplace=False, output_dtype=np.float32, normalize=normalize, @@ -2592,63 +2589,72 @@ def refine_orientation_projection_center( def save( self, - filename: str | None = None, + filename: Path | str | None = None, overwrite: bool | None = None, extension: str | None = None, **kwargs, ) -> None: """Write the signal to file in the specified format. - The function gets the format from the extension: ``h5``, - ``hdf5`` or ``h5ebsd`` for kikuchipy's specification of the - h5ebsd format, ``dat`` for the NORDIF binary format or ``hspy`` - for HyperSpy's HDF5 specification. If no extension is provided - the signal is written to a file in kikuchipy's h5ebsd format. - Each format accepts a different set of parameters. - - This method is a modified version of HyperSpy's function - :meth:`hyperspy.signal.BaseSignal.save`. + If no extension is given, the signal is written to a file in + kikuchipy's h5ebsd format. Parameters ---------- filename - If not given and ``tmp_parameters.filename`` and - ``tmp_parameters.folder`` in signal metadata are defined, - the filename and path will be taken from there. A valid - extension can be provided e.g. ``"data.h5"``, see - ``extension``. + If not given and 'tmp_parameters.filename' and + 'tmp_parameters.folder' are defined in signal metadata, the + filename and path will be taken from there. overwrite If not given and the file exists, it will query the user. If - ``True`` (``False``) it (does not) overwrite the file if it - exists. + True (False) it (does not) overwrite the file if it exists. extension Extension of the file that defines the file format. Options - are ``"h5"``, ``"hdf5"``, ``"h5ebsd"``, ``"dat"``, - ``"hspy"``. ``"h5"``, ``"hdf5"``, and ``"h5ebsd"`` are - equivalent. If not given, the extension is determined from - the following list in this order: i) the filename, ii) - ``tmp_parameters.extension`` or iii) ``"h5"`` (kikuchipy's - h5ebsd format). + are: + + * h5, hdf5, or h5ebsd: kikuchipy's specification of the + h5ebsd format + * dat: NORDIF's binary format + * hspy: HyperSpy's HDF5 format + * zspy: HyperSpy's zarr format + + Each format accepts different parameters. + + If not given, the extension is determined from the following + list in this order: i) the filename, ii) + 'tmp_parameters.extension' or iii) 'h5' (kikuchipy's h5ebsd + format). **kwargs - Keyword arguments passed to the writer. + Keyword arguments passed to the corresponding writer. See Also -------- kikuchipy.io.plugins + + Notes + ----- + This method is a modified version of HyperSpy's function + :meth:`hyperspy.signal.BaseSignal.save`. """ - if filename is None: + if filename is not None: + fname = Path(filename) + else: tmp_params = self.tmp_parameters if tmp_params.has_item("filename") and tmp_params.has_item("folder"): - filename = os.path.join(tmp_params.folder, tmp_params.filename) + fname = Path(tmp_params.folder) / tmp_params.filename extension = tmp_params.extension if not extension else extension elif self.metadata.has_item("General.original_filename"): - filename = self.metadata.General.original_filename + fname = Path(self.metadata.General.original_filename) else: - raise ValueError("Filename not defined") + raise ValueError("filename not given") if extension is not None: - basename, _ = os.path.splitext(filename) - filename = basename + "." + extension - _save(filename, self, overwrite=overwrite, **kwargs) + ext = extension + else: + ext = fname.suffix[1:] + if ext.lower() in ["hspy", "zspy"]: + super().save(filename, overwrite=overwrite, **kwargs) + return + _save(fname, self, overwrite=overwrite, **kwargs) def get_decomposition_model( self, @@ -2712,7 +2718,7 @@ def get_decomposition_model( @insert_doc_disclaimer(cls=Signal2D, meth=Signal2D.crop) def crop(self, *args, **kwargs): - # This method is called by crop_image(), so attributes are + # This method is called by crop_signal(), so attributes are # handled correctly by that method as well old_shape = self.data.shape diff --git a/src/kikuchipy/signals/util/_crystal_map.py b/src/kikuchipy/signals/util/_crystal_map.py index 92cd307d..b15845eb 100644 --- a/src/kikuchipy/signals/util/_crystal_map.py +++ b/src/kikuchipy/signals/util/_crystal_map.py @@ -40,21 +40,22 @@ def _xmap_is_compatible_with_signal( warnings.warn( "Signal navigation axes must be named 'x' and/or 'y' in order to compare " "the signal navigation scales to the crystal map step sizes 'dx' and 'dy' " - "(see `EBSD.axes_manager`)" + "(see EBSD.axes_manager)" ) xmap_scale = list(xmap._step_sizes.values())[-len(navigation_axes) :] compatible = xmap.shape == nav_shape if compatible and not np.allclose(xmap_scale, nav_scale, atol=1e-6): + xmap_scale2 = list(map(float, xmap_scale)) warnings.warn( - f"Crystal map step size(s) {xmap_scale} and signal's step size(s) " - f"{nav_scale} must be the same (see `EBSD.axes_manager`)" + f"Crystal map step size(s) {xmap_scale2} and signal's step size(s) " + f"{nav_scale} must be the same (see EBSD.axes_manager)" ) if not compatible and raise_if_not: raise ValueError( f"Crystal map shape {xmap.shape} and signal's navigation shape {nav_shape} " - "must be the same (see `EBSD.axes_manager`)" + "must be the same (see EBSD.axes_manager)" ) else: return compatible diff --git a/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py b/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py index b40e2b56..9bd27c0a 100644 --- a/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py +++ b/src/kikuchipy/simulations/_kikuchi_pattern_simulation.py @@ -19,8 +19,7 @@ import re from diffsims.crystallography import ReciprocalLatticeVector -from hyperspy.drawing.marker import MarkerBase -from hyperspy.utils.markers import line_segment, point, text +import hyperspy.api as hs import matplotlib.collections as mcollections import matplotlib.figure as mfigure import matplotlib.path as mpath @@ -199,7 +198,7 @@ def as_markers( zone_axes_kwargs: dict | None = None, zone_axes_labels_kwargs: dict | None = None, pc_kwargs: dict | None = None, - ) -> list[MarkerBase]: + ) -> list: """Return a list of simulation markers. Parameters @@ -469,7 +468,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[hs.plot.markers.Lines]: """Get Kikuchi lines as a list of HyperSpy markers. Parameters @@ -496,7 +495,7 @@ def _lines_as_markers(self, **kwargs) -> list[line_segment]: y1 = line[..., 1].squeeze() x2 = line[..., 2].squeeze() y2 = line[..., 3].squeeze() - marker = line_segment(x1=x1, y1=y1, x2=x2, y2=y2, **kw) + marker = hs.plot.markers.Lines([[[x1, y1], [x2, y2]]], **kw) lines_list.append(marker) return lines_list diff --git a/tests/test_data/test_data.py b/tests/test_data/test_data.py index 768ef7ca..d7cb0482 100644 --- a/tests/test_data/test_data.py +++ b/tests/test_data/test_data.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -import os from pathlib import Path from dask.array import Array diff --git a/tests/test_draw/test_markers.py b/tests/test_draw/test_markers.py deleted file mode 100644 index 2bef4f65..00000000 --- a/tests/test_draw/test_markers.py +++ /dev/null @@ -1,286 +0,0 @@ -# Copyright 2019-2024 The kikuchipy developers -# -# This file is part of kikuchipy. -# -# kikuchipy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# kikuchipy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with kikuchipy. If not, see . - -from hyperspy.utils.markers import line_segment, point, text -import numpy as np -import pytest - -from kikuchipy.draw.markers import get_line_segment_list, get_point_list, get_text_list - - -class TestMarkers: - @pytest.mark.parametrize("n_lines", [1, 2, 3]) - def test_get_line_segment_list0d(self, n_lines): - """Lines in 0D () navigation space.""" - nav_shape = () - size = int(np.prod(nav_shape) * n_lines * 4) - lines = np.random.random(size=size).reshape(nav_shape + (n_lines, 4)) - kwargs = dict(linewidth=1, color="red", alpha=1, zorder=1) - line_markers = get_line_segment_list(list(lines), **kwargs) - - # Number of markers - assert isinstance(line_markers, list) - assert len(line_markers) == n_lines - assert isinstance(line_markers[0], line_segment) - - # Coordinates, data shape and marker properties - for line, marker in zip(lines, line_markers): - assert np.allclose( - [ - marker.get_data_position("x1"), - marker.get_data_position("y1"), - marker.get_data_position("x2"), - marker.get_data_position("y2"), - ], - line, - ) - assert marker._get_data_shape() == nav_shape - for k, v in kwargs.items(): - assert marker.marker_properties[k] == v - - def test_get_line_segment_0d_2(self): - """A line in 0d but no (1,) navigation shape.""" - lines = np.random.random(size=4).reshape((4,)) - line_markers = get_line_segment_list(lines) - - assert len(line_markers) == 1 - assert np.allclose( - [ - line_markers[0].get_data_position("x1"), - line_markers[0].get_data_position("y1"), - line_markers[0].get_data_position("x2"), - line_markers[0].get_data_position("y2"), - ], - lines, - ) - - def test_get_line_segment_list1d(self): - """Lines in 1D (2,) navigation space.""" - nav_shape = (2,) - n_lines = 2 - size = int(np.prod(nav_shape) * n_lines * 4) - lines = np.random.random(size=size).reshape(nav_shape + (n_lines, 4)) - line_markers = get_line_segment_list(lines) - - assert len(line_markers) == n_lines - - # Iterate over lines - for i in range(n_lines): - assert line_markers[i]._get_data_shape() == nav_shape # 1d - assert np.allclose( - np.dstack(line_markers[i].data.tolist()[:4]), - lines[:, i], - ) - - def test_get_line_segment_list2d(self): - """Lines in 2D (2, 3) navigation space.""" - nav_shape = (2, 3) - n_lines = 3 - size = int(np.prod(nav_shape) * n_lines * 4) - lines = np.random.random(size=size).reshape(nav_shape + (n_lines, 4)) - line_markers = get_line_segment_list(lines) - - assert len(line_markers) == n_lines - - # Iterate over lines - for i in range(n_lines): - assert line_markers[i]._get_data_shape() == nav_shape - assert np.allclose( - np.dstack(line_markers[i].data.tolist()[:4]), lines[:, :, i] - ) - - def test_get_line_segment_list_nans(self): - lines = np.ones((2, 3, 4)) * np.nan - assert len(get_line_segment_list(lines)) == 0 - - @pytest.mark.parametrize("n_points", [1, 2, 3]) - def test_get_point_list0d(self, n_points): - """Points in 0D () navigation space.""" - nav_shape = () - size = int(np.prod(nav_shape) * n_points * 2) - points = np.random.random(size=size).reshape(nav_shape + (n_points, 2)) - kwargs = dict(s=40, marker="o", facecolor="w", edgecolor="k", zorder=5, alpha=1) - point_markers = get_point_list(list(points), **kwargs) - - # Number of markers - assert isinstance(point_markers, list) - assert len(point_markers) == n_points - assert isinstance(point_markers[0], point) - - # Coordinates, data shape and marker properties - for i, marker in zip(points, point_markers): - assert np.allclose( - [ - marker.get_data_position("x1"), - marker.get_data_position("y1"), - ], - i, - ) - assert marker._get_data_shape() == nav_shape - for k, v in kwargs.items(): - assert marker.marker_properties[k] == v - - def test_get_point_list0d_2(self): - """One point in 0d but no (1,) navigation shape.""" - points = np.random.random(size=2).reshape((2,)) - point_marker = get_point_list(points) - - assert len(point_marker) == 1 - assert np.allclose( - [ - point_marker[0].get_data_position("x1"), - point_marker[0].get_data_position("y1"), - ], - points, - ) - - def test_get_point_list1d(self): - """Points in 1D (2,) navigation space.""" - nav_shape = (2,) - n_points = 2 - size = int(np.prod(nav_shape) * n_points * 2) - points = np.random.random(size=size).reshape(nav_shape + (n_points, 2)) - point_markers = get_point_list(points) - - assert len(point_markers) == n_points - - # Iterate over points - for i in range(n_points): - assert point_markers[i]._get_data_shape() == nav_shape # 1d - assert np.allclose( - np.dstack(point_markers[i].data.tolist()[:2]), points[:, i] - ) - - def test_get_point_list2d(self): - """Points in 2D (2, 3) navigation space.""" - nav_shape = (2, 3) - n_points = 3 - size = int(np.prod(nav_shape) * n_points * 2) - points = np.random.random(size=size).reshape(nav_shape + (n_points, 2)) - point_markers = get_point_list(points) - - assert len(point_markers) == n_points - - # Iterate over points - for i in range(n_points): - assert point_markers[i]._get_data_shape() == nav_shape - assert np.allclose( - np.dstack(point_markers[i].data.tolist()[:2]), points[:, :, i] - ) - - def test_get_point_list_nans(self): - points = np.ones((2, 3, 2)) * np.nan - assert len(get_point_list(points)) == 0 - - @pytest.mark.parametrize("n_labels", [1, 2, 3]) - def test_get_text_list0d(self, n_labels): - """Text labels in 0D () navigation space.""" - nav_shape = () - size = int(np.prod(nav_shape) * n_labels * 2) - texts = ["111", "220", "-220"][:n_labels] - text_coords = np.random.random(size=size).reshape(nav_shape + (n_labels, 2)) - kwargs = dict( - color="k", - zorder=5, - ha="center", - bbox=dict( - facecolor="w", - edgecolor="k", - boxstyle="round, rounding_size=0.2", - pad=0.1, - alpha=0.1, - ), - ) - text_markers = get_text_list( - texts=texts, coordinates=list(text_coords), **kwargs - ) - - # Number of markers - assert isinstance(text_markers, list) - assert len(text_markers) == n_labels - assert isinstance(text_markers[0], text) - - # Coordinates, data shape and marker properties - for i, (t, marker) in enumerate(zip(text_coords, text_markers)): - assert np.allclose( - [marker.get_data_position("x1"), marker.get_data_position("y1")], t - ) - assert marker.get_data_position("text") == texts[i] - assert marker._get_data_shape() == nav_shape - for k, v in kwargs.items(): - assert marker.marker_properties[k] == v - - def test_get_text_list0d_2(self): - """One text label in 0d but no (1,) navigation shape.""" - text_coords = np.random.random(size=2).reshape((2,)) - texts = ["123"] - text_markers = get_text_list(texts=texts, coordinates=text_coords) - - assert len(text_markers) == 1 - assert np.allclose( - [ - text_markers[0].get_data_position("x1"), - text_markers[0].get_data_position("y1"), - ], - text_coords, - ) - assert text_markers[0].get_data_position("text") == texts[0] - - def test_get_text_list1d(self): - """Text labels in 1D (2,) navigation space.""" - nav_shape = (2,) - n_labels = 2 - size = int(np.prod(nav_shape) * n_labels * 2) - texts = ["111", "-220"] - text_coords = np.random.random(size=size).reshape(nav_shape + (n_labels, 2)) - text_markers = get_text_list(texts=texts, coordinates=text_coords) - - assert len(text_markers) == n_labels - - # Iterate over text labels - for i in range(n_labels): - assert text_markers[i]._get_data_shape() == nav_shape - assert np.allclose( - np.dstack(text_markers[i].data.tolist()[:2]), text_coords[:, i] - ) - assert text_markers[i].data["text"] == texts[i] - - def test_get_text_list2d(self): - """Text labels in 2D (2, 3) navigation space.""" - nav_shape = (2, 3) - n_labels = 3 - size = int(np.prod(nav_shape) * n_labels * 2) - texts = ["111", "-2-20", "311"] - text_coords = np.random.random(size=size).reshape(nav_shape + (n_labels, 2)) - text_markers = get_text_list(texts=texts, coordinates=text_coords) - - assert len(text_markers) == n_labels - - # Iterate over text labels - for i in range(n_labels): - assert text_markers[i]._get_data_shape() == nav_shape - assert np.allclose( - np.dstack(text_markers[i].data.tolist()[:2]), text_coords[:, :, i] - ) - assert text_markers[i].data["text"] == texts[i] - - def test_get_text_list_nans(self): - text_coords = np.ones((2, 3, 2)) * np.nan - assert ( - len(get_text_list(texts=["111", "200", "220"], coordinates=text_coords)) - == 0 - ) diff --git a/tests/test_draw/test_navigators.py b/tests/test_draw/test_navigators.py index 3503535d..4547fd03 100644 --- a/tests/test_draw/test_navigators.py +++ b/tests/test_draw/test_navigators.py @@ -15,9 +15,9 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . -from hyperspy.misc.rgb_tools import rgb8, rgb16 import matplotlib.pyplot as plt import numpy as np +from rsciio.utils.rgb_tools import rgb8, rgb16 import kikuchipy as kp diff --git a/tests/test_imaging/test_virtual_bse_imager.py b/tests/test_imaging/test_virtual_bse_imager.py index 38226779..10d22ff9 100644 --- a/tests/test_imaging/test_virtual_bse_imager.py +++ b/tests/test_imaging/test_virtual_bse_imager.py @@ -28,7 +28,7 @@ def test_init(self, dummy_signal): vbse_imager = kp.imaging.VirtualBSEImager(dummy_signal) assert isinstance(vbse_imager.signal, kp.signals.EBSD) - assert vbse_imager.grid_shape == (5, 5) + assert vbse_imager.grid_shape == (3, 3) def test_init_lazy(self, dummy_signal): lazy_signal = dummy_signal.as_lazy() @@ -137,7 +137,7 @@ def test_axes_manager_transfer(self, kikuchipy_h5ebsd_path): def test_get_images_lazy(self, dummy_signal): vbse_imager = kp.imaging.VirtualBSEImager(dummy_signal.as_lazy()) - vbse_img = vbse_imager.get_images_from_grid() + _ = vbse_imager.get_images_from_grid() class TestGetRGBImage: diff --git a/tests/test_io/test_edax_h5ebsd.py b/tests/test_io/test_edax_h5ebsd.py index 5fb8b671..9d353cdc 100644 --- a/tests/test_io/test_edax_h5ebsd.py +++ b/tests/test_io/test_edax_h5ebsd.py @@ -17,7 +17,7 @@ import shutil -from h5py import File +import h5py import pytest import kikuchipy as kp @@ -36,13 +36,13 @@ def test_load( # No 'SEM-PRIAS' group available tmp_file = tmp_path / file.name shutil.copy(file, tmp_file) - with File(tmp_file, mode="r+") as f: + with h5py.File(tmp_file, mode="r+") as f: f["Scan 1"].move("SEM-PRIAS Images", "SSEM-PRIAS Images") s2 = kp.load(tmp_file) assert s2.metadata.Acquisition_instrument.SEM.magnification != mag # Not a square grid - with File(tmp_file, mode="r+") as f: + with h5py.File(tmp_file, mode="r+") as f: grid = f["Scan 1/EBSD/Header/Grid Type"] grid[()] = "HexGrid".encode() with pytest.raises(IOError, match="Only square grids are"): @@ -51,5 +51,5 @@ def test_load( def test_save_error(self, edax_h5ebsd_path): file = edax_h5ebsd_path / "patterns.h5" s = kp.load(file) - with pytest.raises(OSError, match="(.*) is not a supported kikuchipy"): + with pytest.raises(OSError, match="(.*) is not a supported kikuchipy h5ebsd "): s.save(file, add_scan=True) diff --git a/tests/test_io/test_emsoft_ebsd.py b/tests/test_io/test_emsoft_ebsd.py index 13390a8e..41a7efc2 100644 --- a/tests/test_io/test_emsoft_ebsd.py +++ b/tests/test_io/test_emsoft_ebsd.py @@ -16,14 +16,14 @@ # along with kikuchipy. If not, see . import dask.array as da -from h5py import File +import h5py import numpy as np from orix.crystal_map import CrystalMap import pytest from kikuchipy.io._io import load from kikuchipy.io.plugins._h5ebsd import _hdf5group2dict -from kikuchipy.io.plugins.emsoft_ebsd import _check_file_format, _crystaldata2phase +from kikuchipy.io.plugins.emsoft_ebsd._api import _check_file_format, _crystaldata2phase class TestEMsoftEBSDReader: @@ -57,7 +57,7 @@ def test_read_lazy(self, emsoft_ebsd_path): def test_check_file_format(self, save_path_hdf5): """Wrong file format raises an error.""" - with File(save_path_hdf5, mode="w") as f: + with h5py.File(save_path_hdf5, mode="w") as f: g1 = f.create_group("EMheader") g2 = g1.create_group("EBSD") g2.create_dataset( @@ -68,7 +68,7 @@ def test_check_file_format(self, save_path_hdf5): def test_crystaldata2phase(self, emsoft_ebsd_path): """A Phase object is correctly returned.""" - with File(emsoft_ebsd_path / "simulated_ebsd.h5") as f: + with h5py.File(emsoft_ebsd_path / "simulated_ebsd.h5") as f: xtal_dict = _hdf5group2dict(f["CrystalData"]) phase = _crystaldata2phase(xtal_dict) @@ -85,15 +85,13 @@ def test_crystaldata2phase(self, emsoft_ebsd_path): ) assert np.allclose(structure.occupancy, [1, 1]) assert np.allclose(structure.Bisoequiv, [0.5] * 2) - assert np.compare_chararrays( - structure.element, np.array(["13", "29"], dtype="|S2"), "==", rstrip=False - ).all() + assert structure.element.tolist() == [b"13", b"29"] def test_crystaldata2phase_single_atom(self, emsoft_ebsd_path): """A Phase object is correctly returned when there is only one atom present. """ - with File(emsoft_ebsd_path / "simulated_ebsd.h5") as f: + with h5py.File(emsoft_ebsd_path / "simulated_ebsd.h5") as f: xtal_dict = _hdf5group2dict(f["CrystalData"]) xtal_dict["Natomtypes"] = 1 xtal_dict["AtomData"] = xtal_dict["AtomData"][:, 0][..., np.newaxis] diff --git a/tests/test_io/test_emsoft_master_pattern.py b/tests/test_io/test_emsoft_master_pattern.py index b6be84a1..d70c2e9b 100644 --- a/tests/test_io/test_emsoft_master_pattern.py +++ b/tests/test_io/test_emsoft_master_pattern.py @@ -121,8 +121,8 @@ def test_get_datasets( @pytest.mark.parametrize( "projection, hemisphere, error_msg", [ - ("stereographicl", "upper", "'projection' value stereographicl "), - ("lambert", "east", "'hemisphere' value east "), + ("stereographicl", "upper", "'projection' value 'stereographicl' "), + ("lambert", "east", "'hemisphere' value 'east' "), ], ) def test_get_datasets_raises( diff --git a/tests/test_io/test_io.py b/tests/test_io/test_io.py index d9da91e0..96bdb0d4 100644 --- a/tests/test_io/test_io.py +++ b/tests/test_io/test_io.py @@ -17,32 +17,33 @@ import os +import hyperspy.api as hs import numpy as np import pytest import kikuchipy as kp from kikuchipy.io._io import _assign_signal_subclass, _dict2signal +from kikuchipy.io.plugins.kikuchipy_h5ebsd._api import ( + file_reader as kp_h5ebsd_file_reader, +) class TestIO: - @pytest.mark.parametrize("filename", ("im_not_here.h5", "unsupported.h4")) - def test_load(self, kikuchipy_h5ebsd_path, tmpdir, filename): - if filename == "im_not_here.h5": - with pytest.raises(IOError, match="No filename matches"): - _ = kp.load(filename) - else: - s = kp.load(kikuchipy_h5ebsd_path / "patterns.h5") - file_path = tmpdir / "supported.h5" - s.save(file_path) - new_file_path = tmpdir / filename - file_path.rename(new_file_path) - with pytest.raises(IOError, match="Could not read"): - _ = kp.load(new_file_path) + def test_load_unsupported_extension(self, kikuchipy_h5ebsd_path, tmpdir): + s = kp.load(kikuchipy_h5ebsd_path / "patterns.h5") + file_path = tmpdir / "supported.h5" + s.save(file_path) + new_file_path = tmpdir / "unsupported.h4" + file_path.rename(new_file_path) + with pytest.raises(IOError, match="Could not read"): + kp.load(new_file_path) + + def test_load_missing_file(self, kikuchipy_h5ebsd_path, tmpdir): + with pytest.raises(IOError, match="No filename matches"): + kp.load("im_not_here.h5") def test_dict2signal(self, kikuchipy_h5ebsd_path): - scan_dict = kp.io.plugins.kikuchipy_h5ebsd.file_reader( - kikuchipy_h5ebsd_path / "patterns.h5" - )[0] + scan_dict, *_ = kp_h5ebsd_file_reader(kikuchipy_h5ebsd_path / "patterns.h5") scan_dict["metadata"]["Signal"]["record_by"] = "not-image" with pytest.raises(ValueError, match="kikuchipy only supports"): _ = _dict2signal(scan_dict) @@ -105,12 +106,10 @@ def test_save_extensions(self, kikuchipy_h5ebsd_path, extension, tmpdir): with pytest.raises(ValueError, match="'h4' does not"): s.save(file_path) - @pytest.mark.filterwarnings("ignore:Using `set_signal_dimension`") - def test_save_data_dimensions(self, kikuchipy_h5ebsd_path): - s = kp.load(kikuchipy_h5ebsd_path / "patterns.h5") - s.axes_manager.set_signal_dimension(3) - with pytest.raises(ValueError, match="This file format cannot write"): - s.save() + def test_save_data_dimensions(self, tmpdir): + s = kp.signals.EBSD(np.zeros((2, 3, 4, 5, 6))) + with pytest.raises(ValueError, match="Chosen IO plugin 'kikuchipy_h5ebsd' "): + s.save(tmpdir / "test.h5") def test_save_to_existing_file(self, save_path_hdf5, kikuchipy_h5ebsd_path): s = kp.load(kikuchipy_h5ebsd_path / "patterns.h5") @@ -122,3 +121,12 @@ def test_save_to_existing_file(self, save_path_hdf5, kikuchipy_h5ebsd_path): s.save(save_path_hdf5, scan_number=2, overwrite=False, add_scan=False) with pytest.raises(OSError, match="Scan 'Scan 2' is not among the"): _ = kp.load(save_path_hdf5, scan_group_names="Scan 2") + + +class TestHSPYWrite: + def test_hspy_write(self, tmpdir, dummy_signal): + file_path = str(tmpdir / "test.hspy") + s = dummy_signal.as_lazy() + s.save(file_path) + s2 = hs.load(file_path, signal_type="EBSD") + assert np.allclose(dummy_signal.data, s2.data) diff --git a/tests/test_io/test_kikuchipy_h5ebsd.py b/tests/test_io/test_kikuchipy_h5ebsd.py index fd5f66bb..ad898a41 100644 --- a/tests/test_io/test_kikuchipy_h5ebsd.py +++ b/tests/test_io/test_kikuchipy_h5ebsd.py @@ -18,15 +18,15 @@ import os import dask.array as da -from h5py import Dataset, File -from hyperspy.api import load as hs_load +import h5py +import hyperspy.api as hs import numpy as np from orix.quaternion import Rotation import pytest import kikuchipy as kp from kikuchipy.io.plugins._h5ebsd import _dict2hdf5group -from kikuchipy.io.plugins.kikuchipy_h5ebsd import ( +from kikuchipy.io.plugins.kikuchipy_h5ebsd._api import ( KikuchipyH5EBSDReader, KikuchipyH5EBSDWriter, ) @@ -42,21 +42,21 @@ def test_repr(self, kikuchipy_h5ebsd_path): assert repr_str_list[2][-11:] == "patterns.h5" def test_check_file_invalid_version(self, save_path_hdf5): - f = File(save_path_hdf5, mode="w") + f = h5py.File(save_path_hdf5, mode="w") _dict2hdf5group({"manufacturer": "kikuchipy", "versionn": "0.1"}, f["/"]) f.close() - with pytest.raises(IOError, match="(.*) as manufacturer"): + with pytest.raises(IOError, match="Could not find 'version' key in '(.*)'"): _ = KikuchipyH5EBSDReader(save_path_hdf5) def test_check_file_no_scan_groups(self, save_path_hdf5): - f = File(save_path_hdf5, mode="w") + f = h5py.File(save_path_hdf5, mode="w") _dict2hdf5group({"manufacturer": "kikuchipy", "version": "0.1"}, f["/"]) f.close() with pytest.raises(IOError, match="(.*) as no top groups"): _ = KikuchipyH5EBSDReader(save_path_hdf5) def test_dict2hdf5roup(self, save_path_hdf5): - with File(save_path_hdf5, mode="w") as f: + with h5py.File(save_path_hdf5, mode="w") as f: with pytest.warns(UserWarning, match="(c, set())"): _dict2hdf5group({"a": [np.array(24.5)], "c": set()}, f["/"]) @@ -97,20 +97,20 @@ def test_load_manufacturer(self, save_path_hdf5): s.save(save_path_hdf5) # Change manufacturer - with File(save_path_hdf5, mode="r+") as f: + with h5py.File(save_path_hdf5, mode="r+") as f: manufacturer = f["manufacturer"] manufacturer[()] = "Nope".encode() with pytest.raises( OSError, - match="(.*) is not a supported h5ebsd file, as 'nope' is not among ", + match="Could not read (.*). If the file format is supported, ", ): _ = kp.load(save_path_hdf5) def test_read_patterns(self, save_path_hdf5): s = kp.signals.EBSD((255 * np.random.rand(10, 3, 5, 5)).astype(np.uint8)) s.save(save_path_hdf5) - with File(save_path_hdf5, mode="r+") as f: + with h5py.File(save_path_hdf5, mode="r+") as f: del f["Scan 1/EBSD/Data/patterns"] with pytest.raises(KeyError, match="Could not find patterns"): _ = kp.load(save_path_hdf5) @@ -128,7 +128,7 @@ def test_load_with_padding( s.save(save_path_hdf5) new_n_columns = 4 - with File(save_path_hdf5, mode="r+") as f: + with h5py.File(save_path_hdf5, mode="r+") as f: f["Scan 1/EBSD/Header/n_columns"][()] = new_n_columns with pytest.warns(UserWarning) as warninfo: @@ -174,8 +174,7 @@ def test_load_save_hyperspy_cycle(self, tmp_path, kikuchipy_h5ebsd_path): s.save(file) # Reload data and use HyperSpy's set_signal_type function - s_reload = hs_load(file) - s_reload.set_signal_type("EBSD") + s_reload = hs.load(file, signal_type="EBSD") # Check signal type, patterns and learning results assert isinstance(s_reload, kp.signals.EBSD) @@ -242,7 +241,7 @@ def test_load_readonly(self, kikuchipy_h5ebsd_path): ) ) mm = s.data.dask[k] - assert isinstance(mm, Dataset) + assert isinstance(mm, h5py.Dataset) def test_save_fresh(self, save_path_hdf5, tmp_path): scan_size = (10, 3) @@ -255,7 +254,7 @@ def test_save_fresh(self, save_path_hdf5, tmp_path): # Test writing of signal to file when no file name is passed to save() del s.tmp_parameters.filename - with pytest.raises(ValueError, match="Filename not defined"): + with pytest.raises(ValueError, match="filename not given"): s.save(overwrite=True) s.metadata.General.original_filename = "an_original_filename" @@ -280,13 +279,13 @@ def test_save_multiple(self, kikuchipy_h5ebsd_path, save_path_hdf5, scan_number) def test_read_lazily_no_chunks(self, kikuchipy_h5ebsd_path): fname = kikuchipy_h5ebsd_path / "patterns_nochunks.h5" - # First, make sure the data image dataset is not actually chunked - f = File(fname) - data_dset = f["Scan 1/EBSD/Data/patterns"] + # Make sure the data image dataset is not actually chunked + file = h5py.File(fname) + data_dset = file["Scan 1/EBSD/Data/patterns"] assert data_dset.chunks is None - f.close() + file.close() - # Then, make sure it can be read correctly + # Make sure it can be read correctly s = kp.load(fname, lazy=True) assert s.data.chunks == ((60,), (60,)) @@ -320,7 +319,7 @@ def test_save_load_1d_nav(self, save_path_hdf5): # Maintain axis name s_y_only2.axes_manager["y"].name = "x" - with pytest.warns(UserWarning, match=r"Crystal map step size\(s\) \[0\] and "): + with pytest.warns(UserWarning, match=r"Crystal map step size\(s\) \[0.0\] and"): s_y_only2.save(save_path_hdf5, overwrite=True) s_x_only3 = kp.load(save_path_hdf5) assert s_x_only3.data.shape == desired_shape @@ -354,7 +353,7 @@ def test_load_with_detector_multiple_pc(self): def test_writer_check_file(self, save_path_hdf5): s = kp.data.nickel_ebsd_small(lazy=True) - f = File(save_path_hdf5, mode="w") + f = h5py.File(save_path_hdf5, mode="w") _dict2hdf5group({"manufacturer": "kikuchipy", "version": "0.1"}, f["/"]) f.close() with pytest.raises(IOError, match="(.*) as no top groups"): diff --git a/tests/test_io/test_nordif.py b/tests/test_io/test_nordif.py index 20af57af..1d101d17 100644 --- a/tests/test_io/test_nordif.py +++ b/tests/test_io/test_nordif.py @@ -20,9 +20,6 @@ # blob/RELEASE_next_minor/hyperspy/tests/io/test_blockfile.py import datetime -import gc -import os -import tempfile import time import dask.array as da @@ -32,7 +29,7 @@ import pytest import kikuchipy as kp -from kikuchipy.io.plugins.nordif import _get_settings_from_file, _get_string +from kikuchipy.io.plugins.nordif._api import _get_settings_from_file, _get_string # Settings content METADATA = { @@ -184,14 +181,6 @@ } -@pytest.fixture() -def save_path_nordif(): - with tempfile.TemporaryDirectory() as tmp: - file_path = os.path.join(tmp, "nordif", "save_temp.dat") - yield file_path - gc.collect() - - class TestNORDIF: def test_get_settings_from_file(self, nordif_path): settings = _get_settings_from_file(nordif_path / "Setting.txt") @@ -270,12 +259,11 @@ def test_load_save_cycle(self, nordif_path, save_path_nordif): ) assert s.metadata.General.title == "Pattern" - s.save(save_path_nordif, overwrite=True) + s.save(save_path_nordif) with pytest.warns(UserWarning): # No background pattern in directory s_reload = kp.load( save_path_nordif, setting_file=nordif_path / "Setting.txt" ) - assert np.allclose(s.data, s_reload.data) # Add static background and change filename to make metadata equal diff --git a/tests/test_io/test_oxford_binary.py b/tests/test_io/test_oxford_binary.py index 8681b254..7e74b88e 100644 --- a/tests/test_io/test_oxford_binary.py +++ b/tests/test_io/test_oxford_binary.py @@ -20,6 +20,7 @@ import pytest import kikuchipy as kp +from kikuchipy.io.plugins.oxford_binary._api import OxfordBinaryFileReader class TestOxfordBinaryReader: @@ -110,5 +111,5 @@ def test_guess_number_of_patterns(self, oxford_binary_file, n_patterns): the file works. """ with open(oxford_binary_file.name, mode="rb") as f: - fox = kp.io.plugins.oxford_binary.OxfordBinaryFileReader(f) + fox = OxfordBinaryFileReader(f) assert fox.n_patterns == n_patterns diff --git a/tests/test_io/test_util.py b/tests/test_io/test_util.py index 71b5c099..de586330 100644 --- a/tests/test_io/test_util.py +++ b/tests/test_io/test_util.py @@ -41,11 +41,11 @@ def test_get_input_bool(self, answer, should_return): if answer == "m": with replace_stdin(io.StringIO(answer)): with pytest.raises(EOFError): - _ = _get_input_bool(question) + _get_input_bool(question, "") return else: with replace_stdin(io.StringIO(answer)): - returns = _get_input_bool(question) + returns = _get_input_bool(question, "") assert returns == should_return @pytest.mark.parametrize("var_type", (int, 1)) diff --git a/tests/test_signals/test_ebsd.py b/tests/test_signals/test_ebsd.py index c592d671..d6a4312b 100644 --- a/tests/test_signals/test_ebsd.py +++ b/tests/test_signals/test_ebsd.py @@ -366,7 +366,7 @@ def test_inplace(self, dummy_signal): def test_lazy_output(self, dummy_signal): with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.remove_static_background(lazy_output=True) @@ -576,7 +576,7 @@ def test_inplace(self, dummy_signal): @pytest.mark.filterwarnings("ignore:invalid value") def test_lazy_output(self, dummy_signal): with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.remove_dynamic_background(lazy_output=True) @@ -673,7 +673,7 @@ def test_rescale_intensity_percentiles(self, dummy_signal, percentiles, answer): dummy_signal.rescale_intensity(percentiles=percentiles, dtype_out=dtype_out) assert dummy_signal.data.dtype == dtype_out - assert np.allclose(dummy_signal.inav[0, 0].data, answer) + assert np.allclose(dummy_signal.inav[0, 0].data, answer, atol=2) def test_rescale_intensity_in_range(self, dummy_signal): dummy_data = dummy_signal.deepcopy().data @@ -716,7 +716,7 @@ def test_inplace(self, dummy_signal): def test_lazy_output(self, dummy_signal): with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.rescale_intensity(lazy_output=True) @@ -785,7 +785,7 @@ def test_inplace(self): def test_lazy_output(self): s = kp.data.nickel_ebsd_small() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = s.adaptive_histogram_equalization(lazy_output=True) @@ -1035,7 +1035,7 @@ def test_inplace(self, dummy_signal): def test_lazy_output(self, dummy_signal): with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.average_neighbour_patterns(lazy_output=True) @@ -1419,7 +1419,7 @@ def test_lazy_output(self, dummy_signal): function_domain="spatial", ) with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.fft_filter(lazy_output=True, **filter_kw) @@ -1532,7 +1532,7 @@ def test_inplace(self, dummy_signal): def test_lazy_output(self, dummy_signal): with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = dummy_signal.normalize_intensity(lazy_output=True) @@ -2080,13 +2080,13 @@ def test_crop_real_data(self): ), ], ) - def test_crop_image( + def test_crop_signal( self, dummy_signal, top_bottom_left_right, sig_slices, sig_shape ): """Custom properties are cropped correctly.""" static_bg_old = dummy_signal.static_background.copy() - dummy_signal.crop_image(*top_bottom_left_right) + dummy_signal.crop_signal(*top_bottom_left_right) assert np.allclose(dummy_signal.static_background, static_bg_old[sig_slices]) assert dummy_signal.detector.shape == sig_shape @@ -2374,7 +2374,7 @@ def test_inplace(self): def test_lazy_output(self): s = kp.data.nickel_ebsd_small() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = s.downsample(2, lazy_output=True) diff --git a/tests/test_signals/test_ebsd_hough_indexing.py b/tests/test_signals/test_ebsd_hough_indexing.py index 5c533894..4fcf01a8 100644 --- a/tests/test_signals/test_ebsd_hough_indexing.py +++ b/tests/test_signals/test_ebsd_hough_indexing.py @@ -146,7 +146,7 @@ def test_indexer_is_compatible_with_signal(self): # Vendor indexer.vendor = "EDAX" assert not _indexer_is_compatible_with_kikuchipy(indexer, (60, 60), 9) - with pytest.raises(ValueError, match="`indexer.vendor` must be 'kikuchipy', "): + with pytest.raises(ValueError, match="'indexer.vendor' must be 'kikuchipy', "): _indexer_is_compatible_with_kikuchipy(indexer, (60, 60), raise_if_not=True) indexer.vendor = "kikuchipy" diff --git a/tests/test_signals/test_ebsd_master_pattern.py b/tests/test_signals/test_ebsd_master_pattern.py index 1f60c3fa..37de0b25 100644 --- a/tests/test_signals/test_ebsd_master_pattern.py +++ b/tests/test_signals/test_ebsd_master_pattern.py @@ -663,7 +663,7 @@ def test_rescale_intensity_inplace(self): def test_rescale_intensity_lazy_output(self): mp = kp.data.nickel_ebsd_master_pattern_small() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = mp.normalize_intensity(lazy_output=True) @@ -697,7 +697,7 @@ def test_normalize_intensity_inplace(self): def test_normalize_intensity_lazy_output(self): mp = kp.data.nickel_ebsd_master_pattern_small() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): _ = mp.normalize_intensity(lazy_output=True) diff --git a/tests/test_signals/test_ecp_master_pattern.py b/tests/test_signals/test_ecp_master_pattern.py index 09f6b048..5ca3292f 100644 --- a/tests/test_signals/test_ecp_master_pattern.py +++ b/tests/test_signals/test_ecp_master_pattern.py @@ -125,7 +125,7 @@ def test_rescale_intensity_inplace(self, emsoft_ecp_master_pattern_file): def test_rescale_intensity_lazy_output(self, emsoft_ecp_master_pattern_file): mp = kp.load(emsoft_ecp_master_pattern_file) with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): mp.normalize_intensity(lazy_output=True) @@ -159,7 +159,7 @@ def test_normalize_intensity_inplace(self, emsoft_ecp_master_pattern_file): def test_normalize_intensity_lazy_output(self, emsoft_ecp_master_pattern_file): mp = kp.load(emsoft_ecp_master_pattern_file) with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): mp.normalize_intensity(lazy_output=True) diff --git a/tests/test_signals/test_virtual_bse_image.py b/tests/test_signals/test_virtual_bse_image.py index cd6af767..6068d5c6 100644 --- a/tests/test_signals/test_virtual_bse_image.py +++ b/tests/test_signals/test_virtual_bse_image.py @@ -65,7 +65,7 @@ def test_rescale_intensity_lazy_output(self, dummy_signal): vbse_img = vbse_gen.get_images_from_grid() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): vbse_img.normalize_intensity(lazy_output=True) @@ -103,7 +103,7 @@ def test_normalize_intensity_lazy_output(self, dummy_signal): vbse_gen.grid_shape = (3, 3) vbse_img = vbse_gen.get_images_from_grid() with pytest.raises( - ValueError, match="`lazy_output=True` requires `inplace=False`" + ValueError, match="'lazy_output=True' requires 'inplace=False'" ): vbse_img.normalize_intensity(lazy_output=True) diff --git a/tests/test_util/test_deprecated.py b/tests/test_util/test_deprecated.py index 80a18ded..0cc86bbf 100644 --- a/tests/test_util/test_deprecated.py +++ b/tests/test_util/test_deprecated.py @@ -17,10 +17,10 @@ import warnings -import numpy as np import pytest from kikuchipy._util import deprecated, deprecated_argument +from kikuchipy.constants import VisibleDeprecationWarning class TestDeprecationWarning: @@ -35,7 +35,7 @@ def foo(n): """Some docstring.""" return n + 1 - with pytest.warns(np.VisibleDeprecationWarning) as record: + with pytest.warns(VisibleDeprecationWarning) as record: assert foo(4) == 5 desired_msg = ( "Function `foo()` is deprecated and will be removed in version 0.8. Use " @@ -59,7 +59,7 @@ def foo2(n): """ return n + 2 - with pytest.warns(np.VisibleDeprecationWarning) as record2: + with pytest.warns(VisibleDeprecationWarning) as record2: assert foo2(4) == 6 desired_msg2 = "Function `foo2()` is deprecated." assert str(record2[0].message) == desired_msg2 @@ -76,7 +76,7 @@ def test_deprecation_no_old_doc(self): def foo(n): return n + 1 - with pytest.warns(np.VisibleDeprecationWarning) as record: + with pytest.warns(VisibleDeprecationWarning) as record: assert foo(4) == 5 desired_msg = ( "Function `foo()` is deprecated and will be removed in version 0.8. Use " @@ -101,7 +101,7 @@ def b(self): return 1 foo1 = Foo(1) - with pytest.warns(np.VisibleDeprecationWarning) as record: + with pytest.warns(VisibleDeprecationWarning) as record: assert foo1.b == 1 desired_msg = "Attribute `b` is deprecated. Use `c` instead." assert str(record[0].message) == desired_msg @@ -137,7 +137,7 @@ def bar_arg_alt(self, **kwargs): assert my_foo.bar_arg(b=1) == {"b": 1} # Warns - with pytest.warns(np.VisibleDeprecationWarning) as record2: + with pytest.warns(VisibleDeprecationWarning) as record2: assert my_foo.bar_arg(a=2) == {"a": 2} assert str(record2[0].message) == ( r"Parameter `a` is deprecated and will be removed in version 1.4. " @@ -146,7 +146,7 @@ def bar_arg_alt(self, **kwargs): ) # Warns with alternative - with pytest.warns(np.VisibleDeprecationWarning) as record3: + with pytest.warns(VisibleDeprecationWarning) as record3: assert my_foo.bar_arg_alt(a=3) == {"a": 3} assert str(record3[0].message) == ( r"Parameter `a` is deprecated and will be removed in version 1.4. "