Coverage for nilearn/plotting/surface/tests/test_surf_plotting.py: 0%

368 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-16 12:32 +0200

1"""Test nilearn.plotting.surface.surf_plotting functions.""" 

2 

3# ruff: noqa: ARG001 

4 

5import re 

6import tempfile 

7 

8import numpy as np 

9import pandas as pd 

10import pytest 

11from numpy.testing import assert_array_equal 

12 

13from nilearn._utils.exceptions import MeshDimensionError 

14from nilearn._utils.helpers import ( 

15 is_kaleido_installed, 

16 is_plotly_installed, 

17) 

18from nilearn.datasets import fetch_surf_fsaverage 

19from nilearn.plotting import ( 

20 plot_img_on_surf, 

21 plot_surf, 

22 plot_surf_contours, 

23 plot_surf_roi, 

24 plot_surf_stat_map, 

25) 

26 

27 

28@pytest.fixture 

29def surf_roi_data(rng, in_memory_mesh): 

30 roi_map = np.zeros((in_memory_mesh.n_vertices, 1)) 

31 roi_idx = rng.integers(0, in_memory_mesh.n_vertices, size=10) 

32 roi_map[roi_idx] = 1 

33 return roi_map 

34 

35 

36@pytest.mark.parametrize( 

37 "fn", 

38 [ 

39 plot_surf, 

40 plot_surf_stat_map, 

41 plot_surf_contours, 

42 plot_surf_roi, 

43 ], 

44) 

45def test_check_surface_plotting_inputs_error_mesh_and_data_none(fn): 

46 """Fail if no mesh or data is passed.""" 

47 with pytest.raises(TypeError, match="cannot both be None"): 

48 fn(None, None) 

49 

50 

51@pytest.mark.parametrize( 

52 "fn", 

53 [ 

54 plot_surf, 

55 plot_surf_stat_map, 

56 plot_img_on_surf, 

57 plot_surf_roi, 

58 ], 

59) 

60def test_check_surface_plotting_inputs_error_negative_threshold( 

61 fn, in_memory_mesh 

62): 

63 """Fail if negative threshold is passed.""" 

64 with pytest.raises(ValueError, match="Threshold should be a"): 

65 fn(in_memory_mesh, threshold=-1) 

66 

67 

68@pytest.mark.parametrize( 

69 "fn", 

70 [ 

71 plot_surf, 

72 plot_surf_contours, 

73 plot_surf_stat_map, 

74 plot_surf_roi, 

75 ], 

76) 

77@pytest.mark.parametrize("hemi", ["left", "right", "both"]) 

78def test_check_surface_plotting_inputs_single_hemi_data( 

79 in_memory_mesh, fn, hemi 

80): 

81 """Smoke test when single hemi data is passed.""" 

82 parcellation = np.zeros((in_memory_mesh.n_vertices,)) 

83 parcellation[in_memory_mesh.faces[3]] = 1 

84 fn(in_memory_mesh, parcellation, hemi=hemi) 

85 

86 

87def test_check_surface_plotting_inputs_errors(): 

88 """Fail if mesh is None and data is not a SurfaceImage.""" 

89 with pytest.raises(TypeError, match="must be a SurfaceImage instance"): 

90 plot_surf(surf_map=1, surf_mesh=None) 

91 with pytest.raises(TypeError, match="must be a SurfaceImage instance"): 

92 plot_surf_stat_map(stat_map=1, surf_mesh=None) 

93 with pytest.raises(TypeError, match="must be a SurfaceImage instance"): 

94 plot_surf_contours(roi_map=1, surf_mesh=None) 

95 with pytest.raises(TypeError, match="must be a SurfaceImage instance"): 

96 plot_surf_roi(roi_map=1, surf_mesh=None) 

97 

98 

99def test_plot_surf_engine_error(in_memory_mesh): 

100 """Test error if unknown engine is specified.""" 

101 with pytest.raises(ValueError, match="Unknown plotting engine"): 

102 plot_surf(in_memory_mesh, engine="foo") 

103 

104 

105@pytest.mark.skipif( 

106 is_plotly_installed(), 

107 reason="This test is run only if plotly is not installed.", 

108) 

109def test_plot_surf_engine_error_plotly_not_installed(in_memory_mesh): 

110 """Test error if plotly is not installed but specified as engine.""" 

111 with pytest.raises(ImportError, match="Using engine"): 

112 plot_surf(in_memory_mesh, engine="plotly") 

113 

114 

115def test_plot_surf(plt, engine, tmp_path, in_memory_mesh, bg_map): 

116 """Test nilearn.plotting.surface.surf_plotting.plot_surf function with 

117 available engine backends. 

118 """ 

119 # to avoid extra warnings 

120 alpha = None 

121 cbar_vmin = None 

122 cbar_vmax = None 

123 if engine == "matplotlib": 

124 alpha = 0.5 

125 cbar_vmin = 0 

126 cbar_vmax = 150 

127 

128 # Plot mesh only 

129 plot_surf(in_memory_mesh, engine=engine) 

130 

131 # Plot mesh with background 

132 plot_surf(in_memory_mesh, bg_map=bg_map, engine=engine) 

133 plot_surf(in_memory_mesh, bg_map=bg_map, darkness=0.5, engine=engine) 

134 plot_surf( 

135 in_memory_mesh, 

136 bg_map=bg_map, 

137 alpha=alpha, 

138 output_file=tmp_path / "tmp.png", 

139 engine=engine, 

140 ) 

141 

142 # Plot with colorbar 

143 plot_surf(in_memory_mesh, bg_map=bg_map, colorbar=True, engine=engine) 

144 plot_surf( 

145 in_memory_mesh, 

146 bg_map=bg_map, 

147 colorbar=True, 

148 cbar_vmin=cbar_vmin, 

149 cbar_vmax=cbar_vmax, 

150 cbar_tick_format="%i", 

151 engine=engine, 

152 ) 

153 

154 

155@pytest.mark.parametrize("view", ["anterior", "posterior"]) 

156@pytest.mark.parametrize("hemi", ["left", "right", "both"]) 

