From 5c0a4e715cb5783727ff4193fccff60d7fd17446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sun, 17 Nov 2024 10:56:27 +0100 Subject: [PATCH 01/24] Resolve path to datasets for testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conftest.py b/conftest.py index 38b5fc36..b84d44e3 100644 --- a/conftest.py +++ b/conftest.py @@ -51,6 +51,7 @@ DATA_PATH = Path(__file__).parent / "src/kikuchipy/data" +DATA_PATH = DATA_PATH.resolve() # ------------------------------ Setup ------------------------------ # From 0fb2098c42de1b7c93425d56c785cb8ba074bb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 22:05:11 +0100 Subject: [PATCH 02/24] Update pattern header in Oxford binary reader for *.ebsp > v4 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/_api.py | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/src/kikuchipy/io/plugins/oxford_binary/_api.py b/src/kikuchipy/io/plugins/oxford_binary/_api.py index c1773f74..d4aa47d1 100644 --- a/src/kikuchipy/io/plugins/oxford_binary/_api.py +++ b/src/kikuchipy/io/plugins/oxford_binary/_api.py @@ -88,15 +88,6 @@ class OxfordBinaryFileReader: patterns. """ - # Header for each pattern in the file - pattern_header_size: int = 16 - pattern_header_dtype: list = [ - ("is_compressed", np.int32, (1,)), - ("nrows", np.int32, (1,)), - ("ncols", np.int32, (1,)), - ("n_bytes", np.int32, (1,)), - ] - def __init__(self, file: BinaryIO) -> None: """Prepare to read EBSD patterns from an open Oxford Instruments' binary .ebsp file. @@ -106,12 +97,14 @@ def __init__(self, file: BinaryIO) -> None: self.version = self.get_version() _logger.debug(f"Reading Oxford binary file of version {self.version}") + self.pattern_header_size = self.get_pattern_header_size() + # If version > 3, read in the extra byte after file version and # add it to the debug log if self.version > 3: # pragma: no cover self.file.seek(self.pattern_starts_byte_position - 1) unknown_byte = np.fromfile(self.file, dtype=np.uint8, count=1)[0] - _logger.debug(f"Unknown byte (uint8) in file of version 4: {unknown_byte}") + _logger.debug(f"Unknown byte (uint8) in file of version>3: {unknown_byte}") # Number of patterns in the file is not known, so this is # guessed from the file header where the file byte positions of @@ -121,16 +114,14 @@ def __init__(self, file: BinaryIO) -> None: # Determine whether we can read the file, signal shape, and data # type - is_compressed, nrows, ncols, n_bytes = self.get_single_pattern_header( - self.first_pattern_position - ) - if is_compressed: + header = self.get_single_pattern_header(self.first_pattern_position) + if header["is_compressed"][0]: raise NotImplementedError( - f"Cannot read compressed EBSD patterns from {self.file.name}" + f"Cannot read compressed EBSD patterns from {self.file.name!r}" ) - self.signal_shape = (nrows, ncols) - self.n_bytes = n_bytes - if n_bytes == np.prod(self.signal_shape): + self.signal_shape = (header["nrows"][0], header["ncols"][0]) + self.n_bytes = header["n_bytes"][0] + if self.n_bytes == np.prod(self.signal_shape): self.dtype = np.uint8 else: self.dtype = np.uint16 @@ -138,9 +129,9 @@ def __init__(self, file: BinaryIO) -> None: # While the pattern header is always in the same format across # .ebsp file versions, this is not the case for the pattern # footer. Here we determine it's format. - self.pattern_footer_dtype = self.get_pattern_footer_dtype( - self.first_pattern_position - ) + pos = self.first_pattern_position + _logger.debug(f"First pattern byte position: {pos}") + self.pattern_footer_dtype = self.get_pattern_footer_dtype(pos) # Allow for reading of files where only non-indexed patterns are # stored in the file @@ -158,6 +149,8 @@ def __init__(self, file: BinaryIO) -> None: self.memmap = self.get_memmap() + # -------------------------- Properties -------------------------- # + @property def all_patterns_present(self) -> bool: """Whether all or only non-indexed patterns are stored in the @@ -213,6 +206,30 @@ def pattern_starts_byte_position(self) -> int: else: return 8 + @property + def pattern_header_dtype(self) -> list[tuple[str, type, tuple[int]]]: + dtypes = [ + ("map_x", np.int32, (1,)), + ("map_y", np.int32, (1,)), + ("is_compressed", np.int32, (1,)), + ("nrows", np.int32, (1,)), + ("ncols", np.int32, (1,)), + ("n_bytes", np.int32, (1,)), + ] + if self.version < 5: + # Remove map_x and map_y + _ = dtypes.pop(0) + _ = dtypes.pop(0) + return dtypes + + # --------------------------- Methods ---------------------------- # + + def get_pattern_header_size(self): + size = 0 + for _, dtype, _ in self.pattern_header_dtype: + size += np.dtype(dtype).itemsize + return size + def get_memmap(self) -> np.memmap: """Return a memory map of the pattern header, actual patterns, and a potential pattern footer. @@ -221,11 +238,13 @@ def get_memmap(self) -> np.memmap: patterns have the correct signal shape (n rows, n columns). If the pattern footer is available, the memory map has these - fields: + fields (some are new in version 5 of the *.ebsp file format): - ============= =============== =================== - Name Data type Shape - ============= =============== =================== + ============= =============== =================== ================ + Name Data type Shape New in version 5 + ============= =============== =================== ================ + map_x int32 (1,) x + map_y int32 (1,) x is_compressed int32 (1,) nrows int32 (1,) ncols int32 (1,) @@ -235,7 +254,7 @@ def get_memmap(self) -> np.memmap: beam_x float64 (1,) has_beam_y bool (1,) beam_y float64 (1,) - ============= =============== =================== + ============= =============== =================== ================ Returns ------- @@ -246,7 +265,6 @@ def get_memmap(self) -> np.memmap: file_dtype = self.pattern_header_dtype + [pattern_dtype] if len(footer_dtype) != 0: file_dtype += footer_dtype - return np.memmap( self.file.name, dtype=file_dtype, @@ -402,7 +420,7 @@ def get_single_pattern_footer(self, offset: int) -> np.ndarray: self.file.seek(offset + self.pattern_header_size + self.n_bytes) return np.fromfile(self.file, dtype=self.pattern_footer_dtype, count=1) - def get_single_pattern_header(self, offset: int) -> tuple[bool, int, int, int]: + def get_single_pattern_header(self, offset: int) -> np.ndarray: """Return a single pattern header. Parameters @@ -412,23 +430,12 @@ def get_single_pattern_header(self, offset: int) -> tuple[bool, int, int, int]: Returns ------- - is_compressed - Whether the pattern is compressed. - nrows - Number of signal (detector) rows. - ncols - Number of signal (detector) columns. - n_bytes - Number of pattern bytes. + header + The format of this depends on the file :attr:`version`. See + :attr:`pattern_header_dtype` for details. """ self.file.seek(offset) - header = np.fromfile(self.file, dtype=self.pattern_header_dtype, count=1)[0] - return ( - bool(header["is_compressed"][0]), - int(header["nrows"][0]), - int(header["ncols"][0]), - int(header["n_bytes"][0]), - ) + return np.fromfile(self.file, dtype=self.pattern_header_dtype, count=1)[0] def get_version(self) -> int: """Return the .ebsp file version. @@ -539,7 +546,7 @@ def guess_number_of_patterns(self, min_assumed_n_pixels: int = 1600) -> int: # It is assumed that a jump in bytes from one pattern position # to the next does not exceed a number of maximum bytes one # pattern can take up in the file. The array index where - # this happens (plus 2) is assumed to be the number of patterns + # this happens (plus 1) is assumed to be the number of patterns # in the file. diff_pattern_starts = np.diff(assumed_pattern_starts) max_assumed_n_pixels = 1024 * 1344 @@ -547,6 +554,12 @@ def guess_number_of_patterns(self, min_assumed_n_pixels: int = 1600) -> int: max_assumed_pattern_size = max_assumed_n_pixels * 2 + self.pattern_header_size # 20x is chosen as a sufficiently high jump in bytes pattern_start = abs(diff_pattern_starts) > 20 * max_assumed_pattern_size - n_patterns = np.nonzero(pattern_start)[0][0] + 1 + n_patterns = np.nonzero(pattern_start)[0][0] + + # Not sure why this is needed only for these versions... + if self.version < 5: + n_patterns += 1 + + _logger.debug(f"Guessed number of patterns: {n_patterns}") return n_patterns From 6b6b9ef615c069abf06a6c55b1e83f6b8e004747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 22:54:30 +0100 Subject: [PATCH 03/24] Add map (x, y) from Oxford binary *.ebsp > v4 to original metadata 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/_api.py | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/kikuchipy/io/plugins/oxford_binary/_api.py b/src/kikuchipy/io/plugins/oxford_binary/_api.py index d4aa47d1..66ed8138 100644 --- a/src/kikuchipy/io/plugins/oxford_binary/_api.py +++ b/src/kikuchipy/io/plugins/oxford_binary/_api.py @@ -230,6 +230,12 @@ def get_pattern_header_size(self): size += np.dtype(dtype).itemsize return size + def get_pattern_footer_size(self): + size = 0 + for _, dtype, _ in self.get_pattern_footer_dtype(self.first_pattern_position): + size += np.dtype(dtype).itemsize + return size + def get_memmap(self) -> np.memmap: """Return a memory map of the pattern header, actual patterns, and a potential pattern footer. @@ -326,7 +332,9 @@ def get_pattern_starts(self) -> np.ndarray: self.file.seek(self.pattern_starts_byte_position) return np.fromfile(self.file, dtype=np.int64, count=self.n_patterns) - def get_pattern_footer_dtype(self, offset: int) -> list[tuple]: + def get_pattern_footer_dtype( + self, offset: int + ) -> list[tuple[str, type, tuple[int]]]: """Return the pattern footer data types to be used when memory mapping. @@ -489,30 +497,34 @@ def get_scan(self, lazy: bool) -> dict: for i in range(data.ndim) ] fname = self.file.name - metadata = dict( - General=dict( - original_filename=fname, - title=os.path.splitext(os.path.split(fname)[1])[0], - ), - Signal=dict(signal_type="EBSD", record_by="image"), - ) + metadata = { + "General": { + "original_filename": fname, + "title": os.path.splitext(os.path.split(fname)[1])[0], + }, + "Signal": {"signal_type": "EBSD", "record_by": "image"}, + } order = self.pattern_order[self.pattern_is_present] - om = dict( - map1d_id=np.arange(self.n_patterns)[self.pattern_is_present], - file_order=order, - ) + om = { + "map1d_id": np.arange(self.n_patterns)[self.pattern_is_present], + "file_order": order, + } if "beam_y" in self.memmap.dtype.names: om["beam_y"] = self.memmap["beam_y"][..., 0][order] if "beam_x" in self.memmap.dtype.names: om["beam_x"] = self.memmap["beam_x"][..., 0][order] - - scan = dict( - axes=axes, - data=data, - metadata=metadata, - original_metadata=om, - ) + if "map_x" in self.memmap.dtype.names: + om["map_x"] = self.memmap["map_x"][..., 0][order] + if "map_y" in self.memmap.dtype.names: + om["map_y"] = self.memmap["map_y"][..., 0][order] + + scan = { + "axes": axes, + "data": data, + "metadata": metadata, + "original_metadata": om, + } return scan @@ -563,3 +575,11 @@ def guess_number_of_patterns(self, min_assumed_n_pixels: int = 1600) -> int: _logger.debug(f"Guessed number of patterns: {n_patterns}") return n_patterns + + def get_estimated_file_size(self) -> int: + n = self.n_patterns + file_header_size = self.pattern_starts_byte_position + n * 8 + pattern_header_size = self.get_pattern_header_size() + pattern_footer_size = self.get_pattern_footer_size() + pattern_section_size = pattern_header_size + self.n_bytes + pattern_footer_size + return file_header_size + n * pattern_section_size From 334e92e0fef0acb7dddc080a6cf026246dc64094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 22:54:56 +0100 Subject: [PATCH 04/24] Update Oxford binary test fixture to allow mocking > v4 pattern header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conftest.py b/conftest.py index b84d44e3..0acb99e6 100644 --- a/conftest.py +++ b/conftest.py @@ -512,6 +512,9 @@ def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: for i in new_order: r, c = np.unravel_index(i, (nr, nc)) + if ver > 4: + extra_pattern_header = np.array([c, r], dtype=np.int32) + extra_pattern_header.tofile(f) pattern_header.tofile(f) data[r, c].tofile(f) if ver > 1: From 9215f56ee8981af86d38fcea841b9a783203e5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 22:55:26 +0100 Subject: [PATCH 05/24] Test reading of > v4 Oxford binary files 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_oxford_binary.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_io/test_oxford_binary.py b/tests/test_io/test_oxford_binary.py index 7e74b88e..67cf2cf1 100644 --- a/tests/test_io/test_oxford_binary.py +++ b/tests/test_io/test_oxford_binary.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with kikuchipy. If not, see . +import os + import dask.array as da import numpy as np import pytest @@ -113,3 +115,30 @@ def test_guess_number_of_patterns(self, oxford_binary_file, n_patterns): with open(oxford_binary_file.name, mode="rb") as f: fox = OxfordBinaryFileReader(f) assert fox.n_patterns == n_patterns + + @pytest.mark.parametrize( + "oxford_binary_file", + [ + ((2, 3), (50, 50), np.uint8, 5, False, True), + ((2, 3), (50, 50), np.uint8, 6, False, True), + ], + indirect=["oxford_binary_file"], + ) + def test_version_5(self, oxford_binary_file): + with open(oxford_binary_file.name, mode="rb") as f: + fox = OxfordBinaryFileReader(f) + assert fox.n_patterns == 6 + + @pytest.mark.parametrize( + "oxford_binary_file, file_size", + [ + (((2, 3), (50, 50), np.uint8, 5, False, True), 15309), + (((2, 3), (50, 50), np.uint8, 4, False, True), 15261), + ], + indirect=["oxford_binary_file"], + ) + def test_estimated_file_size(self, oxford_binary_file, file_size): + with open(oxford_binary_file.name, mode="rb") as f: + fox = OxfordBinaryFileReader(f) + assert fox.get_estimated_file_size() == file_size + assert os.path.getsize(oxford_binary_file.name) == file_size From f50b6af19d2c3020c67239c292e74516f70c9da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 23:27:28 +0100 Subject: [PATCH 06/24] Update IO tutorial with new original_metadata arrays for Oxford binary > v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- doc/tutorials/load_save_data.ipynb | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/doc/tutorials/load_save_data.ipynb b/doc/tutorials/load_save_data.ipynb index 89eda476..efcaa561 100644 --- a/doc/tutorials/load_save_data.ipynb +++ b/doc/tutorials/load_save_data.ipynb @@ -943,7 +943,13 @@ "Here, the Oxford Instruments binary [file_reader()](../reference/generated/kikuchipy.io.plugins.oxford_binary.file_reader.rst) is called.\n", "\n", "Every pattern's flattened index into the 2D navigation map, as well as their entry in the file (map order isn't always the same as file order) can be retrieved from `s_oxford.original_metadata.map1d_id` and `s_oxford.original_metadata.file_order`, respectively.\n", - "If available in the file, every pattern's row and column beam position in microns can be retrieved from `s_oxford.original_metadata.beam_y` and `s_oxford.original_metadata.beam_x`, respectively.\n", + "The following data may be read as well, depending on their presence in the file:\n", + "\n", + "* `s_oxford.original_metadata.beam_x`: Every pattern's column in microns\n", + "* `s_oxford.original_metadata.beam_y`: Every pattern's row in microns\n", + "* `s_oxford.original_metadata.map_x`: Every pattern's column\n", + "* `s_oxford.original_metadata.map_y`: Every pattern's row\n", + "\n", "All these are 1D arrays." ] }, @@ -1100,10 +1106,11 @@ "## Load and save virtual BSE images\n", "\n", "One or more virtual backscatter electron (BSE) images in a [VirtualBSEImage](../reference/generated/kikuchipy.signals.VirtualBSEImage.rst) signal can be read and written to file using one of HyperSpy's many readers and writers.\n", - "If they are only to be used internally in HyperSpy, they can be written to and read back from HyperSpy's HDF5/zarr specification [as explained above for EBSD master patterns](#Save-patterns).\n", "\n", + "If they are only to be used internally in HyperSpy, they can be written to and read back from HyperSpy's HDF5/zarr specification [as explained above for EBSD master patterns](#Save-patterns).\n", "If we want to write the images to image files, HyperSpy also provides a series of image readers/writers, as explained in their [IO user guide](https://hyperspy.org/hyperspy-doc/v1.7/user_guide/io.html#images).\n", - "If we wanted to write them as a stack of TIFF images" + "\n", + "Writing as a stack of TIFF images" ] }, { @@ -1146,7 +1153,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also write them to e.g. `png` or `bmp` files with `Matplotlib`" + "Read the TIFF stack back into a `VirtualBSEImage` signal" ] }, { @@ -1155,16 +1162,15 @@ "metadata": {}, "outputs": [], "source": [ - "nav_size = vbse.axes_manager.navigation_size\n", - "for i in range(nav_size):\n", - " plt.imsave(temp_dir / f\"vbse{i}.png\", vbse.inav[i].data)" + "vbse2 = hs.load(temp_dir / vbse_fname, signal_type=\"VirtualBSEImage\")\n", + "vbse2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Read the TIFF stack back into a `VirtualBSEImage` signal" + "We can also write them to e.g. `png` or `bmp` files with `Matplotlib`" ] }, { @@ -1173,8 +1179,9 @@ "metadata": {}, "outputs": [], "source": [ - "vbse2 = hs.load(temp_dir / vbse_fname, signal_type=\"VirtualBSEImage\")\n", - "vbse2" + "nav_size = vbse.axes_manager.navigation_size\n", + "for i in range(nav_size):\n", + " plt.imsave(temp_dir / f\"vbse{i}.png\", vbse.inav[i].data)" ] }, { @@ -1211,7 +1218,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.6" + "version": "3.12.7" }, "widgets": { "application/vnd.jupyter.widget-state+json": { From 2280a85bd628b91a99961f445a1bfc1782bb1e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 23:30:17 +0100 Subject: [PATCH 07/24] Mention Oxford binary v6 fix 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 | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 73e76d42..269ebc1d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,23 +13,13 @@ its best to adhere to `Semantic Versioning List entries are sorted in descending chronological order. Contributors to each release were listed in alphabetical order by first name until version 0.7.0. -Unreleased -========== - -Added ------ - -Changed -------- - -Removed -------- - -Deprecated ----------- +0.11.1 (2024-11-24) +=================== Fixed ----- +- Reading of Oxford binary `*.ebsp` files with version 6. + (`#700 `_) 0.11.0 (2024-11-10) =================== From 76dc0f0701087e51599c894eae16547e17249137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 19 Nov 2024 23:31:56 +0100 Subject: [PATCH 08/24] Set version to 0.11.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kikuchipy/__init__.py b/src/kikuchipy/__init__.py index ef1444fa..21a9985b 100644 --- a/src/kikuchipy/__init__.py +++ b/src/kikuchipy/__init__.py @@ -30,7 +30,7 @@ "Carter Francis", "Magnus Nord", ] -__version__ = "0.12.dev0" +__version__ = "0.11.1" __getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) From 96fb6214900061f10147583333e8f0aececfdee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 00:03:25 +0100 Subject: [PATCH 09/24] Add user agent to Pooch HTTP downloader to fix RTD-GH download error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/data/_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kikuchipy/data/_data.py b/src/kikuchipy/data/_data.py index f2aef16c..b504b3cf 100644 --- a/src/kikuchipy/data/_data.py +++ b/src/kikuchipy/data/_data.py @@ -629,7 +629,9 @@ def fetch_file_path( ) -> str: if show_progressbar is None: show_progressbar = hs.preferences.General.show_progressbar - downloader = pooch.HTTPDownloader(progressbar=show_progressbar) + downloader = pooch.HTTPDownloader( + progressbar=show_progressbar, headers={"User-Agent": "agent"} + ) if self.is_in_package: if self.has_correct_hash: From fb4218be286139aeea260b5124d164c5f068144b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 01:25:36 +0100 Subject: [PATCH 10/24] Make dataset name a req. prop. of concrete h5ebsd reader classes 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 | 30 +++++++++++-------- .../io/plugins/bruker_h5ebsd/_api.py | 4 +++ src/kikuchipy/io/plugins/edax_h5ebsd/_api.py | 4 +++ .../io/plugins/kikuchipy_h5ebsd/_api.py | 4 +++ .../io/plugins/oxford_h5ebsd/_api.py | 5 ++++ 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/kikuchipy/io/plugins/_h5ebsd.py b/src/kikuchipy/io/plugins/_h5ebsd.py index d583e258..6a617d0c 100644 --- a/src/kikuchipy/io/plugins/_h5ebsd.py +++ b/src/kikuchipy/io/plugins/_h5ebsd.py @@ -137,12 +137,7 @@ class H5EBSDReader(abc.ABC): Keyword arguments passed to :class:`h5py.File`. """ - manufacturer_patterns: dict[str, str] = { - "bruker nano": "RawPatterns", - "edax": "Pattern", - "kikuchipy": "patterns", - "oxford instruments": "Processed Patterns", - } + supported_manufacturers = ["bruker nano", "edax", "kikuchipy", "oxford instruments"] def __init__(self, filename: str | Path, **kwargs) -> None: self.filename = str(filename) @@ -150,10 +145,8 @@ def __init__(self, filename: str | Path, **kwargs) -> None: self.scan_groups = self.get_scan_groups() self.manufacturer, self.version = self.get_manufacturer_version() self.check_file() - self.patterns_name = self.manufacturer_patterns[self.manufacturer] - def __repr__(self) -> str: - return f"{self.__class__.__name__} ({self.version}): {self.filename}" + # -------------------------- Properties -------------------------- # @property def scan_group_names(self) -> list[str]: @@ -165,6 +158,18 @@ def scan_group_names(self) -> list[str]: names.append(name) return names + @property + @abc.abstractmethod + def patterns_name(self) -> str: + return NotImplemented # pragma: no cover + + # ------------------------ Dunder methods ------------------------ # + + def __repr__(self) -> str: + return f"{self.__class__.__name__} ({self.version}): {self.filename}" + + # ---------------------------- Methods --------------------------- # + def check_file(self) -> None: """Check if the file is a valid h5ebsd file by searching for datasets containing manufacturer, version and scans in the top @@ -192,11 +197,10 @@ def check_file(self) -> None: ) man, _ = self.get_manufacturer_version() man = man.lower() - supported_manufacturers = list(self.manufacturer_patterns.keys()) - if man not in supported_manufacturers and error is None: + if man not in self.supported_manufacturers and error is None: error = ( - f"'{man}' is not among supported manufacturers " - f"{supported_manufacturers}" + f"{man!r} is not among supported manufacturers " + f"{self.supported_manufacturers}" ) if error is not None: raise IOError(f"{self.filename} is not a supported h5ebsd file, as {error}") diff --git a/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py index 8eaf220a..d1502967 100644 --- a/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/bruker_h5ebsd/_api.py @@ -46,6 +46,10 @@ class BrukerH5EBSDReader(H5EBSDReader): def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) + @property + def patterns_name(self) -> str: + return "RawPatterns" + def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: """Read (possibly lazily) patterns from group. diff --git a/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py index 50aa4bac..e0bf261b 100644 --- a/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/edax_h5ebsd/_api.py @@ -43,6 +43,10 @@ class EDAXH5EBSDReader(H5EBSDReader): def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) + @property + def patterns_name(self) -> str: + return "Pattern" + def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: """Read (possibly lazily) patterns from group. diff --git a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py index 6512be8e..181fe58f 100644 --- a/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/kikuchipy_h5ebsd/_api.py @@ -56,6 +56,10 @@ class KikuchipyH5EBSDReader(H5EBSDReader): def __init__(self, filename: str, **kwargs) -> None: super().__init__(filename, **kwargs) + @property + def patterns_name(self) -> str: + return "patterns" + def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: """Read (possibly lazily) patterns from group. diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py index dce3463b..74e0a24f 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py @@ -47,6 +47,11 @@ class OxfordH5EBSDReader(H5EBSDReader): def __init__(self, filename: str | Path, **kwargs) -> None: super().__init__(filename, **kwargs) + self._patterns_name = "Processed Patterns" + + @property + def patterns_name(self) -> str: + return self._patterns_name def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: """Read (possibly lazily) patterns from group. From a57e693a9e68bd18553afb7a6c85aab028104f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 01:36:50 +0100 Subject: [PATCH 11/24] Read either processed or unprocessed patterns from H5OINA file 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/_api.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py index 74e0a24f..6712199f 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py @@ -45,9 +45,11 @@ class OxfordH5EBSDReader(H5EBSDReader): Keyword arguments passed to :class:`h5py.File`. """ - def __init__(self, filename: str | Path, **kwargs) -> None: + pattern_dataset_names = ["Unprocessed Patterns", "Processed Patterns"] + + def __init__(self, filename: str | Path, processed: bool, **kwargs) -> None: super().__init__(filename, **kwargs) - self._patterns_name = "Processed Patterns" + self._patterns_name = self.pattern_dataset_names[int(processed)] @property def patterns_name(self) -> str: @@ -78,7 +80,7 @@ def scan2dict(self, group: h5py.Group, lazy: bool = False) -> dict: """ header_group = _hdf5group2dict(group["EBSD/Header"], recursive=True) data_group = _hdf5group2dict( - group["EBSD/Data"], data_dset_names=[self.patterns_name] + group["EBSD/Data"], data_dset_names=self.pattern_dataset_names ) # Get data shapes @@ -160,6 +162,7 @@ def file_reader( filename: str | Path, scan_group_names: str | list[str] | None = None, lazy: bool = False, + processed: bool = True, **kwargs, ) -> list[dict]: """Read electron backscatter diffraction patterns, a crystal map, @@ -180,6 +183,9 @@ def file_reader( Open the data lazily without actually reading the data from disk until required. Allows opening arbitrary sized datasets. Default is False. + processed + Whether to read processed patterns. Default is True. If False, + try to read unprocessed patterns if available. **kwargs Keyword arguments passed to :class:`h5py.File`. @@ -190,6 +196,12 @@ def file_reader( "metadata", "original_metadata", "detector", and "xmap". This dictionary can be passed as keyword arguments to create an :class:`~kikuchipy.signals.EBSD` signal. + + Raises + ------ + ValueError + If *processed* is False and unprocessed patterns are not + available. """ - reader = OxfordH5EBSDReader(filename, **kwargs) + reader = OxfordH5EBSDReader(filename, processed, **kwargs) return reader.read(scan_group_names, lazy) From 95e9371cea588dbc192b350fe001880767f3dde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 01:59:37 +0100 Subject: [PATCH 12/24] Use dummy H5OINA file from test fixture instead of packaged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 119 ++++++++++++++++++++- src/kikuchipy/signals/_kikuchipy_signal.py | 4 +- tests/test_io/test_oxford_h5ebsd.py | 9 +- 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/conftest.py b/conftest.py index 0acb99e6..955b8d6c 100644 --- a/conftest.py +++ b/conftest.py @@ -443,11 +443,6 @@ def oxford_binary_path() -> Generator[Path, None, None]: yield DATA_PATH / "oxford_binary" -@pytest.fixture -def oxford_h5ebsd_path() -> Generator[Path, None, None]: - yield DATA_PATH / "oxford_h5ebsd" - - @pytest.fixture(params=[((2, 3), (60, 60), np.uint8, 2, False, True)]) def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: """Create a dummy Oxford Instruments' binary .ebsp file. @@ -531,6 +526,120 @@ def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: yield f +@pytest.fixture() +def oxford_h5ebsd_file(tmpdir) -> Generator[TextIOWrapper, None, None]: + """Oxford Instruments h5ebsd file (H5OINA). + + Both processed and unprocessed patterns (processed + 1) are written + to file. + """ + # Quaternions determined from indexing + # fmt: off + qu_grain1 = (0.9542, -0.0183, -0.2806, 0.1018) + qu_grain2 = (0.9542, 0.0608, -0.2295, -0.1818) + rot = Rotation( + [ + qu_grain1, qu_grain2, qu_grain2, + qu_grain1, qu_grain2, qu_grain2, + qu_grain1, qu_grain2, qu_grain2, + ] + ) + # fmt: on + euler = rot.to_euler() + + s = kp.data.nickel_ebsd_small() + ny, nx = s._navigation_shape_rc + n = ny * nx + sy, sx = s._signal_shape_rc + dx = s.axes_manager["x"].scale + + fpath = tmpdir / "patterns.h5oina" + f = h5py.File(fpath, mode="w") + + # Top group + f.create_dataset("Format Version", data=b"5.0") + f.create_dataset("Index", data=b"1") + f.create_dataset("Manufacturer", data=b"Oxford Instruments") + f.create_dataset("Software Version", data=b"6.0.8014.1") + scan = f.create_group("1") + + # EBSD + ebsd = scan.create_group("EBSD") + ones = np.ones(n) + + # Data + data = ebsd.create_group("Data") + data.create_dataset("Band Contrast", dtype="uint8", data=ones) + data.create_dataset("Band Slope", dtype="uint8", data=ones) + data.create_dataset("Bands", dtype="uint8", data=ones) + data.create_dataset("Beam Position X", dtype="float32", data=ones) + data["Beam Position X"].attrs["Unit"] = "um" + data.create_dataset("Beam Position Y", dtype="float32", data=ones) + data["Beam Position Y"].attrs["Unit"] = "um" + data.create_dataset("Detector Distance", dtype="float32", data=ones) + data.create_dataset("Error", dtype="uint8", data=ones) + data["Error"].attrs["HighMAD"] = [np.int32(5)] + data["Error"].attrs["LowBandContrast"] = [np.int32(3)] + data["Error"].attrs["LowBandSlope"] = [np.int32(4)] + data["Error"].attrs["NoSolution"] = [np.int32(2)] + data["Error"].attrs["NotAnalyzed"] = [np.int32(0)] + data["Error"].attrs["Replaced"] = [np.int32(7)] + data["Error"].attrs["Success"] = [np.int32(1)] + data["Error"].attrs["UnexpectedError"] = [np.int32(6)] + data.create_dataset("Euler", dtype="float32", data=euler) + data["Euler"].attrs["Unit"] = "rad" + data.create_dataset("Mean Angular Deviation", dtype="float32", data=ones) + data["Euler"].attrs["Mean Angular Deviation"] = "rad" + data.create_dataset("Pattern Center X", dtype="float32", data=ones) + data.create_dataset("Pattern Center Y", dtype="float32", data=ones) + data.create_dataset("Pattern Quality", dtype="float32", data=ones) + data.create_dataset("Phase", dtype="uint8", data=ones) + s.remove_static_background() + data.create_dataset( + "Processed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) + ) + data.create_dataset( + "Unprocessed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) + 1 + ) + x = np.array([0, 1, 2] * 3) * dx + data.create_dataset("X", dtype="float32", data=x) + data["X"].attrs["Unit"] = "um" + data.create_dataset("Y", dtype="float32", data=np.sort(x)) + data["Y"].attrs["Unit"] = "um" + + # Header + header = ebsd.create_group("Header") + header.create_dataset("Beam Voltage", dtype="float32", data=20) + header["Beam Voltage"].attrs["Unit"] = "kV" + header.create_dataset("Magnification", dtype="float32", data=2000) + header.create_dataset("Working Distance", dtype="float32", data=23.5) + header["Working Distance"].attrs["Unit"] = "mm" + header.create_dataset("X Cells", dtype="int32", data=3) + header["X Cells"].attrs["Unit"] = "px" + header.create_dataset("Y Cells", dtype="int32", data=3) + header["Y Cells"].attrs["Unit"] = "px" + header.create_dataset("X Step", dtype="float32", data=1.5) + header["X Step"].attrs["Unit"] = "um" + header.create_dataset("Y Step", dtype="float32", data=1.5) + header["Y Step"].attrs["Unit"] = "um" + header.create_dataset("Pattern Height", dtype="int32", data=60) + header["Pattern Height"].attrs["Unit"] = "px" + header.create_dataset("Pattern Width", dtype="int32", data=60) + header["Pattern Width"].attrs["Unit"] = "px" + header.create_dataset( + "Processed Static Background", dtype="uint8", data=s.static_background + ) + header.create_dataset("Tilt Angle", dtype="float32", data=np.deg2rad(69.9)) + header.create_dataset( + "Detector Orientation Euler", dtype="float32", data=np.deg2rad([0, 91.5, 0]) + ) + header.create_dataset("Camera Binning Mode", data=b"8x8 (60x60 px)") + + f.close() + + yield fpath + + # -------------------------- EMsoft formats -------------------------- # diff --git a/src/kikuchipy/signals/_kikuchipy_signal.py b/src/kikuchipy/signals/_kikuchipy_signal.py index 2b3e47a1..71080279 100644 --- a/src/kikuchipy/signals/_kikuchipy_signal.py +++ b/src/kikuchipy/signals/_kikuchipy_signal.py @@ -66,12 +66,12 @@ class KikuchipySignal2D(Signal2D): _custom_attributes = [] @property - def _signal_shape_rc(self) -> tuple[int, int] | tuple[int]: + def _signal_shape_rc(self) -> tuple[int, ...]: """Return the signal's signal shape as (row, column).""" return self.axes_manager.signal_shape[::-1] @property - def _navigation_shape_rc(self) -> tuple[int, int] | tuple[int]: + def _navigation_shape_rc(self) -> tuple[int, ...]: """Return the signal's navigation shape as (row, column).""" return self.axes_manager.navigation_shape[::-1] diff --git a/tests/test_io/test_oxford_h5ebsd.py b/tests/test_io/test_oxford_h5ebsd.py index 41d583ab..51368280 100644 --- a/tests/test_io/test_oxford_h5ebsd.py +++ b/tests/test_io/test_oxford_h5ebsd.py @@ -22,9 +22,9 @@ class TestOxfordH5EBSD: def test_load( - self, oxford_h5ebsd_path, ni_small_axes_manager, assert_dictionary_func + self, oxford_h5ebsd_file, ni_small_axes_manager, assert_dictionary_func ): - s = kp.load(oxford_h5ebsd_path / "patterns.h5oina") + s = kp.load(oxford_h5ebsd_file) assert s.data.shape == (3, 3, 60, 60) assert_dictionary_func(s.axes_manager.as_dictionary(), ni_small_axes_manager) assert s.metadata.Acquisition_instrument.SEM.beam_energy == 20 @@ -40,3 +40,8 @@ def test_load( assert np.isclose(det.sample_tilt, 69.9, atol=0.1) assert det.binning == 8 assert np.isclose(det.tilt, 1.5) + + def test_load_unprocessed_patterns(self, oxford_h5ebsd_file): + s1 = kp.load(oxford_h5ebsd_file) + s2 = kp.load(oxford_h5ebsd_file, processed=False) + assert np.allclose(s1.data + 1, s2.data) From 35a739257a6c1bd87c2caf8f6825bece0ad21a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 20:38:23 +0100 Subject: [PATCH 13/24] Move creation of dummy data outside conftest to allow use elsewhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 464 +----------------- .../data/_dummy_files/bruker_h5ebsd.py | 381 ++++++++++++++ .../data/_dummy_files/oxford_h5ebsd.py | 138 ++++++ 3 files changed, 533 insertions(+), 450 deletions(-) create mode 100644 src/kikuchipy/data/_dummy_files/bruker_h5ebsd.py create mode 100644 src/kikuchipy/data/_dummy_files/oxford_h5ebsd.py diff --git a/conftest.py b/conftest.py index 955b8d6c..5e39831a 100644 --- a/conftest.py +++ b/conftest.py @@ -41,6 +41,12 @@ import kikuchipy as kp from kikuchipy import constants from kikuchipy.data._data import marshall +from kikuchipy.data._dummy_files.bruker_h5ebsd import ( + create_dummy_bruker_h5ebsd_file, + create_dummy_bruker_h5ebsd_nonrectangular_roi_file, + create_dummy_bruker_h5ebsd_roi_file, +) +from kikuchipy.data._dummy_files.oxford_h5ebsd import create_dummy_oxford_h5ebsd_file from kikuchipy.io.plugins._h5ebsd import _dict2hdf5group if constants.installed["pyvista"]: @@ -528,115 +534,8 @@ def oxford_binary_file(tmpdir, request) -> Generator[TextIOWrapper, None, None]: @pytest.fixture() def oxford_h5ebsd_file(tmpdir) -> Generator[TextIOWrapper, None, None]: - """Oxford Instruments h5ebsd file (H5OINA). - - Both processed and unprocessed patterns (processed + 1) are written - to file. - """ - # Quaternions determined from indexing - # fmt: off - qu_grain1 = (0.9542, -0.0183, -0.2806, 0.1018) - qu_grain2 = (0.9542, 0.0608, -0.2295, -0.1818) - rot = Rotation( - [ - qu_grain1, qu_grain2, qu_grain2, - qu_grain1, qu_grain2, qu_grain2, - qu_grain1, qu_grain2, qu_grain2, - ] - ) - # fmt: on - euler = rot.to_euler() - - s = kp.data.nickel_ebsd_small() - ny, nx = s._navigation_shape_rc - n = ny * nx - sy, sx = s._signal_shape_rc - dx = s.axes_manager["x"].scale - fpath = tmpdir / "patterns.h5oina" - f = h5py.File(fpath, mode="w") - - # Top group - f.create_dataset("Format Version", data=b"5.0") - f.create_dataset("Index", data=b"1") - f.create_dataset("Manufacturer", data=b"Oxford Instruments") - f.create_dataset("Software Version", data=b"6.0.8014.1") - scan = f.create_group("1") - - # EBSD - ebsd = scan.create_group("EBSD") - ones = np.ones(n) - - # Data - data = ebsd.create_group("Data") - data.create_dataset("Band Contrast", dtype="uint8", data=ones) - data.create_dataset("Band Slope", dtype="uint8", data=ones) - data.create_dataset("Bands", dtype="uint8", data=ones) - data.create_dataset("Beam Position X", dtype="float32", data=ones) - data["Beam Position X"].attrs["Unit"] = "um" - data.create_dataset("Beam Position Y", dtype="float32", data=ones) - data["Beam Position Y"].attrs["Unit"] = "um" - data.create_dataset("Detector Distance", dtype="float32", data=ones) - data.create_dataset("Error", dtype="uint8", data=ones) - data["Error"].attrs["HighMAD"] = [np.int32(5)] - data["Error"].attrs["LowBandContrast"] = [np.int32(3)] - data["Error"].attrs["LowBandSlope"] = [np.int32(4)] - data["Error"].attrs["NoSolution"] = [np.int32(2)] - data["Error"].attrs["NotAnalyzed"] = [np.int32(0)] - data["Error"].attrs["Replaced"] = [np.int32(7)] - data["Error"].attrs["Success"] = [np.int32(1)] - data["Error"].attrs["UnexpectedError"] = [np.int32(6)] - data.create_dataset("Euler", dtype="float32", data=euler) - data["Euler"].attrs["Unit"] = "rad" - data.create_dataset("Mean Angular Deviation", dtype="float32", data=ones) - data["Euler"].attrs["Mean Angular Deviation"] = "rad" - data.create_dataset("Pattern Center X", dtype="float32", data=ones) - data.create_dataset("Pattern Center Y", dtype="float32", data=ones) - data.create_dataset("Pattern Quality", dtype="float32", data=ones) - data.create_dataset("Phase", dtype="uint8", data=ones) - s.remove_static_background() - data.create_dataset( - "Processed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) - ) - data.create_dataset( - "Unprocessed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) + 1 - ) - x = np.array([0, 1, 2] * 3) * dx - data.create_dataset("X", dtype="float32", data=x) - data["X"].attrs["Unit"] = "um" - data.create_dataset("Y", dtype="float32", data=np.sort(x)) - data["Y"].attrs["Unit"] = "um" - - # Header - header = ebsd.create_group("Header") - header.create_dataset("Beam Voltage", dtype="float32", data=20) - header["Beam Voltage"].attrs["Unit"] = "kV" - header.create_dataset("Magnification", dtype="float32", data=2000) - header.create_dataset("Working Distance", dtype="float32", data=23.5) - header["Working Distance"].attrs["Unit"] = "mm" - header.create_dataset("X Cells", dtype="int32", data=3) - header["X Cells"].attrs["Unit"] = "px" - header.create_dataset("Y Cells", dtype="int32", data=3) - header["Y Cells"].attrs["Unit"] = "px" - header.create_dataset("X Step", dtype="float32", data=1.5) - header["X Step"].attrs["Unit"] = "um" - header.create_dataset("Y Step", dtype="float32", data=1.5) - header["Y Step"].attrs["Unit"] = "um" - header.create_dataset("Pattern Height", dtype="int32", data=60) - header["Pattern Height"].attrs["Unit"] = "px" - header.create_dataset("Pattern Width", dtype="int32", data=60) - header["Pattern Width"].attrs["Unit"] = "px" - header.create_dataset( - "Processed Static Background", dtype="uint8", data=s.static_background - ) - header.create_dataset("Tilt Angle", dtype="float32", data=np.deg2rad(69.9)) - header.create_dataset( - "Detector Orientation Euler", dtype="float32", data=np.deg2rad([0, 91.5, 0]) - ) - header.create_dataset("Camera Binning Mode", data=b"8x8 (60x60 px)") - - f.close() - + create_dummy_oxford_h5ebsd_file(fpath) yield fpath @@ -899,115 +798,8 @@ def bruker_path() -> Generator[Path, None, None]: @pytest.fixture def bruker_h5ebsd_file(tmpdir) -> Generator[Path, None, None]: """Bruker h5ebsd file with no region of interest.""" - s = kp.data.nickel_ebsd_small() - ny, nx = s._navigation_shape_rc - n = ny * nx - sy, sx = s._signal_shape_rc - fpath = tmpdir / "patterns.h5" - f = h5py.File(fpath, mode="w") - - # Top group - man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") - man[()] = b"Bruker Nano" - ver = f.create_dataset("Version", shape=(1,), dtype="|S10") - ver[()] = b"Esprit 2.X" - scan = f.create_group("Scan 0") - - # EBSD - ebsd = scan.create_group("EBSD") - - ones9 = np.ones(n, dtype=np.float32) - zeros9 = np.zeros(n, dtype=np.float32) - - ebsd_data = ebsd.create_group("Data") - ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))) - ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) - ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) - - ebsd_header = ebsd.create_group("Header") - ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) - ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=sy) - ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=sx) - grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") - grid_type[()] = b"isometric" - ebsd_header.create_dataset("KV", dtype=float, data=20) - ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) - ebsd_header.create_dataset("Magnification", dtype=float, data=200) - ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) - ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) - ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) - ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) - ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) - ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) - original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") - original_file[()] = b"/a/home/for/your/data.h5" - ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) - ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) - s_mean = s.nanmean((2, 3)).data.astype(np.uint8) - ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) - ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) - ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) - ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) - bg = s.static_background - ebsd_header.create_dataset("StaticBackground", dtype=np.uint16, data=bg) - ebsd_header.create_dataset("TopClip", dtype=float, data=1) - ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("WD", dtype=float, data=1) - ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("ZOffset", dtype=float, data=0) - - phase = ebsd_header.create_group("Phases/1") - formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") - formula[()] = b"Ni" - phase.create_dataset("IT", dtype=np.int32, data=225) - phase.create_dataset( - "LatticeConstants", - dtype=np.float32, - data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), - ) - name = phase.create_dataset("Name", shape=(1,), dtype="|S6") - name[()] = b"Nickel" - phase.create_dataset("Setting", dtype=np.int32, data=1) - space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") - space_group[()] = b"Fm-3m" - atom_pos = phase.create_group("AtomPositions") - atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") - atom_pos1[()] = b"Ni,0,0,0,1,0.0035" - - # SEM - sem = scan.create_group("SEM") - sem.create_dataset("SEM IX", dtype=np.int32, data=np.ones(1)) - sem.create_dataset("SEM IY", dtype=np.int32, data=np.ones(1)) - sem.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) - sem.create_dataset("SEM ImageHeight", dtype=np.int32, data=3) - sem.create_dataset("SEM ImageWidth", dtype=np.int32, data=3) - sem.create_dataset("SEM KV", dtype=float, data=20) - sem.create_dataset("SEM Magnification", dtype=float, data=200) - sem.create_dataset("SEM WD", dtype=float, data=24.5) - sem.create_dataset("SEM XResolution", dtype=float, data=1) - sem.create_dataset("SEM YResolution", dtype=float, data=1) - sem.create_dataset("SEM ZOffset", dtype=float, data=0) - - f.close() - + create_dummy_bruker_h5ebsd_file(fpath) yield fpath @@ -1016,123 +808,9 @@ def bruker_h5ebsd_roi_file(tmpdir) -> Generator[Path, None, None]: """Bruker h5ebsd file with rectangular region of interest (and SEM group under EBSD group). """ - s = kp.data.nickel_ebsd_small() - ny, nx = s._navigation_shape_rc - n = ny * nx - sy, sx = s._signal_shape_rc - - fpath = tmpdir / "patterns_roi.h5" - f = h5py.File(fpath, mode="w") - - # Top group - man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") - man[()] = b"Bruker Nano" - ver = f.create_dataset("Version", shape=(1,), dtype="|S10") - ver[()] = b"Esprit 2.X" - scan = f.create_group("Scan 0") - - # EBSD - ebsd = scan.create_group("EBSD") - - # ROI and shape - roi = np.array( - [ - [0, 1, 1], # 0, 1, 2 | (0, 0) (0, 1) (0, 2) - [0, 1, 1], # 3, 4, 5 | (1, 0) (1, 1) (1, 2) - [0, 1, 1], # 6, 7, 8 | (2, 0) (2, 1) (2, 2) - ], - dtype=bool, - ).flatten() - # Order of ROI patterns: 4, 1, 2, 5, 7, 8 - iy = np.array([1, 0, 0, 1, 2, 2], dtype=int) - ix = np.array([1, 1, 2, 2, 1, 2], dtype=int) - - # Data - ones9 = np.ones(9, dtype=np.float32)[roi] - zeros9 = np.zeros(9, dtype=np.float32)[roi] - ebsd_data = ebsd.create_group("Data") - ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))[roi]) - ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) - ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) - - # Header - ebsd_header = ebsd.create_group("Header") - ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) - ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=23700) - ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=31600) - grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") - grid_type[()] = b"isometric" - ebsd_header.create_dataset("KV", dtype=float, data=20) - ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) - ebsd_header.create_dataset("Magnification", dtype=float, data=200) - ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) - ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) - ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) - ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) - ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) - ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) - original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") - original_file[()] = b"/a/home/for/your/data.h5" - ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) - ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) - s_mean = s.nanmean((2, 3)).data.astype(np.uint8) - ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) - ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) - ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) - ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) - ebsd_header.create_dataset( - "StaticBackground", dtype=np.uint16, data=s.static_background - ) - ebsd_header.create_dataset("TopClip", dtype=float, data=1) - ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("WD", dtype=float, data=1) - ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("ZOffset", dtype=float, data=0) - # Phases - phase = ebsd_header.create_group("Phases/1") - formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") - formula[()] = b"Ni" - phase.create_dataset("IT", dtype=np.int32, data=225) - phase.create_dataset( - "LatticeConstants", - dtype=np.float32, - data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), - ) - name = phase.create_dataset("Name", shape=(1,), dtype="|S6") - name[()] = b"Nickel" - phase.create_dataset("Setting", dtype=np.int32, data=1) - space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") - space_group[()] = b"Fm-3m" - atom_pos = phase.create_group("AtomPositions") - atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") - atom_pos1[()] = b"Ni,0,0,0,1,0.0035" - - # SEM - sem = ebsd.create_group("SEM") - sem.create_dataset("IX", dtype=np.int32, data=ix) - sem.create_dataset("IY", dtype=np.int32, data=iy) - sem.create_dataset("ZOffset", dtype=float, data=0) - - f.close() - - yield fpath + path = tmpdir / "patterns_roi.h5" + create_dummy_bruker_h5ebsd_roi_file(path) + yield path @pytest.fixture @@ -1140,120 +818,6 @@ def bruker_h5ebsd_nonrectangular_roi_file(tmpdir) -> Generator[Path, None, None] """Bruker h5ebsd file with non-rectangular region of interest (and SEM group under EBSD group). """ - s = kp.data.nickel_ebsd_small() - ny, nx = s._navigation_shape_rc - n = ny * nx - sy, sx = s._signal_shape_rc - - fpath = tmpdir / "patterns_roi_nonrectangular.h5" - f = h5py.File(fpath, mode="w") - - # Top group - man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") - man[()] = b"Bruker Nano" - ver = f.create_dataset("Version", shape=(1,), dtype="|S10") - ver[()] = b"Esprit 2.X" - scan = f.create_group("Scan 0") - - # EBSD - ebsd = scan.create_group("EBSD") - - # ROI and shape - roi = np.array( - [ - [0, 1, 1], # 0, 1, 2 | (0, 0) (0, 1) (0, 2) - [0, 1, 1], # 3, 4, 5 | (1, 0) (1, 1) (1, 2) - [0, 1, 0], # 6, 7, 8 | (2, 0) (2, 1) (2, 2) - ], - dtype=bool, - ).flatten() - # Order of ROI patterns: 4, 1, 2, 7, 5 - iy = np.array([1, 0, 0, 2, 1], dtype=int) - ix = np.array([1, 1, 2, 1, 2], dtype=int) - - # Data - ones9 = np.ones(n, dtype=np.float32)[roi] - zeros9 = np.zeros(n, dtype=np.float32)[roi] - ebsd_data = ebsd.create_group("Data") - ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))[roi]) - ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) - ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) - ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) - ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) - - # Header - ebsd_header = ebsd.create_group("Header") - ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) - ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=sy) - ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=sx) - grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") - grid_type[()] = b"isometric" - # ebsd_header.create_dataset("KV", dtype=float, data=20) - ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) - ebsd_header.create_dataset("Magnification", dtype=float, data=200) - ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) - ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) - ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) - ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) - ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) - ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) - original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") - original_file[()] = b"/a/home/for/your/data.h5" - ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) - ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) - s_mean = s.nanmean((2, 3)).data.astype(np.uint8) - ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) - ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) - ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) - ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) - ebsd_header.create_dataset( - "StaticBackground", dtype=np.uint16, data=s.static_background - ) - ebsd_header.create_dataset("TopClip", dtype=float, data=1) - ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) - ebsd_header.create_dataset("WD", dtype=float, data=1) - ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) - ebsd_header.create_dataset("ZOffset", dtype=float, data=0) - # Phases - phase = ebsd_header.create_group("Phases/1") - formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") - formula[()] = b"Ni" - phase.create_dataset("IT", dtype=np.int32, data=225) - phase.create_dataset( - "LatticeConstants", - dtype=np.float32, - data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), - ) - name = phase.create_dataset("Name", shape=(1,), dtype="|S6") - name[()] = b"Nickel" - phase.create_dataset("Setting", dtype=np.int32, data=1) - space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") - space_group[()] = b"Fm-3m" - atom_pos = phase.create_group("AtomPositions") - atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") - atom_pos1[()] = b"Ni,0,0,0,1,0.0035" - - # SEM - sem = ebsd.create_group("SEM") - sem.create_dataset("IX", dtype=np.int32, data=ix) - sem.create_dataset("IY", dtype=np.int32, data=iy) - sem.create_dataset("ZOffset", dtype=float, data=0) - - f.close() - - yield fpath + path = tmpdir / "patterns_roi_nonrectangular.h5" + create_dummy_bruker_h5ebsd_nonrectangular_roi_file(path) + yield path diff --git a/src/kikuchipy/data/_dummy_files/bruker_h5ebsd.py b/src/kikuchipy/data/_dummy_files/bruker_h5ebsd.py new file mode 100644 index 00000000..5ec9c8fc --- /dev/null +++ b/src/kikuchipy/data/_dummy_files/bruker_h5ebsd.py @@ -0,0 +1,381 @@ +# 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 dummy Oxford Instrument's H5OINA file for testing and IO +documentation. +""" + +from pathlib import Path + +import h5py +import numpy as np +import skimage.color as skc + +from kikuchipy.data import nickel_ebsd_small + + +def create_dummy_bruker_h5ebsd_file(path: Path) -> None: + """Create a dummy Bruker Nano h5ebsd file with no region of + interest. + """ + s = nickel_ebsd_small() + ny, nx = s._navigation_shape_rc + n = ny * nx + sy, sx = s._signal_shape_rc + + f = h5py.File(path, mode="w") + + # Top group + man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") + man[()] = b"Bruker Nano" + ver = f.create_dataset("Version", shape=(1,), dtype="|S10") + ver[()] = b"Esprit 2.X" + scan = f.create_group("Scan 0") + + # EBSD + ebsd = scan.create_group("EBSD") + + ones9 = np.ones(n, dtype=np.float32) + zeros9 = np.zeros(n, dtype=np.float32) + + ebsd_data = ebsd.create_group("Data") + ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))) + ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) + ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) + + ebsd_header = ebsd.create_group("Header") + ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) + ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=sy) + ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=sx) + grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") + grid_type[()] = b"isometric" + ebsd_header.create_dataset("KV", dtype=float, data=20) + ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) + ebsd_header.create_dataset("Magnification", dtype=float, data=200) + ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) + ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) + ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) + ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) + ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) + ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) + original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") + original_file[()] = b"/a/home/for/your/data.h5" + ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) + ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) + s_mean = s.nanmean((2, 3)).data.astype(np.uint8) + ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) + ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) + ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) + ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) + bg = s.static_background + ebsd_header.create_dataset("StaticBackground", dtype=np.uint16, data=bg) + ebsd_header.create_dataset("TopClip", dtype=float, data=1) + ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("WD", dtype=float, data=1) + ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("ZOffset", dtype=float, data=0) + + phase = ebsd_header.create_group("Phases/1") + formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") + formula[()] = b"Ni" + phase.create_dataset("IT", dtype=np.int32, data=225) + phase.create_dataset( + "LatticeConstants", + dtype=np.float32, + data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), + ) + name = phase.create_dataset("Name", shape=(1,), dtype="|S6") + name[()] = b"Nickel" + phase.create_dataset("Setting", dtype=np.int32, data=1) + space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") + space_group[()] = b"Fm-3m" + atom_pos = phase.create_group("AtomPositions") + atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") + atom_pos1[()] = b"Ni,0,0,0,1,0.0035" + + # SEM + sem = scan.create_group("SEM") + sem.create_dataset("SEM IX", dtype=np.int32, data=np.ones(1)) + sem.create_dataset("SEM IY", dtype=np.int32, data=np.ones(1)) + sem.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) + sem.create_dataset("SEM ImageHeight", dtype=np.int32, data=3) + sem.create_dataset("SEM ImageWidth", dtype=np.int32, data=3) + sem.create_dataset("SEM KV", dtype=float, data=20) + sem.create_dataset("SEM Magnification", dtype=float, data=200) + sem.create_dataset("SEM WD", dtype=float, data=24.5) + sem.create_dataset("SEM XResolution", dtype=float, data=1) + sem.create_dataset("SEM YResolution", dtype=float, data=1) + sem.create_dataset("SEM ZOffset", dtype=float, data=0) + + f.close() + + +def create_dummy_bruker_h5ebsd_roi_file(path: Path) -> None: + """Create a dummy Bruker Nano h5ebsd file with a rectangular region + of interest (and SEM group under EBSD group). + """ + s = nickel_ebsd_small() + ny, nx = s._navigation_shape_rc + n = ny * nx + sy, sx = s._signal_shape_rc + + f = h5py.File(path, mode="w") + + # Top group + man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") + man[()] = b"Bruker Nano" + ver = f.create_dataset("Version", shape=(1,), dtype="|S10") + ver[()] = b"Esprit 2.X" + scan = f.create_group("Scan 0") + + # EBSD + ebsd = scan.create_group("EBSD") + + # ROI and shape + roi = np.array( + [ + [0, 1, 1], # 0, 1, 2 | (0, 0) (0, 1) (0, 2) + [0, 1, 1], # 3, 4, 5 | (1, 0) (1, 1) (1, 2) + [0, 1, 1], # 6, 7, 8 | (2, 0) (2, 1) (2, 2) + ], + dtype=bool, + ).flatten() + # Order of ROI patterns: 4, 1, 2, 5, 7, 8 + iy = np.array([1, 0, 0, 1, 2, 2], dtype=int) + ix = np.array([1, 1, 2, 2, 1, 2], dtype=int) + + # Data + ones9 = np.ones(9, dtype=np.float32)[roi] + zeros9 = np.zeros(9, dtype=np.float32)[roi] + ebsd_data = ebsd.create_group("Data") + ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))[roi]) + ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) + ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) + + # Header + ebsd_header = ebsd.create_group("Header") + ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) + ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=23700) + ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=31600) + grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") + grid_type[()] = b"isometric" + ebsd_header.create_dataset("KV", dtype=float, data=20) + ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) + ebsd_header.create_dataset("Magnification", dtype=float, data=200) + ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) + ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) + ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) + ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) + ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) + ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) + original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") + original_file[()] = b"/a/home/for/your/data.h5" + ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) + ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) + s_mean = s.nanmean((2, 3)).data.astype(np.uint8) + ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) + ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) + ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) + ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) + ebsd_header.create_dataset( + "StaticBackground", dtype=np.uint16, data=s.static_background + ) + ebsd_header.create_dataset("TopClip", dtype=float, data=1) + ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("WD", dtype=float, data=1) + ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("ZOffset", dtype=float, data=0) + # Phases + phase = ebsd_header.create_group("Phases/1") + formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") + formula[()] = b"Ni" + phase.create_dataset("IT", dtype=np.int32, data=225) + phase.create_dataset( + "LatticeConstants", + dtype=np.float32, + data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), + ) + name = phase.create_dataset("Name", shape=(1,), dtype="|S6") + name[()] = b"Nickel" + phase.create_dataset("Setting", dtype=np.int32, data=1) + space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") + space_group[()] = b"Fm-3m" + atom_pos = phase.create_group("AtomPositions") + atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") + atom_pos1[()] = b"Ni,0,0,0,1,0.0035" + + # SEM + sem = ebsd.create_group("SEM") + sem.create_dataset("IX", dtype=np.int32, data=ix) + sem.create_dataset("IY", dtype=np.int32, data=iy) + sem.create_dataset("ZOffset", dtype=float, data=0) + + f.close() + + +def create_dummy_bruker_h5ebsd_nonrectangular_roi_file(path) -> None: + """Create a dummy Bruker Nano h5ebsd file with non-rectangular + region of interest (and SEM group under EBSD group). + """ + s = nickel_ebsd_small() + ny, nx = s._navigation_shape_rc + n = ny * nx + sy, sx = s._signal_shape_rc + + f = h5py.File(path, mode="w") + + # Top group + man = f.create_dataset("Manufacturer", shape=(1,), dtype="|S11") + man[()] = b"Bruker Nano" + ver = f.create_dataset("Version", shape=(1,), dtype="|S10") + ver[()] = b"Esprit 2.X" + scan = f.create_group("Scan 0") + + # EBSD + ebsd = scan.create_group("EBSD") + + # ROI and shape + roi = np.array( + [ + [0, 1, 1], # 0, 1, 2 | (0, 0) (0, 1) (0, 2) + [0, 1, 1], # 3, 4, 5 | (1, 0) (1, 1) (1, 2) + [0, 1, 0], # 6, 7, 8 | (2, 0) (2, 1) (2, 2) + ], + dtype=bool, + ).flatten() + # Order of ROI patterns: 4, 1, 2, 7, 5 + iy = np.array([1, 0, 0, 2, 1], dtype=int) + ix = np.array([1, 1, 2, 1, 2], dtype=int) + + # Data + ones9 = np.ones(n, dtype=np.float32)[roi] + zeros9 = np.zeros(n, dtype=np.float32)[roi] + ebsd_data = ebsd.create_group("Data") + ebsd_data.create_dataset("DD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MAD", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("MADPhase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("NIndexedBands", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCX", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PCY", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("PHI", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Phase", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonBandCount", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("RadonQuality", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("RawPatterns", data=s.data.reshape((n, sy, sx))[roi]) + ebsd_data.create_dataset("X BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("X SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Y BEAM", dtype=np.int32, data=ones9) + ebsd_data.create_dataset("Y SAMPLE", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("Z SAMPLE", dtype=np.float32, data=zeros9) + ebsd_data.create_dataset("phi1", dtype=np.float32, data=ones9) + ebsd_data.create_dataset("phi2", dtype=np.float32, data=ones9) + + # Header + ebsd_header = ebsd.create_group("Header") + ebsd_header.create_dataset("CameraTilt", dtype=float, data=0) + ebsd_header.create_dataset("DetectorFullHeightMicrons", dtype=np.int32, data=sy) + ebsd_header.create_dataset("DetectorFullWidthMicrons", dtype=np.int32, data=sx) + grid_type = ebsd_header.create_dataset("Grid Type", shape=(1,), dtype="|S9") + grid_type[()] = b"isometric" + # ebsd_header.create_dataset("KV", dtype=float, data=20) + ebsd_header.create_dataset("MADMax", dtype=float, data=1.5) + ebsd_header.create_dataset("Magnification", dtype=float, data=200) + ebsd_header.create_dataset("MapStepFactor", dtype=float, data=4) + ebsd_header.create_dataset("MaxRadonBandCount", dtype=np.int32, data=11) + ebsd_header.create_dataset("MinIndexedBands", dtype=np.int32, data=5) + ebsd_header.create_dataset("NCOLS", dtype=np.int32, data=nx) + ebsd_header.create_dataset("NROWS", dtype=np.int32, data=ny) + ebsd_header.create_dataset("NPoints", dtype=np.int32, data=n) + original_file = ebsd_header.create_dataset("OriginalFile", shape=(1,), dtype="|S50") + original_file[()] = b"/a/home/for/your/data.h5" + ebsd_header.create_dataset("PatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("PatternWidth", dtype=np.int32, data=sx) + ebsd_header.create_dataset("PixelByteCount", dtype=np.int32, data=1) + s_mean = s.nanmean((2, 3)).data.astype(np.uint8) + ebsd_header.create_dataset("SEM Image", data=skc.gray2rgb(s_mean)) + ebsd_header.create_dataset("SEPixelSizeX", dtype=float, data=1) + ebsd_header.create_dataset("SEPixelSizeY", dtype=float, data=1) + ebsd_header.create_dataset("SampleTilt", dtype=float, data=70) + ebsd_header.create_dataset( + "StaticBackground", dtype=np.uint16, data=s.static_background + ) + ebsd_header.create_dataset("TopClip", dtype=float, data=1) + ebsd_header.create_dataset("UnClippedPatternHeight", dtype=np.int32, data=sy) + ebsd_header.create_dataset("WD", dtype=float, data=1) + ebsd_header.create_dataset("XSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("YSTEP", dtype=float, data=1.5) + ebsd_header.create_dataset("ZOffset", dtype=float, data=0) + # Phases + phase = ebsd_header.create_group("Phases/1") + formula = phase.create_dataset("Formula", shape=(1,), dtype="|S2") + formula[()] = b"Ni" + phase.create_dataset("IT", dtype=np.int32, data=225) + phase.create_dataset( + "LatticeConstants", + dtype=np.float32, + data=np.array([3.56, 3.56, 3.56, 90, 90, 90]), + ) + name = phase.create_dataset("Name", shape=(1,), dtype="|S6") + name[()] = b"Nickel" + phase.create_dataset("Setting", dtype=np.int32, data=1) + space_group = phase.create_dataset("SpaceGroup", shape=(1,), dtype="|S5") + space_group[()] = b"Fm-3m" + atom_pos = phase.create_group("AtomPositions") + atom_pos1 = atom_pos.create_dataset("1", shape=(1,), dtype="|S17") + atom_pos1[()] = b"Ni,0,0,0,1,0.0035" + + # SEM + sem = ebsd.create_group("SEM") + sem.create_dataset("IX", dtype=np.int32, data=ix) + sem.create_dataset("IY", dtype=np.int32, data=iy) + sem.create_dataset("ZOffset", dtype=float, data=0) + + f.close() diff --git a/src/kikuchipy/data/_dummy_files/oxford_h5ebsd.py b/src/kikuchipy/data/_dummy_files/oxford_h5ebsd.py new file mode 100644 index 00000000..b61d56a7 --- /dev/null +++ b/src/kikuchipy/data/_dummy_files/oxford_h5ebsd.py @@ -0,0 +1,138 @@ +# 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 dummy Oxford Instrument's H5OINA file for testing and IO +documentation. +""" + +from pathlib import Path + +import h5py +import numpy as np +from orix.quaternion import Rotation + +from kikuchipy.data import nickel_ebsd_small + + +def create_dummy_oxford_h5ebsd_file(path: Path) -> None: + """Create a dummy H5OINA file with the given path. + + Both processed and unprocessed patterns (processed + 1) are written + to file. + """ + # Quaternions determined from indexing + # fmt: off + qu_grain1 = (0.9542, -0.0183, -0.2806, 0.1018) + qu_grain2 = (0.9542, 0.0608, -0.2295, -0.1818) + rot = Rotation( + [ + qu_grain1, qu_grain2, qu_grain2, + qu_grain1, qu_grain2, qu_grain2, + qu_grain1, qu_grain2, qu_grain2, + ] + ) + # fmt: on + euler = rot.to_euler() + + s = nickel_ebsd_small() + ny, nx = s._navigation_shape_rc + n = ny * nx + sy, sx = s._signal_shape_rc + dx = s.axes_manager["x"].scale + + f = h5py.File(path, mode="w") + + # Top group + f.create_dataset("Format Version", data=b"5.0") + f.create_dataset("Index", data=b"1") + f.create_dataset("Manufacturer", data=b"Oxford Instruments") + f.create_dataset("Software Version", data=b"6.0.8014.1") + scan = f.create_group("1") + + # EBSD + ebsd = scan.create_group("EBSD") + ones = np.ones(n) + + # Data + data = ebsd.create_group("Data") + data.create_dataset("Band Contrast", dtype="uint8", data=ones) + data.create_dataset("Band Slope", dtype="uint8", data=ones) + data.create_dataset("Bands", dtype="uint8", data=ones) + data.create_dataset("Beam Position X", dtype="float32", data=ones) + data["Beam Position X"].attrs["Unit"] = "um" + data.create_dataset("Beam Position Y", dtype="float32", data=ones) + data["Beam Position Y"].attrs["Unit"] = "um" + data.create_dataset("Detector Distance", dtype="float32", data=ones) + data.create_dataset("Error", dtype="uint8", data=ones) + data["Error"].attrs["HighMAD"] = [np.int32(5)] + data["Error"].attrs["LowBandContrast"] = [np.int32(3)] + data["Error"].attrs["LowBandSlope"] = [np.int32(4)] + data["Error"].attrs["NoSolution"] = [np.int32(2)] + data["Error"].attrs["NotAnalyzed"] = [np.int32(0)] + data["Error"].attrs["Replaced"] = [np.int32(7)] + data["Error"].attrs["Success"] = [np.int32(1)] + data["Error"].attrs["UnexpectedError"] = [np.int32(6)] + data.create_dataset("Euler", dtype="float32", data=euler) + data["Euler"].attrs["Unit"] = "rad" + data.create_dataset("Mean Angular Deviation", dtype="float32", data=ones) + data["Euler"].attrs["Mean Angular Deviation"] = "rad" + data.create_dataset("Pattern Center X", dtype="float32", data=ones) + data.create_dataset("Pattern Center Y", dtype="float32", data=ones) + data.create_dataset("Pattern Quality", dtype="float32", data=ones) + data.create_dataset("Phase", dtype="uint8", data=ones) + s.remove_static_background() + data.create_dataset( + "Processed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) + ) + data.create_dataset( + "Unprocessed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) + 1 + ) + x = np.array([0, 1, 2] * 3) * dx + data.create_dataset("X", dtype="float32", data=x) + data["X"].attrs["Unit"] = "um" + data.create_dataset("Y", dtype="float32", data=np.sort(x)) + data["Y"].attrs["Unit"] = "um" + + # Header + header = ebsd.create_group("Header") + header.create_dataset("Beam Voltage", dtype="float32", data=20) + header["Beam Voltage"].attrs["Unit"] = "kV" + header.create_dataset("Magnification", dtype="float32", data=2000) + header.create_dataset("Working Distance", dtype="float32", data=23.5) + header["Working Distance"].attrs["Unit"] = "mm" + header.create_dataset("X Cells", dtype="int32", data=3) + header["X Cells"].attrs["Unit"] = "px" + header.create_dataset("Y Cells", dtype="int32", data=3) + header["Y Cells"].attrs["Unit"] = "px" + header.create_dataset("X Step", dtype="float32", data=1.5) + header["X Step"].attrs["Unit"] = "um" + header.create_dataset("Y Step", dtype="float32", data=1.5) + header["Y Step"].attrs["Unit"] = "um" + header.create_dataset("Pattern Height", dtype="int32", data=60) + header["Pattern Height"].attrs["Unit"] = "px" + header.create_dataset("Pattern Width", dtype="int32", data=60) + header["Pattern Width"].attrs["Unit"] = "px" + header.create_dataset( + "Processed Static Background", dtype="uint8", data=s.static_background + ) + header.create_dataset("Tilt Angle", dtype="float32", data=np.deg2rad(69.9)) + header.create_dataset( + "Detector Orientation Euler", dtype="float32", data=np.deg2rad([0, 91.5, 0]) + ) + header.create_dataset("Camera Binning Mode", data=b"8x8 (60x60 px)") + + f.close() From f5b74c99a4d0257dc3d932e3aa95e13e9ebe0fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 20:38:44 +0100 Subject: [PATCH 14/24] Remove unused H5OINA dummy file in data module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .../create_oxford_h5ebsd_file.py | 126 ------------------ .../data/oxford_h5ebsd/patterns.h5oina | Bin 59152 -> 0 bytes 2 files changed, 126 deletions(-) delete mode 100644 src/kikuchipy/data/oxford_h5ebsd/create_oxford_h5ebsd_file.py delete mode 100644 src/kikuchipy/data/oxford_h5ebsd/patterns.h5oina diff --git a/src/kikuchipy/data/oxford_h5ebsd/create_oxford_h5ebsd_file.py b/src/kikuchipy/data/oxford_h5ebsd/create_oxford_h5ebsd_file.py deleted file mode 100644 index 0f2b0baa..00000000 --- a/src/kikuchipy/data/oxford_h5ebsd/create_oxford_h5ebsd_file.py +++ /dev/null @@ -1,126 +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 a dummy Oxford Instruments h5ebsd file (H5OINA) used when -testing the Oxford h5ebsd reader. - -Only the groups and datasets read in the reader are included. -""" - -from pathlib import Path - -from h5py import File -import numpy as np -from orix.quaternion import Rotation - -import kikuchipy as kp - -# Orientations determined from indexing -grain1 = (0.9542, -0.0183, -0.2806, 0.1018) -grain2 = (0.9542, 0.0608, -0.2295, -0.1818) -rot = Rotation([grain1, grain2, grain2, grain1, grain2, grain2, grain1, grain2, grain2]) -euler = rot.to_euler() - -s = kp.data.nickel_ebsd_small() -ny, nx = s._navigation_shape_rc -n = ny * nx -sy, sx = s._signal_shape_rc -dx = s.axes_manager["x"].scale - -dir_data = Path(__file__).parent - - -f = File(dir_data / "patterns.h5oina", mode="w") - -# Top group -f.create_dataset("Format Version", data=b"5.0") -f.create_dataset("Index", data=b"1") -f.create_dataset("Manufacturer", data=b"Oxford Instruments") -f.create_dataset("Software Version", data=b"6.0.8014.1") -scan = f.create_group("1") - -# EBSD -ebsd = scan.create_group("EBSD") -ones = np.ones(n) -zeros = np.zeros(n) - -# Data -data = ebsd.create_group("Data") -data.create_dataset("Band Contrast", dtype="uint8", data=ones) -data.create_dataset("Band Slope", dtype="uint8", data=ones) -data.create_dataset("Bands", dtype="uint8", data=ones) -data.create_dataset("Beam Position X", dtype="float32", data=ones) -data["Beam Position X"].attrs["Unit"] = "um" -data.create_dataset("Beam Position Y", dtype="float32", data=ones) -data["Beam Position Y"].attrs["Unit"] = "um" -data.create_dataset("Detector Distance", dtype="float32", data=ones) -data.create_dataset("Error", dtype="uint8", data=ones) -data["Error"].attrs["HighMAD"] = [np.int32(5)] -data["Error"].attrs["LowBandContrast"] = [np.int32(3)] -data["Error"].attrs["LowBandSlope"] = [np.int32(4)] -data["Error"].attrs["NoSolution"] = [np.int32(2)] -data["Error"].attrs["NotAnalyzed"] = [np.int32(0)] -data["Error"].attrs["Replaced"] = [np.int32(7)] -data["Error"].attrs["Success"] = [np.int32(1)] -data["Error"].attrs["UnexpectedError"] = [np.int32(6)] -data.create_dataset("Euler", dtype="float32", data=euler) -data["Euler"].attrs["Unit"] = "rad" -data.create_dataset("Mean Angular Deviation", dtype="float32", data=ones) -data["Euler"].attrs["Mean Angular Deviation"] = "rad" -data.create_dataset("Pattern Center X", dtype="float32", data=ones) -data.create_dataset("Pattern Center Y", dtype="float32", data=ones) -data.create_dataset("Pattern Quality", dtype="float32", data=ones) -data.create_dataset("Phase", dtype="uint8", data=ones) -s.remove_static_background() -data.create_dataset( - "Processed Patterns", dtype="uint8", data=s.data.reshape((n, sy, sx)) -) -x = np.array([0, 1, 2] * 3) * dx -data.create_dataset("X", dtype="float32", data=x) -data["X"].attrs["Unit"] = "um" -data.create_dataset("Y", dtype="float32", data=np.sort(x)) -data["Y"].attrs["Unit"] = "um" - -# Header -header = ebsd.create_group("Header") -header.create_dataset("Beam Voltage", dtype="float32", data=20) -header["Beam Voltage"].attrs["Unit"] = "kV" -header.create_dataset("Magnification", dtype="float32", data=2000) -header.create_dataset("Working Distance", dtype="float32", data=23.5) -header["Working Distance"].attrs["Unit"] = "mm" -header.create_dataset("X Cells", dtype="int32", data=3) -header["X Cells"].attrs["Unit"] = "px" -header.create_dataset("Y Cells", dtype="int32", data=3) -header["Y Cells"].attrs["Unit"] = "px" -header.create_dataset("X Step", dtype="float32", data=1.5) -header["X Step"].attrs["Unit"] = "um" -header.create_dataset("Y Step", dtype="float32", data=1.5) -header["Y Step"].attrs["Unit"] = "um" -header.create_dataset("Pattern Height", dtype="int32", data=60) -header["Pattern Height"].attrs["Unit"] = "px" -header.create_dataset("Pattern Width", dtype="int32", data=60) -header["Pattern Width"].attrs["Unit"] = "px" -header.create_dataset( - "Processed Static Background", dtype="uint8", data=s.static_background -) -header.create_dataset("Tilt Angle", dtype="float32", data=np.deg2rad(69.9)) -header.create_dataset( - "Detector Orientation Euler", dtype="float32", data=np.deg2rad([0, 91.5, 0]) -) -header.create_dataset("Camera Binning Mode", data=b"8x8 (60x60 px)") - -f.close() diff --git a/src/kikuchipy/data/oxford_h5ebsd/patterns.h5oina b/src/kikuchipy/data/oxford_h5ebsd/patterns.h5oina deleted file mode 100644 index ccb27eb1c3b478aaba9907ad1ec147e12522df74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59152 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 From 0b2917d74f872a099c0dddaac7baa50db8e8c65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 20:39:12 +0100 Subject: [PATCH 15/24] Continue doc build even though pattern files cannot be downloaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- doc/conf.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 083caef8..600bfa6c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -6,8 +6,10 @@ import inspect import os from os.path import dirname, relpath +from pathlib import Path import re import sys +import warnings from numpydoc.docscrape_sphinx import SphinxDocString import pyvista as pv @@ -53,7 +55,6 @@ # Create links to references within kikuchipy's documentation to these # packages intersphinx_mapping = { - # Package "black": ("https://black.readthedocs.io/en/stable", None), "conda": ("https://docs.conda.io/projects/conda/en/latest", None), "coverage": ("https://coverage.readthedocs.io/en/latest", None), @@ -358,7 +359,15 @@ def _str_examples(self): } autosummary_generate = True -# Download example datasets prior to building the docs -print("[kikuchipy] Downloading example datasets (if not found in cache)") -_ = kp.data.nickel_ebsd_large(allow_download=True) -_ = kp.data.si_ebsd_moving_screen(0, allow_download=True) + +def custom_setup(): + """Download files used in the documentation.""" + print("[kikuchipy] Downloading example datasets (if not found in cache)") + try: + _ = kp.data.nickel_ebsd_large(allow_download=True) + _ = kp.data.si_ebsd_moving_screen(0, allow_download=True) + except () as e: + warnings.warn(f"Download failed. Error: {e}") + + +custom_setup() From d2bebea7c7475f926179d64646bdba0f063a6b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 20:46:58 +0100 Subject: [PATCH 16/24] Update IO tutorial to mention loading of unprocessed H5OINA patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 1 - doc/tutorials/load_save_data.ipynb | 5 ++++- src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/conftest.py b/conftest.py index 5e39831a..467140bf 100644 --- a/conftest.py +++ b/conftest.py @@ -36,7 +36,6 @@ from orix.crystal_map import CrystalMap, Phase, PhaseList, create_coordinate_arrays from orix.quaternion import Rotation import pytest -import skimage.color as skc import kikuchipy as kp from kikuchipy import constants diff --git a/doc/tutorials/load_save_data.ipynb b/doc/tutorials/load_save_data.ipynb index efcaa561..69a03450 100644 --- a/doc/tutorials/load_save_data.ipynb +++ b/doc/tutorials/load_save_data.ipynb @@ -983,7 +983,10 @@ "As with the kikuchipy h5ebsd reader, multiple scans can be read at once.\n", "\n", "The projection center (PC) arrays are read into the `detector` attribute and the static background pattern is read into the `static_background` attribute.\n", - "The orientation and phase data are so far not read." + "The orientation and phase data are so far not read.\n", + "\n", + "Oxford Instruments allow writing both processed and unprocessed patterns.\n", + "To read the unprocessed patterns, pass `processed=False` when loading." ] }, { diff --git a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py index 6712199f..dbbc5cf5 100644 --- a/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py +++ b/src/kikuchipy/io/plugins/oxford_h5ebsd/_api.py @@ -199,7 +199,7 @@ def file_reader( Raises ------ - ValueError + KeyError If *processed* is False and unprocessed patterns are not available. """ From aaec91d91ec760b10bb0fe75a1cd8211937d64d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Wed, 20 Nov 2024 20:51:37 +0100 Subject: [PATCH 17/24] Mention improvements to H5OINA reader 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 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 269ebc1d..e2f66e27 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,10 +16,17 @@ were listed in alphabetical order by first name until version 0.7.0. 0.11.1 (2024-11-24) =================== +Added +----- +- Reading of unprocessed patterns from H5OINA files is now possible. + (`#701 `_) + Fixed ----- - Reading of Oxford binary `*.ebsp` files with version 6. - (`#700 `_) + (`#700 `_) +- Unnecessary reading of unprocessed patterns from H5OINA files into the EBSD original + metadata. (`#701 `_) 0.11.0 (2024-11-10) =================== From 3b565b831450c57e6b8ce7ecf0e0bf30a29a9d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 18:35:23 +0100 Subject: [PATCH 18/24] More lenient number check in test, allow reruns for flaky test 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_hough_indexing.py | 2 +- tests/test_signals/test_ebsd_master_pattern.py | 7 ++----- tests/test_simulations/test_kikuchi_pattern_simulator.py | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_signals/test_ebsd_hough_indexing.py b/tests/test_signals/test_ebsd_hough_indexing.py index 4fcf01a8..c5bb5d15 100644 --- a/tests/test_signals/test_ebsd_hough_indexing.py +++ b/tests/test_signals/test_ebsd_hough_indexing.py @@ -316,7 +316,7 @@ def test_optimize_pc(self): assert np.isclose(det.tilt, det0.tilt) assert np.isclose(det.px_size, det0.px_size) - @pytest.mark.flaky(reruns=5) + @pytest.mark.flaky(reruns=3) def test_optimize_pc_pso(self, worker_id): det0 = self.signal.detector diff --git a/tests/test_signals/test_ebsd_master_pattern.py b/tests/test_signals/test_ebsd_master_pattern.py index b843cac4..bf7fab77 100644 --- a/tests/test_signals/test_ebsd_master_pattern.py +++ b/tests/test_signals/test_ebsd_master_pattern.py @@ -192,10 +192,7 @@ class TestProjectFromLambert: def test_get_direction_cosines(self): det = self.detector dc = _get_direction_cosines_from_detector(det) - assert dc.shape == ( - det.size, - 3, - ) + assert dc.shape == (det.size, 3) assert np.max(dc) <= 1 dc2 = _get_direction_cosines_for_fixed_pc.py_func( @@ -493,7 +490,7 @@ def test_get_pixel_from_master_pattern(self): value = _get_pixel_from_master_pattern.py_func( mp, nii[0], nij[0], niip[0], nijp[0], di[0], dj[0], dim[0], djm[0] ) - assert value == 1.0 + assert np.isclose(value, 1) def test_get_cosine_sine_of_alpha_and_azimuthal(self): """Make sure the Numba function is covered.""" diff --git a/tests/test_simulations/test_kikuchi_pattern_simulator.py b/tests/test_simulations/test_kikuchi_pattern_simulator.py index 5762be4a..3bd8eb7c 100644 --- a/tests/test_simulations/test_kikuchi_pattern_simulator.py +++ b/tests/test_simulations/test_kikuchi_pattern_simulator.py @@ -111,12 +111,13 @@ def test_raises(self): with pytest.raises(ValueError, match="Unknown scaling 'cubic', options are"): _ = simulator.calculate_master_pattern(scaling="cubic") + @pytest.mark.flaky(reruns=3) def test_shape(self): """Output shape as expected.""" simulator = self.simulator mp = simulator.calculate_master_pattern(half_size=100, hemisphere="both") assert mp.data.shape == (2, 201, 201) - assert np.allclose(mp.data[0], mp.data[1], atol=1e-7) + assert np.allclose(mp.data[0], mp.data[1], atol=1e-4) axes_names = [a["name"] for a in mp.axes_manager.as_dictionary().values()] assert axes_names == ["hemisphere", "height", "width"] From 40404e82901c90ba726999cc21612c70542f3a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 18:35:59 +0100 Subject: [PATCH 19/24] Include tests in wheel built with Hatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 961ec228..72b2f633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,6 +123,10 @@ src = "" [tool.hatch.build.targets.wheel] include = ["src/kikuchipy"] +[tool.hatch.build.targets.wheel.force-include] +"tests/" = "kikuchipy/tests" +"conftest.py" = "kikuchipy/conftest.py" + [tool.coverage.report] precision = 2 From 4ae48619ccff42e24b8ca3630f88b6853f800ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 18:36:22 +0100 Subject: [PATCH 20/24] Discover kikuchipy.data directory for tests at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index 467140bf..cb9b42b0 100644 --- a/conftest.py +++ b/conftest.py @@ -55,8 +55,7 @@ pv.global_theme.interactive = False -DATA_PATH = Path(__file__).parent / "src/kikuchipy/data" -DATA_PATH = DATA_PATH.resolve() +DATA_PATH = Path(kp.data.__file__).parent.resolve() # ------------------------------ Setup ------------------------------ # From ef221ce492c5ae281e5017e34bc9e3bd8db1f9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 18:36:56 +0100 Subject: [PATCH 21/24] Fix release date for 0.11.1 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 e2f66e27..ba5df9c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,7 +13,7 @@ its best to adhere to `Semantic Versioning List entries are sorted in descending chronological order. Contributors to each release were listed in alphabetical order by first name until version 0.7.0. -0.11.1 (2024-11-24) +0.11.1 (2024-11-30) =================== Added From 3f9d213f4563052df2b8664971ef7044de516289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 18:37:14 +0100 Subject: [PATCH 22/24] Set version to 0.11.1.post0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kikuchipy/__init__.py b/src/kikuchipy/__init__.py index 21a9985b..d3de6492 100644 --- a/src/kikuchipy/__init__.py +++ b/src/kikuchipy/__init__.py @@ -30,7 +30,7 @@ "Carter Francis", "Magnus Nord", ] -__version__ = "0.11.1" +__version__ = "0.11.1.post0" __getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) From 8000e5e83e7f2a1fd4ac96a8c08ce8f7f54afdc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 21:22:33 +0100 Subject: [PATCH 23/24] Update version to 0.12.dev1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- src/kikuchipy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kikuchipy/__init__.py b/src/kikuchipy/__init__.py index d3de6492..22361668 100644 --- a/src/kikuchipy/__init__.py +++ b/src/kikuchipy/__init__.py @@ -30,7 +30,7 @@ "Carter Francis", "Magnus Nord", ] -__version__ = "0.11.1.post0" +__version__ = "0.12.dev1" __getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__) From 02ae06a8b8113efc9650011c9f8ac82d250cfc03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Sat, 30 Nov 2024 21:22:42 +0100 Subject: [PATCH 24/24] Another unreleased section in the changelog waits to be filled in! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- CHANGELOG.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba5df9c4..0d4df870 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,24 @@ its best to adhere to `Semantic Versioning List entries are sorted in descending chronological order. Contributors to each release were listed in alphabetical order by first name until version 0.7.0. +Unreleased +========== + +Added +----- + +Changed +------- + +Removed +------- + +Fixed +----- + +Deprecated +---------- + 0.11.1 (2024-11-30) ===================