Skip to content

Commit c71a127

Browse files
committed
mnt: move tests from test_nonlinear into test_io
1 parent 5647caf commit c71a127

File tree

2 files changed

+120
-90
lines changed

2 files changed

+120
-90
lines changed

nitransforms/tests/test_io.py

Lines changed: 120 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
33
"""I/O test cases."""
4+
45
import os
56
from subprocess import check_call
67
from io import StringIO
@@ -15,11 +16,13 @@
1516
from nibabel.affines import from_matvec
1617
from scipy.io import loadmat
1718
from nitransforms.linear import Affine
19+
from nitransforms.nonlinear import DenseFieldTransform, BSplineFieldTransform
1820
from nitransforms.io import (
1921
afni,
2022
fsl,
2123
lta as fs,
2224
itk,
25+
x5,
2326
)
2427
from nitransforms.io.lta import (
2528
VolumeGeometry as VG,
@@ -68,11 +71,13 @@ def test_volume_group_voxel_ordering():
6871
def test_VG_from_LTA(data_path):
6972
"""Check the affine interpolation from volume geometries."""
7073
# affine manually clipped after running mri_info on the image
71-
oracle = np.loadtxt(StringIO("""\
74+
oracle = np.loadtxt(
75+
StringIO("""\
7276
-3.0000 0.0000 -0.0000 91.3027
7377
-0.0000 2.0575 -2.9111 -25.5251
7478
0.0000 2.1833 2.7433 -105.0820
75-
0.0000 0.0000 0.0000 1.0000"""))
79+
0.0000 0.0000 0.0000 1.0000""")
80+
)
7681

7782
lta_text = "\n".join(
7883
(data_path / "bold-to-t1w.lta").read_text().splitlines()[13:21]
@@ -419,10 +424,17 @@ def test_afni_Displacements():
419424

420425

421426
@pytest.mark.parametrize("only_linear", [True, False])
422-
@pytest.mark.parametrize("h5_path,nxforms", [
423-
(_datadir / "affine-antsComposite.h5", 1),
424-
(_testdir / "ds-005_sub-01_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5", 2),
425-
])
427+
@pytest.mark.parametrize(
428+
"h5_path,nxforms",
429+
[
430+
(_datadir / "affine-antsComposite.h5", 1),
431+
(
432+
_testdir
433+
/ "ds-005_sub-01_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5",
434+
2,
435+
),
436+
],
437+
)
426438
def test_itk_h5(tmpdir, only_linear, h5_path, nxforms):
427439
"""Test displacements fields."""
428440
assert (
@@ -434,7 +446,9 @@ def test_itk_h5(tmpdir, only_linear, h5_path, nxforms):
434446
)
435447
)
436448
)
437-
== nxforms if not only_linear else 1
449+
== nxforms
450+
if not only_linear
451+
else 1
438452
)
439453

440454
with pytest.raises(TransformFileError):
@@ -465,24 +479,33 @@ def test_regressions(file_type, test_file, data_path):
465479
file_type.from_filename(data_path / "regressions" / test_file)
466480

467481

468-
@pytest.mark.parametrize("parameters", [
469-
{"x": 0.1, "y": 0.03, "z": 0.002},
470-
{"x": 0.001, "y": 0.3, "z": 0.002},
471-
{"x": 0.01, "y": 0.03, "z": 0.2},
472-
])
482+
@pytest.mark.parametrize(
483+
"parameters",
484+
[
485+
{"x": 0.1, "y": 0.03, "z": 0.002},
486+
{"x": 0.001, "y": 0.3, "z": 0.002},
487+
{"x": 0.01, "y": 0.03, "z": 0.2},
488+
],
489+
)
473490
@pytest.mark.parametrize("dir_x", (-1, 1))
474491
@pytest.mark.parametrize("dir_y", (-1, 1))
475492
@pytest.mark.parametrize("dir_z", (1, -1))
476-
@pytest.mark.parametrize("swapaxes", [
477-
None, (0, 1), (1, 2), (0, 2),
478-
])
493+
@pytest.mark.parametrize(
494+
"swapaxes",
495+
[
496+
None,
497+
(0, 1),
498+
(1, 2),
499+
(0, 2),
500+
],
501+
)
479502
def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y, dir_z):
480503
tmpdir.chdir()
481504
img, R = _generate_reoriented(
482505
testdata_path / "someones_anatomy.nii.gz",
483506
(dir_x, dir_y, dir_z),
484507
swapaxes,
485-
parameters
508+
parameters,
486509
)
487510
img.to_filename("orig.nii.gz")
488511

