Parts of this page are not yet available in Python; the MATLAB page has them all.

download as script · edit page

A polished two-dimensional section reveals a grain boundary trace, but not the inclination of the boundary plane. This page shows how calcGBND combines many traces with the orientations on their two sides to estimate preferred boundary planes. It then contrasts that stereological estimate with normals measured directly from three-dimensional grains.

The examples assume familiarity with grain reconstruction, grain boundary properties, and misorientation symmetry.

A grain boundary in a two-dimensional MTEX map is one segment between neighbouring EBSD measurements assigned to different grains. The distributions below describe populations of those segments. They do not recover the boundary plane of an individual segment.

import numpy as np
from mtex import *

A reference frame is the coordinate system in which data are expressed. The specimen frame is fixed to the sample, while a crystal frame is fixed to the lattice basis of one phase.

calcGBND estimates two grain boundary normal distributions (GBNDs) and one conditional distribution:

  • The specimen GBND is the distribution of boundary normals in the specimen frame. It asks whether interfaces prefer a sample direction.
  • The crystal GBND is the distribution after the normals have been expressed in crystal frames. It asks which lattice planes, or habits, the interfaces prefer.
  • The grain boundary character distribution (GBCD) shown here is the crystal GBND restricted to boundaries near one fixed misorientation.

A macroscopic grain boundary has five parameters: three for the misorientation and two for the plane normal. A GBCD at one fixed misorientation is therefore a two-dimensional slice through that five-dimensional description.

A single planar section supplies only one specimen-frame view. It cannot determine the specimen GBND. The different crystal orientations in the map supply multiple crystal-frame views, so the crystal GBND and GBCD can be estimated statistically. Three-dimensional data make all three distributions directly accessible.

The crystal GBND from two-dimensional EBSD data

We use a magnesium map containing tension twins. Magnesium deforms by tension twinning on a \(\{10\bar{1}2\}\) plane. A coherent twin boundary lies on that plane, so its known habit gives the estimate a clear target.

# load the map in its specimen plotting frame
plottingConvention.default('y↑→x')
ebsd = mtexdata('twins')

# reconstruct grains with a 5 degree segmentation threshold
grains = calcGrains(ebsd, angle=5 * degree, minPixel=3)

Preserve the segment-to-pixel relation

The two-dimensional estimator looks up the orientations beside each segment through gB.ebsdId. That property is meaningful only while every segment still lies between one specific pair of pixels.

smoothBoundary normally simplifies and resamples the segment list. The options simplify=False and refine=False smooth the vertex positions without changing that list.

grains = smoothBoundary(grains, 10, simplify=False, refine=False)
CS = grains.CS

# compute IPF colours explicitly to keep the published output quiet
colorKey = ipfColorKey(ebsd)
mapColor = colorKey.orientation2color(ebsd.orientations)

plot(ebsd, mapColor)
hold(True)
plot(grains.boundary, lineWidth=2)
hold(False)

The black network follows the reconstructed grain boundaries. Notice the long, narrow lamellae that cut across the larger magnesium grains. Their morphology suggests twinning, but the following plane analysis tests the crystallographic prediction.

Misorientation angle at grain boundaries

The magnesium tension twins produce a sharp misorientation peak at 86.3 degrees. Separate boundaries above and below 80 degrees to make a first, deliberately coarse comparison. An angle threshold alone does not identify a twin law because different axes can have the same angle.

gB = grains.boundary['indexed']
cond = gB.misorientation.angle() > 80 * degree

plot(ebsd, mapColor)
hold(True)
plot(grains.boundary, lineWidth=3)
plot(gB[cond], lineWidth=2, lineColor='w')
hold(False)

The white traces pick out most of the narrow lamellae. Some high-angle boundaries need not be twins, so this spatial agreement is evidence for the selection rather than a complete classification.

Estimate the two crystal GBNDs

Recovering a three-dimensional distribution from planar traces is a deconvolution. An unconstrained reconstruction can ring around sharp features and become negative. The option nonneg enforces the nonnegative result required for a density.

