diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6cee5d91..be522fae 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,6 +18,7 @@ #### For reviewers +- [ ] Files with a BSD 3-Clause license do not import from GPLv3+ software. - [ ] The PR title is short, concise, and will make sense 1 year later. - [ ] New functions are imported in corresponding `__init__.py`. - [ ] New features, API changes, and deprecations are mentioned in the unreleased diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6db41468..132f10ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: if: ${{ contains(matrix.LABEL, 'minimum_requirement') == false && matrix.os == 'macos-latest' }} shell: bash run: | - pip install "pyebsdindex[gpu]" pyvista + pip install ipywidgets IPython "pooch>=1.3.0" psygnal "pyebsdindex[gpu]>=0.3.9.2,!=0.3.10" pyvista - name: Display Python, pip and package versions run: | @@ -134,6 +134,14 @@ jobs: run: | pytest ${{ env.PYTEST_ARGS }} + - name: Run GPU tests on macOS + if: ${{ matrix.os == 'macos-latest' }} + run: | + # TODO: Replace with patched version once it's released + pip install "ebsdsim @ git+https://github.com/hakonanes/ebsdsim@fix-1-multiprocessing-start-method-with-script" + + pytest ${{ env.PYTEST_ARGS }} -m gpu --gpu + - name: Generate line coverage run: | coverage report --show-missing diff --git a/.license_bsd.tmpl b/.license_bsd.tmpl new file mode 100644 index 00000000..c386771a --- /dev/null +++ b/.license_bsd.tmpl @@ -0,0 +1,3 @@ +Copyright ${years} the ${projectname} developers + +SPDX-License-Identifier: BSD-3-Clause diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e3d278e..c7039ecd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,8 @@ ## along with kikuchipy. If not, see . ## +exclude: ^\.github/pull_request_template.md + repos: # https://docs.astral.sh/ruff/configuration - repo: https://github.com/astral-sh/ruff-pre-commit @@ -35,6 +37,12 @@ repos: rev: v0.8.8 hooks: - id: licenseheaders + name: licenseheaders (BSD-3-Clause) + files: ^src/kikuchipy/(_constants\.py|io/plugins/ebsdsim_master_pattern/) + args: ["-t", ".license_bsd.tmpl", "-cy", "-n", "kikuchipy", "-f"] + - id: licenseheaders + name: licenseheaders (GPL-3.0-or-later) + exclude: ^src/kikuchipy/(_constants\.py|io/plugins/ebsdsim_master_pattern/) args: ["-t", ".license.tmpl", "-cy", "-n", "kikuchipy", "-f"] # https://pre-commit.ci/#configuration diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbd873f2..4f4c11fc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,10 @@ Added (`#796 `_) - Function to clear the kikuchipy data cache directory. (`#796 `_) +- Optional dependency on the ebsdsim Python package. + (`#809 `_) +- Reader for master patterns generated by the ebsdsim Python package. + (`#809 `_) Changed ------- diff --git a/LICENSE-BSD-3-Clause b/LICENSE-BSD-3-Clause new file mode 100644 index 00000000..2b699a6b --- /dev/null +++ b/LICENSE-BSD-3-Clause @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright 2026 the kikuchipy developers + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index bea70f46..e63a791c 100644 --- a/README.rst +++ b/README.rst @@ -37,9 +37,12 @@ library. .. |zenodo_doi| image:: https://zenodo.org/badge/doi/10.5281/zenodo.3597646.svg :target: https://doi.org/10.5281/zenodo.3597646 -.. |GPLv3| image:: https://img.shields.io/github/license/pyxem/kikuchipy +.. |GPLv3| image:: https://img.shields.io/badge/license-GPLv3%2B-blue :target: https://opensource.org/license/GPL-3.0 +.. |BSD3| image:: https://img.shields.io/badge/license_(select_components)-BSD--3--Clause-blue + :target: https://opensource.org/license/BSD-3-Clause + .. |GH-discuss| image:: https://img.shields.io/badge/GitHub-Discussions-green?logo=github :target: https://github.com/pyxem/kikuchipy/discussions @@ -63,13 +66,20 @@ library. +----------------------+------------------------------------------------+ | Citation | |arxiv_doi| |zenodo_doi| | +----------------------+------------------------------------------------+ -| License | |GPLv3| | +| License | |GPLv3| |BSD3| | +----------------------+------------------------------------------------+ | Community | |GH-discuss| | +----------------------+------------------------------------------------+ | Formatter | |black| | +----------------------+------------------------------------------------+ +kikuchipy is licensed under the `GNU General Public License v3+ +`__. +Select components are individually licensed under the `BSD 3-Clause License +`__, allowing reuse *of those components +only* in both GPL and non-GPL projects. + + Documentation ------------- @@ -77,6 +87,7 @@ Refer to the `documentation `__ for detailed installation instructions, a user guide, and the `changelog `__. + Installation ------------ @@ -95,6 +106,7 @@ You can also visit `PyPI `__, Further details are available in the `installation guide `__. + Citing kikuchipy ---------------- diff --git a/conftest.py b/conftest.py index d96d0085..73fe40cf 100644 --- a/conftest.py +++ b/conftest.py @@ -30,7 +30,7 @@ import os from pathlib import Path import tempfile -from typing import Callable, Generator +from typing import Callable, Generator, Literal import dask.array as da from diffpy.structure import Atom, Lattice, Structure @@ -65,18 +65,30 @@ def pytest_addoption(parser): # Flags for optional markers. Markers requiring something (installed # dependency, e.g.) should not have a flag to still run them. parser.addoption( - "--weekly", action="store_true", help="Run tests that should run only weekly" + "--gpu", + action="store_true", + help="Run tests that should run only when wgpu has a GPU available", + ) + parser.addoption( + "--weekly", + action="store_true", + help="Run tests that should run only weekly", ) MARKERS = [ + "gpu", "weekly", ] def pytest_runtest_setup(item): - # Skip certain tests when flag is missing: - # https://docs.pytest.org/en/stable/reference/reference.html#pytest.hookspec.pytest_runtest_setup + """Skip certain tests when flag is missing: + https://docs.pytest.org/en/stable/reference/reference.html#pytest.hookspec.pytest_runtest_setup. + + To run tests marked by this marker *only*, say, `gpu`, do + `pytest -m gpu --gpu`. + """ for marker in MARKERS: marker_str = f"--{marker}" if marker in item.keywords and not item.config.getoption( @@ -229,7 +241,7 @@ def dummy_background() -> Generator[np.ndarray, None, None]: yield np.array([5, 4, 5, 4, 3, 4, 4, 4, 3], dtype=np.uint8).reshape((3, 3)) -@pytest.fixture(params=[[(3, 3), (3, 3), False, np.float32]]) +@pytest.fixture def ebsd_with_axes_and_random_data(request) -> Generator[kp.signals.EBSD, None, None]: """EBSD signal with minimally defined axes and random data. @@ -240,7 +252,10 @@ def ebsd_with_axes_and_random_data(request) -> Generator[kp.signals.EBSD, None, lazy : bool dtype : numpy.dtype """ - nav_shape, sig_shape, lazy, dtype = request.param + nav_shape, sig_shape, lazy, dtype = getattr( + request, "param", [(3, 3), (3, 3), False, np.float32] + ) + nav_ndim = len(nav_shape) sig_ndim = len(sig_shape) data_shape = nav_shape + sig_shape @@ -287,12 +302,12 @@ def pc1() -> Generator[list[float], None, None]: yield [0.4210, 0.7794, 0.5049] -@pytest.fixture(params=[[(1,), (60, 60)]]) +@pytest.fixture def detector(request, pc1) -> Generator[kp.detectors.EBSDDetector, None, None]: """An EBSD detector of a given shape with a number of PCs given by a navigation shape. """ - nav_shape, sig_shape = request.param + nav_shape, sig_shape = getattr(request, "param", [(1,), (60, 60)]) yield kp.detectors.EBSDDetector( shape=sig_shape, binning=8, @@ -343,12 +358,12 @@ def _get_single_phase_xmap( # ---------------------------- IO fixtures --------------------------- # -@pytest.fixture(params=["h5"]) +@pytest.fixture def save_path_hdf5(request, tmpdir) -> Generator[Path, None, None]: """Temporary file in a temporary directory for use when tests need to write, and sometimes read again, a signal to, and from, a file. """ - ext = request.param + ext = getattr(request, "param", "h5") yield Path(tmpdir / f"patterns.{ext}") @@ -379,7 +394,7 @@ def ni_small_axes_manager() -> Generator[dict, None, None]: yield axes_manager -@pytest.fixture(params=[("_x{}y{}.tif", (3, 3))]) +@pytest.fixture def ebsd_directory(tmpdir, request) -> Generator[Path, None, None]: """Temporary directory with EBSD files as .tif, .png or .bmp files. @@ -391,7 +406,7 @@ def ebsd_directory(tmpdir, request) -> Generator[Path, None, None]: s = kp.data.nickel_ebsd_small() s.unfold_navigation_space() - xy_pattern, nav_shape = request.param + xy_pattern, nav_shape = getattr(request, "param", ("_x{}y{}.tif", (3, 3))) y, x = np.indices(nav_shape) x = x.ravel() y = y.ravel() @@ -427,7 +442,7 @@ def edax_binary_path() -> Generator[Path, None, None]: yield DATA_PATH / "edax_binary" -@pytest.fixture(params=[(1, (2, 3), (60, 60), "uint8", 2, False)]) +@pytest.fixture def edax_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: """Create a dummy EDAX binary UP1/2 file. @@ -444,7 +459,9 @@ def edax_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: is_hex : bool """ # Unpack parameters - up_ver, (ny, nx), (sy, sx), dtype, ver, is_hex = request.param + up_ver, (ny, nx), (sy, sx), dtype, ver, is_hex = getattr( + request, "param", (1, (2, 3), (60, 60), "uint8", 2, False) + ) if up_ver == 1: fname = tmpdir.join("dummy_edax_file.up1") @@ -497,7 +514,7 @@ def oxford_binary_path() -> Generator[Path, None, None]: yield DATA_PATH / "oxford_binary" -@pytest.fixture(params=[((2, 3), (60, 60), np.uint8, 2, False, True)]) +@pytest.fixture def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: """Create a dummy Oxford Instruments' binary .ebsp file. @@ -514,7 +531,9 @@ def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: all_present : bool """ # Unpack parameters - (nr, nc), (sr, sc), dtype, ver, compressed, all_present = request.param + (nr, nc), (sr, sc), dtype, ver, compressed, all_present = getattr( + request, "param", ((2, 3), (60, 60), np.uint8, 2, False, True) + ) fname = tmpdir.join("dummy_oxford_file.ebsp") f = open(fname, mode="w") @@ -580,7 +599,7 @@ def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: yield f -@pytest.fixture(params=["7.0"]) +@pytest.fixture def oxford_h5ebsd_file(tmpdir, request) -> Generator[Path, None, None]: """Yield the file path to a temporary H5OINA file. @@ -588,12 +607,29 @@ def oxford_h5ebsd_file(tmpdir, request) -> Generator[Path, None, None]: ------------------------------- version : "6.0" or "7.0" """ - version = request.param + version: Literal["6.0", "7.0"] = getattr(request, "param", "7.0") fpath = tmpdir / "patterns.h5oina" create_dummy_oxford_h5ebsd_file(fpath, version=version) yield fpath +# -------------------------- ebsdsim formats ------------------------- # + + +@pytest.fixture(scope="session") +def ebsdsim_master_pattern_file(tmp_path_factory) -> Generator[Path, None, None]: + """Minimal ebsdsim Ni master pattern .npz file, created once per + session. + """ + from kikuchipy.data._dummy_files.ebsdsim_master_pattern_npz import ( + create_small_ebsdsim_npz_file, + ) + + fpath = tmp_path_factory.mktemp("ebsdsim") / "ni_master_pattern.npz" + create_small_ebsdsim_npz_file(fpath) + yield fpath + + # -------------------------- EMsoft formats -------------------------- # @@ -621,9 +657,9 @@ def emsoft_ebsd_master_pattern_metadata() -> Generator[dict, None, None]: } -@pytest.fixture(params=[["hemisphere", "energy", "height", "width"]]) +@pytest.fixture def emsoft_ebsd_master_pattern_axes_manager(request) -> Generator[dict, None, None]: - axes = request.param + axes = getattr(request, "param", ["hemisphere", "energy", "height", "width"]) am = { "hemisphere": { "name": "hemisphere", diff --git a/doc/conf.py b/doc/conf.py index e5ced56e..9dc22ec4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -375,7 +375,7 @@ def _str_examples(self): "gallery_dirs": "examples", "reference_url": {"kikuchipy": None}, "run_stale_examples": False, - "show_memory": True, + "show_memory": False, # Takes too long time } autosummary_generate = True diff --git a/doc/dev/improving_performance.rst b/doc/dev/improving_performance.rst index ddb276a8..a2bed512 100644 --- a/doc/dev/improving_performance.rst +++ b/doc/dev/improving_performance.rst @@ -2,14 +2,16 @@ Improving performance ===================== When we write code, it's important that we (1) get the correct result, (2) don't fill up -memory, and (3) that the computation doesn't take too long. To keep memory in check, we -should use `Dask `__ wherever possible. To speed up -computations, we should use `Numba `__ wherever -possible. +memory, and (3) that the computation doesn't take too long. +To keep memory in check, we should use `Dask `__ +wherever possible. +To speed up computations, we should use `Numba +`__ wherever possible. To check whether a change is an improvement or a regression, a benchmark should be -written. These are stored in the top directory ``kikuchipy/benchmarks``. Benchmarks are -run using `pytest-benchmark +written. +These are stored in the top directory ``kikuchipy/benchmarks``. +Benchmarks are run using `pytest-benchmark `__:: pytest --benchmark-only \ No newline at end of file diff --git a/doc/dev/index.rst b/doc/dev/index.rst index 837caf5c..7ba1fbca 100644 --- a/doc/dev/index.rst +++ b/doc/dev/index.rst @@ -4,17 +4,18 @@ Contributing ============ -kikuchipy is a community maintained project. We welcome contributions in the form of bug -reports, documentation, code, feature requests, and more. The source code is hosted on -`GitHub `__. These guidelines provide resources on -how best to contribute. +kikuchipy is a community maintained project. +We welcome contributions in the form of bug reports, documentation, code, feature +requests, and more. +The source code is hosted on `GitHub `__. +These guidelines provide resources on how best to contribute. .. tip:: This guide can look intimidating to people who want to contribute, but have limited - experience with tools like ``git``, ``pytest``, and ``sphinx``. The shortest route - to start contributing is to create a GitHub account and explain what you want to do - `in an issue `__. + experience with tools like ``git``, ``pytest``, and ``sphinx``. + The shortest route to start contributing is to create a GitHub account and explain + what you want to do `in an issue `__. That said, our contributing workflow is typical for Python projects, so reading this guide can make contributing to similar projects in the future much smoother! @@ -26,8 +27,8 @@ Have a question, comment, suggestion for improvements, or any other inquiries regarding the project? Feel free to `ask a question `__, `open an issue `__ or `make a pull request -`__ in our GitHub repository. We also have a -`Gitter chat `__. +`__ in our GitHub repository. +We also have a `Gitter chat `__. .. toctree:: :maxdepth: 1 @@ -40,6 +41,7 @@ regarding the project? Feel free to `ask a question running_writing_tests.rst adding_to_data_module.rst improving_performance.rst + licensing_considerations.rst continuous_integration.rst maintaining_package_credits.rst Code of conduct \ No newline at end of file diff --git a/doc/dev/licensing_considerations.rst b/doc/dev/licensing_considerations.rst new file mode 100644 index 00000000..a285ee24 --- /dev/null +++ b/doc/dev/licensing_considerations.rst @@ -0,0 +1,26 @@ +Licensing considerations +======================== + +As mentioned :doc:`in the user guide `, kikuchipy as a whole carries +the GPLv3+ license, while select components carry a more permissive BSD 3-Clause +license. + +For developers, this means: + +- When introducing new functionality to kikuchipy, we have the option of giving it the + more permissive BSD license. +- To honor the more permissive license, we cannot import from GPL licensed code in the + files and directories that carry the BSD license. + +For reviewers, this means we must consider the following when reviewing a PR: + +- If used, does the code honor the more permissive BSD license? +- Does the contributor know about the option of licensing using BSD instead of GPL? + If not, we can suggest this. + +Why this change from all GPL to GPL + some BSD? +Some arguments are given in a comment on `this RosettaSciIO discussion about licensing +`. +If you have thoughts or concerns regarding licensing, feel free to `open an issue on +our GitHub repo `_ or send an email to +the maintainers at pyxem.team@gmail.com. diff --git a/doc/index.rst b/doc/index.rst index 8c79d258..7272cd3e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,6 +6,8 @@ kikuchipy is a library for processing, simulating and analyzing electron backsca diffraction (EBSD) patterns in Python, built on the tools for multi-dimensional data analysis provided by the HyperSpy library. +See :doc:`user/licensing` for details on licensing. + .. toctree:: :hidden: :titlesonly: @@ -15,6 +17,7 @@ analysis provided by the HyperSpy library. dev/index.rst changelog.rst + Installation ============ @@ -37,6 +40,7 @@ kikuchipy can be installed with `pip `__ or Further details are available in the :doc:`installation guide `. + Learning resources ================== @@ -81,6 +85,7 @@ Learning resources kikuchipy is a community project maintained for and by its users. There are many ways you can help! + Citing kikuchipy ================ diff --git a/doc/tutorials/load_save_data.ipynb b/doc/tutorials/load_save_data.ipynb index 9d9b5dd9..4a3ec60b 100644 --- a/doc/tutorials/load_save_data.ipynb +++ b/doc/tutorials/load_save_data.ipynb @@ -495,6 +495,7 @@ "\n", "| Format | Read | Write |\n", "| ------------------------------------------------------------------------- | ---- | ----- |\n", + "| [ebsdsim EBSD master pattern npz](#ebsdsim-ebsd-master-pattern-npz) | Yes | No |\n", "| [EMsoft simulated EBSD HDF5](#EMsoft-simulated-EBSD-HDF5) | Yes | No |\n", "| [EMsoft EBSD master pattern HDF5](#EMsoft-EBSD-master-pattern-HDF5) | Yes | No |\n", "| [EMsoft ECP master pattern HDF5](#EMsoft-ECP-master-pattern-HDF5) | Yes | No |\n", @@ -518,6 +519,35 @@ "" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ebsdsim EBSD master pattern npz\n", + "\n", + "Dynamically simulated EBSD patterns saved by [ebsdsim](https://github.com/ZacharyVarley/ebsdsim) in *.npz files can be read as an `EBSDMasterPattern` signal.\n", + "\n", + "
\n", + "\n", + "Note\n", + "\n", + "This reader requires that *ebsdsim* is installed, which is an optional requirement.\n", + "\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# mp_ebsdsim = kp.load(\n", + "# \"file.npz\",\n", + "# hemisphere=\"upper\", # \"upper\", \"lower\", \"both\"\n", + "# )" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1210,7 +1240,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "kp", "language": "python", "name": "python3" }, @@ -1224,7 +1254,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.14.5" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/doc/user/index.rst b/doc/user/index.rst index d939c05a..2f53c1ef 100644 --- a/doc/user/index.rst +++ b/doc/user/index.rst @@ -24,4 +24,4 @@ User guide applications.rst open_datasets.rst related_projects.rst - License + licensing.rst diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 5d5bebc8..ec6efa90 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -125,6 +125,9 @@ This is a list of core package dependencies: Some functionality requires optional dependencies: +* `ebsdsim `__: Dynamical EBSD simulations and + reading of simulations produced by `ebsdsim`. + Currently unavailable from conda-forge. * :doc:`ipywidgets `: Interactive widgets in Jupyter notebooks. * `IPython `__: Interactive widgets in Jupyter notebooks. diff --git a/doc/user/licensing.rst b/doc/user/licensing.rst new file mode 100644 index 00000000..e52b1f71 --- /dev/null +++ b/doc/user/licensing.rst @@ -0,0 +1,16 @@ +========= +Licensing +========= + +kikuchipy is licensed under the `GNU General Public License v3+ +`__. +Select parts of the public API are individually licensed under the `BSD 3-Clause License +`__, allowing reuse *of those parts only* +in both GPL and non-GPL projects. +The parts of the API licensed under BSD are marked as such in the +:doc:`/reference/index`. + +Our licenses can be found here: + +- `GPLv3+ `__ +- `BSD 3-Clause `__ diff --git a/doc/user/related_projects.rst b/doc/user/related_projects.rst index 2a807892..1b6ffae5 100644 --- a/doc/user/related_projects.rst +++ b/doc/user/related_projects.rst @@ -2,8 +2,8 @@ Related projects ================ -This is a non-exhaustive list of related, open-source projects for analysis of -EBSD data that users of kikuchipy might find useful: +This is a non-exhaustive list of related, open-source projects for analysis of EBSD data +that users of kikuchipy might find useful: - :doc:`HyperSpy `: Python library with tools for multi-dimensional data analysis. kikuchipy extends this library for EBSD analysis. @@ -21,6 +21,9 @@ EBSD data that users of kikuchipy might find useful: - :doc:`PyEBSDIndex `: Python library for Hough/Radon based EBSD indexing. kikuchipy depends on this library for Hough indexing. +- `ebsdsim `__: Python library for fast + dynamical EBSD master pattern simulation. + kikuchipy can read master patterns generated with this software. - `OpenECCI `__: GUI-based software for controlled Electron Channelling Contrast Imaging (ECCI) analysis of crystal defects in an SEM. - `MTEX `__: MATLAB toolbox for analyzing and modeling @@ -38,3 +41,8 @@ EBSD data that users of kikuchipy might find useful: digital image correlation data. - `pycotem `__: Python package for working with crystal orientations in transmission electron microscopy. +- `ReciPro `__: Standalone multipurpose + crystallographic GUI software for Windows. + Has numerous features, including EBSD simulation. +- `EMSphinxEBSD `__: GUI + for indexing EBSD patterns using spherical harmonics, part of the EMSphinx software. diff --git a/pyproject.toml b/pyproject.toml index 1807665d..109f3774 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,9 @@ name = "kikuchipy" authors = [{name = "kikuchipy developers"}] maintainers = [{name = "Håkon Wiik Ånes", email = "hwaanes@gmail.com"}] description = "Processing, simulating, and indexing of electron backscatter diffraction (EBSD) patterns" +# TODO: Use once this syntax is picked up by 'pip show kikuchipy' after install +# license-expression = "GPL-3.0-or-later AND BSD-3-Clause" +# license-files = ["LICENSE", "LICENSE-BSD-3-Clause"] license = {file = "LICENSE"} readme = {file = "README.rst", content-type = "text/x-rst"} dynamic = ["version"] @@ -23,6 +26,7 @@ classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Topic :: Scientific/Engineering", @@ -65,24 +69,27 @@ dependencies = [ [project.optional-dependencies] all = [ + "ebsdsim >= 0.1.3", "ipywidgets", "IPython", "nlopt", - "pooch >= 1.3.0", + "pooch >= 1.3.0", "psygnal", # Restriction due to https://github.com/USNavalResearchLaboratory/PyEBSDIndex/issues/80 "pyebsdindex >= 0.3.9.2, != 0.3.10", "pyvista", ] doc = [ + "ebsdsim >= 0.1.3", "ipywidgets", "IPython", "ipympl", "memory_profiler", "nbsphinx >= 0.7", # Restriction due to https://github.com/pyxem/kikuchipy/issues/739 - "numpydoc != 1.9.0", + "numpydoc != 1.9.0", "nlopt", + "pooch >= 1.3.0", "pydata-sphinx-theme", "pyebsdindex >= 0.3.9.2, != 0.3.10", "pyvista", @@ -96,22 +103,22 @@ doc = [ ] tests = [ "numpydoc", - "pooch", - "pytest >= 5.4", + "pooch >= 1.3.0", + "pytest >= 5.4", "pytest-benchmark", "pytest-rerunfailures", "pytest-xdist", ] coverage = [ - "coverage >= 5.0", - "pytest-cov >= 2.8.1", + "coverage >= 5.0", + "pytest-cov >= 2.8.1", ] dev = [ - "black[jupyter] >= 23.1", + "black[jupyter] >= 23.1", "hatch", "isort", "outdated", - "pre-commit >= 1.16", + "pre-commit >= 1.16", "ruff", "kikuchipy[doc,tests,coverage]", ] @@ -191,6 +198,7 @@ filterwarnings = [ ] markers = [ "weekly: mark test as running only weekly on CI", + "gpu: mark test as running only when wgpu has a GPU available", ] xfail_strict = true diff --git a/src/kikuchipy/_constants.py b/src/kikuchipy/_constants.py index 8959cbef..0fd71463 100644 --- a/src/kikuchipy/_constants.py +++ b/src/kikuchipy/_constants.py @@ -1,20 +1,7 @@ # -# Copyright 2019-2026 the kikuchipy developers +# Copyright 2026 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 . +# SPDX-License-Identifier: BSD-3-Clause # """Constants and such useful across modules.""" @@ -32,6 +19,7 @@ "rosettasciio", "scikit-image", # Optional + "ebsdsim", "IPython", "ipywidgets", "nlopt", diff --git a/src/kikuchipy/data/_dummy_files/ebsdsim_master_pattern_npz.py b/src/kikuchipy/data/_dummy_files/ebsdsim_master_pattern_npz.py new file mode 100644 index 00000000..686e839a --- /dev/null +++ b/src/kikuchipy/data/_dummy_files/ebsdsim_master_pattern_npz.py @@ -0,0 +1,41 @@ +# +# Copyright 2019-2026 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 a small EBSD master pattern simulation using ebsdsim for +testing and IO documentation. +""" + +from pathlib import Path + +import ebsdsim as es + + +def create_small_ebsdsim_npz_file(path: Path) -> None: + mp = es.master_pattern_from_cif( + "Ni.cif", + halfw=10, + voltage_kv=20.0, + sigma_deg=70.0, + omega_deg=0.0, + energy_binwidth_keV=10.0, + mc_backend="surrogate", + rank=3, + dmin=0.2, + ) + es.save_master_pattern(mp, path) diff --git a/src/kikuchipy/io/_io.py b/src/kikuchipy/io/_io.py index 3aca21f0..d93f4169 100644 --- a/src/kikuchipy/io/_io.py +++ b/src/kikuchipy/io/_io.py @@ -1,5 +1,5 @@ # -# Copyright 2019-2025 the kikuchipy developers +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -19,6 +19,7 @@ import glob import importlib +import logging import os from pathlib import Path from typing import TYPE_CHECKING @@ -27,11 +28,20 @@ from hyperspy.misc.utils import find_subclasses from hyperspy.signal import BaseSignal import numpy as np +import orix.crystal_map as ocm import yaml from kikuchipy.io._util import _ensure_directory, _get_input_bool, _overwrite import kikuchipy.signals +if TYPE_CHECKING: # pragma: no cover + from kikuchipy.signals.ebsd import EBSD, LazyEBSD + from kikuchipy.signals.ebsd_master_pattern import ( + EBSDMasterPattern, + LazyEBSDMasterPattern, + ) + from kikuchipy.signals.ecp_master_pattern import ECPMasterPattern + PLUGINS: list = [] WRITE_EXTENSIONS = [] specification_paths = list(Path(__file__).parent.rglob("specification.yaml")) @@ -45,13 +55,7 @@ WRITE_EXTENSIONS.append(ext) -if TYPE_CHECKING: # pragma: no cover - from kikuchipy.signals.ebsd import EBSD, LazyEBSD - from kikuchipy.signals.ebsd_master_pattern import ( - EBSDMasterPattern, - LazyEBSDMasterPattern, - ) - from kikuchipy.signals.ecp_master_pattern import ECPMasterPattern +_logger = logging.getLogger(__name__) def load( @@ -182,6 +186,15 @@ def _dict2signal( if "Signal" in md and "signal_type" in md["Signal"]: signal_type = md["Signal"]["signal_type"] + if "phase" in signal_dict and isinstance(signal_dict["phase"], dict): + try: + signal_dict["phase"] = ocm.Phase(**signal_dict["phase"]) + except Exception as err: + _logger.debug( + "An error was raised when trying to parse phase information from the " + f"reader: {err}" + ) + signal = _assign_signal_subclass( signal_dimension=2, signal_type=signal_type, diff --git a/src/kikuchipy/io/plugins/__init__.py b/src/kikuchipy/io/plugins/__init__.py index 85e196a9..c9151729 100644 --- a/src/kikuchipy/io/plugins/__init__.py +++ b/src/kikuchipy/io/plugins/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# """Input/output plugins. @@ -27,6 +29,7 @@ bruker_h5ebsd ebsd_directory + ebsdsim_master_pattern edax_binary edax_h5ebsd emsoft_ebsd diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py index 2f857066..f2ce0c16 100644 --- a/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD data from a Bruker Nano h5ebsd file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py index a7ab28f5..9295c52e 100644 --- a/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD data from a Bruker Nano h5ebsd file.""" - from pathlib import Path import h5py diff --git a/src/kikuchipy/io/plugins/ebsd_directory/__init__.py b/src/kikuchipy/io/plugins/ebsd_directory/__init__.py index 2f857066..ddaaa997 100644 --- a/src/kikuchipy/io/plugins/ebsd_directory/__init__.py +++ b/src/kikuchipy/io/plugins/ebsd_directory/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD patterns from a dictionary of images.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/ebsd_directory/_api.py b/src/kikuchipy/io/plugins/ebsd_directory/_api.py index 6b182c48..723c08cf 100644 --- a/src/kikuchipy/io/plugins/ebsd_directory/_api.py +++ b/src/kikuchipy/io/plugins/ebsd_directory/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD patterns from a dictionary of images.""" - import glob import logging import os diff --git a/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.py b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.py new file mode 100644 index 00000000..7b2c8160 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.py @@ -0,0 +1,13 @@ +# +# Copyright 2026 the kikuchipy developers +# +# SPDX-License-Identifier: BSD-3-Clause +# + +"""Reader for ebsdsim master patterns stored in NumPy .npz files.""" + +import lazy_loader + +__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) + +del lazy_loader diff --git a/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.pyi b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.pyi new file mode 100644 index 00000000..e29969f6 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/__init__.pyi @@ -0,0 +1,9 @@ +# +# Copyright 2026 the kikuchipy developers +# +# SPDX-License-Identifier: BSD-3-Clause +# + +from ._api import file_reader + +__all__ = ["file_reader"] diff --git a/src/kikuchipy/io/plugins/ebsdsim_master_pattern/_api.py b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/_api.py new file mode 100644 index 00000000..86bf09d2 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/_api.py @@ -0,0 +1,247 @@ +# +# Copyright 2026 the kikuchipy developers +# +# SPDX-License-Identifier: BSD-3-Clause +# + +from kikuchipy._constants import verify_dependency_or_raise + +verify_dependency_or_raise("ebsdsim", "Reading of an EBSD master pattern from ebsdsim") + +from pathlib import Path +from typing import Any, Literal, TypedDict, get_args + +from diffpy.structure import Atom, Lattice, Structure +import ebsdsim as es +import numpy as np + +HEMISPHERE = Literal["upper", "lower", "both"] + + +class ebsdsimSite(TypedDict): + index: int + atomic_number: int + symbol: str + fract: list[float] + occupancy: float + b_iso_angstrom_sq: float + b_iso_nm_sq: float + multiplicity: int + + +class ebsdsimCell(TypedDict): + a_angstrom: float + b_angstrom: float + c_angstrom: float + alpha_deg: float + beta_deg: float + gamma_deg: float + volume_angstrom3: float + density_g_cm3: float + lattice_centering: Literal["A", "B", "C", "F", "I", "R", "H", "P"] + space_group: int + pg_num: int + average_atomic_number: float + average_atomic_weight: float + n_sites: int + sites: list[ebsdsimSite] + + +def get_orix_phase_dictionary_from_ebsdsim_cell(cell: ebsdsimCell) -> dict[str, Any]: + """Return a dictionary that can be passed directly to the orix phase + constructor, parsed from the ebsdsim cell dictionary. + """ + sg = cell["space_group"] + atoms = [] + for site in cell["sites"]: + atom = Atom( + atype=site["symbol"], + xyz=site["fract"], + occupancy=site["occupancy"], + Uisoequiv=site["b_iso_angstrom_sq"] / (8 * np.pi**2), + ) + atoms.append(atom) + lattice = Lattice( + a=cell["a_angstrom"], + b=cell["b_angstrom"], + c=cell["c_angstrom"], + alpha=cell["alpha_deg"], + beta=cell["beta_deg"], + gamma=cell["gamma_deg"], + ) + structure = Structure(lattice=lattice, atoms=atoms) + + return {"space_group": sg, "structure": structure} + + +def file_reader( + filename: str | Path, hemisphere: HEMISPHERE = "upper", lazy: bool = False +) -> list[dict]: + """Read an ebsdsim EBSD master pattern from a NumPy .npz file. + + Not meant to be used directly; use :func:`~kikuchipy.load`. + + Parameters + ---------- + filename + Path to the .npz file written by + :func:`ebsdsim.save_master_pattern`. + hemisphere + Which hemisphere to return: "upper" (default), "lower", or + "both". + lazy + Not supported; included for API compatibility. Data are always + loaded eagerly. + + Returns + ------- + signal_dict_list + Data, axes, metadata, original metadata, and a phase dict. + + Notes + ----- + This function carries a BSD 3-Clause license. + """ + hemi = hemisphere.lower() + hemi_options = get_args(HEMISPHERE) + if hemi not in hemi_options: + hemi_options_str = ", ".join(hemi_options) + raise ValueError( + f"Hemisphere must be one of {hemi_options_str}, not {hemisphere!r}" + ) + + fpath = Path(filename) + + loaded = es.load_master_pattern(fpath) + + # loaded.data shape: (E, S, H, side, side) + # Take site-integrated (S index 0) and drop that axis. + energy_data = loaded.data[:, 0, :, :, :] # (E, H, side, side) + + axes_meta = loaded.axes + n_bins = loaded.n_bins + n_hemispheres = axes_meta["hemisphere_dim"] + + # Use per-bin energy slices when available; fall back to the + # integrated slice + if n_bins > 0 and axes_meta["energy_dim"] > 1: + bin_indices = axes_meta["bin_to_energy_index"] + energy_data = energy_data[bin_indices, :, :, :] # (n_bins, H, side, side) + voltages = np.asarray(loaded.bin_voltages_kv, dtype=np.float32) + else: + energy_data = energy_data[0:1, :, :, :] # (1, H, side, side) + voltages = np.zeros(1, dtype=np.float32) + + n_energy = energy_data.shape[0] + + # Hemisphere selection + if hemi == "upper": + data = energy_data[:, 0, :, :] # (n_energy, side, side) + elif hemi == "lower": + if n_hemispheres < 2: + raise ValueError( + "File does not contain lower hemisphere data; use hemisphere='upper'" + ) + data = energy_data[:, 1, :, :] # (n_energy, side, side) + else: # "both" + # For centrosymmetric materials, ebsdsim only stores the northern + # hemisphere (hemisphere_dim=1). Duplicate it so "both" always + # yields a size-2 hemisphere axis, consistent with EMsoft behaviour. + nh = energy_data[:, 0:1, :, :] # (n_energy, 1, side, side) + if n_hemispheres == 1: + sh = nh + else: + sh = energy_data[:, 1:2, :, :] + data = np.moveaxis( + np.concatenate([nh, sh], axis=1), 1, 0 + ) # (2, n_energy, side, side) + + # Squeeze size-1 leading navigation axes so single-energy files + # produce a plain 2-D (or 1-D navigation) signal. + if n_energy == 1: + if hemi == "both": + data = data[:, 0, :, :] # (2, side, side) + else: + data = data[0, :, :] # (side, side) + + # Build axes list + side = loaded.side + axes = [] + idx = 0 + + if hemi == "both": + axes.append( + { + "size": 2, + "index_in_array": idx, + "name": "hemisphere", + "scale": 1.0, + "offset": 0.0, + "units": "", + } + ) + idx += 1 + + if n_energy > 1: + energy_scale = float(np.mean(np.diff(voltages))) + energy_offset = float(voltages[0]) - energy_scale / 2 + axes.append( + { + "size": n_energy, + "index_in_array": idx, + "name": "energy", + "scale": energy_scale, + "offset": energy_offset, + "units": "keV", + } + ) + idx += 1 + + axes.append( + { + "size": side, + "index_in_array": idx, + "name": "height", + "scale": 1.0, + "offset": -(side // 2), + "units": "px", + } + ) + axes.append( + { + "size": side, + "index_in_array": idx + 1, + "name": "width", + "scale": 1.0, + "offset": -(side // 2), + "units": "px", + } + ) + + # Metadata + md = { + "Signal": {"signal_type": "EBSDMasterPattern", "record_by": "image"}, + "General": {"title": fpath.stem, "original_filename": fpath.name}, + } + + # Return the raw cell dictionary so that the caller can build an + # orix phase + phase_dict = get_orix_phase_dictionary_from_ebsdsim_cell(loaded.meta["cell"]) + source = loaded.meta.get("source", None) + if source is not None: + name = Path(source).stem + else: + name = fpath.stem + phase_dict["name"] = name + + return [ + { + "data": data, + "axes": axes, + "metadata": md, + "original_metadata": loaded.meta, + "projection": "lambert", + "phase": phase_dict, + "hemisphere": hemisphere, + } + ] diff --git a/src/kikuchipy/io/plugins/ebsdsim_master_pattern/specification.yaml b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/specification.yaml new file mode 100644 index 00000000..276ee9c6 --- /dev/null +++ b/src/kikuchipy/io/plugins/ebsdsim_master_pattern/specification.yaml @@ -0,0 +1,16 @@ +## +## Copyright 2026 the kikuchipy developers +## +## SPDX-License-Identifier: BSD-3-Clause +## + +name: ebsdsim_master_pattern +description: > + Read support for a dynamical electron backscatter diffraction master + pattern simulated produced by the Python package 'ebsdsim' and stored + in a NumPy zipped archive *.npz file. +file_extensions: ['npz'] +default_extension: 0 +writes: False +manufacturer: ebsdsim +footprints: [] diff --git a/src/kikuchipy/io/plugins/edax_binary/__init__.py b/src/kikuchipy/io/plugins/edax_binary/__init__.py index 2f857066..7d54db8f 100644 --- a/src/kikuchipy/io/plugins/edax_binary/__init__.py +++ b/src/kikuchipy/io/plugins/edax_binary/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,12 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD data from EDAX TSL UP1/2 files. + +The reader is adapted from the EDAX UP1/2 reader in PyEBSDIndex. +""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/edax_binary/_api.py b/src/kikuchipy/io/plugins/edax_binary/_api.py index ad9e269c..6c0adf25 100644 --- a/src/kikuchipy/io/plugins/edax_binary/_api.py +++ b/src/kikuchipy/io/plugins/edax_binary/_api.py @@ -17,11 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD data from EDAX TSL UP1/2 files. - -The reader is adapted from the EDAX UP1/2 reader in PyEBSDIndex. -""" - from pathlib import Path from typing import BinaryIO import warnings diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py index 2f857066..eb6a6e75 100644 --- a/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD data from an EDAX TSL h5ebsd file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py index 359fd2d2..724ed072 100644 --- a/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD data from an EDAX TSL h5ebsd file.""" - from pathlib import Path import h5py diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py index 2f857066..70d98c34 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of simulated EBSD patterns from an EMsoft HDF5 file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py b/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py index bcbe68e2..8b27d489 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of simulated EBSD patterns from an EMsoft HDF5 file.""" - import os from pathlib import Path diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py index 2f857066..0d7afd53 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of simulated EBSD master patterns from an EMsoft HDF5 file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py index 3ed98290..64f2e36f 100644 --- a/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py +++ b/src/kikuchipy/io/plugins/emsoft_ebsd_master_pattern/_api.py @@ -1,5 +1,5 @@ # -# Copyright 2019-2025 the kikuchipy developers +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -10,13 +10,12 @@ # # 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 +# 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 .# - -"""Reader of simulated EBSD master patterns from an EMsoft HDF5 file.""" +# along with kikuchipy. If not, see . +# from pathlib import Path diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py index 2f857066..a8b39b04 100644 --- a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of simulated ECP master patterns from an EMsoft HDF5 file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py index bb1b9efd..a38c717d 100644 --- a/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py +++ b/src/kikuchipy/io/plugins/emsoft_ecp_master_pattern/_api.py @@ -1,5 +1,5 @@ # -# Copyright 2019-2025 the kikuchipy developers +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -10,13 +10,12 @@ # # 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 +# 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 .# - -"""Reader of simulated ECP master patterns from an EMsoft HDF5 file.""" +# along with kikuchipy. If not, see . +# from pathlib import Path diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py index 2f857066..b04c5362 100644 --- a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of simulated TKD master patterns from an EMsoft HDF5 file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py index 75624965..acf8571f 100644 --- a/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py +++ b/src/kikuchipy/io/plugins/emsoft_tkd_master_pattern/_api.py @@ -1,5 +1,5 @@ # -# Copyright 2019-2025 the kikuchipy developers +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -10,13 +10,12 @@ # # 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 +# 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 .# - -"""Reader of simulated TKD master patterns from an EMsoft HDF5 file.""" +# along with kikuchipy. If not, see . +# from pathlib import Path diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py index 2f857066..9048de84 100644 --- a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader and writer of EBSD data from a kikuchipy h5ebsd file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py index c8042d26..da4c6d99 100644 --- a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader and writer of EBSD data from a kikuchipy h5ebsd file.""" - import os from pathlib import Path from typing import TYPE_CHECKING diff --git a/src/kikuchipy/io/plugins/nordif/__init__.py b/src/kikuchipy/io/plugins/nordif/__init__.py index 2f857066..82458559 100644 --- a/src/kikuchipy/io/plugins/nordif/__init__.py +++ b/src/kikuchipy/io/plugins/nordif/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader and writer of EBSD patterns from a NORDIF binary file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/nordif/_api.py b/src/kikuchipy/io/plugins/nordif/_api.py index f812e7bb..f2790800 100644 --- a/src/kikuchipy/io/plugins/nordif/_api.py +++ b/src/kikuchipy/io/plugins/nordif/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader and writer of EBSD patterns from a NORDIF binary file.""" - from io import TextIOWrapper import logging import os diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py index 2f857066..c4b84430 100644 --- a/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD calibration patterns from NORDIF files.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py b/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py index bf399870..a5d1d614 100644 --- a/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py +++ b/src/kikuchipy/io/plugins/nordif_calibration_patterns/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD calibration patterns from NORDIF files.""" - import os from pathlib import Path import warnings diff --git a/src/kikuchipy/io/plugins/oxford_binary/__init__.py b/src/kikuchipy/io/plugins/oxford_binary/__init__.py index 2f857066..0c720a5f 100644 --- a/src/kikuchipy/io/plugins/oxford_binary/__init__.py +++ b/src/kikuchipy/io/plugins/oxford_binary/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,14 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of uncompressed EBSD patterns from a Oxford Instruments binary +.ebsp file. + +Information about the file format was generously provided by Oxford +Instruments. +""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/oxford_binary/_api.py b/src/kikuchipy/io/plugins/oxford_binary/_api.py index 66ed8138..f750b00f 100644 --- a/src/kikuchipy/io/plugins/oxford_binary/_api.py +++ b/src/kikuchipy/io/plugins/oxford_binary/_api.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,13 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . - -"""Reader of uncompressed EBSD patterns from a Oxford Instruments binary -.ebsp file. - -Information about the file format was generously provided by Oxford -Instruments. -""" +# import logging import os diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py index 2f857066..d4148e43 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2019-2024 The kikuchipy developers +# +# Copyright 2019-2026 the kikuchipy developers # # This file is part of kikuchipy. # @@ -14,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +# + +"""Reader of EBSD data from an Oxford Instruments h5ebsd (H5OINA) file.""" import lazy_loader diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py index 5daa1790..4943e274 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py @@ -17,8 +17,6 @@ # along with kikuchipy. If not, see . # -"""Reader of EBSD data from an Oxford Instruments h5ebsd (H5OINA) file.""" - import logging from pathlib import Path import re diff --git a/tests/test_io/test_ebsdsim_master_pattern.py b/tests/test_io/test_ebsdsim_master_pattern.py new file mode 100644 index 00000000..9b657442 --- /dev/null +++ b/tests/test_io/test_ebsdsim_master_pattern.py @@ -0,0 +1,111 @@ +# +# Copyright 2019-2026 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 . +# + +"""Tests for the ebsdsim master pattern reader.""" + +import pytest + +_ = pytest.importorskip("ebsdsim", reason="ebsdsim is not installed") +import numpy as np + +import kikuchipy as kp +from kikuchipy.io.plugins.ebsdsim_master_pattern import file_reader + + +@pytest.mark.gpu +class TestEbsdsimMasterPatternReader: + # The Ni file has halfw=10 (side=21), 2 energy bins ([15, 5] keV), + # and hemisphere_dim=1 (centrosymmetric → only northern hemisphere stored). + + def test_default_load(self, ebsdsim_master_pattern_file): + mp = kp.load(ebsdsim_master_pattern_file) + + assert isinstance(mp, kp.signals.EBSDMasterPattern) + assert mp.data.shape == (2, 21, 21) + assert mp.projection == "lambert" + assert mp.hemisphere == "upper" + + def test_phase(self, ebsdsim_master_pattern_file): + mp: kp.signals.EBSDMasterPattern = kp.load(ebsdsim_master_pattern_file) + + assert mp.phase.name == "Ni" + assert mp.phase.space_group.number == 225 # Fm-3m + assert str(mp.phase.point_group.name) == "m-3m" + + def test_metadata(self, ebsdsim_master_pattern_file): + mp = kp.load(ebsdsim_master_pattern_file) + + assert mp.metadata["Signal"]["signal_type"] == "EBSDMasterPattern" + assert mp.metadata["General"]["title"] == "ni_master_pattern" + + def test_energy_axis(self, ebsdsim_master_pattern_file): + mp = kp.load(ebsdsim_master_pattern_file) + + energy_axis = mp.axes_manager["energy"] + assert energy_axis.size == 2 + assert energy_axis.units == "keV" + # Bins are at 20 and 10 keV; offset is the first bin, scale is + # the step + np.testing.assert_allclose(energy_axis.axis, [20.0, 10.0], atol=1.0) + + def test_signal_axes(self, ebsdsim_master_pattern_file): + mp = kp.load(ebsdsim_master_pattern_file) + + assert mp.axes_manager["height"].size == 21 + assert mp.axes_manager["width"].size == 21 + assert mp.axes_manager["height"].units == "px" + assert mp.axes_manager["width"].units == "px" + + def test_hemisphere_both(self, ebsdsim_master_pattern_file): + mp = kp.load(ebsdsim_master_pattern_file, hemisphere="both") + + # Centrosymmetric: northern hemisphere is duplicated to form the south. + assert mp.data.shape == (2, 2, 21, 21) + assert mp.hemisphere == "both" + np.testing.assert_array_equal(mp.data[0], mp.data[1]) + + def test_hemisphere_lower_raises_for_centrosymmetric( + self, ebsdsim_master_pattern_file + ): + with pytest.raises(ValueError, match="lower hemisphere"): + kp.load(ebsdsim_master_pattern_file, hemisphere="lower") + + def test_invalid_hemisphere_raises(self, ebsdsim_master_pattern_file): + with pytest.raises(ValueError, match="Hemisphere must be one of"): + kp.load(ebsdsim_master_pattern_file, hemisphere="north") + + def test_file_reader_returns_list(self, ebsdsim_master_pattern_file): + result = file_reader(ebsdsim_master_pattern_file) + + assert isinstance(result, list) + assert len(result) == 1 + d = result[0] + assert set(d).issuperset( + { + "data", + "axes", + "metadata", + "original_metadata", + "phase", + "projection", + "hemisphere", + } + ) + assert d["projection"] == "lambert" + assert isinstance(d["phase"], dict) diff --git a/tests/test_signals/test_ebsd_master_pattern.py b/tests/test_signals/test_ebsd_master_pattern.py index a5bbe3fb..ba74ae11 100644 --- a/tests/test_signals/test_ebsd_master_pattern.py +++ b/tests/test_signals/test_ebsd_master_pattern.py @@ -780,26 +780,27 @@ class TestFitPatternDetectorOrientation: detector orientations. """ - detector = kp.detectors.EBSDDetector( - shape=(480, 640), px_size=50, pc=(20, 20, 15000), convention="emsoft4" - ) + def setup_method(self): + self.detector = kp.detectors.EBSDDetector( + shape=(480, 640), px_size=50, pc=(20, 20, 15000), convention="emsoft4" + ) - phase = kp.data.nickel_ebsd_master_pattern_small().phase + self.phase = kp.data.nickel_ebsd_master_pattern_small().phase - hkl = [(1, 1, 1), (2, 0, 0), (2, 2, 0), (3, 1, 1)] + self.hkl = [(1, 1, 1), (2, 0, 0), (2, 2, 0), (3, 1, 1)] - ref = ReciprocalLatticeVector(phase=phase, hkl=hkl) - ref = ref.symmetrise() + ref = ReciprocalLatticeVector(phase=self.phase, hkl=self.hkl) + self.ref = ref.symmetrise() - simulator = kp.simulations.KikuchiPatternSimulator(ref) + self.simulator = kp.simulations.KikuchiPatternSimulator(self.ref) - rotations = Rotation.from_euler([23, 14, 5], degrees=True) + self.rotations = Rotation.from_euler([23, 14, 5], degrees=True) - # Transformation from CSs to cartesian crystal reference frame CSc - u_o = rotations.to_matrix().squeeze() + # Transformation from CSs to cartesian crystal reference frame CSc + self.u_o = self.rotations.to_matrix().squeeze() - # Transformation from CSc to direct crystal reference frame CSk - u_a = phase.structure.lattice.base + # Transformation from CSc to direct crystal reference frame CSk + self.u_a = self.phase.structure.lattice.base def setup_detector_sim_and_u_os(self, tilt, azimuthal, twist): det = self.detector.deepcopy()