-
Notifications
You must be signed in to change notification settings - Fork 3
Hdf5 Deformation fields
See here for a description of the file name conventions for "forward" and "inverse" transformations.
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).
Store an affine transform and a deformation field as dataset in the HDF5 or N5 specification.
- The forward transforms are stored in the
dfield
dataset. - The inverse transform must be stored in the
invdfield
dataset (optional).
-
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.
- Displacement fields are stored as 3D datasets, whose dimensions are ordered
- 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.
- Displacement fields are stored as 4D datasets, whose dimensions are ordered
-
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.
- Blocks must be of size
-
- 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)
- If the type is an integer type, the dataset must stores a
- Data can be stored as floating point (
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.
- 2D
- The affine part of a transformation is stored as a double array of length 6 in the
affine
attribute. - It is interpreted as a
2 x 3
matrix stored in row-major order, and is the upper2 x 3
submatrix of the3 x 3
matrix in homogeneous coordinates
- The affine part of a transformation is stored as a double array of length 6 in the
- 3D
- The affine part of a transformation is stored as a double array of length 12 in the
affine
attribute. - It is interpreted as a
3 x 4
matrix stored in row-major order, and is the upper3 x 4
submatrix of the4 x 4
matrix in homogeneous coordinates
- The affine part of a transformation is stored as a double array of length 12 in the
-
quantization_multiplier
stores quantization multiplier as adouble
(see below). - 2D
-
affine
stores the affine part of the transformation as adouble[6]
(see above). -
spacing
stores pixel spacing (resolution) as adouble[2]
-
- 3D
-
affine
stores the affine part of the transformation as adouble[12]
(see above). -
spacing
stores pixel spacing (resolution) as adouble[3]
-
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
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 )
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:
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.
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();
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
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