Skip to content

Commit 72393e4

Browse files
committed
enh: add test with constant field
1 parent 9f5b7eb commit 72393e4

File tree

1 file changed

+99
-2
lines changed

1 file changed

+99
-2
lines changed

nitransforms/tests/test_io_itk.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import nibabel as nb
1313
from nibabel.eulerangles import euler2mat
1414
from nibabel.affines import from_matvec
15+
from scipy import ndimage as ndi
1516
from scipy.io import loadmat
1617
from h5py import File as H5File
1718

@@ -27,8 +28,7 @@
2728
from nitransforms.conftest import _datadir, _testdir
2829
from nitransforms.tests.utils import get_points
2930

30-
rng = np.random.default_rng()
31-
31+
RNG_SEED = 202508140819
3232
LPS = np.diag([-1, -1, 1, 1])
3333
ITK_MAT = LPS.dot(np.ones((4, 4)).dot(LPS))
3434

@@ -451,3 +451,100 @@ def test_densefield_map_vs_ants(testdata_path, tmp_path, ongrid):
451451
atol = 0 if ongrid else 1e-1
452452
rtol = 1e-4 if ongrid else 1e-3
453453
np.testing.assert_allclose(mapped, ants_pts, atol=atol, rtol=rtol)
454+
455+
456+
@pytest.mark.parametrize("image_orientation", ["RAS", "LAS", "LPS", "oblique"])
457+
@pytest.mark.parametrize("ongrid", [True, False])
458+
def test_constant_field_vs_ants(tmp_path, get_testdata, image_orientation, ongrid):
459+
"""Create a constant displacement field and compare mappings."""
460+
461+
rng = np.random.default_rng(RNG_SEED)
462+
463+
nii = get_testdata[image_orientation]
464+
465+
# Get sampling indices
466+
coords_xyz, points_ijk, grid_xyz, shape, ref_affine, reference, subsample = (
467+
get_points(nii, ongrid, npoints=5, rng=rng)
468+
)
469+
470+
tol = (
471+
{"atol": 0, "rtol": 1e-4}
472+
if image_orientation != "oblique"
473+
else {"atol": 1e-4, "rtol": 1e-2}
474+
)
475+
coords_map = grid_xyz.reshape(*shape, 3)
476+
477+
deltas = np.hstack(
478+
(
479+
np.zeros(np.prod(shape)),
480+
np.linspace(-80, 80, num=np.prod(shape)),
481+
np.linspace(-50, 50, num=np.prod(shape)),
482+
)
483+
).reshape(shape + (3,))
484+
gold_mapped_xyz = coords_map + deltas
485+
486+
fieldnii = nb.Nifti1Image(deltas, ref_affine, None)
487+
warpfile = tmp_path / "itk_transform.nii.gz"
488+
489+
# Ensure direct (xfm) and ITK roundtrip (itk_xfm) are equivalent
490+
xfm = nitnl.DenseFieldTransform(fieldnii)
491+
xfm.to_filename(warpfile, fmt="itk")
492+
itk_xfm = nitnl.DenseFieldTransform(ITKDisplacementsField.from_filename(warpfile))
493+
494+
np.testing.assert_allclose(xfm.reference.affine, itk_xfm.reference.affine)
495+
np.testing.assert_allclose(ref_affine, itk_xfm.reference.affine)
496+
np.testing.assert_allclose(xfm.reference.shape, itk_xfm.reference.shape)
497+
np.testing.assert_allclose(xfm._field, itk_xfm._field, **tol)
498+
if image_orientation != "oblique":
499+
assert xfm == itk_xfm
500+
501+
# Ensure deltas and mapped grid are equivalent
502+
orig_grid_mapped_xyz = xfm.map(grid_xyz).reshape(*shape, -1)
503+
np.testing.assert_allclose(gold_mapped_xyz, orig_grid_mapped_xyz)
504+
505+
# Test ANTs mapping
506+
grid_mapped_xyz = itk_xfm.map(grid_xyz).reshape(*shape, -1)
507+
508+
# Check apparent healthiness of mapping
509+
np.testing.assert_allclose(gold_mapped_xyz, grid_mapped_xyz, **tol)
510+
np.testing.assert_allclose(orig_grid_mapped_xyz, grid_mapped_xyz, **tol)
511+
512+
csvout = tmp_path / "mapped_xyz.csv"
513+
csvin = tmp_path / "coords_xyz.csv"
514+
# antsApplyTransformsToPoints wants LPS coordinates, see last post at
515+
# http://sourceforge.net/p/advants/discussion/840261/thread/2a1e9307/
516+
lps_xyz = coords_xyz.copy() * (-1, -1, 1)
517+
np.savetxt(csvin, lps_xyz, delimiter=",", header="x,y,z", comments="")
518+
519+
cmd = f"antsApplyTransformsToPoints -d 3 -i {csvin} -o {csvout} -t {warpfile}"
520+
exe = cmd.split()[0]
521+
if not shutil.which(exe):
522+
pytest.skip(f"Command {exe} not found on host")
523+
check_call(cmd, shell=True)
524+
525+
ants_res = np.genfromtxt(csvout, delimiter=",", names=True)
526+
# antsApplyTransformsToPoints writes LPS coordinates, see last post at
527+
# http://sourceforge.net/p/advants/discussion/840261/thread/2a1e9307/
528+
ants_pts = np.vstack([ants_res[n] for n in ("x", "y", "z")]).T * (-1, -1, 1)
529+
530+
nb.Nifti1Image(grid_mapped_xyz, ref_affine, None).to_filename(
531+
tmp_path / "grid_mapped.nii.gz"
532+
)
533+
nb.Nifti1Image(coords_map, ref_affine, None).to_filename(
534+
tmp_path / "baseline_field.nii.gz"
535+
)
536+
nb.Nifti1Image(gold_mapped_xyz, ref_affine, None).to_filename(
537+
tmp_path / "gold_mapped_xyz.nii.gz"
538+
)
539+
540+
if ongrid:
541+
ants_pts = ants_pts.reshape(*shape, 3)
542+
543+
nb.Nifti1Image(ants_pts, ref_affine, None).to_filename(
544+
tmp_path / "ants_mapped_xyz.nii.gz"
545+
)
546+
np.testing.assert_allclose(gold_mapped_xyz, ants_pts, rtol=1e-2, atol=1e-3)
547+
np.testing.assert_allclose(deltas, ants_pts - coords_map, rtol=1e-2, atol=1e-3)
548+
else:
549+
# TODO Change test to norms and investigate extreme cases (probably we're hitting OBB points)
550+
np.testing.assert_allclose(xfm.map(coords_xyz) - coords_xyz, ants_pts - coords_xyz, rtol=1, atol=1)

0 commit comments

Comments
 (0)