Coverage for nilearn/conftest.py: 45%

328 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1"""Configuration and extra fixtures for pytest.""" 

2 

3import warnings 

4 

5import nibabel 

6import numpy as np 

7import pandas as pd 

8import pytest 

9from nibabel import Nifti1Image 

10 

11from nilearn import image 

12from nilearn._utils.data_gen import ( 

13 generate_fake_fmri, 

14 generate_labeled_regions, 

15 generate_maps, 

16) 

17from nilearn._utils.helpers import is_matplotlib_installed 

18 

19# we need to import these fixtures even if not used in this module 

20from nilearn.datasets.tests._testing import ( 

21 request_mocker, # noqa: F401 

22 temp_nilearn_data_dir, # noqa: F401 

23) 

24from nilearn.surface import ( 

25 InMemoryMesh, 

26 PolyMesh, 

27 SurfaceImage, 

28) 

29 

30collect_ignore = ["datasets/data/convert_templates.py"] 

31collect_ignore_glob = ["reporting/_visual_testing/*"] 

32 

33# Plotting tests are skipped if matplotlib is missing. 

34# If the version is greater than the minimum one we support 

35# We skip the tests where the generated figures are compared to a baseline. 

36 

37if is_matplotlib_installed(): 37 ↛ 38line 37 didn't jump to line 38 because the condition on line 37 was never true

38 import matplotlib 

39 

40 from nilearn._utils.helpers import ( 

41 OPTIONAL_MATPLOTLIB_MIN_VERSION, 

42 compare_version, 

43 ) 

44 

45 if compare_version( 

46 matplotlib.__version__, ">", OPTIONAL_MATPLOTLIB_MIN_VERSION 

47 ): 

48 collect_ignore.extend( 

49 [ 

50 "plotting/tests/test_baseline_comparisons.py", 

51 ] 

52 ) 

53 

54else: 

55 collect_ignore.extend( 

56 [ 

57 "_utils/plotting.py", 

58 "plotting", 

59 "reporting/html_report.py", 

60 "reporting/tests/test_html_report.py", 

61 ] 

62 ) 

63 matplotlib = None # type: ignore[assignment] 

64 

65 

66def pytest_configure(config): # noqa: ARG001 

67 """Use Agg so that no figures pop up.""" 

68 if matplotlib is not None: 68 ↛ 69line 68 didn't jump to line 69 because the condition on line 68 was never true

69 matplotlib.use("Agg", force=True) 

70 

71 

72@pytest.fixture(autouse=True) 

73def no_int64_nifti(monkeypatch): 

74 """Prevent creating or writing a Nift1Image containing 64-bit ints. 

75 

76 It is easy to create such images by mistake because Numpy uses int64 by 

77 default, but tools like FSL fail to read them and Nibabel will refuse to 

78 write them in the future. 

79 

80 For tests that do need to manipulate int64 images, it is always possible to 

81 disable this fixture by parametrizing a test to override it: 

82 

83 @pytest.mark.parametrize("no_int64_nifti", [None]) 

84 def test_behavior_when_user_provides_int64_img(): 

85 # ... 

86 

87 But by default it is used automatically so that Nilearn doesn't create such 

88 images by mistake. 

89 

90 """ 

91 forbidden_types = (np.int64, np.uint64) 

92 error_msg = ( 

93 "Creating or saving an image containing 64-bit ints is forbidden." 

94 ) 

95 

96 to_filename = nibabel.nifti1.Nifti1Image.to_filename 

97 

98 def checked_to_filename(img, filename): 

99 assert image.get_data(img).dtype not in forbidden_types, error_msg 

100 return to_filename(img, filename) 

101 

102 monkeypatch.setattr( 

103 "nibabel.nifti1.Nifti1Image.to_filename", checked_to_filename 

104 ) 

105 

106 init = nibabel.nifti1.Nifti1Image.__init__ 

107 

108 def checked_init(self, dataobj, *args, **kwargs): 

109 assert dataobj.dtype not in forbidden_types, error_msg 

110 return init(self, dataobj, *args, **kwargs) 

111 

