Repository: mne-tools/mne-python

Issue:
mne.stats.summarize_clusters_stc for volumteric source space?
#### Describe the problem
mne.stats.summarize_clusters_stc seems to only support surface based source space.
Specifically for vertices, it says: list of array | None
    The vertex numbers associated with the source space locations. Defaults to None. If None, equals `[np.arange(10242), np.arange(10242)]`.

I tried to call the function and input vertices using a volumetric source space (a single array), the error message says: ValueError: Vertices must be a list containing two numpy arrays, got type <class 'numpy.ndarray'> ([ 2986  2987  3018 ... 40539 40540 40541]). 

#### Describe your solution
It would be nice to have one function to handle both types of source spaces and an visualization example will be great too!




Discussion:

Resolving patch:
diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py
--- a/mne/stats/cluster_level.py
+++ b/mne/stats/cluster_level.py
@@ -18,7 +18,9 @@
 from ..fixes import jit, has_numba
 from ..utils import (split_list, logger, verbose, ProgressBar, warn, _pl,
                      check_random_state, _check_option, _validate_type)
-from ..source_estimate import SourceEstimate
+from ..source_estimate import (SourceEstimate, VolSourceEstimate,
+                               MixedSourceEstimate)
+from ..source_space import SourceSpaces
 
 
 def _get_buddies_fallback(r, s, neighbors, indices=None):
@@ -1454,9 +1456,13 @@ def summarize_clusters_stc(clu, p_thresh=0.05, tstep=1.0, tmin=0,
         The time of the first sample.
     subject : str
         The name of the subject.
-    vertices : list of array | None
+    vertices : list of array | instance of SourceSpaces | None
         The vertex numbers associated with the source space locations. Defaults
         to None. If None, equals ``[np.arange(10242), np.arange(10242)]``.
+        Can also be an instance of SourceSpaces to get vertex numbers from.
+
+        .. versionchanged:: 0.21
+           Added support for SourceSpaces.
 
     Returns
     -------
@@ -1466,12 +1472,30 @@ def summarize_clusters_stc(clu, p_thresh=0.05, tstep=1.0, tmin=0,
         contain each individual cluster. The magnitude of the activity
         corresponds to the duration spanned by the cluster (duration units are
         determined by ``tstep``).
+
+        .. versionchanged:: 0.21
+           Added support for volume and mixed source estimates.
     """
+    _validate_type(vertices, (None, list, SourceSpaces), 'vertices')
     if vertices is None:
         vertices = [np.arange(10242), np.arange(10242)]
+        klass = SourceEstimate
+    elif isinstance(vertices, SourceSpaces):
+        klass = dict(surface=SourceEstimate,
+                     volume=VolSourceEstimate,
+                     mixed=MixedSourceEstimate)[vertices.kind]
+        vertices = [s['vertno'] for s in vertices]
+    else:
+        klass = {1: VolSourceEstimate,
+                 2: SourceEstimate}.get(len(vertices), MixedSourceEstimate)
+    n_vertices_need = sum(len(v) for v in vertices)
 
     t_obs, clusters, clu_pvals, _ = clu
     n_times, n_vertices = t_obs.shape
+    if n_vertices != n_vertices_need:
+        raise ValueError(
+            f'Number of cluster vertices ({n_vertices}) did not match the '
+            f'provided vertices ({n_vertices_need})')
     good_cluster_inds = np.where(clu_pvals < p_thresh)[0]
 
     #  Build a convenient representation of each cluster, where each
@@ -1493,5 +1517,4 @@ def summarize_clusters_stc(clu, p_thresh=0.05, tstep=1.0, tmin=0,
         # visualization
     data_summary[:, 0] = np.sum(data_summary, axis=1)
 
-    return SourceEstimate(data_summary, vertices, tmin=tmin, tstep=tstep,
-                          subject=subject)
+    return klass(data_summary, vertices, tmin, tstep, subject)


Test patch:
diff --git a/mne/stats/tests/test_cluster_level.py b/mne/stats/tests/test_cluster_level.py
--- a/mne/stats/tests/test_cluster_level.py
+++ b/mne/stats/tests/test_cluster_level.py
@@ -12,6 +12,8 @@
                            assert_array_almost_equal, assert_allclose)
 import pytest
 
+from mne import (SourceEstimate, VolSourceEstimate, MixedSourceEstimate,
+                 SourceSpaces)
 from mne.fixes import has_numba
 from mne.parallel import _force_serial
 from mne.stats import cluster_level, ttest_ind_no_p, combine_adjacency
@@ -610,16 +612,44 @@ def ttest_1samp(X):
     return stats.ttest_1samp(X, 0)[0]
 
 
-def test_summarize_clusters():
+@pytest.mark.parametrize('kind', ('surface', 'volume', 'mixed'))
+def test_summarize_clusters(kind):
     """Test cluster summary stcs."""
-    clu = (np.random.random([1, 20484]),
+    src_surf = SourceSpaces(
+        [dict(vertno=np.arange(10242), type='surf') for _ in range(2)])
+    assert src_surf.kind == 'surface'
+    src_vol = SourceSpaces(
+        [dict(vertno=np.arange(10), type='vol')])
+    assert src_vol.kind == 'volume'
+    if kind == 'surface':
+        src = src_surf
+        klass = SourceEstimate
+    elif kind == 'volume':
+        src = src_vol
+        klass = VolSourceEstimate
+    else:
+        assert kind == 'mixed'
+        src = src_surf + src_vol
+        klass = MixedSourceEstimate
+    n_vertices = sum(len(s['vertno']) for s in src)
+    clu = (np.random.random([1, n_vertices]),
            [(np.array([0]), np.array([0, 2, 4]))],
            np.array([0.02, 0.1]),
            np.array([12, -14, 30]))
-    stc_sum = summarize_clusters_stc(clu)
+    kwargs = dict()
+    if kind == 'volume':
+        with pytest.raises(ValueError, match='did not match'):
+            summarize_clusters_stc(clu)
+        assert len(src) == 1
+        kwargs['vertices'] = [src[0]['vertno']]
+    elif kind == 'mixed':
+        kwargs['vertices'] = src
+    stc_sum = summarize_clusters_stc(clu, **kwargs)
+    assert isinstance(stc_sum, klass)
     assert stc_sum.data.shape[1] == 2
     clu[2][0] = 0.3
-    pytest.raises(RuntimeError, summarize_clusters_stc, clu)
+    with pytest.raises(RuntimeError, match='No significant'):
+        summarize_clusters_stc(clu, **kwargs)
 
 
 def test_permutation_test_H0(numba_conditional):