task: metadata: install: pip install -e .[dev]
patch: diff --git a/nilearn/_utils/docs.py b/nilearn/_utils/docs.py
index c046f616a..7fb68c1c5 100644
--- a/nilearn/_utils/docs.py
+++ b/nilearn/_utils/docs.py
@@ -939,6 +939,14 @@ standardize_confounds : :obj:`bool`, default=True
     their mean is put to 0 and their variance to 1 in the time dimension.
 """
 
+# standardize_confounds
+docdict["strategy"] = """
+strategy : :obj:`str`, default="mean"
+    The name of a valid function to reduce the region with.
+    Must be one of: sum, mean, median, minimum, maximum, variance,
+    standard_deviation.
+"""
+
 # symmetric_cbar
 docdict["symmetric_cbar"] = """
 symmetric_cbar : :obj:`bool`, or "auto", default="auto"
diff --git a/nilearn/_utils/param_validation.py b/nilearn/_utils/param_validation.py
index 68060297a..d7b97c302 100644
--- a/nilearn/_utils/param_validation.py
+++ b/nilearn/_utils/param_validation.py
@@ -361,3 +361,27 @@ def _cast_to_int32(sample_mask):
             what can be represented by int32: {highest}."
         raise ValueError(msg)
     return np.asarray(sample_mask, new_dtype)
+
+
+def check_reduction_strategy(strategy: str):
+    """Check that the provided strategy is supported.
+
+    Parameters
+    ----------
+    %(strategy)s
+    """
+    available_reduction_strategies = {
+        "mean",
+        "median",
+        "sum",
+        "minimum",
+        "maximum",
+        "standard_deviation",
+        "variance",
+    }
+
+    if strategy not in available_reduction_strategies:
+        raise ValueError(
+            f"Invalid strategy '{strategy}'. "
+            f"Valid strategies are {available_reduction_strategies}."
+        )
diff --git a/nilearn/maskers/nifti_labels_masker.py b/nilearn/maskers/nifti_labels_masker.py
index ab91eb1ac..1f24daeac 100644
--- a/nilearn/maskers/nifti_labels_masker.py
+++ b/nilearn/maskers/nifti_labels_masker.py
@@ -9,6 +9,7 @@ from nibabel import Nifti1Image
 from nilearn import _utils
 from nilearn._utils import logger
 from nilearn._utils.helpers import is_matplotlib_installed
