|
7 | 7 |
|
8 | 8 | import numpy as np
|
9 | 9 | import nibabel as nb
|
10 |
| -from nitransforms.resampling import apply |
11 | 10 | from nitransforms.base import TransformError, ImageGrid
|
12 | 11 | from nitransforms.io.base import TransformFileError
|
13 | 12 | from nitransforms.nonlinear import (
|
|
17 | 16 | from ..io.itk import ITKDisplacementsField
|
18 | 17 |
|
19 | 18 |
|
20 |
| -SOME_TEST_POINTS = np.array([ |
21 |
| - [0.0, 0.0, 0.0], |
22 |
| - [1.0, 2.0, 3.0], |
23 |
| - [10.0, -10.0, 5.0], |
24 |
| - [-5.0, 7.0, -2.0], |
25 |
| - [12.0, 0.0, -11.0], |
26 |
| -]) |
| 19 | +SOME_TEST_POINTS = np.array( |
| 20 | + [ |
| 21 | + [0.0, 0.0, 0.0], |
| 22 | + [1.0, 2.0, 3.0], |
| 23 | + [10.0, -10.0, 5.0], |
| 24 | + [-5.0, 7.0, -2.0], |
| 25 | + [12.0, 0.0, -11.0], |
| 26 | + ] |
| 27 | +) |
| 28 | + |
27 | 29 |
|
28 | 30 | @pytest.mark.parametrize("size", [(20, 20, 20), (20, 20, 20, 3)])
|
29 | 31 | def test_itk_disp_load(size):
|
@@ -88,109 +90,114 @@ def test_bsplines_references(testdata_path):
|
88 | 90 | testdata_path / "someones_bspline_coefficients.nii.gz"
|
89 | 91 | ).to_field()
|
90 | 92 |
|
91 |
| - with pytest.raises(TransformError): |
92 |
| - apply( |
93 |
| - BSplineFieldTransform( |
94 |
| - testdata_path / "someones_bspline_coefficients.nii.gz" |
95 |
| - ), |
96 |
| - testdata_path / "someones_anatomy.nii.gz", |
97 |
| - ) |
98 |
| - |
99 |
| - apply( |
100 |
| - BSplineFieldTransform(testdata_path / "someones_bspline_coefficients.nii.gz"), |
101 |
| - testdata_path / "someones_anatomy.nii.gz", |
| 93 | + BSplineFieldTransform( |
| 94 | + testdata_path / "someones_bspline_coefficients.nii.gz", |
102 | 95 | reference=testdata_path / "someones_anatomy.nii.gz",
|
103 | 96 | )
|
104 | 97 |
|
105 | 98 |
|
106 |
| -@pytest.mark.xfail( |
107 |
| - reason="GH-267: disabled while debugging", |
108 |
| - strict=False, |
109 |
| -) |
110 |
| -def test_bspline(tmp_path, testdata_path): |
| 99 | +def test_map_bspline_vs_displacement(tmp_path, testdata_path): |
111 | 100 | """Cross-check B-Splines and deformation field."""
|
112 | 101 | os.chdir(str(tmp_path))
|
113 | 102 |
|
114 | 103 | img_name = testdata_path / "someones_anatomy.nii.gz"
|
115 | 104 | disp_name = testdata_path / "someones_displacement_field.nii.gz"
|
116 | 105 | bs_name = testdata_path / "someones_bspline_coefficients.nii.gz"
|
117 | 106 |
|
118 |
| - bsplxfm = BSplineFieldTransform(bs_name, reference=img_name) |
| 107 | + bsplxfm = BSplineFieldTransform(bs_name, reference=img_name).to_field() |
119 | 108 | dispxfm = DenseFieldTransform(disp_name)
|
| 109 | + # Interpolating field should be reasonably similar |
| 110 | + np.testing.assert_allclose(dispxfm._field, bsplxfm._field, atol=1e-1, rtol=1e-4) |
120 | 111 |
|
121 |
| - out_disp = apply(dispxfm, img_name) |
122 |
| - out_bspl = apply(bsplxfm, img_name) |
123 |
| - |
124 |
| - out_disp.to_filename("resampled_field.nii.gz") |
125 |
| - out_bspl.to_filename("resampled_bsplines.nii.gz") |
126 |
| - |
127 |
| - assert ( |
128 |
| - np.sqrt( |
129 |
| - (out_disp.get_fdata(dtype="float32") - out_bspl.get_fdata(dtype="float32")) |
130 |
| - ** 2 |
131 |
| - ).mean() |
132 |
| - < 0.2 |
133 |
| - ) |
134 | 112 |
|
135 | 113 | @pytest.mark.parametrize("image_orientation", ["RAS", "LAS", "LPS", "oblique"])
|
136 | 114 | @pytest.mark.parametrize("ongrid", [True, False])
|
137 |
| -def test_densefield_map(tmp_path, get_testdata, image_orientation, ongrid): |
| 115 | +def test_densefield_map(get_testdata, image_orientation, ongrid): |
138 | 116 | """Create a constant displacement field and compare mappings."""
|
139 | 117 |
|
140 | 118 | nii = get_testdata[image_orientation]
|
141 | 119 |
|
| 120 | + # Get sampling indices |
| 121 | + rng = np.random.default_rng() |
| 122 | + |
142 | 123 | # Create a reference centered at the origin with various axis orders/flips
|
143 | 124 | shape = nii.shape
|
144 | 125 | ref_affine = nii.affine.copy()
|
145 | 126 | reference = ImageGrid(nb.Nifti1Image(np.zeros(shape), ref_affine, None))
|
146 |
| - indices = reference.ndindex |
147 |
| - |
148 |
| - gridpoints = reference.ras(indices) |
149 |
| - points = gridpoints if ongrid else SOME_TEST_POINTS |
150 |
| - |
151 |
| - coordinates = gridpoints.reshape(*shape, 3) |
152 |
| - deltas = np.stack(( |
153 |
| - np.zeros(np.prod(shape), dtype="float32").reshape(shape), |
154 |
| - np.linspace(-80, 80, num=np.prod(shape), dtype="float32").reshape(shape), |
155 |
| - np.linspace(-50, 50, num=np.prod(shape), dtype="float32").reshape(shape), |
156 |
| - ), axis=-1) |
157 |
| - |
158 |
| - atol = 1e-4 if image_orientation == "oblique" else 1e-7 |
| 127 | + grid_ijk = reference.ndindex |
| 128 | + grid_xyz = reference.ras(grid_ijk) |
| 129 | + |
| 130 | + subsample = rng.choice(grid_ijk.shape[0], 5000) |
| 131 | + points_ijk = grid_ijk.copy() if ongrid else grid_ijk[subsample] |
| 132 | + coords_xyz = ( |
| 133 | + grid_xyz |
| 134 | + if ongrid |
| 135 | + else reference.ras(points_ijk) + rng.normal(size=points_ijk.shape) |
| 136 | + ) |
159 | 137 |
|
160 |
| - # Build an identity transform (deltas) |
161 |
| - id_xfm_deltas = DenseFieldTransform(reference=reference) |
162 |
| - np.testing.assert_array_equal(coordinates, id_xfm_deltas._field) |
163 |
| - np.testing.assert_allclose(points, id_xfm_deltas.map(points), atol=atol) |
| 138 | + coords_map = grid_xyz.reshape(*shape, 3) |
| 139 | + deltas = np.stack( |
| 140 | + ( |
| 141 | + np.zeros(np.prod(shape), dtype="float32").reshape(shape), |
| 142 | + np.linspace(-80, 80, num=np.prod(shape), dtype="float32").reshape(shape), |
| 143 | + np.linspace(-50, 50, num=np.prod(shape), dtype="float32").reshape(shape), |
| 144 | + ), |
| 145 | + axis=-1, |
| 146 | + ) |
164 | 147 |
|
165 |
| - # Build an identity transform (deformation) |
166 |
| - id_xfm_field = DenseFieldTransform(coordinates, is_deltas=False, reference=reference) |
167 |
| - np.testing.assert_array_equal(coordinates, id_xfm_field._field) |
168 |
| - np.testing.assert_allclose(points, id_xfm_field.map(points), atol=atol) |
| 148 | + if ongrid: |
| 149 | + atol = 1e-3 if image_orientation == "oblique" or not ongrid else 1e-7 |
| 150 | + # Build an identity transform (deltas) |
| 151 | + id_xfm_deltas = DenseFieldTransform(reference=reference) |
| 152 | + np.testing.assert_array_equal(coords_map, id_xfm_deltas._field) |
| 153 | + np.testing.assert_allclose(coords_xyz, id_xfm_deltas.map(coords_xyz)) |
| 154 | + |
| 155 | + # Build an identity transform (deformation) |
| 156 | + id_xfm_field = DenseFieldTransform( |
| 157 | + coords_map, is_deltas=False, reference=reference |
| 158 | + ) |
| 159 | + np.testing.assert_array_equal(coords_map, id_xfm_field._field) |
| 160 | + np.testing.assert_allclose(coords_xyz, id_xfm_field.map(coords_xyz), atol=atol) |
169 | 161 |
|
170 |
| - # Collapse to zero transform (deltas) |
171 |
| - zero_xfm_deltas = DenseFieldTransform(-coordinates, reference=reference) |
172 |
| - np.testing.assert_array_equal(np.zeros_like(zero_xfm_deltas._field), zero_xfm_deltas._field) |
173 |
| - np.testing.assert_allclose(np.zeros_like(points), zero_xfm_deltas.map(points), atol=atol) |
| 162 | + # Collapse to zero transform (deltas) |
| 163 | + zero_xfm_deltas = DenseFieldTransform(-coords_map, reference=reference) |
| 164 | + np.testing.assert_array_equal( |
| 165 | + np.zeros_like(zero_xfm_deltas._field), zero_xfm_deltas._field |
| 166 | + ) |
| 167 | + np.testing.assert_allclose( |
| 168 | + np.zeros_like(coords_xyz), zero_xfm_deltas.map(coords_xyz), atol=atol |
| 169 | + ) |
174 | 170 |
|
175 |
| - # Collapse to zero transform (deformation) |
176 |
| - zero_xfm_field = DenseFieldTransform(np.zeros_like(deltas), is_deltas=False, reference=reference) |
177 |
| - np.testing.assert_array_equal(np.zeros_like(zero_xfm_field._field), zero_xfm_field._field) |
178 |
| - np.testing.assert_allclose(np.zeros_like(points), zero_xfm_field.map(points), atol=atol) |
| 171 | + # Collapse to zero transform (deformation) |
| 172 | + zero_xfm_field = DenseFieldTransform( |
| 173 | + np.zeros_like(deltas), is_deltas=False, reference=reference |
| 174 | + ) |
| 175 | + np.testing.assert_array_equal( |
| 176 | + np.zeros_like(zero_xfm_field._field), zero_xfm_field._field |
| 177 | + ) |
| 178 | + np.testing.assert_allclose( |
| 179 | + np.zeros_like(coords_xyz), zero_xfm_field.map(coords_xyz), atol=atol |
| 180 | + ) |
179 | 181 |
|
180 | 182 | # Now let's apply a transform
|
181 | 183 | xfm = DenseFieldTransform(deltas, reference=reference)
|
182 | 184 | np.testing.assert_array_equal(deltas, xfm._deltas)
|
183 |
| - np.testing.assert_array_equal(coordinates + deltas, xfm._field) |
| 185 | + np.testing.assert_array_equal(coords_map + deltas, xfm._field) |
184 | 186 |
|
185 |
| - mapped = xfm.map(points) |
186 |
| - nit_deltas = mapped - points |
| 187 | + mapped = xfm.map(coords_xyz) |
| 188 | + nit_deltas = mapped - coords_xyz |
187 | 189 |
|
188 | 190 | if ongrid:
|
189 | 191 | mapped_image = mapped.reshape(*shape, 3)
|
190 |
| - np.testing.assert_allclose(deltas + coordinates, mapped_image) |
| 192 | + np.testing.assert_allclose(deltas + coords_map, mapped_image) |
191 | 193 | np.testing.assert_allclose(deltas, nit_deltas.reshape(*shape, 3), atol=1e-4)
|
192 | 194 | np.testing.assert_allclose(xfm._field, mapped_image)
|
193 |
| - |
| 195 | + else: |
| 196 | + ongrid_xyz = xfm.map(grid_xyz[subsample]) |
| 197 | + assert ( |
| 198 | + (np.linalg.norm(ongrid_xyz - mapped, axis=1) > 2).sum() |
| 199 | + / ongrid_xyz.shape[0] |
| 200 | + ) < 0.5 |
194 | 201 |
|
195 | 202 |
|
196 | 203 | @pytest.mark.parametrize("is_deltas", [True, False])
|
|
0 commit comments