112 monkeypatch.setattr("nibabel.nifti1.Nifti1Image.__init__", checked_init) 

113 

114 

115@pytest.fixture(autouse=True) 

116def close_all(): 

117 """Close all matplotlib figures.""" 

118 yield 

119 if matplotlib is not None: 119 ↛ 120line 119 didn't jump to line 120 because the condition on line 119 was never true

120 import matplotlib.pyplot as plt 

121 

122 plt.close("all") # takes < 1 us so just always do it 

123 

124 

125@pytest.fixture(autouse=True) 

126def suppress_specific_warning(): 

127 """Ignore internal deprecation warnings.""" 

128 with warnings.catch_warnings(): 

129 messages = ( 

130 "The `darkness` parameter will be deprecated.*|" 

131 "In release 0.13, this fetcher will return a dictionary.*|" 

132 "The default strategy for standardize.*|" 

133 "The 'fetch_bids_langloc_dataset' function will be removed.*|" 

134 ) 

135 warnings.filterwarnings( 

136 "ignore", 

137 message=messages, 

138 category=DeprecationWarning, 

139 ) 

140 yield 

141 

142 

143# ------------------------ RNG ------------------------# 

144 

145 

146def _rng(seed=42): 

147 return np.random.default_rng(seed) 

148 

149 

150@pytest.fixture() 

151def rng(): 

152 """Return a seeded random number generator.""" 

153 return _rng() 

154 

155 

156# ------------------------ AFFINES ------------------------# 

157 

158 

159def _affine_mni(): 

160 """Return an affine corresponding to 2mm isotropic MNI template. 

161 

162 Mostly used for set up in other fixtures in other testing modules. 

163 """ 

164 return np.array( 

165 [ 

166 [2.0, 0.0, 0.0, -98.0], 

167 [0.0, 2.0, 0.0, -134.0], 

168 [0.0, 0.0, 2.0, -72.0], 

169 [0.0, 0.0, 0.0, 1.0], 

170 ] 

171 ) 

172 

173 

174@pytest.fixture() 

175def affine_mni(): 

176 """Return an affine corresponding to 2mm isotropic MNI template.""" 

177 return _affine_mni() 

178 

179 

180def _affine_eye(): 

181 """Return an identity matrix affine. 

182 

183 Mostly used for set up in other fixtures in other testing modules. 

184 """ 

185 return np.eye(4) 

186 

187 

188@pytest.fixture() 

189def affine_eye(): 

190 """Return an identity matrix affine.""" 

191 return _affine_eye() 

192 

193 

194# ------------------------ SHAPES ------------------------# 

195 

196 

197def _shape_3d_default(): 

198 """Return default shape for a 3D image. 

199 

200 Mostly used for set up in other fixtures in other testing modules. 

201 """ 

202 # avoid having identical shapes values, 

203 # because this fails to detect if the code does not handle dimensions well. 

204 return (7, 8, 9) 

205 

206 

207def _shape_3d_large(): 

208 """Shape usually used for maps images. 

209 

210 Mostly used for set up in other fixtures in other testing modules. 

211 """ 

212 # avoid having identical shapes values, 

213 # because this fails to detect if the code does not handle dimensions well. 

214 return (29, 30, 31) 

215 

216 

217def _shape_4d_default(): 

218 """Return default shape for a 4D image. 

219 

220 Mostly used for set up in other fixtures in other testing modules. 

221 """ 

222 # avoid having identical shapes values, 

223 # because this fails to detect if the code does not handle dimensions well. 

224 return (7, 8, 9, 5) 

225 

226 

227def _shape_4d_medium(): 

228 """Return default shape for a long 4D image.""" 

229 # avoid having identical shapes values, 

230 # because this fails to detect if the code does not handle dimensions well. 

231 return (7, 8, 9, 100) 

232 

233 

234def _shape_4d_long(): 

235 """Return default shape for a long 4D image.""" 

236 # avoid having identical shapes values, 

237 # because this fails to detect if the code does not handle dimensions well. 

238 return (7, 8, 9, 1500) 

239 

240 

241@pytest.fixture() 

