Grain Boundary Properties edit page

A grain boundary is stored as short segments between neighbouring EBSD measurements that belong to different grains. Most properties come in pairs: two pixels, grains, phases, and the misorientation between them. The remaining properties describe the segment's geometry and its place in the boundary network.

This page assumes that the map has already been divided into grains as in Grain Reconstruction. Select Grain Boundaries covers phase and grain selection. Grain Boundaries explains the physical meaning of a boundary trace in a two-dimensional section.

property

meaning

property

meaning

ebsdId

neighbouring pixel IDs

grainId

neighbouring grain IDs

phaseId

neighbouring phase IDs

misorientation

rotation across the segment

F

endpoint vertex IDs

direction

segment direction

midPoint

segment midpoint

segLength

segment length

curvature

signed segment curvature

triplePoints

triple-point list

chainId

chain label

chainSize

number of segments in the chain

arcLength

distance along the chain

chainLength

total chain length

isClosed

whether the chain closes

junctionId

junction vertex IDs

componentId

connected-component label

componentSize

number of segments in the component

close all;

% load the example map in its specimen plotting frame
plottingConvention.default('y↑→x');
mtexdata twins silent;
ebsd.prop = rmfield(ebsd.prop,{'error','bands'});

% reconstruct the grains
[grains,ebsd] = calcGrains(ebsd,'angle',10*degree,'minPixel',3);

Preserve the segment-to-pixel relation

This page uses ebsdId to inspect the pixel pair beside each segment. Boundary simplification and refinement change the segment count, and a resampled segment no longer lies between one specific pixel pair. The smoothBoundary options 'noSimplify' and 'noRefine' preserve that relation while smoothing.

grains = grains.smoothBoundary(5,'noSimplify','noRefine');

% draw the grains and overlay the complete boundary network
plot(grains,'FaceColor',[0.9 0.9 0.9],'micronbar','off');
gB = grains.boundary;
hold on;
plot(gB,'lineWidth',2);
hold off;

The thick black lines show that the boundary is a network of individual segments. Each segment separates one pair of neighbouring measurements.

The two sides of a segment

ebsdId, grainId, and phaseId are \(N \times 2\) matrices. Each row records what lies on the two sides of one segment. Consider the boundary of one small grain:

gB4 = grains(4).boundary
gB4 = grainBoundary (y↑→x)
 
 Segments  length  mineral 1  mineral 2
        8  1.3 µm  Magnesium  Magnesium

The object summary reports eight segments. Its ebsdId property is therefore an 8 by 2 matrix.

gB4.ebsdId
ans =
        1103        1104
         966         967
         966         829
         965         828
         965         964
         965        1102
        1103        1102
        1103        1240

These values are measurement IDs, not positions in the current list. IDs and list positions differ as soon as measurements have been removed from the map. Indexing by ID must say so explicitly.

ebsd('id',gB4.ebsdId)
ans = EBSDsquare (y↑→x, row←col↑)
 
 Phase  Orientations    Mineral         Color  Symmetry  Crystal reference frame
     1     16 (100%)  Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
   Id   Phase             orientation    bc    bs   mad   oldId   grainId
 1103       1   (114.7°,15.7°,218.5°)   182   174   0.5    1011         4
  966       1   (113.7°,15.5°,219.3°)   164   158   0.5    1010         4
  966       1   (113.7°,15.5°,219.3°)   164   158   0.5    1010         4
  965       1   (115.3°,15.6°,218.2°)   170   176   0.7     843         4
  965       1   (115.3°,15.6°,218.2°)   170   176   0.7     843         4
  965       1   (115.3°,15.6°,218.2°)   170   176   0.7     843         4
 1103       1   (114.7°,15.7°,218.5°)   182   174   0.5    1011         4
 1103       1   (114.7°,15.7°,218.5°)   182   174   0.5    1011         4
 1104       1     (4.7°,80.8°,195.4°)   174   181   0.5    1178        42
  967       1     (4.5°,80.7°,195.2°)   168   171   0.3    1177        42
  829       1     (4.6°,80.7°,195.4°)   156   160   0.4    1009        42
  828       1     (4.9°,80.4°,195.2°)   167   170   0.5     842        42
  964       1     (4.9°,80.4°,195.2°)   176   196   0.5     676        42
 1102       1     (4.4°,80.5°,195.1°)   174   197   0.3     844        42
 1102       1     (4.4°,80.5°,195.1°)   174   197   0.3     844        42
 1240       1     (4.4°,80.5°,195.2°)   176   168   0.4    1012        42
 Scan unit : um
 X × Y × Z : [1.8 → 2.7] × [1.2 → 2.1] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :8 × 2

