Gridded EBSD Data edit page

Most EBSD maps are measured on a square or hexagonal scan lattice. By default, EBSD.load keeps that structure: it returns an EBSDsquare or EBSDhex whenever every measurement fits on one lattice. Otherwise it keeps the measurements as a plain EBSD list and explains why.

This page assumes basic EBSD selection and plotting. See Select by Index first if MATLAB row and column indexing is unfamiliar.

A scan lattice, a matrix layout, and a reference frame answer different questions. The lattice says which measurements are neighbours. The layout says which specimen directions the matrix indices follow. The reference frame says which axes the positions and orientations are expressed in.

plottingConvention.default('y↑→x');
mtexdata twins
ebsd = EBSDsquare (y↑→x, row↑→col)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
     0     46 (0.2%)  notIndexed          none                                   
     1  22833 (100%)   Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad, oldId
 Scan unit : um
 X × Y × Z : [0 → 50] × [0 → 41] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :137 × 167

The summary identifies ebsd as an EBSDsquare. Its 22879 measurements form a 137 by 167 matrix, with one entry per scan position. Apart from its matrix shape, it behaves like any other EBSD variable.

plot(ebsd('Magnesium'),ebsd('Magnesium').orientations)

The rectangular outline follows the square measurement grid. The four corners of one pixel give the same information directly.

ebsd.unitCell
ans = vector3d (y↑→x)
 size: 1 × 4
      x     y     z
  -0.15 -0.15     0
  -0.15  0.15     0
   0.15  0.15     0
   0.15 -0.15     0

What a Grid Is Good For

  • Per-pixel data can be handed to image-processing and registration tools as a matrix, and ebsd(i,j) addresses one scan position.
  • Plotting and denoising are considerably faster because the raster does not have to be reconstructed.

Matrix indexing means what it says. The measurement in row 50 and column 100 is

ebsd(50,100)
ans = EBSD (y↑→x)
 
 Phase  Orientations    Mineral         Color  Symmetry  Crystal reference frame
     1      1 (100%)  Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
    Id   Phase              orientation   bands    bc    bs   error   mad   oldId
 13613       1   (155.8°,100.6°,239.3°)      10   149   133       0   0.7    8283
 Scan unit : um
 X × Y × Z : [30 → 30] × [15 → 15] × [0 → 0]
 Normal vector: (0,0,1)

In the default layout, the first matrix dimension follows the grid direction closest to y. The second follows the direction closest to x. Both indices advance towards increasing coordinates. Thus ebsd(1,1) is the corner with the smallest coordinates, and ebsd(i,j) is the j-th pixel in the i-th scan row.

This layout belongs to the map, not to the file traversal order. It is the same whichever corner the acquisition started from. Pass 'rowMajor' to gridify for the transposed layout.

A stored matrix is not required nearly as often as it once was. The orientation gradient, curvature, GND, and fill operate on the virtual lattice derived by lattice. They also work on plain lists, phase subsets, and arbitrarily aligned maps.

Choosing the Layout

A matrix is useful for image processing only when it is stored the same way round as the image. A forescatter or BSE image follows the order in which its detector wrote the pixels. That order need not match the map. Maps and Images compares a real map with SEM images of the same area.

'columnMajor' and 'rowMajor' are the two layouts aligned with x and y. Both are gridLayout objects. A layout names the row direction first, in the same order as size(A). Suppose an image's rows run against x and its columns run along y, as for a detector mounted a quarter turn from the scan.

gL = gridLayout(-xvector,yvector)
gL = gridLayout (row||-x, col||y)

Handing that layout to gridify changes the matrix shape and ordering.

ebsdI = gridify(ebsd,gL)
ebsdI = EBSDsquare (y↑→x, row←col↑)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
     0     46 (0.2%)  notIndexed          none                                   
     1  22833 (100%)   Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad, oldId
 Scan unit : um
 X × Y × Z : [0 → 50] × [0 → 41] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :167 × 137

The map records the layout in which it is stored.

ebsdI.layout
ans = gridLayout (row||-x, col||y)

Every per-pixel property follows the same layout. For this pair of layouts, band contrast is related by a quarter turn.

isequal(ebsdI.bc, rot90(ebsd.bc))
ans =
  logical
   1

No value was resampled or invented. A transpose and flips are sufficient, so conversion back to the original layout is exact.

