{ "cells": [ { "cell_type": "markdown", "metadata": { "nbsphinx": "hidden" }, "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": {}, "source": [ "# Geometrical EBSD simulations\n", "\n", "In this tutorial, we will inspect and visualize the results from EBSD indexing by plotting Kikuchi lines and zone axes onto an EBSD signal.\n", "We consider this a *geometrical* EBSD simulation, since it is only positions of Kikuchi lines and zone axes that are computed.\n", "These simulations are based on the work by Aimo Winkelmann in the supplementary material to Britton et al. (2016).\n", "\n", "These simulations can be helpful when checking whether indexing results are correct and for interpreting them. \n", "\n", "Let's import the necessary libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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", "from diffpy.structure import Atom, Lattice, Structure\n", "from diffsims.crystallography import ReciprocalLatticeVector\n", "import hyperspy.api as hs\n", "import kikuchipy as kp\n", "from orix.crystal_map import Phase\n", "from orix.quaternion import Rotation\n", "\n", "\n", "# Plotting parameters\n", "plt.rcParams.update(\n", " {\"figure.figsize\": (10, 10), \"font.size\": 20, \"lines.markersize\": 10}\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll inspect the indexing results of a small nickel EBSD dataset of (3, 3) patterns" ] }, { "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": {}, "source": [ "Let's enhance the Kikuchi bands by removing the static and dynamic backgrounds" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s.remove_static_background()\n", "s.remove_dynamic_background()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_ = hs.plot.plot_images(\n", " s, axes_decor=None, label=None, colorbar=False, tight_layout=True\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To project Kikuchi lines and zone axes onto our detector, we need\n", "\n", "1. a description of the crystal phase\n", "\n", "2. the set of Kikuchi bands to consider, e.g. the sets of planes {111}, {200}, {220}, and {311}\n", "\n", "3. the crystal orientations with respect to the reference frame\n", "\n", "4. the position of the detector with respect to the sample, in the form of a sample-detector model which includes the sample and detector tilt and the projection center (shortes distance from the source point on the sample to the detector), given here as (PC$_x$, PC$_y$, PC$_z$)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Prepare phase and reflector list\n", "\n", "We'll store the crystal phase information in an [orix.crystal_map.Phase](https://orix.readthedocs.io/en/stable/reference/generated/orix.crystal_map.Phase.html) instance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "phase = Phase(\n", " space_group=225,\n", " structure=Structure(\n", " atoms=[Atom(\"Ni\", [0, 0, 0])],\n", " lattice=Lattice(3.52, 3.52, 3.52, 90, 90, 90),\n", " ),\n", ")\n", "\n", "print(phase)\n", "print(phase.structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll build up the reflector list using [diffsims.crystallography.ReciprocalLatticeVector](https://diffsims.readthedocs.io/en/latest/reference/generated/diffsims.crystallography.ReciprocalLatticeVector.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rlv = ReciprocalLatticeVector(\n", " phase=phase, hkl=[[1, 1, 1], [2, 0, 0], [2, 2, 0], [3, 1, 1]]\n", ")\n", "rlv" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll obtain the symmetrically equivalent vectors and plot each family of vectors in a distinct colour in the stereographic projection" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rlv = rlv.symmetrise().unique()\n", "rlv.size" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rlv.print_table()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Dictionary with {hkl} as key and indices into ReciprocalLatticeVector as values\n", "hkl_sets = rlv.get_hkl_sets()\n", "hkl_sets" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hkl_colors = np.zeros((rlv.size, 3))\n", "for idx, color in zip(\n", " hkl_sets.values(),\n", " [\n", " [1, 0, 0],\n", " [0, 1, 0],\n", " [0, 0, 1],\n", " [0.75, 0, 0.75],\n", " ], # Red, green, blue, magenta\n", "):\n", " hkl_colors[idx] = color" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hkl_labels = []\n", "for hkl in rlv.hkl.round(0).astype(int):\n", " hkl_labels.append(str(hkl).replace(\"[\", \"(\").replace(\"]\", \")\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rlv.scatter(c=hkl_colors, grid=True, ec=\"k\", vector_labels=hkl_labels)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also plot the plane traces, i.e. the Kikuchi lines, in both hemispheres (they are identical for Ni)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rlv.draw_circle(\n", " color=hkl_colors, hemisphere=\"both\", figure_kwargs=dict(figsize=(15, 10))\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specify rotations and detector-sample geometry\n", "\n", "Rotations and the detector-sample geometry for the nine nickel EBSD patterns are stored in the kikuchipy h5ebsd file.\n", "These were found by Hough indexing using *PyEBSDIndex* followed by orientation and PC refinement using a nickel EBSD master pattern simulated with *EMsoft*.\n", "See the [Hough indexing](hough_indexing.ipynb) and [Pattern matching](pattern_matching.ipynb) tutorials for details on indexing and the [reference frame](reference_frames.ipynb) tutorial for details on the definition of the detector-sample geometry." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rot = s.xmap.rotations\n", "rot = rot.reshape(*s.xmap.shape)\n", "rot # Quaternions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We describe the sample-detector model in an [kikuchipy.detectors.EBSDDetector](../reference/generated/kikuchipy.detectors.EBSDDetector.rst) instance.\n", "The sample was tilted $70^{\\circ}$ about the microscope X direction towards the detector, and the detector normal was orthogonal to the optical axis (beam direction).\n", "Using this information, the projection center (PC) was found using *PyEBSDIndex* as described in the Hough indexing tutorial." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s.detector" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s.detector.pc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create simulations on detector\n", "\n", "Now we're ready to create geometrical simulations.\n", "We create simulations using the [kikuchipy.simulations.KikuchiPatternSimulator](../reference/generated/kikuchipy.simulations.KikuchiPatternSimulator.rst), which takes the reflectors as input" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "simulator = kp.simulations.KikuchiPatternSimulator(rlv)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim = simulator.on_detector(s.detector, rot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By passing the detector and crystal orientations to\n", "[KikuchiPatternSimulator.on_detector()](../reference/generated/kikuchipy.simulations.KikuchiPatternSimulator.on_detector.rst),\n", "we've obtained a\n", "[kikuchipy.simulations.GeometricalKikuchiPatternSimulation](../reference/generated/kikuchipy.simulations.GeometricalKikuchiPatternSimulation.rst),\n", "which stores the detector and gnomonic coordinates of the Kikuchi lines and\n", "zone axes for each crystal orientation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that not all 50 of the reflectors in the reflector list are present in some pattern." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot simulations\n", "\n", "These geometrical simulations can be plotted one-by-one by themselves" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, they can be plotted on top of patterns in three ways: passing a pattern to [GeometricalKikuchiPatternSimulation.plot()](../reference/generated/kikuchipy.simulations.GeometricalKikuchiPatternSimulation.plot.rst)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbsphinx-thumbnail": { "tooltip": "Plotting Kikuchi line and zone axes onto EBSD patterns" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "sim.plot(index=(1, 2), pattern=s.inav[2, 1].data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, we can obtain collections of lines, zone axes and zone axes labels as *Matplotlib* objects via [GeometricalKikuchiPatternSimulation.as_collections()](../reference/generated/kikuchipy.simulations.GeometricalKikuchiPatternSimulation.as_collections.rst) and add them to an existing Matplotlib axis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(ncols=3, nrows=3, figsize=(15, 15))\n", "\n", "for idx in np.ndindex(s.axes_manager.navigation_shape[::-1]):\n", " ax[idx].imshow(s.data[idx], cmap=\"gray\")\n", " ax[idx].axis(\"off\")\n", "\n", " lines, zone_axes, zone_axes_labels = sim.as_collections(\n", " idx,\n", " zone_axes=True,\n", " zone_axes_labels=True,\n", " zone_axes_labels_kwargs=dict(fontsize=12),\n", " )\n", " ax[idx].add_collection(lines)\n", " ax[idx].add_collection(zone_axes)\n", " for label in zone_axes_labels:\n", " ax[idx].add_artist(label)\n", "\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, we can obtain the lines, zone axes, zone axes labels and PCs as *HyperSpy* markers via [GeometricalKikuchiPatternSimulation.as_markers()](../reference/generated/kikuchipy.simulations.GeometricalKikuchiPatternSimulation.as_markers.rst) and add them to a signal of the same navigation shape as the simulation instance.\n", "This enables navigating the patterns *with* the geometrical simulations" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "markers = sim.as_markers()\n", "\n", "# To delete previously added permanent markers, do\n", "# del s.metadata.Markers\n", "\n", "s.add_marker(markers, plot_marker=False, permanent=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s.plot()" ] } ], "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.11.4" } }, "nbformat": 4, "nbformat_minor": 4 }