The grain IDs on the two sides reveal the local grain arrangement.

gB4.grainId
ans =
     4    42
     4    42
     4    42
     4    42
     4    42
     4    42
     4    42
     4    42

Grain 4 has grain 42 on the far side of all eight segments. Grain 4 is therefore an inclusion: it is entirely surrounded by one other grain.

plot(grains(4),'FaceColor','DarkBlue','micronbar','off');
hold on;
plot(grains(42),'FaceColor','LightCoral');
hold off;

The dark-blue inclusion sits wholly inside the coral grain. The plot is the spatial counterpart of the repeated pair [4 42] above.

From segments to chains

A chain is a maximal run of segments laid end to end. It runs from one junction to the next and never passes through a junction. Every segment belongs to exactly one chain. The same two grains lie on its two sides along the entire chain.

A junction is a vertex where the number of meeting segments is not two. It need not be a triple point. For example, the end of a boundary at the map rim is a junction but not a point where three real grains meet.

The inclusion boundary reaches no junction and closes onto itself. Its eight segments consequently form one closed chain.

chainSummary = table(gB4.chainId(1),gB4.chainSize(1),gB4.isClosed(1),...
  'VariableNames',{'chainId','chainSize','isClosed'})
chainSummary =
  1×3 table
    chainId    chainSize    isClosed
    _______    _________    ________
       1           8         true

arcLength is the cumulative length from the start of a chain to the end of each segment. chainLength repeats the total length on every segment, so either property remains aligned with the boundary list during indexing.

The misorientation across a segment

A segment's misorientation is the rotation from the orientation of its second pixel to that of its first. It follows the column order of ebsdId and can be reproduced from the two pixel orientations.

gB4(1).misorientation

inv(ebsd('id',gB4.ebsdId(1,2)).orientations) .* ...
  ebsd('id',gB4.ebsdId(1,1)).orientations
ans = misorientation (Magnesium → Magnesium)
  antipodal: true
 
  Bunge Euler angles in degree
     phi1     Phi    phi2
  329.875  86.401 150.169
 
 
ans = misorientation (Magnesium → Magnesium)
 
  Bunge Euler angles in degree
     phi1     Phi    phi2
  329.875  86.401 150.169

The two rotations agree, but only the stored one has its antipodal flag set. A boundary has no preferred side, so MTEX treats that rotation and its inverse as the same boundary misorientation. The rotation computed directly from two ordered orientations retains its direction.

A list of misorientations is meaningful only when every segment relates the same two phases. Select the phase pair before analysing angles or axes.

gB_Mg = gB('Magnesium','Magnesium');

% plot the misorientation angle on each magnesium boundary segment
moriAngle = gB_Mg.misorientation.angle ./ degree;
plot(gB_Mg,moriAngle,'lineWidth',4,'micronbar','off');
mtexColorbar('title','misorientation angle (°)');

Long runs share nearly the same colour, as expected for segments along one twin boundary. Most non-twin boundaries form the contrasting shorter runs.

The dominant magnesium twin has a misorientation angle of 86.3 degrees. In this map, 52.8 percent of the magnesium segments are within 3 degrees of that angle, and the median angle is 84.3 degrees.

