Repository: mne-tools/mne-python

Issue:
Feature: Source labels simulation
As I said in #5058 , I start the discussion of source labels simulation feature, so feel free to participate in it.

The objective is to create a function which would provide a simple an general way to simulate cortical regions which would be used for stc simulation.

There is already a function `grow_labels` in MNE which creates growing regions around a seed point but it requires some extra scripting from user that, I think, can be included to API.
For a moment, here is a possible API of such a function.

```
def simulate_source_labels(subject, parcels, seeds, extents,
                           overlap=True, grow_outside_parcel=True, subjects_dir=None,
                           n_jobs=1, names=None,random_state=None, surface='white'):
    """Simulate source labels.
    
    Parameters
    ----------
    subject : string
        Name of the subject as in SUBJECTS_DIR.
    parcels : instance of Label, str | list
        Define where the source seed will be chosen. If str, can be
        'rh' or 'lh'.
    seeds : int, str | list
        Seed, or list of seeds. If int, seed represents a vertex number in
        corresponding parcel. If str, can be either 'random' or 'barycenter'.
    extents : float | list
        Extents (radius in mm) of the labels, i.e. maximum geodesic distance 
        on the white matter surface from the seed. If 0, source contains only one
        seed vertex.
    overlap : bool
        Produce overlapping labels. If True (default), the resulting labels
        can be overlapping. If False, each label will be grown one step at a
        time, and occupied territory will not be invaded.
    grow_outside_parcel : bool | list
        Let regions grow outside the parcel where seed was defined.
    subjects_dir : string
        Path to SUBJECTS_DIR if not set in the environment.
    n_jobs : int
        Number of jobs to run in parallel.
    names : None | list of str
        Assign names to the new labels (list needs to have the same length as
        seeds).
    random_state : None | int | np.random.RandomState
        To specify the random generator state.
    surface : string
        The surface used to grow the labels, defaults to the white surface.
    
    Returns
    -------
    labels : list of Label
    """
```
Usage example:

```
subject = 'sample'
labels = read_labels_from_annot(subject)

##################################################3
# Simulate 3 source regions with different parameters:
# 1. Select random seed inside "bankssts-lh" parcel, grow region of radius 10mm
#    which can potentially grow outside the initial parcel.
# 2. Select barycenter of "bankssts-rh" parcel, grow region of radius 15mm 
#    and constrain the region to lie completely inside the initial parcel.
# 3. Select 42th vertice of right hemi surface mesh and keep it as a single source.

parcels = [labels[0], labels[1], 'rh']
seeds = ['random', 'barycenter', 42]
extents = [10., 15, 0]
gop=[True, False, True]
source_labels = simulate_source_labels(subject, parcels, seeds, extents, 
                                       grow_outside_parcel=gop)
```

Another important question to discuss is adding visual interface which would allow manual selection of sources on the cortical mesh. @christospapa can provide some code which already exists.



Discussion:
@makkostya could you open a PR with the code you are proposing and reference #5058? Ping me if you need any help with it.
For the moment there is no code to pull. I just started the discussion.
I would go with:

```
def simulate_label(subject, label, seeds, extent=0.,
                                grow_outside=True, subjects_dir=None,
                                name=None, random_state=None,
                                surface='white'):
    """Simulate source labels.
    
    Parameters
    ----------
    subject : string
        Name of the subject as in SUBJECTS_DIR.
    label : instance of Label | str
        Define where the source seed will be chosen. If str, can be
        'rh' or 'lh'.
    location : 'random' | 'barycenter' | int
        Location to grow label from. If int, location represents the vertex number
        in corresponding label. If str, can be either 'random' or 'barycenter'.
    extent : float
        Extents (radius in mm) of the labels, i.e. maximum geodesic distance 
        on the white matter surface from the seed. If 0, source contains only one
        seed vertex.
    grow_outside : bool
        Let labels grow outside the original label where location was defined.
    subjects_dir : string
        Path to SUBJECTS_DIR if not set in the environment.
    names : None | str
        Assign name to the new label.
    random_state : None | int | np.random.RandomState
        To specify the random generator state.
    surface : string
        The surface used to simulated the label, defaults to the white surface.
  
    Returns
    -------
    label : instance of Label
            The simulated label.
    """
```

then if you want many labels just use a for loop.

wdyt?

I put list of labels as possible input because theoretically user could want to simulate two regions issued from different parcels, allowed to grow outside their parcels but not allow these regions to intersect. In this case two parcels could not be treated independently via for loop. Even though I think that this scenario is quite unlikely.

Another advantage of giving list of labels as an input is possibility of using "n_jobs" parameter of `grow_labels`  function which will be called inside `simulate_labels` for parallel computing.
let's do one thing at a time and add options when need emerges. I
think that n_jobs here is also overkill given how fast it should be to
compute.


Resolving patch:
diff --git a/mne/label.py b/mne/label.py
--- a/mne/label.py
+++ b/mne/label.py
@@ -2431,3 +2431,93 @@ def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False,
     for fname, annot, ctab, hemi_names in to_save:
         logger.info('   writing %d labels to %s' % (len(hemi_names), fname))
         _write_annot(fname, annot, ctab, hemi_names)
