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

1"""Test the resampling code.""" 

2 

3import copy 

4import math 

5import os 

6import sys 

7from pathlib import Path 

8 

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) 

20 

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 

37 

38ANGLES_TO_TEST = (0, np.pi, np.pi / 2.0, np.pi / 4.0, np.pi / 3.0) 

39 

40SHAPE = (3, 2, 5, 2) 

41 

42 

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) 

54 

55 

56@pytest.fixture 

57def shape(): 

58 return SHAPE 

59 

60 

61@pytest.fixture 

62def data(rng, shape): 

63 return rng.random(shape) 

64 

65 

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]) 

69 

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 ) 

77 

78 

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]) 

83 

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 ) 

91 

92 assert_almost_equal(data, get_data(rot_img)) 

93 

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 ) 

102 

103 assert_almost_equal(data, get_data(rot_img)) 

104 

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 ) 

113 

114 

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. 

122 

123 with big endian data ('>f8') 

124 with little endian data ('<f8') 

125 """ 

126 affine_eye[:3, -1] = 0.5 * np.array(shape[:3]) 

127 

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 ) 

135 

136 assert_almost_equal(data, get_data(rot_img)) 

137 

138 

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 ) 

149 

150 downsampled = data[::2, ::2, ::2, ...] 

151 x, y, z = downsampled.shape[:3] 

152 

153 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...]) 

154 

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 ) 

162 

163 assert_almost_equal(get_data(rot_img_2), get_data(rot_img)) 

164 

165 

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. 

173 

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. 

176 

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 ) 

187 

188 downsampled = data[::2, ::2, ::2, ...] 

189 x, y, z = downsampled.shape[:3] 

190 

191 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...]) 

192 

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 ) 

201 

202 assert_almost_equal(downsampled, get_data(rot_img)[:x, :y, :z, ...]) 

203 

204 

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. 

210 

211 Check on 3D and 4D data. 

212 """ 

213 angle = np.pi / 4 

214 rot = rotation(0, angle) 

215 

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 ) 

235 

236 assert get_data(rot_img).flatten()[0] == value 

237 

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 ) 

246 

247 assert get_data(rot_img2).flatten()[0] == value 

248 

249 

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. 

255 

256 Check on 3D and 4D data. 