157def test_plot_surf_hemi_views(plt, engine, in_memory_mesh, hemi, view, bg_map): 

158 """Check plotting view and hemispheres.""" 

159 plot_surf( 

160 in_memory_mesh, bg_map=bg_map, hemi=hemi, view=view, engine=engine 

161 ) 

162 

163 

164@pytest.mark.parametrize("hemi", ["left", "right", "both"]) 

165def test_plot_surf_swap_hemi(plt, engine, surf_img_1d, hemi, flip_surf_img): 

166 """Check error is raised if background image is incompatible.""" 

167 with pytest.raises( 

168 MeshDimensionError, 

169 match="Number of vertices do not match for between meshes.", 

170 ): 

171 plot_surf( 

172 surf_map=surf_img_1d, 

173 bg_map=flip_surf_img(surf_img_1d), 

174 hemi=hemi, 

175 surf_mesh=None, 

176 engine=engine, 

177 ) 

178 

179 

180def test_plot_surf_error(plt, engine, rng, in_memory_mesh): 

181 """Check error if invalid parameters values are specified to 

182 nilearn.plotting.surface.surf_plotting.plot_surf. 

183 """ 

184 # Wrong inputs for view or hemi 

185 with pytest.raises(ValueError, match="Invalid view definition"): 

186 plot_surf(in_memory_mesh, view="middle", engine=engine) 

187 with pytest.raises(ValueError, match="Invalid hemispheres definition"): 

188 plot_surf(in_memory_mesh, hemi="lft", engine=engine) 

189 

190 # Wrong size of background image 

191 with pytest.raises( 

192 ValueError, match="bg_map does not have the same number of vertices" 

193 ): 

194 plot_surf( 

195 in_memory_mesh, 

196 bg_map=rng.standard_normal(size=in_memory_mesh.n_vertices - 1), 

197 engine=engine, 

198 ) 

199 

200 # Wrong size of surface data 

201 with pytest.raises( 

202 ValueError, match="surf_map does not have the same number of vertices" 

203 ): 

204 plot_surf( 

205 in_memory_mesh, 

206 surf_map=rng.standard_normal(size=in_memory_mesh.n_vertices + 1), 

207 engine=engine, 

208 ) 

209 

210 with pytest.raises( 

211 ValueError, match="'surf_map' can only have one dimension" 

212 ): 

213 plot_surf( 

214 in_memory_mesh, 

215 surf_map=rng.standard_normal(size=(in_memory_mesh.n_vertices, 2)), 

216 engine=engine, 

217 ) 

218 

219 

220@pytest.mark.parametrize( 

221 "kwargs", [{"symmetric_cmap": True}, {"title_font_size": 18}] 

222) 

223def test_plot_surf_warnings_not_implemented_in_matplotlib( 

224 matplotlib_pyplot, kwargs, in_memory_mesh, bg_map 

225): 

226 """Test if nilearn.plotting.surface.surf_plotting.plot_surf raises error 

227 when a parameter that is not supported by matplotlib is specified with a 

228 value other than None. 

229 """ 

230 with pytest.warns( 

231 UserWarning, match="is not implemented for the matplotlib engine" 

232 ): 

233 plot_surf( 

234 in_memory_mesh, 

235 surf_map=bg_map, 

236 engine="matplotlib", 

237 **kwargs, 

238 ) 

239 

240 

241@pytest.mark.parametrize("kwargs", [{"avg_method": "mean"}, {"alpha": "auto"}]) 

242def test_plot_surf_warnings_not_implemented_in_plotly( 

243 plotly, kwargs, in_memory_mesh, bg_map 

244): 

245 """Test if nilearn.plotting.surface.surf_plotting.plot_surf raises error 

246 when a parameter that is not supported by plotly is specified with a 

247 value other than None. 

248 """ 

249 with pytest.warns( 

250 UserWarning, match="is not implemented for the plotly engine" 

251 ): 

252 plot_surf( 

253 in_memory_mesh, 

254 surf_map=bg_map, 

255 engine="plotly", 

256 **kwargs, 

257 ) 

258 

259 

260@pytest.mark.skipif( 

261 is_kaleido_installed(), 

262 reason=("This test only runs if Plotly is installed, but not kaleido."), 

263) 

264def test_plot_surf_error_when_kaleido_missing( 

265 plotly, tmp_path, in_memory_mesh, bg_map 

266): 

267 """Test if nilearn.plotting.surface.surf_plotting.plot_surf raises 

268 ImportError when engine is 'plotly' and kaleido is not installed. 

269 """ 

270 with pytest.raises(ImportError, match="Saving figures"): 

271 # Plot with non None output file 

272 plot_surf( 

273 in_memory_mesh, 

274 bg_map=bg_map, 

275 engine="plotly", 

276 output_file=tmp_path / "tmp.png", 

277 ) 

278 

279 

280def test_plot_surf_avg_method(matplotlib_pyplot, in_memory_mesh, bg_map): 

281 """Test nilearn.plotting.surface.surf_plotting.plot_surf for valid 

282 values of avg_method. 

283 """ 

284 # Plot with avg_method 

285 # Test all built-in methods and check 

286 faces = in_memory_mesh.faces 

287 ENGINE = "matplotlib" 

288 

289 for method in ["mean", "median", "min", "max"]: 

290 display = plot_surf( 

291 in_memory_mesh, 

292 surf_map=bg_map, 

293 avg_method=method, 

294 engine=ENGINE, 

295 ) 

296 if method == "mean": 

297 agg_faces = np.mean(bg_map[faces], axis=1) 

298 elif method == "median": 

299 agg_faces = np.median(bg_map[faces], axis=1) 

300 elif method == "min": 

301 agg_faces = np.min(bg_map[faces], axis=1) 

302 elif method == "max": 

303 agg_faces = np.max(bg_map[faces], axis=1) 

304 vmin = np.min(agg_faces) 

305 vmax = np.max(agg_faces) 

306 agg_faces -= vmin 

307 agg_faces /= vmax - vmin 

308 cmap = matplotlib_pyplot.get_cmap( 

309 matplotlib_pyplot.rcParamsDefault["image.cmap"] 

310 ) 