242def shape_3d_default(): 

243 """Return default shape for a 3D image.""" 

244 return _shape_3d_default() 

245 

246 

247@pytest.fixture 

248def shape_3d_large(): 

249 """Shape usually used for maps images.""" 

250 return _shape_3d_large() 

251 

252 

253@pytest.fixture() 

254def shape_4d_default(): 

255 """Return default shape for a 4D image.""" 

256 return _shape_4d_default() 

257 

258 

259@pytest.fixture() 

260def shape_4d_long(): 

261 """Return long shape for a 4D image.""" 

262 return _shape_4d_long() 

263 

264 

265def _img_zeros(shape, affine): 

266 return Nifti1Image(np.zeros(shape), affine) 

267 

268 

269def _img_ones(shape, affine): 

270 return Nifti1Image(np.ones(shape), affine) 

271 

272 

273# ------------------------ 3D IMAGES ------------------------# 

274 

275 

276def _img_3d_rand(affine=None): 

277 """Return random 3D Nifti1Image in MNI space. 

278 

279 Mostly used for set up in other fixtures in other testing modules. 

280 """ 

281 if affine is None: 

282 affine = _affine_eye() 

283 data = _rng().random(_shape_3d_default()) 

284 return Nifti1Image(data, affine) 

285 

286 

287@pytest.fixture() 

288def img_3d_rand_eye(): 

289 """Return random 3D Nifti1Image in MNI space.""" 

290 return _img_3d_rand() 

291 

292 

293def _img_3d_mni(affine=None): 

294 if affine is None: 

295 affine = _affine_mni() 

296 data_positive = np.zeros((7, 7, 3)) 

297 rng = _rng() 

298 data_rng = rng.random((7, 7, 3)) 

299 data_positive[1:-1, 2:-1, 1:] = data_rng[1:-1, 2:-1, 1:] 

300 return Nifti1Image(data_positive, affine) 

301 

302 

303@pytest.fixture() 

304def img_3d_mni(): 

305 """Return a default random 3D Nifti1Image in MNI space.""" 

306 return _img_3d_mni() 

307 

308 

309@pytest.fixture() 

310def img_3d_mni_as_file(tmp_path): 

311 """Return path to a random 3D Nifti1Image in MNI space saved to disk.""" 

312 filename = tmp_path / "img.nii" 

313 _img_3d_mni().to_filename(filename) 

314 return filename 

315 

316 

317def _img_3d_zeros(shape=None, affine=None): 

318 """Return a default zeros filled 3D Nifti1Image (identity affine). 

319 

320 Mostly used for set up in other fixtures in other testing modules. 

321 """ 

322 if shape is None: 

323 shape = _shape_3d_default() 

324 if affine is None: 

325 affine = _affine_eye() 

326 return _img_zeros(shape, affine) 

327 

328 

329@pytest.fixture 

330def img_3d_zeros_eye(): 

331 """Return a zeros-filled 3D Nifti1Image (identity affine).""" 

332 return _img_3d_zeros() 

333 

334 

335def _img_3d_ones(shape=None, affine=None): 

336 """Return a ones-filled 3D Nifti1Image (identity affine). 

337 

338 Mostly used for set up in other fixtures in other testing modules. 

339 """ 

340 if shape is None: 

341 shape = _shape_3d_default() 

342 if affine is None: 

343 affine = _affine_eye() 

344 return _img_ones(shape, affine) 

345 

346 

347@pytest.fixture 

348def img_3d_ones_eye(): 

349 """Return a ones-filled 3D Nifti1Image (identity affine).""" 

350 return _img_3d_ones() 

351 

352 

353@pytest.fixture 

354def img_3d_ones_mni(): 

355 """Return a ones-filled 3D Nifti1Image (identity affine).""" 

356 return _img_3d_ones(shape=_shape_3d_default(), affine=_affine_mni()) 

357 

358 

359def _mask_data(): 

360 mask_data = np.zeros(_shape_3d_default(), dtype="int32") 

361 mask_data[3:6, 3:6, 3:6] = 1 

362 return mask_data 

