#!/usr/bin/env python
# coding: utf-8
# This notebook is part of the `kikuchipy` documentation https://kikuchipy.org.
# Links to the documentation won't work from the notebook.
# # Load and save data
#
# ## Load patterns
#
# ### From a file
#
# kikuchipy can read and write experimental EBSD patterns and EBSD master patterns
# from/to multiple formats (see [supported formats](#Supported-EBSD-formats)). To
# load patterns from file use the [load()](../reference.rst#kikuchipy.io._io.load)
# function. Let's import the necessary libraries and read the Nickel EBSD test
# data set directly from file (not via
# [kikuchipy.data.nickel_ebsd_small()](../reference.rst#kikuchipy.data.nickel_ebsd_small)):
# In[ ]:
# Exchange inline for notebook or qt5 (from pyqt) for interactive plotting
get_ipython().run_line_magic('matplotlib', 'inline')
import tempfile
import dask.array as da
import hyperspy.api as hs
import kikuchipy as kp
import numpy as np
import matplotlib.pyplot as plt
datadir = "../../kikuchipy/data/"
nordif_ebsd = "nordif/Pattern.dat"
s = kp.load(datadir + nordif_ebsd)
s
# Or, load the stereographic projection of the northern hemisphere of an EBSD master
# pattern for a 20 keV beam energy from a modified version of EMsoft's master
# pattern file, returned from their `EMEBSDmaster.f90` program:
# In[ ]:
emsoft_master_pattern = (
"emsoft_ebsd_master_pattern/ni_mc_mp_20kv_uint8_gzip_opts9.h5"
)
s_mp = kp.load(filename=datadir + emsoft_master_pattern)
s_mp
# Both the stereographic and the square Lambert projections of this master pattern
# data is available via
# [kikuchipy.data.nickel_ebsd_master_pattern_small()](../reference.rst#kikuchipy.data.nickel_ebsd_master_pattern_small).
#
# All file readers support accessing the data without loading it into memory (with
# the [Dask library](https://docs.dask.org/en/latest)), which can be useful when
# processing large data sets to avoid memory errors:
# In[ ]:
s_lazy = kp.load(datadir + nordif_ebsd, lazy=True)
print(s_lazy)
s_lazy.data
# Parts or all of the data can be read into memory by calling
# [compute()](http://hyperspy.org/hyperspy-doc/current/api/hyperspy._signals.lazy.html#hyperspy._signals.lazy.LazySignal.compute):
# In[ ]:
s_lazy_copy = s_lazy.inav[:2, :].deepcopy()
s_lazy_copy.compute()
s_lazy_copy
# In[ ]:
s_lazy.compute()
s_lazy
#
#
# Note
#
# When lazily loaded EBSD patterns are processed, they are processed chunk by
# chunk, which in many cases leads to longer processing times, so processing lazy
# data sets should be done with some care. See the relevant
# [HyperSpy user guide](http://hyperspy.org/hyperspy-doc/current/user_guide/big_data.html)
# for information on how to do this.
#
#
# Visualization of data is done by navigating navigation space, showing the signal
# in each navigation point:
# In[ ]:
s.plot()
# Upon loading, kikuchipy tries to read all scan information from the file and
# stores everything it can read in the `original_metadata` attribute:
# In[ ]:
# s.original_metadata # Long output
# Also, some information may be stored in a standard location in the `metadata`
# attribute where it can be used by EBSD class methods:
# In[ ]:
s.metadata
#
#
# Warning
#
# The `Acquisition_instrument.SEM.Detector.EBSD` and `Sample.Phases` metadata
# nodes are deprecated and will be removed in v0.6.
#
# There are three main reasons for this change: the first is that only the static
# background array stored in the
# `Acquisition_instrument.SEM.Detector.EBSD.static_background` node is used
# internally, and so the remaining metadata is unnecessary. The background pattern
# will be stored in its own `EBSD.static_background` property instead. The second
# is that keeping track of the unnecessary metadata makes writing and maintaining
# input/ouput plugins challenging. The third is that the
# [EBSD.xmap](../reference.rst#kikuchipy.signals.EBSD.xmap) and
# [EBSD.detector](../reference.rst#kikuchipy.signals.EBSD.detector) properties,
# which keeps track of the
# [CrystalMap](https://orix.readthedocs.io/en/stable/reference.html#orix.crystal_map.CrystalMap)
# and [EBSDDetector](../reference.rst#kikuchipy.detectors.EBSDDetector) for a
# signal, respectively, should be used instead of the more "static" metadata.
#
#
#
# The number of patterns in horizontal and vertical direction, pattern size in
# pixels, scan step size and detector pixel size is stored in the `axes_manager`
# attribute:
# In[ ]:
print(s.axes_manager) # Just "s.axes_manager" looks bad on a dark background
# This information can be modified directly, and information in `metadata` and
# `axes_manager` can also be modified by the
# [EBSD](../reference.rst#kikuchipy.signals.EBSD) class methods
# [set_experimental_parameters()](../reference.rst#kikuchipy.signals.EBSD.set_experimental_parameters),
# [set_phase_parameters()](../reference.rst#kikuchipy.signals.EBSD.set_phase_parameters),
# [set_scan_calibration()](../reference.rst#kikuchipy.signals.EBSD.set_scan_calibration) and
# [set_detector_calibration()](../reference.rst#kikuchipy.signals.EBSD.set_detector_calibration).
# For example, to set or change the accelerating voltage, horizontal pattern
# centre coordinate and static background pattern (stored as a `numpy.ndarray`):
# In[ ]:
s.set_experimental_parameters(
beam_energy=15,
xpc=0.5073,
static_background=plt.imread(
datadir + "nordif/Background acquisition pattern.bmp"
)
)
# In addition to the HyperSpy provided `metadata`, `original_metadata` and
# `axes_manager` properties, kikuchipy tries to read a [CrystalMap](https://orix.readthedocs.io/en/stable/reference.html#crystalmap) object with indexing results into a
# `xmap` property and an
# [EBSDDetector](../reference.rst#kikuchipy.detectors.EBSDDetector)
# object into a `detector` property:
# In[ ]:
s.xmap # This is empty unless it is set
# In[ ]:
s.detector
# ### From a NumPy array
#
# An `EBSD` or `EBSDMasterPattern` signal can also be created directly from a
# `numpy.ndarray`. To create a data set of (60 x 60) pixel patterns in a
# (10 x 20) grid, i.e. 10 and 20 patterns in the horizontal and vertical scan
# directions respectively, of random intensities:
# In[ ]:
s_np = kp.signals.EBSD(np.random.random((20, 10, 60, 60)))
s_np
# ### From a Dask array
#
# When processing large data sets, it is useful to load data lazily with the
# Dask library. This can be done upon reading patterns [from a file](#From-a-file)
# by setting `lazy=True` when using the `load()` function, or directly from a
# `dask.array.Array`:
# In[ ]:
s_da = kp.signals.LazyEBSD(
da.random.random((20, 10, 60, 60), chunks=(2, 10, 60, 60))
)
print(s_da)
s_da.data
# ### From a HyperSpy signal
#
# HyperSpy provides the method
# [set_signal_type()](http://hyperspy.org/hyperspy-doc/current/api/hyperspy.signal.html#hyperspy.signal.BaseSignal.set_signal_type)
# to change between [BaseSignal](http://hyperspy.org/hyperspy-doc/current/api/hyperspy.signal.html#hyperspy.signal.BaseSignal) subclasses, of which
# `EBSD`, `EBSDMasterPattern` and
# [VirtualBSEImage](../reference.rst#kikuchipy.signals.VirtualBSEImage) are three. To
# get one of these objects from a [HyperSpy Signal2D](http://hyperspy.org/hyperspy-doc/current/api/hyperspy._signals.signal2d.html?highlight=signal2d#hyperspy._signals.signal2d.Signal2D)
# object:
# In[ ]:
s_hs = hs.signals.Signal2D(np.random.random((20, 10, 60, 60)))
s_hs
# In[ ]:
s_hs.set_signal_type("EBSD")
s_hs
# In[ ]:
s_hs.set_signal_type("VirtualBSEImage")
s_hs
# In[ ]:
s_hs.set_signal_type("EBSDMasterPattern")
s_hs
# ---
# ## Save patterns
#
# To save experimental EBSD patterns to file use the
# [save()](../reference.rst#kikuchipy.signals.EBSD.save) method. For example, to save
# an `EBSD` signal in an HDF5 file, with file name `patterns.h5`, in our default
# [h5ebsd format](#h5ebsd):
# In[ ]:
temp_dir = tempfile.mkdtemp() + "/"
s.save(temp_dir + "patterns")
#
#
# Warning
#
# If we want to overwrite an existing file:
#
# ```python
# s.save("patterns.h5", overwrite=True)
# ```
#
#
# If we want to save patterns in NORDIF's binary .dat format instead:
# In[ ]:
s.save(temp_dir + "patterns.dat")
# To save an `EBSDMasterPattern` to an HDF5 file, we use the [save method inherited from HyperSpy](http://hyperspy.org/hyperspy-doc/current/api/hyperspy.signal.html#hyperspy.signal.BaseSignal.save)
# to write to [their HDF5 specification](http://hyperspy.org/hyperspy-doc/current/user_guide/io.html#hspy-hyperspy-s-hdf5-specification):
# In[ ]:
s_hs.save(temp_dir + "master_pattern.hspy")
s_hs
# These master patterns can then be read into an `EBSDMasterPattern` signal again
# via HyperSpy's
# [load()](http://hyperspy.org/hyperspy-doc/current/api/hyperspy.io.html#hyperspy.io.load):
# In[ ]:
s_mp2 = hs.load(
temp_dir + "master_pattern.hspy", signal_type="EBSDMasterPattern"
)
s_mp2
#
#
# Note
#
# To save results from statistical decomposition (machine learning) of patterns to
# file see the section
# [Saving and loading results](http://hyperspy.org/hyperspy-doc/current/user_guide/mva.html#saving-and-loading-results)
# in HyperSpy's user guide. Note that the file extension `.hspy` must be used upon
# saving, `s.save('patterns.hspy')`, as the default extension in kikuchipy, `.h5`,
# yields a kikuchipy h5ebsd file where the decomposition results aren't saved. The
# saved patterns can then be reloaded using HyperSpy's `load()` function and
# passing the `signal_type="EBSD"` parameter
# [as explained above](#From-a-HyperSpy-signal).
#
#
# ---
# ## Supported EBSD formats
#
# Currently, kikuchipy has readers and writers for the following formats:
#
# | Format | Read | Write |
# | ------------------------------------------------------------------- | ---- | ----- |
# | [EMsoft simulated EBSD HDF5](#EMsoft-simulated-EBSD-HDF5) | Yes | No |
# | [EMsoft EBSD master pattern HDF5](#EMsoft-EBSD-master-pattern-HDF5) | Yes | No |
# | [Bruker Nano h5ebsd](#h5ebsd) | Yes | No |
# | [EDAX TSL h5ebsd](#h5ebsd) | Yes | No |
# | [kikuchipy h5ebsd](#h5ebsd) | Yes | Yes |
# | [NORDIF binary](#NORDIF-binary) | Yes | Yes |
# | [NORDIF calibration patterns](#NORDIF-calibration-patterns) | Yes | No |
# | [Oxford Instruments binary](#Oxford-Instruments-binary) | Yes | No |
#
#
#
# Note
#
# If you want to process your patterns with kikuchipy, but use an unsupported EBSD
# vendor software, or if you want to write your processed patterns to a vendor
# format that does not support writing, please request this feature in our
# [issue tracker](https://github.com/pyxem/kikuchipy/issues).
#
#
# ### EMsoft simulated EBSD HDF5
#
# Dynamically simulated EBSD patterns returned by EMsoft's `EMEBSD.f90` program
# as HDF5 files can be read as an `EBSD` signal:
# In[ ]:
emsoft_ebsd = "emsoft_ebsd/simulated_ebsd.h5" # Dummy data set
s_sim = kp.load(filename=datadir + emsoft_ebsd)
s_sim
# Here, the EMsoft simulated EBSD
# [file_reader()](../reference.rst#kikuchipy.io.plugins.emsoft_ebsd.file_reader) is
# called, which takes the optional argument `scan_size`. Passing
# `scan_size=(2, 5)` will reshape the pattern data shape from `(10, 10, 10)` to
# `(2, 5, 10, 10)`:
# In[ ]:
s_sim2 = kp.load(filename=datadir + emsoft_ebsd, scan_size=(2, 5))
print(s_sim2)
print(s_sim2.data.shape)
# Simulated EBSD patterns can be written to the
# [kikuchipy h5ebsd format](#h5ebsd), the [NORDIF binary format](#NORDIF-binary),
# or to HDF5 files using HyperSpy's HDF5 specification
# [as explained above](#Save-patterns).
# ### EMsoft EBSD master pattern HDF5
#
# Master patterns returned by EMsoft's `EMEBSDmaster.f90` program as HDF5 files
# can be read as an `EBSDMasterPattern` signal:
# In[ ]:
s_mp = kp.load(filename=datadir + emsoft_master_pattern)
print(s_mp)
print(s_mp.projection)
print(s_mp.hemisphere)
print(s_mp.phase)
# Here, the EMsoft EBSD master pattern
# [file_reader()](../reference.rst#kikuchipy.io.plugins.emsoft_ebsd_master_pattern.file_reader)
# is called, which takes the optional arguments `projection`, `hemisphere` and
# `energy`. The stereographic projection is read by default. Passing
# `projection="lambert"` will read the square Lambert projection instead. The
# northern hemisphere is read by default. Passing `hemisphere="south"` or
# `hemisphere="both"` will read the southern hemisphere projection or both,
# respectively. Master patterns for all beam energies are read by default. Passing
# `energy=(10, 20)` or `energy=15` will read the master pattern(s) with beam
# energies from 10 to 20 keV, or just 15 keV, respectively:
# In[ ]:
s_mp = kp.load(
datadir + emsoft_master_pattern,
projection="lambert",
hemisphere="both",
energy=20
)
print(s_mp)
print(s_mp.projection)
print(s_mp.hemisphere)
# Master patterns can be written to HDF5 files using HyperSpy's HDF5 specification
# [as explained above](#Save-patterns).
#
# See Jackson et al. (2019) for a
# hands-on tutorial explaining how to simulate these patterns with EMsoft, and
# Callahan & De Graef (2013) for
# details of the underlying theory.
# ### h5ebsd
#
# The h5ebsd format
# Jackson et al. (2014) is based on the
# [HDF5 open standard](http://www.hdfgroup.org/HDF5) (Hierarchical Data Format
# version 5). HDF5 files can be read and edited using e.g. the HDF Group's reader
# [HDFView](https://www.hdfgroup.org/downloads/hdfview) or the Python package
# used by kikuchipy, [h5py](http://docs.h5py.org/en/stable). Upon loading an HDF5
# file with extension .h5, .hdf5, or .h5ebsd, the correct reader is determined
# from the file. Supported h5ebsd formats are listed in the
# [table above](#Supported-EBSD-formats).
#
# If an h5ebsd file contains multiple scans, as many scans as desirable can be
# read from the file. For example, if the file contains two scans with names
# ``My awes0m4 Xcan #! with a long title`` and ``Scan 2``:
# In[ ]:
kikuchipy_ebsd = "kikuchipy_h5ebsd/patterns.h5"
s_awsm, s2 = kp.load(
filename=datadir + kikuchipy_ebsd,
scan_group_names=["My awes0m4 Xcan #! with a long title", "Scan 2"]
)
print(s_awsm)
print(s2)
# Here, the h5ebsd
# [file_reader()](../reference.rst#kikuchipy.io.plugins.h5ebsd.file_reader) is
# called. If only `Scan 2` is to be read, `scan_group_names="Scan 2"` can be
# passed:
# In[ ]:
s2 = kp.load(filename=datadir + kikuchipy_ebsd, scan_group_names="Scan 2")
s2
# The `scan_group_names` parameter is unnecessary if only the first scan in the
# file is to be read, since reading only the first scan in the file is the default
# behaviour.
#
# So far, only [saving patterns](#Save-patterns) to kikuchipy's own h5ebsd format
# is supported. It is possible to write a new scan with a scan name `Scan x`,
# where `x` is an integer, to an existing, but closed, h5ebsd file in the
# kikuchipy format, e.g. one containing only `Scan 1`, by passing:
# In[ ]:
new_file = "patterns_new.h5"
s2.save(temp_dir + new_file, scan_number=1)
s_awsm.save(filename=temp_dir + new_file, add_scan=True, scan_number=2)
s2_new, s_awsm_new = kp.load(
filename=temp_dir + new_file, scan_group_names=["Scan 1", "Scan 2"]
)
print(s2_new)
print(s_awsm_new)
# Here, the h5ebsd
# [file_writer()](../reference.rst#kikuchipy.io.plugins.h5ebsd.file_writer) is
# called.
#
#
#
# Note
#
# The `EBSD.xmap` and `EBSD.detector` properties are so far not written to this
# file format.
#
#
# ### NORDIF binary
#
# Patterns acquired using NORDIF's acquisition software are stored in a binary
# file usually named "Pattern.dat". Scan information is stored in a separate text
# file usually named "Setting.txt", and both files usually reside in the same
# directory. If this is the case, the patterns can be loaded by passing the file
# name as the only parameter. If this is not the case, the setting file can be
# passed upon loading:
# In[ ]:
s_nordif = kp.load(
filename=datadir + nordif_ebsd, setting_file=datadir + "nordif/Setting.txt"
)
s_nordif
# Here, the NORDIF
# [file_reader()](../reference.rst#kikuchipy.io.plugins.nordif.file_reader) is
# called. If the scan information, i.e. scan and pattern size, in the setting file
# is incorrect or the setting file is not available, patterns can be loaded by
# passing:
# In[ ]:
s_nordif = kp.load(
filename=datadir + nordif_ebsd, scan_size=(1, 9), pattern_size=(60, 60)
)
s_nordif
# If a static background pattern named "Background acquisition pattern.bmp" is
# stored in the same directory as the pattern file, this is stored in ``metadata``
# upon loading.
#
# Patterns can also be [saved to a NORDIF binary file](#Save-patterns), upon
# which the NORDIF
# [file_writer()](../reference.rst#kikuchipy.io.plugins.nordif.file_writer) is
# called. Note, however, that so far no new setting file, background pattern, or
# calibration patterns are created upon saving.
# ### NORDIF calibration patterns
#
# NORDIF calibration patterns in bitmap format named "Calibration (x,y).bmp",
# where "x" and "y" correspond to coordinates listed in the NORDIF setting file,
# usually named "Setting.txt", can be loaded
# In[ ]:
s_nordif_cal = kp.load(filename=datadir + "nordif/Setting.txt")
s_nordif_cal
# Here, the NORDIF calibration patterns
# [file_reader()](../reference.rst#kikuchipy.io.plugins.nordif_calibration_patterns.file_reader) is
# called. Lazy loading is not supported for this reader, thus the `lazy` parameter
# is not used.
#
# If a static background pattern named "Background calibration pattern.bmp" is
# stored in the same directory as the pattern file, this is stored in ``metadata``
# upon loading.
# ### Oxford Instruments binary
#
# Uncompressed patterns stored in the Oxford Instruments binary .ebsp file format,
# with intensities as 8-bit or 16-bit unsigned integer, can be read
# In[ ]:
oxford_binary_path = datadir + "oxford_binary/"
s_oxford = kp.load(filename=oxford_binary_path + "patterns.ebsp")
s_oxford
# Here, the Oxford Instruments binary
# [file_reader()](../reference.rst#kikuchipy.io.plugins.oxford_binary.file_reader) is
# called.
#
# 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. 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. All these are 1D arrays.
# In[ ]:
s_oxford.original_metadata
# If the beam positions aren't present in the file, the returned signal will have
# a single navigation dimension the size of the number of patterns.
#
# Files with only the non-indexed patterns can also be read. The returned signal
# will then have a single navigation dimension the size of the number of patterns.
# The flattened index into the 2D navigation map mentioned above can be useful to
# determine the location of each non-indexed pattern.
# ## From kikuchipy into other software
#
# Patterns saved in the [h5ebsd format](#h5ebsd) can be read by the
# dictionary indexing and related routines in
# [EMsoft](http://vbff.materials.cmu.edu/EMsoft) using the `EMEBSD` reader. Those
# routines in EMsoft also have a `NORDIF` reader.
#
# Patterns saved in the [h5ebsd format](#h5ebsd) can of course be read
# in Python like any other HDF5 data set:
# In[ ]:
import h5py
with h5py.File(datadir + kikuchipy_ebsd, mode="r") as f:
dset = f['Scan 2/EBSD/Data/patterns']
print(dset)
patterns = dset[()]
print(patterns.shape)
plt.figure()
plt.imshow(patterns[0], cmap="gray")
plt.axis("off")
# ## Load and save virtual BSE images
#
# One or more virtual backscatter electron (BSE) images in a
# [VirtualBSEImage](../reference.rst#kikuchipy.signals.VirtualBSEImage) signal can be
# read and written to file using one of HyperSpy's many readers and writers. If
# they are only to be used internally in HyperSpy, they can be written to and read
# back from HyperSpy's HDF5 specification
# [as explained above for EBSD master patterns](#Save-patterns).
#
# 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](http://hyperspy.org/hyperspy-doc/current/user_guide/io.html#images).
# If we wanted to write them as a stack of TIFF images:
# In[ ]:
# Get virtual image from generator
vbse_gen = kp.generators.VirtualBSEGenerator(s)
print(vbse_gen)
print(vbse_gen.grid_shape)
vbse = vbse_gen.get_images_from_grid()
print(vbse)
# In[ ]:
vbse.rescale_intensity()
vbse.unfold_navigation_space() # 1D navigation space required for TIFF
vbse
# In[ ]:
vbse_fname = "vbse.tif"
vbse.save(temp_dir + vbse_fname) # Easily read into e.g. ImageJ
# We can also write them to e.g. `png` or `bmp` files with `Matplotlib`:
# In[ ]:
nav_size = vbse.axes_manager.navigation_size
_ = [
plt.imsave(temp_dir + f"vbse{i}.png", vbse.inav[i].data)
for i in range(nav_size)
]
# Read the TIFF stack back into a `VirtualBSEImage` signal:
# In[ ]:
vbse2 = hs.load(temp_dir + vbse_fname, signal_type="VirtualBSEImage")
vbse2
# In[ ]:
# Remove files written to disk in this user guide
import os
for file in [
"patterns.h5",
"patterns_new.h5",
"patterns.dat",
"master_pattern.hspy",
"vbse.tif",
]:
os.remove(temp_dir + file)
for i in range(25):
os.remove(temp_dir + f"vbse{i}.png")
os.rmdir(temp_dir)