Skip to content

ANTs transform concepts and file formats

Philip Cook edited this page Oct 2, 2024 · 7 revisions

Transform concepts

ANTs is based on ITK, SimpleITK Fundamental Concepts is a good introduction to this topic. The slicer primer on coordinate systems is also very useful. ITK and ANTs use LPS coordinates, as explained there. These are the "physical coordinates" as defined by ITK / ANTs. The units of ITK coordinates are millimeters.

ANTs registration transforms

By default, ANTs registration outputs an affine transform object (0GenericAffine.mat) and displacement fields for the forward and inverse warps (1Warp.nii.gz, 1InverseWarp.nii.gz).

Why is there no inverse affine?

Affine transforms are invertible, so the decision was made long ago to only store the forward affine transform. It is inverted on the fly when needed.

Displacement fields are not invertible, you cannot recover the inverse warp from the forward warp. The inverse can be approximated, but this is not straightforward, so both warps are saved.

Definition of forward and inverse transforms

The forward transforms are used to move a point in physical space within the fixed image to the corresponding point in the moving image.

When resampling the moving image into the fixed space, we start with the grid of points we want to output (the center of every voxel in the fixed space), then warp this point into the moving space to find our sample value.

The inverse transforms move points from moving to fixed space. For more on this topic and examples in the command line ANTs, see this wiki page.

Displacement field images

The forward displacement field is defined by the domain of the fixed image. It has the same voxel grid, spacing, and orientation as the fixed image, but each voxel is a displacement vector in physical space for a point at the center of the voxel in fixed space, towards the moving space, before applying the affine transform.

The inverse displacement field is also in the domain of the fixed image. It has the same voxel grid, spacing, and orientation as the forward warps - but the displacement vectors in each voxel transform points towards the fixed space, after applying the inverse affine.

When applying the transforms, the order matters: for forward transforms, the warp field comes first, and for inverse transforms the inverted affine comes first.

Example

import shutil
import ants

fi = ants.image_read(ants.get_ants_data('r16'))
mi = ants.image_read(ants.get_ants_data('r64'))

fi = ants.resample_image(fi, (60,60), 1, 0)
mi = ants.resample_image(mi, (60,60), 1, 0)

mytx = ants.registration(fixed=fi, moving=mi, type_of_transform = 'SyN' )

shutil.copyfile(mytx['fwdtransforms'][0], 'movingToFixed1Warp.nii.gz')
shutil.copyfile(mytx['fwdtransforms'][1], 'movingToFixed0GenericAffine.mat')

# Note invtransforms in opposite order
shutil.copyfile(mytx['invtransforms'][0], 'fixedtoMoving0GenericAffine.mat')
shutil.copyfile(mytx['invtransforms'][1], 'fixedtoMoving1Warp.nii.gz')

Applying the transforms

mi_deformed = ants.apply_transforms(fixed=fi,
                                    moving=mi,
                                    transformlist=mytx['fwdtransforms'],
                                    whichtoinvert=[False, False])

fi_deformed = ants.apply_transforms(fixed=mi,
                                    moving=fi,
                                    transformlist=mytx['invtransforms'],
                                    whichtoinvert=[True, False])

The whichtoinvert list tells apply_transforms which (if any) elements in transformlist are affine transforms to invert.

Transform file formats

Affine transforms

We can examine the parameters of an affine transform with ANTs or ANTsPy. In ANTs:

% antsTransformInfo movingToFixed0GenericAffine.mat
Transform file: movingToFixed0GenericAffine.mat
Number of transforms = 1
AffineTransform (0x7fbc8d2271c0)
  RTTI typeinfo:   itk::AffineTransform<double, 2u>
  Reference Count: 2
  Modified Time: 469
  Debug: Off
  Object Name: 
  Observers: 
    none
  Matrix: 
    0.944867 -0.0207925 
    0.0200101 1.00835 
  Offset: [-18.0306, 11.094]
  Center: [125.262, 129.116]
  Translation: [-27.6213, 14.6789]
  Inverse: 
    1.05789 0.021814 
    -0.0209931 0.991284 
  Singular: 0
