Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Load arrays of linear transforms from AFNI files #143

Merged
merged 4 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions nitransforms/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ def cli_apply(pargs):
"Cannot determine transformation format, manually set format with the `--fmt` flag"
)

if pargs.nonlinear:
xfm = nlinload(pargs.transform, fmt=fmt)
else:
xfm = linload(pargs.transform, fmt=fmt)
xfm = (
nlinload(pargs.transform, fmt=fmt)
if pargs.nonlinear else
linload(pargs.transform, fmt=fmt)
)

# ensure a reference is set
xfm.reference = pargs.ref or pargs.moving
Expand Down
41 changes: 34 additions & 7 deletions nitransforms/io/afni.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DisplacementsField,
LinearParameters,
TransformFileError,
_ensure_image,
)

LPS = np.diag([-1, -1, 1, 1])
Expand All @@ -34,19 +35,25 @@ def to_string(self, banner=True):

@classmethod
def from_ras(cls, ras, moving=None, reference=None):
"""Create an ITK affine from a nitransform's RAS+ matrix."""
ras = ras.copy()
pre = LPS.copy()
post = LPS.copy()
if _is_oblique(reference.affine):
"""Create an AFNI affine from a nitransform's RAS+ matrix."""
pre = LPS
post = LPS

if reference is not None:
reference = _ensure_image(reference)

if reference is not None and _is_oblique(reference.affine):
print("Reference affine axes are oblique.")
M = reference.affine
A = shape_zoom_affine(
reference.shape, voxel_sizes(M), x_flip=False, y_flip=False
)
pre = M.dot(np.linalg.inv(A)).dot(LPS)

if _is_oblique(moving.affine):
if moving is not None:
moving = _ensure_image(moving)

if moving is not None and _is_oblique(moving.affine):
print("Moving affine axes are oblique.")
M2 = moving.affine
A2 = shape_zoom_affine(
Expand All @@ -55,7 +62,7 @@ def from_ras(cls, ras, moving=None, reference=None):
post = A2.dot(np.linalg.inv(M2))

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

tf = cls()
tf.structarr["parameters"] = parameters.T
Expand Down Expand Up @@ -84,6 +91,26 @@ def from_string(cls, string):
sa["parameters"] = parameters
return tf

def to_ras(self, moving=None, reference=None):
"""Return a nitransforms internal RAS+ matrix."""
pre = LPS
post = LPS

if reference is not None:
reference = _ensure_image(reference)

if reference is not None and _is_oblique(reference.affine):
raise NotImplementedError

if moving is not None:
moving = _ensure_image(moving)

if moving is not None and _is_oblique(moving.affine):
raise NotImplementedError

# swapaxes is necessary, as axis 0 encodes series of transforms
return post @ np.swapaxes(self.structarr["parameters"].T, 0, 1) @ pre


class AFNILinearTransformArray(BaseLinearTransformList):
"""A string-based structure for series of AFNI linear transforms."""
Expand Down
2 changes: 2 additions & 0 deletions nitransforms/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ def from_filename(cls, filename, fmt="X5", reference=None, moving=None):
_factory = io.LinearTransformArray
elif fmt.lower() == "fsl":
_factory = io.fsl.FSLLinearTransformArray
elif fmt.lower() == "afni":
_factory = io.afni.AFNILinearTransformArray
else:
raise NotImplementedError

Expand Down
7 changes: 1 addition & 6 deletions nitransforms/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ def test_apply_nl(tmpdir, testdata_path):
with pytest.raises(ValueError):
ntcli(nlargs)

nlargs.extend(["--fmt", "afni"])
# no linear afni support
with pytest.raises(NotImplementedError):
ntcli(nlargs)

output = "moved_from_warp.nii.gz"
nlargs.extend(["--nonlinear", "--out", output])
nlargs.extend(["--nonlinear", "--fmt", "afni", "--out", output])
ntcli(nlargs)
assert (tmpdir / output).check()