Skip to content

Commit 967c373

Browse files
committed
fix(AFNI): load arrays of linear transforms - one less XFail (#40)
1 parent 95aef0d commit 967c373

File tree

3 files changed

+34
-4
lines changed

3 files changed

+34
-4
lines changed

nitransforms/io/afni.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
DisplacementsField,
1010
LinearParameters,
1111
TransformFileError,
12+
_ensure_image,
1213
)
1314

1415
LPS = np.diag([-1, -1, 1, 1])
@@ -38,15 +39,22 @@ def from_ras(cls, ras, moving=None, reference=None):
3839
ras = ras.copy()
3940
pre = LPS.copy()
4041
post = LPS.copy()
41-
if _is_oblique(reference.affine):
42+
43+
if reference is not None:
44+
reference = _ensure_image(reference)
45+
46+
if reference is not None and _is_oblique(reference.affine):
4247
print("Reference affine axes are oblique.")
4348
M = reference.affine
4449
A = shape_zoom_affine(
4550
reference.shape, voxel_sizes(M), x_flip=False, y_flip=False
4651
)
4752
pre = M.dot(np.linalg.inv(A)).dot(LPS)
4853

49-
if _is_oblique(moving.affine):
54+
if moving is not None:
55+
moving = _ensure_image(moving)
56+
57+
if moving is not None and _is_oblique(moving.affine):
5058
print("Moving affine axes are oblique.")
5159
M2 = moving.affine
5260
A2 = shape_zoom_affine(
@@ -55,7 +63,7 @@ def from_ras(cls, ras, moving=None, reference=None):
5563
post = A2.dot(np.linalg.inv(M2))
5664

5765
# swapaxes is necessary, as axis 0 encodes series of transforms
58-
parameters = np.swapaxes(post.dot(ras.dot(pre)), 0, 1)
66+
parameters = np.swapaxes(post @ ras @ pre, 0, 1)
5967

6068
tf = cls()
6169
tf.structarr["parameters"] = parameters.T
@@ -84,6 +92,26 @@ def from_string(cls, string):
8492
sa["parameters"] = parameters
8593
return tf
8694

95+
def to_ras(self, moving=None, reference=None):
96+
"""Return a nitransforms internal RAS+ matrix."""
97+
pre = LPS.copy()
98+
post = LPS.copy()
99+
100+
if reference is not None:
101+
reference = _ensure_image(reference)
102+
103+
if reference is not None and _is_oblique(reference.affine):
104+
raise NotImplementedError
105+
106+
if moving is not None:
107+
moving = _ensure_image(moving)
108+
109+
if moving is not None and _is_oblique(moving.affine):
110+
raise NotImplementedError
111+
112+
# swapaxes is necessary, as axis 0 encodes series of transforms
113+
return post @ np.swapaxes(self.structarr["parameters"].T, 0, 1) @ pre
114+
87115

88116
class AFNILinearTransformArray(BaseLinearTransformList):
89117
"""A string-based structure for series of AFNI linear transforms."""

nitransforms/linear.py

+2
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ def from_filename(cls, filename, fmt="X5", reference=None, moving=None):
243243
_factory = io.LinearTransformArray
244244
elif fmt.lower() == "fsl":
245245
_factory = io.fsl.FSLLinearTransformArray
246+
elif fmt.lower() == "afni":
247+
_factory = io.afni.AFNILinearTransformArray
246248
else:
247249
raise NotImplementedError
248250

nitransforms/tests/test_linear.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def test_loadsave_itk(tmp_path, data_path, testdata_path):
7272
)
7373

7474

75-
# @pytest.mark.xfail(reason="Not fully implemented")
75+
@pytest.mark.xfail(reason="Not fully implemented")
7676
@pytest.mark.parametrize("fmt", ["itk", "fsl", "afni", "lta"])
7777
def test_loadsave(tmp_path, data_path, testdata_path, fmt):
7878
"""Test idempotency."""

0 commit comments

Comments
 (0)