From 95aef0db64291bd5dd54e2c2baaaad7f7214a28d Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 15 Feb 2022 18:23:00 +0100 Subject: [PATCH 1/4] enh: review comments Co-authored-by: Chris Markiewicz --- nitransforms/tests/test_linear.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nitransforms/tests/test_linear.py b/nitransforms/tests/test_linear.py index 400910a1..d4185ba7 100644 --- a/nitransforms/tests/test_linear.py +++ b/nitransforms/tests/test_linear.py @@ -72,7 +72,7 @@ def test_loadsave_itk(tmp_path, data_path, testdata_path): ) -@pytest.mark.xfail(reason="Not fully implemented") +# @pytest.mark.xfail(reason="Not fully implemented") @pytest.mark.parametrize("fmt", ["itk", "fsl", "afni", "lta"]) def test_loadsave(tmp_path, data_path, testdata_path, fmt): """Test idempotency.""" From 967c37386b633b99d984e52589306a65fd70b6e8 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 15 Feb 2022 18:14:15 +0100 Subject: [PATCH 2/4] fix(AFNI): load arrays of linear transforms - one less XFail (#40) --- nitransforms/io/afni.py | 34 ++++++++++++++++++++++++++++--- nitransforms/linear.py | 2 ++ nitransforms/tests/test_linear.py | 2 +- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/nitransforms/io/afni.py b/nitransforms/io/afni.py index 42a5ab8e..d41bf59f 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]) @@ -38,7 +39,11 @@ def from_ras(cls, ras, moving=None, reference=None): ras = ras.copy() pre = LPS.copy() post = LPS.copy() - if _is_oblique(reference.affine): + + 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 +51,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 +63,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 +92,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.copy() + post = LPS.copy() + + 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_linear.py b/nitransforms/tests/test_linear.py index d4185ba7..400910a1 100644 --- a/nitransforms/tests/test_linear.py +++ b/nitransforms/tests/test_linear.py @@ -72,7 +72,7 @@ def test_loadsave_itk(tmp_path, data_path, testdata_path): ) -# @pytest.mark.xfail(reason="Not fully implemented") +@pytest.mark.xfail(reason="Not fully implemented") @pytest.mark.parametrize("fmt", ["itk", "fsl", "afni", "lta"]) def test_loadsave(tmp_path, data_path, testdata_path, fmt): """Test idempotency.""" From 6696caa9da81a5b01bc1eb71d805d408e3228e08 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 16 Feb 2022 09:02:39 +0100 Subject: [PATCH 3/4] fix: remove NotImplementedError check --- nitransforms/cli.py | 9 +++++---- nitransforms/tests/test_cli.py | 7 +------ 2 files changed, 6 insertions(+), 10 deletions(-) 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/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() From 09166ef352fcd27b7de248657bddbd1f1f6a40fb Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 16 Feb 2022 09:05:38 +0100 Subject: [PATCH 4/4] fix: remove unnecessary ``.copy()`` of ndarrays Co-authored-by: Chris Markiewicz --- nitransforms/io/afni.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nitransforms/io/afni.py b/nitransforms/io/afni.py index d41bf59f..51100af3 100644 --- a/nitransforms/io/afni.py +++ b/nitransforms/io/afni.py @@ -35,10 +35,9 @@ 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() + """Create an AFNI affine from a nitransform's RAS+ matrix.""" + pre = LPS + post = LPS if reference is not None: reference = _ensure_image(reference) @@ -94,8 +93,8 @@ def from_string(cls, string): def to_ras(self, moving=None, reference=None): """Return a nitransforms internal RAS+ matrix.""" - pre = LPS.copy() - post = LPS.copy() + pre = LPS + post = LPS if reference is not None: reference = _ensure_image(reference)