The halfwidth controls how far each observation is spread in the estimate. See Density Estimation for the resolution–noise trade-off behind this value.

gbnd1 = calcGBND(gB[cond], ebsd, halfwidth=5 * degree, nonneg=True)
gbnd2 = calcGBND(gB[~cond], ebsd, halfwidth=5 * degree, nonneg=True)

# mark the full family of magnesium tension-twin planes
tp = Miller(1, 0, -1, 2, CS, 'hkil')

# use one colour range so weak structure is not visually amplified
contourf(gbnd1, colorRange=[0.5, 1.5], complete=True, upper=True, axisLabels=False)
mtexTitle(r'GBND for $\omega > 80^{\circ}$')
mtexColorMap('parula')
annotate(symmetrise(tp), labeled=True, backgroundColor='w')
nextAxis()
contourf(gbnd2, colorRange=[0.5, 1.5], complete=True, upper=True, axisLabels=False)
mtexTitle(r'GBND for $\omega < 80^{\circ}$')
mtexColorMap('parula')
mtexColorbar()

Both panels are normalized so that a value of one is uniform. The common colour range is essential: otherwise automatic scaling would make the nearly uniform right panel appear as structured as the left.

The high-angle distribution has maxima beside the six symmetrically equivalent \(\{10\bar{1}2\}\) planes. These are full-sphere plots, so all six variants are marked. Marking only one plane would single out one maximum for no crystallographic reason. The remaining boundaries stay much closer to one and show no comparable preferred plane.

highAnglePeak, highAnglePeakPosition = gbnd1.max()
highAngleSummary = {'peakDensity': highAnglePeak,
                    'distanceToTwinPlaneDegree': np.min(highAnglePeakPosition.angle(symmetrise(tp))) / degree}
highAngleSummary
{'peakDensity': 1.391213111695422, 'distanceToTwinPlaneDegree': 5.575478383679621}

remainingSummary = {'minimumDensity': gbnd2.min()[0], 'maximumDensity': gbnd2.max()[0]}
remainingSummary
{'minimumDensity': 0.8590915918782432, 'maximumDensity': 1.0848686147087483}

The peak lies 5.6 degrees from the nearest tension-twin plane. The remaining distribution ranges from 0.86 to 1.09. These values quantify the visual contrast between the panels.

The grain boundary character distribution

The angle split above is intentionally crude. Passing a misorientation as the third argument selects boundaries near that complete relationship and expresses their plane normals consistently. The result is the GBCD slice for that misorientation.

# define the ideal magnesium tension-twin relationship
moriRef = orientation.byAxisAngle(Miller(1, 1, -2, 0, CS, 'uvtw'), 86.3 * degree, CS, CS)

gbcd = calcGBND(gB, grains, moriRef, halfwidth=5 * degree, nonneg=True)

plot(gbcd, contourf=True)
mtexTitle('GBCD for the tension twin')
mtexColorMap('parula')
annotate(symmetrise(tp), labeled=True, backgroundColor='w')
mtexColorbar()

Two of the six \(\{10\bar{1}2\}\) variants carry the maxima. The specimen was deformed, so not every twin variant is equally active. The strongest maximum sits directly on a tension-twin plane.

twinPeak, twinPeakPosition = gbcd.max()
twinGBCDSummary = {'peakDensity': twinPeak,
                   'distanceToTwinPlaneDegree': np.min(twinPeakPosition.angle(symmetrise(tp), symmetry=False)) / degree}
twinGBCDSummary
{'peakDensity': 2.189019735501391, 'distanceToTwinPlaneDegree': 4.8433511616078455}

The printed angular distance is 1.3 degrees. Conditioning on the complete misorientation sharpens the habit-plane result compared with the angle-only split.

Three-dimensional data

A triangulated three-dimensional boundary stores its normal directly, so no stereological reconstruction is required. We use the DREAM.3D data set introduced in Three-Dimensional Grains.

grains3 = grain3d.load(mtexdatafile('SmallIN100'))

