Coverage for nilearn/image/tests/test_resampling.py: 0%
484 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Test the resampling code."""
3import copy
4import math
5import os
6import sys
7from pathlib import Path
9import numpy as np
10import pytest
11from nibabel import Nifti1Header, Nifti1Image
12from nibabel.freesurfer import MGHImage
13from numpy.testing import (
14 assert_allclose,
15 assert_almost_equal,
16 assert_array_almost_equal,
17 assert_array_equal,
18 assert_equal,
19)
21from nilearn import _utils
22from nilearn._utils import testing
23from nilearn._utils.exceptions import DimensionError
24from nilearn.image import get_data
25from nilearn.image.image import crop_img
26from nilearn.image.resampling import (
27 BoundingBoxError,
28 coord_transform,
29 from_matrix_vector,
30 get_bounds,
31 get_mask_bounds,
32 reorder_img,
33 resample_img,
34 resample_to_img,
35)
36from nilearn.image.tests._testing import match_headers_keys, pad_array
38ANGLES_TO_TEST = (0, np.pi, np.pi / 2.0, np.pi / 4.0, np.pi / 3.0)
40SHAPE = (3, 2, 5, 2)
43def rotation(theta, phi):
44 """Return a rotation 3x3 matrix."""
45 cos = np.cos
46 sin = np.sin
47 a1 = np.array(
48 [[cos(theta), -sin(theta), 0], [sin(theta), cos(theta), 0], [0, 0, 1]]
49 )
50 a2 = np.array(
51 [[1, 0, 0], [0, cos(phi), -sin(phi)], [0, sin(phi), cos(phi)]]
52 )
53 return np.dot(a1, a2)
56@pytest.fixture
57def shape():
58 return SHAPE
61@pytest.fixture
62def data(rng, shape):
63 return rng.random(shape)
66def test_resample_deprecation_force_resample(data, shape, affine_eye):
67 """Test change of value of force_resample."""
68 affine_eye[:3, -1] = 0.5 * np.array(shape[:3])
70 with pytest.warns(FutureWarning, match="force_resample"):
71 resample_img(
72 Nifti1Image(data, affine_eye),
73 target_affine=affine_eye,
74 interpolation="nearest",
75 force_resample=None,
76 )
79@pytest.mark.parametrize("force_resample", [False, True])
80def test_identity_resample(data, force_resample, shape, affine_eye):
81 """Test resampling with an identity affine."""
82 affine_eye[:3, -1] = 0.5 * np.array(shape[:3])
84 rot_img = resample_img(
85 Nifti1Image(data, affine_eye),
86 target_affine=affine_eye,
87 interpolation="nearest",
88 force_resample=force_resample,
89 copy_header=True,
90 )
92 assert_almost_equal(data, get_data(rot_img))
94 # Test with a 3x3 affine
95 rot_img = resample_img(
96 Nifti1Image(data, affine_eye),
97 target_affine=affine_eye[:3, :3],
98 interpolation="nearest",
99 force_resample=force_resample,
100 copy_header=True,
101 )
103 assert_almost_equal(data, get_data(rot_img))
105 # Smoke-test with a list affine
106 rot_img = resample_img(
107 Nifti1Image(data, affine_eye),
108 target_affine=affine_eye.tolist(),
109 interpolation="nearest",
110 force_resample=force_resample,
111 copy_header=True,
112 )
115@pytest.mark.parametrize("force_resample", [False, True])
116@pytest.mark.parametrize("endian_type", [">f8", "<f8"])
117@pytest.mark.parametrize("interpolation", ["nearest", "linear", "continuous"])
118def test_identity_resample_non_native_endians(
119 data, force_resample, shape, affine_eye, endian_type, interpolation
120):
121 """Test resampling with an identity affine with non native endians.
123 with big endian data ('>f8')
124 with little endian data ('<f8')
125 """
126 affine_eye[:3, -1] = 0.5 * np.array(shape[:3])
128 rot_img = resample_img(
129 Nifti1Image(data.astype(endian_type), affine_eye),
130 target_affine=affine_eye.tolist(),
131 interpolation=interpolation,
132 force_resample=force_resample,
133 copy_header=True,
134 )
136 assert_almost_equal(data, get_data(rot_img))
139@pytest.mark.parametrize("force_resample", [False, True])
140def test_downsample(data, force_resample, affine_eye):
141 """Test resampling with a 1/2 down-sampling affine."""
142 rot_img = resample_img(
143 Nifti1Image(data, affine_eye),
144 target_affine=2 * affine_eye,
145 interpolation="nearest",
146 force_resample=force_resample,
147 copy_header=True,
148 )
150 downsampled = data[::2, ::2, ::2, ...]
151 x, y, z = downsampled.shape[:3]
153 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...])
155 rot_img_2 = resample_img(
156 Nifti1Image(data, affine_eye),
157 target_affine=2 * affine_eye,
158 interpolation="nearest",
159 force_resample=force_resample,
160 copy_header=True,
161 )
163 assert_almost_equal(get_data(rot_img_2), get_data(rot_img))
166@pytest.mark.parametrize("endian_type", [">f8", "<f8"])
167@pytest.mark.parametrize("copy_data", [True, False])
168@pytest.mark.parametrize("force_resample", [True, False])
169def test_downsample_non_native_endian_data(
170 data, affine_eye, endian_type, copy_data, force_resample
171):
172 """Test resampling with a 1/2 down-sampling affine with non native endians.
174 Test to check that if giving non native endian data as input should
175 work as normal and expected to return the same output as above tests.
177 Big endian data ">f8"
178 Little endian data "<f8"
179 """
180 rot_img = resample_img(
181 Nifti1Image(data, affine_eye),
182 target_affine=2 * affine_eye,
183 interpolation="nearest",
184 force_resample=force_resample,
185 copy_header=True,
186 )
188 downsampled = data[::2, ::2, ::2, ...]
189 x, y, z = downsampled.shape[:3]
191 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...])
193 rot_img = resample_img(
194 Nifti1Image(data.astype(endian_type), affine_eye),
195 target_affine=2 * affine_eye,
196 interpolation="nearest",
197 copy=copy_data,
198 force_resample=force_resample,
199 copy_header=True,
200 )
202 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...])
205@pytest.mark.parametrize("force_resample", [False, True])
206@pytest.mark.parametrize("shape", [(1, 4, 4), (1, 4, 4, 3)])
207@pytest.mark.parametrize("value", [-3.75, 0])
208def test_resampling_fill_value(data, affine_eye, value, force_resample):
209 """Test resampling with a non-zero fill value.
211 Check on 3D and 4D data.
212 """
213 angle = np.pi / 4
214 rot = rotation(0, angle)
216 if value:
217 rot_img = resample_img(
218 Nifti1Image(data, affine_eye),
219 target_affine=rot,
220 interpolation="nearest",
221 fill_value=value,
222 clip=False,
223 force_resample=force_resample,
224 copy_header=True,
225 )
226 else:
227 rot_img = resample_img(
228 Nifti1Image(data, affine_eye),
229 target_affine=rot,
230 interpolation="nearest",
231 clip=False,
232 force_resample=force_resample,
233 copy_header=True,
234 )
236 assert get_data(rot_img).flatten()[0] == value
238 rot_img2 = resample_to_img(
239 Nifti1Image(data, affine_eye),
240 rot_img,
241 interpolation="nearest",
242 fill_value=value,
243 force_resample=force_resample,
244 copy_header=True,
245 )
247 assert get_data(rot_img2).flatten()[0] == value
250@pytest.mark.parametrize("force_resample", [False, True])
251@pytest.mark.parametrize("shape", [(1, 4, 4), (1, 4, 4, 3)])
252@pytest.mark.parametrize("angle", ANGLES_TO_TEST)
253def test_resampling_with_affine(data, affine_eye, angle, force_resample):
254 """Test resampling with a given rotation part of the affine.
256 Check on 3D and 4D data.
257 """
258 rot = rotation(0, angle)
260 rot_img = resample_img(
261 Nifti1Image(data, affine_eye),
262 target_affine=rot,
263 interpolation="nearest",
264 force_resample=force_resample,
265 copy_header=True,
266 )
268 assert np.max(data) == np.max(get_data(rot_img))
269 assert get_data(rot_img).dtype == data.dtype
271 # We take the same rotation logic as above and test with nonnative endian
272 # data as input
273 img = Nifti1Image(data.astype(">f8"), affine_eye)
274 rot = rotation(0, angle)
276 rot_img = resample_img(
277 img,
278 target_affine=rot,
279 interpolation="nearest",
280 force_resample=force_resample,
281 copy_header=True,
282 )
284 assert np.max(data) == np.max(get_data(rot_img))
287@pytest.mark.parametrize("force_resample", [False, True])
288@pytest.mark.parametrize("shape", [(1, 10, 10), (1, 10, 10, 3)])
289@pytest.mark.parametrize("angle", (0, np.pi / 2.0, np.pi, 3 * np.pi / 2.0))
290def test_resampling_continuous_with_affine(
291 data, affine_eye, angle, force_resample
292):
293 rot = rotation(0, angle)
294 img = Nifti1Image(data, affine_eye)
296 rot_img = resample_img(
297 img,
298 target_affine=rot,
299 interpolation="continuous",
300 force_resample=force_resample,
301 copy_header=True,
302 )
303 rot_img_back = resample_img(
304 rot_img,
305 target_affine=affine_eye,
306 interpolation="continuous",
307 force_resample=force_resample,
308 copy_header=True,
309 )
311 center = slice(1, 9)
312 # values on the edges are wrong for some reason
313 mask = (0, center, center)
314 assert_allclose(get_data(img)[mask], get_data(rot_img_back)[mask])
316 assert get_data(rot_img).dtype == np.dtype(
317 data.dtype.name.replace("int", "float")
318 )
321@pytest.mark.parametrize("force_resample", [False, True])
322def test_resampling_error_checks(tmp_path, force_resample, data, affine_eye):
323 img = Nifti1Image(data, affine_eye)
324 target_shape = (5, 3, 2)
326 # Correct parameters: no exception
327 resample_img(
328 img,
329 target_shape=target_shape,
330 target_affine=affine_eye,
331 force_resample=force_resample,
332 copy_header=True,
333 )
334 resample_img(
335 img,
336 target_affine=affine_eye,
337 force_resample=force_resample,
338 copy_header=True,
339 )
341 filename = testing.write_imgs_to_path(img, file_path=tmp_path)
342 resample_img(
343 filename,
344 target_shape=target_shape,
345 target_affine=affine_eye,
346 force_resample=force_resample,
347 copy_header=True,
348 )
350 # Missing parameter
351 with pytest.raises(ValueError, match="target_affine should be specified"):
352 resample_img(
353 img,
354 target_shape=target_shape,
355 force_resample=force_resample,
356 copy_header=True,
357 )
359 # Invalid shape
360 with pytest.raises(ValueError, match="shape .* should be .* 3D grid"):
361 resample_img(
362 img,
363 target_shape=(2, 3),
364 target_affine=affine_eye,
365 force_resample=force_resample,
366 copy_header=True,
367 )
369 # Invalid interpolation
370 with pytest.raises(ValueError, match="interpolation must be one of"):
371 resample_img(
372 img,
373 target_shape=target_shape,
374 target_affine=affine_eye,
375 interpolation="an_invalid_interpolation",
376 force_resample=force_resample,
377 copy_header=True,
378 )
381@pytest.mark.parametrize("force_resample", [False, True])
382@pytest.mark.parametrize("target_shape", [None, (3, 2, 5)])
383def test_resampling_copy_has_no_shared_memory(
384 target_shape, force_resample, data, affine_eye
385):
386 """copy=true guarantees output array shares no memory with input array."""
387 img = Nifti1Image(data, affine_eye)
388 target_affine = None if target_shape is None else affine_eye
390 img_r = resample_img(
391 img,
392 target_affine=target_affine,
393 target_shape=target_shape,
394 copy=False,
395 force_resample=force_resample,
396 copy_header=True,
397 )
399 assert img_r == img
401 img_r = resample_img(
402 img,
403 target_affine=target_affine,
404 target_shape=target_shape,
405 copy=True,
406 force_resample=force_resample,
407 copy_header=True,
408 )
410 assert not np.may_share_memory(get_data(img_r), get_data(img))
411 assert_almost_equal(get_data(img_r), get_data(img))
412 assert_almost_equal(img_r.affine, img.affine)
415@pytest.mark.parametrize(
416 "force_resample",
417 [False, True],
418)
419def test_resampling_warning_s_form(data, affine_eye, force_resample):
420 img_no_sform = Nifti1Image(data, affine_eye)
421 img_no_sform.set_sform(None)
423 with pytest.warns(Warning, match="The provided image has no sform"):
424 resample_img(
425 img_no_sform,
426 target_affine=affine_eye,
427 force_resample=force_resample,
428 copy_header=True,
429 )
432@pytest.mark.parametrize("force_resample", [False, True])
433def test_resampling_warning_binary_image(affine_eye, rng, force_resample):
434 # Resampling a binary image with continuous or
435 # linear interpolation should raise a warning.
436 data_binary = rng.integers(4, size=(1, 4, 4), dtype="int32")
437 data_binary[data_binary > 0] = 1
439 assert sorted(np.unique(data_binary)) == [0, 1]
441 rot = rotation(0, np.pi / 4)
442 img_binary = Nifti1Image(data_binary, affine_eye)
444 assert _utils.niimg.is_binary_niimg(img_binary)
446 with pytest.warns(Warning, match="Resampling binary images with"):
447 resample_img(
448 img_binary,
449 target_affine=rot,
450 interpolation="continuous",
451 force_resample=force_resample,
452 copy_header=True,
453 )
455 with pytest.warns(Warning, match="Resampling binary images with"):
456 resample_img(
457 img_binary,
458 target_affine=rot,
459 interpolation="linear",
460 force_resample=force_resample,
461 copy_header=True,
462 )
465@pytest.mark.parametrize("force_resample", [False, True])
466def test_resample_img_copied_header(img_4d_mni_tr2, force_resample):
467 # Test that the header is copied when resampling
468 result = resample_img(
469 img_4d_mni_tr2,
470 target_affine=np.diag((6, 6, 6)),
471 copy_header=True,
472 force_resample=force_resample,
473 )
474 # pixdim[1:4] should change to [6, 6, 6]
475 assert (result.header["pixdim"][1:4] == np.array([6, 6, 6])).all()
476 # pixdim at other indices should remain the same
477 assert (
478 result.header["pixdim"][4:] == img_4d_mni_tr2.header["pixdim"][4:]
479 ).all()
480 assert result.header["pixdim"][0] == img_4d_mni_tr2.header["pixdim"][0]
481 # dim, srow_* and min/max should also change
482 match_headers_keys(
483 img_4d_mni_tr2,
484 result,
485 except_keys=[
486 "pixdim",
487 "dim",
488 "cal_max",
489 "cal_min",
490 "srow_x",
491 "srow_y",
492 "srow_z",
493 ],
494 )
497@pytest.mark.parametrize("force_resample", [False, True])
498def test_4d_affine_bounding_box_error(affine_eye, force_resample):
499 bigger_data = np.zeros([10, 10, 10])
500 bigger_img = Nifti1Image(bigger_data, affine_eye)
502 small_data = np.ones([4, 4, 4])
503 small_data_4D_affine = affine_eye
504 small_data_4D_affine[:3, -1] = np.array([5, 4, 5])
505 small_img = Nifti1Image(small_data, small_data_4D_affine)
507 # We would like to check whether all/most of the data
508 # will be contained in the resampled image
509 # The measure will be the l2 norm, since some resampling
510 # schemes approximately conserve it
512 def l2_norm(arr):
513 return (arr**2).sum()
515 # resample using 4D affine and specified target shape
516 small_to_big_with_shape = resample_img(
517 small_img,
518 target_affine=bigger_img.affine,
519 target_shape=bigger_img.shape,
520 force_resample=force_resample,
521 copy_header=True,
522 )
523 # resample using 3D affine and no target shape
524 small_to_big_without_shape_3D_affine = resample_img(
525 small_img,
526 target_affine=bigger_img.affine[:3, :3],
527 copy_header=True,
528 force_resample=force_resample,
529 )
530 # resample using 4D affine and no target shape
531 small_to_big_without_shape = resample_img(
532 small_img,
533 target_affine=bigger_img.affine,
534 copy_header=True,
535 force_resample=force_resample,
536 )
538 # The first 2 should pass
539 assert_almost_equal(
540 l2_norm(small_data), l2_norm(get_data(small_to_big_with_shape))
541 )
542 assert_almost_equal(
543 l2_norm(small_data),
544 l2_norm(get_data(small_to_big_without_shape_3D_affine)),
545 )
547 # After correcting decision tree for 4x4 affine given + no target shape
548 # from "use initial shape" to "calculate minimal bounding box respecting
549 # the affine anchor and the data"
550 assert_almost_equal(
551 l2_norm(small_data), l2_norm(get_data(small_to_big_without_shape))
552 )
554 assert_array_equal(
555 small_to_big_without_shape.shape,
556 small_data_4D_affine[:3, -1] + np.array(small_img.shape),
557 )
560@pytest.mark.parametrize("force_resample", [False, True])
561def test_raises_upon_3x3_affine_and_no_shape(affine_eye, force_resample):
562 img = Nifti1Image(np.zeros([8, 9, 10]), affine=affine_eye)
563 message = (
564 "Given target shape without anchor "
565 "vector: Affine shape should be \\(4, 4\\) and "
566 "not \\(3, 3\\)"
567 )
568 with pytest.raises(ValueError, match=message):
569 resample_img(
570 img,
571 target_affine=np.eye(3) * 2,
572 target_shape=(10, 10, 10),
573 force_resample=force_resample,
574 copy_header=True,
575 )
578@pytest.mark.parametrize("force_resample", [False, True])
579def test_3x3_affine_bbox(affine_eye, force_resample):
580 """Test that the bounding-box is properly computed when \
581 transforming with a negative affine component.
583 This is specifically to test for a change in behavior between
584 scipy < 0.18 and scipy >= 0.18, which is an interaction between
585 offset and a diagonal affine
586 """
587 image = np.ones((20, 30))
588 source_affine = affine_eye
589 # Give the affine an offset
590 source_affine[:2, 3] = np.array([96, 64])
592 # We need to turn this data into a nibabel image
593 img = Nifti1Image(image[:, :, np.newaxis], affine=source_affine)
595 target_affine_3x3 = np.eye(3) * 2
596 # One negative axes
597 target_affine_3x3[1] *= -1
599 img_3d_affine = resample_img(
600 img,
601 target_affine=target_affine_3x3,
602 force_resample=force_resample,
603 copy_header=True,
604 )
606 # If the bounding box is computed wrong, the image will be only zeros
607 assert_allclose(get_data(img_3d_affine).max(), image.max())
610@pytest.mark.parametrize("force_resample", [False, True])
611def test_raises_bbox_error_if_data_outside_box(affine_eye, force_resample):
612 """Make some cases which should raise exceptions."""
613 # original image
614 data = np.zeros([8, 9, 10])
615 affine_offset = np.array([1, 1, 1])
616 affine = affine_eye
617 affine[:3, 3] = affine_offset
618 img = Nifti1Image(data, affine)
620 # some axis flipping affines
621 diag = [
622 [-1, 1, 1, 1],
623 [1, -1, 1, 1],
624 [1, 1, -1, 1],
625 [-1, -1, 1, 1],
626 [-1, 1, -1, 1],
627 [1, -1, -1, 1],
628 ]
629 axis_flips = np.array(list(map(np.diag, diag)))
631 # some in plane 90 degree rotations base on these
632 # (by permuting two lines)
633 af = axis_flips
634 rotations = np.array(
635 [
636 af[0][[1, 0, 2, 3]],
637 af[0][[2, 1, 0, 3]],
638 af[1][[1, 0, 2, 3]],
639 af[1][[0, 2, 1, 3]],
640 af[2][[2, 1, 0, 3]],
641 af[2][[0, 2, 1, 3]],
642 ]
643 )
645 new_affines = np.concatenate([axis_flips, rotations])
646 new_offset = np.array([0.0, 0.0, 0.0])
647 new_affines[:, :3, 3] = new_offset[np.newaxis, :]
649 exception = BoundingBoxError
650 message = (
651 "The field of view given "
652 "by the target affine does "
653 "not contain any of the data"
654 )
655 for new_affine in new_affines:
656 with pytest.raises(exception, match=message):
657 resample_img(
658 img,
659 target_affine=new_affine,
660 force_resample=force_resample,
661 copy_header=True,
662 )
665@pytest.mark.parametrize("force_resample", [False, True])
666@pytest.mark.parametrize(
667 "axis_permutation", [[0, 1, 2], [1, 0, 2], [2, 1, 0], [0, 2, 1]]
668)
669def test_resampling_result_axis_permutation(
670 affine_eye, axis_permutation, force_resample
671):
672 """Transform real data using easily checkable transformations.
674 For now: axis permutations
675 create a cuboid full of deterministic data, padded with one
676 voxel thickness of zeros
677 """
678 core_shape = (3, 5, 4)
679 core_data = np.arange(np.prod(core_shape)).reshape(core_shape)
680 full_data_shape = np.array(core_shape) + 2
681 full_data = np.zeros(full_data_shape)
682 full_data[tuple(slice(1, 1 + s) for s in core_shape)] = core_data
684 source_img = Nifti1Image(full_data, affine_eye)
686 # check 3x3 transformation matrix
687 target_affine = np.eye(3)[axis_permutation]
689 resampled_img = resample_img(
690 source_img,
691 target_affine=target_affine,
692 force_resample=force_resample,
693 copy_header=True,
694 )
696 resampled_data = get_data(resampled_img)
697 expected_data = full_data.transpose(axis_permutation)
698 assert_array_almost_equal(resampled_data, expected_data)
700 # check 4x4 transformation matrix
701 offset = np.array([-2, 1, -3])
703 target_affine = affine_eye
704 target_affine[:3, :3] = np.eye(3)[axis_permutation]
705 target_affine[:3, 3] = offset
707 resampled_img = resample_img(
708 source_img,
709 target_affine=target_affine,
710 force_resample=force_resample,
711 copy_header=True,
712 )
714 resampled_data = get_data(resampled_img)
715 offset_cropping = (
716 np.vstack([-offset[axis_permutation][np.newaxis, :], np.zeros([1, 3])])
717 .T.ravel()
718 .astype(int)
719 )
720 expected_data = pad_array(
721 full_data.transpose(axis_permutation), list(offset_cropping)
722 )
723 assert_array_almost_equal(resampled_data, expected_data)
726@pytest.mark.parametrize("force_resample", [False, True])
727@pytest.mark.parametrize("core_shape", [(3, 5, 4), (3, 5, 4, 2)])
728def test_resampling_nan(affine_eye, core_shape, force_resample):
729 """Test that when the data has NaNs they do not propagate to the \
730 whole image.
731 """
732 # create deterministic data, padded with one
733 # voxel thickness of zeros
734 core_data = (
735 np.arange(np.prod(core_shape)).reshape(core_shape).astype(np.float64)
736 )
737 # Introduce a nan
738 core_data[2, 2:4, 1] = np.nan
739 full_data_shape = np.array(core_shape) + 2
740 full_data = np.zeros(full_data_shape)
741 full_data[tuple(slice(1, 1 + s) for s in core_shape)] = core_data
743 source_img = Nifti1Image(full_data, affine_eye)
745 # Transform real data using easily checkable transformations
746 # For now: axis permutations
747 axis_permutation = [0, 1, 2]
749 # check 3x3 transformation matrix
750 target_affine = np.eye(3)[axis_permutation]
751 resampled_img = resample_img(
752 source_img,
753 target_affine=target_affine,
754 force_resample=force_resample,
755 copy_header=True,
756 )
758 resampled_data = get_data(resampled_img)
759 if full_data.ndim == 4:
760 axis_permutation.append(3)
761 expected_data = full_data.transpose(axis_permutation)
762 non_nan = np.isfinite(expected_data)
764 # Check that the input data hasn't been modified:
765 assert not np.all(non_nan)
767 # Check that for finite value resampling works without problems
768 assert_array_almost_equal(resampled_data[non_nan], expected_data[non_nan])
770 # Check that what was not finite is still not finite
771 assert not np.any(np.isfinite(resampled_data[np.logical_not(non_nan)]))
774@pytest.mark.parametrize("force_resample", [False, True])
775def test_resampling_nan_big(affine_eye, force_resample):
776 """Test with an actual resampling, in the case of a bigish hole.
778 This checks the extrapolation mechanism: if we don't do any
779 extrapolation before resampling, the hole creates big
780 artifacts
781 """
782 data = 10 * np.ones((10, 10, 10))
783 data[4:6, 4:6, 4:6] = np.nan
784 source_img = Nifti1Image(data, 2 * affine_eye)
786 with pytest.warns(RuntimeWarning):
787 resampled_img = resample_img(
788 source_img,
789 target_affine=affine_eye,
790 force_resample=force_resample,
791 copy_header=True,
792 )
794 resampled_data = get_data(resampled_img)
795 assert_allclose(10, resampled_data[np.isfinite(resampled_data)])
798@pytest.mark.parametrize("force_resample", [False, True])
799def test_resample_to_img(data, affine_eye, force_resample):
800 source_affine = affine_eye
801 source_img = Nifti1Image(data, source_affine)
803 target_affine = 2 * affine_eye
804 target_img = Nifti1Image(data, target_affine)
806 result_img = resample_to_img(
807 source_img,
808 target_img,
809 interpolation="nearest",
810 force_resample=force_resample,
811 copy_header=True,
812 )
814 downsampled = data[::2, ::2, ::2, ...]
815 x, y, z = downsampled.shape[:3]
816 assert_almost_equal(downsampled, get_data(result_img)[:x, :y, :z, ...])
819def test_crop(affine_eye):
820 # Testing that padding of arrays and cropping of images work symmetrically
821 shape = (4, 6, 2)
822 data = np.ones(shape)
823 padded = pad_array(data, [3, 2, 4, 4, 5, 7])
824 padd_nii = Nifti1Image(padded, affine_eye)
826 cropped = crop_img(padd_nii, pad=False, copy_header=True)
828 assert_equal(get_data(cropped), data)
831@pytest.mark.parametrize("force_resample", [False, True])
832def test_resample_identify_affine_int_translation(
833 affine_eye, rng, force_resample
834):
835 source_shape = (6, 4, 6)
836 source_affine = affine_eye
837 source_affine[:, 3] = np.append(rng.integers(0, 4, 3), 1)
838 source_data = rng.random(source_shape)
839 source_img = Nifti1Image(source_data, source_affine)
841 target_shape = (11, 10, 9)
842 target_data = np.zeros(target_shape)
843 target_affine = source_affine
844 target_affine[:3, 3] -= 3 # add an offset of 3 in x, y, z
845 target_data[3:9, 3:7, 3:9] = (
846 source_data # put the data at the offset location
847 )
848 target_img = Nifti1Image(target_data, target_affine)
850 result_img = resample_to_img(
851 source_img,
852 target_img,
853 interpolation="nearest",
854 force_resample=force_resample,
855 copy_header=True,
856 )
857 assert_almost_equal(get_data(target_img), get_data(result_img))
859 result_img_2 = resample_to_img(
860 result_img,
861 source_img,
862 interpolation="nearest",
863 force_resample=force_resample,
864 copy_header=True,
865 )
866 assert_almost_equal(get_data(source_img), get_data(result_img_2))
868 result_img_3 = resample_to_img(
869 result_img,
870 source_img,
871 interpolation="nearest",
872 force_resample=force_resample,
873 copy_header=True,
874 )
875 assert_almost_equal(get_data(result_img_2), get_data(result_img_3))
877 result_img_4 = resample_to_img(
878 source_img,
879 target_img,
880 interpolation="nearest",
881 force_resample=force_resample,
882 copy_header=True,
883 )
884 assert_almost_equal(get_data(target_img), get_data(result_img_4))
887@pytest.mark.parametrize("force_resample", [False, True])
888def test_resample_clip(affine_eye, force_resample):
889 # Resample and image and get larger and smaller
890 # value than in the original. Use clip to get rid of these images
892 shape = (6, 3, 6)
893 data = np.zeros(shape=shape)
894 data[1:-2, 1:-1, 1:-2] = 1
896 source_affine = np.diag((2, 2, 2, 1))
897 source_img = Nifti1Image(data, source_affine)
899 no_clip_data = get_data(
900 resample_img(
901 source_img,
902 target_affine=affine_eye,
903 clip=False,
904 force_resample=force_resample,
905 copy_header=True,
906 )
907 )
908 clip_data = get_data(
909 resample_img(
910 source_img,
911 target_affine=affine_eye,
912 clip=True,
913 force_resample=force_resample,
914 copy_header=True,
915 )
916 )
918 not_clip = np.where(
919 (no_clip_data > data.min()) & (no_clip_data < data.max())
920 )
922 assert np.any(no_clip_data > data.max())
923 assert np.any(no_clip_data < data.min())
924 assert np.all(clip_data <= data.max())
925 assert np.all(clip_data >= data.min())
926 assert_array_equal(no_clip_data[not_clip], clip_data[not_clip])
929@pytest.mark.parametrize("force_resample", [False, True])
930def test_reorder_img(affine_eye, rng, force_resample):
931 # We need to test on a square array, as rotation does not change
932 # shape, whereas reordering does.
933 shape = (5, 5, 5, 2, 2)
934 data = rng.uniform(size=shape)
935 affine = affine_eye
936 affine[:3, -1] = 0.5 * np.array(shape[:3])
937 ref_img = Nifti1Image(data, affine)
939 # Test with purely positive matrices and compare to a rotation
940 for theta, phi in rng.integers(4, size=(5, 2)):
941 rot = rotation(theta * np.pi / 2, phi * np.pi / 2)
942 rot[np.abs(rot) < 0.001] = 0
943 rot[rot > 0.9] = 1
944 rot[rot < -0.9] = 1
945 b = 0.5 * np.array(shape[:3])
946 new_affine = from_matrix_vector(rot, b)
948 rot_img = resample_img(
949 ref_img,
950 target_affine=new_affine,
951 force_resample=force_resample,
952 copy_header=True,
953 )
955 assert_array_equal(rot_img.affine, new_affine)
956 assert_array_equal(get_data(rot_img).shape, shape)
958 reordered_img = reorder_img(rot_img, copy_header=True)
960 assert_array_equal(reordered_img.affine[:3, :3], np.eye(3))
961 assert_almost_equal(get_data(reordered_img), data)
964@pytest.mark.parametrize("force_resample", [False, True])
965def test_reorder_img_with_resample_arg(affine_eye, rng, force_resample):
966 shape = (5, 5, 5, 2, 2)
967 data = rng.uniform(size=shape)
968 affine = affine_eye
969 affine[:3, -1] = 0.5 * np.array(shape[:3])
970 ref_img = Nifti1Image(data, affine)
972 interpolation = "nearest"
974 reordered_img = reorder_img(
975 ref_img, resample=interpolation, copy_header=True
976 )
978 resampled_img = resample_img(
979 ref_img,
980 target_affine=reordered_img.affine,
981 interpolation=interpolation,
982 force_resample=force_resample,
983 copy_header=True,
984 )
985 assert_array_equal(get_data(reordered_img), get_data(resampled_img))
988def test_reorder_img_error_reorder_axis(affine_eye, rng):
989 shape = (5, 5, 5, 2, 2)
990 data = rng.uniform(size=shape)
992 # Create a non-diagonal affine,
993 # and check that we raise a sensible exception
994 non_diagonal_affine = affine_eye
995 non_diagonal_affine[1, 0] = 0.1
996 ref_img = Nifti1Image(data, non_diagonal_affine)
998 # Test that no exception is raised when resample='continuous'
999 reorder_img(ref_img, resample="continuous", copy_header=True)
1001 with pytest.raises(ValueError, match="Cannot reorder the axes"):
1002 reorder_img(ref_img, copy_header=True)
1005def test_reorder_img_flipping_axis(affine_eye, rng):
1006 shape = (5, 5, 5, 2, 2)
1008 data = rng.uniform(size=shape)
1010 for i in (0, 1, 2):
1011 # Make a diagonal affine with a negative axis, and check that
1012 # can be reordered, also vary the shape
1013 shape = (i + 1, i + 2, 3 - i)
1014 affine = affine_eye
1015 affine[i, i] *= -1
1017 img = Nifti1Image(data, affine)
1018 orig_img = copy.copy(img)
1020 img2 = reorder_img(img, copy_header=True)
1022 # Check that img has not been changed
1023 assert_array_equal(img.affine, orig_img.affine)
1024 assert_array_equal(get_data(img), get_data(orig_img))
1026 # Test that the affine is indeed diagonal:
1027 assert_array_equal(
1028 img2.affine[:3, :3], np.diag(np.diag(img2.affine[:3, :3]))
1029 )
1030 assert np.all(np.diag(img2.affine) >= 0)
1033def test_reorder_img_error_interpolation(affine_eye, rng):
1034 shape = (5, 5, 5, 2, 2)
1035 data = rng.uniform(size=shape)
1036 affine = affine_eye
1037 affine[1, 0] = 0.1
1038 ref_img = Nifti1Image(data, affine)
1040 with pytest.raises(ValueError, match="interpolation must be one of"):
1041 reorder_img(
1042 ref_img, resample="an_invalid_interpolation", copy_header=True
1043 )
1046@pytest.mark.parametrize("force_resample", [False, True])
1047def test_reorder_img_non_native_endianness(force_resample):
1048 def _get_resampled_img(dtype):
1049 data = np.ones((10, 10, 10), dtype=dtype)
1050 data[3:7, 3:7, 3:7] = 2
1052 theta = math.pi / 6.0
1053 c = math.cos(theta)
1054 s = math.sin(theta)
1056 affine = np.array(
1057 [[1, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, 1]]
1058 )
1060 img = Nifti1Image(data, affine)
1061 return resample_img(
1062 img,
1063 target_affine=affine,
1064 force_resample=force_resample,
1065 copy_header=True,
1066 )
1068 img_1 = _get_resampled_img("<f8")
1069 img_2 = _get_resampled_img(">f8")
1071 assert_equal(get_data(img_1), get_data(img_2))
1074def test_reorder_img_mirror():
1075 affine = np.array(
1076 [
1077 [-1.1, -0.0, 0.0, 0.0],
1078 [-0.0, -1.2, 0.0, 0.0],
1079 [-0.0, -0.0, 1.3, 0.0],
1080 [0.0, 0.0, 0.0, 1.0],
1081 ]
1082 )
1083 img = Nifti1Image(np.zeros((4, 6, 8)), affine=affine)
1084 reordered = reorder_img(img, copy_header=True)
1085 assert_allclose(
1086 get_bounds(reordered.shape, reordered.affine),
1087 get_bounds(img.shape, img.affine),
1088 )
1091def test_reorder_img_copied_header(img_4d_mni_tr2):
1092 # Test that the header is copied when reordering
1093 result = reorder_img(img_4d_mni_tr2, copy_header=True)
1094 # all header fields should stay the same
1095 match_headers_keys(
1096 img_4d_mni_tr2,
1097 result,
1098 except_keys=[],
1099 )
1102@pytest.mark.parametrize(
1103 "func, input_img",
1104 [
1105 (resample_img, "img_4d_mni_tr2"),
1106 (reorder_img, "img_4d_mni_tr2"),
1107 ],
1108)
1109def test_warning_copy_header_false(request, func, input_img):
1110 # Use the request fixture to get the actual fixture value
1111 actual_input_img = request.getfixturevalue(input_img)
1112 with pytest.warns(FutureWarning, match="From release 0.13.0 onwards*"):
1113 func(actual_input_img, copy_header=False)
1116def test_coord_transform_trivial(affine_eye, rng):
1117 sform = affine_eye
1118 x = rng.random((10,))
1119 y = rng.random((10,))
1120 z = rng.random((10,))
1122 x_, y_, z_ = coord_transform(x, y, z, sform)
1123 assert_array_equal(x, x_)
1124 assert_array_equal(y, y_)
1125 assert_array_equal(z, z_)
1127 sform[:, -1] = 1
1128 x_, y_, z_ = coord_transform(x, y, z, sform)
1129 assert_array_equal(x + 1, x_)
1130 assert_array_equal(y + 1, y_)
1131 assert_array_equal(z + 1, z_)
1133 # Test the output in case of one item array
1134 x, y, z = x[:1], y[:1], z[:1]
1135 x_, y_, z_ = coord_transform(x, y, z, sform)
1136 assert_array_equal(x + 1, x_)
1137 assert_array_equal(y + 1, y_)
1138 assert_array_equal(z + 1, z_)
1140 # Test the output in case of simple items
1141 x, y, z = x[0], y[0], z[0]
1142 x_, y_, z_ = coord_transform(x, y, z, sform)
1143 assert_array_equal(x + 1, x_)
1144 assert_array_equal(y + 1, y_)
1145 assert_array_equal(z + 1, z_)
1147 # Test the outputs have the same shape as the inputs
1148 x = np.ones((3, 2, 4))
1149 y = np.ones((3, 2, 4))
1150 z = np.ones((3, 2, 4))
1151 x_, y_, z_ = coord_transform(x, y, z, sform)
1152 assert x.shape == x_.shape
1155# TODO "This test does not run on ARM arch.",
1156@pytest.mark.parametrize("force_resample", [False, True])
1157@pytest.mark.skipif(
1158 not testing.is_64bit(), reason="This test only runs on 64bits machines."
1159)
1160@pytest.mark.skipif(
1161 os.environ.get("APPVEYOR") == "True",
1162 reason="This test too slow (7-8 minutes) on AppVeyor",
1163)
1164@pytest.mark.skipif(
1165 sys.platform == "darwin",
1166 reason="This test is too slow (sometimes up to 7-8 minutes) on macOS",
1167)
1168def test_resample_img_segmentation_fault(force_resample):
1169 # see https://github.com/nilearn/nilearn/issues/346
1170 shape_in = (64, 64, 64)
1171 aff_in = np.diag([2.0, 2.0, 2.0, 1.0])
1172 aff_out = np.diag([3.0, 3.0, 3.0, 1.0])
1173 # fourth_dim = 1024 works fine but for 1025 creates a segmentation
1174 # fault with scipy < 0.14.1
1175 fourth_dim = 1025
1177 try:
1178 data = np.ones((*shape_in, fourth_dim), dtype=np.float64)
1179 except MemoryError:
1180 # This can happen on AppVeyor and for 32-bit Python on Windows
1181 pytest.skip("Not enough RAM to run this test")
1182 else:
1183 img_in = Nifti1Image(data, aff_in)
1185 resample_img(
1186 img_in,
1187 target_affine=aff_out,
1188 interpolation="nearest",
1189 force_resample=force_resample,
1190 copy_header=True,
1191 )
1194@pytest.mark.parametrize("force_resample", [False, True])
1195@pytest.mark.parametrize(
1196 "dtype",
1197 [
1198 np.int8,
1199 np.int16,
1200 np.int32,
1201 np.uint8,
1202 np.uint16,
1203 np.uint32,
1204 np.float32,
1205 np.float64,
1206 float,
1207 ">i4",
1208 "<i4",
1209 ],
1210)
1211def test_resampling_with_int_types_no_crash(affine_eye, dtype, force_resample):
1212 data = np.zeros((2, 2, 2))
1213 img = Nifti1Image(data.astype(dtype), affine_eye)
1214 resample_img(
1215 img,
1216 target_affine=2.0 * affine_eye,
1217 force_resample=force_resample,
1218 copy_header=True,
1219 )
1222@pytest.mark.parametrize("force_resample", [False, True])
1223@pytest.mark.parametrize("dtype", ["int64", "uint64", "<i8", ">i8"])
1224@pytest.mark.parametrize("no_int64_nifti", ["allow for this test"])
1225def test_resampling_with_int64_types_no_crash(
1226 affine_eye, dtype, force_resample
1227):
1228 data = np.zeros((2, 2, 2))
1229 # Passing dtype or header is required when using int64
1230 # https://nipy.org/nibabel/changelog.html#api-changes-and-deprecations
1231 hdr = Nifti1Header()
1232 hdr.set_data_dtype(dtype)
1233 img = Nifti1Image(data.astype(dtype), affine_eye, header=hdr)
1234 resample_img(
1235 img,
1236 target_affine=2.0 * affine_eye,
1237 force_resample=force_resample,
1238 copy_header=True,
1239 )
1242@pytest.mark.parametrize("force_resample", [False, True])
1243def test_resample_input(affine_eye, shape, rng, tmp_path, force_resample):
1244 data = rng.integers(0, 10, shape, dtype="int32")
1245 affine = affine_eye
1246 affine[:3, -1] = 0.5 * np.array(shape[:3])
1247 img = Nifti1Image(data, affine)
1249 filename = testing.write_imgs_to_path(
1250 img, file_path=tmp_path, create_files=True
1251 )
1252 filename = Path(filename)
1253 resample_img(
1254 filename,
1255 target_affine=affine,
1256 interpolation="nearest",
1257 force_resample=force_resample,
1258 copy_header=True,
1259 )
1262@pytest.mark.parametrize("force_resample", [False, True])
1263def test_smoke_resampling_non_nifti(affine_eye, shape, rng, force_resample):
1264 target_affine = 2 * affine_eye
1265 data = rng.integers(0, 10, shape, dtype="int32")
1266 img = MGHImage(data, affine_eye)
1268 resample_img(
1269 img,
1270 target_affine=target_affine,
1271 interpolation="nearest",
1272 force_resample=force_resample,
1273 copy_header=True,
1274 )
1277@pytest.mark.parametrize("shape", [(1, 4, 4)])
1278def test_get_mask_bounds(data, affine_eye):
1279 img = Nifti1Image(data, affine_eye)
1280 assert_allclose((0.0, 0.0, 0.0, 3.0, 0.0, 3.0), get_mask_bounds(img))
1283def test_get_mask_bounds_error(data, affine_eye):
1284 with pytest.raises(TypeError, match="input should be a NiftiLike object"):
1285 get_mask_bounds(None)
1287 with pytest.raises(
1288 DimensionError, match="Expected dimension is 3D and you provided a 4D"
1289 ):
1290 img = Nifti1Image(data, affine_eye)
1291 get_mask_bounds(img)