Convenience wrapper for image registration using the Advanced Normalization Tools
Author timholy
6 Stars
Updated Last
1 Year Ago
Started In
May 2018


Build Status

This provides a Julia wrapper around the Advanced Normalization Tools image registration and motion correction suite.


] add


Image data and file format

If you are passing the data via filenames, ensure that you have stored your images in an ITK-readable format. NRRD is recommended. For those performing acquisition with Imagine, you can write out an NRRD header.

Unfortunately, the NRRD format lacks a file-validator, and a few aspects of the standard description seem to leave room for interpretation. If you encounter bugs, it is possible that Julia and ITK differ with respect to the implementation of the NRRD header. Try copying the command and running it the shell prompt, then report the error here.

Performing registration


The process of registering images can be broken down into stages, and multiple stages can be cascaded together. A stage is specified as follows:

stage = Stage(fixedimg, transform, metric=MI(), shrink=(8,4,2,1), smooth=(3,2,1,0), iterations=(1000,500,250,5))

The transform might be one of the following:

transform = Global("Rigid")
transform = Global("Affine")
transform = Syn()

The last one is for a diffeomorphism (warping) registration.

This particular stage uses the MI metric for comparing the two images. MI is short for mutual information, a generalization of the notion of cross-correlation. This can be a good choice particularly when the images differ in ways other than just a spatial transformation, for example when they may be collected by different imaging modalities or exhibit intensity differences due to calcium transients. (With MI you can optionally specify various parameters such as the number of histogram bins.) Alternatively you can use MeanSquares (where the images are compared based on their mean-squared-difference) or CC (which stands for neighborhood cross correlation).

Finally, the last arguments in the example above indicate that we want to use a 4-level registration. For the first (coarsest) level, the image will be shrunk by a factor of 8, smoothed over a 3-pixel radius, and then aligned, allowing the parameters to be tweaked up to 1000 times when trying to minimize the metric. Choosing to shrink can improve performance, because small image pairs require fewer pixelwise comparisons than large images, as long as you don't shrink so much that features useful for alignment are eliminated. Likewise, smoothing can help find a good minimum by increasing the size of the "attraction basin," as long as you don't blur out sharp features that actually aid alignment.

Once the rigid transformation has been found for this coarsest level, it will be used to initialize the transformation for the next level. The final level uses a shrink of 1 (meaning to use the images at their provided size), a smooth of 0 (meaning no smoothing), and 5 iterations. This will ensure that the transformation doesn't miss opportunities for sub-pixel alignment at the finest scale.

All parameters after transform have default values, so you only need to assign them if you need to control them more precisely.

Note on physical units: if your images have anisotropic resolution, you should strongly consider using physical units for your smoothing. For example,

using Unitful: μm

would be appropriate for a two-iteration stage.

Top-level API

To register the single image moving to the single image fixed, use

imgw = register(fixed, moving, pipeline; kwargs...)

where pipeline is a single Stage or a vector of stages. For example, you can begin with an affine registration followed by a deformable registration:

stageaff = Stage(fixed, Global("Affine"))
stagesyn = Stage(fixed, Syn())
imgw = register(fixed, moving, [stageaff,stagesyn]; kwargs...)

This choice will align the images as well as possible (given the default parameters) using a pure-affine transformation, and then introduce warping where needed to improve the alignment.

If instead you'd like to correct for motion in an image sequence, consider

motioncorr((infofilename, warpedfilename), fixed, movingfilename, pipeline)

Here you represent the moving image sequence via its filename. The first argument stores the names of the files to which the data should be written. Of course, you can alternatively call register iteratively for each image in the series.

For more detailed information, see the help on individual types and functions.

Some notes for working on Windows

Currently, ANTs does not actively support Windows system, officially, they suggest using Linux subsystem instead.

And the latest binaries they built and released is 4 years old, and it may not work on every machine. Because of this, if you just install this package on Windows system and use the default Binary files downloaded by BinaryProvider in deps.jl, it's highly possible it will not work.

However, it's tested ANTs can be built from source in Windows system. (Windows 10, VS 2017) (Although officially they suggest compiling under Linux subsystem ). Conceptually it's the same as compiling under MacOS / Linux. It majorly involves using Cmake to determine the specificities of your machine / system and automatically generate the configuration file. And then using the compilers of Windows system (e.g. Visual Studio) to build the binary files from source.

After building from source (usually takes hours), you get your path containing the binary files (supposing it's D:\ANTs_2.1.0_Windows_new_build\bin\Release). Then, we have to inform ANTsRegistration.jl package of the correct location of the binary files. Currently, we have to do it by manually changing deps.jl file generated when building this julia package.

For example, change the relevent lines in deps.jl to be

const ants_bin_dir = raw"D:\ANTs_2.1.0_Windows_new_build\bin\Release\\" # use this variable for outside ANTs binary
const ants = joinpath(ants_bin_dir, "ANTS.exe") #joinpath(dirname(@__FILE__), "usr\\bin\\ANTS.exe")
const antsRegistration = joinpath(ants_bin_dir, "antsRegistration.exe") # joinpath(dirname(@__FILE__), "usr\\bin\\antsRegistration.exe")
const antsMotionCorr = joinpath(ants_bin_dir, "antsMotionCorr.exe")

Then, hopefully, the package can pass all of the tests!

Note that if you rebuild the package ("ANTsRegistration")) it is likely that these customizations will be overwritten. You are advised to save a copy of the modified deps.jl file and/or change the permissions to prevent it from being overwritten.