311 assert_array_equal( 

312 cmap(agg_faces), 

313 display._axstack.as_list()[0].collections[0]._facecolors, 

314 ) 

315 

316 # Try custom avg_method 

317 def custom_avg_function(vertices): 

318 return vertices[0] * vertices[1] * vertices[2] 

319 

320 plot_surf( 

321 in_memory_mesh, 

322 surf_map=bg_map, 

323 avg_method=custom_avg_function, 

324 engine=ENGINE, 

325 ) 

326 

327 

328def test_plot_surf_avg_method_errors( 

329 matplotlib_pyplot, in_memory_mesh, bg_map 

330): 

331 """Test nilearn.plotting.surface.surf_plotting.plot_surf for invalid 

332 values of avg_method. 

333 """ 

334 ENGINE = "matplotlib" 

335 with pytest.raises( 

336 ValueError, 

337 match=( 

338 "Array computed with the custom " 

339 "function from avg_method does " 

340 "not have the correct shape" 

341 ), 

342 ): 

343 

344 def custom_avg_function(vertices): 

345 return [vertices[0] * vertices[1], vertices[2]] 

346 

347 plot_surf( 

348 in_memory_mesh, 

349 surf_map=bg_map, 

350 avg_method=custom_avg_function, 

351 engine=ENGINE, 

352 ) 

353 

354 with pytest.raises( 

355 ValueError, 

356 match=re.escape( 

357 "avg_method should be either " 

358 "['mean', 'median', 'max', 'min'] " 

359 "or a custom function" 

360 ), 

361 ): 

362 custom_avg_function = {} 

363 

364 plot_surf( 

365 in_memory_mesh, 

366 surf_map=bg_map, 

367 avg_method=custom_avg_function, 

368 engine=ENGINE, 

369 ) 

370 

371 plot_surf( 

372 in_memory_mesh, 

373 surf_map=bg_map, 

374 avg_method="foo", 

375 engine=ENGINE, 

376 ) 

377 

378 with pytest.raises( 

379 ValueError, 

380 match=re.escape( 

381 "Array computed with the custom function " 

382 "from avg_method should be an array of " 

383 "numbers (int or float)" 

384 ), 

385 ): 

386 

387 def custom_avg_function(vertices): 

388 return "string" 

389 

390 plot_surf( 

391 in_memory_mesh, 

392 surf_map=bg_map, 

393 avg_method=custom_avg_function, 

394 engine=ENGINE, 

395 ) 

396 

397 

398def test_plot_surf_with_title(matplotlib_pyplot, in_memory_mesh, bg_map): 

399 """Test if figure title is set correctly in 

400 nilearn.plotting.surface.surf_plotting.plot_surf. 

401 """ 

402 display = plot_surf( 

403 in_memory_mesh, bg_map=bg_map, title="Test title", engine="matplotlib" 

404 ) 

405 

406 assert len(display.axes) == 1 

407 assert display.axes[0].title._text == "Test title" 

408 

409 

410def test_surface_plotting_axes_error(matplotlib_pyplot, surf_img_1d): 

411 """Test error msg for invalid axes.""" 

412 figure, axes = matplotlib_pyplot.subplots() 

413 with pytest.raises(AttributeError, match="the projection must be '3d'"): 

414 plot_surf_stat_map(stat_map=surf_img_1d, axes=axes) 

415 

416 

417def test_plot_surf_contours( 

418 matplotlib_pyplot, in_memory_mesh, parcellation, surf_mask_1d 

419): 

420 """Test nilearn.plotting.surface.plot_surf_contours for valid input 

421 values. 

422 """ 

423 plot_surf_contours(in_memory_mesh, parcellation) 

424 plot_surf_contours(in_memory_mesh, parcellation, levels=[1, 2]) 

425 plot_surf_contours( 

426 in_memory_mesh, parcellation, levels=[1, 2], cmap="gist_ncar" 

427 ) 

428 

429 

430def test_plot_surf_contour_roi_map_as_surface_image( 

431 matplotlib_pyplot, surf_mesh, surf_mask_1d 

432): 

433 """Check that mesh can be PolyMesh and roi_map can be a SurfaceImage.""" 

434 plot_surf_contours(surf_mesh, roi_map=surf_mask_1d, hemi="both") 

435 

436 

437def test_plot_surf_contours_legend( 

438 matplotlib_pyplot, in_memory_mesh, parcellation 

439): 

440 """Test nilearn.plotting.surface.plot_surf_contours creates figure legend 

441 when `legend=True`. 

442 """ 

443 fig = plot_surf_contours( 

444 in_memory_mesh, 

445 parcellation, 

446 legend=True, 

447 ) 

448 assert fig.legends is not None 

449 

450 

451def test_plot_surf_contours_colors( 

452 matplotlib_pyplot, in_memory_mesh, parcellation 

453): 

454 """Test nilearn.plotting.surface.plot_surf_contours for different inputs as 

455 `colors`. 

456 """ 

457 plot_surf_contours( 

458 in_memory_mesh, parcellation, levels=[1, 2], colors=["r", "g"] 

459 ) 

460 plot_surf_contours( 

461 in_memory_mesh, 

462 parcellation, 

463 levels=[1, 2], 

464 labels=["1", "2"], 

465 colors=["r", "g"], 

466 ) 

467 plot_surf_contours( 

468 in_memory_mesh, 

469 parcellation, 

470 levels=[1, 2], 

471 colors=[[0, 0, 0, 1], [1, 1, 1, 1]], 

472 ) 

473 

474 

475def test_plot_surf_contours_axis_title( 

476 matplotlib_pyplot, in_memory_mesh, parcellation 

477): 

478 """Test nilearn.plotting.surface.plot_surf_contours for axis title.""" 

479 fig = plot_surf(in_memory_mesh) 

480 plot_surf_contours(in_memory_mesh, parcellation, figure=fig) 

481 display = plot_surf_contours( 

482 in_memory_mesh, 

483 parcellation, 

484 levels=[1, 2], 

485 labels=["1", "2"], 

486 colors=["r", "g"], 

487 legend=True, 

488 title="title", 

489 figure=fig, 

490 ) 

