Skip to content

Commit f84af7e

Browse files
committed
fix: in reality, there's nothing we must do :D
1 parent 6a11c30 commit f84af7e

File tree

6 files changed

+70
-180
lines changed

6 files changed

+70
-180
lines changed

docs/notebooks/02_afni_deoblique.ipynb

Lines changed: 46 additions & 124 deletions
Large diffs are not rendered by default.

nitransforms/io/afni.py

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from math import pi
33
import numpy as np
44
from nibabel.affines import (
5-
from_matvec,
5+
apply_affine,
66
obliquity,
77
voxel_sizes,
88
)
@@ -39,25 +39,8 @@ def to_string(self, banner=True):
3939
@classmethod
4040
def from_ras(cls, ras, moving=None, reference=None):
4141
"""Create an AFNI affine from a nitransform's RAS+ matrix."""
42-
pre = LPS
43-
post = LPS
44-
45-
if reference is not None:
46-
reference = _ensure_image(reference)
47-
48-
if reference is not None and _is_oblique(reference.affine):
49-
print("Reference affine axes are oblique.")
50-
ras = ras @ _afni_warpdrive(reference.affine, ras=True, forward=False)
51-
52-
if moving is not None:
53-
moving = _ensure_image(moving)
54-
55-
if moving is not None and _is_oblique(moving.affine):
56-
print("Moving affine axes are oblique.")
57-
ras = _afni_warpdrive(reference.affine, ras=True) @ ras
58-
5942
# swapaxes is necessary, as axis 0 encodes series of transforms
60-
parameters = np.swapaxes(post @ ras @ pre, 0, 1)
43+
parameters = np.swapaxes(LPS @ ras @ LPS, 0, 1)
6144

6245
tf = cls()
6346
tf.structarr["parameters"] = parameters.T
@@ -89,23 +72,8 @@ def from_string(cls, string):
8972

9073
def to_ras(self, moving=None, reference=None):
9174
"""Return a nitransforms internal RAS+ matrix."""
92-
pre = LPS
93-
post = LPS
94-
95-
if reference is not None:
96-
reference = _ensure_image(reference)
97-
98-
if reference is not None and _is_oblique(reference.affine):
99-
raise NotImplementedError
100-
101-
if moving is not None:
102-
moving = _ensure_image(moving)
103-
104-
if moving is not None and _is_oblique(moving.affine):
105-
raise NotImplementedError
106-
10775
# swapaxes is necessary, as axis 0 encodes series of transforms
108-
return post @ np.swapaxes(self.structarr["parameters"].T, 0, 1) @ pre
76+
return LPS @ np.swapaxes(self.structarr["parameters"].T, 0, 1) @ LPS
10977

11078

11179
class AFNILinearTransformArray(BaseLinearTransformList):
@@ -183,7 +151,7 @@ def _is_oblique(affine, thres=OBLIQUITY_THRESHOLD_DEG):
183151
return (obliquity(affine).min() * 180 / pi) > thres
184152

185153

