From a9d0f6437762270f074b65fea8672371349df6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 5 Oct 2024 14:35:09 +0200 Subject: [PATCH 01/49] Fix PR link in changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35d3029f..300a4ea2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,7 +22,7 @@ Added Changed ------- - Minimum Python version is now 3.10. - (`#? `_) + (`#689 `_) Removed ------- From bcba247bf72ba0023f4b60f9ed3c2cdad3defe13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 5 Oct 2024 14:35:36 +0200 Subject: [PATCH 02/49] Add RosettaSciIO dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- CHANGELOG.rst | 2 ++ doc/conf.py | 1 + doc/user/installation.rst | 3 +- hyperspy_extension.yaml | 75 --------------------------------------- pyproject.toml | 16 ++++----- 5 files changed, 11 insertions(+), 86 deletions(-) delete mode 100644 hyperspy_extension.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 300a4ea2..c67ee3ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,8 @@ Unreleased Added ----- +- Dependency on RosettaSciIO for read/write of some file formats. + (`#? `_) Changed ------- 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/user/installation.rst b/doc/user/installation.rst index be19d15c..69ec28e0 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -106,11 +106,12 @@ 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:`rosettasciio `: Read/write of some file formats * :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/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..6fec59d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,15 +46,15 @@ dependencies = [ "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", + "rosettasciio", "scikit-image >= 0.16.2", "scikit-learn", "scipy >= 1.7", + "tqdm >= 0.5.2", ] [project.entry-points."hyperspy.extensions"] @@ -111,19 +111,15 @@ dev = [ [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" = "" [tool.coverage.report] precision = 2 From 0f274a8ccc1bd3658926c82e0952f5f755979c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 18:42:57 +0200 Subject: [PATCH 03/49] Install unreleased HyperSpy 2.2 from GitHub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- pyproject.toml | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6fec59d3..278f4f76 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,7 +42,8 @@ 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", @@ -57,16 +60,6 @@ dependencies = [ "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,19 +101,35 @@ dev = [ "kikuchipy[doc,tests,coverage]", ] +[project.entry-points."hyperspy.extensions"] +kikuchipy = "src/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" # https://github.com/pypa/hatch/discussions/427 # https://github.com/pypa/hatch/issues/492 [tool.hatch.build.targets.wheel.sources] -"src" = "" +src = "" [tool.hatch.build.targets.wheel] include = [ "src/kikuchipy", ] +# TODO: Remove once HyperSpy is not installed from a direct reference (GitHub) +[tool.hatch.metadata] +allow-direct-references = true + [tool.coverage.report] precision = 2 From 28df16961c72184ad5640fb0c4d2e2f36b4efa6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 20:12:30 +0200 Subject: [PATCH 04/49] Allow import NumPy's VisibleDeprecationWarning before/after v1.25 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/_util/_deprecated.py | 10 +++++----- src/kikuchipy/constants.py | 12 ++++++++++++ tests/test_util/test_deprecated.py | 14 +++++++------- 3 files changed, 24 insertions(+), 12 deletions(-) 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/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. " From 978dcc8a194270f4eefefe2017afd4831109fc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 20:16:21 +0200 Subject: [PATCH 05/49] Prepare IO module for updating to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 276 ++++++++++++++++++++-------------------- 1 file changed, 137 insertions(+), 139 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 4abd031a..3f5b948a 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -16,51 +16,38 @@ # 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.signal import BaseSignal import numpy as np +from rsciio import IO_PLUGINS +from rsciio.utils.tools import get_object_package_info +from rsciio.utils.tools import overwrite as overwrite_method +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, -) 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"] = f"kikuchipy.io.plugins.{path.parts[-2]}" + plugins.append(spec) + if spec["writes"]: + for ext in spec["file_extensions"]: + write_extensions.append(ext) +for plugin in IO_PLUGINS: + if plugin["name"].lower() in ["hspy", "zspy"]: + plugins.append("plugin") + if TYPE_CHECKING: # pragma: no cover from kikuchipy.signals.ebsd import EBSD, LazyEBSD @@ -70,22 +57,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 +71,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 +86,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") @@ -131,21 +113,25 @@ def load( extension = os.path.splitext(filename)[1][1:] readers = [] for plugin in plugins: - if extension.lower() in plugin.file_extensions: + 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 +152,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 +165,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 +180,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 +200,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 +221,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 +236,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 +286,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 +305,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 +335,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 +362,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,36 +378,40 @@ def _save( file. **kwargs Keyword arguments passed to the writer. + + 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: + 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: + 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"]: # Get writers that can write this data writing_plugins = [] 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) raise ValueError( @@ -430,34 +423,39 @@ def _save( 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) + question = f"Add scan to {filename!r} (y/n)?\n" + add_scan = _get_input_bool(question) 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 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) + if writer["name"].lower() in ["hspy", "zspy"]: + signal_dict = signal._to_dictionary(add_models=True) + signal_dict["package_info"] = get_object_package_info(signal) + kwargs["signal"] = signal_dict + else: + kwargs["signal"] = signal + + file_writer = importlib.import_module(writer["api"]).file_writer + file_writer(filename, **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]) From f5fab17abdbf9f5c06513267bd22e5a3e8607d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 20:17:24 +0200 Subject: [PATCH 06/49] Update NORDIF plugin to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/plugins/nordif/__init__.py | 23 ++++++ src/kikuchipy/io/plugins/nordif/__init__.pyi | 20 ++++++ .../io/plugins/{nordif.py => nordif/_api.py} | 72 +++++++++---------- .../io/plugins/nordif/specification.yaml | 7 ++ 4 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 src/kikuchipy/io/plugins/nordif/__init__.py create mode 100644 src/kikuchipy/io/plugins/nordif/__init__.pyi rename src/kikuchipy/io/plugins/{nordif.py => nordif/_api.py} (88%) create mode 100644 src/kikuchipy/io/plugins/nordif/specification.yaml 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 88% rename from src/kikuchipy/io/plugins/nordif.py rename to src/kikuchipy/io/plugins/nordif/_api.py index 693a3b57..5e7bfbe7 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: @@ -443,9 +437,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 +446,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) + np.array(pattern.flatten()).tofile(file) else: for pattern in signal._iterate_signal(): - pattern.flatten().tofile(f) + 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: [] From 1ec474441b10720212b366d4ca56af5f7c41abbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 20:18:13 +0200 Subject: [PATCH 07/49] Update NORDIF calibration patterns plugin to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../nordif_calibration_patterns/__init__.py | 23 +++++++ .../nordif_calibration_patterns/__init__.pyi | 20 ++++++ .../_api.py} | 62 ++++++++----------- .../specification.yaml | 7 +++ 4 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py create mode 100644 src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.pyi rename src/kikuchipy/io/plugins/{nordif_calibration_patterns.py => nordif_calibration_patterns/_api.py} (67%) create mode 100644 src/kikuchipy/io/plugins/nordif_calibration_patterns/specification.yaml 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: [] From ae908895d22ede9b9c11cf637609718602216062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 26 Oct 2024 20:18:30 +0200 Subject: [PATCH 08/49] Update Bruker h5ebsd reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/plugins/_h5ebsd.py | 44 ++++++-- .../io/plugins/bruker_h5ebsd/__init__.py | 23 ++++ .../io/plugins/bruker_h5ebsd/__init__.pyi | 20 ++++ .../_api.py} | 103 +++++++----------- .../plugins/bruker_h5ebsd/specification.yaml | 13 +++ 5 files changed, 130 insertions(+), 73 deletions(-) create mode 100644 src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py create mode 100644 src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.pyi rename src/kikuchipy/io/plugins/{bruker_h5ebsd.py => bruker_h5ebsd/_api.py} (68%) create mode 100644 src/kikuchipy/io/plugins/bruker_h5ebsd/specification.yaml 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'] From 0e4e51a8fd190967d0e17e46b4bbe6e4d2ff17b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:45:26 +0100 Subject: [PATCH 09/49] Update EBSD directory reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/ebsd_directory/__init__.py | 23 ++++++++++++ .../io/plugins/ebsd_directory/__init__.pyi | 20 ++++++++++ .../_api.py} | 37 ++++++------------- .../plugins/ebsd_directory/specification.yaml | 7 ++++ 4 files changed, 61 insertions(+), 26 deletions(-) create mode 100644 src/kikuchipy/io/plugins/ebsd_directory/__init__.py create mode 100644 src/kikuchipy/io/plugins/ebsd_directory/__init__.pyi rename src/kikuchipy/io/plugins/{ebsd_directory.py => ebsd_directory/_api.py} (87%) create mode 100644 src/kikuchipy/io/plugins/ebsd_directory/specification.yaml 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: [] From c667c122296297553dc84da4a6d66b034d477873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:46:20 +0100 Subject: [PATCH 10/49] Update EDAX binary patterns reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/edax_binary/__init__.py | 23 +++++++++++ .../io/plugins/edax_binary/__init__.pyi | 20 +++++++++ .../{edax_binary.py => edax_binary/_api.py} | 41 ++++--------------- .../io/plugins/edax_binary/specification.yaml | 10 +++++ 4 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 src/kikuchipy/io/plugins/edax_binary/__init__.py create mode 100644 src/kikuchipy/io/plugins/edax_binary/__init__.pyi rename src/kikuchipy/io/plugins/{edax_binary.py => edax_binary/_api.py} (88%) create mode 100644 src/kikuchipy/io/plugins/edax_binary/specification.yaml 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: [] From e200de1cda72adab752768f68f710c54ec2d880d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:46:59 +0100 Subject: [PATCH 11/49] Update EDAX h5ebsd reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/edax_h5ebsd/__init__.py | 23 +++++++++ .../io/plugins/edax_h5ebsd/__init__.pyi | 20 ++++++++ .../{edax_h5ebsd.py => edax_h5ebsd/_api.py} | 49 +++++-------------- .../io/plugins/edax_h5ebsd/specification.yaml | 13 +++++ 4 files changed, 67 insertions(+), 38 deletions(-) create mode 100644 src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py create mode 100644 src/kikuchipy/io/plugins/edax_h5ebsd/__init__.pyi rename src/kikuchipy/io/plugins/{edax_h5ebsd.py => edax_h5ebsd/_api.py} (77%) create mode 100644 src/kikuchipy/io/plugins/edax_h5ebsd/specification.yaml 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'] From 6af9a80f5df5078afa81516695048da557b18370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:47:27 +0100 Subject: [PATCH 12/49] Update EMsoft EBSD reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/emsoft_ebsd/__init__.py | 23 +++++++ .../io/plugins/emsoft_ebsd/__init__.pyi | 20 ++++++ .../{emsoft_ebsd.py => emsoft_ebsd/_api.py} | 69 ++++++------------- .../io/plugins/emsoft_ebsd/specification.yaml | 10 +++ 4 files changed, 75 insertions(+), 47 deletions(-) create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd/__init__.pyi rename src/kikuchipy/io/plugins/{emsoft_ebsd.py => emsoft_ebsd/_api.py} (77%) create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd/specification.yaml 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'] From 8ea5075c367cece17b37710fbf405168c2f08004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:48:02 +0100 Subject: [PATCH 13/49] Update EMsoft EBSD master pattern reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/_emsoft_master_pattern.py | 47 +++++++++-------- .../emsoft_ebsd_master_pattern/__init__.py | 23 +++++++++ .../emsoft_ebsd_master_pattern/__init__.pyi | 20 ++++++++ .../_api.py} | 50 ++++++++----------- .../specification.yaml | 9 ++++ 5 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.pyi rename src/kikuchipy/io/plugins/{emsoft_ebsd_master_pattern.py => emsoft_ebsd_master_pattern/_api.py} (68%) create mode 100644 src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/specification.yaml diff --git a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py index 43bf0fcb..bea1eb46 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,9 +303,9 @@ 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 ------- @@ -318,22 +318,21 @@ def _get_datasets( 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)}" ) 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 proj_label, hemi_label in zip(projections.values(), hemispheres.values()): + datasets.append(data_group[proj_label + hemi_label]) elif hemisphere in hemispheres: dset_name = projections[projection] + 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/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'] From fbb488b08a88df552f2718a8f2cafdffc60a06f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:48:26 +0100 Subject: [PATCH 14/49] Update EMsoft ECP master pattern reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../emsoft_ecp_master_pattern/__init__.py | 23 +++++++++++ .../emsoft_ecp_master_pattern/__init__.pyi | 20 ++++++++++ .../_api.py} | 40 ++++++------------- .../specification.yaml | 9 +++++ 4 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py create mode 100644 src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.pyi rename src/kikuchipy/io/plugins/{emsoft_ecp_master_pattern.py => emsoft_ecp_master_pattern/_api.py} (70%) create mode 100644 src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/specification.yaml 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'] From ee7ffad29ed8c4af0543ff1f6ec7db7786762e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:48:46 +0100 Subject: [PATCH 15/49] Update EMsoft TKD master pattern reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../emsoft_tkd_master_pattern/__init__.py | 23 +++++++++++ .../emsoft_tkd_master_pattern/__init__.pyi | 20 ++++++++++ .../_api.py} | 40 ++++++------------- .../specification.yaml | 9 +++++ 4 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py create mode 100644 src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.pyi rename src/kikuchipy/io/plugins/{emsoft_tkd_master_pattern.py => emsoft_tkd_master_pattern/_api.py} (70%) create mode 100644 src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/specification.yaml 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'] From 917b2cc3488dd71324a1e1542ff16af16e9c7e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:49:13 +0100 Subject: [PATCH 16/49] Update our h5ebsd reader/writer to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/kikuchipy_h5ebsd/__init__.py | 23 ++++ .../io/plugins/kikuchipy_h5ebsd/__init__.pyi | 20 ++++ .../_api.py} | 105 +++++++----------- .../kikuchipy_h5ebsd/specification.yaml | 13 +++ 4 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py create mode 100644 src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.pyi rename src/kikuchipy/io/plugins/{kikuchipy_h5ebsd.py => kikuchipy_h5ebsd/_api.py} (83%) create mode 100644 src/kikuchipy/io/plugins/kikuchipy_h5ebsd/specification.yaml 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'] From f060f732a0c6d8375c9d203fcb358b175f91fa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:49:34 +0100 Subject: [PATCH 17/49] Update Oxford binary EBSD pattern reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/oxford_binary/__init__.py | 23 +++++++++++++++++++ .../io/plugins/oxford_binary/__init__.pyi | 20 ++++++++++++++++ .../_api.py} | 19 +++------------ .../plugins/oxford_binary/specification.yaml | 7 ++++++ 4 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 src/kikuchipy/io/plugins/oxford_binary/__init__.py create mode 100644 src/kikuchipy/io/plugins/oxford_binary/__init__.pyi rename src/kikuchipy/io/plugins/{oxford_binary.py => oxford_binary/_api.py} (97%) create mode 100644 src/kikuchipy/io/plugins/oxford_binary/specification.yaml 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: [] From 88641ea1b25c5b0ffe2aa10766ae18ffeb636fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:49:56 +0100 Subject: [PATCH 18/49] Update Oxford's h5ebsd/H5OINA reader to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/oxford_h5ebsd/__init__.py | 23 +++++++ .../io/plugins/oxford_h5ebsd/__init__.pyi | 20 +++++++ .../_api.py} | 60 +++++++------------ .../plugins/oxford_h5ebsd/specification.yaml | 14 +++++ 4 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py create mode 100644 src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.pyi rename src/kikuchipy/io/plugins/{oxford_h5ebsd.py => oxford_h5ebsd/_api.py} (74%) create mode 100644 src/kikuchipy/io/plugins/oxford_h5ebsd/specification.yaml 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: [] From 16b5ef78277fe7cf2c7fcc1e480cb549b9e7bf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:50:29 +0100 Subject: [PATCH 19/49] Fix creation of IO plugin list and look-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 12 +++++----- src/kikuchipy/io/plugins/__init__.py | 16 ++------------ src/kikuchipy/io/plugins/__init__.pyi | 32 ++------------------------- 3 files changed, 10 insertions(+), 50 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 3f5b948a..287b7f6c 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -33,20 +33,20 @@ from kikuchipy.io._util import _ensure_directory, _get_input_bool import kikuchipy.signals -plugins: list = [] +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"] = f"kikuchipy.io.plugins.{path.parts[-2]}" - plugins.append(spec) + PLUGINS.append(spec) if spec["writes"]: for ext in spec["file_extensions"]: write_extensions.append(ext) for plugin in IO_PLUGINS: if plugin["name"].lower() in ["hspy", "zspy"]: - plugins.append("plugin") + PLUGINS.append(plugin) if TYPE_CHECKING: # pragma: no cover @@ -112,7 +112,7 @@ def load( # Find matching reader for file extension extension = os.path.splitext(filename)[1][1:] readers = [] - for plugin in plugins: + for plugin in PLUGINS: if extension.lower() in plugin["file_extensions"]: readers.append(plugin) @@ -391,7 +391,7 @@ def _save( filename += "." + ext writer = None - for plugin in plugins: + for plugin in PLUGINS: if ext.lower() in plugin["file_extensions"] and plugin["writes"]: writer = plugin break @@ -407,7 +407,7 @@ def _save( if writer.writes is not True and (sig_dim, nav_dim) not in writer["writes"]: # Get writers that can write this data writing_plugins = [] - for plugin in plugins: + for plugin in PLUGINS: if ( plugin["writes"] is True or plugin["writes"] is not False diff --git a/src/kikuchipy/io/plugins/__init__.py b/src/kikuchipy/io/plugins/__init__.py index 85e196a9..d94a5892 100644 --- a/src/kikuchipy/io/plugins/__init__.py +++ b/src/kikuchipy/io/plugins/__init__.py @@ -17,7 +17,7 @@ """Input/output plugins. -.. currentmodule:: kikuchipy.io.plugins +.. currentmodule:: kikuchipy.io .. rubric:: Modules @@ -25,19 +25,7 @@ :toctree: ../generated/ :template: custom-module-template.rst - 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 + plugins """ import lazy_loader 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"] From 8e8fdf7fc248af26da0e263093a84510cead2008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:51:18 +0100 Subject: [PATCH 20/49] Remove unused HyperSpy markers tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/draw/markers.py | 117 ------------- tests/test_draw/test_markers.py | 286 -------------------------------- 2 files changed, 403 deletions(-) delete mode 100644 src/kikuchipy/draw/markers.py delete mode 100644 tests/test_draw/test_markers.py 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/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 - ) From ec9e7e0b639de5587f5af414913509b0942e8b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:51:41 +0100 Subject: [PATCH 21/49] Remove passing of parallel=True to HyperSpy's map function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/signals/_kikuchipy_signal.py | 164 ++++++++++----------- src/kikuchipy/signals/ebsd.py | 39 +++-- 2 files changed, 97 insertions(+), 106 deletions(-) diff --git a/src/kikuchipy/signals/_kikuchipy_signal.py b/src/kikuchipy/signals/_kikuchipy_signal.py index a8c234fd..66ca4268 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,14 @@ 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, + "parallel": True, + "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 +472,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 +512,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..66b22e9a 100644 --- a/src/kikuchipy/signals/ebsd.py +++ b/src/kikuchipy/signals/ebsd.py @@ -541,16 +541,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) @@ -666,17 +665,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) @@ -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, From bb2e2d380acec4debf640a1886336244d7124ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:52:13 +0100 Subject: [PATCH 22/49] Update IO plugin tests following updates to RosettaSciIO's design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_io/test_emsoft_ebsd.py | 2 +- tests/test_io/test_io.py | 2 +- tests/test_io/test_kikuchipy_h5ebsd.py | 2 +- tests/test_io/test_nordif.py | 2 +- tests/test_io/test_oxford_binary.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_io/test_emsoft_ebsd.py b/tests/test_io/test_emsoft_ebsd.py index 13390a8e..ccfa1cc3 100644 --- a/tests/test_io/test_emsoft_ebsd.py +++ b/tests/test_io/test_emsoft_ebsd.py @@ -23,7 +23,7 @@ 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: diff --git a/tests/test_io/test_io.py b/tests/test_io/test_io.py index d9da91e0..2db3b59a 100644 --- a/tests/test_io/test_io.py +++ b/tests/test_io/test_io.py @@ -40,7 +40,7 @@ def test_load(self, kikuchipy_h5ebsd_path, tmpdir, filename): _ = kp.load(new_file_path) def test_dict2signal(self, kikuchipy_h5ebsd_path): - scan_dict = kp.io.plugins.kikuchipy_h5ebsd.file_reader( + scan_dict = kp.io.plugins._api.file_reader( kikuchipy_h5ebsd_path / "patterns.h5" )[0] scan_dict["metadata"]["Signal"]["record_by"] = "not-image" diff --git a/tests/test_io/test_kikuchipy_h5ebsd.py b/tests/test_io/test_kikuchipy_h5ebsd.py index fd5f66bb..0fb759f2 100644 --- a/tests/test_io/test_kikuchipy_h5ebsd.py +++ b/tests/test_io/test_kikuchipy_h5ebsd.py @@ -26,7 +26,7 @@ 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, ) diff --git a/tests/test_io/test_nordif.py b/tests/test_io/test_nordif.py index 20af57af..f5f54af2 100644 --- a/tests/test_io/test_nordif.py +++ b/tests/test_io/test_nordif.py @@ -32,7 +32,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 = { diff --git a/tests/test_io/test_oxford_binary.py b/tests/test_io/test_oxford_binary.py index 8681b254..77135cf1 100644 --- a/tests/test_io/test_oxford_binary.py +++ b/tests/test_io/test_oxford_binary.py @@ -110,5 +110,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 = kp.io.plugins._api.OxfordBinaryFileReader(f) assert fox.n_patterns == n_patterns From 0f0d24d3d578f7bc923f16ef98f2db2410e4d755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 11:52:32 +0100 Subject: [PATCH 23/49] Import RGB8 and RGB16 data types from RosettaSciIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_draw/test_navigators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4c94df98855f3eecd80dfbac0175a5ecd2176700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 12:22:00 +0100 Subject: [PATCH 24/49] Replace HyperSpy marker functionality so tests don't error (not meant to work!) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/imaging/vbse.py | 4 ---- src/kikuchipy/simulations/_kikuchi_pattern_simulation.py | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/kikuchipy/imaging/vbse.py b/src/kikuchipy/imaging/vbse.py index bb9f6f72..6e98ab62 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 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 From c33a3cc9056fba328cd2ce048f2dcdc506dd3486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 12:39:42 +0100 Subject: [PATCH 25/49] Fix incorrect combining of desired projection/hemisphere labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../io/plugins/_emsoft_master_pattern.py | 19 ++++++++++++------- tests/test_data/test_data.py | 1 - 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py index bea1eb46..3a9b783d 100644 --- a/src/kikuchipy/io/plugins/_emsoft_master_pattern.py +++ b/src/kikuchipy/io/plugins/_emsoft_master_pattern.py @@ -311,24 +311,29 @@ def _get_datasets( ------- 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: raise ValueError( 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": datasets = [] - for proj_label, hemi_label in zip(projections.values(), hemispheres.values()): - datasets.append(data_group[proj_label + hemi_label]) + 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( 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 From c68fe163e66981389558b727a025ea623c257652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 12:51:39 +0100 Subject: [PATCH 26/49] Improve error handling for incompatible IO plugin and signal type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 16 +++++++++++++--- tests/test_io/test_edax_h5ebsd.py | 10 ++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 287b7f6c..5b9be373 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -379,6 +379,15 @@ def _save( **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`. @@ -404,7 +413,7 @@ def _save( else: 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"]: + if writer["writes"] is not True and (sig_dim, nav_dim) not in writer["writes"]: # Get writers that can write this data writing_plugins = [] for plugin in PLUGINS: @@ -414,9 +423,10 @@ def _save( and (sig_dim, nav_dim) in plugin["writes"] ): writing_plugins.append(plugin) + writing_plugins_list = map(lambda d: d["name"], writing_plugins) 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: {strlist2enumeration(writing_plugins_list)}" ) _ensure_directory(filename) diff --git a/tests/test_io/test_edax_h5ebsd.py b/tests/test_io/test_edax_h5ebsd.py index 5fb8b671..23920b38 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,7 @@ 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( + ValueError, match="Chosen IO plugin 'kikuchipy_h5ebsd' cannot write this " + ): s.save(file, add_scan=True) From 7939f332448de9f6898312cbc38c07c9ebd9a4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 13:41:05 +0100 Subject: [PATCH 27/49] Set specification "_api" key using part of Path.parts list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 5b9be373..d1feee31 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -39,7 +39,7 @@ for path in specification_paths: with open(path) as file: spec = yaml.safe_load(file) - spec["api"] = f"kikuchipy.io.plugins.{path.parts[-2]}" + spec["api"] = ".".join(path.parts[-5:-1]) PLUGINS.append(spec) if spec["writes"]: for ext in spec["file_extensions"]: From 687e9444326f6938536bb7329130f7d180f6b142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 13:41:53 +0100 Subject: [PATCH 28/49] Add bounds to VirtualBSEImager.grid_shape based on signal shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/imaging/vbse.py | 28 ++++++++++++++++--- tests/test_imaging/test_virtual_bse_imager.py | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/kikuchipy/imaging/vbse.py b/src/kikuchipy/imaging/vbse.py index 6e98ab62..fda6ca2d 100644 --- a/src/kikuchipy/imaging/vbse.py +++ b/src/kikuchipy/imaging/vbse.py @@ -44,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) @@ -66,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/tests/test_imaging/test_virtual_bse_imager.py b/tests/test_imaging/test_virtual_bse_imager.py index 38226779..c278441c 100644 --- a/tests/test_imaging/test_virtual_bse_imager.py +++ b/tests/test_imaging/test_virtual_bse_imager.py @@ -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: From f991c004360152823c20d7fcce49270c7fe975c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 16:14:04 +0100 Subject: [PATCH 29/49] Fix checking whether IO plugin writer is compatible with signal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index d1feee31..2e30222a 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -413,20 +413,18 @@ def _save( else: 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"]: - # Get writers that can write this data - writing_plugins = [] + 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 (sig_dim, nav_dim) in plugin["writes"] + and [sig_dim, nav_dim] in plugin["writes"] ): - writing_plugins.append(plugin) - writing_plugins_list = map(lambda d: d["name"], writing_plugins) + compatible_plugin_names.append(plugin["name"]) raise ValueError( f"Chosen IO plugin {writer["name"]!r} cannot write this data. The " - f"following plugins can: {strlist2enumeration(writing_plugins_list)}" + f"following plugins can: {strlist2enumeration(compatible_plugin_names)}" ) _ensure_directory(filename) From ec3a64f578a68c2a2ebca52b155a67bf928c1896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 16:14:38 +0100 Subject: [PATCH 30/49] Fix tests with now incorrect imports and other improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_io/test_emsoft_ebsd.py | 12 +++--- tests/test_io/test_emsoft_master_pattern.py | 4 +- tests/test_io/test_io.py | 42 ++++++++++----------- tests/test_io/test_oxford_binary.py | 3 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/tests/test_io/test_emsoft_ebsd.py b/tests/test_io/test_emsoft_ebsd.py index ccfa1cc3..41a7efc2 100644 --- a/tests/test_io/test_emsoft_ebsd.py +++ b/tests/test_io/test_emsoft_ebsd.py @@ -16,7 +16,7 @@ # 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 @@ -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 2db3b59a..74faea7f 100644 --- a/tests/test_io/test_io.py +++ b/tests/test_io/test_io.py @@ -22,27 +22,27 @@ 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._api.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 +105,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") diff --git a/tests/test_io/test_oxford_binary.py b/tests/test_io/test_oxford_binary.py index 77135cf1..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._api.OxfordBinaryFileReader(f) + fox = OxfordBinaryFileReader(f) assert fox.n_patterns == n_patterns From ff3fd13a82ee0c2d75555e54d9dbb2d3d2da7347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 17:47:41 +0100 Subject: [PATCH 31/49] Fix final errors in IO tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 5 ++++ src/kikuchipy/io/plugins/nordif/_api.py | 7 +++--- tests/test_io/test_edax_h5ebsd.py | 4 +--- tests/test_io/test_kikuchipy_h5ebsd.py | 32 ++++++++++++------------- tests/test_io/test_nordif.py | 14 +---------- 5 files changed, 27 insertions(+), 35 deletions(-) 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/src/kikuchipy/io/plugins/nordif/_api.py b/src/kikuchipy/io/plugins/nordif/_api.py index 5e7bfbe7..7e025db0 100644 --- a/src/kikuchipy/io/plugins/nordif/_api.py +++ b/src/kikuchipy/io/plugins/nordif/_api.py @@ -421,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 @@ -448,8 +449,8 @@ def file_writer(filename: str, signal: "EBSD | LazyEBSD") -> None: """ with open(filename, "wb") as file: if signal._lazy: - for pattern in signal._iterate_signal(): + for pattern in signal._iterate_signal(iterpath="flyback"): np.array(pattern.flatten()).tofile(file) else: - for pattern in signal._iterate_signal(): + for pattern in signal._iterate_signal(iterpath="flyback"): pattern.flatten().tofile(file) diff --git a/tests/test_io/test_edax_h5ebsd.py b/tests/test_io/test_edax_h5ebsd.py index 23920b38..9d353cdc 100644 --- a/tests/test_io/test_edax_h5ebsd.py +++ b/tests/test_io/test_edax_h5ebsd.py @@ -51,7 +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( - ValueError, match="Chosen IO plugin 'kikuchipy_h5ebsd' cannot write this " - ): + with pytest.raises(OSError, match="(.*) is not a supported kikuchipy h5ebsd "): s.save(file, add_scan=True) diff --git a/tests/test_io/test_kikuchipy_h5ebsd.py b/tests/test_io/test_kikuchipy_h5ebsd.py index 0fb759f2..ad4c9b69 100644 --- a/tests/test_io/test_kikuchipy_h5ebsd.py +++ b/tests/test_io/test_kikuchipy_h5ebsd.py @@ -18,7 +18,7 @@ import os import dask.array as da -from h5py import Dataset, File +import h5py from hyperspy.api import load as hs_load import numpy as np from orix.quaternion import Rotation @@ -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: @@ -242,7 +242,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) @@ -280,13 +280,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,)) @@ -354,7 +354,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 f5f54af2..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 @@ -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 From b88a976312bb2755e7215f65ba4bff13b06c6f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 17:52:46 +0100 Subject: [PATCH 32/49] Revert to "old" HyperSpy entry point path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 278f4f76..1db76da5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ dev = [ ] [project.entry-points."hyperspy.extensions"] -kikuchipy = "src/kikuchipy" +kikuchipy = "kikuchipy" [project.urls] Documentation = "https://kikuchipy.org" From f70d5ca50c7b41f3ba2687e4be7ac8403c604542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 17:53:06 +0100 Subject: [PATCH 33/49] Force Python float in EBSDDetector string repr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/detectors/ebsd_detector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}, " From 61c4ed38d26fe9c7c2c777617966da52be3a9ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:23:54 +0100 Subject: [PATCH 34/49] Ensure Python floats and ints in string repr and errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/indexing/_hough_indexing.py | 10 ++++------ tests/test_signals/test_ebsd_hough_indexing.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) 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/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" From d5b26bddfd91e158cc66177addd5957f14ac058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:25:48 +0100 Subject: [PATCH 35/49] Use crop_signal instead of crop_image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- examples/selecting_data/crop_signal_axes.py | 6 +++--- src/kikuchipy/signals/_kikuchipy_signal.py | 1 - src/kikuchipy/signals/ebsd.py | 13 ++++++------ tests/test_signals/test_ebsd.py | 22 ++++++++++----------- 4 files changed, 20 insertions(+), 22 deletions(-) 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/src/kikuchipy/signals/_kikuchipy_signal.py b/src/kikuchipy/signals/_kikuchipy_signal.py index 66ca4268..97106717 100644 --- a/src/kikuchipy/signals/_kikuchipy_signal.py +++ b/src/kikuchipy/signals/_kikuchipy_signal.py @@ -440,7 +440,6 @@ def adaptive_histogram_equalization( map_kw = { "show_progressbar": show_progressbar, - "parallel": True, "output_dtype": dtype_out, "kernel_size": kernel_size, "clip_limit": clip_limit, diff --git a/src/kikuchipy/signals/ebsd.py b/src/kikuchipy/signals/ebsd.py index 66b22e9a..2e8c6073 100644 --- a/src/kikuchipy/signals/ebsd.py +++ b/src/kikuchipy/signals/ebsd.py @@ -505,7 +505,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 @@ -632,7 +632,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 @@ -867,7 +867,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 @@ -994,7 +994,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) @@ -1151,7 +1151,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") @@ -1189,7 +1189,6 @@ def downsample( map_kw = { "show_progressbar": show_progressbar, - "parallel": True, "output_dtype": dtype_out, "factor": factor, "omin": omin, @@ -2709,7 +2708,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/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) From a5e1f06d68fef0a16a67fc2765a6ff8e0c06c118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:26:16 +0100 Subject: [PATCH 36/49] Ensure Python float in xmap attribute warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/signals/util/_crystal_map.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 From dc572e2acd5d063e6ba8b213ada15ea2a4f9ad09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:26:56 +0100 Subject: [PATCH 37/49] Fix VirtualBSEImager.grid_shape test following change to default shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_imaging/test_virtual_bse_imager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_imaging/test_virtual_bse_imager.py b/tests/test_imaging/test_virtual_bse_imager.py index c278441c..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() From db72dcc6d51611fdf397fec8f9e5a5df025ce1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:27:23 +0100 Subject: [PATCH 38/49] Update warning message in IO test following change to Python float MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_io/test_kikuchipy_h5ebsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_io/test_kikuchipy_h5ebsd.py b/tests/test_io/test_kikuchipy_h5ebsd.py index ad4c9b69..6833a127 100644 --- a/tests/test_io/test_kikuchipy_h5ebsd.py +++ b/tests/test_io/test_kikuchipy_h5ebsd.py @@ -320,7 +320,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 From 35e910596f442eaca69d7908c345f0d3b4289a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:27:58 +0100 Subject: [PATCH 39/49] Update error message matches in tests following replace of `` with '' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_signals/test_ebsd_master_pattern.py | 4 ++-- tests/test_signals/test_ecp_master_pattern.py | 4 ++-- tests/test_signals/test_virtual_bse_image.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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) From 3109e0756a091ddccf253901fc47eb8fc424686e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 18:33:28 +0100 Subject: [PATCH 40/49] Fix f-string by using '' within "" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 2e30222a..7180ed37 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -423,7 +423,7 @@ def _save( ): compatible_plugin_names.append(plugin["name"]) raise ValueError( - f"Chosen IO plugin {writer["name"]!r} cannot write this data. The " + f"Chosen IO plugin {writer['name']!r} cannot write this data. The " f"following plugins can: {strlist2enumeration(compatible_plugin_names)}" ) From 58fbe20b2648e842a29c3236f2de20e889d44042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 19:55:00 +0100 Subject: [PATCH 41/49] Fix IO API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/plugins/__init__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/kikuchipy/io/plugins/__init__.py b/src/kikuchipy/io/plugins/__init__.py index d94a5892..85e196a9 100644 --- a/src/kikuchipy/io/plugins/__init__.py +++ b/src/kikuchipy/io/plugins/__init__.py @@ -17,7 +17,7 @@ """Input/output plugins. -.. currentmodule:: kikuchipy.io +.. currentmodule:: kikuchipy.io.plugins .. rubric:: Modules @@ -25,7 +25,19 @@ :toctree: ../generated/ :template: custom-module-template.rst - plugins + 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 """ import lazy_loader From e7b9f2a8832ba91e202e3878a031d1b88fe64277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 20:02:03 +0100 Subject: [PATCH 42/49] Install ipykernel with pip on RTD using environments.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 4f7f9576..847e6e91 100644 --- a/environment.yml +++ b/environment.yml @@ -4,5 +4,6 @@ channels: dependencies: - python=3.11 - pip + - ipykernel - pip: - --editable .[doc,all] \ No newline at end of file From 7ae205778d3a85753b6362db6ce312169f57d18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 27 Oct 2024 20:13:18 +0100 Subject: [PATCH 43/49] Add ipykernel in environment.yml file for RTD, not for binder... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- doc/environment.yml | 3 ++- environment.yml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) 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/environment.yml b/environment.yml index 847e6e91..4f7f9576 100644 --- a/environment.yml +++ b/environment.yml @@ -4,6 +4,5 @@ channels: dependencies: - python=3.11 - pip - - ipykernel - pip: - --editable .[doc,all] \ No newline at end of file From 34990dbfd76484ce5cc0efa0926ac68f278e4041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 19:38:20 +0100 Subject: [PATCH 44/49] Add convenience function to determine whether to overwrite file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_util.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) 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: From 8659c6847ef336f725d1c986303f57e44710ad1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 19:39:04 +0100 Subject: [PATCH 45/49] Fall back to BaseSignal.save() if no matching write plugin found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 7180ed37..501a8dbd 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -22,15 +22,12 @@ from typing import TYPE_CHECKING import h5py -from hyperspy.misc.utils import find_subclasses, strlist2enumeration +from hyperspy.misc.utils import find_subclasses from hyperspy.signal import BaseSignal import numpy as np -from rsciio import IO_PLUGINS -from rsciio.utils.tools import get_object_package_info -from rsciio.utils.tools import overwrite as overwrite_method import yaml -from kikuchipy.io._util import _ensure_directory, _get_input_bool +from kikuchipy.io._util import _ensure_directory, _get_input_bool, _overwrite import kikuchipy.signals PLUGINS: list = [] @@ -44,9 +41,6 @@ if spec["writes"]: for ext in spec["file_extensions"]: write_extensions.append(ext) -for plugin in IO_PLUGINS: - if plugin["name"].lower() in ["hspy", "zspy"]: - PLUGINS.append(plugin) if TYPE_CHECKING: # pragma: no cover @@ -405,6 +399,13 @@ def _save( writer = plugin break + if writer is None and ext.lower() in ["hspy", "zspy"]: + try: + super(type(signal), signal).save(filename, overwrite=overwrite, **kwargs) + return + except () as e: + raise ValueError("Attempt to write with HyperSpy failed:") from e + if writer is None: raise ValueError( f"{ext!r} does not correspond to any supported format. Supported file " @@ -424,7 +425,7 @@ def _save( compatible_plugin_names.append(plugin["name"]) raise ValueError( f"Chosen IO plugin {writer['name']!r} cannot write this data. The " - f"following plugins can: {strlist2enumeration(compatible_plugin_names)}" + f"following plugins can: {compatible_plugin_names}" ) _ensure_directory(filename) @@ -433,8 +434,13 @@ def _save( # Check if we are to add signal to an already existing h5ebsd file if writer["name"] == "kikuchipy_h5ebsd" and overwrite is not True and is_file: if add_scan is None: - question = f"Add scan to {filename!r} (y/n)?\n" - add_scan = _get_input_bool(question) + 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: # So that the 2nd statement below triggers overwrite = True @@ -442,7 +448,7 @@ def _save( # 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 elif overwrite is False and is_file: @@ -454,16 +460,8 @@ def _save( ) if write: - if writer["name"].lower() in ["hspy", "zspy"]: - signal_dict = signal._to_dictionary(add_models=True) - signal_dict["package_info"] = get_object_package_info(signal) - kwargs["signal"] = signal_dict - else: - kwargs["signal"] = signal - file_writer = importlib.import_module(writer["api"]).file_writer - file_writer(filename, **kwargs) - + 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]) From 51d9a803ef463af7c69bcb47888b910aef1dc067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 19:40:11 +0100 Subject: [PATCH 46/49] Add Path type option to EBSD.save() signature, improve docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/signals/ebsd.py | 53 +++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/kikuchipy/signals/ebsd.py b/src/kikuchipy/signals/ebsd.py index 2e8c6073..0c36b386 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 @@ -2588,49 +2589,53 @@ 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. Alternatively, + the extension can be passed to *extension*. 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: tmp_params = self.tmp_parameters From 1b7599632c529dce039cc1d3ac4b2870ecefb282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 19:42:03 +0100 Subject: [PATCH 47/49] Fix tests of internal IO convenience methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- tests/test_io/test_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)) From b4fb34343c3272f2862d35f3b556cc8c89001d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 19:42:22 +0100 Subject: [PATCH 48/49] Remove explicit dependency on RosettaSciIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- doc/user/installation.rst | 1 - pyproject.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 69ec28e0..b3915834 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -106,7 +106,6 @@ 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 -* :doc:`rosettasciio `: Read/write of some file formats * :doc:`scikit-image `: Image processing like adaptive histogram equalization * `scikit-learn `__: Multivariate analysis diff --git a/pyproject.toml b/pyproject.toml index 1db76da5..75015376 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,6 @@ dependencies = [ "orix >= 0.12.1", "pooch >= 1.3.0", "pyyaml", - "rosettasciio", "scikit-image >= 0.16.2", "scikit-learn", "scipy >= 1.7", From 099349e3d47cbe64ebb77993ab4f95ab704a1eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Mon, 28 Oct 2024 20:22:18 +0100 Subject: [PATCH 49/49] Simplify path to saving to H/ZSPY from EBSD.save() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/io/_io.py | 13 +++---------- src/kikuchipy/signals/ebsd.py | 23 ++++++++++++++--------- tests/test_io/test_io.py | 10 ++++++++++ tests/test_io/test_kikuchipy_h5ebsd.py | 7 +++---- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 501a8dbd..55e083d5 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -31,7 +31,7 @@ import kikuchipy.signals PLUGINS: list = [] -write_extensions = [] +WRITE_EXTENSIONS = [] specification_paths = list(Path(__file__).parent.rglob("specification.yaml")) for path in specification_paths: with open(path) as file: @@ -40,7 +40,7 @@ PLUGINS.append(spec) if spec["writes"]: for ext in spec["file_extensions"]: - write_extensions.append(ext) + WRITE_EXTENSIONS.append(ext) if TYPE_CHECKING: # pragma: no cover @@ -399,17 +399,10 @@ def _save( writer = plugin break - if writer is None and ext.lower() in ["hspy", "zspy"]: - try: - super(type(signal), signal).save(filename, overwrite=overwrite, **kwargs) - return - except () as e: - raise ValueError("Attempt to write with HyperSpy failed:") from e - if writer is None: raise ValueError( f"{ext!r} does not correspond to any supported format. Supported file " - f"extensions are: {write_extensions!r}" + f"extensions are: {WRITE_EXTENSIONS!r}" ) else: sig_dim = signal.axes_manager.signal_dimension diff --git a/src/kikuchipy/signals/ebsd.py b/src/kikuchipy/signals/ebsd.py index 0c36b386..395f6e49 100644 --- a/src/kikuchipy/signals/ebsd.py +++ b/src/kikuchipy/signals/ebsd.py @@ -2604,8 +2604,7 @@ def save( filename 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. Alternatively, - the extension can be passed to *extension*. + 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. @@ -2637,19 +2636,25 @@ def save( 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, diff --git a/tests/test_io/test_io.py b/tests/test_io/test_io.py index 74faea7f..96bdb0d4 100644 --- a/tests/test_io/test_io.py +++ b/tests/test_io/test_io.py @@ -17,6 +17,7 @@ import os +import hyperspy.api as hs import numpy as np import pytest @@ -120,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 6833a127..ad898a41 100644 --- a/tests/test_io/test_kikuchipy_h5ebsd.py +++ b/tests/test_io/test_kikuchipy_h5ebsd.py @@ -19,7 +19,7 @@ import dask.array as da import h5py -from hyperspy.api import load as hs_load +import hyperspy.api as hs import numpy as np from orix.quaternion import Rotation import pytest @@ -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) @@ -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"