491 # Non-regression assertion: we switched from _suptitle to axis title 

492 assert display._suptitle is None 

493 assert display.axes[0].get_title() == "title" 

494 

495 fig = plot_surf(in_memory_mesh, title="title 2") 

496 display = plot_surf_contours( 

497 in_memory_mesh, 

498 parcellation, 

499 levels=[1, 2], 

500 labels=["1", "2"], 

501 colors=["r", "g"], 

502 legend=True, 

503 figure=fig, 

504 ) 

505 # Non-regression assertion: we switched from _suptitle to axis title 

506 assert display._suptitle is None 

507 assert display.axes[0].get_title() == "title 2" 

508 with tempfile.NamedTemporaryFile() as tmp_file: 

509 plot_surf_contours( 

510 in_memory_mesh, parcellation, output_file=tmp_file.name 

511 ) 

512 

513 

514def test_plot_surf_contours_fig_axes( 

515 matplotlib_pyplot, in_memory_mesh, parcellation 

516): 

517 """Test nilearn.plotting.surface.surf_plotting.plot_surf_contours with 

518 matplotlib figure and axes. 

519 """ 

520 fig, axes = matplotlib_pyplot.subplots( 

521 1, 1, subplot_kw={"projection": "3d"} 

522 ) 

523 plot_surf_contours(in_memory_mesh, parcellation, axes=axes) 

524 plot_surf_contours(in_memory_mesh, parcellation, figure=fig) 

525 

526 

527def test_plot_surf_contours_error( 

528 matplotlib_pyplot, rng, in_memory_mesh, parcellation 

529): 

530 """Test nilearn.plotting.surface.surf_plotting.plot_surf_contours for 

531 invalid parameters. 

532 """ 

533 # we need an invalid parcellation for testing 

534 invalid_parcellation = rng.uniform(size=(in_memory_mesh.n_vertices)) 

535 with pytest.raises( 

536 ValueError, match="Vertices in parcellation do not form region." 

537 ): 

538 plot_surf_contours(in_memory_mesh, invalid_parcellation) 

539 

540 _, axes = matplotlib_pyplot.subplots(1, 1) 

541 with pytest.raises(ValueError, match="Axes must be 3D."): 

542 plot_surf_contours(in_memory_mesh, parcellation, axes=axes) 

543 

544 msg = "All elements of colors .* matplotlib .* RGBA" 

545 with pytest.raises(ValueError, match=msg): 

546 plot_surf_contours( 

547 in_memory_mesh, parcellation, levels=[1, 2], colors=[[1, 2], 3] 

548 ) 

549 

550 msg = "Levels, labels, and colors argument .* same length or None." 

551 with pytest.raises(ValueError, match=msg): 

552 plot_surf_contours( 

553 in_memory_mesh, 

554 parcellation, 

555 levels=[1, 2], 

556 colors=["r"], 

557 labels=["1", "2"], 

558 ) 

559 

560 

561def test_plot_surf_contours_errors_with_plotly_figure(plotly, in_memory_mesh): 

562 """Test that plot_surf_contours raises error when given plotly obj.""" 

563 figure = plot_surf(in_memory_mesh, engine="plotly") 

564 with pytest.raises(ValueError): 

565 plot_surf_contours(in_memory_mesh, np.ones((10,)), figure=figure) 

566 with pytest.raises(ValueError): 

567 plot_surf_contours(in_memory_mesh, np.ones((10,)), axes=figure) 

568 

569 

570def test_plot_surf_stat_map(plt, engine, in_memory_mesh, bg_map): 

571 """Smoke test when stat_map is specified to 

572 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map together with 

573 mesh. 

574 """ 

575 alpha = 1 if engine == "matplotlib" else None 

576 

577 plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, engine=engine) 

578 plot_surf_stat_map( 

579 in_memory_mesh, stat_map=bg_map, alpha=alpha, engine=engine 

580 ) 

581 

582 

583def test_plot_surf_stat_map_with_background( 

584 plt, engine, in_memory_mesh, bg_map 

585): 

586 """Smoke test when background map is specified also as stat_map to 

587 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

588 """ 

589 plot_surf_stat_map( 

590 in_memory_mesh, stat_map=bg_map, bg_map=bg_map, engine=engine 

591 ) 

592 plot_surf_stat_map( 

593 in_memory_mesh, 

594 stat_map=bg_map, 

595 bg_map=bg_map, 

596 bg_on_data=True, 

597 darkness=0.5, 

598 engine=engine, 

599 ) 

600 

601 

602def test_plot_surf_stat_map_with_title(plt, engine, in_memory_mesh, bg_map): 

603 """Test if nilearn.plotting.surface.surf_plotting.plot_surf_stat_map adds 

604 title when specified. 

605 """ 

606 display = plot_surf_stat_map( 

607 in_memory_mesh, stat_map=bg_map, title="Stat map title" 

608 ) 

609 assert display.axes[0].title._text == "Stat map title" 

610 

611 

612def test_plot_surf_stat_map_with_threshold( 

613 plt, engine, in_memory_mesh, bg_map 

614): 

615 """Smoke test when threshold is specified to 

616 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

617 """ 

618 plot_surf_stat_map( 

619 in_memory_mesh, 

620 stat_map=bg_map, 

621 threshold=0.3, 

622 engine=engine, 

623 ) 

624 

625 

626def test_plot_surf_stat_map_vmax(plt, engine, in_memory_mesh, bg_map): 

627 """Smoke test when vmax is specified to 

628 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

629 """ 

630 plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, vmax=5, engine=engine) 

631 

632 

633def test_plot_surf_stat_map_colormap(plt, engine, in_memory_mesh, bg_map): 

634 """Smoke test when colormap is specified to 

635 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

636 """ 

637 plot_surf_stat_map( 

638 in_memory_mesh, stat_map=bg_map, cmap="cubehelix", engine=engine 

639 ) 

640 

641 

642def test_plot_surf_stat_map_error(in_memory_mesh, bg_map): 