@@ -507,9 +530,8 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
507530
"orig.nii.gz",
508531
)
509532

510-
diff = (
511-
np.asanyarray(img.dataobj, dtype="uint8")
512-
- np.asanyarray(nt3drefit.dataobj, dtype="uint8")
533+
diff = np.asanyarray(img.dataobj, dtype="uint8") - np.asanyarray(
534+
nt3drefit.dataobj, dtype="uint8"
513535
)
514536
assert np.sqrt((diff[10:-10, 10:-10, 10:-10] ** 2).mean()) < 0.1
515537

@@ -522,14 +544,15 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
522544
"deob_3drefit.nii.gz",
523545
)
524546

525-
diff = (
526-
np.asanyarray(img.dataobj, dtype="uint8")
527-
- np.asanyarray(nt_undo3drefit.dataobj, dtype="uint8")
547+
diff = np.asanyarray(img.dataobj, dtype="uint8") - np.asanyarray(
548+
nt_undo3drefit.dataobj, dtype="uint8"
528549
)
529550
assert np.sqrt((diff[10:-10, 10:-10, 10:-10] ** 2).mean()) < 0.1
530551

531552
# Check the target grid by 3dWarp and the affine & size interpolated by NiTransforms
532-
cmd = f"3dWarp -verb -deoblique -NN -prefix {tmpdir}/deob.nii.gz {tmpdir}/orig.nii.gz"
553+
cmd = (
554+
f"3dWarp -verb -deoblique -NN -prefix {tmpdir}/deob.nii.gz {tmpdir}/orig.nii.gz"
555+
)
533556
assert check_call([cmd], shell=True) == 0
534557

535558
deobnii = nb.load("deob.nii.gz")
@@ -540,11 +563,12 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
540563

541564
# Check resampling in deobliqued grid
542565
ntdeobnii = apply(
543-
Affine(np.eye(4), reference=deobnii.__class__(
544-
np.zeros(deobshape, dtype="uint8"),
545-
deobaff,
546-
deobnii.header
547-
)),
566+
Affine(
567+
np.eye(4),
568+
reference=deobnii.__class__(
569+
np.zeros(deobshape, dtype="uint8"), deobaff, deobnii.header
570+
),
571+
),
548572
img,
549573
order=0,
550574
)
@@ -559,9 +583,8 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
559583
)
560584
mask = np.asanyarray(ntdeobmask.dataobj, dtype=bool)
561585

