This page reconstructs prior austenite with a grain graph. It uses the same martensite data as Parent Austenite Reconstruction, but replaces that page's variant graph with a graph having one node per grain.
The distinction matters. A variant graph decides among candidate variants before merging, while a grain graph reasons directly about compatibility between neighbouring grains. This page concentrates on that second route.
plottingConvention.default('y↑→x');
mtexdata martensiteebsd = EBSDsquare (y↑→x, row↑→col)
Phase Orientations Mineral Color Symmetry Crystal reference frame
0 92415 (27%) notIndexed none
1 251187 (73%) Iron bcc (old) LightSkyBlue 432
Properties: bands, bc, bs, error, mad, reliabilityindex, oldId
Scan unit : um
X × Y × Z : [0 → 353] × [0 → 242] × [0 → 0]
Normal vector: (0,0,1)
Square grid :486 × 707Segment the child grains
The 3-degree segmentation is deliberately finer than the parent structure. It removes grains below five pixels and smooths the retained boundaries.
[grains,ebsd] = calcGrains(ebsd,'angle',3*degree,...
'minPixel',5,'alpha',6.1);
grains = smoothBoundary(grains,5);
plot(ebsd('Iron bcc'),ebsd('Iron bcc').orientations,...
'figSize','large')
hold on
plot(grains.boundary,'lineWidth',2)
hold off
The black child boundaries divide larger groups of related orientation colours. The graph will test which of those boundaries can disappear when the child grains are assigned a common parent.
Set up and fit the orientation relationship
The earlier steel page introduces parentGrainReconstructor and its phase guess. We again start from the Kurdjumov-Sachs (KS) parent-to-child OR. A Nishiyama-Wassermann OR could be supplied as another initial guess.
job = parentGrainReconstructor(ebsd,grains);
initialP2C = orientation.KurdjumovSachs(job.csParent,job.csChild);
job.p2c = initialP2C
initialFit = job.calcGBFit ./ degree;
fitQuantilesInitial = quantile(initialFit,[0.2 0.4 0.6 0.8])job = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 0 0% 0%
child Iron bcc (old) 432 4466 100%
OR: (111) || (011) [101̅] || [111̅]
c2c fit: 2.4°, 3.4°, 4.4°, 5.5° (quintiles)
fitQuantilesInitial =
2.3630
3.3709
4.4305
5.5006The former page reported initial fit quintiles of 2.5, 3.5, 4.5, and 5.5 degrees. This run gives 2.363, 3.371, 4.431, and 5.501 degrees. Parent Austenite Reconstruction shows how to overlay the full histograms and map the fit onto boundary segments.
job.calcParent2Child
optimizedFit = job.calcGBFit ./ degree;
fitQuantilesOptimized = quantile(optimizedFit,[0.2 0.4 0.6 0.8])
changeFromKS = angle(initialP2C,job.p2c) ./ degreeans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 0 0% 0%
child Iron bcc (old) 432 4466 100%
OR: (348.1°,10.3°,55.9°)
c2c fit: 1.5°, 2°, 2.5°, 3.9° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
fitQuantilesOptimized =
1.4531
1.9681
2.5111
3.9723
changeFromKS =
3.1100 calcParent2Child uses the iterative method of Nyyssönen and co-workers. It assumes that most measured boundaries are child-to-child boundaries. The first fit quintile is 1.453 degrees, consistent with the former rounded value of 1.5 degrees. The change from KS is 3.110 degrees, not 2.3 degrees. The 2.3-degree value in the displayed summary is the distance to the closest rounded ideal OR, so the earlier text compared two different quantities.
Older text said the command stored the misfits in job.fit. The current class has no such property. calcGBFit computes them on demand and also returns their child-grain neighbour pairs. The former reference also paired an unrelated title with a 2018 DOI. The References section gives the OR-fitting paper and DOI used here.
Build the grain graph
A grain graph has one node per grain and one edge per shared grain boundary. An edge weight expresses how compatible the two grains are with a common parent orientation under the fitted OR.
calcGraph derives that weight from the child-to-child misorientation fit. The threshold is the misfit whose weight is exactly one half. The tolerance controls the width of the high-to-low transition. Earlier text called tolerance a Gaussian standard deviation, but the implementation uses it directly as the transition-width parameter.
job.calcGraph('threshold',2.5*degree,...
'tolerance',2.5*degree)
graphWeights = nonzeros(triu(job.graph,1));
numGraphEdges = length(graphWeights)
graphWeightQuantiles = quantile(graphWeights,[0.1 0.5 0.9])ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 0 0% 0%
child Iron bcc (old) 432 4466 100%
OR: (348.1°,10.3°,55.9°)
c2c fit: 1.5°, 2°, 2.5°, 4° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
graph: 4466 grains in 1 clusters + 2 single grain clusters
numGraphEdges =
11154
graphWeightQuantiles =
0.0080
0.6677
0.9464The printed count is the number of weighted neighbour relations retained by the graph: 11,154 in this run. Its 10th, 50th, and 90th weight percentiles are 0.0080, 0.6677, and 0.9464.
Read the graph on the map
plotGraph maps the edge weights back to their grain boundaries.
plot(ebsd('Iron bcc'),ebsd('Iron bcc').orientations,...
'figSize','large','faceAlpha',0.5)
hold on
job.plotGraph('lineWidth',2)
hold off
Faint boundaries have high graph weight and are candidates to lie inside one parent grain. Opaque boundaries have weak support and should separate clusters. This inverse transparency is deliberate: likely internal boundaries visually recede.
Cluster the grain graph
clusterGraph uses Markov clustering to turn compatible nodes into components. inflationPower controls their granularity. A smaller value produces fewer, larger clusters; a larger value separates the graph more aggressively.
job.clusterGraph('inflationPower',1.6)ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 0 0% 0%
child Iron bcc (old) 432 4466 100%
OR: (348.1°,10.3°,55.9°)
c2c fit: 1.5°, 1.9°, 2.5°, 3.9° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
graph: 4414 grains in 237 clusters + 54 single grain clustersThe displayed graph summary gives clustered grains, component count, and single-grain components. Here 4,414 grains form 237 components and 54 components contain only one grain. These are hypotheses, not yet accepted parent grains.
Transform grains from the graph components
calcParentFromGraph fits one common parent to every component. It then assigns each member its compatible candidate parent orientation. This is the transform step introduced on the earlier steel page. It does not yet merge the component footprints.
job.calcParentFromGraph
numGraphTransformed = nnz(job.isTransformed)
plot(job.parentGrains,job.parentGrains.meanOrientation)ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 4414 100% 99%
child Iron bcc (old) 432 52 0.26%
OR: (348.1°,10.3°,55.9°)
p2c fit: 4.9°, 6.9°, 13°, 21° (quintiles)
c2c fit: 5.8°, 5.8°, 5.8°, 5.8° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
numGraphTransformed =
4414
Most child grains now appear in the parent phase. The graph transforms 4,414 grains and leaves 52 child grains unchanged. Neighbouring fragments with similar colours belong to graph components that still await quality control and merging.
Diagnose and reject weak components
job.grains.fit is the angular mismatch between each transformed grain's candidate parent and the fitted parent of its component. job.grains.clusterSize is the number of grains in that component.
finiteGraphFit = job.grains.fit(isfinite(job.grains.fit)) ./ degree;
graphFitQuantiles = quantile(finiteGraphFit,[0.5 0.9 0.99])
plot(job.grains,job.grains.fit./degree)
setColorRange([0,5])
mtexColorbar('title','fit (degrees)')graphFitQuantiles =
2.1835
4.1930
19.1252
Low values inside a large component support its common parent. High values and isolated small components expose assignments that should be undone before boundary votes use them as neighbours.
rejectGraphAssignment = job.grains.fit > 5*degree | ...
job.grains.clusterSize < 10;
numGrainsFailingGraphCriteria = nnz(rejectGraphAssignment)
job.revert(rejectGraphAssignment)
plot(job.parentGrains,job.parentGrains.meanOrientation)numGrainsFailingGraphCriteria =
848
ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 3620 89% 81%
child Iron bcc (old) 432 846 11%
OR: (348.1°,10.3°,55.9°)
p2c fit: 4.4°, 9.6°, 13°, 21° (quintiles)
c2c fit: 1.3°, 1.8°, 2.3°, 3.4° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
revert restores the measured child phase and orientation for the rejected grains. The criteria flag 848 grains. Of those, 54 carry no graph assignment to undo, so this call reverts 794 of them and leaves 846 child grains. Called without an argument, job.revert undoes the entire reconstruction. That full reset is useful when comparing graph parameters.
Recover rejected grains with boundary votes
Each boundary to an accepted parent grain votes for a candidate parent of the neighbouring child grain. Three passes let newly transformed grains support the next frontier. Their thresholds relax from 2.5 to 5 and then 7.5 degrees.
childGrainsAfterVote = zeros(1,3);
for k = 1:3
job.calcGBVotes('p2c','threshold',k*2.5*degree);
job.calcParentFromVote
childGrainsAfterVote(k) = length(job.childGrains);
end
childGrainsAfterVote
plot(job.parentGrains,job.parentGrains.meanOrientation)ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 3852 93% 86%
child Iron bcc (old) 432 614 7.1%
OR: (348.1°,10.3°,55.9°)
p2c fit: 7.8°, 11°, 15°, 23° (quintiles)
c2c fit: 1.4°, 1.9°, 2.4°, 3.5° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
votes: 1 × 1
probabilities: 0%, 0%, 0%, 0% (quintiles)
ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 4079 95% 91%
child Iron bcc (old) 432 387 4.5%
OR: (348.1°,10.3°,55.9°)
p2c fit: 11°, 13°, 19°, 24° (quintiles)
c2c fit: 1.4°, 1.9°, 2.4°, 3.4° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
votes: 4 × 1
probabilities: 0%, 0%, 0%, 0% (quintiles)
ans = parentGrainReconstructor
phase mineral symmetry grains area reconstructed
parent Iron fcc 432 4292 98% 96%
child Iron bcc (old) 432 174 1.9%
OR: (348.1°,10.3°,55.9°)
p2c fit: 17°, 20°, 24°, 26° (quintiles)
c2c fit: 1.4°, 2°, 2.6°, 3.2° (quintiles)
closest ideal OR: (111) || (011) [11̅0] || [100] fit: 2.3°
childGrainsAfterVote =
614 387 174
The printed sequence should decrease as the reconstructed neighbourhood expands. It is 614, 387, and 174 child grains in this run. The final map fills most rejected regions without accepting their original weak graph assignments.
Merge parent fragments and inclusions
The earlier steel page defines the separate transform and merge steps. mergeSimilar now combines neighbouring parent fragments within 7.5 degrees. mergeInclusions then absorbs enclosed grains containing at most 50 pixels into their parent host.
numParentsBeforeMerge = length(job.parentGrains)
job.mergeSimilar('threshold',7.5*degree);
numParentsAfterSimilarMerge = length(job.parentGrains)
job.mergeInclusions('maxSize',50);
numParentsAfterInclusionMerge = length(job.parentGrains)
plot(job.parentGrains,job.parentGrains.meanOrientation)numParentsBeforeMerge =
4292
numParentsAfterSimilarMerge =
292
numParentsAfterInclusionMerge =
273
The first count change measures orientation-based merging. The second measures topological inclusion cleanup. Large parent footprints remain while the many compatible fragments and small enclosed child grains disappear.
Check packets and input-to-parent membership
calcVariants stores variantId and packetId on job.transformedGrains. A packet groups variants that share the same parent {111} habit plane. Here packetId selects the parent {111} plane closest to martensite (011).
job.calcVariants
packetColor = ind2color(job.transformedGrains.packetId);
plot(job.transformedGrains,packetColor,'faceAlpha',0.5)
hold on
parentGrains = smoothBoundary(job.parentGrains,10);
plot(parentGrains.boundary,'lineWidth',3)
grainSelected = parentGrains(...
parentGrains.findByLocation([100,80]));
plot(grainSelected.boundary,'lineWidth',3,'lineColor','w')
hold off
selectedParentId = grainSelected.id
childGrains = job.grainsPrior(...
job.mergeId == grainSelected.id);
numChildrenInSelectedParent = length(childGrains)selectedParentId =
326
numChildrenInSelectedParent =
93
Packet colours should organize inside the thick parent boundaries. The white boundary selects the parent at [100,80]. A parent ID depends on the reconstruction run that produced it, so selectedParentId prints the one this page arrived at rather than a number to carry over from elsewhere.
job.grainsPrior preserves the input grains, while job.mergeId maps each one to its current parent ID. The printed child count confirms the selected footprint can be traced back to its 93 measured grains.
Reuse the common crystallographic and pixel checks
The earlier Parent Austenite Reconstruction works through the remaining checks in full. calcVariantId compares the measured child orientations with all theoretical variants of one reconstructed parent. Its pole figure tests crystallography inside a grain, not only its outline.
calcParentEBSD transfers the grain result to individual pixels. The per-pixel property parentEBSD.fit measures correspondence between the pixel-derived parent and its reconstructed grain orientation.
parentEBSD = job.calcParentEBSD;
pixelFit = parentEBSD('Iron fcc').fit ./ degree;
pixelFitQuantiles = quantile(pixelFit(~isnan(pixelFit)),[0.5 0.9 0.99])pixelFitQuantiles =
2.2340
4.1554
7.1494The printed fit quantiles are the compact numerical check for this graph reconstruction. The median, 90th, and 99th percentiles are 2.234, 4.155, and 7.149 degrees. Low values mean the measured child pixels agree with their assigned parent orientation.
To fill remaining notIndexed or not reconstructed pixels, the earlier page segments parentEBSD at 3 degrees with 'minPixel',10, smooths the parent boundaries by 10 iterations, and applies a halfQuadraticFilter:
[parentGrains,parentEBSD] = calcGrains(parentEBSD,...
'angle',3*degree,'minPixel',10);
parentGrains = smoothBoundary(parentGrains,10);
F = halfQuadraticFilter;
parentEBSD = smooth(parentEBSD('indexed'),F,'fill',parentGrains);The grain boundaries constrain the fill so it does not blur across the reconstructed parent partition.
References
- F. Niessen, T. Nyyssönen, A. A. Gazder, and R. Hielscher, Parent grain reconstruction from partially or fully transformed microstructures in MTEX, Journal of Applied Crystallography 55 (2022), 180-194, defines the grain-graph reconstruction, rejection, and boundary-voting workflow used here.
- T. Nyyssönen, M. Isakov, P. Peura, and V.-T. Kuokkala, Iterative determination of the orientation relationship between austenite and martensite from a large amount of grain pair misorientations, Metallurgical and Materials Transactions A 47 (2016), 2587-2590, gives the OR-fitting method.
Next
Continue with Triple Point Based Reconstruction. It replaces pairwise boundary evidence with the stronger constraint available where three distinct grains meet.
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/GrainGraphBasedReconstruction.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.