Skip to content

Commit a3372bc

Browse files
committed
feat(io.reader): draft implementation of ITK/ANTs' composite HDF5 transforms
Uses ``h5py`` to browse the transforms group, and concatenates them in a list ready for nitransforms' wrapping into a ``TransformsChain``. The writer is not included in this PR. The implementation takes into account the misspelling of "Tranforms" present in transforms generated with older versions of ITK. This implementation only accepts the composite transforms generated by ANTs. Resolves: #4
1 parent 439354a commit a3372bc

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

nitransforms/io/itk.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import warnings
33
import numpy as np
44
from scipy.io import savemat as _save_mat
5+
from nibabel import Nifti1Header, Nifti1Image
56
from nibabel.affines import from_matvec
67
from .base import (
78
BaseLinearTransformList,
@@ -280,3 +281,64 @@ def from_image(cls, imgobj):
280281
field[..., (0, 1)] *= -1.0
281282

282283
return imgobj.__class__(field, imgobj.affine, hdr)
284+
285+
286+
class ITKCompositeH5:
287+
"""A data structure for ITK's HDF5 files."""
288+
289+
@classmethod
290+
def from_filename(cls, filename):
291+
"""Read the struct from a file given its path."""
292+
from h5py import File as H5File
293+
if not str(filename).endswith('.h5'):
294+
raise RuntimeError("Extension is not .h5")
295+
296+
with H5File(str(filename)) as f:
297+
return cls.from_h5obj(f)
298+
299+
@classmethod
300+
def from_h5obj(cls, fileobj, check=True):
301+
"""Read the struct from a file object."""
302+
xfm_list = []
303+
h5group = fileobj["TransformGroup"]
304+
typo_fallback = "Transform"
305+
try:
306+
h5group['1'][f"{typo_fallback}Parameters"]
307+
except KeyError:
308+
typo_fallback = "Tranform"
309+
310+
for xfm in reversed(h5group.keys())[:-1]:
311+
if h5group[xfm]["TransformType"][0].startswith(b"AffineTransform"):
312+
xfm_list.append(
313+
ITKLinearTransform(
314+
parameters=np.asanyarray(h5group[xfm][f"{typo_fallback}Parameters"]),
315+
offset=np.asanyarray(h5group[xfm][f"{typo_fallback}FixedParameters"])
316+
)
317+
)
318+
continue
319+
if h5group[xfm]["TransformType"][0].startswith(b"DisplacementFieldTransform"):
320+
_fixed = np.asanyarray(h5group[xfm][f"{typo_fallback}FixedParameters"])
321+
shape = _fixed[:3].astype('uint16').tolist()
322+
offset = _fixed[3:6].astype('uint16')
323+
zooms = _fixed[6:9].astype('float')
324+
directions = _fixed[9:].astype('float').reshape((3, 3))
325+
affine = from_matvec(directions * zooms, offset)
326+
field = np.asanyarray(h5group[xfm][f"{typo_fallback}Parameters"]).reshape(
327+
tuple(shape + [-1])
328+
)
329+
hdr = Nifti1Header()
330+
hdr.set_intent("vector")
331+
hdr.set_data_dtype("float")
332+
333+
xfm_list.append(
334+
ITKDisplacementsField.from_image(
335+
Nifti1Image(field.astype("float"), affine, hdr)
336+
)
337+
)
338+
continue
339+
340+
raise NotImplementedError(
341+
f"Unsupported transform type {h5group[xfm]['TransformType'][0]}"
342+
)
343+
344+
return xfm_list

0 commit comments

Comments
 (0)