{ "cells": [ { "cell_type": "markdown", "metadata": { "nbsphinx": "hidden", "pycharm": { "name": "#%% md\n" } }, "source": [ "This notebook is part of the `kikuchipy` documentation https://kikuchipy.org.\n", "Links to the documentation won't work from the notebook." ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "# Reference frames\n", "\n", "In this tutorial, we will learn of the reference frames most important to EBSD,\n", "which conventions are used in kikuchipy, and how these relate to conventions in\n", "the other softwares Bruker, EDAX TSL, EMsoft, and Oxford Instruments. We will\n", "also test the conversions between these conventions by indexing simulated\n", "patterns using Hough indexing (HI) from PyEBSDIndex.\n", "\n", "## Detector-sample geometry\n", "\n", "The figure below shows the [sample reference frame](#detector-sample-geometry)\n", "and the [detector reference frame](#detector-coordinates) used in kikuchipy, all\n", "of which are right handed. In short, the sample reference frame ($X_s$, $Y_s$,\n", "$Z_s$) is the one used by EDAX TSL, (RD, TD, ND), while the pattern center ($PC_x$,\n", "$PC_y$, $PC_z$) is the one used by Bruker Nano, ($PC_x$, $PC_y$, $DD$)." ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "In **a**, the electron beam interacts with the sample in the source point, and\n", "the shortest distance from this point to the detector is called the\n", "projection/pattern center (PC). A part of the Kikuchi sphere resulting from the\n", "beam-sample interaction is projected onto the flat detector in the gnomonic\n", "projection, the EBSD pattern (EBSP). A gnomonic coordinate system $CS_g$, ($X_g$,\n", "$Y_g$, $Z_g$) with (0, 0, 0) in the PC is defined for the detector plane. We\n", "also define a detector coordinate system $CS_d$, ($X_d$, $Y_d$, $Z_d$), for the\n", "detector plane, with (0, 0, 0) in the upper left corner. The projection center\n", "coordinates ($PC_x$, $PC_y$, $PC_z$) are defined in this detector coordinate\n", "system:\n", "\n", "- $PC_x$ is measured from the left border of the detector in fractions of\n", " detector width.\n", "- $PC_y$ is measured from the top border of the detector in fractions of\n", " detector height.\n", "- $PC_z$ is the distance from the detector scintillator to the sample divided by\n", " pattern height.\n", "\n", "Orientations are defined in the Bunge convention with respect to the sample\n", "coordinate system $CS_s$, ($X_s$, $Y_s$, $Z_s$). The detector and sample viewed along\n", "the microscope $X$ axis are shown in **b**, with the three coordinate systems\n", "and the PC also shown. The scanned map is shown in **c**. Note the orientation\n", "of $CS_s$ and the sample \"Top\": the map is scanned from the bottom of the sample\n", "and upwards. Three tilt angles are defined: the sample tilt $\\sigma$ shown in\n", "**a** and **b**; the detector tilt $\\theta$ shown in **b**; the azimuthal angle\n", "$\\omega$ which is defined as the sample tilt angle around the $X_s$ axis, shown\n", "in a top view of the detector and sample along the microscope $Z$ axis in **d**." ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above figure shows the EBSP in the\n", "[sample reference frame figure](#detector-sample-geometry) **a** as viewed from\n", "behind the screen towards the sample in (left) the gnomonic coordinate system\n", "with its origin (0, 0) in the PC, and in (right) the detector coodinate system\n", "with (0, 0) in the upper left pixel. The circles indicate the angular distance\n", "from the PC in steps of $10^{\\circ}$." ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## The EBSD detector\n", "\n", "All relevant parameters for the detector-sample geometry are stored in an\n", "[kikuchipy.detectors.EBSDDetector](../reference/generated/kikuchipy.detectors.EBSDDetector.rst)\n", "instance. Let's first import necessary libraries and a small Nickel EBSD test\n", "data set" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Exchange inline for notebook or qt5 (from pyqt) for interactive plotting\n", "%matplotlib inline\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "import kikuchipy as kp\n", "from orix.quaternion import Orientation, Rotation\n", "from orix.vector import Vector3d\n", "\n", "\n", "plt.rcParams[\"figure.figsize\"] = (15, 5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Use kp.load(\"data.h5\") to load your own data\n", "s = kp.data.nickel_ebsd_small()\n", "s" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Then we can define a detector with the same parameters as the one used to\n", "acquire the small Nickel data set" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det = kp.detectors.EBSDDetector(\n", " shape=s.axes_manager.signal_shape[::-1],\n", " pc=[0.4221, 0.2179, 0.4954],\n", " px_size=70, # Microns\n", " binning=8,\n", " tilt=0,\n", " sample_tilt=70,\n", ")\n", "det" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.pc_tsl()" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Above, the PC was passed in the Bruker convention. Passing the PC in the EDAX\n", "TSL, Oxford, or EMsoft convention is also supported. The definitions of the\n", "conventions are given in the\n", "[EBSDDetector](../reference/generated/kikuchipy.detectors.EBSDDetector.rst) API\n", "reference, together with the conversion from PC coordinates in the EDAX TSL,\n", "Oxford, or EMsoft conventions to PC coordinates in the Bruker convention.\n", "\n", "The PC coordinates in the EDAX TSL, Oxford, or EMsoft conventions can be\n", "retreived via\n", "[EBSDDetector.pc_tsl()](../reference/generated/kikuchipy.detectors.EBSDDetector.pc_tsl.rst),\n", "[EBSDDetector.pc_oxford()](../reference/generated/kikuchipy.detectors.EBSDDetector.pc_oxford.rst),\n", "and [EBSDDetector.pc_emsoft()](../reference/generated/kikuchipy.detectors.EBSDDetector.pc_emsoft.rst),\n", "respectively. The latter requires the unbinned detector pixel size in microns\n", "and the detector binning to be given upon initialization." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.pc_emsoft()" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "The detector can be plotted to show whether the average PC is placed as\n", "expected using\n", "[EBSDDetector.plot()](../reference/generated/kikuchipy.detectors.EBSDDetector.plot.rst)\n", "(see its docstring for a complete explanation of its parameters)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "det.plot(pattern=s.inav[0, 0].data)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "This will produce a figure similar to the right panel in the\n", "[detector coordinates figure](#detector-coordinates) above, without the arrows\n", "and colored labels.\n", "\n", "Multiple PCs with a 1D or 2D navigation shape can be passed to the `pc`\n", "parameter upon initialization, or can be set directly. This gives the detector\n", "a navigation shape (not to be confused with the detector shape) and a navigation\n", "dimension (maximum of two)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.pc = np.ones([3, 4, 3]) * det.pc\n", "det.navigation_shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.navigation_dimension" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "det.navigation_size" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.pc = det.pc[0, 0]\n", "det.navigation_shape" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "
\n", "\n", "Note\n", "\n", "The offset and scale of HyperSpy’s `axes_manager` is fixed for a signal,\n", "meaning that we cannot let the PC vary with scan position if we want to\n", "calibrate the EBSD detector via the `axes_manager`. The need for a varying\n", "PC was the main motivation behind the `EBSDDetector` class.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "The left panel in the [detector coordinates figure](#detector-coordinates)\n", "above shows the detector plotted in the gnomonic projection using\n", "[EBSDDetector.plot()](../reference/generated/kikuchipy.detectors.EBSDDetector.plot.rst).\n", "The 2D gnomonic coordinates ($x_g, y_g$) in $CS_g$ are defined in $CS_d$ are\n", "\n", "$$\n", "x_g = \\frac{x_d}{z_d}, \\qquad y_g = \\frac{y_d}{z_d}.\n", "$$\n", "\n", "The detector bounds and pixel scale in this projection, per navigation point,\n", "are stored with the detector" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.bounds" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.gnomonic_bounds" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.x_range" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "det.r_max # Largest radial distance to PC" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Other software's reference frames\n", "\n", "Other software use other reference frames. To aid in the conversion of a set of\n", "observations, typically orientations, between softwares, the reference frames\n", "used in other softwares are also shown here, represented to the best of the\n", "contributors understanding.\n", "\n", "
\n", "\n", "Warning\n", "\n", "Reference frames used in other softwares given here are based on instruction\n", "manuals from the internet. Use with care, and double check whenever possible.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EMsoft" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Bruker" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### EDAX TSL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Oxford Instruments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test PC conventions with PyEBSDIndex\n", "\n", "We can test the PC conventions using Hough indexing in PyEBSDIndex. We will use\n", "ten simulated nickel patterns with a fixed PC and random orientations. We check\n", "for consistency by passing the PC in all the conventions described\n", "above when indexing, and making sure that the indexed orientations are rotated\n", "so that they are defined with respect to the same sample reference frame (the\n", "one used in kikuchipy, EDAX TSL and EMsoft).\n", "\n", "
\n", "\n", "Note\n", "\n", "PyEBSDIndex is an optional dependency of kikuchipy, and can be installed with both `pip` and `conda` (from `conda-forge`).\n", "To install PyEBSDIndex, see their [installation instructions](https://pyebsdindex.readthedocs.io/en/latest/installation.html).\n", "\n", "PyEBSDIndex supports indexing face centered and body centered cubic (FCC and BCC) materials.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load master pattern in the square Lambert projection" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mp = kp.data.nickel_ebsd_master_pattern_small(\n", " projection=\"lambert\", hemisphere=\"upper\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define a rectangular EBSD detector to project simulated patterns onto" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "det2 = kp.detectors.EBSDDetector(\n", " (100, 120), pc=(0.4, 0.2, 0.5), sample_tilt=70\n", ")\n", "det2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create ten random orientations" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rot = Rotation.random(10)\n", "ori = Orientation(rot, mp.phase.point_group)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Project patterns onto detector" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s2 = mp.get_patterns(rot, det2, energy=20, compute=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plot the first simulated pattern and the PC" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbsphinx-thumbnail": { "tooltip": "Definitions of the sample and projection/pattern center reference frames" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "det2.plot(pattern=s2.inav[0].data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load necessary PyEBSDIndex modules for Hough indexing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyebsdindex import ebsd_index, pcopt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the various softwares, define the PCs and the transformations bringing\n", "the indexed orientations returned from PyEBSDIndex back to the sample reference\n", "frame used in kikuchipy, EDAX TSL and EMsoft" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pcs = {\n", " \"KIKUCHIPY\": det2.pc,\n", " \"BRUKER\": det2.pc,\n", " \"EDAX\": det2.pc_tsl(),\n", " \"OXFORD\": det2.pc_oxford(),\n", " \"EMSOFT\": det2.pc_emsoft(),\n", "}\n", "post_rot = {\n", " \"KIKUCHIPY\": Rotation.identity(),\n", " \"BRUKER\": Rotation.from_axes_angles((0, 0, 1), -np.pi / 2),\n", " \"EDAX\": Rotation.identity(),\n", " \"OXFORD\": Rotation.from_axes_angles((0, 0, 1), -np.pi / 2),\n", " \"EMSOFT\": Rotation.identity(),\n", "}\n", "\n", "# Some wrangling to display a nice table\n", "for softw, pc_i in pcs.items():\n", " print(\n", " f\"{softw:<9} {pc_i[0, 0]:>6.3f} {pc_i[0, 1]:>6.3f} {pc_i[0, 2]:>6.3f}\"\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we do the following in a loop:\n", "\n", "1. Initialize a PyEBSDIndex indexer, specifying the vendor and vendor specific PC\n", "2. Index the ten patterns\n", "3. Apply vendor specific post rotation to the returned orientations\n", "4. Print misorientation angle between indexed orientations and ground truth orientations\n", "5. Plot the indexed orientations and the ground truth in inverse pole figures (IPFs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Keyword arguments common to all vendors\n", "v_sample = Vector3d([(1, 0, 0), (0, 1, 0), (0, 0, 1)])\n", "for vendor, pc in pcs.items():\n", " print(vendor)\n", "\n", " if vendor == \"EMSOFT\":\n", " # PyEBSDIndex requires that the pixel size is passed as the forth PC\n", " # value in order to correctly scale the L (PCz) parameter to obtain the\n", " # PC used internally in PyEBSDIndex\n", " pc = np.append(pc, [1])\n", "\n", " indexer = ebsd_index.EBSDIndexer(\n", " vendor=vendor,\n", " PC=pc,\n", " sampleTilt=det2.sample_tilt,\n", " camElev=det2.tilt,\n", " patDim=det2.shape,\n", " )\n", " data, *_ = indexer.index_pats(s2.data)\n", " rot_hi = Rotation(data[-1][\"quat\"]) * post_rot[vendor]\n", " ori_hi = Orientation(rot_hi, mp.phase.point_group)\n", "\n", " print(\"Mori. angle to ground truth:\", ori_hi.angle_with(ori, degrees=True))\n", " fig = ori.scatter(\n", " \"ipf\",\n", " direction=v_sample,\n", " c=\"b\",\n", " s=200,\n", " return_figure=True,\n", " )\n", " ori_hi.scatter(\"ipf\", figure=fig, c=\"orange\", s=100)\n", " plt.pause(0.5) # Show IPFs before continuing with next vendor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the IPFs, we see that all indexed orientations for all vendors are very\n", "close to the ground truth orientations, with an average misorientation angle\n", "below 0.5$^{\\circ}$. This confirms that the PC conventions for the various\n", "vendors shown above are consistent, and that PyEBSDIndex is consistent with\n", "kikuchipy." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 4 }