Smoothing Three-Dimensional Grain Boundaries edit page

Grains reconstructed from voxel data have a boundary that follows the voxel faces. Every face normal points along one of the three axes, so a boundary normal distribution or a curvature computed from it measures the grid, not the specimen. This page coarsens the voxel surface, smooths it, and measures the price in grain volume. The aim is a surface suitable for quantitative analysis, not simply a smoother-looking picture.

plottingConvention.default('y↑→x');
how2plot = plottingConvention.default3D;

fname = fullfile(mtexDataPath,'EBSD','SmallIN100_MeshStats.dream3d');
ebsd = EBSD3.load(fname);
grains = calcGrains(ebsd,'angle',5*degree)
grains = grain3d (y↑→x)
 
 Phase  Grains  Volume  Mineral  Symmetry  Crystal reference frame
     1     812   15625  unknown       432                         
 
 boundary faces: 757562
 
 Properties: meanRotation, GOS

The boundary network is stratified

A boundary face separates two grains. Along a triple line three grains meet, at a quadruple point four. nodeType counts the grains at every vertex and adds 10 on the outer hull of the measured volume, where a vertex inside a single grain also occurs. These are counts of mesh vertices, so they depend on resolution; they are not counts of complete triple lines or physical quadruple points.

t = nodeType(grains.boundary);
n = accumarray(t(t>0),1);
[find(n), n(n>0)]
ans =
           2      241422
           3       41328
           4        3806
           5         167
           6           4
          11       46985
          12       11912
          13        1078
          14          27

A junction must not be averaged with the interior of a face, which is why the smoothing below treats each stratum in turn.

Coarsen first

reduceBoundary merges the vertices of each cell of twice the voxel size into one. Clustering distinguishes the grains meeting at a vertex and the faces of the hull, preserving their junction structure where the coarsened mesh remains resolved. The centroid of a cluster already averages the voxel steps; the flag 'quadric' puts the vertex where the faces around it are best approximated instead, which keeps flat boundaries flat.

grainsC = reduceBoundary(grains,2,'quadric')
grainsC = grain3d (y↑→x)
 
 Phase  Grains  Volume  Mineral  Symmetry  Crystal reference frame
     1     812   15625  unknown       432                         
 
 boundary faces: 289348
 
 Properties: meanRotation, GOS

Check how many faces remain and whether very small grains have lost their enclosing surface. Coarsening can collapse a grain below its cell size; this matters when the fine-grain population is part of the question.

[length(grains.boundary), length(grainsC.boundary)]
nnz(grainsC.volume <= 0)
ans =
      757562      289348
ans =
     6

The largest grain makes the geometric change easy to see.

[~,id] = max(grains.volume);
plot(grains(id),'micronbar','off','edgeAlpha',0.2)
setCamera(how2plot)
plot(grainsC(id),'micronbar','off','edgeAlpha',0.2)
setCamera(how2plot)

Smooth

smoothBoundary moves the vertices and nothing else. Quadruple points and the hull stay fixed, every triple line is smoothed as a curve between its quadruple points, and every boundary face as a surface between its triple lines. The filters are the ones of the two-dimensional grain smoothing: laplaceFilter shrinks each grain a little per iteration, taubinFilter keeps the volumes approximately, curvatureFilter solves for a smoothing length in one step.

grainsS = smoothBoundary(grainsC,taubinFilter(20));
newMtexFigure('layout',[1,3],'figSize','large');
plot(grains(id),'micronbar','off','edgeAlpha',0.2)
setCamera(how2plot)
mtexTitle('Voxel surface')
nextAxis
plot(grainsC(id),'micronbar','off','edgeAlpha',0.2)
setCamera(how2plot)
mtexTitle('Coarsened')
nextAxis
plot(grainsS(id),'micronbar','off','edgeAlpha',0.2)
setCamera(how2plot)
mtexTitle('Taubin smoothed')

Triple lines remain shared by the same grains, but their vertices may move. Add 'fixTripleLines' to hold them fixed during smoothing. Keeping the outer hull fixed conserves the enclosed total volume for a valid mesh; it does not conserve each grain's volume.

