Skip to content

Commit e4e3f44

Browse files
Julien MarabottoJulien Marabotto
authored andcommitted
enh: x5 implementation
1 parent ba8a389 commit e4e3f44

File tree

4 files changed

+88
-27
lines changed

4 files changed

+88
-27
lines changed

nitransforms/base.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,52 @@ def to_filename(self, filename, fmt="X5"):
350350

351351
def _to_hdf5(self, x5_root):
352352
"""Serialize this object into the x5 file format."""
353+
transform_group = x5_root.create_group("TransformGroup")
354+
355+
"""Group '0' containing Affine transform"""
356+
transform_0 = transform_group.create_group("0")
357+
358+
transform_0.attrs["Type"] = "Affine"
359+
transform_0.create_dataset("Transform", data=self._matrix)
360+
transform_0.create_dataset("Inverse", data=np.linalg.inv(self._matrix))
361+
362+
metadata = {"key": "value"}
363+
transform_0.attrs["Metadata"] = str(metadata)
364+
365+
"""sub-group 'Domain' contained within group '0' """
366+
domain_group = transform_0.create_group("Domain")
367+
domain_group.attrs["Grid"] = self.grid
368+
domain_group.create_dataset("Size", data=_as_homogeneous(self._reference.shape))
369+
domain_group.create_dataset("Mapping", data=self.map)
370+
353371
raise NotImplementedError
372+
373+
def read_x5(x5_root):
374+
variables = {}
375+
with h5py.File(x5_root, "r") as f:
376+
f.visititems(lambda name, x5_root: _from_hdf5(name, x5_root, variables))
377+
378+
_transform = variables["TransformGroup/0/Transform"]
379+
_inverse = variables["TransformGroup/0/Inverse"]
380+
_size = variables["TransformGroup/0/Domain/Size"]
381+
_map = variables["TransformGroup/0/Domain/Mapping"]
382+
383+
return _transform, _inverse, _size, _map
384+
385+
def _from_hdf5(name, x5_root, storage):
386+
if isinstance(x5_root, h5py.Dataset):
387+
storage[name] = {
388+
'type': 'dataset',
389+
'attrs': dict(x5_root.attrs),
390+
'shape': x5_root.shape,
391+
'data': x5_root[()] # Read the data
392+
}
393+
elif isinstance(x5_root, h5py.Group):
394+
storage[name] = {
395+
'type': 'group',
396+
'attrs': dict(x5_root.attrs),
397+
'members': {}
398+
}
354399

355400

356401
def _as_homogeneous(xyz, dtype="float32", dim=3):

nitransforms/io/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"fs": (lta, "FSLinearTransform"),
2323
"fsl": (fsl, "FSLLinearTransform"),
2424
"afni": (afni, "AFNILinearTransform"),
25-
"x5": (x5, "X5LinearTransform")
25+
"x5": (x5, "X5Transform")
2626
}
2727

2828

nitransforms/io/x5.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,43 @@
1313
TransformFileError,
1414
)
1515

16-
LPS = np.diag([-1, -1, 1, 1])
17-
18-
class X5LinearTransform(LinearParameters):
16+
class X5Transform:
1917
"""A string-based structure for X5 linear transforms."""
2018

21-
template_dtype = np.dtype(
22-
[
23-
("type", "i4"),
24-
("index", "i4"),
25-
("parameters", "f8", (4, 4)),
26-
("offset", "f4", 3), # Center of rotation
27-
]
28-
)
29-
dtype = template_dtype
19+
_transform = None
3020

3121
def __init__(self, parameters=None, offset=None):
3222
return
3323

3424
def __str__(self):
3525
return
3626

37-
def to_filename(self, filename):
38-
'''store this transform to a file with the X5 format'''
39-
sa = self.structarr
40-
affine = '''some affine that will return a 4x4 array'''
41-
return
42-
4327
@classmethod
4428
def from_filename(cls, filename):
4529
"""Read the struct from a X5 file given its path."""
4630
if str(filename).endswith(".h5"):
47-
with H5File(str(filename)) as f:
48-
return cls.from_h5obj(f)
31+
with H5File(str(filename), 'r') as hdf:
32+
return cls.from_h5obj(hdf)
33+
34+
@classmethod
35+
def from_h5obj(cls, h5obj):
36+
"""Read the transformations in an X5 file."""
37+
xfm_list = list(h5obj.keys())
38+
39+
xfm = xfm_list["Transform"]
40+
inv = xfm_list["Inverse"]
41+
coords = xfm_list["Size"]
42+
map = xfm_list["Mapping"]
43+
44+
return xfm, inv, coords, map
45+
4946

5047
class X5LinearTransformArray(BaseLinearTransformList):
5148
"""A string-based structure for series of X5 linear transforms."""
5249

53-
_inner_type = X5LinearTransform
50+
_inner_type = X5Transform
5451

5552
@property
5653
def xforms(self):
57-
"""Get the list of internal ITKLinearTransforms."""
54+
"""Get the list of internal X5LinearTransforms."""
5855
return self._xforms

nitransforms/linear.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,34 @@ def map(self, x, inverse=False):
180180
affine = self._inverse
181181
return affine.dot(coords).T[..., :-1]
182182

183-
def _to_hdf5(self, x5_root):
183+
def _to_hdf5(self, x, x5_root):
184184
"""Serialize this object into the x5 file format."""
185-
xform = x5_root.create_dataset("Transform", data=[self._matrix])
186-
xform.attrs["Type"] = "affine"
187-
x5_root.create_dataset("Inverse", data=[(~self).matrix])
185+
transgrp = x5_root.create_group("TransformGroup")
186+
affine = self._x5group_affine(transgrp)
187+
coords = self._x5group_domain(x, affine)
188188

189189
if self._reference:
190190
self.reference._to_hdf5(x5_root.create_group("Reference"))
191191

192+
return #nothing?
193+
194+
def _x5group_affine(self, TransformGroup):
195+
"""Create group "0" for affine in x5_root/TransformGroup/ according to x5 file format"""
196+
aff = TransformGroup.create_group("0")
197+
aff.attrs["Type"] = "affine" #Should have shape {scalar}
198+
aff.attrs["Metadata"] = 'metadata' #This is a draft for metadata. Should have shape {scalar}
199+
aff.create_dataset("Transform", data=[self._matrix]) #Should have shape {3,4}
200+
aff.create_dataset("Inverse", data=[(~self).matrix]) #Should have shape {4,3}
201+
return aff
202+
203+
def _x5group_domain(self, x, transform):
204+
"""Create group "Domain" in x5_root/TransformGroup/0/ according to x5 file format"""
205+
coords = transform.create_group("Domain")
206+
coords.attrs["Grid"] = "grid" #How do I interpet this 'grid'? Should have shape {scalar}
207+
coords.create_dataset("Size", data=_as_homogeneous(x, dim=self._matrix.shape[0] - 1).T) #Should have shape {3}
208+
coords.create_dataset("Mapping", data=[self.map(self, x)]) #Should have shape {4,4}
209+
return coords
210+
192211
def to_filename(self, filename, fmt="X5", moving=None):
193212
"""Store the transform in the requested output format."""
194213
writer = get_linear_factory(fmt, is_array=False)

0 commit comments

Comments
 (0)