643 """Test if nilearn.plotting.surface.surf_plotting.plot_surf_stat_map 

644 raises error with wrong size of stat map data. 

645 """ 

646 # Wrong size of stat map data 

647 with pytest.raises( 

648 ValueError, match="surf_map does not have the same number of vertices" 

649 ): 

650 plot_surf_stat_map( 

651 in_memory_mesh, stat_map=np.hstack((bg_map, bg_map)) 

652 ) 

653 

654 with pytest.raises( 

655 ValueError, match="'surf_map' can only have one dimension" 

656 ): 

657 plot_surf_stat_map( 

658 in_memory_mesh, stat_map=np.vstack((bg_map, bg_map)).T 

659 ) 

660 

661 

662def test_plot_surf_stat_map_colorbar_tick(plotly, in_memory_mesh, bg_map): 

663 """Smoke test when colorbar tick format with plotly engine is specified to 

664 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

665 """ 

666 plot_surf_stat_map( 

667 in_memory_mesh, 

668 stat_map=bg_map, 

669 cbar_tick_format="%.2g", 

670 engine="plotly", 

671 ) 

672 

673 

674@pytest.mark.parametrize("symmetric_cmap", [True, False, None]) 

675def test_plot_surf_stat_map_symmetric_cmap_plotly( 

676 plotly, in_memory_mesh, bg_map, symmetric_cmap 

677): 

678 """Smoke test when symmetric_cmap with plotly engine is specified to 

679 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

680 """ 

681 plot_surf_stat_map( 

682 in_memory_mesh, 

683 stat_map=bg_map, 

684 symmetric_cmap=symmetric_cmap, 

685 engine="plotly", 

686 ) 

687 

688 

689def test_plot_surf_stat_map_symmetric_cmap_matplotlib( 

690 matplotlib_pyplot, in_memory_mesh, bg_map 

691): 

692 """Smoke test when symmetric_cmap is specified as None for matplotlib 

693 engine to nilearn.plotting.surface.surf_plotting.plot_surf_stat_map. 

694 """ 

695 plot_surf_stat_map( 

696 in_memory_mesh, 

697 stat_map=bg_map, 

698 symmetric_cmap=None, 

699 engine="matplotlib", 

700 ) 

701 

702 

703@pytest.mark.parametrize("symmetric_cmap", [True, False]) 

704def test_plot_surf_stat_map_symmetric_cmap_matplotlib_error( 

705 matplotlib_pyplot, in_memory_mesh, bg_map, symmetric_cmap 

706): 

707 """Test if 

708 nilearn.plotting.surface.surf_plotting.plot_surf_stat_map raises error when 

709 True or False is specified as symmetric_cmap for matplotlib engine. 

710 """ 

711 with pytest.warns(UserWarning, match="'symmetric_cmap' is not implement"): 

712 plot_surf_stat_map( 

713 in_memory_mesh, 

714 stat_map=bg_map, 

715 symmetric_cmap=symmetric_cmap, 

716 engine="matplotlib", 

717 ) 

718 

719 

720def test_plot_surf_stat_map_matplotlib_specific( 

721 matplotlib_pyplot, in_memory_mesh, bg_map 

722): 

723 """Test nilearn.plotting.surface.surf_plotting.plot_surf_stat_map for 

724 matplotlib engine specific parameters. 

725 """ 

726 # Plot to axes 

727 axes = matplotlib_pyplot.subplots( 

728 ncols=2, subplot_kw={"projection": "3d"} 

729 )[1] 

730 for ax in axes.flatten(): 

731 plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, axes=ax) 

732 axes = matplotlib_pyplot.subplots( 

733 ncols=2, subplot_kw={"projection": "3d"} 

734 )[1] 

735 for ax in axes.flatten(): 

736 plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, axes=ax) 

737 

738 fig = plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, colorbar=False) 

739 

740 assert len(fig.axes) == 1 

741 

742 # symmetric_cbar 

743 fig = plot_surf_stat_map( 

744 in_memory_mesh, stat_map=bg_map, symmetric_cbar=True 

745 ) 

746 fig.canvas.draw() 

747 

748 assert len(fig.axes) == 2 

749 

750 yticklabels = fig.axes[1].get_yticklabels() 

751 first, last = yticklabels[0].get_text(), yticklabels[-1].get_text() 

752 

753 assert float(first) == -float(last) 

754 

755 # no symmetric_cbar 

756 fig = plot_surf_stat_map( 

757 in_memory_mesh, stat_map=bg_map, symmetric_cbar=False 

758 ) 

759 fig.canvas.draw() 

760 

761 assert len(fig.axes) == 2 

762 

763 yticklabels = fig.axes[1].get_yticklabels() 

764 first, last = yticklabels[0].get_text(), yticklabels[-1].get_text() 

765 

766 assert float(first) != -float(last) 

767 

768 # Test handling of nan values in texture data 

769 # Add nan values in the texture 

770 bg_map[2] = np.nan 

771 # Plot the surface stat map 

772 fig = plot_surf_stat_map(in_memory_mesh, stat_map=bg_map) 

773 # Check that the resulting plot facecolors contain no transparent faces 

774 # (last column equals zero) even though the texture contains nan values 

775 tmp = fig._axstack.as_list()[0].collections[0] 

776 

777 assert ( 

778 in_memory_mesh.faces.shape[0] == ((tmp._facecolors[:, 3]) != 0).sum() 

779 ) 

780 

781 

782@pytest.mark.parametrize("colorbar", [True, False]) 

783def test_plot_surf_roi(plt, engine, surface_image_roi, colorbar): 

784 """Smoke test for nilearn.plotting.surface.surf_plotting.plot_surf_roi 

785 for colorbar parameter. 

786 """ 

787 plot_surf_roi( 

788 surface_image_roi.mesh, 

789 roi_map=surface_image_roi, 

790 colorbar=colorbar, 

791 engine=engine, 

792 ) 

793 

794 

795def test_plot_surf_roi_cmap_as_lookup_table(surface_image_roi): 

796 """Test colormap passed as BIDS lookup table.""" 

797 lut = pd.DataFrame( 

798 {"index": [0, 1], "name": ["foo", "bar"], "color": ["#000", "#fff"]} 

799 ) 

