{ "cells": [ { "cell_type": "markdown", "id": "71c86ad9-8a0e-41db-90ab-138f9a00790c", "metadata": {}, "source": [ "# Microstructure Maps -- The CellData Group" ] }, { "cell_type": "markdown", "id": "2be8f9e8-a431-4454-b998-429bd42619ee", "metadata": {}, "source": [ "The main focus of the *Pymicro* package is to provide tools to study the *microstructure-properties* relationship for polycrystalline materials. This work naturally involves working from a description of the microstructure of the studied material samples. These descriptions are generally microstructural images, that can be:\n", "\n", "* outputs of real imaging experiments (DCT, CT, EBSD ...)\n", "* digitally generated microstructure images (Voronoi tesselations, outputs from softwares like Neper, DREAM3D ...)\n", "* outputs of numerical simulations techniques that provide images (such as FFT-based solvers)\n", "\n", "They can be bi-dimensional or tri-dimensional, and usually are maps of the material phases or crystal orientation. This tutorial will review how to store, load and process microstructure images of a material with *Pymicro* and the `Microstructure` class." ] }, { "cell_type": "markdown", "id": "cc4a851f-8d89-4445-a8ed-c13b1d8f6f11", "metadata": {}, "source": [ "The *data model* of [the Microstructure class](./Microstructure_class.ipynb) includes a specific *Group* to store microstructure images, the `CellData` group. " ] }, { "cell_type": "markdown", "id": "c9c8bb16-3bbb-440f-81fd-0c00cd8f49f8", "metadata": {}, "source": [ "## The CellData Group" ] }, { "cell_type": "markdown", "id": "7c8b0dd4-b86f-4f2a-aa33-0647041c7c2e", "metadata": {}, "source": [ "The `CellData` group is an *Image Group* (see [dedicated tutorial](./Image_data.ipynb)), *i.e.* a group designed to store several images of the same dimension. The `CellData` group aim is to store on the same grid images of the studied microstructure from various modalities (for instance, EBSD and SEM images), to allow for simultaneous visualization and cross processing of these data. \n", "\n", "Note that you can directly benefit from 3D visualization of the `CellData` group content by using the Paraview software: an XDMF file for your dataset is automatically generated if you close your microstructure, that you can directly open it with the Paraview software. Alternatively, you can use the `pause_for_visualization` method to do it from your interactive Python interpreter. More details on these functionalities, please refer to this [tutorial](./Get_Datasets_Information.ipynb).\n", "\n", "One of Pymicro's example datasets will be used to illustrate the `CellData` content, let's start by opening it with the `Microstructure` class and display the content of this group:" ] }, { "cell_type": "code", "execution_count": null, "id": "7918b52d-35e3-4315-9870-f4b22cac37dd", "metadata": {}, "outputs": [], "source": [ "import os \n", "from pymicro import get_examples_data_dir # import file directory path\n", "PYMICRO_EXAMPLES_DATA_DIR=get_examples_data_dir() # get file directory path\n", "# import Microstructure class\n", "from pymicro.crystal.microstructure import Microstructure \n", "micro = Microstructure(filename=os.path.join(PYMICRO_EXAMPLES_DATA_DIR,'t5_dct_slice_data.h5'))\n", "\n", "# display CellData group content\n", "micro.print_node_info('CellData')\n", "micro.print_group_content('CellData', short=True)" ] }, { "cell_type": "markdown", "id": "808dfbc8-57c3-4e27-8d50-3c53611701c4", "metadata": {}, "source": [ "Let us now open the dataset and display the content of the group, using the `Microstructure` class:" ] }, { "cell_type": "markdown", "id": "98ae02b6-3e90-4ac6-8e83-c452a31624f0", "metadata": {}, "source": [ "This dataset `CellData` group contains a 3D image of $654x654x1$ voxels, with a resolution of $0.0014$ mm, that has 3 fields: `grain_map`, `mask` and`phase_map`. The microstructure image is actually a 3D image. It is stored as a 3D image as the dataset has been created by extracting a slice of a 3D microstructure dataset. \n", "\n", "These three fields are part of the standard *data model* of the `Microstructure` datasets:\n", "\n", "* `mask`: a field describing the geometry of the sample. It has a 1 value inside the sample, and a 0 value outside the sample (typically, segmented X-ray Contrast Tomography outputs can be used as Microstructure mask). \n", "* `phase_map`: a field indicating for each pixel/voxel the ID of the phase in which it is located. In areas of the Image that are not part of the material sample, the *grain_map* field takes a zero or negative value.\n", "* `grain_map`: a field indicating for each pixel/voxel the ID of the grain in which it is located. In areas of the Image that are not part of the material sample, the *grain_map* field takes a zero or negative value.\n", "\n", "The `phase_map` is empty here. The microstructure only has 1 phase, so this field is not usefull for this dataset." ] }, { "cell_type": "markdown", "id": "5509ce26-5e03-4459-aaef-94df238d5680", "metadata": {}, "source": [ "
\n", "\n", "**Note** \n", " \n", "The length unit for *Pymicro* `CellData` image is millimeters (mm).\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "796f6e5e-6bc1-4b11-b9da-a67e177f09f1", "metadata": {}, "source": [ "The `grain_map`, `mask` and`phase_map` arrays are essential for the `Microstructure`: a wide range of data processing methods can be applied to these arrays, or rely on their values. For instance, they are used to compute statistics of the grains morphology, provide various visualizations of the microstructure, merge two different microstructures into one dataset, create inputs for simulation softwares... Many examples can be found in the documentation tutorials, Cookbook and Examples." ] }, { "cell_type": "markdown", "id": "f8841c10-4183-4da2-a775-d746f18afe79", "metadata": {}, "source": [ "### Get CellData array values" ] }, { "cell_type": "markdown", "id": "55b0cc67-f184-47cb-84f8-7b43f2553302", "metadata": {}, "source": [ "To retrieve these arrays, you can used dedicated methods of the `Microstructure` class..." ] }, { "cell_type": "code", "execution_count": null, "id": "d1b261cc-9e03-47f6-9f72-ac2ec01cf2fc", "metadata": {}, "outputs": [], "source": [ "# import Numpy to process arrays\n", "import numpy as np\n", "\n", "# get arrays in CellData group\n", "mask = micro.get_mask()\n", "phase_map = micro.get_phase_map()\n", "grain_map = micro.get_grain_map()\n", "\n", "print(f'List of values in the mask:\\n {np.unique(mask)}\\n')\n", "print(f'List of values in the phase map:\\n {np.unique(phase_map)}\\n')\n", "print(f'List of values in the grain map:\\n {np.unique(grain_map)}\\n')\n", "\n", "print(f'Number of grains in the grain map: {len(np.unique(grain_map))}')" ] }, { "cell_type": "markdown", "id": "41234d71-1298-45f2-97e0-4d9a80166f25", "metadata": {}, "source": [ "...or using the features of the `SampleData` class ([see dedicated tutorial](./Data_Items.ipynb)) to retrieve data:" ] }, { "cell_type": "code", "execution_count": null, "id": "a40f2203-db4a-48a2-be71-2499843f65bb", "metadata": {}, "outputs": [], "source": [ "# attribute like access\n", "mask2 = micro.mask\n", "print(f'Attribute like access test : {np.all(mask2 == mask)}')\n", "\n", "# dictionary like access\n", "grain_map2 = micro['grain_map']\n", "print(f'Dictionary like access test : {np.all(grain_map2 == grain_map)}')" ] }, { "cell_type": "markdown", "id": "28c55c39-5575-468d-94ef-fff8d8cc1e76", "metadata": {}, "source": [ "### Visualization : View Slice method" ] }, { "cell_type": "markdown", "id": "6d728a51-ce92-45a4-8d29-2c63124d1fde", "metadata": {}, "source": [ "The `Microstructure` class provides the `view_slice` method to plot a slice of the microstructure (the whole image if it is a 2D microstructure) using the `grain_map` and `mask` arrays:" ] }, { "cell_type": "code", "execution_count": null, "id": "1968d4b2-5164-41d6-82af-5a226790f3ef", "metadata": {}, "outputs": [], "source": [ "micro.view_slice()" ] }, { "cell_type": "markdown", "id": "e8edd89f-f54d-47e1-9515-beef4c76515f", "metadata": {}, "source": [ "When no optional argument is provided, the method plots the middle $(X,Y)$-wise slice of the sample grain map, with a random color map for the grain ids, and shows the mask in red on the foreground in transparency mode. The image reveals colored areas that indicate the different grains in the material sample? The mask (in red) reveals the geometry of the sample associated to the dataset. \n", "\n", "The optional arguments of the method are:\n", "\n", "* `plane`: the plane along which the slice is cut, must be one of 'XY', 'YZ' or 'XZ'\n", "* `slice`: the slice index in the arrays of the `CellData` group. 'YZ' and `slice=10` would plot `CellDataField[10,:,:]`\n", "* `color`: a string to chose the colormap from (`random`, `grain_ids`, `schmid`: Schmid factors, `ipf`: Inverse pole figure)\n", "* `show_mask`: a flag to show the mask by transparency.\n", "* `show_grain_ids`: a flag to annotate the plot with the grain ids.\n", "* `highlight_ids`: a list of grain ids to restrict the annotations (by default all grains are annotated).\n", "* `slip_system`: an instance (or a list of instances) of the class SlipSystem to compute the Schmid factor.\n", "* `axis`: the unit vector for the load direction to compute the Schmid factor or to display IPF coloring. \n", "* `show_slip_traces`: activate slip traces plot in each grain.\n", "* `hkl_planes`: the list of planes to plot the slip traces.\n", "* `unit`: switch between mm and pixel units.\n", "* `show_gb`: show the grain boundaries, works only with color='ipf' for now.\n", "* `display`: if True, the show method is called, otherwise, the figure is simply returned.\n", "\n", "Let's some more advanced examples of the visualization possibilities:" ] }, { "cell_type": "code", "execution_count": null, "id": "a7fe17b9-7b47-4980-a109-d594f2fc4cec", "metadata": {}, "outputs": [], "source": [ "# plot Inverse pole figure map of the microstructure and show grain ids + set scale to millimeters\n", "micro.view_slice(show_mask=False, show_grain_ids=True, color='ipf', unit='mm')" ] }, { "cell_type": "code", "execution_count": null, "id": "b0d2a855-5d2a-4d7e-9f1b-33f956e06e15", "metadata": {}, "outputs": [], "source": [ "# plot Schmid factor map of first basal slip system and show traces of all prismatic slip systems\n", "#=============================================================================================\n", "# get one basal slip system to compute its schmid factors and plot them on the slice\n", "from pymicro.crystal.lattice import SlipSystem\n", "lattice = micro.get_phase().get_lattice()\n", "slip_system = lattice.get_slip_systems('basal')[1]\n", "\n", "# get basal slip planes\n", "plane_list = [] \n", "slip_system = lattice.get_slip_systems('prism')[0]\n", "plane_list.append(slip_system.get_slip_plane())\n", "slip_system = lattice.get_slip_systems('prism')[1]\n", "plane_list.append(slip_system.get_slip_plane())\n", "slip_system = lattice.get_slip_systems('prism')[2]\n", "plane_list.append(slip_system.get_slip_plane())\n", "\n", "# plot slice with schmid factor colormap\n", "# set display to True to try reproducing the figure below !\n", "micro.view_slice(color='schmid', slip_system=slip_system, show_mask=False, show_slip_traces=True, hkl_planes=plane_list)\n", "\n", "\n", "# plot same slice with IPF colormap to better visualize slip traces\n", "micro.view_slice(color='ipf', slip_system=slip_system, show_mask=False, show_slip_traces=True, hkl_planes=plane_list)" ] }, { "cell_type": "code", "execution_count": null, "id": "c93c006d-a59c-4f52-a101-024467adb024", "metadata": {}, "outputs": [], "source": [ "del micro" ] }, { "cell_type": "markdown", "id": "26f8d1ab-2b90-4dbc-bc20-d8f4c953ef70", "metadata": {}, "source": [ "### Active Maps" ] }, { "cell_type": "markdown", "id": "0fc72e81-300e-4d6f-a3a5-0ae249231786", "metadata": {}, "source": [ "In some situations, it may be relevant, to store several arrays for the same microstructural map. For instance, when reconstructing a microstructure from an imaging experiment, the raw imaging output is an important data item to store, but may be improved with some image processing. As a result, the dataset may contain more than one array that could be used as a `grain_map` or a `phase_map`.\n", "\n", "The `Microstructure` class uses two attributes, `active_grain_map` and `active_phase_map`, to specify which array is to be used as the main `grain_map` or a `phase_map` for data processing. In practice, `active_grain_map` and `active_grain_map` are *strings* that indicated the name of the array to use, and:\n", "\n", "* the `get_grain_map` and `get_phase_map` return the `active_grain_map` and `active_grain_map` arrays.\n", "* `Microstructure` methods will use the `active_grain_map` and `active_grain_map` as `grain_map` or a `phase_map`. This will impact for instance the `view_slice` method presented above." ] }, { "cell_type": "markdown", "id": "33683a74-b500-4e0f-baaa-79b1198ceda2", "metadata": {}, "source": [ "The dataset `example_microstructure` in `Pymicro/examples` contains several grain_maps. We will use it to illustrate how to use active maps." ] }, { "cell_type": "code", "execution_count": null, "id": "6fa4495b-dc97-4484-9e1f-debf92587c2e", "metadata": {}, "outputs": [], "source": [ "# Open dataset \n", "#=============\n", "dataset_file = os.path.join(PYMICRO_EXAMPLES_DATA_DIR, 'example_microstructure') # test dataset desired file path\n", "tar_file = os.path.join(PYMICRO_EXAMPLES_DATA_DIR, 'example_microstructure.tar.gz') # dataset zipped archive path\n", "\n", "# Save current directory\n", "cwd = os.getcwd()\n", "# move to example data directory\n", "os.chdir(PYMICRO_EXAMPLES_DATA_DIR)\n", "# unarchive the dataset\n", "os.system(f'tar -xvf {tar_file}')\n", "# get back to UserGuide directory\n", "os.chdir(cwd)\n", "\n", "# Open Microstructure dataset\n", "micro = Microstructure(filename=dataset_file)\n", "\n", "# print content of CellData group\n", "micro.print_group_content('CellData', short=True)" ] }, { "cell_type": "markdown", "id": "1a0ecc7b-ac6e-40a2-ab11-490d2499402f", "metadata": {}, "source": [ "This dataset has been built from a synchrotron X-ray diffraction experiment, and store the processed and raw version of the reconstructed microstructure in the `grain_map` and `grain_map_raw` data items. The active grain map can be known from the class attribute:" ] }, { "cell_type": "code", "execution_count": null, "id": "57273c86-1368-43fc-99e4-4bae7c468b12", "metadata": {}, "outputs": [], "source": [ "print(f'The active grain map is \"{micro.active_grain_map}\"')" ] }, { "cell_type": "markdown", "id": "34def201-e8a9-4c32-9062-ecd8ccc92957", "metadata": {}, "source": [ "To set the value of the active grain map, you can use `set_active_grain_map` method: " ] }, { "cell_type": "code", "execution_count": null, "id": "deda24f1-bdd6-4731-81f7-e695e289d26b", "metadata": {}, "outputs": [], "source": [ "# get both grain maps arrays\n", "grain_map = micro['grain_map']\n", "grain_map_raw = micro['grain_map_raw']\n", "\n", "# check class method return grain map\n", "print(f'The active grain map is \"{micro.active_grain_map}\"')\n", "print(f'Is the active grain map equal to \"grain_map\" array ? {np.all(grain_map == micro.get_grain_map())}')\n", "print(f'Is the active grain map equal to \"grain_map_raw\" array ? {np.all(grain_map_raw == micro.get_grain_map())}\\n')\n", "\n", "# change grain map and redo the test\n", "micro.set_active_grain_map('grain_map_raw')\n", "print(f'The active grain map is \"{micro.active_grain_map}\"')\n", "print(f'Is the active grain map equal to \"grain_map\" array ? {np.all(grain_map == micro.get_grain_map())}')\n", "print(f'Is the active grain map equal to \"grain_map_raw\" array ? {np.all(grain_map_raw == micro.get_grain_map())}')\n" ] }, { "cell_type": "markdown", "id": "0a907bf9-370f-4fbd-bad9-a8b9b7c89102", "metadata": { "tags": [] }, "source": [ "As shown above, the `get_grain_map` method return the array that is named after the string stored in the `active_grain_map` attribute of the `Microstructure` class instance. For further illustration, we will see how it impacts visualization with the `view_slice` method:" ] }, { "cell_type": "code", "execution_count": null, "id": "dcf96499-7c75-435c-b5f0-3665e2b7cfbb", "metadata": {}, "outputs": [], "source": [ "# set active grain map and visualize\n", "micro.set_active_grain_map('grain_map')\n", "print(f'The active grain map is \"{micro.active_grain_map}\"')\n", "micro.view_slice()\n", "\n", "# set active grain map and visualize\n", "micro.set_active_grain_map('grain_map_raw')\n", "print(f'The active grain map is \"{micro.active_grain_map}\"')\n", "micro.view_slice()" ] }, { "cell_type": "markdown", "id": "9aaccd42-16b5-4dc2-987a-0ccd8288cac3", "metadata": {}, "source": [ "The `grain_map_raw` field is very similar to the `grain_map` field but contains empty regions around grain boundaries, that come from uncertainties in the microstructure reconstruction process from X-ray diffraction experiments. Several image processing steps have enable to fill these gaps and create a *grain map* with no empty regions, that can be used, for instance, as an input for numerical simulations. " ] }, { "cell_type": "markdown", "id": "bbb9e526-60ee-4a90-88ba-3d8fd29d1633", "metadata": {}, "source": [ "
\n", "\n", "**Note** \n", " \n", "Note that the same mechanism allows to handle several the phase map fields, with the `active_phase_map` class attribute and `set_active_phase_map` method.\n", "\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "31606246-ed05-47b4-9b36-dbc91368614d", "metadata": {}, "source": [ "## Add microstructure maps in datasets" ] }, { "cell_type": "markdown", "id": "3dd35908-f75d-4e3a-ae88-fb4c65bef4de", "metadata": {}, "source": [ "To conclude this tutorial, we will see how to add data into the `CellData` group of a dataset. To set the values of the `grain_map`, `mask` and`phase_map`, you may use the `set_mask` , `set_grain_map` and `set_phase_map` methods of the `Microstructure` class:" ] }, { "cell_type": "code", "execution_count": null, "id": "768efbf8-76bc-4838-a180-e55e7f03a331", "metadata": {}, "outputs": [], "source": [ "# create an empty Microstructure object\n", "micro2 = Microstructure(filename='micro_test', autodelete=True)\n", "# print content of phase data group\n", "print('CellData group at dataset creation:')\n", "micro2.print_node_info('CellData')\n", "\n", "# get grain map from example dataset \n", "micro.set_active_grain_map('grain_map')\n", "grain_map = micro.get_grain_map()\n", "\n", "# set grain map in new microstructure\n", "micro2.set_grain_map(grain_map, voxel_size=micro.get_attribute('spacing','CellData')[0])\n", "\n", "# print content of phase data group and visualize\n", "print('CellData group after setting grain map:')\n", "micro2.print_node_info('CellData')\n", "micro2.view_slice()" ] }, { "cell_type": "markdown", "id": "54bdfa14-4e1c-4c8e-95dd-b90c9692083b", "metadata": {}, "source": [ "The `CellData` group is created empty in a new dataset. For this reason, the `set_grain_map` method requires that the user provides a voxel/pixel size for the image when adding the first microstructural map in the dataset. \n", "\n", "The `set_grain_map` method can also be used to add a second grain map field, by using the `map_name` input argument. Its defaults value, `\"grain_map\"`, leads to overwriting the current `grain_map` array stored in the dataset. Using a different value allows to create a new array in the `CellData` group, and automatically sets it as active grain map: " ] }, { "cell_type": "code", "execution_count": null, "id": "4e2ab707-fc3f-4430-87d8-9c3e9148436c", "metadata": {}, "outputs": [], "source": [ "# use set_grain_map to add raw grain map\n", "micro2.set_grain_map(grain_map=micro['grain_map_raw'], map_name='grain_map_raw')\n", "print(f'The active grain map is \"{micro2.active_grain_map}\"')\n", "micro2.view_slice()" ] }, { "cell_type": "markdown", "id": "4ec671c4-30c7-4310-85cc-0ec215eda516", "metadata": {}, "source": [ "As an *Image Group*, you can also add fields to the `CellData` group with the *SampleData* method `add_field`:" ] }, { "cell_type": "code", "execution_count": null, "id": "1cfede2b-d35d-4c6e-9007-c99186da1497", "metadata": {}, "outputs": [], "source": [ "# add grain map raw into new microstructure \n", "grain_map_raw = micro['grain_map_raw']\n", "micro2.add_field(gridname='CellData', fieldname='grain_map_raw_2', array=grain_map_raw)\n", "\n", "# print content of phase data group and visualize\n", "print('CellData group after adding a second grain map raw array:')\n", "micro2.print_node_info('CellData')\n", "micro2.set_active_grain_map('grain_map_raw_2')\n", "micro2.view_slice()" ] }, { "cell_type": "markdown", "id": "807801da-701b-4bce-b7f4-bbf9f9021119", "metadata": {}, "source": [ "
\n", "\n", "**Note** \n", " \n", "Set the `mask` and `phase_map` field values in the same way as with the `grain_map` field, using the `set_mask` and `set_phase_map` methods.\n", "\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "3eb8e685-6515-4fd5-8d27-4f41d23b9bb2", "metadata": {}, "source": [ "This concludes the tutorial on Pymicro's microstructural maps." ] }, { "cell_type": "code", "execution_count": null, "id": "d2142d74-c45c-4715-8899-4fc17c82985d", "metadata": {}, "outputs": [], "source": [ "del micro2\n", "del micro\n", "os.remove(dataset_file+'.h5')\n", "os.remove(dataset_file+'.xdmf')" ] } ], "metadata": { "kernelspec": { "display_name": "env_dev", "language": "python", "name": "env_dev" }, "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.8.17" } }, "nbformat": 4, "nbformat_minor": 5 }