Skip to content

Hdf5 Deformation fields

John Bogovic edited this page Jun 4, 2024 · 24 revisions

See here for a description of the file name conventions for "forward" and "inverse" transformations.

Motivation

Some applications apply transformations (deformation fields) on a small subset of the domain over which it is defined (e.g., transforming sparse points or skeletons). In these cases, it is unnecessary and wasteful to read the whole of the deformation field from file. This format stores deformation fields as HDF5 files or N5 datasets, both of which enable blockwise, incremental loading from disk.

We observe that transforming sparse skeletons with h5 displacement fields can be over 35x faster than using nifti format displacements fields. The slowdown of the nifti is due to the overhead of loading in extraneous parts of the displacement field.

Furthermore, it enables quantization of displacement values to reduce file size. It is also possible to store multiple displacement fields at different resolutions (downsample factors).

Format specification

Store an affine transform and a deformation field as dataset in the HDF5 or N5 specification.

Datasets

  • The forward transforms are stored in the dfield dataset.
  • The inverse transform must be stored in the invdfield dataset (optional).

Deformation fields

  • Dimensionality

    • Currently 2D and 3D deformation fields are supported.
    • 2D
      • Displacement fields are stored as 3D datasets, whose dimensions are ordered [2,X,Y] where the first dimension varies fastest in memory (i.e. F-order) contains displacement vector components.
    • 3D
      • Displacement fields are stored as 4D datasets, whose dimensions are ordered [3,X,Y,Z] where the first dimension varies fastest in memory (i.e. F-order) contains displacement vector components.
  • Block / cell size

    • Blocks must be of size 3 in their last dimension (i.e., block may not split across the displacement vector dimension). Otherwise, any block size is permissible.
  • Data type

    • Data can be stored as floating point (H5T_NATIVE_FLOAT,H5T_NATIVE_DOUBLE) types
    • or as signed integer types (H5T_NATIVE_CHAR, H5T_NATIVE_SHORT, H5T_NATIVE_INT)
      • If the type is an integer type, the dataset must stores a quantization_multiplier attribute (see below) and is interpreted as quantized continuous values (see below)

Multi-resolution (optional)

Downsampled versions of deformation fields may be stored in the same container as the original resolution in the same container. In this case, transformations are stored in datasets of the form /<level>/dfield, where level indicates the amount of downsampling, and level=0 represents the full-resolution deformation field. For example an h5 file might contain these datasets:

/0/dfield
/0/invdfield
/1/dfield
/1/invdfield
/2/dfield
/2/invdfield

where /0/dfield is the foward deformation field at full resolution, and /2/invdfield is the inverse deformation field at the lowest resolution.

Affine

  • 2D
    • The affine part of a transformation is stored as a double array of length 6 in the affine attribute.
    • It is interpreted as a2 x 3 matrix stored in row-major order, and is the upper 2 x 3 submatrix of the 3 x 3 matrix in homogeneous coordinates
  • 3D
    • The affine part of a transformation is stored as a double array of length 12 in the affine attribute.
    • It is interpreted as a3 x 4 matrix stored in row-major order, and is the upper 3 x 4 submatrix of the 4 x 4 matrix in homogeneous coordinates

Attributes

  • quantization_multiplier stores quantization multiplier as a double (see below).
  • 2D
    • affine stores the affine part of the transformation as a double[6] (see above).
    • spacing stores pixel spacing (resolution) as a double[2]
  • 3D
    • affine stores the affine part of the transformation as a double[12] (see above).
    • spacing stores pixel spacing (resolution) as a double[3]

Quantization

If the datatype is an integer type, the stored values are interpreted as quantized continuous values, where the continuous values are obtained by multiplying with the quantization_multiplier attribute, which must be present. Specifically:

  • vector_displacement_i = quantization_multiplier * (double) integer_value_i

Spatial extents

2D

Values stored at the discrete coordinate (v,i,j) in the 3D array correspond to the physical position:

  • (x, y) = ( spacing[0] * i, spacing[1] * j )

3D

Values stored at the discrete coordinate (v,i,j,k) in the 4D array correspond to the physical position:

  • (x, y, z) = ( spacing[0] * i, spacing[1] * j, spacing[2] * k ) Values stored at the discrete coordinate (v,i,j,k) in the 4D array correspond to the physical position:

Inverses

This format can store both the forward and inverse transformations in a single file, and often each direction contains both an affine and deformable component (e.g., when the transform was found by ANTs - see here for ANTs conventions. The order in which these transforms are applied is different, and so great care must be taken to ensure the transform are applied correctly.

Code and usage examples

Inverse

We take great care in applying the inverse transforms correctly, especially when it is the concatenation of an affine and deformable component. The safest way to proceed is to always obtain an invertible transform as follows:

n5 = new N5HDF5Reader( hdf5Path, 3, 32, 32, 32);
interp = new NLinearInterpolatorFactory<>();

transform = N5DisplacementField.openInvertible( n5, new DoubleType(), interp );
inverseTransform = transform.inverse();

Converting transforms between types

CMTK-compatible affine and displacement fields from H5 (Terminal)

Use the convertH5Transform script, and give it --cmtk as the third argument.

This will produce a file ending in .xform containing the affine part, a file ending in _fwd.nrrd containing the forward deformable part, and a file ending in _inv.nrrd containing the inverse deformable part (if it is present in the h5 file).

convertH5Transform transform.h5 cmtk_transform_prefix --cmtk
ANTS-compatible affine and displacement fields from H5 (Terminal)

Use the convertH5Transform script. It's default behavior is to produce ANTs-compatible transforms, but you can explicitly give it the flag --ants as the third argument.

This will produce a file ending in .mat containing the affine part, a file ending in _fwd.nii containing the forward deformable part, and a file ending in _inv.nii containing the inverse deformable part (if it is present in the h5 file).

convertH5Transform transform.h5 ants_transform_prefix

# or
# convertH5Transform transform.h5 cmtk_transform_prefix --ants

Converting the affine part of transforms

ANTs-compatible forward and inverse affines from H5 (Java)
N5Reader n5 = new N5HDF5Reader( hdf5Path, 32, 32, 32, 3 );
AffineTransform3D forwardAffine = new AffineTransform3D();
forwardAffine.set( n5.getAttribute( "dfield", N5DisplacementField.AFFINE_ATTR, double[].class ));

AffineTransform3D reverseAffine = new AffineTransform3D();
reverseAffine.set( n5.getAttribute( "invdfield", N5DisplacementField.AFFINE_ATTR, double[].class ));
ANTs-compatible forward and inverse affines from H5 (Terminal)
convertAffine transform.h5 forward_affine_file.mat

# Anything after the colon ':' tells the script which dataset to use
# 'invdfield' contains the inverrse transform
convertAffine transform.h5:invdfield inverse_affine_file.mat
CMTK-compatible forward and inverse affines from H5 (Terminal)

Use the convertAffine script, and give your output file name an xform extension

convertAffine transform.h5 forward_affine_file.xform

# Anything after the colon ':' tells the script which dataset to use
# 'invdfield' contains the inverrse transform
convertAffine transform.h5:invdfield inverse_affine_file.xform