562-
diff = (
563-
np.asanyarray(deobnii.dataobj, dtype="uint8")
564-
- np.asanyarray(ntdeobnii.dataobj, dtype="uint8")
586+
diff = np.asanyarray(deobnii.dataobj, dtype="uint8") - np.asanyarray(
587+
ntdeobnii.dataobj, dtype="uint8"
565588
)
566589
assert np.sqrt((diff[mask] ** 2).mean()) < 0.1
567590

@@ -591,7 +614,7 @@ def _generate_reoriented(path, directions, swapaxes, parameters):
591614
aff = np.diag((*directions, 1)) @ aff
592615

593616
for ax in range(3):
594-
if (directions[ax] == -1):
617+
if directions[ax] == -1:
595618
aff[ax, 3] = last_xyz[ax]
596619
data = np.flip(data, ax)
597620

@@ -621,16 +644,15 @@ def test_itk_linear_h5(tmpdir, data_path, testdata_path):
621644
assert len(h5xfm.xforms) == 1
622645

623646
# File loadable with single affine object
624-
itk.ITKLinearTransform.from_filename(
625-
data_path / "affine-antsComposite.h5"
626-
)
647+
itk.ITKLinearTransform.from_filename(data_path / "affine-antsComposite.h5")
627648

628649
with open(data_path / "affine-antsComposite.h5", "rb") as f:
629650
itk.ITKLinearTransform.from_fileobj(f)
630651

631652
# Exercise only_linear
632653
itk.ITKCompositeH5.from_filename(
633-
testdata_path / "ds-005_sub-01_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5",
654+
testdata_path
655+
/ "ds-005_sub-01_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5",
634656
only_linear=True,
635657
)
636658

@@ -673,9 +695,54 @@ def test_itk_linear_h5(tmpdir, data_path, testdata_path):
673695
with pytest.raises(TransformIOError):
674696
itk.ITKLinearTransform.from_filename("test.h5")
675697

676-
# Added tests for h5 orientation bug
677698

699+
@pytest.mark.parametrize("is_deltas", [True, False])
700+
def test_densefield_x5_roundtrip(tmp_path, is_deltas):
701+
"""Ensure dense field transforms roundtrip via X5."""
702+
ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4))
703+
disp = nb.Nifti1Image(np.random.rand(2, 2, 2, 3).astype("float32"), np.eye(4))
704+
705+
xfm = DenseFieldTransform(disp, is_deltas=is_deltas, reference=ref)
706+
707+
node = xfm.to_x5(metadata={"GeneratedBy": "pytest"})
708+
assert node.type == "nonlinear"
709+
assert node.subtype == "densefield"
710+
assert node.representation == "displacements" if is_deltas else "deformations"
711+
assert node.domain.size == ref.shape
712+
assert node.metadata["GeneratedBy"] == "pytest"
713+
714+
fname = tmp_path / "test.x5"
715+
x5.to_filename(fname, [node])
716+
717+
xfm2 = DenseFieldTransform.from_filename(fname, fmt="X5")
718+
719+
assert xfm2.reference.shape == ref.shape
720+
assert np.allclose(xfm2.reference.affine, ref.affine)
721+
assert xfm == xfm2
722+
723+
724+
def test_bspline_to_x5(tmp_path):
725+
"""Check BSpline transforms export to X5."""
726+
coeff = nb.Nifti1Image(np.zeros((2, 2, 2, 3), dtype="float32"), np.eye(4))
727+
ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4))
678728

729+
xfm = BSplineFieldTransform(coeff, reference=ref)
730+
node = xfm.to_x5(metadata={"tool": "pytest"})
731+
assert node.type == "nonlinear"
732+
assert node.subtype == "bspline"
733+
assert node.representation == "coefficients"
734+
assert node.metadata["tool"] == "pytest"
735+
736+
fname = tmp_path / "bspline.x5"
737+
x5.to_filename(fname, [node])
738+
739+
xfm2 = BSplineFieldTransform.from_filename(fname, fmt="X5")
740+
assert np.allclose(xfm._coeffs, xfm2._coeffs)
741+
assert xfm2.reference.shape == ref.shape
742+
assert np.allclose(xfm2.reference.affine, ref.affine)
743+
744+
745+
# Added tests for h5 orientation bug
679746
@pytest.mark.xfail(
680747
reason="GH-137/GH-171: displacement field dimension order is wrong",
681748
strict=False,
@@ -687,11 +754,15 @@ def test_itk_h5_field_order(tmp_path):
687754
field = np.stack([vals, vals + 100, vals + 200], axis=0)
688755

689756
params = field.reshape(-1, order="C")
690-
fixed = np.array(list(shape) + [0, 0, 0] + [1, 1, 1] + list(np.eye(3).ravel()), dtype=float)
757+
fixed = np.array(
758+
list(shape) + [0, 0, 0] + [1, 1, 1] + list(np.eye(3).ravel()), dtype=float
759+
)
691760
fname = tmp_path / "field.h5"
692761
with H5File(fname, "w") as f:
693762
grp = f.create_group("TransformGroup")
694-
grp.create_group("0")["TransformType"] = np.array([b"CompositeTransform_double_3_3"])
763+
grp.create_group("0")["TransformType"] = np.array(
764+
[b"CompositeTransform_double_3_3"]
765+
)
695766
g1 = grp.create_group("1")
696767
g1["TransformType"] = np.array([b"DisplacementFieldTransform_float_3_3"])
697768
g1["TransformFixedParameters"] = fixed
@@ -709,8 +780,10 @@ def _load_composite_testdata(data_path):
709780
# Generated using
710781
# CompositeTransformUtil --disassemble ants_t1_to_mniComposite.h5 \
711782
# ants_t1_to_mniComposite
712-
warpfile = data_path / "regressions" / (
713-
"01_ants_t1_to_mniComposite_DisplacementFieldTransform.nii.gz"
783+
warpfile = (
784+
data_path
785+
/ "regressions"
786+
/ ("01_ants_t1_to_mniComposite_DisplacementFieldTransform.nii.gz")
714787
)
715788
if not (h5file.exists() and warpfile.exists()):
716789
pytest.skip("Composite transform test data not available")
@@ -764,11 +837,15 @@ def test_itk_h5_field_order_fortran(tmp_path):
764837
field = np.stack([vals, vals + 100, vals + 200], axis=0)
765838

766839
params = field.reshape(-1, order="F")
767-
fixed = np.array(list(shape) + [0, 0, 0] + [1, 1, 1] + list(np.eye(3).ravel()), dtype=float)
840+
fixed = np.array(
841+
list(shape) + [0, 0, 0] + [1, 1, 1] + list(np.eye(3).ravel()), dtype=float
842+
)
768843
fname = tmp_path / "field_f.h5"
769844
with H5File(fname, "w") as f:
770845
grp = f.create_group("TransformGroup")
771-
grp.create_group("0")["TransformType"] = np.array([b"CompositeTransform_double_3_3"])
846+
grp.create_group("0")["TransformType"] = np.array(
847+
[b"CompositeTransform_double_3_3"]
848+
)
772849
g1 = grp.create_group("1")
773850
g1["TransformType"] = np.array([b"DisplacementFieldTransform_float_3_3"])
774851
g1["TransformFixedParameters"] = fixed

nitransforms/tests/test_nonlinear.py

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
BSplineFieldTransform,
1515
DenseFieldTransform,
1616
)
17-
from nitransforms import io
1817
from ..io.itk import ITKDisplacementsField
1918

2019

@@ -122,52 +121,6 @@ def test_bspline(tmp_path, testdata_path):
122121
)
123122