800 plot_surf_roi(surface_image_roi.mesh, roi_map=surface_image_roi, cmap=lut) 

801 

802 lut = pd.DataFrame({"index": [0, 1], "name": ["foo", "bar"]}) 

803 with pytest.warns( 

804 UserWarning, match="No 'color' column found in the look-up table." 

805 ): 

806 plot_surf_roi( 

807 surface_image_roi.mesh, roi_map=surface_image_roi, cmap=lut 

808 ) 

809 

810 

811def test_plot_surf_roi_error(engine, rng, in_memory_mesh, surf_roi_data): 

812 """Test for nilearn.plotting.surface.surf_plotting.plot_surf_roi 

813 for invalid parameter values. 

814 """ 

815 # too many axes 

816 with pytest.raises( 

817 ValueError, match="roi_map can only have one dimension but has" 

818 ): 

819 plot_surf_roi( 

820 in_memory_mesh, 

821 roi_map=np.array([surf_roi_data, surf_roi_data]), 

822 engine=engine, 

823 ) 

824 

825 # wrong number of vertices 

826 roi_idx = rng.integers(0, in_memory_mesh.n_vertices, size=5) 

827 with pytest.raises( 

828 ValueError, match="roi_map does not have the same number of vertices" 

829 ): 

830 plot_surf_roi(in_memory_mesh, roi_map=roi_idx, engine=engine) 

831 

832 # negative value in roi map 

833 surf_roi_data[0] = -1 

834 with pytest.warns( 

835 DeprecationWarning, 

836 match="Negative values in roi_map will no longer be allowed", 

837 ): 

838 plot_surf_roi(in_memory_mesh, roi_map=surf_roi_data, engine=engine) 

839 

840 # float value in roi map 

841 surf_roi_data[0] = 1.2 

842 with pytest.warns( 

843 DeprecationWarning, 

844 match="Non-integer values in roi_map will no longer be allowed", 

845 ): 

846 plot_surf_roi(in_memory_mesh, roi_map=surf_roi_data, engine=engine) 

847 

848 

849def test_plot_surf_roi_matplotlib_specific( 

850 matplotlib_pyplot, surface_image_roi 

851): 

852 """Test for nilearn.plotting.surface.surf_plotting.plot_surf_roi 

853 for matplotlib engine specific parameters. 

854 """ 

855 ENGINE = "matplotlib" 

856 # change vmin, vmax 

857 img = plot_surf_roi( 

858 surface_image_roi.mesh, 

859 roi_map=surface_image_roi, 

860 avg_method="median", 

861 cbar_tick_format="%i", 

862 vmin=1.2, 

863 vmax=8.9, 

864 colorbar=True, 

865 engine=ENGINE, 

866 ) 

867 img.canvas.draw() 

868 cbar = img.axes[-1] 

869 cbar_vmin = float(cbar.get_yticklabels()[0].get_text()) 

870 cbar_vmax = float(cbar.get_yticklabels()[-1].get_text()) 

871 

872 assert cbar_vmin == 1.0 

873 assert cbar_vmax == 8.0 

874 

875 img2 = plot_surf_roi( 

876 surface_image_roi.mesh, 

877 roi_map=surface_image_roi, 

878 vmin=1.2, 

879 vmax=8.9, 

880 colorbar=True, 

881 cbar_tick_format="%.2g", 

882 engine=ENGINE, 

883 ) 

884 img2.canvas.draw() 

885 cbar = img2.axes[-1] 

886 cbar_vmin = float(cbar.get_yticklabels()[0].get_text()) 

887 cbar_vmax = float(cbar.get_yticklabels()[-1].get_text()) 

888 

889 assert cbar_vmin == 1.2 

890 assert cbar_vmax == 8.9 

891 

892 

893def test_plot_surf_roi_matplotlib_specific_nan_handling( 

894 matplotlib_pyplot, 

895 surface_image_parcellation, 

896): 

897 """Test for nilearn.plotting.surface.surf_plotting.plot_surf_roi 

898 for NAN handling with matplotlib engine. 

899 """ 

900 # Test nans handling 

901 surface_image_parcellation.data.parts["left"][::2] = np.nan 

902 img = plot_surf_roi( 

903 surface_image_parcellation.mesh, 

904 roi_map=surface_image_parcellation, 

905 engine="matplotlib", 

906 hemi="left", 

907 ) 

908 # Check that the resulting plot facecolors contain no transparent faces 

909 # (last column equals zero) even though the texture contains nan values 

910 tmp = img._axstack.as_list()[0].collections[0] 

911 n_faces = surface_image_parcellation.mesh.parts["left"].faces.shape[0] 

912 

913 assert n_faces == ((tmp._facecolors[:, 3]) != 0).sum() 

914 

915 

916def test_plot_surf_roi_matplotlib_specific_plot_to_axes( 

917 matplotlib_pyplot, surface_image_roi 

918): 

919 """Test plotting directly on some axes.""" 

920 ENGINE = "matplotlib" 

921 

922 plot_surf_roi( 

923 surface_image_roi.mesh, 

924 roi_map=surface_image_roi, 

925 axes=None, 

926 figure=matplotlib_pyplot.gcf(), 

927 engine=ENGINE, 

928 ) 

929 

930 _, ax = matplotlib_pyplot.subplots(subplot_kw={"projection": "3d"}) 

931 

932 with tempfile.NamedTemporaryFile() as tmp_file: 

933 plot_surf_roi( 

934 surface_image_roi.mesh, 

935 roi_map=surface_image_roi, 

936 axes=ax, 

937 figure=None, 

938 output_file=tmp_file.name, 

939 engine=ENGINE, 

940 ) 

941 

942 with tempfile.NamedTemporaryFile() as tmp_file: 

943 plot_surf_roi( 

944 surface_image_roi.mesh, 

945 roi_map=surface_image_roi, 

946 axes=ax, 

947 figure=None, 

948 output_file=tmp_file.name, 

949 colorbar=True, 

950 engine=ENGINE, 

951 ) 

952 

953 