+
+
+def select_sources(subject, label, location='center', extent=0.,
+                   grow_outside=True, subjects_dir=None, name=None,
+                   random_state=None, surf='white'):
+    """Select sources from a label.
+
+    Parameters
+    ----------
+    subject : string
+        Name of the subject as in SUBJECTS_DIR.
+    label : instance of Label | str
+        Define where the seed will be chosen. If str, can be 'lh' or 'rh',
+        which correspond to left or right hemisphere, respectively.
+    location : 'random' | 'center' | int
+        Location to grow label from. If the location is an int, it represents
+        the vertex number in the corresponding label. If it is a str, it can be
+        either 'random' or 'center'.
+    extent : float
+        Extents (radius in mm) of the labels, i.e. maximum geodesic distance
+        on the white matter surface from the seed. If 0, the resulting label
+        will contain only one vertex.
+    grow_outside : bool
+        Let the region grow outside the original label where location was
+        defined.
+    subjects_dir : string
+        Path to SUBJECTS_DIR if not set in the environment.
+    name : None | str
+        Assign name to the new label.
+    random_state : None | int | ~numpy.random.RandomState
+        To specify the random generator state.
+    surf : string
+        The surface used to simulated the label, defaults to the white surface.
+
+    Returns
+    -------
+    label : instance of Label
+        The label that contains the selected sources.
+
+    Notes
+    -----
+    This function selects a region of interest on the cortical surface based
+    on a label (or a hemisphere). The sources are selected by growing a region
+    around a seed which is selected randomly, is the center of the label, or
+    is a specific vertex. The selected vertices can extend beyond the initial
+    provided label. This can be prevented by setting grow_outside to False.
+
+    The selected sources are returned in the form of a new Label object. The
+    values of the label contain the distance from the seed in millimeters.
+
+    .. versionadded:: 0.18
+    """
+    # If label is a string, convert it to a label that contains the whole
+    # hemisphere.
+    if isinstance(label, str):
+        _check_option('label', label, ['lh', 'rh'])
+        surf_filename = op.join(subjects_dir, subject, 'surf',
+                                label + '.white')
+        vertices, _ = read_surface(surf_filename)
+        indices = np.arange(len(vertices), dtype=int)
+        label = Label(indices, vertices, hemi=label)
+
+    # Choose the seed according to the selected strategy.
+    if isinstance(location, str):
+        _check_option('location', location, ['center', 'random'])
+
+        if location == 'center':
+            seed = label.center_of_mass(
+                subject, restrict_vertices=True, subjects_dir=subjects_dir,
+                surf=surf)
+        else:
+            rng = check_random_state(random_state)
+            seed = rng.choice(label.vertices)
+    else:
+        seed = label.vertices[location]
+
+    hemi = 0 if label.hemi == 'lh' else 1
+    new_label = grow_labels(subject, seed, extent, hemi, subjects_dir)[0]
+
+    # We override the name because grow_label automatically adds a -rh or -lh
+    # to the given parameter.
+    new_label.name = name
+
+    # Restrict the new label to the vertices of the input label if needed.
+    if not grow_outside:
+        to_keep = np.array([v in label.vertices for v in new_label.vertices])
+        new_label = Label(new_label.vertices[to_keep], new_label.pos[to_keep],
+                          hemi=new_label.hemi, name=name, subject=subject)
+
+    return new_label


Test patch:
diff --git a/mne/tests/test_label.py b/mne/tests/test_label.py
--- a/mne/tests/test_label.py
+++ b/mne/tests/test_label.py
@@ -1,3 +1,5 @@
+from itertools import product
+
 import glob
 import os
 import os.path as op
@@ -17,7 +19,8 @@
                  write_labels_to_annot, split_label, spatial_tris_connectivity,
                  read_surface, random_parcellation, morph_labels,
                  labels_to_stc)
-from mne.label import Label, _blend_colors, label_sign_flip, _load_vert_pos
+from mne.label import (Label, _blend_colors, label_sign_flip, _load_vert_pos,
+                       select_sources)
 from mne.utils import (_TempDir, requires_sklearn, get_subjects_dir,
                        run_tests_if_main)
 from mne.label import _n_colors
@@ -931,3 +934,51 @@ def test_label_center_of_mass():
 
 
 run_tests_if_main()
+
+
+@testing.requires_testing_data
+def test_select_sources():
+    """Test the selection of sources for simulation."""
+    subject = 'sample'
+    label_file = op.join(subjects_dir, subject, 'label', 'aparc',
+                         'temporalpole-rh.label')
+
+    # Regardless of other parameters, using extent 0 should always yield a
+    # a single source.
+    tp_label = read_label(label_file)
+    tp_label.values[:] = 1
+    labels = ['lh', tp_label]
+    locations = ['random', 'center']
+    for label, location in product(labels, locations):
+        label = select_sources(
+            subject, label, location, extent=0, subjects_dir=subjects_dir)
+        assert (len(label.vertices) == 1)
+
+    # As we increase the extent, the new region should contain the previous
+    # one.
+    label = select_sources(subject, 'lh', 0, extent=0,
+                           subjects_dir=subjects_dir)
+    for extent in range(1, 3):
+        new_label = select_sources(subject, 'lh', 0, extent=extent * 2,
+                                   subjects_dir=subjects_dir)
+        assert (set(new_label.vertices) > set(label.vertices))
+        assert (new_label.hemi == 'lh')
+        label = new_label
+
+    # With a large enough extent and not allowing growing outside the label,
+    # every vertex of the label should be in the region.
+    label = select_sources(subject, tp_label, 0, extent=30,
+                           grow_outside=False, subjects_dir=subjects_dir)
+    assert (set(label.vertices) == set(tp_label.vertices))
+
+    # Without this restriction, we should get new vertices.
+    label = select_sources(subject, tp_label, 0, extent=30,
+                           grow_outside=True, subjects_dir=subjects_dir)
+    assert (set(label.vertices) > set(tp_label.vertices))
+
+    # Other parameters are taken into account.
+    label = select_sources(subject, tp_label, 0, extent=10,
+                           grow_outside=False, subjects_dir=subjects_dir,
+                           name='mne')
+    assert (label.name == 'mne')
+    assert (label.hemi == 'rh')