#!/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.
# # Reference frames
#
# ## Sample-detector geometry
#
# The figure below shows the [sample reference frame](#detector-sample-geometry)
# and the [detector reference frame](#detector-coordinates) used in kikuchipy, all
# of which are right handed. In short, the sample reference frame is the one used
# by EDAX TSL, RD-TD-ND, while the pattern center is defined as in the Bruker
# software.
#
# In **(a)** (lower left), a schematic of the microscope chamber shows the
# definition of the sample reference frame, RD-TD-ND. The
# $x_{euler}-y_{euler}-z_{euler}$ crystal reference frame used by Bruker is shown
# for reference. An EBSD pattern on the detector screen is viewed from *behind*
# the screen *towards* the sample. The inset **(b)** shows the detector and sample
# normals viewed from above, and the azimuthal angle $\omega$ which is defined as
# the sample tilt angle round the RD axis. **(c)** shows how the EBSD map appears
# within the data collection software, with the sample reference frame and the
# scanning reference frame, $x_{scan}-y_{scan}-z_{scan}$, attached. Note the
# $180^{\circ}$ rotation of the map about ND. **(d)** shows the relationship
# between the sample reference frame and the detector reference frame,
# $x_{detector}-y_{detector}-z_{detector}$, with the projection center
# highlighted. The detector tilt $\theta$ and sample tilt $\sigma$, in this case
# $10^{\circ}$ and $70^{\circ}$, respectively, are also shown.
#
# The above figure shows the EBSD pattern in the
# [sample reference frame figure](#detector-sample-geometry) (a) as viewed from
# behind the screen towards the sample (left), with the detector reference frame
# the same as in (d) with its origin (0, 0) in the upper left pixel. The detector
# pixels' gnomonic coordinates can be described with a calibrated projection
# center (PC) (right), with the gnomonic reference frame origin (0, 0) in ($PC_x,
# PC_y$). The circles indicate the angular distance from the PC in steps of
# $10^{\circ}$.
# ## The EBSD detector
#
# All relevant parameters for the sample-detector geometry are stored in an
# [kikuchipy.detectors.EBSDDetector](../reference.rst#kikuchipy.detectors.EBSDDetector)
# instance. Let's first import necessary libraries and a small Nickel EBSD test
# data set
# In[ ]:
# Exchange inline for notebook or qt5 (from pyqt) for interactive plotting
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
import numpy as np
import kikuchipy as kp
s = kp.data.nickel_ebsd_small() # Use kp.load("data.h5") to load your own data
s
# Then we can define a detector with the same parameters as the one used to
# acquire the small Nickel data set
# In[ ]:
detector = kp.detectors.EBSDDetector(
shape=s.axes_manager.signal_shape[::-1],
pc=[0.421, 0.779, 0.505],
convention="tsl",
px_size=70, # microns
binning=8,
tilt=0,
sample_tilt=70
)
detector
# In[ ]:
detector.pc_tsl()
# The projection/pattern center (PC) is stored internally in the Bruker
# convention:
#
# - $PC_x$ is measured from the left border of the detector in fractions of detector
# width.
#
# - $PC_y$ is measured from the top border of the detector in fractions of detector
# height.
#
# - $PC_z$ is the distance from the detector scintillator to the sample divided by
# pattern height.
#
# Above, the PC was passed in the EDAX TSL convention. Passing the PC in the
# Bruker, Oxford, or EMsoft v4 or v5 convention is also supported. The definitions
# of the conventions are given in the
# [EBSDDetector](../reference.rst#kikuchipy.detectors.EBSDDetector) API reference,
# together with the conversion from PC coordinates in the TSL, Oxford, or EMsoft
# conventions to PC coordinates in the Bruker convention.
#
# The PC coordinates in the TSL, Oxford, or EMsoft conventions can be retreived
# via [EBSDDetector.pc_tsl()](../reference.rst#kikuchipy.detectors.EBSDDetector.pc_tsl),
# [EBSDDetector.pc_oxford()](../reference.rst#kikuchipy.detectors.EBSDDetector.pc_oxford),
# and [EBSDDetector.pc_emsoft()](../reference.rst#kikuchipy.detectors.EBSDDetector.pc_emsoft),
# respectively. The latter requires the unbinned detector pixel size in microns
# and the detector binning to be given upon initialization.
# In[ ]:
detector.pc_emsoft()
# The detector can be plotted to show whether the average PC is placed as
# expected using
# [EBSDDetector.plot()](../reference.rst#kikuchipy.detectors.EBSDDetector.plot) (see
# its docstring for a complete explanation of its parameters)
# In[ ]:
detector.plot(pattern=s.inav[0, 0].data)
# This will produce a figure similar to the left panel in the
# [detector coordinates figure](#detector-coordinates) above, without the arrows
# and colored labels.
#
# Multiple PCs with a 1D or 2D navigation shape can be passed to the `pc`
# parameter upon initialization, or can be set directly. This gives the detector
# a navigation shape (not to be confused with the detector shape) and a navigation
# dimension (maximum of two)
# In[ ]:
detector.pc = np.ones([3, 4, 3]) * [0.421, 0.779, 0.505]
detector.navigation_shape
# In[ ]:
detector.navigation_dimension
# In[ ]:
detector.pc = detector.pc[0, 0]
detector.navigation_shape
#
#
# Note
#
# The offset and scale of HyperSpy’s `axes_manager` is fixed for a signal,
# meaning that we cannot let the PC vary with scan position if we want to
# calibrate the EBSD detector via the `axes_manager`. The need for a varying
# PC was the main motivation behind the `EBSDDetector` class.
#
#
# The right panel in the [detector coordinates figure](#detector-coordinates)
# above shows the detector plotted in the gnomonic projection using
# [EBSDDetector.plot()](../reference.rst#kikuchipy.detectors.EBSDDetector.plot). We
# assign 2D gnomonic coordinates ($x_g, y_g$) in a gnomonic projection plane
# parallel to the detector screen to a 3D point ($x_d, y_d, z_d$) in the detector
# frame as
#
# $$
# x_g = \frac{x_d}{z_d}, \qquad y_g = \frac{y_d}{z_d}.
# $$
#
# The detector bounds and pixel scale in this projection, per navigation point,
# are stored with the detector
# In[ ]:
detector.bounds
# In[ ]:
detector.gnomonic_bounds
# In[ ]:
detector.x_range
# In[ ]:
detector.r_max # Largest radial distance to PC
# ## Projection center calibration
# The gnomonic projection (pattern) center (PC) of an EBSD detector can be
# estimated by the "moving-screen" technique
# Hjelen et al.. The technique relies
# on the assumption that the beam normal, shown in the
# [top figure (d)](#detector-sample-geometry) above, is normal to the detector
# screen as well as the incoming electron beam, and will therefore intersect the
# screen at a position independent of the detector distance (DD). To find this
# position, we need two EBSD patterns acquired with a stationary beam but with a
# known difference $\Delta z$ in DD, say 5 mm.
#
# First, the goal is to find the pattern position which does not shift between the
# two camera positions, ($PC_x$, $PC_y$). This point can be estimated in fractions
# of screen width and height, respectively, by selecting the same pattern features
# in both patterns. The two points of each pattern feature can then be used to
# form a straight line, and two or more such lines should intersect at ($PC_x$,
# $PC_y$).
#
# Second, the DD ($PC_z$) can be estimated from the same points. After finding
# the distances $L_{in}$ and $L_{out}$ between two points (features) in both
# patterns (in = operating position, out = 5 mm from operating position), the DD
# can be found from the relation
#
# $$
# \mathrm{DD} = \frac{\Delta z}{L_{out}/L_{in} - 1},
# $$
#
# where DD is given in the same unit as the known camera distance difference. If
# also the detector pixel size $\delta$ is known (e.g. 46 mm / 508 px), $PC_z$ can
# be given in the fraction of the detector screen height
#
# $$
# PC_z = \frac{\mathrm{DD}}{N_r \delta b},
# $$
#
# where $N_r$ is the number of detector rows and $b$ is the binning factor.
#
# Let's find an estimate of the PC from two single crystal Silicon EBSD patterns,
# which are included in the [kikuchipy.data](../reference.rst#data) module
# In[ ]:
s_in = kp.data.silicon_ebsd_moving_screen_in(allow_download=True)
s_in.remove_static_background()
s_in.remove_dynamic_background()
s_out5mm = kp.data.silicon_ebsd_moving_screen_out5mm(allow_download=True)
s_out5mm.remove_static_background()
s_out5mm.remove_dynamic_background()
# As a first approximation, we can find the detector pixel positions of the same
# features in both patterns by plotting them and noting the upper right
# coordianates provided by Matplotlib when plotting with an interactive backend
# (e.g. qt5 or notebook) and hovering over image pixels
# In[ ]:
fig, ax = plt.subplots(ncols=2, sharex=True, sharey=True, figsize=(20, 10))
ax[0].imshow(s_in.data, cmap="gray")
_ = ax[1].imshow(s_out5mm.data, cmap="gray")
# For this example we choose the positions of three zone axes. The PC calibration
# is performed by creating an instance of the
# [PCCalibrationMovingScreen](../reference.rst#kikuchipy.detectors.PCCalibrationMovingScreen)
# class
# In[ ]:
cal = kp.detectors.PCCalibrationMovingScreen(
pattern_in=s_in.data,
pattern_out=s_out5mm.data,
points_in=[(109, 131), (390, 139), (246, 232)],
points_out=[(77, 146), (424, 156), (246, 269)],
delta_z=5,
px_size=None, # Default
convention="tsl", # Default
)
cal
# We see that ($PC_x$, $PC_y$) = (0.5123, 0.8606), while DD = 21.7 mm. To get
# $PC_z$ in fractions of detector height, we have to provide the detector pixel
# size $\delta$ upon initialization, or set it directly and recalculate the PC
# In[ ]:
cal.px_size = 46 / 508 # mm/px
cal
# We can visualize the estimation by using the (opinionated) convenience method
# [PCCalibrationMovingScreen.plot()](../reference.rst#kikuchipy.detectors.PCCalibrationMovingScreen.plot)
# In[ ]:
cal.plot()
# As expected, the three lines in the right figure meet at a more or less the same
# position. We can replot the three images and zoom in on the PC to see how close
# they are to each other. We will use two standard deviations of all $PC_x$
# estimates as the axis limits (scaled with pattern shape)
# In[ ]:
# PCy defined from top to bottom, otherwise "tsl", defined from bottom to top
cal.convention = "bruker"
pcx, pcy, _ = cal.pc
two_std = 2 * np.std(cal.pcx_all, axis=0)
fig, ax = cal.plot(return_fig_ax=True)
ax[2].set_xlim([cal.ncols * (pcx - two_std), cal.ncols * (pcx + two_std)])
_ = ax[2].set_ylim([cal.nrows * ( pcy - two_std), cal.nrows * (pcy + two_std)])
# Finally, we can use this PC estimate along with the orientation of the Si
# crystal, as determined by Hough indexing with a commercial software, to see how
# good the estimate is, by performing a
# [geometrical EBSD simulation](geometrical_ebsd_simulations.ipynb) of positions of
# Kikuchi band centres and zone axes from the five $\{hkl\}$ families $\{111\}$,
# $\{200\}$, $\{220\}$, $\{222\}$, and $\{311\}$
# In[ ]:
from diffsims.crystallography import ReciprocalLatticePoint
from orix import crystal_map, quaternion
# Create simulation generator from a detector and crystal phase and orientation
detector = kp.detectors.EBSDDetector(
shape=cal.shape, pc=cal.pc, sample_tilt=70, convention=cal.convention
)
phase = crystal_map.Phase(space_group=227)
r = quaternion.Rotation.from_euler(np.deg2rad([133.3, 88.7, 177.8]))
simgen = kp.generators.EBSDSimulationGenerator(
detector=detector, phase=phase, rotations=r
)
simgen.navigation_shape = s_in.axes_manager.navigation_shape
# Specify which plane families for which to simulate bands and zone axes
rlp = ReciprocalLatticePoint(
phase=phase, hkl=[[1, 1, 1], [2, 0, 0], [2, 2, 0], [2, 2, 2], [3, 1, 1]]
).symmetrise() # Symmetrise to get all symmetrically equivalent planes
simgeo = simgen.geometrical_simulation(rlp)
#del s_in.metadata.Markers # Uncomment this if we want to re-add markers
s_in.add_marker(
marker=simgeo.as_markers(),
plot_marker=False,
permanent=True
)
s_in.plot(navigator=None, colorbar=False, axes_off=True, title="")
# The PC is not perfect, but the estimate might be good enough for a further PC
# and/or orientation refinement.