954@pytest.mark.parametrize("colorbar", [True, False]) 

955@pytest.mark.parametrize("cbar_tick_format", ["auto", "%f"]) 

956def test_plot_surf_roi_parcellation_plotly( 

957 plotly, 

958 colorbar, 

959 surface_image_parcellation, 

960 cbar_tick_format, 

961): 

962 """Smoke test for nilearn.plotting.surface.surf_plotting.plot_surf_roi 

963 for plotly parameters. 

964 """ 

965 plot_surf_roi( 

966 surface_image_parcellation.mesh, 

967 roi_map=surface_image_parcellation, 

968 engine="plotly", 

969 colorbar=colorbar, 

970 cbar_tick_format=cbar_tick_format, 

971 ) 

972 

973 

974@pytest.mark.parametrize("avg_method", ["mean", "median"]) 

975@pytest.mark.parametrize("symmetric_cmap", [True, False, None]) 

976def test_plot_surf_roi_default_arguments( 

977 plt, engine, symmetric_cmap, avg_method, surface_image_roi 

978): 

979 """Regression test for https://github.com/nilearn/nilearn/issues/3941.""" 

980 # To avoid extra warnings 

981 if engine == "plotly": 

982 avg_method = None 

983 

984 plot_surf_roi( 

985 surface_image_roi.mesh, 

986 roi_map=surface_image_roi, 

987 engine=engine, 

988 symmetric_cmap=symmetric_cmap, 

989 darkness=None, # to avoid deprecation warning 

990 cmap="RdYlBu_r", 

991 avg_method=avg_method, 

992 ) 

993 

994 

995@pytest.mark.parametrize( 

996 "kwargs", [{"vmin": 2}, {"vmin": 2, "threshold": 5}, {"threshold": 5}] 

997) 

998def test_plot_surf_roi_colorbar_vmin_equal_across_engines( 

999 matplotlib_pyplot, plotly, kwargs, in_memory_mesh 

1000): 

1001 """Regression test for https://github.com/nilearn/nilearn/issues/3944.""" 

1002 roi_map = np.arange(0, len(in_memory_mesh.coordinates)) 

1003 

1004 mpl_plot = plot_surf_roi( 

1005 in_memory_mesh, 

1006 roi_map=roi_map, 

1007 colorbar=True, 

1008 engine="matplotlib", 

1009 **kwargs, 

1010 ) 

1011 plotly_plot = plot_surf_roi( 

1012 in_memory_mesh, 

1013 roi_map=roi_map, 

1014 colorbar=True, 

1015 engine="plotly", 

1016 **kwargs, 

1017 ) 

1018 assert ( 

1019 mpl_plot.axes[-1].get_ylim()[0] == plotly_plot.figure.data[1]["cmin"] 

1020 ) 

1021 

1022 

1023@pytest.mark.parametrize( 

1024 "hemispheres, views", 

1025 [ 

1026 (["right"], ["lateral"]), 

1027 ("left", ["lateral"]), 

1028 (["both"], ["lateral"]), 

1029 (["left", "right"], ["anterior"]), 

1030 (["right"], ["medial", "lateral"]), 

1031 (["left", "right"], ["dorsal", "ventral"]), 

1032 # Check that manually set view angles work. 

1033 (["left", "right"], [(210.0, 90.0), (15.0, -45.0)]), 

1034 ], 

1035) 

1036def test_plot_img_on_surf_hemispheres_and_orientations( 

1037 matplotlib_pyplot, img_3d_mni, hemispheres, views 

1038): 

1039 """Smoke test for nilearn.plotting.surface.plot_img_on_surf for 

1040 combinations of 1D or 2D hemis and orientations. 

1041 """ 

1042 # Check that all combinations of 1D or 2D hemis and orientations work. 

1043 plot_img_on_surf(img_3d_mni, hemispheres=hemispheres, views=views) 

1044 

1045 

1046@pytest.mark.timeout(0) 

1047def test_plot_img_on_surf_colorbar(matplotlib_pyplot, img_3d_mni): 

1048 """Smoke test for nilearn.plotting.surface.plot_img_on_surf colorbar 

1049 parameter. 

1050 """ 

1051 plot_img_on_surf( 

1052 img_3d_mni, 

1053 hemispheres=["right"], 

1054 views=["lateral"], 

1055 colorbar=True, 

1056 vmin=-5, 

1057 vmax=5, 

1058 threshold=3, 

1059 ) 

1060 plot_img_on_surf( 

1061 img_3d_mni, 

1062 hemispheres=["right"], 

1063 views=["lateral"], 

1064 colorbar=True, 

1065 vmin=-1, 

1066 vmax=5, 

1067 symmetric_cbar=False, 

1068 threshold=3, 

1069 ) 

1070 plot_img_on_surf( 

1071 img_3d_mni, hemispheres=["right"], views=["lateral"], colorbar=False 

1072 ) 

1073 plot_img_on_surf( 

1074 img_3d_mni, 

1075 hemispheres=["right"], 

1076 views=["lateral"], 

1077 colorbar=False, 

1078 cmap="roy_big_bl", 

1079 ) 

1080 plot_img_on_surf( 

1081 img_3d_mni, 

1082 hemispheres=["right"], 

1083 views=["lateral"], 

1084 colorbar=True, 

1085 cmap="roy_big_bl", 

1086 vmax=2, 

1087 ) 

1088 

1089 

1090def test_plot_img_on_surf_inflate(matplotlib_pyplot, img_3d_mni): 

1091 """Smoke test for nilearn.plotting.surface.plot_img_on_surf inflate 

1092 parameter. 

1093 """ 

1094 plot_img_on_surf( 

1095 img_3d_mni, hemispheres=["right"], views=["lateral"], inflate=True 

1096 ) 

1097 

1098 

1099@pytest.mark.parametrize("surf_mesh", ["fsaverage5", fetch_surf_fsaverage()]) 

1100def test_plot_img_on_surf_surf_mesh(matplotlib_pyplot, img_3d_mni, surf_mesh): 

1101 """Smoke test for nilearn.plotting.surface.plot_img_on_surf for surf_mesh 

1102 parameter. 

1103 """ 