# separate internal interfaces from faces on the measured box
allB3 = grains3.boundary
isInnerFace = np.all(allB3.grainId > 0, axis=1)
surfaceFaceAreaPercent = 100 * allB3.area[~isInnerFace].sum() / allB3.area.sum()
surfaceFaceSummary = {'percentOfTotalFaceArea': surfaceFaceAreaPercent}
gB3 = allB3[isInnerFace]
surfaceFaceSummary
{'percentOfTotalFaceArea': 22.840211725967627}

Faces at the surface of the measured volume belong to only one grain. They are the six sides of the measured box, not grain boundaries. They contribute 22.8 percent of the total face area. Their flat, axis-aligned normals would otherwise dominate the specimen GBND.

The specimen GBND

With a three-dimensional boundary as its only argument, calcGBND uses the measured normals in the specimen frame. Each triangular face is weighted by its area.

gbndSpecimen = calcGBND(gB3)

contourf(gbndSpecimen, upper=True)
mtexTitle('specimen GBND')
mtexColorMap('parula')
mtexColorbar()

Read this plot in the specimen frame: the strongest maximum lies near Z. It describes a preferred interface direction in the sample, not a crystallographic habit plane. The distribution ranges from 0.61 to 1.74.

specimenSummary = {'minimumDensity': gbndSpecimen.min()[0], 'maximumDensity': gbndSpecimen.max()[0]}
specimenSummary
{'minimumDensity': 0.6112683632589166, 'maximumDensity': 1.7424354059310012}

The crystal GBND

Passing the grains as well rotates each measured normal into the crystal frame of the grain beside it. Both sides contribute, and each face keeps its area weight.

gbndCrystal = calcGBND(gB3, grains3)

contourf(gbndCrystal, complete=True, upper=True)
mtexTitle('crystal GBND')
mtexColorMap('parula')
mtexColorbar()

The crystal GBND has its maximum of 2.0 at the \(\{111\}\) planes and its minimum of 0.74 at \(\{100\}\). Taken over all internal interfaces, this data set already prefers \(\{111\}\) as the boundary plane.

crystalSummary = {'minimumDensity': gbndCrystal.min()[0], 'maximumDensity': gbndCrystal.max()[0]}
crystalSummary
{'minimumDensity': 0.7411072299996191, 'maximumDensity': 2.004265757082114}

The GBCD in three dimensions

The preference sharpens when the distribution is conditioned on the cubic \(\Sigma 3\) misorientation: a 60 degree rotation about a [111] axis. The ten-degree halfwidth below gives the estimator a twenty-degree inclusion window, matching the implementation of the GBCD kernel.

cs3 = grains3.CSList[1]
sigma3 = orientation.byAxisAngle(Miller(1, 1, 1, cs3), 60 * degree, cs3, cs3)

# measure a strict Sigma 3 fraction and the broader kernel window
grainInd = grains3.id2ind(gB3.grainId)
ori1 = grains3.meanOrientation[grainInd[:, 0]]
ori2 = grains3.meanOrientation[grainInd[:, 1]]
sigma3Deviation = (ori1.inv() * ori2).angle(sigma3)
sigma3Summary = {}
for tol in (5, 20):
  isSigma3 = sigma3Deviation < tol * degree
  sigma3Summary[tol] = {'facePercent': 100 * isSigma3.mean(),
                        'areaPercent': 100 * gB3.area[isSigma3].sum() / gB3.area.sum()}
sigma3Summary
{5: {'facePercent': 26.234542728259434, 'areaPercent': 23.249091855256523}, 20: {'facePercent': 44.814951910710136, 'areaPercent': 42.287972180027914}}
gbcd3 = calcGBND(gB3, grains3, sigma3, halfwidth=10 * degree)

contourf(gbcd3)
mtexTitle(r'GBCD for $\Sigma 3$')
mtexColorMap('parula')
sigma3Plane = Miller(1, 1, 1, cs3)
annotate(symmetrise(sigma3Plane), labeled=True, backgroundColor='w')
mtexColorbar()

