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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Test nilearn.plotting.surface.surf_plotting functions."""
3# ruff: noqa: ARG001
5import re
6import tempfile
8import numpy as np
9import pandas as pd
10import pytest
11from numpy.testing import assert_array_equal
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)
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
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)
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)
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)
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)
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")
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")
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
128 # Plot mesh only
129 plot_surf(in_memory_mesh, engine=engine)
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 )
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 )
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 )
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 )
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)
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 )
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 )
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 )
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 )
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 )
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 )
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"
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 )
316 # Try custom avg_method
317 def custom_avg_function(vertices):
318 return vertices[0] * vertices[1] * vertices[2]
320 plot_surf(
321 in_memory_mesh,
322 surf_map=bg_map,
323 avg_method=custom_avg_function,
324 engine=ENGINE,
325 )
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 ):
344 def custom_avg_function(vertices):
345 return [vertices[0] * vertices[1], vertices[2]]
347 plot_surf(
348 in_memory_mesh,
349 surf_map=bg_map,
350 avg_method=custom_avg_function,
351 engine=ENGINE,
352 )
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 = {}
364 plot_surf(
365 in_memory_mesh,
366 surf_map=bg_map,
367 avg_method=custom_avg_function,
368 engine=ENGINE,
369 )
371 plot_surf(
372 in_memory_mesh,
373 surf_map=bg_map,
374 avg_method="foo",
375 engine=ENGINE,
376 )
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 ):
387 def custom_avg_function(vertices):
388 return "string"
390 plot_surf(
391 in_memory_mesh,
392 surf_map=bg_map,
393 avg_method=custom_avg_function,
394 engine=ENGINE,
395 )
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 )
406 assert len(display.axes) == 1
407 assert display.axes[0].title._text == "Test title"
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)
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 )
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")
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
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 )
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"
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 )
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)
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)
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)
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 )
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 )
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)
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
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 )
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 )
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"
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 )
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)
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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)
738 fig = plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, colorbar=False)
740 assert len(fig.axes) == 1
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()
748 assert len(fig.axes) == 2
750 yticklabels = fig.axes[1].get_yticklabels()
751 first, last = yticklabels[0].get_text(), yticklabels[-1].get_text()
753 assert float(first) == -float(last)
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()
761 assert len(fig.axes) == 2
763 yticklabels = fig.axes[1].get_yticklabels()
764 first, last = yticklabels[0].get_text(), yticklabels[-1].get_text()
766 assert float(first) != -float(last)
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]
777 assert (
778 in_memory_mesh.faces.shape[0] == ((tmp._facecolors[:, 3]) != 0).sum()
779 )
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 )
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)
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 )
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 )
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)
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)
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)
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())
872 assert cbar_vmin == 1.0
873 assert cbar_vmax == 8.0
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())
889 assert cbar_vmin == 1.2
890 assert cbar_vmax == 8.9
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]
913 assert n_faces == ((tmp._facecolors[:, 3]) != 0).sum()
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"
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 )
930 _, ax = matplotlib_pyplot.subplots(subplot_kw={"projection": "3d"})
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 )
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 )
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 )
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
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 )
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))
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 )
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)
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 )
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 )
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 )
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.
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)
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)
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 )
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 )
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 )
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 )
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."
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."
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))
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))