363 

364 

365def _img_mask_mni(): 

366 """Return a 3D nifti mask in MNI space with some 1s in the center.""" 

367 return Nifti1Image(_mask_data(), _affine_mni()) 

368 

369 

370@pytest.fixture 

371def img_mask_mni(): 

372 """Return a 3D nifti mask in MNI space with some 1s in the center.""" 

373 return _img_mask_mni() 

374 

375 

376def _img_mask_eye(): 

377 """Return a 3D nifti mask with identity affine with 1s in the center.""" 

378 return Nifti1Image(_mask_data(), _affine_eye()) 

379 

380 

381@pytest.fixture 

382def img_mask_eye(): 

383 """Return a 3D nifti mask with identity affine with 1s in the center.""" 

384 return _img_mask_eye() 

385 

386 

387# ------------------------ 4D IMAGES ------------------------# 

388 

389 

390def _img_4d_zeros(shape=None, affine=None): 

391 """Return a default zeros filled 4D Nifti1Image (identity affine). 

392 

393 Mostly used for set up in other fixtures in other testing modules. 

394 """ 

395 if shape is None: 

396 shape = _shape_4d_default() 

397 if affine is None: 

398 affine = _affine_eye() 

399 return _img_zeros(shape, affine) 

400 

401 

402def _img_4d_rand_eye(): 

403 """Return a default random filled 4D Nifti1Image (identity affine).""" 

404 data = _rng().random(_shape_4d_default()) 

405 return Nifti1Image(data, _affine_eye()) 

406 

407 

408def _img_4d_rand_eye_medium(): 

409 """Return a random 4D Nifti1Image (identity affine, many volumes).""" 

410 data = _rng().random(_shape_4d_medium()) 

411 return Nifti1Image(data, _affine_eye()) 

412 

413 

414def _img_4d_mni(shape=None, affine=None): 

415 if shape is None: 

416 shape = _shape_4d_default() 

417 if affine is None: 

418 affine = _affine_mni() 

419 return Nifti1Image(_rng().uniform(size=shape), affine=affine) 

420 

421 

422@pytest.fixture 

423def img_4d_zeros_eye(): 

424 """Return a default zeros filled 4D Nifti1Image (identity affine).""" 

425 return _img_4d_zeros() 

426 

427 

428@pytest.fixture 

429def img_4d_ones_eye(): 

430 """Return a default ones filled 4D Nifti1Image (identity affine).""" 

431 return _img_ones(_shape_4d_default(), _affine_eye()) 

432 

433 

434@pytest.fixture 

435def img_4d_rand_eye(): 

436 """Return a default random filled 4D Nifti1Image (identity affine).""" 

437 return _img_4d_rand_eye() 

438 

439 

440@pytest.fixture 

441def img_4d_mni(): 

442 """Return a default random filled 4D Nifti1Image.""" 

443 return _img_4d_mni() 

444 

445 

446@pytest.fixture 

447def img_4d_rand_eye_medium(): 

448 """Return a default random filled 4D Nifti1Image of medium length.""" 

449 return _img_4d_rand_eye_medium() 

450 

451 

452@pytest.fixture 

453def img_4d_long_mni(rng, shape_4d_long, affine_mni): 

454 """Return a default random filled long 4D Nifti1Image.""" 

455 return Nifti1Image(rng.uniform(size=shape_4d_long), affine=affine_mni) 

456 

457 

458# ------------------------ ATLAS, LABELS, MAPS ------------------------# 

459 

460 

461@pytest.fixture() 

462def img_atlas(shape_3d_default, affine_mni): 

463 """Return an atlas and its labels.""" 

464 atlas = np.ones(shape_3d_default, dtype="int32") 

465 atlas[2:5, :, :] = 2 

466 atlas[5:8, :, :] = 3 

467 return { 

468 "img": Nifti1Image(atlas, affine_mni), 

469 "labels": { 

470 "gm": 1, 

471 "wm": 2, 

472 "csf": 3, 

473 }, 

474 } 

475 

476 

477def _n_regions(): 

478 """Return a default number of regions for maps.""" 

