{ "cells": [ { "cell_type": "markdown", "id": "6c70ca3f", "metadata": { "nbsphinx": "hidden" }, "source": [ "This notebook is part of the *orix* documentation https://orix.readthedocs.io. Links to the documentation won’t work from the notebook." ] }, { "cell_type": "markdown", "id": "92d69e3d", "metadata": {}, "source": [ "# Crystal map\n", "\n", "In this tutorial we will create, load and save crystallographic mapping data and learn how to access and modify with the data and visualize it. All interactions with this type of data is done with the [orix.crystal_map.CrystalMap](../reference/generated/orix.crystal_map.CrystalMap.rst) class.\n", "\n", "There are more examples of using crystal maps in the [Examples section](../examples/index.rst#Crystal-maps).\n", "\n", "Orientations and other properties acquired from a super-duplex stainless steel (SDSS) EBSD data set with two phases, austenite and ferrite, are used as example data. The data can be downloaded to your local cache via the [orix.data](../reference/generated/orix.data.rst) module, courtesy of Prof. Jarle Hjelen from the Norwegian University of Science and Technology." ] }, { "cell_type": "code", "execution_count": null, "id": "12cfee4e", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "import tempfile\n", "\n", "from diffpy.structure import Atom, Lattice, Structure\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "from orix import data, io, plot\n", "from orix.crystal_map import CrystalMap, Phase, PhaseList\n", "from orix.quaternion import Orientation, Rotation, symmetry\n", "from orix.vector import Vector3d\n", "\n", "\n", "plt.rcParams.update({\"figure.figsize\": (7, 7), \"font.size\": 15})\n", "tempdir = tempfile.mkdtemp() + \"/\"" ] }, { "cell_type": "markdown", "id": "bcfafc3e", "metadata": {}, "source": [ "## Load, create and save\n", "\n", "A CrystalMap instance can be obtained by reading an orientation data set stored in a format supported by orix using the [orix.io.load()](../reference/generated/orix.io.load.rst) function, or by passing the necessary arrays when creating the the [CrystalMap()](../reference/generated/orix.crystal_map.CrystalMap.rst) method. Three file formats are supported, in addition to orix's own HDF5 format: Data in the .ang format produced by the softwares EDAX TSL OIM Data Collection v7, NanoMegas ASTAR Index, and EMsoft v4/v5 via the `EMdpmerge` program, data in EMsoft v4/v5 HDF5 files produced by the `EMEBSDDI` program, and data in Bruker's HDF5 files resulting from Hough indexing.\n", "\n", "Two writers are supported, namely orix's own HDF5 format, readable by orix and any HDF5 file viewer, and the .ang format, readable at least by MTEX and EDAX TSL OIM Analysis v7." ] }, { "cell_type": "markdown", "id": "84443c4d-b771-44da-aeed-dbceb6fa6eea", "metadata": {}, "source": [ "### Load or create\n", "\n", "Let's load a small crystal map from an .ang file produced with EMsoft. We have to explicitly allow download from an external source." ] }, { "cell_type": "code", "execution_count": null, "id": "41783cca-cc61-4ee1-bba6-899a5f2b770d", "metadata": {}, "outputs": [], "source": [ "xmap = data.sdss_ferrite_austenite(allow_download=True)\n", "xmap" ] }, { "cell_type": "markdown", "id": "b6d056c6", "metadata": {}, "source": [ "Let's inspect the data and plot it" ] }, { "cell_type": "code", "execution_count": null, "id": "088b7b2e", "metadata": { "tags": [] }, "outputs": [], "source": [ "xmap.plot(\n", " overlay=\"dp\"\n", ") # Dot product values added to the alpha (RGBA) channel\n", "xmap" ] }, { "cell_type": "markdown", "id": "6dbb9f56", "metadata": {}, "source": [ "The indexing properties returned by EMsoft in their .ang files are the pattern image quality (iq) (according to Niels Krieger Lassen's method), and the highest normalized dot product (dp) between the experimental and best matching simulated pattern.\n", "\n", "The same `CrystalMap` object can be obtained by reading each array from the .ang file ourselves and passing this to `CrystalMap.__init__()`" ] }, { "cell_type": "code", "execution_count": null, "id": "9a6ec708", "metadata": {}, "outputs": [], "source": [ "# Directly access *private* cache data path from module\n", "_target = data._fetcher.path / \"sdss/sdss_ferrite_austenite.ang\"\n", "\n", "# Read each column from the file\n", "eu1, eu2, eu3, x, y, iq, dp, phase_id = np.loadtxt(_target, unpack=True)\n", "\n", "# Create a Rotation object from Euler angles\n", "euler_angles = np.column_stack((eu1, eu2, eu3))\n", "rotations = Rotation.from_euler(euler_angles)\n", "\n", "# Create a property dictionary\n", "properties = dict(iq=iq, dp=dp)\n", "\n", "# Create unit cells of the phases\n", "structures = [\n", " Structure(\n", " title=\"austenite\",\n", " atoms=[Atom(\"fe\", [0] * 3)],\n", " lattice=Lattice(0.360, 0.360, 0.360, 90, 90, 90),\n", " ),\n", " Structure(\n", " title=\"ferrite\",\n", " atoms=[Atom(\"fe\", [0] * 3)],\n", " lattice=Lattice(0.287, 0.287, 0.287, 90, 90, 90),\n", " ),\n", "]\n", "phase_list = PhaseList(\n", " names=[\"austenite\", \"ferrite\"],\n", " point_groups=[\"432\", \"432\"],\n", " structures=structures,\n", ")\n", "\n", "# Create a CrystalMap instance\n", "xmap2 = CrystalMap(\n", " rotations=rotations,\n", " phase_id=phase_id,\n", " x=x,\n", " y=y,\n", " phase_list=phase_list,\n", " prop=properties,\n", ")\n", "xmap2.scan_unit = \"um\"\n", "\n", "xmap2" ] }, { "cell_type": "markdown", "id": "45b6ea45", "metadata": {}, "source": [ "### Save\n", "\n", "#### orix HDF5 format\n", "\n", "As mentioned, the two writers implemented are orix's own HDF5 format and the .ang format, used by calling [orix.io.save()](../reference/generated/orix.io.save.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "4f6fe317", "metadata": {}, "outputs": [], "source": [ "io.save(\n", " filename=tempdir + \"sdss_ferrite_austenite2.h5\",\n", " object2write=xmap,\n", " overwrite=True, # Default is False\n", ")" ] }, { "cell_type": "markdown", "id": "70f5d06c-3259-4e67-8049-c57e5d0b2caf", "metadata": {}, "source": [ "Read the file contents back into a `CrystalMap` object using [orix.io.load()](../reference/generated/orix.io.load.rst) function.\n", "\n", "All contents in this file can be inspected using any HDF5 viewer and read back into Python using the h5py library (which we use)." ] }, { "cell_type": "markdown", "id": "003791dd-037e-465d-bd62-958d2978746b", "metadata": {}, "source": [ "#### .ang format\n", "\n", "The .ang writer supports many use cases. Some of these are demonstrated here, by reloading the saved crystal maps.\n", "\n", "First, let's write the multi phase map to an .ang file, specifying that the `xmap.dp` property should be written to the confidence index (CI) column" ] }, { "cell_type": "code", "execution_count": null, "id": "c7308781", "metadata": {}, "outputs": [], "source": [ "fname_ang1 = \"sdss_dp_ci.ang\"\n", "io.save(\n", " filename=tempdir + fname_ang1,\n", " object2write=xmap,\n", " confidence_index_prop=\"dp\",\n", ")\n", "\n", "xmap_reload1 = io.load(tempdir + fname_ang1)\n", "print(xmap_reload1)\n", "print(xmap_reload1.prop)" ] }, { "cell_type": "markdown", "id": "184e9228", "metadata": {}, "source": [ "Note that points not in data are set to `not_indexed` when reloaded from the .ang file, and that all properties in points not in the data set are set to zero, except for the CI column where this property in points not in the data (the austenite points) are set to -1, which MTEX and EDAX TSL expects in these points.\n", "\n", "Finally, it is worth mentioning that if a map has more than one rotation/match and phase ID per point, the index parameter can be passed to write any \"layer\" of the data to file." ] }, { "cell_type": "markdown", "id": "f101b235", "metadata": {}, "source": [ "## Modify crystal phases\n", "\n", "The phases are stored in a [PhaseList](../reference/generated/orix.crystal_map.PhaseList.rst) instance in the `CrystalMap.phases` attribute" ] }, { "cell_type": "code", "execution_count": null, "id": "cb440ca3", "metadata": {}, "outputs": [], "source": [ "xmap.phases" ] }, { "cell_type": "markdown", "id": "259daa7d-0e8e-4c12-bbc7-ad251f88cb8e", "metadata": {}, "source": [ "### Symmetry\n", "\n", "The point group symmetry are stored in the vendor and EMsoft files, however they provide no space group symmetry. We can set this *per phase* by providing a space group number (1-230) according to the International Tables of Crystallography (useful link: http://img.chem.ucl.ac.uk/sgp/large/sgp.htm)" ] }, { "cell_type": "code", "execution_count": null, "id": "e396a0db", "metadata": {}, "outputs": [], "source": [ "xmap.phases[1].space_group = 225\n", "xmap.phases[2].space_group = 229\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "8c1e43bf", "metadata": {}, "source": [ "Note that this also changed the point group, because this is always determined from the space group. But the proper point group, without any inversion or mirror planes, stayed the same. The `space_group` attribute stores a [diffpy.structure.spacegroups.SpaceGroup](https://www.diffpy.org/diffpy.structure/mod_spacegroup.html#diffpy.structure.spacegroupmod.SpaceGroup) instance.\n", "\n", "We can get the point group which a space group is the subgroup of" ] }, { "cell_type": "code", "execution_count": null, "id": "4a1d5b02", "metadata": { "tags": [] }, "outputs": [], "source": [ "print(symmetry.get_point_group(200).name, symmetry.get_point_group(230).name)" ] }, { "cell_type": "markdown", "id": "07923847", "metadata": {}, "source": [ "The point group stores symmetry operations as quaternions. We can get them as orientation matrices" ] }, { "cell_type": "code", "execution_count": null, "id": "7de0bed1", "metadata": {}, "outputs": [], "source": [ "xmap.phases[1].point_group[:2]" ] }, { "cell_type": "code", "execution_count": null, "id": "0eceb131", "metadata": {}, "outputs": [], "source": [ "xmap.phases[1].point_group[:2].to_matrix()" ] }, { "cell_type": "markdown", "id": "701bdced", "metadata": {}, "source": [ "`diffpy.structure` stores rotation symmetry operations as orientation matrices and translations as 1D arrays" ] }, { "cell_type": "code", "execution_count": null, "id": "6807718a", "metadata": {}, "outputs": [], "source": [ "[(i.R, i.t) for i in xmap.phases[1].space_group.symop_list[:2]]" ] }, { "cell_type": "markdown", "id": "1a1ff060", "metadata": {}, "source": [ "We can get the quaternion representation of these matrices" ] }, { "cell_type": "code", "execution_count": null, "id": "37d7b229", "metadata": {}, "outputs": [], "source": [ "[\n", " Rotation.from_matrix(i.R)\n", " for i in xmap.phases[1].space_group.symop_list[:2]\n", "]" ] }, { "cell_type": "markdown", "id": "d26c43cc-dbba-45ce-be07-0bf55332581a", "metadata": {}, "source": [ "### Index phase list\n", "\n", "The phase list can be indexed by phase ID or name" ] }, { "cell_type": "code", "execution_count": null, "id": "14827de2", "metadata": {}, "outputs": [], "source": [ "xmap.phases[1]" ] }, { "cell_type": "code", "execution_count": null, "id": "ad0becf4", "metadata": {}, "outputs": [], "source": [ "xmap.phases[\"austenite\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "03fbe843", "metadata": {}, "outputs": [], "source": [ "xmap.phases[1:]" ] }, { "cell_type": "code", "execution_count": null, "id": "2457b281", "metadata": {}, "outputs": [], "source": [ "xmap.phases[\"austenite\", \"ferrite\"]" ] }, { "cell_type": "markdown", "id": "3388f95f", "metadata": {}, "source": [ "When asking for a single phase, either by an integer or a single string, a [Phase](../reference/generated/orix.crystal_map.Phase.rst) instance was returned. In the other cases, a `PhaseList` object was returned" ] }, { "cell_type": "code", "execution_count": null, "id": "0bc3d4dc", "metadata": { "tags": [] }, "outputs": [], "source": [ "print(type(xmap.phases[1]), type(xmap.phases[1:]))" ] }, { "cell_type": "markdown", "id": "301ef42b", "metadata": {}, "source": [ "Valid point group names to use when setting the point group symmetry are" ] }, { "cell_type": "code", "execution_count": null, "id": "0a314f70", "metadata": {}, "outputs": [], "source": [ "[point_group.name for point_group in symmetry._groups]" ] }, { "cell_type": "code", "execution_count": null, "id": "b27ff073", "metadata": {}, "outputs": [], "source": [ "xmap.phases[\"austenite\"].point_group = \"-43m\"\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "a71efbf0", "metadata": {}, "source": [ "Note that the `space_group` was set to `None` since space group Fm-3m is not a subgroup of -43m.\n", "\n", "Let's revert to the correct space group (and the name, for convenience)" ] }, { "cell_type": "code", "execution_count": null, "id": "77f5eefd", "metadata": {}, "outputs": [], "source": [ "xmap.phases[\"austenite\"].space_group = 225\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "1632fc5a", "metadata": {}, "source": [ "We can add a phase by giving its name and point group symmetry" ] }, { "cell_type": "code", "execution_count": null, "id": "9d4a5532", "metadata": {}, "outputs": [], "source": [ "xmap.phases.add(Phase(\"sigma\", point_group=\"4/mmm\"))\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "4652f0e5", "metadata": {}, "source": [ "When adding a phase to the phase list like this, the phases' structure contains no atoms and the default lattice parameters are used" ] }, { "cell_type": "code", "execution_count": null, "id": "123903bb", "metadata": {}, "outputs": [], "source": [ "xmap.phases[\"sigma\"].structure.lattice.abcABG()" ] }, { "cell_type": "markdown", "id": "3e413d01", "metadata": {}, "source": [ "So let's set this" ] }, { "cell_type": "code", "execution_count": null, "id": "f02a0b23", "metadata": { "tags": [] }, "outputs": [], "source": [ "xmap.phases[\"sigma\"].structure.lattice = Lattice(\n", " 0.880, 0.880, 0.880, 90, 90, 90\n", ")\n", "print(xmap.phases[\"sigma\"].structure.lattice)" ] }, { "cell_type": "markdown", "id": "8be59a2c", "metadata": {}, "source": [ "If some data points are considered as not indexed, a \"not_indexed\" phase can be added to the phase list to keep track of these points" ] }, { "cell_type": "code", "execution_count": null, "id": "492b4be0", "metadata": {}, "outputs": [], "source": [ "xmap.phases.add_not_indexed()\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "bde75640", "metadata": {}, "source": [ "No points in this data set are considered not indexed. A phase list with only the phases in the data is stored in the `phases_in_data` attribute" ] }, { "cell_type": "code", "execution_count": null, "id": "4e997dc3", "metadata": {}, "outputs": [], "source": [ "xmap.phases_in_data" ] }, { "cell_type": "markdown", "id": "606a21f1", "metadata": {}, "source": [ "We can of course remove a phase from the phase list, either by its name or phase ID" ] }, { "cell_type": "code", "execution_count": null, "id": "4903a55f", "metadata": {}, "outputs": [], "source": [ "del xmap.phases[\"sigma\"]\n", "del xmap.phases[-1]\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "10894118-c657-4cc1-b21e-f7a58bf7a888", "metadata": {}, "source": [ "### Properties\n", "\n", "The phase name, space group, point group, proper point group, color and structure can be accessed for the full phase list or a single phase" ] }, { "cell_type": "code", "execution_count": null, "id": "395f91b8", "metadata": { "tags": [] }, "outputs": [], "source": [ "print(xmap.phases.names)\n", "print([i.short_name for i in xmap.phases.space_groups])\n", "print([i.name for i in xmap.phases.point_groups])\n", "print([i.proper_subgroup.name for i in xmap.phases.point_groups])\n", "print(xmap.phases.colors)\n", "print(xmap.phases.structures)" ] }, { "cell_type": "markdown", "id": "61a90d84", "metadata": {}, "source": [ "Note that the structures' representations are empty lists since no atoms have been added to them yet." ] }, { "cell_type": "code", "execution_count": null, "id": "215b967a", "metadata": { "tags": [] }, "outputs": [], "source": [ "xmap.phases[\"austenite\"]\n", "print(xmap.phases[\"austenite\"].name)\n", "print(xmap.phases[\"austenite\"].space_group.short_name)\n", "print(xmap.phases[\"austenite\"].point_group.name)\n", "print(xmap.phases[\"austenite\"].point_group.proper_subgroup.name)\n", "print(xmap.phases[\"austenite\"].color)\n", "print(xmap.phases[\"austenite\"].structure)" ] }, { "cell_type": "markdown", "id": "336fa081", "metadata": {}, "source": [ "These attributes (not the phase ID) can be set *per phase*" ] }, { "cell_type": "code", "execution_count": null, "id": "6f187440", "metadata": { "tags": [] }, "outputs": [], "source": [ "xmap.phases[\"austenite\"].structure = Structure(\n", " lattice=Lattice(0.36, 0.36, 0.36, 90, 90, 90)\n", ")\n", "print(xmap.phases[\"austenite\"].structure)\n", "\n", "xmap.phases[\"austenite\"].color = \"lime\" # Sets RGB tuple (0, 1, 0)\n", "print(xmap.phases[\"austenite\"].color_rgb)\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "cd7c55f8-8e66-45dc-8ddd-8ede801b18e9", "metadata": {}, "source": [ "Valid color strings can be found here: https://matplotlib.org/stable/tutorials/colors/colors.html" ] }, { "cell_type": "markdown", "id": "2f8b299c-ef28-4d97-93ba-7e2f37b1054d", "metadata": {}, "source": [ "#### Create phase list\n", "\n", "We can create a phase list using [PhaseList](../reference/generated/orix.crystal_map.PhaseList.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "b76fdf57", "metadata": {}, "outputs": [], "source": [ "PhaseList(\n", " names=[\"al\", \"cu\"],\n", " space_groups=[225, 225],\n", " colors=[\"lime\", \"xkcd:violet\"],\n", " ids=[0, 1],\n", " structures=[\n", " Structure(\n", " atoms=[Atom(\"al\", [0] * 3)],\n", " lattice=Lattice(0.405, 0.405, 0.405, 90, 90, 90),\n", " ),\n", " Structure(\n", " atoms=[Atom(\"cu\", [0] * 3)],\n", " lattice=Lattice(0.361, 0.361, 0.361, 90, 90, 90),\n", " ),\n", " ],\n", ")" ] }, { "cell_type": "markdown", "id": "edacb010", "metadata": {}, "source": [ "or by creating `Phase` objects and passing these to the first argument in `PhaseList.__init__()` as a list (or single `Phase` objects)" ] }, { "cell_type": "code", "execution_count": null, "id": "ac20dc2d", "metadata": {}, "outputs": [], "source": [ "al = Phase(name=\"al\", space_group=225, color=\"C0\")\n", "cu = Phase(\n", " color=\"C1\",\n", " structure=Structure(\n", " title=\"cu\", lattice=Lattice(0.361, 0.361, 0.361, 90, 90, 90)\n", " ),\n", ")\n", "\n", "PhaseList([al, cu])" ] }, { "cell_type": "markdown", "id": "b097165e-2f44-4a02-a688-c1af945168fa", "metadata": {}, "source": [ "Note that the Cu phase name was retrieved from the `Structure` object." ] }, { "cell_type": "markdown", "id": "ed203524-2154-4acc-b8e5-72b0af4b1375", "metadata": {}, "source": [ "### Copying\n", "\n", "If we want a shallow copy of the phase list" ] }, { "cell_type": "code", "execution_count": null, "id": "813de999", "metadata": {}, "outputs": [], "source": [ "pl = xmap.phases\n", "pl[\"ferrite\"].color = \"red\"\n", "\n", "xmap.phases" ] }, { "cell_type": "markdown", "id": "8ee2ab54", "metadata": {}, "source": [ "If we want a deep copy of the phase list" ] }, { "cell_type": "code", "execution_count": null, "id": "b252fcae", "metadata": { "tags": [] }, "outputs": [], "source": [ "pl = xmap.phases.deepcopy()\n", "pl.add(Phase(\"chi\", point_group=\"-43m\"))\n", "print(pl, \"\\n\")\n", "\n", "print(xmap.phases)" ] }, { "cell_type": "markdown", "id": "97329895", "metadata": {}, "source": [ "## Orientation data\n", "\n", "Rotations are stored in a [Rotation](../reference/generated/orix.quaternion.Rotation.rst) instance" ] }, { "cell_type": "code", "execution_count": null, "id": "cd239b85", "metadata": {}, "outputs": [], "source": [ "xmap.rotations" ] }, { "cell_type": "markdown", "id": "f469d2b9", "metadata": {}, "source": [ "Orientations *per phase* can be obtained by applying the phase point group symmetry" ] }, { "cell_type": "code", "execution_count": null, "id": "303d56b7", "metadata": {}, "outputs": [], "source": [ "O_au = xmap[\"austenite\"].orientations\n", "\n", "O_au" ] }, { "cell_type": "markdown", "id": "7373f05a", "metadata": {}, "source": [ "The above is equivalent to" ] }, { "cell_type": "code", "execution_count": null, "id": "7a681600", "metadata": {}, "outputs": [], "source": [ "R_au = xmap[\"austenite\"].rotations\n", "O_au2 = Orientation(R_au, symmetry=xmap[\"austenite\"].phases[1].point_group)" ] }, { "cell_type": "markdown", "id": "40bbdaea", "metadata": {}, "source": [ "Orientation angles and axes are readily available" ] }, { "cell_type": "code", "execution_count": null, "id": "412e01a1", "metadata": {}, "outputs": [], "source": [ "O_au.angle" ] }, { "cell_type": "code", "execution_count": null, "id": "3808166c", "metadata": {}, "outputs": [], "source": [ "O_au.axis" ] }, { "cell_type": "markdown", "id": "2d3668d3", "metadata": {}, "source": [ "## Map properties\n", "\n", "Map properties are stored in the `CrystalMap.prop` attribute dictionary" ] }, { "cell_type": "code", "execution_count": null, "id": "3ddbdf97", "metadata": {}, "outputs": [], "source": [ "xmap.prop" ] }, { "cell_type": "markdown", "id": "f5c3946f", "metadata": {}, "source": [ "All properties in this dictionary are also available directly from the `CrystalMap` as attributes" ] }, { "cell_type": "code", "execution_count": null, "id": "2c3dd5ad", "metadata": {}, "outputs": [], "source": [ "xmap.iq" ] }, { "cell_type": "code", "execution_count": null, "id": "d982ce4e", "metadata": {}, "outputs": [], "source": [ "xmap.dp" ] }, { "cell_type": "markdown", "id": "d0c154d6", "metadata": {}, "source": [ "We can add a map property by specifying its name and an initial value in each map point" ] }, { "cell_type": "code", "execution_count": null, "id": "87896b60", "metadata": {}, "outputs": [], "source": [ "xmap.prop[\"grain_boundary\"] = 0\n", "\n", "xmap.grain_boundary" ] }, { "cell_type": "code", "execution_count": null, "id": "2ba37678", "metadata": {}, "outputs": [], "source": [ "xmap.prop[\"grain_boundary2\"] = np.arange(xmap.size, dtype=int)\n", "\n", "xmap.grain_boundary2" ] }, { "cell_type": "markdown", "id": "a4737c0c", "metadata": {}, "source": [ "We can also delete a property from the `prop` dictionary" ] }, { "cell_type": "code", "execution_count": null, "id": "80335021", "metadata": {}, "outputs": [], "source": [ "del xmap.prop[\"grain_boundary2\"]\n", "\n", "xmap.prop" ] }, { "cell_type": "markdown", "id": "ed48bfdf", "metadata": {}, "source": [ "## Plotting\n", "\n", "Map plotting can either be done via the [CrystalMap.plot()](../reference/generated/orix.crystal_map.CrystalMap.plot.rst) method, or via the [CrystalMapPlot](../reference/generated/orix.plot.CrystalMapPlot.rst) `matplotlib` projection. To plot a phase map via `CrystalMap.plot()`, we simply do" ] }, { "cell_type": "code", "execution_count": null, "id": "ec3a5045", "metadata": {}, "outputs": [], "source": [ "xmap.plot()" ] }, { "cell_type": "markdown", "id": "d3e3c612", "metadata": {}, "source": [ "Using the `matplotlib` projection" ] }, { "cell_type": "code", "execution_count": null, "id": "f8974777", "metadata": {}, "outputs": [], "source": [ "# fig, ax = plt.subplots(subplot_kw=dict(projection=\"plot_map\"))\n", "# im = ax.plot_map(xmap)" ] }, { "cell_type": "markdown", "id": "6a58dab7-6276-460c-83b9-2807c17fe67e", "metadata": {}, "source": [ "Hover over figure points to display the (x,y) position and orientations in that point when plotting interactively!\n", "\n", "Note that `plot()` wraps `matplotlib.axes.Axes.imshow`. All key word arguments in `plot()` are passed to `imshow()`, so be sure to check [its documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.imshow.html) out for any additional arguments.\n", "\n", "If we obtain a colour per orientation using [IPFColorKeyTSL.orientation2color()](../reference/generated/orix.plot.IPFColorKeyTSL.orientation2color.rst) (see also the [inverse pole figure tutorial](inverse_pole_figures.rst)), we can plot this as well" ] }, { "cell_type": "code", "execution_count": null, "id": "fd47cbf7-64ca-4236-b035-a3215194fdfe", "metadata": {}, "outputs": [], "source": [ "ckey_m3m = plot.IPFColorKeyTSL(\n", " xmap.phases[\"austenite\"].point_group, direction=Vector3d.zvector()\n", ")\n", "rgb_au = ckey_m3m.orientation2color(xmap[\"austenite\"].orientations)\n", "rgb_fe = ckey_m3m.orientation2color(xmap[\"ferrite\"].orientations)" ] }, { "cell_type": "code", "execution_count": null, "id": "2853d341-b86a-4710-9acd-605981f315f1", "metadata": {}, "outputs": [], "source": [ "xmap[\"austenite\"].plot(rgb_au)" ] }, { "cell_type": "code", "execution_count": null, "id": "95ae3165-39b7-452c-b334-6ad86abc6182", "metadata": {}, "outputs": [], "source": [ "xmap[\"ferrite\"].plot(rgb_fe)" ] }, { "cell_type": "markdown", "id": "8705e856-4d5b-4076-baf0-26d895df0ee9", "metadata": {}, "source": [ "And the combined plot with the IPF color key added to the figure:" ] }, { "cell_type": "code", "execution_count": null, "id": "6cdefa8a-6394-40d4-9fff-88ade1a28438", "metadata": { "nbsphinx-thumbnail": { "tooltip": "Handling and manipulation of crystallographic data" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "rgb_all = np.zeros((xmap.size, 3))\n", "rgb_all[xmap.phase_id == 1] = rgb_au\n", "rgb_all[xmap.phase_id == 2] = rgb_fe\n", "\n", "fig = xmap.plot(rgb_all, return_figure=True)\n", "\n", "rc = {\"font.size\": 8}\n", "with plt.rc_context(rc): # Temporarily reduce font size\n", " ax_ipfkey = fig.add_axes(\n", " [0.72, 0.87, 0.2, 0.1],\n", " projection=\"ipf\",\n", " symmetry=xmap.phases[\"austenite\"].point_group,\n", " )\n", " ax_ipfkey.plot_ipf_color_key()\n", " ax_ipfkey.set_title(\"\")" ] }, { "cell_type": "markdown", "id": "5bc240aa", "metadata": {}, "source": [ "We can also color orientations from their Euler angles using [EulerColorKey.orientation2color()](../reference/generated/orix.plot.EulerColorKey.orientation2color.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "33dc4ccb", "metadata": {}, "outputs": [], "source": [ "ckey_euler = plot.EulerColorKey(xmap.phases[\"austenite\"].point_group)\n", "rgb_au_euler = ckey_euler.orientation2color(xmap[\"austenite\"].orientations)\n", "rgb_fe_euler = ckey_euler.orientation2color(xmap[\"ferrite\"].orientations)" ] }, { "cell_type": "code", "execution_count": null, "id": "7d9a3aef", "metadata": {}, "outputs": [], "source": [ "rgb_all_euler = np.zeros((xmap.size, 3))\n", "rgb_all_euler[xmap.phase_id == 1] = rgb_au_euler\n", "rgb_all_euler[xmap.phase_id == 2] = rgb_fe_euler\n", "\n", "xmap.plot(rgb_all_euler)" ] }, { "cell_type": "markdown", "id": "18e34e65", "metadata": {}, "source": [ "We can plot the color key to see the fundamental Euler region for point group *432*" ] }, { "cell_type": "code", "execution_count": null, "id": "0cb5b9fe", "metadata": {}, "outputs": [], "source": [ "ckey_euler.plot()" ] }, { "cell_type": "markdown", "id": "fe10212b", "metadata": {}, "source": [ "We can add any overlay, from any property with a value in each map point, to the map by either passing the property name as a string, or the actual (flattened) array" ] }, { "cell_type": "code", "execution_count": null, "id": "c9a1d664", "metadata": {}, "outputs": [], "source": [ "xmap.plot(overlay=xmap.dp)" ] }, { "cell_type": "markdown", "id": "7f0fc411", "metadata": {}, "source": [ "To save our phase map with the scalebar and legend, but without white padding" ] }, { "cell_type": "code", "execution_count": null, "id": "fdc10428", "metadata": {}, "outputs": [], "source": [ "fig = xmap.plot(overlay=\"dp\", return_figure=True, remove_padding=True)\n", "fig.savefig(tempdir + \"phase_map.png\", bbox_inches=\"tight\", pad_inches=0)" ] }, { "cell_type": "markdown", "id": "25785bba", "metadata": {}, "source": [ "To save phase map without a scalebar, legend and white padding, and one image pixel per map point" ] }, { "cell_type": "code", "execution_count": null, "id": "3f1fa050", "metadata": {}, "outputs": [], "source": [ "ax = fig.axes[0]\n", "ax" ] }, { "cell_type": "code", "execution_count": null, "id": "e9cce5a2", "metadata": {}, "outputs": [], "source": [ "# 2D NumPy array, possibly with an RGB tuple in each element\n", "plt.imsave(tempdir + \"phase_map_no_fluff.png\", arr=ax.images[0].get_array())" ] }, { "cell_type": "markdown", "id": "07c39513", "metadata": {}, "source": [ "We can plot any property with a value in each map point, also adding a colorbar" ] }, { "cell_type": "code", "execution_count": null, "id": "7658e476", "metadata": {}, "outputs": [], "source": [ "fig = xmap.plot(\n", " xmap.dp,\n", " cmap=\"inferno\",\n", " colorbar=True,\n", " colorbar_label=\"Dottproduct\",\n", " return_figure=True,\n", ")" ] }, { "cell_type": "markdown", "id": "6fd38033", "metadata": {}, "source": [ "We can update the colorbar" ] }, { "cell_type": "code", "execution_count": null, "id": "917cf844", "metadata": {}, "outputs": [], "source": [ "cbar = fig.axes[0].colorbar\n", "cbar" ] }, { "cell_type": "code", "execution_count": null, "id": "a7e05da6", "metadata": {}, "outputs": [], "source": [ "cbar.ax.set_ylabel(\"Dot product\");" ] }, { "cell_type": "markdown", "id": "8aa2de51", "metadata": {}, "source": [ "We can also plot orientation related values, like axis and angles etc., and restrict the color bar maximum" ] }, { "cell_type": "code", "execution_count": null, "id": "a9492106", "metadata": {}, "outputs": [], "source": [ "# Get rotation angles in degrees\n", "angles = xmap.rotations.angle * 180 / np.pi\n", "\n", "xmap.plot(\n", " angles,\n", " vmax=angles.max() - 10,\n", " overlay=xmap.iq,\n", " colorbar=True,\n", " colorbar_label=\"Rotation angle, $\\omega$ [$^{\\circ}$]\",\n", ")" ] }, { "cell_type": "markdown", "id": "2daeb184", "metadata": {}, "source": [ "To plot only one phase, while passing custom\n", "* scalebar properties (https://github.com/ppinard/matplotlib-scalebar/)\n", "* legend properties (https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html)" ] }, { "cell_type": "code", "execution_count": null, "id": "97530ace", "metadata": {}, "outputs": [], "source": [ "xmap[\"austenite\"].plot(\n", " scalebar_properties=dict(location=\"upper left\", frameon=False, sep=6),\n", " legend_properties=dict(\n", " framealpha=1, handlelength=1.5, handletextpad=0.1\n", " ),\n", ")" ] }, { "cell_type": "markdown", "id": "977a0cd0", "metadata": {}, "source": [ "Plot only a rectangle of the map" ] }, { "cell_type": "code", "execution_count": null, "id": "01f11901", "metadata": {}, "outputs": [], "source": [ "xmap2 = xmap[20:50, 40:90]\n", "xmap2.plot(overlay=xmap2.dp)" ] }, { "cell_type": "markdown", "id": "4edd92b5", "metadata": {}, "source": [ "Plot only parts of a map based on chained conditionals, like belonging to one phase or having a property value above a threshold" ] }, { "cell_type": "code", "execution_count": null, "id": "3753931a", "metadata": {}, "outputs": [], "source": [ "# Conditional slicing\n", "xmap[xmap.dp > 0.81].plot(\n", " \"iq\", cmap=\"gray\", colorbar=True, colorbar_label=\"Image quality, $Q$\"\n", ")\n", "\n", "# Chained conditional slicing\n", "xmap[(xmap.dp > 0.81) & (xmap.phase_id == 1)].plot(\n", " \"dp\", cmap=\"viridis\", colorbar=True, colorbar_label=\"Dot product\"\n", ")" ] }, { "cell_type": "markdown", "id": "4f589a94", "metadata": {}, "source": [ "Plot histogram of a property per phase" ] }, { "cell_type": "code", "execution_count": null, "id": "62e7b80d", "metadata": {}, "outputs": [], "source": [ "# Property of interest\n", "this_prop = \"dp\"\n", "\n", "# Plot phase map again to see color changes\n", "xmap.plot(overlay=this_prop, remove_padding=True)\n", "\n", "# Declare lists for plotting\n", "data = []\n", "labels = []\n", "colors = []\n", "\n", "# Get property values, name and color per phase\n", "for _, p in xmap.phases_in_data:\n", " labels.append(p.name)\n", " colors.append(p.color)\n", "\n", " # Accessing the property dictionary directly\n", " data.append(xmap[p.name].prop[this_prop])\n", " # or indirectly\n", " # data.append(xmap[p.name].dp)\n", "\n", "# Nice bar plot with property histogram per phase\n", "fig, ax = plt.subplots()\n", "ax.hist(\n", " data, bins=20, histtype=\"bar\", density=True, label=labels, color=colors\n", ")\n", "ax.set_xlabel(this_prop)\n", "ax.set_ylabel(\"Frequency\")\n", "ax.legend();" ] }, { "cell_type": "code", "execution_count": null, "id": "885fedfd", "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "# Remove files written to disk in this tutorial\n", "import os\n", "\n", "for f in [\n", " tempdir + \"sdss_ferrite_austenite2.h5\",\n", " tempdir + \"sdss_dp_ci.ang\",\n", " tempdir + \"phase_map.png\",\n", " tempdir + \"phase_map_no_fluff.png\",\n", "]:\n", " os.remove(f)\n", "os.rmdir(tempdir)" ] } ], "metadata": { "interpreter": { "hash": "4396f389b93e7269692bd3bea4c62813bbe379469bde939b058805f538feec11" }, "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.6" } }, "nbformat": 4, "nbformat_minor": 5 }