{ "cells": [ { "cell_type": "markdown", "id": "67c9e5c3-ee6f-4934-8c89-6134540ebc83", "metadata": {}, "source": [ "# Grain Data " ] }, { "cell_type": "markdown", "id": "d8ccc98a-b48b-4469-b00d-6496403de02a", "metadata": {}, "source": [ "To study the *microstructure-properties* relationship for polycrystalline materials, collecting morphological and physical properties of the grains constituting a microstructure is an important task, that complements the study of microstructure images. This tutorial will review how to store, load and process grain data with *Pymicro*.\n", "\n", "In the `Microstructure` class data model, the `GrainData` group is aimed at storing statistical data describing the sample grains, that are mostly stored within a structured array, the **GrainDataTable**. \n", "\n", "One of Pymicro's example datasets will be used to study this group. Let's start by opening it with the `Microstructure` class and display the content of this group:" ] }, { "cell_type": "code", "execution_count": null, "id": "1ae9463c-c1e9-4a2e-be40-ae0389f809bb", "metadata": {}, "outputs": [], "source": [ "from pymicro import get_examples_data_dir # import file directory path\n", "PYMICRO_EXAMPLES_DATA_DIR = get_examples_data_dir() # get the file directory path\n", "import os \n", "\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('GrainData')\n", "micro.print_group_content('GrainData', short=True)" ] }, { "cell_type": "markdown", "id": "3479522f-7db8-49cf-bfc3-486f386da372", "metadata": {}, "source": [ "## The Grain Data Table" ] }, { "cell_type": "markdown", "id": "9eb22593-a8ce-49b3-9c0c-2d1c5a8288eb", "metadata": {}, "source": [ "The `GrainDataTable` mentioned above is the only data item stored in the `GrainData` group in the standard data model of the `Microstructure` class. Let's take a look at its content:" ] }, { "cell_type": "code", "execution_count": null, "id": "40064569-ba85-4a52-bc54-b46c4a931c90", "metadata": {}, "outputs": [], "source": [ "print(type(micro['GrainDataTable']))\n", "print(micro['GrainDataTable'].dtype)\n", "print(micro['GrainDataTable'])" ] }, { "cell_type": "markdown", "id": "af5d0232-f77a-4f63-9184-812c8d91322a", "metadata": {}, "source": [ "\n", "### Get values from the GrainDataTable" ] }, { "cell_type": "markdown", "id": "910c9529-12ee-4d9a-a0b1-61cb081bb48a", "metadata": { "tags": [] }, "source": [ "The `GrainDataTable` stores a structured table, that can be retrieved as a *Numpy* structured array. As shown below, its fields provide the following information on the sample grains:\n", "\n", "* an identity number of the grain\n", "* two columns describing the grain geometry: volume and center (position of center of mass in sample)\n", "* the orientation of the grain provided as a **Rodrigues** vector\n", "* the indices of the grain bounding box in the `CellData` image field arrays\n", "\n", "To retrieve those values, you can use standard manipulation commands for `SampleData` structured tables and numpy arrays (see [dedicated tutorial](./Data_Items.ipynb)), or dedicated `Microstructure` class methods, such as `get_grain_centers`:\n", "\n", "* `get_grain_centers`\n", "* `get_grain_bounding_boxes`\n", "* `get_grain_volumes`" ] }, { "cell_type": "code", "execution_count": null, "id": "8c24724c-d9e4-47b1-9a79-4e609f018a28", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "# retrieve table as numpy structured array with SampleData dictionary like access\n", "GrainDataTable = micro['GrainDataTable']\n", "\n", "# get table columns from class methods and compare to arrays got with numpy array manipulation\n", "grain_ids = micro.get_grain_ids()\n", "print(f'grain ids equal ? {np.all(grain_ids == GrainDataTable[\"idnumber\"])}')\n", "grain_centers = micro.get_grain_centers()\n", "print(f'grain centers equal ? {np.all(grain_centers == GrainDataTable[\"center\"])}')\n", "grain_volumes = micro.get_grain_volumes()\n", "print(f'grain volumes equal ? {np.all(grain_volumes == GrainDataTable[\"volume\"])}')\n", "grain_bboxes = micro.get_grain_bounding_boxes()\n", "print(f'grain bounding boxes equal ? {np.all(grain_bboxes == GrainDataTable[\"bounding_box\"])}')\n", "grain_rodrigues = micro.get_grain_rodrigues()\n", "print(f'grain orientations equal ? {np.all(grain_rodrigues == GrainDataTable[\"orientation\"])}')" ] }, { "cell_type": "markdown", "id": "9b324102-bee8-4560-99dd-426ce1d8df58", "metadata": {}, "source": [ "Other methods to get specific grain data are also available in the class interface:" ] }, { "cell_type": "code", "execution_count": null, "id": "94f2f0ea-924e-40c8-8164-ea6f67cb2610", "metadata": {}, "outputs": [], "source": [ "centers = micro.get_grain_positions()\n", "print(f'The position of the 10 first grain centers of mass are:\\n {centers[:10]}\\n')\n", "\n", "volume_fractions = micro.get_grain_volume_fractions()\n", "print(f'The 10 first grain volume fractions are:\\n {volume_fractions[:10]}\\n')\n", "\n", "volume_fr = micro.get_grain_volume_fraction(18)\n", "print(f'Volume fraction of grain 18 is {volume_fr*100:1.3f}%')" ] }, { "cell_type": "markdown", "id": "9be6b538-b405-4072-bfe1-bb3006681381", "metadata": {}, "source": [ "### The grains attribute" ] }, { "cell_type": "markdown", "id": "8274deeb-7792-49d9-8520-26a70701e76b", "metadata": {}, "source": [ "The `Microstructure.grains` attribute is an alias for the *GrainDataTable* data item. As such, it allows to manipulate and interact directly with the *GrainDataTable*. You can use it to access grain data just like you would manipulate a *Numpy* structured array: " ] }, { "cell_type": "code", "execution_count": null, "id": "d8e9a2df-081e-4dd2-98d7-89b58c64c45c", "metadata": {}, "outputs": [], "source": [ "print(micro.grains[0]['center'],'\\n')\n", "print(micro.grains[4:10]['orientation'])" ] }, { "cell_type": "markdown", "id": "78451269-e85f-49f0-bf22-1a6f2deaf903", "metadata": {}, "source": [ "Hence, getting an information on a grain whose id number is known, can be done with a call of the form `micro.grains[\"idnumber\"]['\"information\"']`." ] }, { "cell_type": "markdown", "id": "33416b04-f69a-4c02-b6a5-853499a2b765", "metadata": { "tags": [] }, "source": [ "### Iterate Grains" ] }, { "cell_type": "markdown", "id": "f7813cea-36a3-4334-a355-e21e9a79e185", "metadata": {}, "source": [ "The `GrainDataTable` can also be iterated. The iteration is done row by row of the structured array, and the iterator return these rows during a for loop: " ] }, { "cell_type": "code", "execution_count": null, "id": "18c6c878-eefd-468d-a07e-2a671b6efc8c", "metadata": {}, "outputs": [], "source": [ "# iterate through grains with ID number below 200 and print center of masss\n", "for g in micro.grains:\n", " if g[\"idnumber\"] > 100:\n", " break\n", " print(f'Grain {g[\"idnumber\"]} center of mass is located at {g[\"center\"]}')" ] }, { "cell_type": "markdown", "id": "2a2b5a40-d037-42ca-93cb-9f9b1f0f57ad", "metadata": {}, "source": [ "These row object can be manipulated just like *Numpy* structured arrays. They have the same fields as the `GrainDataTable`." ] }, { "cell_type": "markdown", "id": "cbd0c134-56d8-4d25-8734-9c3f48b0d9f0", "metadata": {}, "source": [ "## Grain Objects" ] }, { "cell_type": "markdown", "id": "11daccdf-251e-4833-843e-1525d39118d4", "metadata": {}, "source": [ "*Pymicro* also has *Grain* objects that are specific containers equivalent to a row of the dataset *GrainDataTable*. You can get them with the following methods: " ] }, { "cell_type": "code", "execution_count": null, "id": "cf4ad9b9-df95-4f28-a9a9-c964d78f55e8", "metadata": { "tags": [] }, "outputs": [], "source": [ "# get the grain object of a specific grain\n", "grain = micro.get_grain(18)\n", "print(f'Grain 18 grain object:\\n {grain}')" ] }, { "cell_type": "markdown", "id": "1c1fefcf-45e0-474f-a806-e9e5814f6cb3", "metadata": {}, "source": [ "**As you can see, a `Grain` object is essentially defined by its identity number and is crystal orientation**. In addition, it also stores the position of the grain (`center` attribute).\n", "\n", "The `Grain` class provides methods to get physical or crystallographic information on the grain (volume, Schmid factor, Bragg condition, orientation...). For instance, to compute the Schmid factor of the grain for a given slip system, you can proceed as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "39a51b43-4940-48f4-934c-2877dbe9c7be", "metadata": {}, "outputs": [], "source": [ "# get the lattice of the sample phase\n", "lattice = micro.get_phase().get_lattice()\n", "\n", "# get Schmid factor of second grain in microstructure for first basal slip system\n", "grain_id = GrainDataTable['idnumber'][1]\n", "grain = micro.get_grain(grain_id)\n", "Schmid = grain.schmid_factor(lattice.get_slip_systems('basal')[0]) \n", "print(f'Schmid factor of grain {grain.id} for first basal slip system is {Schmid:1.3f}')\n", "\n", "# get Schmid factor of grain 18 for third prismatic slip system\n", "grain = micro.get_grain(18)\n", "Schmid = grain.schmid_factor(lattice.get_slip_systems('prism')[2]) \n", "print(f'Schmid factor of grain {grain.id} for first basal slip system is {Schmid:1.3f}')" ] }, { "cell_type": "markdown", "id": "50435fdd-319d-4837-9b21-0e9d2d894138", "metadata": {}, "source": [ "You may also get a list of grain objects that includes all grains in the dataset, with the `get_all_grains` method:" ] }, { "cell_type": "code", "execution_count": null, "id": "4fa413e0-a44f-42b0-b5c3-a4c87632189b", "metadata": {}, "outputs": [], "source": [ "# get a list of all grain objects in the microstructure\n", "grains_list = micro.get_all_grains()\n", "print(f'First 2 grain objects of the microstructure:\\n {grains_list[:2]}')" ] }, { "cell_type": "markdown", "id": "844cb952-4cd2-4df3-b7c0-332b8e4cc0de", "metadata": {}, "source": [ "## Set GrainDataTable values" ] }, { "cell_type": "markdown", "id": "446b377d-5874-4d4e-965f-4bb79d874a85", "metadata": {}, "source": [ "To conclude this tutorial, we will see how to add data into the `GrainDataTable` of a dataset. First, a new microstructure must be created to serve as an example:" ] }, { "cell_type": "code", "execution_count": null, "id": "16fe3f2c-03c2-484f-995e-10e8452a96bf", "metadata": {}, "outputs": [], "source": [ "# create an empty Microstructure object\n", "micro2 = Microstructure(filename='micro_test', autodelete=True, overwrite_hdf5=True)\n", "# print content of grain data table\n", "print(f'Current grain data table {micro2.grains}')" ] }, { "cell_type": "markdown", "id": "768ad43b-fe6e-489a-9fd4-0f77318d0db3", "metadata": {}, "source": [ "### Add grains to the Grain Data Table" ] }, { "cell_type": "markdown", "id": "1a87445c-2522-4ebc-8995-dfcdefe52b27", "metadata": {}, "source": [ "New grains can be added to the `GrainDataTable`, it can be initialized from a list of grain orientations, with the `add_grains` method. To illustrate that, we will initialize the table of the new microstructure from the orientations of the example dataset:" ] }, { "cell_type": "code", "execution_count": null, "id": "8fde2cf7-03e2-465f-8711-e49f4f229332", "metadata": {}, "outputs": [], "source": [ "# get example dataset orientations\n", "orientations = micro.grains[:]['orientation']\n", "\n", "# count number of grains in the table\n", "print(f'The microstructure has {micro2.get_number_of_grains()} grains')\n", "\n", "# add new grains to new microstructure \n", "# orientations in GrainDataTable are stored with Rodrigues vector \n", "micro2.add_grains(orientation_list=orientations, orientation_type='rod')\n", "\n", "# print content of grain data table\n", "print(f'Current grain data table content (first 5 grains) \\n {micro2.grains[:5]}')\n", "print(f'Current grain ids (first 10 grains) \\n {micro2.grains[:10][\"idnumber\"]}')\n", "\n", "# count number of grains in the table\n", "print(f'The microstructure has {micro2.get_number_of_grains()} grains')" ] }, { "cell_type": "markdown", "id": "86ac5382-5d48-47b6-bf5d-48a67208a724", "metadata": {}, "source": [ "As you can see 21 new grains have been created from the crystal orientation provided to the `add_grains` method. Their `idnumber` has been initialized from 0 to the n umber of added grains, and all other values (centers, volumes, bounding boxes) have been initialized to zero. If we repeat the operation, we will add again 21 grains to the microstructure, with the same orientations: " ] }, { "cell_type": "code", "execution_count": null, "id": "620392f1-eb14-42d3-be6a-5187cb652393", "metadata": {}, "outputs": [], "source": [ "# count number of grains in the table\n", "print(f'The microstructure has {micro2.get_number_of_grains()} grains')\n", "\n", "# add new grains to new microstructure \n", "# orientations in GrainDataTable are stored with Rodrigues vector \n", "micro2.add_grains(orientation_list=orientations, orientation_type='rod')\n", "\n", "# count number of grains in the table\n", "print(f'The microstructure has now {micro2.get_number_of_grains()} grains')" ] }, { "cell_type": "markdown", "id": "8567ab98-0473-4f0a-8512-fa57f103fbaf", "metadata": {}, "source": [ "### Set values from arrays" ] }, { "cell_type": "markdown", "id": "8b631cff-d619-4c84-8e0a-11cb3f8f627e", "metadata": {}, "source": [ "The methods to get values from the table, such as `get_grain_centers`, that have been shown [above](#l1) , have a counterpart to set these values, such as the `set_grain_centers`:\n", "\n", "* `set_centers`\n", "* `set_bounding_boxes`\n", "* `set_volumes`\n", "\n", "These methods allow to set all the values of a `GrainDataTable` column. Hence their input must have the appropriate shape:" ] }, { "cell_type": "code", "execution_count": null, "id": "714feed3-f670-44ca-909b-c37ad1b49402", "metadata": {}, "outputs": [], "source": [ "# set grain centers of new microstructure from centers of example dataset\n", "centers = micro.get_grain_centers()\n", "\n", "# new microstructure has new 42 grains \n", "# set new microstructure centers : 42 values must be passed \n", "micro2.set_centers(np.concatenate((centers, centers)))\n", "\n", "# set grain centers of new microstructure from centers of example dataset\n", "volumes = micro.get_grain_volumes()\n", "\n", "# new microstructure has new 42 grains \n", "# set new microstructure centers : 42 values must be passed \n", "micro2.set_volumes(np.concatenate((volumes, volumes)))\n", "\n", "# set grain centers of new microstructure from centers of example dataset\n", "bbox = micro.get_grain_bounding_boxes()\n", "\n", "# new microstructure has new 42 grains \n", "# set new microstructure centers : 42 values must be passed \n", "micro2.set_bounding_boxes(np.concatenate((bbox, bbox)))" ] }, { "cell_type": "code", "execution_count": null, "id": "de7a09e5-71ff-40dc-a596-1560640bec83", "metadata": {}, "outputs": [], "source": [ "# print content of grain data table\n", "print(f'Current grain data table \\n {micro2.grains[:]}')" ] }, { "cell_type": "markdown", "id": "5c680c1f-ff8b-40d6-aa6b-0adbcfee05c3", "metadata": {}, "source": [ "The `GrainDataTable` has been now completely filled, and can be used to compute statistics or for various grain related data processings. " ] }, { "cell_type": "markdown", "id": "e458caf7-c594-4341-b8ce-d6abdd2a5af6", "metadata": {}, "source": [ "### Setting data for a specific grain" ] }, { "cell_type": "markdown", "id": "1afdba79-e287-4f1d-b049-f021822dece9", "metadata": {}, "source": [ "To change the data stored for a specific grain, you must iterate the table to find your grain object and set one of its values as if it was a *Numpy* structured array. Then you have to use the specific `update` method on the grain row to set the value in the dataset, as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "9d32cf45-185a-48b3-8e90-2fc01a020c30", "metadata": {}, "outputs": [], "source": [ "# get old orientation value \n", "grain_orientation = micro2.GrainDataTable[15]['orientation']\n", "print(f'The orientation of the grain is {micro2.GrainDataTable[15][\"orientation\"]}')\n", "\n", "# iterate to find the grain and set its orientation to a random value \n", "for g in micro2.grains:\n", " if g['idnumber'] == 15:\n", " g['orientation'] = np.random.rand(3)\n", " g.update()\n", "print(f'The new orientation of the grain is {micro2.GrainDataTable[15][\"orientation\"]}')\n", "\n", "# Set back the original value of the orientation \n", "for g in micro2.grains:\n", " if g['idnumber'] == 15:\n", " g['orientation'] = grain_orientation\n", " g.update()\n", "\n", "print(f'The orientation of the grain is back at {micro2.GrainDataTable[15][\"orientation\"]}')" ] }, { "cell_type": "code", "execution_count": null, "id": "a6f2a34b-93de-44f2-89ad-673a5b637bb7", "metadata": {}, "outputs": [], "source": [ "del micro2" ] }, { "cell_type": "markdown", "id": "b2eb00e9-09fc-4bed-a1ea-426a0501cad3", "metadata": {}, "source": [ "### Setting grain data from the Grain Map" ] }, { "cell_type": "markdown", "id": "e1c8ff2b-0e55-47ab-b82e-26b8cf22f0bc", "metadata": {}, "source": [ "The `GrainDataTable` contains data describing the grains position and morphology. These values can be computed from the `grain map` (see [dedicated tutorial](./Cell_Data.ipynb)), that contains the information of the geometry of each grain. Specific methods of the `Microstucture` class allow to compute those values and automatically fill the `GrainDataTable` with them. They are:\n", "\n", "* `recompute_grain_centers`: computes and fills the `center` column of the *GrainDataTable* from the grains geometry in grain map\n", "* `recompute_grain_volumes`: computes and fills the `volume` column of the *GrainDataTable* from the grains geometry in grain map\n", "* `recompute_grain_bounding_boxes`: computes and fills the `bounding_box` column of the *GrainDataTable* from the grains geometry in grain map\n", "\n", "If you need to call them all, you can do it at once with the `build_grain_table_from_grain_map`, that will first synchronize the grain ids that are in the `grain map` and the `GrainDataTable`, and then call the 3 previous methods to fill the geometric grain data in the table. Note that the \n", "\n", "**Microstructure datasets have been designed to have the `GrainDataTable` and the `grain_map` synchronized. Try to keep consistent values in your datasets to use all Pymicro's functionalities.** \n", "\n", "This is an alternative way to initialize the `GrainDataTable` than the one shown above, that starts from grain orientations. This latter method is well suited for datasets created from imaging experiments.\n", "\n", "Here is an example of out it can be done:" ] }, { "cell_type": "code", "execution_count": null, "id": "0ff0295d-6bef-41c5-addf-daecde163cc3", "metadata": {}, "outputs": [], "source": [ "# create an empty Microstructure object\n", "micro2 = Microstructure(filename='micro_test', autodelete=True, overwrite_hdf5=True)\n", " \n", "# print content of grain data table\n", "print(f'Current grain data table {micro2.grains} \\n')" ] }, { "cell_type": "code", "execution_count": null, "id": "02e4858e-691f-4424-815d-c45c3c4b7e5c", "metadata": {}, "outputs": [], "source": [ "# set the grain map from example dataset \n", "micro2.set_grain_map(micro.get_grain_map(), voxel_size=micro.get_attribute('spacing','CellData')[0])\n", "\n", "# build the grain data table from the grain map\n", "micro2.build_grain_table_from_grain_map()\n", "\n", "# print new grain data table\n", "print(micro2.GrainDataTable)" ] }, { "cell_type": "markdown", "id": "9ef09f4e-e617-4fb1-a1c1-6c493166231f", "metadata": { "tags": [] }, "source": [ "The table has been created with its `'center', 'bounding_box', 'volume'` columns that have been computed from the `grain_map`. The grain orientations have been initialized with a random value. To set user defined values, the `set_orientations` method can be used:" ] }, { "cell_type": "code", "execution_count": null, "id": "f1604f10-af4d-453f-a29c-e76bff365bfc", "metadata": {}, "outputs": [], "source": [ "# get orientations from example dataset\n", "orientations = micro.get_grain_rodrigues()\n", "\n", "# set orientations in new microstructure\n", "micro2.set_orientations(orientations)\n", "\n", "# print content of grain data table\n", "print(micro2.GrainDataTable)" ] }, { "cell_type": "markdown", "id": "e6d8b8eb-3922-49f5-97c8-1ec308054ae8", "metadata": {}, "source": [ "This concludes the tutorial on Pymicro's grain data !" ] }, { "cell_type": "code", "execution_count": null, "id": "01a3a721-387c-4d7a-b059-4037cbc70973", "metadata": {}, "outputs": [], "source": [ "del micro2\n", "del micro" ] } ], "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 }