479 return 9 

480 

481 

482@pytest.fixture 

483def n_regions(): 

484 """Return a default number of regions for maps.""" 

485 return _n_regions() 

486 

487 

488def _img_maps(n_regions=None): 

489 """Generate a default map image.""" 

490 if n_regions is None: 

491 n_regions = _n_regions() 

492 return generate_maps( 

493 shape=_shape_3d_default(), n_regions=n_regions, affine=_affine_eye() 

494 )[0] 

495 

496 

497@pytest.fixture 

498def img_maps(): 

499 """Generate fixture for default map image.""" 

500 return _img_maps() 

501 

502 

503def _img_labels(): 

504 """Generate fixture for default label image. 

505 

506 DO NOT CHANGE n_regions (some tests expect this value). 

507 """ 

508 return generate_labeled_regions( 

509 shape=_shape_3d_default(), 

510 affine=_affine_eye(), 

511 n_regions=_n_regions(), 

512 ) 

513 

514 

515@pytest.fixture 

516def img_labels(): 

517 """Generate fixture for default label image.""" 

518 return _img_labels() 

519 

520 

521@pytest.fixture 

522def length(): 

523 """Return a default length for 4D images.""" 

524 return 10 

525 

526 

527@pytest.fixture 

528def img_fmri(shape_3d_default, affine_eye, length): 

529 """Return a default length for fmri images.""" 

530 return generate_fake_fmri( 

531 shape_3d_default, affine=affine_eye, length=length 

532 )[0] 

533 

534 

535# ------------------------ SURFACE ------------------------# 

536@pytest.fixture 

537def single_mesh(rng): 

538 """Create random coordinates and faces for a single mesh. 

539 

540 This does not generate meaningful surfaces. 

541 """ 

542 coords = rng.random((20, 3)) 

543 faces = rng.integers(coords.shape[0], size=(30, 3)) 

544 return [coords, faces] 

545 

546 

547@pytest.fixture 

548def in_memory_mesh(single_mesh): 

549 """Create a random InMemoryMesh. 

550 

551 This does not generate meaningful surfaces. 

552 """ 

553 coords, faces = single_mesh 

554 return InMemoryMesh(coordinates=coords, faces=faces) 

555 

556 

557def _make_mesh(): 

558 """Create a sample mesh with two parts: left and right, and total of 

559 9 vertices and 10 faces. 

560 

561 The left part is a tetrahedron with four vertices and four faces. 

562 The right part is a pyramid with five vertices and six faces. 

563 """ 

