API summary
Creating deformations
RegisterDeformation.GridDeformation
— Typeϕ = GridDeformation(u::Array{<:SVector}, axs)
creates a deformation ϕ
for an array with axes axs
. u
specifies the "pixel-wise" displacement at a series of nodes that are evenly-spaced over the domain specified by axs
(i.e., using node-vectors range(first(axs[d]), stop=last(axs[d]), length=size(u,d))
). In particular, each corner of the array is the site of one node.
ϕ = GridDeformation(u::Array{<:SVector}, nodes)
specifies the node-vectors manually. u
must have dimensions equal to (length(nodes[1]), length(nodes[2]), ...)
.
ϕ = GridDeformation(u::Array{T<:Real}, ...)
constructs the deformation from a "plain" u
array. For a deformation in N
dimensions, u
must have N+1
dimensions, where the first dimension corresponds to the displacement along each axis (and therefore size(u,1) == N
).
Finally, ϕ = GridDeformation((u1, u2, ...), ...)
allows you to construct the deformation using an N
-tuple of shift-arrays, each with N
dimensions.
Example
To represent a two-dimensional deformation over a spatial region 1:100 × 1:200
(e.g., for an image of that size),
gridsize = (3, 5) # a coarse grid
u = 10*randn(2, gridsize...) # each displacement is 2-dimensional, typically ~10 pixels
nodes = (range(1, stop=100, length=gridsize[1]), range(1, stop=200, length=gridsize[2]))
ϕ = GridDeformation(u, nodes) # this is a "naive" deformation (not ready for interpolation)
RegisterDeformation.tform2deformation
— Functionϕ = tform2deformation(tform, imgaxes, gridsize)
Construct a deformation ϕ
from the affine transform tform
suitable for warping arrays with axes imgaxes
. The array of grid points defining ϕ
has size specified by gridsize
. The dimensionality of tform
must match that specified by arraysize
and gridsize
.
Note it's more accurate to warp(img, tform)
directly; the main use of this function is to initialize a GridDeformation for later optimization.
RegisterDeformation.griddeformations
— Functionϕs = griddeformations(u, nodes)
constructs a vector ϕs
of seqeuential deformations. The last dimension of the array u
should correspond to time; ϕs[i]
is produced from u[:, ..., i]
.
RegisterDeformation.regrid
— Functionϕnew = regrid(ϕ, gridsize)
Reparametrize the deformation ϕ
so that it has a grid size gridsize
.
Example
ϕnew = regrid(ϕ, (13,11,7))
for a 3-dimensional deformation ϕ
.
RegisterDeformation.similarϕ
— Functionϕ = similarϕ(ϕref, coefs)
Create a deformation with the same nodes as ϕref
but using coefs
for the data. This is primarily useful for testing purposes, e.g., computing gradients with ForwardDiff
where the elements are ForwardDiff.Dual
numbers.
If ϕref
is interpolating, coefs
will be used for the interpolation coefficients, not the node displacements. Typically you want to create ϕref
with
ϕref = interpolate!(copy(ϕ0))
rather than interpolate(ϕ0)
(see interpolate!
).
Conversion to interpolating form
Interpolations.interpolate
— Methodϕi = interpolate(ϕ, BC=Flat(OnCell()))
Create a deformation ϕi
suitable for interpolation, matching the displacements of ϕ.u
at the nodes. A quadratic interpolation scheme is used, with default flat boundary conditions.
Interpolations.interpolate!
— Methodϕi = interpolate!(ϕ, BC=InPlace(OnCell()))
Create a deformation ϕi
suitable for interpolation, matching the displacements of ϕ.u
at the nodes. ϕ
is destroyed in the process.
A quadratic interpolation scheme is used, with the default being to use "InPlace" boundary conditions.
Because of the difference in default boundary conditions, interpolate!(copy(ϕ))
does not yield a result identical to interpolate(ϕ)
. When it matters, it is recommended that you annotate such calls with # not same as interpolate(ϕ)
in your code.
Interpolations.extrapolate
— Methodϕi = extrapolate(ϕ, BC=Flat(OnCell()))
Create a deformation ϕi
suitable for interpolation, matching the displacements of ϕ.u
at the nodes. A quadratic interpolation scheme is used, with default flat boundary conditions and Line
extrapolation.
Extrapolation beyond the supported region of ϕ
can yield poor results.
RegisterDeformation.extrapolate!
— Methodϕi = extrapolate!(ϕ, BC=InPlace(OnCell()))
Create a deformation ϕi
suitable for interpolation, matching the displacements of ϕ.u
at the nodes. ϕ
is destroyed in the process.
A quadratic interpolation scheme is used, with the default being to use "InPlace" boundary conditions.
Because of the difference in default boundary conditions, extrapolate!(copy(ϕ))
does not yield a result identical to extrapolate(ϕ)
. When it matters, it is recommended that you annotate such calls with # not same as extrapolate(ϕ)
in your code.
Warping images
RegisterDeformation.WarpedArray
— TypeA WarpedArray
W
is an AbstractArray for which W[x] = A[ϕ(x)]
for some parent array A
and some deformation ϕ
. The object is created lazily, meaning that computation of the displaced values occurs only when you ask for them explicitly.
Create a WarpedArray
like this:
W = WarpedArray(A, ϕ)
where
- The first argument
A
is anAbstractExtrapolation
that can be evaluated anywhere. See the Interpolations package. - ϕ is an
AbstractDeformation
ImageTransformations.warp
— Functionwimg = warp(img, ϕ)
warps the array img
according to the deformation ϕ
.
ImageTransformations.warp!
— Functionwarp!(dest, src::WarpedArray)
instantiates a WarpedArray
in the output dest
.
warp!(dest, img, ϕ)
warps img
using the deformation ϕ
. The result is stored in dest
.
warp!(dest, img, tform, ϕ)
warps img
using a combination of the affine transformation tform
followed by deformation with ϕ
. The result is stored in dest
.
warp!(T, io, img, ϕs; [nworkers=1])
writes warped images to disk. io
is an IO
object or HDF5/JLD2 dataset (the latter must be pre-allocated using d_create
to be of the proper size). img
is an image sequence, and ϕs
is a vector of deformations, one per image in img
. If nworkers
is greater than one, it will spawn additional processes to perform the deformation.
An alternative syntax is warp!(T, io, img, uarray; [nworkers=1])
, where uarray
is an array of u
values with size(uarray)[end] == nimages(img)
.
RegisterDeformation.translate
— FunctionAtrans = translate(A, displacement)
shifts A
by an amount specified by displacement
. Specifically, in simple cases Atrans[i, j, ...] = A[i+displacement[1], j+displacement[2], ...]
. More generally, displacement
is applied only to the spatial coordinates of A
.
NaN
is filled in for any missing pixels.
Composing deformations
By computing the composition ϕc = ϕ1(ϕ2)
, you mimic the effect of warping the image with ϕ1
and then warping the result with ϕ2
.
CoordinateTransformations.compose
— Functionϕ_c = ϕ_old(ϕ_new)
computes the composition of two deformations, yielding a deformation for which ϕ_c(x) ≈ ϕ_old(ϕ_new(x))
. ϕ_old
must be interpolating (see interpolate(ϕ_old)
).
ϕ_c, g = compose(ϕ_old, ϕ_new)
also yields the gradient g
of ϕ_c
with respect to u_new
. g[i,j,...]
is the Jacobian matrix at grid position (i,j,...)
.
You can use _, g = compose(identity, ϕ_new)
if you need the gradient for when ϕ_old
is equal to the identity transformation.
ϕsi_old
and ϕs_new
will generate ϕs_c
vector and g
vector ϕsi_old
is interpolated `ϕs_old
: e.g) ϕsi_old = map(Interpolations.interpolate!, copy(ϕs_old))
Temporal manipulations
RegisterDeformation.medfilt
— Functionϕs′ = medfilt(ϕs)
Perform temporal median-filtering on a sequence of deformations. This is a form of smoothing that does not "round the corners" on sudden (but persistent) shifts.
RegisterDeformation.tinterpolate
— Functionϕs = tinterpolate(ϕsindex, tindex, nstack)
uses linear interpolation/extrapolation in time to "fill out" to times 1:nstack
a deformation defined intermediate times tindex
. Note that ϕs[tindex] == ϕsindex
.
Visualizing deformations
RegisterDeformation.nodegrid
— Functionimg = nodegrid(T, nodes)
img = nodegrid(nodes)
img = nodegrid([T], ϕ)
Returns an image img
which has value 1 at points that are "on the nodes." If nodes
/ϕ
is two-dimensional, the image will consist of grid lines; for three-dimensional inputs, the image will have grid planes.
The meaning of "on the nodes" is that x[d] == nodes[d][i]
for some dimension d
and node index i
. An exception is made for the edge values, which are brought inward by one pixel to prevent complete loss under subpixel deformations.
Optionally specify the element type T
of img
.
See also warpgrid
.
RegisterDeformation.warpgrid
— Functionimg = warpgrid(ϕ; [scale=1, showidentity=false])
Returns an image img
that permits visualization of the deformation ϕ
. The output is a warped rectangular grid with nodes centered on the control points as specified by the nodes of ϕ
. If ϕ
is a two-dimensional deformation, the image will consist of grid lines; for a three-dimensional deformation, the image will have grid planes.
scale
multiplies ϕ.u
, effectively making the deformation stronger (for scale > 1
). This can be useful if you are trying to visualize subtle changes. If showidentity
is true
, an RGB image is returned that has the warped grid in magenta and the original grid in green.
See also nodegrid
.
Low-level utilities
RegisterDeformation.eachnode
— Functioniter = eachnode(ϕ)
Create an iterator for visiting all the nodes of ϕ
.
RegisterDeformation.centeraxes
— Functioncaxs = centeraxes(axs)
Return a set of axes centered on zero. Specifically, if axs[i]
is a range, then caxs[i]
is a UnitRange of the same length that is approximately symmetric around 0.