Repository: mne-tools/mne-python

Issue:
BUG: read_selection does not deal with old/new neuromag ch_name formatting
channels won't be recognized if the whitespaces are mismatching between channels and layout.



Discussion:
do you have a proposed fix / PR or a script to reproduce?

I'll propose a fix, it's easier :) I know what's wrong, no time now.


Resolving patch:
diff --git a/mne/selection.py b/mne/selection.py
--- a/mne/selection.py
+++ b/mne/selection.py
@@ -6,43 +6,49 @@
 
 from os import path
 
+from .io.meas_info import Info
+from . import pick_types
 from .utils import logger, verbose
-from .externals import six
 
 
 @verbose
-def read_selection(name, fname=None, verbose=None):
+def read_selection(name, fname=None, info=None, verbose=None):
     """Read channel selection from file
 
-    By default, the selections used in mne_browse_raw are supported*.
+    By default, the selections used in ``mne_browse_raw`` are supported.
     Additional selections can be added by specifying a selection file (e.g.
-    produced using mne_browse_raw) using the fname parameter.
+    produced using ``mne_browse_raw``) using the ``fname`` parameter.
 
-    The name parameter can be a string or a list of string. The returned
+    The ``name`` parameter can be a string or a list of string. The returned
     selection will be the combination of all selections in the file where
     (at least) one element in name is a substring of the selection name in
-    the file. For example, "name = ['temporal', 'Right-frontal']" will produce
-    a comination of "Left-temporal", "Right-temporal", and "Right-frontal".
+    the file. For example, ``name=['temporal', 'Right-frontal']`` will produce
+    a combination of ``'Left-temporal'``, ``'Right-temporal'``, and
+    ``'Right-frontal'``.
 
     The included selections are:
 
-        * ``Vertex``
-        * ``Left-temporal``
-        * ``Right-temporal``
-        * ``Left-parietal``
-        * ``Right-parietal``
-        * ``Left-occipital``
-        * ``Right-occipital``
-        * ``Left-frontal``
-        * ``Right-frontal``
+        * ``'Vertex'``
+        * ``'Left-temporal'``
+        * ``'Right-temporal'``
+        * ``'Left-parietal'``
+        * ``'Right-parietal'``
+        * ``'Left-occipital'``
+        * ``'Right-occipital'``
+        * ``'Left-frontal'``
+        * ``'Right-frontal'``
 
 
     Parameters
     ----------
-    name : string or list of string
+    name : str or list of str
         Name of the selection. If is a list, the selections are combined.
-    fname : string
+    fname : str
         Filename of the selection file (if None, built-in selections are used).
+    info : instance of Info
+        Measurement info file, which will be used to determine the spacing
+        of channel names to return, e.g. ``'MEG 0111'`` for old Neuromag
+        systems and ``'MEG0111'`` for new ones.
     verbose : bool, str, int, or None
         If not None, override default verbose level (see mne.verbose).
 
@@ -53,59 +59,58 @@ def read_selection(name, fname=None, verbose=None):
     """
 
     # convert name to list of string
-    if isinstance(name, tuple):
-        name = list(name)
-
-    if not isinstance(name, list):
+    if not isinstance(name, (list, tuple)):
         name = [name]
+    if isinstance(info, Info):
+        picks = pick_types(info, meg=True, exclude=())
+        if len(picks) > 0 and ' ' not in info['ch_names'][picks[0]]:
+            spacing = 'new'
+        else:
+            spacing = 'old'
+    elif info is not None:
+        raise TypeError('info must be an instance of Info or None, not %s'
+                        % (type(info),))
+    else:  # info is None
+        spacing = 'old'
 
     # use built-in selections by default
     if fname is None:
         fname = path.join(path.dirname(__file__), 'data', 'mne_analyze.sel')
 
-    if not path.exists(fname):
+    if not path.isfile(fname):
         raise ValueError('The file %s does not exist.' % fname)
 
     # use this to make sure we find at least one match for each name