257 """ 

258 rot = rotation(0, angle) 

259 

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 ) 

267 

268 assert np.max(data) == np.max(get_data(rot_img)) 

269 assert get_data(rot_img).dtype == data.dtype 

270 

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) 

275 

276 rot_img = resample_img( 

277 img, 

278 target_affine=rot, 

279 interpolation="nearest", 

280 force_resample=force_resample, 

281 copy_header=True, 

282 ) 

283 

284 assert np.max(data) == np.max(get_data(rot_img)) 

285 

286 

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) 

295 

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 ) 

310 

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]) 

315 

316 assert get_data(rot_img).dtype == np.dtype( 

317 data.dtype.name.replace("int", "float") 

318 ) 

319 

320 

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) 

325 

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 ) 

340 

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 ) 

349 

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 ) 

358 

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 ) 

368 

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 ) 

379 

380 

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 

389 

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 ) 

398 

399 assert img_r == img 

400 

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 ) 

409 

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) 

413 

414 

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) 

422 

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 ) 

430 

431 

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 

438 

439 assert sorted(np.unique(data_binary)) == [0, 1] 

440 

441 rot = rotation(0, np.pi / 4) 

442 img_binary = Nifti1Image(data_binary, affine_eye) 

443 

444 assert _utils.niimg.is_binary_niimg(img_binary) 

445 

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 ) 

454 

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 ) 

463 

464 

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 ) 

495 

496 

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) 

501 

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) 

506 

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 

511 

512 def l2_norm(arr): 

513 return (arr**2).sum() 

514 

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 ) 

537 

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 ) 

546 

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 ) 

553 

554 assert_array_equal( 

555 small_to_big_without_shape.shape, 

556 small_data_4D_affine[:3, -1] + np.array(small_img.shape), 

557 ) 

558 

559 

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 ) 

576 

577 

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. 

582 

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]) 

591 

592 # We need to turn this data into a nibabel image 

593 img = Nifti1Image(image[:, :, np.newaxis], affine=source_affine) 

594 

595 target_affine_3x3 = np.eye(3) * 2 

596 # One negative axes 

597 target_affine_3x3[1] *= -1 

598 

599 img_3d_affine = resample_img( 

600 img, 

601 target_affine=target_affine_3x3, 

602 force_resample=force_resample, 

603 copy_header=True, 

604 ) 

605 

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()) 

608 

609 

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) 

619 

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))) 

630 

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 ) 

644 

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, :] 

648 

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 ) 

663 

664 

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. 

673 

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 

683 

684 source_img = Nifti1Image(full_data, affine_eye) 

685 

686 # check 3x3 transformation matrix 

687 target_affine = np.eye(3)[axis_permutation] 

688 

689 resampled_img = resample_img( 

690 source_img, 

691 target_affine=target_affine, 

692 force_resample=force_resample, 

693 copy_header=True, 

694 ) 

695 

696 resampled_data = get_data(resampled_img) 

697 expected_data = full_data.transpose(axis_permutation) 

698 assert_array_almost_equal(resampled_data, expected_data) 

699 

700 # check 4x4 transformation matrix 

701 offset = np.array([-2, 1, -3]) 

702 

703 target_affine = affine_eye 

704 target_affine[:3, :3] = np.eye(3)[axis_permutation] 

705 target_affine[:3, 3] = offset 

706 

707 resampled_img = resample_img( 

708 source_img, 

709 target_affine=target_affine, 

710 force_resample=force_resample, 

711 copy_header=True, 

712 ) 

713 

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) 

724 

725 

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 

742 

743 source_img = Nifti1Image(full_data, affine_eye) 

744 

745 # Transform real data using easily checkable transformations 

746 # For now: axis permutations 

747 axis_permutation = [0, 1, 2] 

748 

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 ) 

757 

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) 

763 

764 # Check that the input data hasn't been modified: 

765 assert not np.all(non_nan) 

766 

767 # Check that for finite value resampling works without problems 

768 assert_array_almost_equal(resampled_data[non_nan], expected_data[non_nan]) 

769 

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)])) 

772 

773 

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. 

777 

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) 

785 

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 ) 

793 

794 resampled_data = get_data(resampled_img) 

795 assert_allclose(10, resampled_data[np.isfinite(resampled_data)]) 

796 

797 

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) 

802 

803 target_affine = 2 * affine_eye 

804 target_img = Nifti1Image(data, target_affine) 

805 

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 ) 

813 

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, ...]) 

817 

818 

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) 

825 

826 cropped = crop_img(padd_nii, pad=False, copy_header=True) 

827 

828 assert_equal(get_data(cropped), data) 

829 

830 

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) 

840 

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) 

849 

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)) 

858 

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)) 

867 

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)) 

876 

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)) 

885 

886 

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 

891 

892 shape = (6, 3, 6) 

893 data = np.zeros(shape=shape) 

894 data[1:-2, 1:-1, 1:-2] = 1 

895 

896 source_affine = np.diag((2, 2, 2, 1)) 

897 source_img = Nifti1Image(data, source_affine) 

898 

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 ) 

917 

918 not_clip = np.where( 

919 (no_clip_data > data.min()) & (no_clip_data < data.max()) 

920 ) 

921 

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]) 

927 

928 

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) 

938 

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) 

947 

948 rot_img = resample_img( 

949 ref_img, 

950 target_affine=new_affine, 

951 force_resample=force_resample, 

952 copy_header=True, 

953 ) 

954 

955 assert_array_equal(rot_img.affine, new_affine) 

956 assert_array_equal(get_data(rot_img).shape, shape) 

957 

958 reordered_img = reorder_img(rot_img, copy_header=True) 

959 

960 assert_array_equal(reordered_img.affine[:3, :3], np.eye(3)) 

961 assert_almost_equal(get_data(reordered_img), data) 

962 

963 

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) 

971 

972 interpolation = "nearest" 

973 

974 reordered_img = reorder_img( 

975 ref_img, resample=interpolation, copy_header=True 

976 ) 

977 

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)) 

986 

987 

988def test_reorder_img_error_reorder_axis(affine_eye, rng): 

989 shape = (5, 5, 5, 2, 2) 

990 data = rng.uniform(size=shape) 

991 

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) 

997 

998 # Test that no exception is raised when resample='continuous' 

999 reorder_img(ref_img, resample="continuous", copy_header=True) 

1000 

1001 with pytest.raises(ValueError, match="Cannot reorder the axes"): 

1002 reorder_img(ref_img, copy_header=True) 

1003 

1004 

1005def test_reorder_img_flipping_axis(affine_eye, rng): 

1006 shape = (5, 5, 5, 2, 2) 

1007 

1008 data = rng.uniform(size=shape) 

1009 

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 

1016 

1017 img = Nifti1Image(data, affine) 

1018 orig_img = copy.copy(img) 

1019 

1020 img2 = reorder_img(img, copy_header=True) 

1021 

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)) 

1025 

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) 

1031 

1032 

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) 

1039 

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 ) 

1044 

1045 

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 

1051 

1052 theta = math.pi / 6.0 

1053 c = math.cos(theta) 

1054 s = math.sin(theta) 

1055 

1056 affine = np.array( 

1057 [[1, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, 1]] 

1058 ) 

1059 

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 ) 

1067 

1068 img_1 = _get_resampled_img("<f8") 

1069 img_2 = _get_resampled_img(">f8") 

1070 

1071 assert_equal(get_data(img_1), get_data(img_2)) 

1072 

1073 

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 ) 

1089 

1090 

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 ) 

1100 

1101 

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) 

1114 

1115 

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,)) 

1121 

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_) 

1126 

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_) 

1132 

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_) 

1139 

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_) 

1146 

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 

1153 

1154 

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 

1176 

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) 

1184 

1185 resample_img( 

1186 img_in, 

1187 target_affine=aff_out, 

1188 interpolation="nearest", 

1189 force_resample=force_resample, 

1190 copy_header=True, 

1191 ) 

1192 

1193 

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 ) 

1220 

1221 

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 ) 

1240 

1241 

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) 

1248 

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 ) 

1260 

1261 

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) 

1267 

1268 resample_img( 

1269 img, 

1270 target_affine=target_affine, 

1271 interpolation="nearest", 

1272 force_resample=force_resample, 

1273 copy_header=True, 

1274 ) 

1275 

1276 

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)) 

1281 

1282 

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) 

1286 

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)