isequal(gridify(ebsdI,ebsd.layout).bc, ebsd.bc)
ans =
  logical
   1

Layout changes storage, not the specimen. The positions and orientations are unchanged, and the plotting convention still draws both maps in the same specimen frame.

newMtexFigure('layout',[1,2])
plot(ebsd,ebsd.bc), mtexColorMap gray
title('columnMajor')
nextAxis
plot(ebsdI,ebsdI.bc), mtexColorMap gray
title('row against x, column along y')

The same features occupy the same screen positions in both panels. Only the labels describe a different order in memory.

A rotated or sheared grid cannot match an axis-aligned layout exactly. gridify chooses the nearest transpose-and-flip permutation, just as it does for the two named flags. For an already gridded map, transformReferenceFrame performs the same reindexing. Despite that method name, this operation is not a frame change: it does not re-express the specimen in different axes.

Data That Cannot Be Put on a Grid

A rectangular raster is faithful only when every measurement occupies a distinct site of one lattice. If two measurements land in the same cell, one would be lost. MTEX therefore keeps such data as a list and reports the collision rather than silently dropping a measurement.

ebsd = EBSD.load([mtexEBSDPath filesep 'eclogite.ctf'],...
  'EulerCorrection', rotation.byAxisAngle(zvector,180*degree))
Warning: 92 of 613 indexed measurements share a lattice cell with another one,
so gridding them would drop those measurements. Keeping the data as a list -
call gridify explicitly if you want the raster anyway. 
 