Determinant: 0.953175

In ANTsPy

>>> fwd_aff = ants.read_transform('movingToFixed0GenericAffine.mat')
>>> fwd_aff.parameters
array([ 9.44866776e-01, -2.07925290e-02,  2.00100522e-02,  1.00835252e+00,
       -2.76213417e+01,  1.46789398e+01])
>>> fwd_aff.fixed_parameters
array([125.26158142, 129.11642456])

The parameters array contains the parameters of the 2x2 transformation matrix, in row-major order. The last two parameters are the translation. For a 3D transform, there would be 12 parameters, for a 3x3 matrix and a 3D translation.

The fixed parameters describe the center of rotation.

Alternative representation of the affine transform

The parameters in the transform above describe a 2x2 matrix A, a 1x2 translation t, and 1x2 center c. We can represent the transform with a single matrix in homogenous coordinates:

$$ A_h = \begin{pmatrix} A & t + c - A(c) \\ 0 & 1 \end{pmatrix} $$

The right column contains the "offset" $w = (t + c - Ac)$, so the full matrix is

$$ A_h = \begin{pmatrix} A_{11} & A_{12} & w_1 \\ A_{21} & A_{22} & w_2 \\ 0 & 0 & 1 \end{pmatrix} $$

The transform of a 2D point x is then $A_h(p)$, where p is $(x_1, x_2, 1)$.

Displacement fields

The displacement field is stored as a 5D NIFTI image. Use PrintHeader on the command line to view the header details.

For 2D registration, you should see something like

    dim[0] = 5
    dim[1] = 60
    dim[2] = 60
    dim[3] = 1
    dim[4] = 1
    dim[5] = 2
    dim[6] = 1
    dim[7] = 1

where dim[0] = 5, dim[1] and dim[2] are the dimensions of the fixed image, and dim[5] = 2. The (x,y) coordinates of the warp vector are stored in the 5th dimension. For 3D warps, dim[2] >= 1 and dim[5] = 3.

The image should have a NIFTI intent code for a vector image

    intent_code = 1007

Warps must be 5D, never 4D (such an image would be interpreted as a time series). It is possible to have 4D warps, where dim[5] = 4, and the last element of the displacement vector is a displacement in seconds. But this is an advanced use case, most registrations are in the 2D or 3D spatial domain.

Affine transform in Matlab

A 3D example

>> t = open('transform0GenericAffine.mat')

t = 

  struct with fields:

    AffineTransform_double_3_3: [12x1 double]
                         fixed: [3x1 double]

>> t.AffineTransform_double_3_3

ans =

    0.9959
    0.0352
   -0.0834
    0.0156
    0.8404
    0.5417
    0.0892
   -0.5408
    0.8364
   -1.1429
  -12.0815
   -8.7514

>> t.fixed

ans =

     0
    18
    18

The field AffineTransform_double_3_3 contains the 9 elements of the 3x3 matrix in row-major order, followed by the three translation parameters. The field fixed contains the center of rotation.

$ antsTransformInfo transform0GenericAffine.mat
Transform file: transform0GenericAffine.mat
AffineTransform (0x175d8c0)
  RTTI typeinfo:   itk::AffineTransform<double, 3u>
  Reference Count: 3
  Modified Time: 512
  Debug: Off
  Object Name: 
  Observers: 
    none
  Matrix: 
    0.995892 0.0352335 -0.0834134 
    0.0156409 0.84041 0.541725 
    0.0891883 -0.540805 0.836406 
  Offset: [-0.275673, -18.9599, 3.92781]
  Center: [0, 18, 18]
  Translation: [-1.14291, -12.0815, -8.75136]
  Inverse: 
    0.995892 0.0156409 0.0891883 
    0.0352335 0.84041 -0.540805 
    -0.0834134 0.541725 0.836406 
  Singular: 0