Repository: nilearn/nilearn
Pull request #5389: [ENH] Refactor to separate backends for plot_surf_stat_map
Author: login: hndgzkn
Created: 2025-05-19T12:49:01Z | Merged: 2025-05-20T08:03:08Z

<!---
This is a suggested pull request template for nilearn.
It's designed to capture information we've found to be useful in reviewing pull requests.

If there is other information that would be helpful to include, please don't hesitate to add it!

Please make sure your pull request also follows the
[contribution guidelines](https://nilearn.github.io/stable/development.html#contribution-guidelines) that
will be enforced during the review process.
-->

<!-- Please indicate after the # which issue you're closing with this PR.
This is helpful for the maintainers AND will magically close the issue when this
pull request is merged!
If the PR closes multiple issues, includes "closes" before each one is listed.
https://help.github.com/articles/closing-issues-using-keywords -->
- Closes None, but relates to:
  - #5234 
  - #5253 
  - #5276
  - #5282 
  - #5322
  - #5349

<!-- Please give a brief overview of what has changed in the PR.
If you're not sure what to write, consider it a note to the maintainers to indicate
what they should be looking for when they review the pull request. -->
Changes proposed in this pull request:

-  Refactor `plotting.surface.surf_plotting.plot_surf_stat_maps` to separate matplotlib code to `_matplotlib_backend` and plotly code to `_plotly_backend` and common parts to `backend`
- Add/update missing docstrings for tests in `test_surf_plotting` for `plot_surf_stat_maps`


Diff:
diff --git a/nilearn/plotting/_utils.py b/nilearn/plotting/_utils.py
index 12ed053779b..05ef58ef089 100644
--- a/nilearn/plotting/_utils.py
+++ b/nilearn/plotting/_utils.py
@@ -13,11 +13,9 @@
 
 def engine_warning(engine):
     message = (
-        (
-            f"'{engine}' is not installed. To be able to use '{engine}' as "
-            "plotting engine for 'nilearn.plotting' package:\n"
-            " pip install 'nilearn[plotting]'"
-        ),
+        f"'{engine}' is not installed. To be able to use '{engine}' as "
+        "plotting engine for 'nilearn.plotting' package:\n"
+        " pip install 'nilearn[plotting]'"
     )
     warn(message, stacklevel=find_stack_level())
 
diff --git a/nilearn/plotting/surface/_backend.py b/nilearn/plotting/surface/_backend.py
index 69f0fae396f..46384644c96 100644
--- a/nilearn/plotting/surface/_backend.py
+++ b/nilearn/plotting/surface/_backend.py
@@ -21,6 +21,7 @@
 
 import numpy as np
 
+from nilearn import DEFAULT_DIVERGING_CMAP
 from nilearn._utils.logger import find_stack_level
 from nilearn._utils.param_validation import check_params
 from nilearn.plotting.surface._utils import (
@@ -194,6 +195,77 @@ def plot_surf_contours(
             **kwargs,
         )
 
+    def plot_surf_stat_map(
+        self,
+        surf_mesh=None,
+        stat_map=None,
+        bg_map=None,
+        hemi=DEFAULT_HEMI,
+        view=None,
+        cmap=DEFAULT_DIVERGING_CMAP,
+        colorbar=True,
+        avg_method=None,
+        threshold=None,
+        alpha=None,
+        bg_on_data=False,
+        darkness=0.7,
+        vmin=None,
+        vmax=None,
+        symmetric_cbar="auto",
+        cbar_tick_format="auto",
+        title=None,
+        title_font_size=None,
+        output_file=None,
+        axes=None,
+        figure=None,
+        **kwargs,
+    ):
+        check_params(locals())
+
+        stat_map, surf_mesh, bg_map = check_surface_plotting_inputs(
+            stat_map, surf_mesh, hemi, bg_map, map_var_name="stat_map"
+        )
+        check_extensions(stat_map, DATA_EXTENSIONS, FREESURFER_DATA_EXTENSIONS)
+        loaded_stat_map = load_surf_data(stat_map)
+
+        # derive symmetric vmin, vmax and colorbar limits depending on
+        # symmetric_cbar settings
+        cbar_vmin, cbar_vmax, vmin, vmax = (
+            self._adjust_colorbar_and_data_ranges(
+                stat_map,
+                vmin=vmin,
+                vmax=vmax,
+                symmetric_cbar=symmetric_cbar,
+            )
+        )
+
+        return self._plot_surf(
+            surf_mesh,
+            surf_map=stat_map,
+            bg_map=bg_map,
+            hemi=hemi,
+            view=view,
+            avg_method=avg_method,
+            threshold=threshold,
+            cmap=cmap,
+            symmetric_cmap=True,
+            colorbar=colorbar,
+            cbar_tick_format=cbar_tick_format,
+            alpha=alpha,
+            bg_on_data=bg_on_data,
+            darkness=darkness,
+            vmax=vmax,
+            vmin=vmin,
+            title=title,
+            title_font_size=title_font_size,
+            output_file=output_file,
+            axes=axes,
+            figure=figure,
+            cbar_vmin=cbar_vmin,
+            cbar_vmax=cbar_vmax,
+            **kwargs,
+        )
+
 
 def _check_hemisphere_is_valid(hemi):
     return hemi in VALID_HEMISPHERES
diff --git a/nilearn/plotting/surface/_matplotlib_backend.py b/nilearn/plotting/surface/_matplotlib_backend.py
index eefe0dae0c8..e53c84f2ead 100644
--- a/nilearn/plotting/surface/_matplotlib_backend.py
+++ b/nilearn/plotting/surface/_matplotlib_backend.py
@@ -610,3 +610,13 @@ def _plot_surf_contours(
             axes.set_title(title)
 
         return save_figure_if_needed(figure, output_file)
+
+    def _adjust_colorbar_and_data_ranges(
+        self, stat_map, vmin=None, vmax=None, symmetric_cbar=None
+    ):
+        return get_colorbar_and_data_ranges(
+            stat_map,
+            vmin=vmin,
+            vmax=vmax,
+            symmetric_cbar=symmetric_cbar,
+        )
diff --git a/nilearn/plotting/surface/_plotly_backend.py b/nilearn/plotting/surface/_plotly_backend.py
index c89b1d1fce4..e794c733ee4 100644
--- a/nilearn/plotting/surface/_plotly_backend.py
+++ b/nilearn/plotting/surface/_plotly_backend.py
@@ -11,6 +11,7 @@
 
 from nilearn import DEFAULT_DIVERGING_CMAP
 from nilearn._utils.helpers import is_kaleido_installed
+from nilearn.plotting._utils import get_colorbar_and_data_ranges
 from nilearn.plotting.displays import PlotlySurfaceFigure
 from nilearn.plotting.js_plotting_utils import colorscale
 from nilearn.plotting.surface._backend import (
@@ -365,3 +366,15 @@ def _plot_surf_contours(
         **kwargs,
     ):
         raise NotImplementedError()
+
+    def _adjust_colorbar_and_data_ranges(
+        self, stat_map, vmin=None, vmax=None, symmetric_cbar=None
+    ):
+        _, _, vmin, vmax = get_colorbar_and_data_ranges(
+            stat_map,
+            vmin=vmin,
+            vmax=vmax,
+            symmetric_cbar=symmetric_cbar,
+        )
+
+        return None, None, vmin, vmax
diff --git a/nilearn/plotting/surface/surf_plotting.py b/nilearn/plotting/surface/surf_plotting.py
index 2fba3988be7..4ebbcd9680e 100644
--- a/nilearn/plotting/surface/surf_plotting.py
+++ b/nilearn/plotting/surface/surf_plotting.py
@@ -396,22 +396,22 @@ def plot_surf_stat_map(
     hemi=DEFAULT_HEMI,
     view=None,
     engine=DEFAULT_ENGINE,
+    cmap=DEFAULT_DIVERGING_CMAP,
+    colorbar=True,
+    avg_method=None,
     threshold=None,
     alpha=None,
+    bg_on_data=False,
+    darkness=0.7,
     vmin=None,
     vmax=None,
-    cmap=DEFAULT_DIVERGING_CMAP,
-    colorbar=True,
     symmetric_cbar="auto",
     cbar_tick_format="auto",
-    bg_on_data=False,
-    darkness=0.7,
     title=None,
-    title_font_size=18,
+    title_font_size=None,
     output_file=None,
     axes=None,
     figure=None,
-    avg_method=None,
     **kwargs,
 ):
     """Plot a stats map on a surface :term:`mesh` with optional background.
@@ -463,23 +463,9 @@ def plot_surf_stat_map(
             Please report bugs that you may encounter.
 
 
-    threshold : a number or None, default=None
-        If None is given, the image is not thresholded.
-        If a number is given, it is used to threshold the image,
-        values below the threshold (in absolute value) are plotted
-        as transparent.
-
     %(cmap)s
         default="RdBu_r"
 
-    %(cbar_tick_format)s
-        Default="auto" which will select:
-
-            - '%%.2g' (scientific notation) with ``matplotlib`` engine.
-            - '.1f' (rounded floats) with ``plotly`` engine.
-
-        .. versionadded:: 0.7.1
-
     %(colorbar)s
 
         .. note::
@@ -487,6 +473,23 @@ def plot_surf_stat_map(
 
         Default=True.
 
+    %(avg_method)s
+
+        .. note::
+            This option is currently only implemented for the
+            ``matplotlib`` engine.
+
+        When using matplotlib as engine,
+        `avg_method` will default to ``"mean"`` if ``None`` is passed.
+
+        .. versionadded:: 0.10.3dev
+
+    threshold : a number or None, default=None
+        If None is given, the image is not thresholded.
+        If a number is given, it is used to threshold the image,
+        values below the threshold (in absolute value) are plotted
+        as transparent.
+
     alpha : :obj:`float` or 'auto' or None, default=None
         Alpha level of the :term:`mesh` (not the stat_map).
         Will default to ``"auto"`` if ``None`` is passed.
@@ -497,12 +500,6 @@ def plot_surf_stat_map(
             This option is currently only implemented for the
             ``matplotlib`` engine.
 
-    %(vmin)s
-
-    %(vmax)s
-
-    %(symmetric_cbar)s
-
     %(bg_on_data)s
 
     %(darkness)s
@@ -512,9 +509,23 @@ def plot_surf_stat_map(
             This option is currently only implemented for the
             ``matplotlib`` engine.
 
+    %(vmin)s
+
+    %(vmax)s
+
+    %(symmetric_cbar)s
+
+    %(cbar_tick_format)s
+        Default="auto" which will select:
+
+            - '%%.2g' (scientific notation) with ``matplotlib`` engine.
+            - '.1f' (rounded floats) with ``plotly`` engine.
+
+        .. versionadded:: 0.7.1
+
     %(title)s
 
-    title_font_size : :obj:`int`, default=18
+    title_font_size : :obj:`int`, default=None
         Size of the title font (only implemented for the plotly engine).
 
         .. versionadded:: 0.9.0
@@ -537,17 +548,6 @@ def plot_surf_stat_map(
             This option is currently only implemented for the
             ``matplotlib`` engine.
 
-    %(avg_method)s
-
-        .. note::
-            This option is currently only implemented for the
-            ``matplotlib`` engine.
-
-        When using matplotlib as engine,
-        `avg_method` will default to ``"mean"`` if ``None`` is passed.
-
-        .. versionadded:: 0.10.3dev
-
     kwargs : :obj:`dict`, optional
         Keyword arguments passed to :func:`nilearn.plotting.plot_surf`.
 
@@ -560,59 +560,31 @@ def plot_surf_stat_map(
 
     nilearn.surface.vol_to_surf : For info on the generation of surfaces.
     """
-    # set default view to dorsal if hemi is both and view is not set
-    check_params(locals())
-    if view is None:
-        view = "dorsal" if hemi == "both" else "lateral"
-
-    stat_map, surf_mesh, bg_map = check_surface_plotting_inputs(
-        stat_map, surf_mesh, hemi, bg_map, map_var_name="stat_map"
-    )
-
-    check_extensions(stat_map, DATA_EXTENSIONS, FREESURFER_DATA_EXTENSIONS)
-    loaded_stat_map = load_surf_data(stat_map)
-
-    # Call get_colorbar_and_data_ranges to derive symmetric vmin, vmax
-    # And colorbar limits depending on symmetric_cbar settings
-    cbar_vmin, cbar_vmax, vmin, vmax = get_colorbar_and_data_ranges(
-        loaded_stat_map,
-        vmin=vmin,
-        vmax=vmax,
-        symmetric_cbar=symmetric_cbar,
-    )
-    # Set to None the values that are not used by plotly
-    # to avoid warnings thrown by plot_surf
-    if engine == "plotly":
-        cbar_vmin = None
-        cbar_vmax = None
-
-    display = plot_surf(
-        surf_mesh,
-        surf_map=loaded_stat_map,
+    display = get_surface_backend(engine).plot_surf_stat_map(
+        surf_mesh=surf_mesh,
+        stat_map=stat_map,
         bg_map=bg_map,
         hemi=hemi,
         view=view,
-        engine=engine,
-        avg_method=avg_method,
-        threshold=threshold,
         cmap=cmap,
-        symmetric_cmap=True,
         colorbar=colorbar,
-        cbar_tick_format=cbar_tick_format,
+        avg_method=avg_method,
+        threshold=threshold,
         alpha=alpha,
         bg_on_data=bg_on_data,
         darkness=darkness,
-        vmax=vmax,
         vmin=vmin,
+        vmax=vmax,
+        symmetric_cbar=symmetric_cbar,
+        cbar_tick_format=cbar_tick_format,
         title=title,
         title_font_size=title_font_size,
         output_file=output_file,
         axes=axes,
         figure=figure,
-        cbar_vmin=cbar_vmin,
-        cbar_vmax=cbar_vmax,
         **kwargs,
     )
+
     return display
 
 
diff --git a/nilearn/plotting/surface/tests/test_backend.py b/nilearn/plotting/surface/tests/test_backend.py
index cb40c2530ce..f3fdade747d 100644
--- a/nilearn/plotting/surface/tests/test_backend.py
+++ b/nilearn/plotting/surface/tests/test_backend.py
@@ -1,5 +1,7 @@
 """Test nilearn.plotting.surface._backend functions."""
 
+# ruff: noqa: ARG001
+
 import pytest
 
 from nilearn.plotting.surface._backend import (
@@ -8,6 +10,22 @@
 )
 
 
+@pytest.fixture
+def backend(engine):
+    if engine == "matplotlib":
+        from nilearn.plotting.surface._matplotlib_backend import (
+            MatplotlibSurfaceBackend,
+        )
+
+        return MatplotlibSurfaceBackend()
+    elif engine == "plotly":
+        from nilearn.plotting.surface._plotly_backend import (
+            PlotlySurfaceBackend,
+        )
+
+        return PlotlySurfaceBackend()
+
+
 @pytest.mark.parametrize(
     "view,is_valid",
     [
@@ -36,3 +54,56 @@ def test_check_view_is_valid(view, is_valid):
 )
 def test_check_hemisphere_is_valid(hemi, is_valid):
     assert _check_hemisphere_is_valid(hemi) is is_valid
+
+
+def test_plot_surf(plt, engine, backend, tmp_path, in_memory_mesh, bg_map):
+    """Test
+    nilearn.plotting.surface._backend.MatplotlibBackend.plot_surf and
+    nilearn.plotting.surface._backend.PlotlyBackend.plot_surf functions
+    with available engine backends.
+    """
+    # to avoid extra warnings
+    alpha = None
+    cbar_vmin = None
+    cbar_vmax = None
+    if engine == "matplotlib":
+        alpha = 0.5
+        cbar_vmin = 0
+        cbar_vmax = 150
+
+    # Plot mesh only
+    backend.plot_surf(in_memory_mesh)
+
+    # Plot mesh with background
+    backend.plot_surf(in_memory_mesh, bg_map=bg_map)
+    backend.plot_surf(in_memory_mesh, bg_map=bg_map, darkness=0.5)
+    backend.plot_surf(
+        in_memory_mesh,
+        bg_map=bg_map,
+        alpha=alpha,
+        output_file=tmp_path / "tmp.png",
+    )
+
+    # Plot with colorbar
+    backend.plot_surf(in_memory_mesh, bg_map=bg_map, colorbar=True)
+    backend.plot_surf(
+        in_memory_mesh,
+        bg_map=bg_map,
+        colorbar=True,
+        cbar_vmin=cbar_vmin,
+        cbar_vmax=cbar_vmax,
+        cbar_tick_format="%i",
+    )
+
+
+def test_plot_surf_stat_map(plt, engine, backend, in_memory_mesh, bg_map):
+    """Smoke test when stat_map is specified to
+    nilearn.plotting.surface._backend.MatplotlibBackend.plot_surf_stat_map
+    and
+    nilearn.plotting.surface._backend.PlotlyBackend.plot_surf_stat_map
+    functions together with mesh.
+    """
+    alpha = 1 if engine == "matplotlib" else None
+
+    backend.plot_surf_stat_map(in_memory_mesh, stat_map=bg_map)
+    backend.plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, alpha=alpha)
diff --git a/nilearn/plotting/surface/tests/test_matplotlib_backend.py b/nilearn/plotting/surface/tests/test_matplotlib_backend.py
index 560a7dd50f6..bb7b16b6b4e 100644
--- a/nilearn/plotting/surface/tests/test_matplotlib_backend.py
+++ b/nilearn/plotting/surface/tests/test_matplotlib_backend.py
@@ -230,3 +230,23 @@ def test_compute_facecolors_deprecation():
             0.5,
             alpha,
         )
+
+
+def test_plot_surf_contours(
+    matplotlib_backend,
+    matplotlib_pyplot,
+    in_memory_mesh,
+    parcellation,
+    surf_mask_1d,
+):
+    """Test
+    nilearn.plotting.surface._backend.MatplotlibBackend.plot_surf_contours
+    for valid input values.
+    """
+    matplotlib_backend.plot_surf_contours(in_memory_mesh, parcellation)
+    matplotlib_backend.plot_surf_contours(
+        in_memory_mesh, parcellation, levels=[1, 2]
+    )
+    matplotlib_backend.plot_surf_contours(
+        in_memory_mesh, parcellation, levels=[1, 2], cmap="gist_ncar"
+    )
diff --git a/nilearn/plotting/surface/tests/test_surf_plotting.py b/nilearn/plotting/surface/tests/test_surf_plotting.py
index 0532c7f3ea9..866aff564b4 100644
--- a/nilearn/plotting/surface/tests/test_surf_plotting.py
+++ b/nilearn/plotting/surface/tests/test_surf_plotting.py
@@ -537,8 +537,12 @@ def test_plot_surf_contours_errors_with_plotly_figure(plotly, in_memory_mesh):
 
 
 def test_plot_surf_stat_map(plt, engine, in_memory_mesh, bg_map):
+    """Smoke test when stat_map is specified to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map together with
+    mesh.
+    """
     alpha = 1 if engine == "matplotlib" else None
-    # Plot mesh with stat map
+
     plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, engine=engine)
     plot_surf_stat_map(
         in_memory_mesh, stat_map=bg_map, alpha=alpha, engine=engine
@@ -548,7 +552,9 @@ def test_plot_surf_stat_map(plt, engine, in_memory_mesh, bg_map):
 def test_plot_surf_stat_map_with_background(
     plt, engine, in_memory_mesh, bg_map
 ):
-    """Plot mesh with background and stat map."""
+    """Smoke test when background map is specified also as stat_map to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map.
+    """
     plot_surf_stat_map(
         in_memory_mesh, stat_map=bg_map, bg_map=bg_map, engine=engine
     )
@@ -563,7 +569,9 @@ def test_plot_surf_stat_map_with_background(
 
 
 def test_plot_surf_stat_map_with_title(plt, engine, in_memory_mesh, bg_map):
-    """Check title is added."""
+    """Test if nilearn.plotting.surface.surf_plotting.plot_surf_stat_map adds
+    title when specified.
+    """
     display = plot_surf_stat_map(
         in_memory_mesh, stat_map=bg_map, title="Stat map title"
     )
@@ -573,7 +581,9 @@ def test_plot_surf_stat_map_with_title(plt, engine, in_memory_mesh, bg_map):
 def test_plot_surf_stat_map_with_threshold(
     plt, engine, in_memory_mesh, bg_map
 ):
-    """Check title is added."""
+    """Smoke test when threshold is specified to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map.
+    """
     plot_surf_stat_map(
         in_memory_mesh,
         stat_map=bg_map,
@@ -583,18 +593,25 @@ def test_plot_surf_stat_map_with_threshold(
 
 
 def test_plot_surf_stat_map_vmax(plt, engine, in_memory_mesh, bg_map):
-    """Change vmax."""
+    """Smoke test when vmax is specified to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map.
+    """
     plot_surf_stat_map(in_memory_mesh, stat_map=bg_map, vmax=5, engine=engine)
 
 
 def test_plot_surf_stat_map_colormap(plt, engine, in_memory_mesh, bg_map):
-    """Change colormap."""
+    """Smoke test when colormap is specified to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map.
+    """
     plot_surf_stat_map(
         in_memory_mesh, stat_map=bg_map, cmap="cubehelix", engine=engine
     )
 
 
 def test_plot_surf_stat_map_error(in_memory_mesh, bg_map):
+    """Test if nilearn.plotting.surface.surf_plotting.plot_surf_stat_map
+    raises error with wrong size of stat map data.
+    """
     # Wrong size of stat map data
     with pytest.raises(
         ValueError, match="surf_map does not have the same number of vertices"
@@ -612,7 +629,9 @@ def test_plot_surf_stat_map_error(in_memory_mesh, bg_map):
 
 
 def test_plot_surf_stat_map_colorbar_tick(plotly, in_memory_mesh, bg_map):
-    """Change colorbar tick format."""
+    """Smoke test when colorbar tick format with plotly engine is specified to
+    nilearn.plotting.surface.surf_plotting.plot_surf_stat_map.
+    """
     plot_surf_stat_map(
         in_memory_mesh,
         stat_map=bg_map,
@@ -624,6 +643,9 @@ def test_plot_surf_stat_map_colorbar_tick(plotly, in_memory_mesh, bg_map):
 def test_plot_surf_stat_map_matplotlib_specific(
     matplotlib_pyplot, in_memory_mesh, bg_map
 ):
+    """Test nilearn.plotting.surface.surf_plotting.plot_surf_stat_map for
+    matplotlib engine specific parameters.
+    """
     # Plot to axes
     axes = matplotlib_pyplot.subplots(
         ncols=2, subplot_kw={"projection": "3d"}


Reviews:
user: login: hndgzkn
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T12:50:43Z
user: login: Remi-Gau
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T14:20:19Z
user: login: Remi-Gau
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-19T14:22:30Z
user: login: bthirion
body: I'm happy with the change set, but what about the many codecov complaints ?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T21:15:02Z

Review comments:
diff_hunk: @@ -396,22 +396,22 @@ def plot_surf_stat_map(
     hemi=DEFAULT_HEMI,
     view=None,
     engine=DEFAULT_ENGINE,
+    cmap=DEFAULT_DIVERGING_CMAP,
+    colorbar=True,
+    avg_method=None,
     threshold=None,
     alpha=None,
+    bg_on_data=False,
+    darkness=0.7,
     vmin=None,
     vmax=None,
-    cmap=DEFAULT_DIVERGING_CMAP,
-    colorbar=True,
     symmetric_cbar="auto",
     cbar_tick_format="auto",
-    bg_on_data=False,
-    darkness=0.7,
     title=None,
-    title_font_size=18,
+    title_font_size=None,
path: nilearn/plotting/surface/surf_plotting.py
user: login: hndgzkn
body: Here I've just changed the order of parameters to be consistent with the previous functions.
created_at: 2025-05-19T12:50:43Z
author_association: COLLABORATOR
diff_hunk: @@ -13,11 +13,9 @@
 
 def engine_warning(engine):
     message = (
-        (
-            f"'{engine}' is not installed. To be able to use '{engine}' as "
-            "plotting engine for 'nilearn.plotting' package:\n"
-            " pip install 'nilearn[plotting]'"
-        ),
+        f"'{engine}' is not installed. To be able to use '{engine}' as "
+        "plotting engine for 'nilearn.plotting' package:\n"
+        " pip install 'nilearn[plotting]'"
path: nilearn/plotting/_utils.py
user: login: Remi-Gau
body: that one is my fault: I changed it when changing the stacklevel of the warnings but had not tested it properly

it also creates issues on main, that should therefore be fixed by this PR
created_at: 2025-05-19T14:20:19Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-19T12:58:03Z
body: 👋 @hndgzkn Thanks for creating a PR!

Until this PR is ready for review, you can include the [WIP] tag in its title, or leave it as a github draft.

Please make sure it is compliant with our [contributing guidelines](https://nilearn.github.io/stable/development.html#contribution-guidelines). In particular, be sure it checks the boxes listed below.
- [ ] PR has an interpretable title.
- [ ] PR links to Github issue with mention `Closes #XXXX` (see our documentation on [PR structure](https://nilearn.github.io/stable/development.html#pr-structure))
- [ ] Code is PEP8-compliant (see our documentation on [coding style](https://nilearn.github.io/stable/development.html#coding-style))
- [ ] Changelog or what's new entry in `doc/changes/latest.rst` (see our documentation on [PR structure](https://nilearn.github.io/stable/development.html#pr-structure))

For new features:
- [ ] There is at least one unit test per new function / class (see our documentation on [testing](https://nilearn.github.io/stable/development.html#tests))
- [ ] The new feature is demoed in at least one relevant example.

For bug fixes:
- [ ] There is at least one test that would fail under the original bug conditions.

We will review it as quick as possible, feel free to ping us with questions if needed.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-19T13:13:58Z
body: ## [Codecov](https://app.codecov.io/gh/nilearn/nilearn/pull/5389?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) Report
Attention: Patch coverage is `97.77778%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 96.83%. Comparing base [(`e696094`)](https://app.codecov.io/gh/nilearn/nilearn/commit/e6960944dc8655a96216fef2740f722f3c62901c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) to head [(`af1458e`)](https://app.codecov.io/gh/nilearn/nilearn/commit/af1458e6bd362beb967c51c03ec1fb80bffce401?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/nilearn/nilearn/pull/5389?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | Patch % | Lines |
|---|---|---|
| [nilearn/plotting/surface/tests/test\_backend.py](https://app.codecov.io/gh/nilearn/nilearn/pull/5389?src=pr&el=tree&filepath=nilearn%2Fplotting%2Fsurface%2Ftests%2Ftest_backend.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn#diff-bmlsZWFybi9wbG90dGluZy9zdXJmYWNlL3Rlc3RzL3Rlc3RfYmFja2VuZC5weQ==) | 96.15% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nilearn/nilearn/pull/5389?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #5389      +/-   ##
==========================================
+ Coverage   96.20%   96.83%   +0.63%     
==========================================
  Files         282      282              
  Lines       38836    38870      +34     
  Branches     3841     3842       +1     
==========================================
+ Hits        37362    37640     +278     
+ Misses        892      678     -214     
+ Partials      582      552      -30     
```

| [Flag](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | Coverage Δ | |
|---|---|---|
| [macos-latest_3.10_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [macos-latest_3.11_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [macos-latest_3.12_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [macos-latest_3.13_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [macos-latest_3.9_min](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `77.57% <0.00%> (-0.07%)` | :arrow_down: |
| [macos-latest_3.9_plot_min](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.12% <84.44%> (?)` | |
| [macos-latest_3.9_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [ubuntu-latest_3.10_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (+<0.01%)` | :arrow_up: |
| [ubuntu-latest_3.11_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (?)` | |
| [ubuntu-latest_3.12_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (?)` | |
| [ubuntu-latest_3.13_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (?)` | |
| [ubuntu-latest_3.13_pre](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (?)` | |
| [ubuntu-latest_3.9_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.55% <97.77%> (+<0.01%)` | :arrow_up: |
| [windows-latest_3.10_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `91.80% <97.77%> (-3.73%)` | :arrow_down: |
| [windows-latest_3.11_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [windows-latest_3.12_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.53% <97.77%> (+<0.01%)` | :arrow_up: |
| [windows-latest_3.13_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `95.14% <97.77%> (-0.40%)` | :arrow_down: |
| [windows-latest_3.9_plotting](https://app.codecov.io/gh/nilearn/nilearn/pull/5389/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | `91.82% <97.77%> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nilearn/nilearn/pull/5389?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: hndgzkn
created_at: 2025-05-20T07:05:58Z
body: > I'm happy with the change set, but what about the many codecov complaints ?

Actually I am not adding any code, just moving. 

In any case, I've added some tests (they are the same tests in in `test_surf_plotting`, instead of testing functions like `plot_surf` directly, I call corresponding functions in `backend` instance). Let's see if codecov is happy with them.
author_association: COLLABORATOR
user: login: Remi-Gau
created_at: 2025-05-20T08:03:02Z
body: coverage report uploaded here does not show any issue with coverage: 

https://github.com/nilearn/nilearn/actions/runs/15131021592/artifacts/3157645385

and the full report at sentry is also fine: https://app.codecov.io/gh/nilearn/nilearn/pull/5389?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn

merging
author_association: COLLABORATOR