fractionNearTwin = mean(abs(moriAngle - 86.3) < 3)
medianMoriAngle = median(moriAngle)
fractionNearTwin =
    0.5280
medianMoriAngle =
   84.2547

The high fraction confirms that this specimen is dominated by twin boundaries. Twinning develops that selection.

Which way a segment runs

direction is the direction of the segment trace. Its angle to the misorientation axis is used when classifying tilt and twist boundaries. See Twist and Tilt. Compute the axis in specimen coordinates from the two pixel orientations. The stored misorientation alone no longer retains that specimen-frame information.

% compute misorientation axes in specimen coordinates
ori = ebsd('id',gB_Mg.ebsdId).orientations;
axes = axis(ori(:,1),ori(:,2),'antipodal');

% plot the angle between each axis and its raw segment direction
rawAxisTraceAngle = angle(gB_Mg.direction,axes) ./ degree;
plot(gB_Mg,rawAxisTraceAngle,'lineWidth',4,'micronbar','off');
mtexColorbar('title','axis-to-trace angle (°)');

The colour flickers along boundaries that are straight on the scale of several pixels. This is the pixel staircase: one segment has only a few possible grid directions, regardless of the direction of the underlying boundary.

calcMeanDirection uses a window along each chain and never crosses a junction. An argument of 4 includes four neighbouring segments on each side of the segment.

meanDirection = gB_Mg.calcMeanDirection(4);
meanAxisTraceAngle = angle(meanDirection,axes) ./ degree;
plot(gB_Mg,meanAxisTraceAngle,'lineWidth',4,'micronbar','off');
mtexColorbar('title','axis-to-mean-trace angle (°)');

The flicker is suppressed, so nearby segments on one boundary now carry similar colours. The values differ from boundary to boundary, which is the information the flicker was hiding. The median angle is 43.2 degrees, and 10.5 percent of the segments lie below 20 degrees. The misorientation axes are therefore mostly not aligned with the traces in this section.

medianAxisTraceAngle = median(meanAxisTraceAngle)
fractionBelow20 = mean(meanAxisTraceAngle < 20)
medianAxisTraceAngle =
   43.2434
fractionBelow20 =
    0.1053

Be careful with what follows from that comparison. A trace is not a plane. It is the one direction of the boundary plane revealed by the section, while the inclination remains unknown. An axis parallel to the trace lies in the boundary plane and identifies a tilt boundary. A large angle to the trace settles nothing on its own. Twist and Tilt takes this further.

Where a segment is

midPoint gives the segment position as a vector3d. It supplies the anchor for quantities drawn at a segment, such as the misorientation axes computed above.

plot(grains,'FaceColor',[0.9 0.9 0.9],...
  'faceAlpha',0.3,'micronbar','off');
hold on;
quiver(gB_Mg(1:3:end),axes(1:3:end),...
  'color','black','autoScaleFactor',0.6);
hold off;

The black arrows are anchored at every third magnesium boundary midpoint. Their positions come from the boundary segments, while their directions come from the two neighbouring pixel orientations.

The same midpoint property supports spatial selection.

pos = gB_Mg.midPoint;
isTop = pos.y > 30;

plot(grains,'FaceColor',[0.9 0.9 0.9],...
  'faceAlpha',0.3,'micronbar','off');
hold on;
plot(gB_Mg(isTop),'lineWidth',3,'lineColor','red');
plot(gB_Mg(~isTop),'lineWidth',3,'lineColor','blue');
hold off;

Red marks segments above \(y = 30\) micrometres, and blue marks the rest. Select Grain Boundaries develops this indexing pattern for other per-segment properties.

Two lengths are easy to confuse. length(gB_Mg) is the number of segments, whereas segLength(gB_Mg) contains the length of each segment in µm. Their sum is the total length of the selected boundary list.