ebsd = EBSD (y↑→x)
 
 Phase  Orientations                Mineral       Color  Symmetry  Crystal reference frame
     0     4 (0.65%)             notIndexed        none                                   
     4     165 (27%)  Garnet - (Mg,Ni)3Al2(  LightCoral      m-3m                         
     5     215 (35%)              Omphacite    DarkBlue     12/m1        X||a*, Y||b, Z||c
     6     61 (9.9%)                Coesite   DarkGreen     12/m1        X||a*, Y||b, Z||c
     7     172 (28%)             Quartz-new     DarkRed      -3m1        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad
 Scan unit : um
 X × Y × Z : [2 → 716] × [2 → 623] × [0 → 0]
 Normal vector: (0,0,1)

The warning above gives the number of colliding measurements. MTEX also keeps a list when the positions are too irregular to span a sensible raster. You may request a list independently of the data, either for one import,

ebsd = EBSD.load(fname,'noGrid')

or for the whole session.

setMTEXpref('gridifyOnImport',false)

You may change representation later. EBSD(ebsd) flattens a gridded map into a list, while gridify puts a list on its grid.

Selecting a Subset Drops the Matrix Shape

Selecting a phase, region, or indexed measurements usually leaves a shape that is not rectangular. The result is therefore a plain list, although every retained measurement still lies on the original lattice.

mtexdata twins silent

ebsdMg = ebsd('Magnesium')
ebsdMg = EBSD (y↑→x)
 
 Phase  Orientations    Mineral         Color  Symmetry  Crystal reference frame
     1  22833 (100%)  Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad, oldId
 Scan unit : um
 X × Y × Z : [0 → 50] × [0 → 41] × [0 → 0]
 Normal vector: (0,0,1)

Reapplying gridify restores the matrix shape.

ebsdMg = ebsd('Magnesium').gridify
ebsdMg = EBSDsquare (y↑→x, row↑→col)
 
 Phase  Orientations    Mineral         Color  Symmetry  Crystal reference frame
     1  22833 (100%)  Magnesium  LightSkyBlue     6/mmm        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad, oldId
 Scan unit : um
 X × Y × Z : [0 → 50] × [0 → 41] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :137 × 167

The variables ebsd and ebsdMg differ at the 46 positions that were not indexed. In ebsd those are real measurements in the separate 'notIndexed' phase: a diffraction pattern was recorded but could not be indexed. Selecting magnesium removes those measurements and creates gaps, meaning missing sites within scan lines.

After gridify, each gap is an empty lattice site in ebsdMg. Its orientation and phaseId are NaN because no selected measurement belongs there. It is not a notIndexed measurement.

[nnz(isnan(ebsd.phaseId)), nnz(isnan(ebsdMg.phaseId))]
ans =
     0    46

The empty sites complete the rectangle. Row and column ranges can therefore select and plot a rectangular subregion.

plot(ebsdMg(50:100,5:100),ebsdMg(50:100,5:100).orientations)

Gridding Reorders the Measurements

gridify does not preserve input order, and generally cannot. The layout fixes the first matrix dimension to y, whereas .ctf and .ang files usually write x fastest. MATLAB linear indexing runs down a matrix column, while such a file runs across the map. Consequently, ebsd(k) after gridding is generally not line k of the input file.

Nothing is lost. The property oldId keeps the original ids. Its upper left corner shows consecutive ids running along matrix rows, while MATLAB linear indices run down columns.

ebsd.oldId(1:3,1:4)
ans =
     1     2     3     4
   168   169   170   171
   335   336   337   338

The second output of gridify translates in the other direction. In particular, ebsdGrid.pos(newId) returns the gridded positions in the order of the input list.

[ebsdGrid,newId] = gridify(EBSD(ebsd));

isequal(ebsdGrid.pos(newId), EBSD(ebsd).pos)
ans =
  logical
   1

This distinction matters only to code that depends on input order. Grain reconstruction does not: calcGrains returns the same grains from the list and from the map.

The Gradient

The orientation gradient, the incomplete Nye tensor, and the gradient form of the weighted Burgers vector are computed on the virtual lattice. They therefore do not require a stored matrix. The default integral form of the weighted Burgers vector is a raster algorithm and grids internally.

A grid does no harm, so this example continues with ebsdMg. The result has the same matrix shape as the map.

gradX = ebsdMg.gradientX;

plot(ebsdMg,norm(gradX))
setColorRange([0,4*degree])

The colour field follows the same rectangular raster, including its empty sites. No separate gridding step was needed for the derivative itself.

Hexagonal Grids

The same principles apply to a hexagonal scan. MTEX imports it as an EBSDhex and provides the same row and column indexing.

mtexdata copper silent

[grains, ebsd] = calcGrains(ebsd);

ebsd
ebsd = EBSDhex (y↑→x)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
    -1    68 (0.42%)  notIndexed          none                                   
     0  16116 (100%)      Copper  LightSkyBlue      m-3m                         
 
 Properties: confidenceindex, fit, imagequality, semsignal, unknown_11, unknown_12, unknown_13, unknown_14, oldId, grainId
 Scan unit : um
 X × Y × Z : [0 → 592] × [0 → 585] × [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 × 119
plot(ebsd(1:20,1:40),ebsd(1:20,1:40).orientations,...
  'micronbar','off','edgeColor','black')

The black cell edges reveal the alternating half-step offset between scan rows. The matrix is rectangular even though its pixel footprints are hexagons.

Switching from a Hexagonal to a Square Grid

Some external image-processing tools require square pixels. Passing a square unitCell to gridify resamples the hexagonal measurements onto such a grid.

% define a square unit cell
unitCell = 2.5 * vector3d([-1 -1 1 1].',[-1 1 1 -1].',0);

% use the square unit cell for gridify
ebsdS = ebsd.gridify('unitCell',unitCell)

% visualize the result
plot(ebsd,ebsd.orientations,'layout',[1,2])
nextAxis
plot(ebsdS,ebsdS.orientations)
ebsdS = EBSDsquare (y↑→x, row↑→col)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
    -1   137 (0.97%)  notIndexed          none                                   
     0   14023 (99%)      Copper  LightSkyBlue      m-3m                         
 
 Properties: confidenceindex, fit, imagequality, semsignal, unknown_11, unknown_12, unknown_13, unknown_14, oldId, grainId
 Scan unit : um
 X × Y × Z : [0 → 595] × [0 → 585] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :118 × 120

This operation differs fundamentally from restoring a map's own lattice. The new grid sites do not coincide with the measured ones, so interp copies values from the nearest measurements. The left panel shows the measured hexagons; the right shows the square pixels after resampling.

The square cell above has approximately the same size as the hexagonal one. Squares cannot reproduce hexagonal outlines, so grain boundaries in the right panel are visibly staircased. A smaller square cell reduces the size of the steps.

% a smaller unit cell
unitCell = 0.5*vector3d([-1 -1 1 1].',[-1 1 1 -1].',0);

% use the small square unit cell for gridify
ebsdS = ebsd.gridify('unitCell',unitCell)

plot(ebsdS,ebsdS.orientations)
hold on
plot(grains.boundary,'lineWidth',2)
hold off
ebsdS = EBSDsquare (y↑→x, row↑→col)
 
 Phase   Orientations     Mineral         Color  Symmetry  Crystal reference frame
    -1   1311 (0.38%)  notIndexed          none                                   
     0  346773 (100%)      Copper  LightSkyBlue      m-3m                         
 
 Properties: confidenceindex, fit, imagequality, semsignal, unknown_11, unknown_12, unknown_13, unknown_14, oldId, grainId
 Scan unit : um
 X × Y × Z : [0 → 593] × [0 → 585] × [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :586 × 594

The smaller cells follow the original boundary more closely. They do not add spatial resolution: every new orientation still comes from a nearest measured hexagon. Regridding and Interpolation develops this distinction in detail.

Pixels that remain notIndexed correspond to measurements that were not indexed in the source map. fill may replace them by nearest-neighbour interpolation. smooth offers more sophisticated methods; see Filling Missing Data.

Rotated Grids

Rotating a map rotates its unit cell together with its positions. The lattice therefore survives, and the map remains an EBSDsquare or EBSDhex. Nothing needs repair or interpolation.

ebsdR = rotate(ebsd,20*degree)
ebsdR = EBSDhex (y↑→x)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
    -1    68 (0.42%)  notIndexed          none                                   
     0  16116 (100%)      Copper  LightSkyBlue      m-3m                         
 
 Properties: confidenceindex, fit, imagequality, semsignal, unknown_11, unknown_12, unknown_13, unknown_14, oldId, grainId
 Scan unit : um
 X × Y × Z : [-198 → 555] × [0 → 752] × [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 × 119
plot(ebsdR,ebsdR.orientations)

The map is turned on screen, while the object summary still identifies a hexagonal grid. If the same data arrives as a plain list, gridify recovers the rotated lattice without requiring axis alignment.

gridify(EBSD(ebsdR))
ans = EBSDhex (y↑→x)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
    -1    68 (0.42%)  notIndexed          none                                   
     0  16116 (100%)      Copper  LightSkyBlue      m-3m                         
 
 Properties: confidenceindex, fit, imagequality, semsignal, unknown_11, unknown_12, unknown_13, unknown_14, oldId, grainId
 Scan unit : um
 X × Y × Z : [-198 → 555] × [0 → 752] × [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 × 119

Robustness to Distorted Grids

EBSD is measured on a tilted specimen. Seen from a finite working distance, the far edge is farther away and appears smaller. The measured positions can therefore depart smoothly from any single rigid lattice. MTEX still reconstructs the underlying grid indices of an EBSD object. This matters to gridify and to every operation that needs neighbours, including calcGrains and the surf plotting backend.

Grid reconstruction uses a local deformation model. MTEX first fits an ideal affine grid. It then interpolates the local deviation between the measured positions and that grid wherever a cell has no measurement. Thus a gap, a notIndexed hole, and the dummy cells used to bound the map follow the measured distortion rather than an unrelated rigid lattice.

These terms are distinct. A gap is a run of measurements removed from a scan line, such as by selecting one phase. A hole is a connected notIndexed area inside the scanned region. A dummy cell is a synthetic cell beyond the scanned edge; it has no id and never becomes a grain.

The example uses a real map because the failure appears only when the map is realistically wide. A small synthetic grid remains safe at distortion levels that already misindex a wide map.

mtexdata small
ebsd = EBSD (y↑→x)
 
 Phase  Orientations     Mineral         Color  Symmetry  Crystal reference frame
     0    1197 (32%)  notIndexed          none                                   
     1    1952 (52%)  Forsterite  LightSkyBlue       mmm                         
     2    290 (7.8%)   Enstatite  DarkSeaGreen       mmm                         
     3    282 (7.6%)    Diopside     Goldenrod     12/m1        X||a*, Y||b, Z||c
 
 Properties: bands, bc, bs, error, mad, oldId
 Scan unit : um
 X × Y × Z : [33000 → 36000] × [4500 → 7500] × [0 → 0]
 Normal vector: (0,0,1)

transform moves every pixel and its unit cell. It leaves orientations and all other per-pixel properties untouched. The command takes a spatialTransform, which records the mapping as an object; see Spatial Transforms.

A tilt is a projective transform. Straight lines remain straight, but parallel lines need not, and scale varies across the frame. Three values specify it: the surface tilt, the working distance, and the point left in place. byTilt tilts about the x axis, so the tilt angle foreshortens the map across that axis, along y. The working distance controls perspective, which vanishes as that distance grows.

Here the tilt is known and imposed. Recovering an unknown tilt from two measured images is the inverse problem. spatialTransformTilt fits it in stages.

theta  = 20*degree;                             % surface tilt
wd     = 8*(max(ebsd.pos.y) - min(ebsd.pos.y)); % working distance
centre = mean(ebsd.pos);                        % what the tilt leaves in place

distort = spatialTransformProjective.byTilt(theta,wd,centre)
distort = spatialTransformProjective
 
 model       stage  parameters                
 projective  ·      perspective (0, -1.31e-05)

The map is compressed along y, from 3000 to 2820 micrometres, while the x extent is left alone by the tilt and only stretched by perspective. It tapers towards the edge that is farther away.

ebsdDistorted = transform(ebsd,distort);

plot(ebsdDistorted('Fo'),ebsdDistorted('Fo').orientations)
hold on
plot(ebsdDistorted('En'),ebsdDistorted('En').orientations)
plot(ebsdDistorted('Di'),ebsdDistorted('Di').orientations)
hold off

The following pair measures the imposed distortion in cell units. Its first value is the maximum pixel displacement. Its second is the largest residual after the best affine grid has been removed.

pos0 = [ebsd.pos.x(:),ebsd.pos.y(:)];
posD = [ebsdDistorted.pos.x(:),ebsdDistorted.pos.y(:)];
ijD = double(ebsdDistorted.lattice.ij);
isIndexed = ebsdDistorted.isIndexed(:);
affineDesign = [ones(size(ijD,1),1),ijD];
affineFit = affineDesign(isIndexed,:) \ posD(isIndexed,:);
cellSize = ebsd.lattice.dxy;
distortionInCells = [max(vecnorm(posD-pos0,2,2)),...
  max(vecnorm(posD-affineDesign*affineFit,2,2))] / cellSize
distortionInCells =
    2.4800    0.8041

Every pixel has moved by up to 2.48 cells. Once the affine foreshortening is removed, 0.80 cell remains. That is enough for a rigid reconstruction to round a pixel onto its neighbour's site. Even so, MTEX recovers the same grid indices as for the undistorted map.

isequal(ebsdDistorted('Fo').lattice.ij, ebsd('Fo').lattice.ij)
ans =
  logical
   1

Drawing each measurement as its unit cell shows the result. The cells form one continuous deformed mesh, with no cell sheared independently of its neighbours. A rigid reconstruction would make the mesh wavy and would give calcGrains the wrong neighbours.

plot(ebsdDistorted('Fo'),ebsdDistorted('Fo').orientations,...
  'unitCell','EdgeColor','black')
hold on
plot(ebsdDistorted('En'),ebsdDistorted('En').orientations,...
  'unitCell','EdgeColor','black')
plot(ebsdDistorted('Di'),ebsdDistorted('Di').orientations,...
  'unitCell','EdgeColor','black')
hold off

Grain reconstruction consequently sees the distorted map as the same neighbourhood graph as the original.

grains = calcGrains(ebsdDistorted,'minPixel',3)
grains = smoothBoundary(grains,'noSimplify');

hold on
plot(grains.boundary,'lineWidth',2)
hold off
grains = grain2d (y↑→x)
 
 Phase  Grains  Pixels     Mineral  Symmetry         Color
     0       1       1  notIndexed                    none
     1      15    1943  Forsterite       mmm  LightSkyBlue
     2       3     283   Enstatite       mmm  DarkSeaGreen
     3       6     240    Diopside     12/m1     Goldenrod
 
 boundary segments: 982 (38445 µm)
 inner boundary segments: 26 (1058 µm)
 triple points: 22
 
 Properties: meanRotation, GOS

The boundary overlay remains closed and follows the tapered map. No duplicated or missing grain strips appear along its edges.

Further Reading

Next

Maps and Images uses layouts to compare an EBSD map with detector images pixel by pixel. Regridding and Interpolation develops resampling onto a different lattice. Spatial Transforms introduces the transform models used above, and TrueEBSD Distortion Correction fits them to measured images. Continue with Grain Reconstruction when the goal is to turn measurements into grains.

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