|
17 | 17 | from nibabel.affines import from_matvec
|
18 | 18 | from scipy.io import loadmat
|
19 | 19 | from nitransforms.linear import Affine
|
| 20 | +from nitransforms import nonlinear as nitnl |
20 | 21 | from nitransforms.io import (
|
21 | 22 | afni,
|
22 | 23 | fsl,
|
23 | 24 | lta as fs,
|
24 | 25 | itk,
|
| 26 | + x5 |
25 | 27 | )
|
26 | 28 | from nitransforms.io.lta import (
|
27 | 29 | VolumeGeometry as VG,
|
@@ -695,6 +697,17 @@ def test_itk_linear_h5(tmpdir, data_path, testdata_path):
|
695 | 697 | itk.ITKLinearTransform.from_filename("test.h5")
|
696 | 698 |
|
697 | 699 |
|
| 700 | + |
| 701 | +def test_itk_disp_load_intent(): |
| 702 | + """Checks whether the NIfTI intent is fixed.""" |
| 703 | + with pytest.warns(UserWarning): |
| 704 | + field = itk.ITKDisplacementsField.from_image( |
| 705 | + nb.Nifti1Image(np.zeros((20, 20, 20, 1, 3)), np.eye(4), None) |
| 706 | + ) |
| 707 | + |
| 708 | + assert field.header.get_intent()[0] == "vector" |
| 709 | + |
| 710 | + |
698 | 711 | # Added tests for displacements fields orientations (ANTs/ITK)
|
699 | 712 | @pytest.mark.parametrize("image_orientation", ["RAS", "LAS", "LPS", "oblique"])
|
700 | 713 | def test_itk_displacements(tmp_path, get_testdata, image_orientation):
|
@@ -736,12 +749,63 @@ def test_itk_displacements(tmp_path, get_testdata, image_orientation):
|
736 | 749 | np.testing.assert_allclose(itk_nii.dataobj, nit_nii.dataobj)
|
737 | 750 | np.testing.assert_allclose(itk_nii.affine, nit_nii.affine)
|
738 | 751 |
|
| 752 | + # Check ITK-generated field has LPS-rotated affine |
| 753 | + np.testing.assert_allclose(itk_nii.affine, LPS @ ref_affine) |
| 754 | + # Test ITK-generated dataobject vs. original field |
| 755 | + np.testing.assert_allclose(itk_nii.dataobj, field.transpose(2, 1, 0, 3)[..., None, :]) |
| 756 | + |
739 | 757 | # Test round trip
|
740 | 758 | assert itk_nit_nii.shape == field.shape
|
| 759 | + np.testing.assert_allclose(itk_nit_nii.dataobj, field) |
| 760 | + np.testing.assert_allclose(itk_nit_nii.dataobj.transpose(2, 1, 0, 3)[..., None, :], nit_nii.dataobj) |
741 | 761 | np.testing.assert_allclose(itk_nit_nii.affine, ref_affine)
|
| 762 | + np.testing.assert_allclose(LPS @ itk_nit_nii.affine, nit_nii.affine) |
742 | 763 |
|
743 |
| - field[..., (0, 1)] *= -1.0 # Undo LPS flip |
744 |
| - np.testing.assert_allclose(itk_nit_nii.dataobj, field) |
| 764 | + |
| 765 | +@pytest.mark.parametrize("is_deltas", [True, False]) |
| 766 | +def test_densefield_x5_roundtrip(tmp_path, is_deltas): |
| 767 | + """Ensure dense field transforms roundtrip via X5.""" |
| 768 | + ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4)) |
| 769 | + disp = nb.Nifti1Image(np.random.rand(2, 2, 2, 3).astype("float32"), np.eye(4)) |
| 770 | + |
| 771 | + xfm = nitnl.DenseFieldTransform(disp, is_deltas=is_deltas, reference=ref) |
| 772 | + |
| 773 | + node = xfm.to_x5(metadata={"GeneratedBy": "pytest"}) |
| 774 | + assert node.type == "nonlinear" |
| 775 | + assert node.subtype == "densefield" |
| 776 | + assert node.representation == "displacements" if is_deltas else "deformations" |
| 777 | + assert node.domain.size == ref.shape |
| 778 | + assert node.metadata["GeneratedBy"] == "pytest" |
| 779 | + |
| 780 | + fname = tmp_path / "test.x5" |
| 781 | + x5.to_filename(fname, [node]) |
| 782 | + |
| 783 | + xfm2 = nitnl.DenseFieldTransform.from_filename(fname, fmt="X5") |
| 784 | + |
| 785 | + assert xfm2.reference.shape == ref.shape |
| 786 | + assert np.allclose(xfm2.reference.affine, ref.affine) |
| 787 | + assert xfm == xfm2 |
| 788 | + |
| 789 | + |
| 790 | +def test_bspline_to_x5(tmp_path): |
| 791 | + """Check BSpline transforms export to X5.""" |
| 792 | + coeff = nb.Nifti1Image(np.zeros((2, 2, 2, 3), dtype="float32"), np.eye(4)) |
| 793 | + ref = nb.Nifti1Image(np.zeros((2, 2, 2), dtype="uint8"), np.eye(4)) |
| 794 | + |
| 795 | + xfm = nitnl.BSplineFieldTransform(coeff, reference=ref) |
| 796 | + node = xfm.to_x5(metadata={"tool": "pytest"}) |
| 797 | + assert node.type == "nonlinear" |
| 798 | + assert node.subtype == "bspline" |
| 799 | + assert node.representation == "coefficients" |
| 800 | + assert node.metadata["tool"] == "pytest" |
| 801 | + |
| 802 | + fname = tmp_path / "bspline.x5" |
| 803 | + x5.to_filename(fname, [node]) |
| 804 | + |
| 805 | + xfm2 = nitnl.BSplineFieldTransform.from_filename(fname, fmt="X5") |
| 806 | + assert np.allclose(xfm._coeffs, xfm2._coeffs) |
| 807 | + assert xfm2.reference.shape == ref.shape |
| 808 | + assert np.allclose(xfm2.reference.affine, ref.affine) |
745 | 809 |
|
746 | 810 |
|
747 | 811 | # Added tests for h5 orientation bug
|
|
0 commit comments