totalBoundaryLength = sum(gB_Mg.segLength)
totalBoundaryLength =
  722.2508

Chains and connected components

A chain stops at a junction. A connected component does not: it contains every segment reachable through touching segments, including branches through junctions. One component can therefore contain several chains.

componentId labels each connected group. componentSize repeats the number of segments in that component on every row. It is a segment count, not a geometric length.

The next example first selects segments close to the magnesium twin relation, then colours each segment by the size of its component.

CS = ebsd.CS;
twinning = orientation.map(Miller(1,-1,0,1,CS),Miller(1,0,-1,-1,CS),...
  Miller(0,1,-1,1,CS,'uvw'),Miller(1,-1,0,1,CS,'uvw'));

gBTwin = gB(gB.isTwinning(twinning));

plot(grains,'FaceColor',[0.9 0.9 0.9],...
  'faceAlpha',0.25,'micronbar','off');
hold on;
plot(gBTwin,gBTwin.componentSize,'lineWidth',4);
hold off;
mtexColorbar('title','segments per component');

Long twin lamellae appear as large connected components. Short isolated matches appear in the low end of the colour scale and may be accidental.

numTwinComponents = max(gBTwin.componentId);
componentSummary = table(length(gBTwin),numTwinComponents,...
  'VariableNames',{'twinSegments','components'})
componentSummary =
  1×2 table
    twinSegments    components
    ____________    __________
        1648            54

The 1648 selected segments form 54 components. A twin lamella crossing a grain is one long component. A few isolated segments that happen to meet the twin criterion form a short component, which is why component labels are useful.

A component-scale straightness descriptor

A simple straightness descriptor divides the maximum distance between two component midpoints by the component's total segment length. Its value approaches 1 for one straight lamella. It decreases when a component meanders or branches. This is a user-defined descriptor, not a built-in MTEX property.

componentId = gBTwin.componentId;
numComponents = max(componentId);
span = zeros(numComponents,1);

for k = 1:numComponents
  x = gBTwin.midPoint.x(componentId == k);
  y = gBTwin.midPoint.y(componentId == k);
  distanceSquared = (x-x.').^2 + (y-y.').^2;
  span(k) = sqrt(max(distanceSquared(:)));
end

componentLength = accumarray(componentId,gBTwin.segLength,...
  [numComponents,1],@sum);
straightness = span ./ componentLength;

plot(grains,'FaceColor',[0.9 0.9 0.9],...
  'faceAlpha',0.25,'micronbar','off');
hold on;
plot(gBTwin,straightness(componentId),'lineWidth',4);
hold off;
mtexColorMap blue2red;
mtexColorbar('title','component straightness');

Straight components are red, while meandering or branched components are blue. The values reach 0.97 and have a median of 0.66.

Define a large component here as one containing more than 50 segments. The 13 large components have a median of 0.48, compared with 0.67 for the rest. A large component in this map is often several lamellae meeting inside one grain, rather than one long straight lamella.

componentSegmentCount = accumarray(componentId,1);
isLarge = componentSegmentCount > 50;
straightnessSummary = table(max(straightness),median(straightness),...
  sum(isLarge),median(straightness(isLarge)),...
  median(straightness(~isLarge)),...
  'VariableNames',{'maximum','median','largeComponents',...
  'largeMedian','otherMedian'})
straightnessSummary =
  1×5 table
    maximum    median     largeComponents    largeMedian    otherMedian
    _______    _______    _______________    ___________    ___________
    0.96667    0.65528          13             0.48004        0.66692

This descriptor separates single straight lamellae from branched networks, rather than twins from misindexing. It is still worth checking for the latter. A few segments selected by accident, for example because of pseudosymmetry, form neither a lamella nor a branched twin network.

Next

Misorientations at Grain Boundaries develops the angle and axis on each side of a boundary. Curvature uses chain order for signed curvature, and Triple Points develops the junctions where three real grains meet.

Further reading

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