1104 plot_img_on_surf( 

1105 img_3d_mni, 

1106 hemispheres=["right", "left"], 

1107 views=["anterior"], 

1108 surf_mesh=surf_mesh, 

1109 ) 

1110 

1111 

1112def test_plot_img_on_surf_surf_mesh_low_alpha(matplotlib_pyplot, img_3d_mni): 

1113 """Check that low alpha value do not cause floating point error. 

1114 

1115 regression test for: https://github.com/nilearn/nilearn/issues/4900 

1116 """ 

1117 plot_img_on_surf(img_3d_mni, threshold=3, alpha=0.1) 

1118 

1119 

1120def test_plot_img_on_surf_with_invalid_orientation(img_3d_mni): 

1121 """Test if nilearn.plotting.surface.plot_img_on_surf raises error when 

1122 invalid views parameter is specified. 

1123 """ 

1124 kwargs = {"hemisphere": ["right"], "inflate": True} 

1125 with pytest.raises(ValueError): 

1126 plot_img_on_surf(img_3d_mni, views=["latral"], **kwargs) 

1127 with pytest.raises(ValueError): 

1128 plot_img_on_surf(img_3d_mni, views=["dorsal", "post"], **kwargs) 

1129 with pytest.raises(TypeError): 

1130 plot_img_on_surf(img_3d_mni, views=0, **kwargs) 

1131 with pytest.raises(ValueError): 

1132 plot_img_on_surf(img_3d_mni, views=["medial", {"a": "a"}], **kwargs) 

1133 

1134 

1135@pytest.mark.parametrize( 

1136 "hemispheres", [["lft]"], "lft", 0, ["left", "right", "middle"]] 

1137) 

1138def test_plot_img_on_surf_with_invalid_hemisphere(img_3d_mni, hemispheres): 

1139 """Test if nilearn.plotting.surface.plot_img_on_surf raises error when 

1140 invalid hemispheres parameter is specified. 

1141 """ 

1142 with pytest.raises(ValueError): 

1143 plot_img_on_surf( 

1144 img_3d_mni, 

1145 views=["lateral"], 

1146 inflate=True, 

1147 hemispheres=hemispheres, 

1148 ) 

1149 

1150 

1151def test_plot_img_on_surf_with_figure_kwarg(img_3d_mni): 

1152 """Test if nilearn.plotting.surface.plot_img_on_surf raises error when 

1153 figure parameter is specified. 

1154 """ 

1155 with pytest.raises(ValueError): 

1156 plot_img_on_surf( 

1157 img_3d_mni, 

1158 views=["anterior"], 

1159 hemispheres=["right"], 

1160 figure=True, 

1161 ) 

1162 

1163 

1164def test_plot_img_on_surf_with_axes_kwarg(img_3d_mni): 

1165 """Test if nilearn.plotting.surface.plot_img_on_surf raises error when axes 

1166 parameter is specified. 

1167 """ 

1168 with pytest.raises(ValueError): 

1169 plot_img_on_surf( 

1170 img_3d_mni, 

1171 views=["anterior"], 

1172 hemispheres=["right"], 

1173 inflat=True, 

1174 axes="something", 

1175 ) 

1176 

1177 

1178def test_plot_img_on_surf_with_engine_kwarg(img_3d_mni): 

1179 """Test if nilearn.plotting.surface.plot_img_on_surf raises error when 

1180 engine parameter is specified. 

1181 """ 

1182 with pytest.raises(ValueError): 

1183 plot_img_on_surf( 

1184 img_3d_mni, 

1185 views=["anterior"], 

1186 hemispheres=["right"], 

1187 inflat=True, 

1188 engine="something", 

1189 ) 

1190 

1191 

1192def test_plot_img_on_surf_title(matplotlib_pyplot, img_3d_mni): 

1193 """Test nilearn.plotting.surface.plot_img_on_surf with and without title 

1194 specified. 

1195 . 

1196 """ 

1197 title = "Title" 

1198 fig, _ = plot_img_on_surf( 

1199 img_3d_mni, hemispheres=["right"], views=["lateral"] 

1200 ) 

1201 assert fig._suptitle is None, "Created title without title kwarg." 

1202 fig, _ = plot_img_on_surf( 

1203 img_3d_mni, hemispheres=["right"], views=["lateral"], title=title 

1204 ) 

1205 assert fig._suptitle is not None, "Title not created." 

1206 assert fig._suptitle.get_text() == title, "Title text not assigned." 

1207 

1208 

1209def test_plot_img_on_surf_output_file(matplotlib_pyplot, tmp_path, img_3d_mni): 

1210 """Test nilearn.plotting.surface.plot_img_on_surf for output_file.""" 

1211 fname = tmp_path / "tmp.png" 

1212 return_value = plot_img_on_surf( 

1213 img_3d_mni, 

1214 hemispheres=["right"], 

1215 views=["lateral"], 

1216 output_file=str(fname), 

1217 ) 

1218 assert return_value is None, "Returned figure and axes on file output." 

1219 assert fname.is_file(), "Saved image file could not be found." 

1220 

1221 

1222def test_plot_img_on_surf_input_as_file(matplotlib_pyplot, img_3d_mni_as_file): 

1223 """Test nifti is supported when passed as string or path to a file.""" 

1224 plot_img_on_surf(stat_map=img_3d_mni_as_file) 

1225 plot_img_on_surf(stat_map=str(img_3d_mni_as_file)) 

1226 

1227 

1228@pytest.mark.parametrize( 

1229 "function", 

1230 [plot_surf_roi, plot_surf_stat_map, plot_surf_contours, plot_surf], 

1231) 

1232def test_error_nifti_not_supported( 

1233 function, img_3d_mni_as_file, in_memory_mesh 

1234): 

1235 """Test nifti file not supported by several surface plotting functions.""" 

1236 with pytest.raises(ValueError, match="The input type is not recognized"): 

1237 function(in_memory_mesh, img_3d_mni_as_file) 

1238 with pytest.raises(ValueError, match="The input type is not recognized"): 

1239 function(in_memory_mesh, str(img_3d_mni_as_file))