186-
def _afni_warpdrive(nii, forward=True, ras=False):
154+
def _afni_warpdrive(oblique, shape, forward=True, ras=False):
187155
"""
188156
Calculate AFNI's ``WARPDRIVE_MATVEC_FOR_000000`` (de)obliquing affine.
189157
@@ -204,15 +172,18 @@ def _afni_warpdrive(nii, forward=True, ras=False):
204172
to be oblique.
205173
206174
"""
207-
oblique = nii.affine
208-
plumb = oblique[:3, :3] / np.abs(oblique[:3, :3]).max(0)
209-
plumb[np.abs(plumb) < 1.0] = 0
210-
plumb *= voxel_sizes(oblique)
211-
212-
R = from_matvec(plumb @ np.linalg.inv(oblique[:3, :3]), (0, 0, 0))
213-
plumb_orig = np.linalg.inv(R[:3, :3]) @ oblique[:3, 3]
214-
print(plumb_orig)
215-
R[:3, 3] = R[:3, :3] @ (plumb_orig - oblique[:3, 3])
175+
shape = np.array(shape[:3])
176+
plumb_r = oblique[:3, :3] / np.abs(oblique[:3, :3]).max(0)
177+
plumb_r[np.abs(plumb_r) < 1.0] = 0
178+
plumb_r *= voxel_sizes(oblique)
179+
plumb = np.eye(4)
180+
plumb[:3, :3] = plumb_r
181+
obliq_o = apply_affine(oblique, 0.5 * (shape - 1))
182+
plumb_c = apply_affine(plumb, 0.5 * (shape - 1))
183+
plumb[:3, 3] = -plumb_c + obliq_o
184+
print(obliq_o, apply_affine(plumb, 0.5 * (shape - 1)))
185+
186+
R = plumb @ np.linalg.inv(oblique)
216187
if not ras:
217188
# Change sign to match AFNI's warpdrive_matvec signs
218189
B = np.ones((2, 2))
@@ -228,5 +199,8 @@ def _afni_header(nii, field="WARPDRIVE_MATVEC_FOR_000000"):
228199
root.find(f".//*[@atr_name='{field}']").text,
229200
sep="\n",
230201
dtype="float32"
231-
).reshape((3, 4))
232-
return np.vstack((retval, (0, 0, 0, 1)))
202+
)
203+
if retval.size == 12:
204+
return np.vstack((retval.reshape((3, 4)), (0, 0, 0, 1)))
205+
206+
return retval
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
affine-RAS.afni

nitransforms/tests/test_io.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,6 @@ def test_LT_conversions(data_path, fname):
176176
)
177177
@pytest.mark.parametrize("sw", ["afni", "fsl", "fs", "itk"])
178178
def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata):
179-
if (image_orientation, sw) == ("oblique", "afni"):
180-
pytest.skip("AFNI Deoblique unsupported.")
181-
182179
tmpdir.chdir()
183180

184181
moving = get_testdata[image_orientation]

nitransforms/tests/test_linear.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
}
3535

3636

37-
@pytest.mark.parametrize("matrix", [[0.0], np.ones((3, 3, 3)), np.ones((3, 4)),])
37+
@pytest.mark.parametrize("matrix", [[0.0], np.ones((3, 3, 3)), np.ones((3, 4)), ])
3838
def test_linear_typeerrors1(matrix):
3939
"""Exercise errors in Affine creation."""
4040
with pytest.raises(TypeError):
@@ -141,9 +141,6 @@ def test_loadsave(tmp_path, data_path, testdata_path, fmt):
141141
@pytest.mark.parametrize("sw_tool", ["itk", "fsl", "afni", "fs"])
142142
def test_linear_save(tmpdir, data_path, get_testdata, image_orientation, sw_tool):
143143
"""Check implementation of exporting affines to formats."""
144-
if (image_orientation, sw_tool) == ("oblique", "afni"):
145-
pytest.skip("AFNI Deoblique unsupported.")
146-
147144
tmpdir.chdir()
148145
img = get_testdata[image_orientation]
149146
# Generate test transform

nitransforms/tests/test_nonlinear.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,6 @@ def test_displacements_field1(
112112
axis,
113113
):
114114
"""Check a translation-only field on one or more axes, different image orientations."""
115-
if (image_orientation, sw_tool) == ("oblique", "afni") and axis in ((1, 2), (0, 1, 2)):
116-
pytest.skip("AFNI Deoblique unsupported.")
117115
os.chdir(str(tmp_path))
118116
nii = get_testdata[image_orientation]
119117
msk = get_testmask[image_orientation]
@@ -176,6 +174,7 @@ def test_displacements_field1(
176174
sw_moved = nb.load("resampled.nii.gz")
177175

178176
nt_moved = xfm.apply(nii, order=0)
177+
nt_moved.set_data_dtype(nii.get_data_dtype())
179178
nt_moved.to_filename("nt_resampled.nii.gz")
180179
sw_moved.set_data_dtype(nt_moved.get_data_dtype())
181180
diff = (

0 commit comments

Comments
 (0)