resample an image in the shifted frame of its registration partner
Description
Every pixel of img sits at pos and is measured to belong at pos + s, so the value at pos itself has to be interpolated from neighbours. The two backends answer that from opposite ends.
'scattered' takes the forward route: it treats the displaced pixels as a point cloud, triangulates it, and asks for the nearest one to each grid point. That is O(n log n) to build and a tree search per query, and both halves get slower than linearly as the image grows.
'inverse' takes the backward route. The displacement field is smooth by construction - it is whatever the distortion model fitted, an affine, a spline along the slow scan direction or a homography - so p -> p + s(p) is invertible and its inverse can be found per pixel by the fixed point iteration q <- p - s(q), evaluating s on the regular grid it is stored on. Three or four passes converge to well under a hundredth of a pixel for the displacement magnitudes registration deals with. Sampling img at q is then a plain index, so the whole thing is O(n) with no triangulation.
The two agree on all but a few pixels in ten thousand - ties in the nearest neighbour lookup fall differently - and 'inverse' keeps a slightly wider border, because 'scattered' returns NaN outside the convex hull of the displaced points while 'inverse' returns NaN outside the image.
If the fixed point iteration does not settle, the mapping is not invertible on this grid, which means the fitted distortion folds the image over itself. That is a broken registration rather than a hard case, so it warns and falls back to 'scattered' rather than returning a wrong answer.
Syntax
imgOut = remapShifted(pos,shifts,img,'test2ref')
imgOut = remapShifted(pos,shifts,img,'ref2test','backend','scattered')Input
| pos | vector3d, position of every pixel, r*c |
| shifts | pairShifts, the displacement field to apply, in um. Anything with xShiftsMap and yShiftsMap fields will do, which is how undistort passes a displacement accumulated over several hops without having to fabricate a pairShifts for it. |
| img | r*c*z numeric, image values. Every channel is resampled through one mapping, so stacking things that travel together |
| image channels and an EBSD id map, say - is cheaper than calling this once each. | |
| shiftdir | 'test2ref' to add the shifts, 'ref2test' to subtract them |
Output
| imgOut | img resampled on pos, same r*c*z, NaN where pos falls outside the shifted image |
Options
| 'backend', 'inverse' | invert the displacement field and sample the image on a regular grid (default) |
| 'backend', 'scattered' | scatter the shifted pixels and rebuild with scatteredInterpolant, as TrueEBSD <= 2.1.0 did |