-
-
Notifications
You must be signed in to change notification settings - Fork 404
Applying transforms to point data
Point set transformations use the opposite transform ordering compared to resampling images.
Transform points from fixed to moving space:
antsApplyTransformsToPoints \
-d 3 \
-i landmarksInFixedSpace.csv \
-o landmarksInMovingSpace.csv \
-t movingToFixed_1Warp.nii.gz \
-t movingToFixed_0GenericAffine.mat
The forward warps, which we use to deform an image from moving to fixed space, are the warps that transform points from fixed to moving space. To move points in the opposite direction:
antsApplyTransformsToPoints \
-d 3 \
-i landmarksInMovingSpace.csv \
-o landmarksInFixedSpace.csv \
-t [movingToFixed_0GenericAffine.mat, 1]
-t movingToFixed_1InverseWarp.nii.gz \
The reason the transforms are reversed is that resampling an image from a moving space to
a fixed space requires transforming points in the opposite direction. Internally, we
begin with a regular voxel grid in the fixed space. For each point, we have to find where
in the moving space it corresponds to, and compute some interpolated value. Conceptually,
it's tempting to think of the moving image being "pushed", deformed into the fixed space.
In reality, antsApplyTransforms is taking points in the fixed space and then figuring
out where in the moving space to "pull" a value from.
With point set registrations, we aren't doing a "pull" operation, we are "pushing" points from one space to another and leaving them at their precise location in the other space. Thus we use the forward warps to move a point from fixed to moving space, and inverse warps to go the other way.
The input and output to antsApplyTransformsToPoints is coordinates in the LPS+
physical space used by ITK / DICOM. This differs from NIFTI coordinates (RAS+).
ITK-SNAP, while based on ITK, also uses RAS+ coordinates for points.
The units of the coordinates are millimeters.
Input to antsApplyTransformsToPoints is either a CSV file with physical coordinates, or
a binary 2D MetaImage file containing the coordinates.
The CSV format should have a header row, and at least as many columns as the there are dimensions to the transform. Usually, the transform will be 3D, and so the CSV file should look like this:
x,y,z
-10.0,20.0,30.0
-15.0,25.0,35.0
Additional numeric columns are permitted, but they will not be transformed. For example,
x,y,z,label
-10.0,20.0,30.0,1
-15.0,25.0,35.0,2
The points will be transformed and the "label" column will be copied to the output CSV file unaltered. Non-numeric data are not supported.
The MetaImage format is a 2D binary image (.mha) where the first dimension corresponds to the number of points, and the second dimension corresponds to the number of dimensions in the transform. For example, for a 3D transform and 2 points, the image would have dimensions (2, 3). The pixel values in the image correspond to the coordinates of each point.
ANTsPy has a function ants.make_image that can be used to create a MetaImage from a list of points.
import ants
points = [[-10.0, 20.0, 30.0], [-15.0, 25.0, 35.0]]
points_image = ants.make_image(points)
ants.image_write(points_image, 'points.mha')If you need to generate coordinates from a landmark image, you can use
LabelGeometryMeasures. This will report the centroid of all unique labels in the image.
If you have multiple points per label, you can use LabelClustersUniquely to assign a
unique label to each cluster of connected voxels. Example:
LabelClustersUniquely \
3 \
points.nii.gz \
points_unique.nii.gz \
0 \
0
Then LabelGeometryMeasures can be used to compute the centroid of each unique label,
which will correspond to a single point in the CSV file.
LabelGeometryMeasures \
3 \
points_unique.nii.gz \
na \
points.csv
ANTsPy provides a utility function to convert points from index space to physical space, and vice versa. To convert to physical space,
import ants
source_image = ants.image_read('source.nii.gz')
points_voxel = [[10, 20, 30], [15, 25, 35]]
points_physical = [ ants.transform_index_to_physical_point(source_image, x) for x in points_voxel ]