Skip to content

Commit

Permalink
Merge pull request #186 from nipy/fix/optimize-afni-toras
Browse files Browse the repository at this point in the history
FIX: Inefficient iterative reloading of reference and moving images
  • Loading branch information
oesteban authored Nov 16, 2023
2 parents 1674e86 + 01cfcbc commit 6e70c02
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 6 deletions.
26 changes: 21 additions & 5 deletions nitransforms/io/afni.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,17 @@ class AFNILinearTransformArray(BaseLinearTransformList):

def to_ras(self, moving=None, reference=None):
"""Return a nitransforms' internal RAS matrix."""
return np.stack(
[xfm.to_ras(moving=moving, reference=reference) for xfm in self.xforms]
)

pre_rotation = post_rotation = np.eye(4)
if reference is not None and _is_oblique(ref_aff := _ensure_image(reference).affine):
pre_rotation = _cardinal_rotation(ref_aff, True)
if moving is not None and _is_oblique(mov_aff := _ensure_image(moving).affine):
post_rotation = _cardinal_rotation(mov_aff, False)

return np.stack([
post_rotation @ (xfm.to_ras() @ pre_rotation)
for xfm in self.xforms
])

def to_string(self):
"""Convert to a string directly writeable to file."""
Expand All @@ -144,14 +152,22 @@ def to_string(self):
if line.strip()
]
strings += lines
return "\n".join(strings)
return "\n".join(strings + [""])

@classmethod
def from_ras(cls, ras, moving=None, reference=None):
"""Create an ITK affine from a nitransform's RAS+ matrix."""
_self = cls()

pre_rotation = post_rotation = np.eye(4)

if reference is not None and _is_oblique(ref_aff := _ensure_image(reference).affine):
pre_rotation = _cardinal_rotation(ref_aff, False)
if moving is not None and _is_oblique(mov_aff := _ensure_image(moving).affine):
post_rotation = _cardinal_rotation(mov_aff, True)

_self.xforms = [
cls._inner_type.from_ras(ras[i, ...], moving=moving, reference=reference)
cls._inner_type.from_ras(post_rotation @ ras[i, ...] @ pre_rotation)
for i in range(ras.shape[0])
]
return _self
Expand Down
1 change: 1 addition & 0 deletions nitransforms/tests/data/affine-LAS.afni-array
1 change: 1 addition & 0 deletions nitransforms/tests/data/affine-LPS.afni-array
3 changes: 3 additions & 0 deletions nitransforms/tests/data/affine-RAS.afni-array
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 3dvolreg matrices (DICOM-to-DICOM, row-by-row):
0.999999 -0.000999999 -0.001 -4 0.00140494 0.621609 0.783327 -2 -0.000161717 -0.783327 0.62161 -1
0.999999 -0.000999999 -0.001 -4 0.00140494 0.621609 0.783327 -2 -0.000161717 -0.783327 0.62161 -1
1 change: 1 addition & 0 deletions nitransforms/tests/data/affine-oblique.afni-array
7 changes: 6 additions & 1 deletion nitransforms/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def test_LT_conversions(data_path, fname):
"oblique",
],
)
@pytest.mark.parametrize("sw", ["afni", "fsl", "fs", "itk"])
@pytest.mark.parametrize("sw", ["afni", "fsl", "fs", "itk", "afni-array"])
def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata):
tmpdir.chdir()

Expand All @@ -190,6 +190,8 @@ def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata):
ext = ""
if sw == "afni":
factory = afni.AFNILinearTransform
elif sw == "afni-array":
factory = afni.AFNILinearTransformArray
elif sw == "fsl":
factory = fsl.FSLLinearTransform
elif sw == "itk":
Expand Down Expand Up @@ -222,6 +224,9 @@ def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata):

# Test from_ras
RAS = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
if sw == "afni-array":
RAS = np.array([RAS, RAS])

xfm = factory.from_ras(RAS, reference=reference, moving=moving)
assert np.allclose(xfm.to_ras(reference=reference, moving=moving), RAS)

Expand Down

0 comments on commit 6e70c02

Please sign in to comment.