-    name_found = {}
-    for n in name:
-        name_found[n] = False
-
-    fid = open(fname, 'r')
-    sel = []
-
-    for line in fid:
-        line = line.strip()
-
-        # skip blank lines and comments
-        if len(line) == 0 or line[0] == '#':
-            continue
-
-        # get the name of the selection in the file
-        pos = line.find(':')
-        if pos < 0:
-            logger.info('":" delimiter not found in selections file, '
-                        'skipping line')
-            continue
-
-        sel_name_file = line[:pos]
-
-        # search for substring match with name provided
-        for n in name:
-            if sel_name_file.find(n) >= 0:
-                sel.extend(line[pos + 1:].split('|'))
-                name_found[n] = True
-                break
-
-    fid.close()
+    name_found = dict((n, False) for n in name)
+    with open(fname, 'r') as fid:
+        sel = []
+        for line in fid:
+            line = line.strip()
+            # skip blank lines and comments
+            if len(line) == 0 or line[0] == '#':
+                continue
+            # get the name of the selection in the file
+            pos = line.find(':')
+            if pos < 0:
+                logger.info('":" delimiter not found in selections file, '
+                            'skipping line')
+                continue
+            sel_name_file = line[:pos]
+            # search for substring match with name provided
+            for n in name:
+                if sel_name_file.find(n) >= 0:
+                    sel.extend(line[pos + 1:].split('|'))
+                    name_found[n] = True
+                    break
 
     # make sure we found at least one match for each name
-    for n, found in six.iteritems(name_found):
+    for n, found in name_found.items():
         if not found:
             raise ValueError('No match for selection name "%s" found' % n)
 
     # make the selection a sorted list with unique elements
     sel = list(set(sel))
     sel.sort()
-
+    if spacing == 'new':  # "new" or "old" by now, "old" is default
+        sel = [s.replace('MEG ', 'MEG') for s in sel]
     return sel


Test patch:
diff --git a/mne/tests/test_selection.py b/mne/tests/test_selection.py
--- a/mne/tests/test_selection.py
+++ b/mne/tests/test_selection.py
@@ -1,4 +1,14 @@
+import os.path as op
+
+from nose.tools import assert_true, assert_equal, assert_raises
+
 from mne import read_selection
+from mne.io import read_raw_fif
+from mne.utils import run_tests_if_main
+
+test_path = op.join(op.split(__file__)[0], '..', 'io', 'tests', 'data')
+raw_fname = op.join(test_path, 'test_raw.fif')
+raw_new_fname = op.join(test_path, 'test_chpi_raw_sss.fif')
 
 
 def test_read_selection():
@@ -10,18 +20,31 @@ def test_read_selection():
                  'Right-parietal', 'Left-occipital', 'Right-occipital',
                  'Left-frontal', 'Right-frontal']
 
+    raw = read_raw_fif(raw_fname)
     for i, name in enumerate(sel_names):
         sel = read_selection(name)
-        assert(ch_names[i] in sel)
+        assert_true(ch_names[i] in sel)
+        sel_info = read_selection(name, info=raw.info)
+        assert_equal(sel, sel_info)
 
     # test some combinations
     all_ch = read_selection(['L', 'R'])
     left = read_selection('L')
     right = read_selection('R')
 
-    assert(len(all_ch) == len(left) + len(right))
-    assert(len(set(left).intersection(set(right))) == 0)
+    assert_true(len(all_ch) == len(left) + len(right))
+    assert_true(len(set(left).intersection(set(right))) == 0)
 
     frontal = read_selection('frontal')
     occipital = read_selection('Right-occipital')
-    assert(len(set(frontal).intersection(set(occipital))) == 0)
+    assert_true(len(set(frontal).intersection(set(occipital))) == 0)
+
+    ch_names_new = [ch.replace(' ', '') for ch in ch_names]
+    raw_new = read_raw_fif(raw_new_fname)
+    for i, name in enumerate(sel_names):
+        sel = read_selection(name, info=raw_new.info)
+        assert_true(ch_names_new[i] in sel)
+
+    assert_raises(TypeError, read_selection, name, info='foo')
+
+run_tests_if_main()