Within 5 degrees of the ideal \(\Sigma 3\) relationship lie 26.2 percent of the internal faces, carrying 23.2 percent of the area. This is the sense in which about a quarter of the inner faces are \(\Sigma 3\) twins. The kernel’s 20 degree support admits 44.8 percent of the faces but gives less weight to those farther from the ideal relationship.

Of the four \(\{111\}\) variants, only the variant along the \(\Sigma 3\) rotation axis is strongly populated. These twins are coherent, and the maximum coincides with that boundary plane.

sigma3Peak, sigma3PeakPosition = gbcd3.max()
sigma3GBCDSummary = {'peakDensity': sigma3Peak,
                     'distanceToSigma3PlaneDegree': np.min(sigma3PeakPosition.angle(symmetrise(sigma3Plane))) / degree}
sigma3GBCDSummary
{'peakDensity': 16.30684787994216, 'distanceToSigma3PlaneDegree': 0.011782645270847976}

The peak density is 16.3, about 0.01 degrees from the plane, eight times the maximum of the all-boundary crystal GBND. The \(\{111\}\) preference seen there comes from these twins: they make up about a quarter of the internal faces, and averaging over every misorientation dilutes a habit that is specific to one boundary character.

Interpreting a stereological estimate

The two-dimensional estimator weights every trace segment by its length. The three-dimensional estimator weights every triangular face by its area. Neither result is a count of physical boundaries or twin domains.

Plane normals are axes: a normal and its negative describe the same unoriented plane. Crystal symmetry adds further equivalent normals, which is why every member of a plane family is annotated above.

A single-section estimate also needs enough differently oriented grains to supply varied crystal-frame views. Strong texture can bias this stereological sampling. Compare orthogonal sections or direct three-dimensional data when the anisotropy is important.

References

Next

This page closes the grain boundary chapter by moving from individual traces to a population of plane normals. Continue with Three-Dimensional Grains to inspect the mesh faces used in the direct calculation, or with Spherical Functions to work with the resulting density objects.

Technical Details

calcGBND estimates from a planar section with the default kernel, which inverts the integral equation of the section and agrees with MATLAB’s to the digit, or with the nonneg and bump kernels. The crystal GBND takes the symmetry of the phase, as the numbers of the page require: without it the peak lies 8.4 degrees from the twin plane. The high-angle GBND peaks at 1.3912, 5.58 degrees from the twin plane, for MATLAB’s 1.3905 and 5.60. The tension-twin GBCD peaks at 2.19, 4.8 degrees from the twin plane, where MATLAB’s peaks at 2.10, 1.3 degrees from it, and the Julia port at about one degree; both take the same 1648 segments within ten degrees of the twin relation, whose traces lie as close to the twin planes in both, so the difference is in how the traces are assigned to the equivalent planes, and it is open. The three-dimensional half gives MATLAB’s numbers. MATLAB’s tables are dictionaries here.

Citing this page. This page is part of the documentation of MTEX, a free and open source MATLAB toolbox for analyzing and modeling crystallographic textures. It was written by The MTEX Developers and is published at https://mtex-toolbox.github.io/BoundaryNormalDistribution_py.html. If you use MTEX, or reuse text or figures from this page, in your research, please cite

F. Bachmann, R. Hielscher, H. Schaeben: Texture Analysis with MTEX - Free and Open Source Software Toolbox, Solid State Phenomena 160 (2010), 63-68. 10.4028/www.scientific.net/SSP.160.63

BibTeX
@article{bachmann2010mtex,
  author  = {F. Bachmann and R. Hielscher and H. Schaeben},
  title   = {Texture Analysis with MTEX - Free and Open Source Software Toolbox},
  journal = {Solid State Phenomena},
  volume  = {160},
  pages   = {63-68},
  year    = {2010},
  doi     = {10.4028/www.scientific.net/SSP.160.63},
  url     = {https://doi.org/10.4028/www.scientific.net/SSP.160.63}
}

Other papers describing specific MTEX methods are listed under Publications — please cite the one that best fits your application. The MTEX source code is licensed under the GNU General Public License v2.0; the text and figures of this documentation are licensed under CC BY 4.0, which permits reuse — including by automated systems — provided The MTEX Developers and this page are credited.