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 twinsebsd = 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 167This 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.unitCellans = 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 0What a Grid Is Good For
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').gridifyebsdMg = 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 167The 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 46Either 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 337and 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
1This 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);
ebsdebsd = 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 119plot(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 offebsdS = 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 119plot(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 119Robustness 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 smallebsd = 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
1grains = calcGrains(ebsdDistorted,'minPixel',5)
hold on
plot(grains.boundary,'lineWidth',2)
hold offgrains = 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