diff --git a/nitransforms/cli.py b/nitransforms/cli.py index 63cd421c..59c6b9d3 100644 --- a/nitransforms/cli.py +++ b/nitransforms/cli.py @@ -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 diff --git a/nitransforms/io/afni.py b/nitransforms/io/afni.py index 42a5ab8e..51100af3 100644 --- a/nitransforms/io/afni.py +++ b/nitransforms/io/afni.py @@ -9,6 +9,7 @@ DisplacementsField, LinearParameters, TransformFileError, + _ensure_image, ) LPS = np.diag([-1, -1, 1, 1]) @@ -34,11 +35,14 @@ 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( @@ -46,7 +50,10 @@ def from_ras(cls, ras, moving=None, reference=None): ) 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( @@ -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 @@ -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.""" diff --git a/nitransforms/linear.py b/nitransforms/linear.py index a03f2695..9a8a6002 100644 --- a/nitransforms/linear.py +++ b/nitransforms/linear.py @@ -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 diff --git a/nitransforms/tests/test_cli.py b/nitransforms/tests/test_cli.py index 5772e9b1..7f16a1de 100644 --- a/nitransforms/tests/test_cli.py +++ b/nitransforms/tests/test_cli.py @@ -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()