Gridded EBSD Data edit page

EBSD data is measured on a square or a hexagonal grid, and MTEX keeps it on that grid: EBSD.load returns an EBSDsquare or an EBSDhex whenever the measurements really do sit on one lattice, and falls back to a plain list of pixels only when they do not. Which of the two you have got is stated by the class of the variable

plottingConvention.default('y↑→x');
mtexdata twins
ebsd = EBSDsquare (y↑→x)
 
 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 x Y x Z : [0 → 50] x [0 → 41] x [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :137 x 167

This is an EBSDsquare, i.e. the 22879 measurements are stored as a 137 x 167 matrix, one entry per scan position and arranged the way the map is. Apart from that it behaves like any other EBSD variable

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

and a look at the unit cell confirms the square grid

ebsd.unitCell
ans = vector3d (y↑→x)
 size: 1 x 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

  • the data can be handed to image processing and registration tools as a matrix, and ebsd(i,j) addresses a scan position
  • plotting and denoising are considerably faster, as the raster does not have to be reconstructed first

Matrix indexing means what it says - the measurement in row 50 and column 100 of the map 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 x Y x Z : [30 → 30] x [15 → 15] x [0 → 0]
 Normal vector: (0,0,1)

In the default layout the first matrix dimension is the grid direction closest to y and the second one the grid direction closest to x, both oriented such that the coordinates increase. Accordingly ebsd(1,1) is the corner with the smallest coordinates and ebsd(i,j) is the j-th pixel of the i-th scan row. This is a property of the map and not of the file - it is the same whichever corner the acquisition started from and whichever direction it scanned in. Pass 'rowMajor' to gridify if you want the transposed layout.

A grid is not required nearly as often as it used to be. The orientation gradient, curvature, GND and fill are computed on the virtual lattice that lattice derives from the unit cell, and therefore work on arbitrarily aligned data just as well.

Data That Can Not Be Put on a Grid

Gridding writes the measurements into a rectangular raster, which is faithful only if they really do sit on one lattice. Should two of them land in the same cell, one would be lost - so MTEX keeps such a data set as a plain list instead, and says why

ebsd = EBSD.load([mtexEBSDPath filesep 'eclogite.ctf'])
Warning: .ctf files usually come with different coordinate systems for the Euler
angles and the spatial coordinates. I assumed the relative alignment of these
coordinate systems to be a rotation about the z-axis by 180 degree. You may want
to verify this and specify the correct alignment explicitely by

ebsd = EBSD.load(fileName,'EulerCorrection',
rotation.byAxisAngle(zvector,180*degree))

Click <a href="matlab:MTEXdoc('EBSDReferenceFrame')">here</a> for more
information.
Warning: 48 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 (Y1↓→X1)
 
 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 x Y x Z : [2 → 716] x [2 → 623] x [0 → 0]
 Normal vector: (0,0,1)

The same happens for a scan whose positions are too irregular to span a sensible raster at all. Independently of the data you may always ask for a plain list, either for a single import

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

or for the whole session

setMTEXpref('gridifyOnImport',false)

and you may always change your mind afterwards: EBSD(ebsd) flattens a gridded map into a list, gridify puts a list onto its grid.

Selecting a Subset Drops the Matrix Shape

It is important to understand that the property of being shaped as a matrix is lost as soon as we select a subset of the data - a phase, a region, the indexed measurements - since what is left is in general not a rectangle any more

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 x Y x Z : [0 → 50] x [0 → 41] x [0 → 0]
 Normal vector: (0,0,1)

We may always force it back into matrix form by reapplying the command gridify

ebsdMg = ebsd('Magnesium').gridify
ebsdMg = EBSDsquare (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 x Y x Z : [0 → 50] x [0 → 41] x [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :137 x 167

The two matrix shaped variables ebsd and ebsdMg differ in what sits at the 46 positions that were not indexed. In ebsd those are measurements like any other, belonging to the separate phase 'notIndexed'. In ebsdMg they were never part of the selection, so gridify had nothing to put there and left the lattice site empty - its orientation is NaN and it belongs to no phase at all, which is what an empty phaseId distinguishes

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

Either way the map is a full rectangle, which is what allows to select and plot subregions of it in a very intuitive way

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

Gridding Reorders the Measurements

gridify does not preserve the order in which the measurements arrive, and can not: the layout described above fixes the first matrix dimension to y, while a .ctf or .ang file is written with x varying fastest, so MATLAB's column major linear indexing runs down the map where the file runs across it. After gridding, ebsd(k) is therefore in general not the measurement in line k of the file any more.

Nothing is lost by that. The original ids are kept as the property oldId, and looking at its top left corner shows the effect directly - consecutive ids run along a matrix row, i.e. across the map, while MATLAB's linear index runs down a column

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

and the second output of gridify is the translation in the other direction, i.e. ebsdGrid.pos(newId) are the positions of the list in the order of the list

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

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

This matters only for code that depends on the order of its input, and grain reconstruction does not - calcGrains returns the same grains whether it is handed the list or the map.

The Gradient

The orientation gradient, the incomplete Nye tensor and the weighted Burgers vector are computed on the virtual lattice and therefore do not require a grid. A grid does not hurt either, so we simply continue with the map we already have - the result then comes back in the shape of the map.

gradX = ebsdMg.gradientX;

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

Hexagonal Grids

Nothing above is specific to square grids. Data measured on a hexagonal grid is imported as an EBSDhex and indexed in exactly the same way

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 x Y x Z : [0 → 592] x [0 → 585] x [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 x 119
plot(ebsd(1:20,1:40),ebsd(1:20,1:40).orientations,'micronbar','off','edgeColor','black')

Switching from Hexagonal to Square Grid

Sometimes it is required to resample EBSD data measured on a hex grid onto a square grid. This can be accomplished by passing to the command gridify a square unit cell by the option unitCell.

% 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)
 
 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 x Y x Z : [0 → 595] x [0 → 585] x [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :118 x 120

Note the difference to gridding a map onto its own lattice: a custom unit cell defines a grid the measurements do not lie on, so the values of the new pixels are interpolated from the nearest measurements - see interp. The resampled map is complete, but it can only be as good as its resolution allows. In the example above we have chosen the square unit cell to have approximately the same size as the hexagonal one, and since squares can not reproduce the shapes of hexagons the grain boundaries come out visibly stair cased. We reduce this by choosing the square unit cell significantly smaller than the hexagonal one.

% 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)
 
 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 x Y x Z : [0 → 593] x [0 → 585] x [0 → 0]
 Normal vector: (0,0,1)
 Square grid  :586 x 594

What is left not indexed in the resampled map are the pixels that were not indexed in the source map. Those may be interpolated as well, either by fill, which performs nearest neighbor interpolation, or by smooth, which allows for more sophisticated methods - see Filling Missing Data.

Rotated Grids

Rotating a map rotates its unit cell along with the positions, so the grid survives the rotation and the map remains an EBSDsquare or an EBSDhex. There is nothing to repair and no data to interpolate.

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 x Y x Z : [-198 → 555] x [0 → 752] x [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 x 119
plot(ebsdR,ebsdR.orientations)

The same holds if such data reaches MTEX as a plain list - gridify recovers the rotated lattice, it does not require an axis aligned one

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 x Y x Z : [-198 → 555] x [0 → 752] x [0 → 0]
 Normal vector: (0,0,1)
 Hex grid     :136 x 119

Robustness to Distorted (e.g. Trapezoidal) Grids

Real EBSD stages sometimes drift smoothly during a scan rather than rotating rigidly. A common signature is a trapezoidal distortion: each scan row is stretched or compressed about the map centre by an amount that grows with the row's y position, so the same nominally rectangular map ends up narrower at one edge than at the other. MTEX reconstructs the underlying grid indices of an EBSD object robustly under this kind of smooth, non-rigid distortion - which matters for gridify above, but also for any operation that needs the map's grid structure internally, such as calcGrains or the surf plotting backend.

We demonstrate this on a real map, rather than a small synthetic one, since the failure mode this guards against only becomes visible once the map is realistically wide - a small toy grid stays safe at distortion levels that already break a real, 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 x Y x Z : [33000 → 36000] x [4500 → 7500] x [0 → 0]
 Normal vector: (0,0,1)

The command transform applies an arbitrary function to the position of every pixel, leaving orientations and all other properties untouched. Here we scale the x-position of every pixel about the map's centre by an amount that grows linearly with y - a trapezoidal stage drift of up to trapFrac at the top and bottom edges. Note that the distortion is defined through the physical y position, not through a column of ebsd.lattice.ij - which of its two columns happens to correspond to rows vs columns depends on an internal, unspecified choice of the lattice basis and is not something to rely on.

x = ebsd.pos.x; xCenter = (min(x)+max(x))/2;
y = ebsd.pos.y; yCenter = (min(y)+max(y))/2; yHalf = (max(y)-min(y))/2;
trapFrac = 0.05;

distort = @(pos) vector3d( ...
  xCenter + (pos.x-xCenter) .* (1 + trapFrac*(pos.y-yCenter)/yHalf), ...
  pos.y, pos.z);

ebsdDistorted = transform(ebsd, distort);

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

Even though every row has now shifted by a different amount, MTEX recovers exactly the same grid indices as for the undistorted map

isequal(ebsdDistorted('Fo').lattice.ij, ebsd('Fo').lattice.ij)
ans =
  logical
   1
grains = calcGrains(ebsdDistorted,'minPixel',5)

hold on
plot(grains.boundary,'lineWidth',2)
hold off
grains = grain2d (y↑→x)
 
 Phase  Grains  Pixels     Mineral  Symmetry         Color
     1      12    1936  Forsterite       mmm  LightSkyBlue
     2       4     284   Enstatite       mmm  DarkSeaGreen
     3       3     230    Diopside     12/m1     Goldenrod
 
 boundary segments: 952 (37582 µm)
 inner boundary segments: 28 (1092 µm)
 triple points: 15
 
 Id   Phase   Pixels       meanRotation      GOS
  1       3       29    (155°,47°,299°)   0.0072
  2       3      148    (32°,148°,241°)    0.023
  3       1      149   (177°,111°,285°)    0.011
  4       3       53    (142°,81°,317°)     0.02
  5       2        9     (73°,36°,353°)    0.013
  6       1       93    (176°,93°,246°)    0.042
  7       1       15     (56°,68°,280°)    0.033
  8       1        8   (168°,101°,264°)   0.0062
  9       1      526     (19°,85°,256°)    0.038
 10       1      874   (162°,102°,268°)     0.13
 11       1       38    (155°,53°,302°)    0.059
 12       1       22    (174°,82°,282°)   0.0098
 13       1       14   (128°,146°,231°)   0.0071
 14       2       36    (125°,62°,331°)   0.0084
 15       1      123   (179°,128°,261°)    0.024
 16       1       38    (167°,97°,261°)     0.01
 17       2        1    (164°,35°,295°)        0
 18       2      238    (164°,35°,295°)   0.0075
 19       1       36    (167°,98°,260°)   0.0083