Compare Laplace and Taubin smoothing from the same coarsened mesh. Include the coarsened result separately so its contribution is visible.

grainsL = smoothBoundary(grainsC,laplaceFilter(20));
vol = [grains.volume, grainsC.volume, grainsL.volume, grainsS.volume];
sum(vol)
ans =
   1.0e+04 *
    1.5625    1.5625    1.5625    1.5625

Relative volume change reveals whether the surface treatment affects the fine grains more strongly. The three columns below correspond to coarsening, coarsening plus Laplace, and coarsening plus Taubin.

relativeChange = 100 * (vol(:,2:4) - vol(:,1)) ./ vol(:,1);
median(abs(relativeChange),1)

figure
scatter(vol(:,1),relativeChange(:,2),12,'filled','DisplayName','Laplace')
hold on
scatter(vol(:,1),relativeChange(:,3),12,'filled','DisplayName','Taubin')
yline(0,'k--')
hold off
set(gca,'XScale','log')
xlabel('original grain volume (length units^3)')
ylabel('volume change (%)')
legend('Location','best')
ans =
    0.6447   11.9610    3.3128

The option 'maxDisplacement' limits each coordinate change relative to the mesh entering the smoothing step. It does not undo displacement or a collapsed grain from coarsening. For example, half the smallest voxel spacing gives a bound tied to measurement resolution:

grainsBounded = smoothBoundary(grainsC,taubinFilter(20), ...
  'maxDisplacement',0.5*min([ebsd.dx,ebsd.dy,ebsd.dz]));

The scheme 'coupled' smooths all movable vertices together instead of treating triple lines before face interiors. Compare its geometric effect if junction positions are central to the analysis.

What the smoothing changes downstream

The boundary normal distribution of the voxel surface has all its weight on the three axes. After smoothing the normals spread over the sphere.

gB = grains.boundary; gB = gB(all(gB.grainId > 0,2));
plot(calcGBND(gB),'upper','micronbar','off')
mtexColorbar
gBS = grainsS.boundary; gBS = gBS(all(gBS.grainId > 0,2));
plot(calcGBND(gBS),'upper','micronbar','off')
mtexColorbar

Refine

refineBoundary splits every edge at its midpoint and every face into four. No vertex moves, so volumes, areas and the grains a face separates are inherited exactly. Refinement increases mesh density, for instance before a filter with a short smoothing length. It adds no measurement information and cannot recover a feature removed by coarsening.

grainsR = refineBoundary(grainsS);
[length(grainsS.boundary), length(grainsR.boundary)]
ans =
      289348     1157392

Choose the treatment for the quantity being measured

Volume, area and normal distributions need different levels of geometric fidelity. The area change below complements the volume comparison: a small change in volume can accompany a large decrease in staircase area.

surfaceArea = [sum(grains.surface), sum(grainsC.surface), ...
  sum(grainsL.surface), sum(grainsS.surface)];
100 * (surfaceArea / surfaceArea(1) - 1)
ans =
         0  -15.0252  -32.9219  -31.0261

Here the sum counts shared interfaces twice, consistently for all four meshes. For a physical internal area per volume, count each interface once as on the Boundary Network page. Repeat the comparison with a smaller coarsening factor or fewer filter iterations: a conclusion about morphology should survive reasonable choices near the voxel resolution. A smooth mesh alone does not establish a resolved curvature or remove uncertainty from missing orientations.

Function reference

Function

Purpose

Function

Purpose

reduceBoundary

coarsen a triangular mesh

refineBoundary

subdivide triangles

smoothBoundary

smooth the boundary network

calcGBND

measure the normal distribution

laplaceFilter

average neighbouring vertices

taubinFilter

smooth with reduced shrinkage

References

Next

Continue with Boundary Network to measure interface area and inspect junctions. Properties measures the enclosed grains, and Boundary Normal Distribution develops the analysis of their surface normals.

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/Grains3DSmoothing.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.