124123

125-
@pytest.mark.parametrize("is_deltas", [True, False])
126-
def test_densefield_x5_roundtrip(tmp_path, is_deltas):
127-
"""Ensure dense field transforms roundtrip via X5."""
128-
ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4))
129-
disp = nb.Nifti1Image(np.random.rand(2, 2, 2, 3).astype("float32"), np.eye(4))
130-
131-
xfm = DenseFieldTransform(disp, is_deltas=is_deltas, reference=ref)
132-
133-
node = xfm.to_x5(metadata={"GeneratedBy": "pytest"})
134-
assert node.type == "nonlinear"
135-
assert node.subtype == "densefield"
136-
assert node.representation == "displacements" if is_deltas else "deformations"
137-
assert node.domain.size == ref.shape
138-
assert node.metadata["GeneratedBy"] == "pytest"
139-
140-
fname = tmp_path / "test.x5"
141-
io.x5.to_filename(fname, [node])
142-
143-
xfm2 = DenseFieldTransform.from_filename(fname, fmt="X5")
144-
145-
assert xfm2.reference.shape == ref.shape
146-
assert np.allclose(xfm2.reference.affine, ref.affine)
147-
assert xfm == xfm2
148-
149-
150-
def test_bspline_to_x5(tmp_path):
151-
"""Check BSpline transforms export to X5."""
152-
coeff = nb.Nifti1Image(np.zeros((2, 2, 2, 3), dtype="float32"), np.eye(4))
153-
ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4))
154-
155-
xfm = BSplineFieldTransform(coeff, reference=ref)
156-
node = xfm.to_x5(metadata={"tool": "pytest"})
157-
assert node.type == "nonlinear"
158-
assert node.subtype == "bspline"
159-
assert node.representation == "coefficients"
160-
assert node.metadata["tool"] == "pytest"
161-
162-
fname = tmp_path / "bspline.x5"
163-
io.x5.to_filename(fname, [node])
164-
165-
xfm2 = BSplineFieldTransform.from_filename(fname, fmt="X5")
166-
assert np.allclose(xfm._coeffs, xfm2._coeffs)
167-
assert xfm2.reference.shape == ref.shape
168-
assert np.allclose(xfm2.reference.affine, ref.affine)
169-
170-
171124
@pytest.mark.parametrize("is_deltas", [True, False])
172125
def test_densefield_oob_resampling(is_deltas):
173126
"""Ensure mapping outside the field returns input coordinates."""

0 commit comments

Comments
 (0)