+from nilearn._utils.param_validation import check_reduction_strategy
 from nilearn.image import get_data, load_img, resample_img
 from nilearn.maskers._utils import (
     compute_middle_image,
@@ -125,10 +126,7 @@ class NiftiLabelsMasker(BaseMasker):
 
     %(verbose0)s
 
-    strategy : :obj:`str`, default='mean'
-        The name of a valid function to reduce the region with.
-        Must be one of: sum, mean, median, minimum, maximum, variance,
-        standard_deviation.
+    %(strategy)s
 
     %(keep_masked_labels)s
 
@@ -531,20 +529,7 @@ class NiftiLabelsMasker(BaseMasker):
             This parameter is unused. It is solely included for scikit-learn
             compatibility.
         """
-        available_reduction_strategies = {
-            "mean",
-            "median",
-            "sum",
-            "minimum",
-            "maximum",
-            "standard_deviation",
-            "variance",
-        }
-        if self.strategy not in available_reduction_strategies:
-            raise ValueError(
-                f"Invalid strategy '{self.strategy}'. "
-                f"Valid strategies are {available_reduction_strategies}."
-            )
+        check_reduction_strategy(self.strategy)
 
         if self.resampling_target not in ("labels", "data", None):
             raise ValueError(
diff --git a/nilearn/maskers/surface_labels_masker.py b/nilearn/maskers/surface_labels_masker.py
index 830955224..bbfebe6e5 100644
--- a/nilearn/maskers/surface_labels_masker.py
+++ b/nilearn/maskers/surface_labels_masker.py
@@ -6,6 +6,7 @@ from pathlib import Path
 import numpy as np
 import pandas as pd
 from joblib import Memory
+from scipy import ndimage
 
 from nilearn import signal
 from nilearn._utils.bids import (
@@ -19,9 +20,11 @@ from nilearn._utils.helpers import (
     constrained_layout_kwargs,
     is_matplotlib_installed,
 )
+from nilearn._utils.param_validation import check_reduction_strategy
 from nilearn.maskers.base_masker import _BaseSurfaceMasker
 from nilearn.surface.surface import (
     SurfaceImage,
+    at_least_2d,
     check_same_n_vertices,
     concat_imgs,
     mean_img,
@@ -32,9 +35,10 @@ def _apply_surf_mask_on_labels(mask_data, labels_data, background_label=0):
     """Apply mask to labels data.
 
     Ensures that we only get the data back
-    according to the mask that was applied. So if some labels were removed,
-    we will only get the data for the remaining labels, the vertices that were
-    masked out will be set to the background label.
+    according to the mask that was applied.
+    So if some labels were removed,
+    we will only get the data for the remaining labels,
+    the vertices that were masked out will be set to the background label.
     """
     labels_before_mask = {int(label) for label in np.unique(labels_data)}
     labels_data[np.logical_not(mask_data.flatten())] = background_label
@@ -195,6 +199,7 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
         memory=None,
         memory_level=1,
         verbose=0,
+        strategy="mean",
         reports=True,
         cmap="inferno",
         clean_args=None,
@@ -216,6 +221,7 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
         self.memory_level = memory_level
         self.verbose = verbose
         self.reports = reports
+        self.strategy = strategy
         self.cmap = cmap
         self.clean_args = clean_args
 
@@ -243,6 +249,8 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
         """
         del img, y
 
+        check_reduction_strategy(self.strategy)
+
         if self.labels_img is None:
             raise ValueError(
                 "Please provide a labels_img to the masker. For example, "
@@ -381,7 +389,7 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
 
         Returns
         -------
-        output : :obj:`numpy.ndarray`
+        region_signals : 2D :obj:`numpy.ndarray`
             Signal for each element.
             shape: (img data shape, total number of vertices)
         """
@@ -393,6 +401,7 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
             img = [img]
         img = concat_imgs(img)
         check_same_n_vertices(self.labels_img.mesh, img.mesh)
+        img = at_least_2d(img)
         # concatenate data over hemispheres
         img_data = np.concatenate(list(img.data.parts.values()), axis=0)
 
@@ -435,19 +444,25 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
             self.memory = Memory(location=None)
 
         n_time_points = 1 if len(img_data.shape) == 1 else img_data.shape[1]
-        output = np.empty((n_time_points, len(labels)))
-        for i, label in enumerate(labels):
-            output[:, i] = img_data[labels_data == label].mean(axis=0)
+        region_signals = np.empty((n_time_points, len(labels)))
+
+        # adapted from nilearn.regions.signal_extraction.img_to_signals_labels
+        # iterate over time points and apply reduction function over labels.
+        reduction_function = getattr(ndimage, self.strategy)
+        for n, img in enumerate(np.rollaxis(img_data, -1)):
+            region_signals[n] = np.asarray(
+                reduction_function(img, labels=labels_data, index=labels)
+            )
 
         # signal cleaning here
-        output = cache(
+        region_signals = cache(
             signal.clean,
             memory=self.memory,
             func_memory_level=2,
             memory_level=self.memory_level,
             shelve=self._shelving,
         )(
-            output,
+            region_signals,
             detrend=parameters["detrend"],
             standardize=parameters["standardize"],
             standardize_confounds=parameters["standardize_confounds"],
@@ -459,7 +474,7 @@ class SurfaceLabelsMasker(_BaseSurfaceMasker):
             **parameters["clean_args"],
         )
 
-        return output
+        return region_signals
 
     def fit_transform(self, img, y=None, confounds=None, sample_mask=None):
         """Prepare and perform signal extraction from regions.
diff --git a/nilearn/regions/signal_extraction.py b/nilearn/regions/signal_extraction.py
index b1dc4a937..f7705168d 100644
--- a/nilearn/regions/signal_extraction.py
+++ b/nilearn/regions/signal_extraction.py
@@ -12,6 +12,8 @@ import numpy as np
 from nibabel import Nifti1Image
 from scipy import linalg, ndimage
 
+from nilearn._utils.param_validation import check_reduction_strategy
+
 from .. import _utils, masking
 from .._utils.niimg import safe_get_data
 from ..image import new_img_like
@@ -210,34 +212,6 @@ def _get_labels_data(
     return labels, labels_data
 
 
-def _check_reduction_strategy(strategy: str):
-    """Check that the provided strategy is supported.
-
-    Parameters
-    ----------
-    strategy : :obj:`str`
-        The name of a valid function to reduce the region with.
-        Must be one of: sum, mean, median, minimum, maximum, variance,
-        standard_deviation.
-
-    """
-    available_reduction_strategies = {
-        "mean",
-        "median",
-        "sum",
-        "minimum",
-        "maximum",
-        "standard_deviation",
-        "variance",
-    }
-
-    if strategy not in available_reduction_strategies:
-        raise ValueError(
-            f"Invalid strategy '{strategy}'. "
-            f"Valid strategies are {available_reduction_strategies}."
-        )
-
-
 # FIXME: naming scheme is not really satisfying. Any better idea appreciated.
 @_utils.fill_doc
 def img_to_signals_labels(
@@ -279,10 +253,8 @@ def img_to_signals_labels(
     order : :obj:`str`, default='F'
         Ordering of output array ("C" or "F").
 
-    strategy : :obj:`str`, default="mean"
-        The name of a valid function to reduce the region with.
-        Must be one of: sum, mean, median, minimum, maximum, variance,
-        standard_deviation.
+    %(strategy)s
+
     %(keep_masked_labels)s
 
     return_masked_atlas : :obj:`bool`, default=False
@@ -316,7 +288,7 @@ def img_to_signals_labels(
     """
     labels_img = _utils.check_niimg_3d(labels_img)
 
-    _check_reduction_strategy(strategy)
+    check_reduction_strategy(strategy)
 
     # TODO: Make a special case for list of strings
     # (load one image at a time).

problem_statement: [ENH] implement strategies in SurfaceLabelMasker
nilearn.maskers.NiftiLabelsMasker and nilearn.regions.img_to_signals_labels have a way to summarize data from a region using different strategies: sum, mean, median, minimum, maximum, variance, standard_deviation.

![Image](https://github.com/user-attachments/assets/52f3069e-1f03-4c9a-8679-e6b21f491d94)

https://nilearn.github.io/dev/modules/generated/nilearn.regions.img_to_signals_labels.html#nilearn.regions.img_to_signals_labels

https://nilearn.github.io/dev/modules/generated/nilearn.maskers.NiftiLabelsMasker.html#nilearn-maskers-niftilabelsmasker

At the moment SurfaceLabelMasker only implement mean: it'd be good to extend this

repo: nilearn/nilearn
test_cmd: pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
test_patch: diff --git a/nilearn/maskers/tests/test_surface_labels_masker.py b/nilearn/maskers/tests/test_surface_labels_masker.py
index 4033b0b57..5cec0adee 100644
--- a/nilearn/maskers/tests/test_surface_labels_masker.py
+++ b/nilearn/maskers/tests/test_surface_labels_masker.py
@@ -155,11 +155,26 @@ def test_surface_label_masker_fit_no_report(surf_label_img):
     assert masker._reporting_data is None
 
 
+@pytest.mark.parametrize(
+    "strategy",
+    (
+        "variance",
+        "minimum",
+        "mean",
+        "standard_deviation",
+        "sum",
+        "median",
+        "maximum",
+    ),
+)
 def test_surface_label_masker_transform(
-    surf_label_img, surf_img_1d, surf_img_2d
+    surf_label_img, surf_img_1d, surf_img_2d, strategy
 ):
-    """Test transform extract signals."""
-    masker = SurfaceLabelsMasker(labels_img=surf_label_img)
+    """Test transform extract signals.
+
+    Also a smoke test for different strategies.
+    """
+    masker = SurfaceLabelsMasker(labels_img=surf_label_img, strategy=strategy)
     masker = masker.fit()
 
     # only one 'timepoint'
@@ -577,3 +592,10 @@ def test_masker_reporting_mpl_warning(surf_label_img):
 
     assert len(warning_list) == 1
     assert issubclass(warning_list[0].category, ImportWarning)
+
+
+def test_error_wrong_strategy(surf_label_img):
+    """Throw error for unsupported strategies."""
+    masker = SurfaceLabelsMasker(labels_img=surf_label_img, strategy="foo")
+    with pytest.raises(ValueError, match="Invalid strategy 'foo'."):
+        masker.fit()