align a sequence of images and EBSD maps of one specimen area
Two pictures of the same area rarely overlay: the EBSD map is taken at a tilt and drifts as it scans, the electron images do not, and no single distortion relates the extremes. TrueEBSD relates them through a CHAIN of intermediate maps instead, each pair separated by one distortion simple enough to fit, and composes the hops. What comes out is one common grid on which every pixel of every map is the same piece of specimen, so an image becomes a per pixel property of the map.
The sequence is ordered from the most distorted map to the ground truth, and hop n separates imgList(n) from imgList(n+1). Three methods walk it: pixelSizeMatch puts every map on one grid, calcDistortion measures and fits each hop, undistort accumulates the hops and resamples.
A handle class, as every class guiding a process in MTEX is - see parentGrainReconstructor. The methods still return the job, so job = pixelSizeMatch(job,pixSz) reads as before, but the job is modified in place: a second variable holding it is an alias rather than a snapshot, and a method that errors part way leaves the job partly filled.
THE MODEL IS THE CLASS. A hop's distortion is an unfitted spatialTransform - the class names which distortion separates the pair and calcDistortion fills in the coefficients. A multi stage hop is written with + and never *, because mtimes absorbs an operand reporting isid and an unfitted prototype has zero coefficients, so * would silently drop a stage. There is no sentinel on the reference: where nothing separates a pair, the entry is spatialTransformId.
Syntax
job = trueEbsd2(imgList)
job = trueEbsd2(imgList,T)imgList = [mapImage(ebsd.bc,ebsd), mapImage(img,'dxy',0.05)]
job = trueEbsd2(imgList, spatialTransformShift + spatialTransformDrift)
job.pixelSizeMatch
job.calcDistortion('fitErr')
job.undistortInput
| imgList | mapImage array, most distorted first, ground truth last. One array, not one argument per map. An EBSD map joins the sequence as mapImage(ebsd.bc,ebsd) - an image, with the map riding along |
| T | spatialTransform array, one per hop, numel(imgList)-1 of them. Unfitted prototypes. May also be set afterwards as job.T |
Output
| job | trueEbsd2 |
Class Properties
| imgList | mapImage array as imported, differing pixel sizes and extents |
| T | spatialTransform array, one per hop |
| resizedList | mapImage array on one common pixel grid, filled by pixelSizeMatch |
| shifts | cell, one entry per hop, each a pairShifts array with one entry per fit stage, the last being the final fit |
| fitError | pairShifts array, one per hop: the residual shifts measured AFTER correction, which is what says whether registration worked. Needs the 'fitErr' flag |
| undistortedList | mapImage array, aligned, filled by undistort |
| opt | struct array, one per map, the registration settings. Set with setOptions rather than by hand |
| Indexing, with numel(job.T) == numel(job.imgList) | 1 |
| job.imgList(n) % mapImage job.T(n) % spatialTransform, hop n | |
| job.T is a sequence and every spatialTransform method takes one transform, | |
| so inv(job.T) and isid(job.T) do not work | index a hop, or fold the chain with *. See spatialTransform and docs/adr/0007. job.opt(n) % the settings for map n job.shifts{n}(m) % hop n, fit stage m job.fitError(n) % pairShifts |
References
Tong et al., TrueEBSD in MTEX: automatic image matching for correlative microscopy applications, arXiv 2605.00703
See also
mapImage spatialTransform pairShifts trueEbsd2.pixelSizeMatch trueEbsd2.calcDistortion trueEbsd2.undistort trueEbsd2.setOptions parentGrainReconstructor