564 left_coords = np.asarray([[0.0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]) 

565 left_faces = np.asarray([[1, 0, 2], [0, 1, 3], [0, 3, 2], [1, 2, 3]]) 

566 right_coords = ( 

567 np.asarray([[0.0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1]]) 

568 + 2.0 

569 ) 

570 right_faces = np.asarray( 

571 [ 

572 [0, 1, 4], 

573 [0, 3, 1], 

574 [1, 3, 2], 

575 [1, 2, 4], 

576 [2, 3, 4], 

577 [0, 4, 3], 

578 ] 

579 ) 

580 return PolyMesh( 

581 left=InMemoryMesh(left_coords, left_faces), 

582 right=InMemoryMesh(right_coords, right_faces), 

583 ) 

584 

585 

586@pytest.fixture() 

587def surf_mesh(): 

588 """Return _make_mesh as a function allowing it to be used as a fixture.""" 

589 return _make_mesh() 

590 

591 

592def _make_surface_img(n_samples=1): 

593 mesh = _make_mesh() 

594 data = {} 

595 for i, (key, val) in enumerate(mesh.parts.items()): 

596 data_shape = (val.n_vertices, n_samples) 

597 data_part = ( 

598 np.arange(np.prod(data_shape)).reshape(data_shape[::-1]) 

599 ) * 10**i 

600 data[key] = data_part.astype(float).T 

601 return SurfaceImage(mesh, data) 

602 

603 

604@pytest.fixture 

605def surf_img_2d(): 

606 """Create a sample surface image using the sample mesh. 

607 This will add some random data to the vertices of the mesh. 

608 The shape of the data will be (n_vertices, n_samples). 

609 n_samples by default is 1. 

610 """ 

611 return _make_surface_img 

612 

613 

614@pytest.fixture 

615def surf_img_1d(): 

616 """Create a sample surface image using the sample mesh. 

617 This will add some random data to the vertices of the mesh. 

618 The shape of the data will be (n_vertices,). 

619 """ 

620 img = _make_surface_img(n_samples=1) 

621 img.data.parts["left"] = np.squeeze(img.data.parts["left"]) 

622 img.data.parts["right"] = np.squeeze(img.data.parts["right"]) 

623 return img 

624 

625 

626def _make_surface_mask(n_zeros=4): 

627 mesh = _make_mesh() 

628 data = {} 

629 for key, val in mesh.parts.items(): 

630 data_shape = (val.n_vertices, 1) 

631 data_part = np.ones(data_shape, dtype=int) 

632 for i in range(n_zeros // 2): 

633 data_part[i, ...] = 0 

634 data_part = data_part.astype(bool) 

635 data[key] = data_part 

636 return SurfaceImage(mesh, data) 

637 

638 

639def _surf_mask_1d(): 

640 """Create a sample surface mask using the sample mesh. 

641 This will create a mask with n_zeros zeros (default is 4) and the 

642 rest ones. 

643 

644 The shape of the data will be (n_vertices,). 

645 """ 

646 mask = _make_surface_mask() 

647 mask.data.parts["left"] = np.squeeze(mask.data.parts["left"]) 

648 mask.data.parts["right"] = np.squeeze(mask.data.parts["right"]) 

649 

650 return mask 

651 

652 

653@pytest.fixture 

654def surf_mask_1d(): 

655 """Create a sample surface mask using the sample mesh. 

656 This will create a mask with n_zeros zeros (default is 4) and the 

657 rest ones. 

658 

659 The shape of the data will be (n_vertices,). 

660 """ 

661 return _surf_mask_1d() 

662 

663 

664@pytest.fixture 

665def surf_mask_2d(): 

666 """Create a sample surface mask using the sample mesh. 

667 This will create a mask with n_zeros zeros (default is 4) and the 

668 rest ones. 

669 

670 The shape of the data will be (n_vertices, 1). Could be useful for testing 

671 input validation where we throw an error if the mask is not 1D. 

672 """ 

673 return _make_surface_mask 

674 

675 

676@pytest.fixture 

677def surf_label_img(surf_mesh): 

678 """Return a sample surface label image using the sample mesh. 

679 Has two regions with values 0 and 1 respectively. 

680 """ 

681 data = { 

682 "left": np.asarray([0, 0, 1, 1]), 

683 "right": np.asarray([1, 1, 0, 0, 0]), 

684 } 

685 return SurfaceImage(surf_mesh, data) 

686 

687 

688@pytest.fixture 

689def surf_three_labels_img(surf_mesh): 

690 """Return a sample surface label image using the sample mesh. 

691 Has 3 regions with values 0, 1 and 2. 

692 """ 

693 data = { 

694 "left": np.asarray([0, 0, 1, 1]), 

695 "right": np.asarray([1, 1, 0, 2, 0]), 

696 } 

697 return SurfaceImage(surf_mesh, data) 

698 

699 

700def _surf_maps_img(): 

701 """Return a sample surface map image using the sample mesh. 

702 Has 6 regions in total: 3 in both, 1 only in left and 2 only in right. 

703 Later we multiply the data with random "probability" values to make it 

704 more realistic. 

705 """ 

706 data = { 

707 "left": np.asarray( 

708 [ 

709 [1, 1, 0, 1, 0, 0], 

710 [0, 1, 1, 1, 0, 0], 

711 [1, 0, 1, 1, 0, 0], 

712 [1, 1, 1, 0, 0, 0], 

713 ] 

714 ), 

715 "right": np.asarray( 

716 [ 

717 [1, 0, 0, 0, 1, 1], 

718 [1, 1, 0, 0, 1, 1], 

719 [0, 1, 1, 0, 1, 1], 

720 [1, 1, 1, 0, 0, 1], 

721 [0, 0, 1, 0, 0, 1], 

722 ] 

723 ), 

724 } 

725 # multiply with random "probability" values 

726 data = { 

727 part: data[part] * _rng().random(data[part].shape) for part in data 

728 } 

729 return SurfaceImage(_make_mesh(), data) 

730 

731 

732@pytest.fixture 

733def surf_maps_img(): 

734 """Return a sample surface map as fixture.""" 

735 return _surf_maps_img() 

736 

737 

738def _flip_surf_img_parts(poly_obj): 

739 """Flip hemispheres of a surface image data or mesh.""" 

740 keys = list(poly_obj.parts.keys()) 

741 keys = [keys[-1]] + keys[:-1] 

742 return dict(zip(keys, poly_obj.parts.values())) 

743 

744 

745@pytest.fixture 

746def flip_surf_img_parts(): 

747 """Flip hemispheres of a surface image data or mesh.""" 

748 return _flip_surf_img_parts 

749 

750 

751def _flip_surf_img(img): 

752 """Flip hemispheres of a surface image.""" 

753 return SurfaceImage( 

754 _flip_surf_img_parts(img.mesh), _flip_surf_img_parts(img.data) 

755 ) 

756 

757 

758@pytest.fixture 

759def flip_surf_img(): 

760 """Flip hemispheres of a surface image.""" 

761 return _flip_surf_img 

762 

763 

764def _drop_surf_img_part(img, part_name="right"): 

765 """Remove one hemisphere from a SurfaceImage.""" 

766 mesh_parts = img.mesh.parts.copy() 

767 mesh_parts.pop(part_name) 

768 data_parts = img.data.parts.copy() 

769 data_parts.pop(part_name) 

770 return SurfaceImage(mesh_parts, data_parts) 

771 

772 

773@pytest.fixture 

774def drop_surf_img_part(): 

775 """Remove one hemisphere from a SurfaceImage.""" 

776 return _drop_surf_img_part 

777 

778 

779def _make_surface_img_and_design(n_samples=5): 

780 des = pd.DataFrame( 

781 _rng().standard_normal((n_samples, 3)), columns=["", "", ""] 

782 ) 

783 return _make_surface_img(n_samples), des 

784 

785 

786@pytest.fixture() 

787def surface_glm_data(): 

788 """Create a surface image and design matrix for testing.""" 

789 return _make_surface_img_and_design 

790 

791 

792# ------------------------ PLOTTING ------------------------# 

793 

794 

795@pytest.fixture(scope="function") 

796def matplotlib_pyplot(): 

797 """Set up and teardown fixture for matplotlib. 

798 

799 This fixture checks if we can import matplotlib. If not, the tests will be 

800 skipped. Otherwise, we close the figures before and after running the 

801 functions. 

802 

803 Returns 

804 ------- 

805 pyplot : module 

806 The ``matplotlib.pyplot`` module. 

807 """ 

808 pyplot = pytest.importorskip("matplotlib.pyplot") 

809 pyplot.close("all") 

810 yield pyplot 

811 pyplot.close("all") 

812 

813 

814@pytest.fixture(scope="function") 

815def plotly(): 

816 """Check if we can import plotly. 

817 

818 If not, the tests will be skipped. 

819 

820 Returns 

821 ------- 

822 plotly : module 

823 The ``plotly`` module. 

824 """ 

825 yield pytest.importorskip( 

826 "plotly", reason="Plotly is not installed; required to run the tests!" 

827 ) 

828 

829 

830@pytest.fixture 

831def transparency_image(rng, affine_mni): 

832 """Return 3D image to use as transparency image. 

833 

834 Make sure that values are not just between 0 and 1. 

835 """ 

836 data_positive = np.zeros((7, 7, 3)) 

837 data_rng = rng.random((7, 7, 3)) * 10 - 5 

838 data_positive[1:-1, 2:-1, 1:] = data_rng[1:-1, 2:-1, 1:] 

839 return Nifti1Image(data_positive, affine_mni)