Files
2017-03-10 12:24:05 -05:00

191 lines
7.0 KiB
Python

# Authors: Alexandre Gramfort <alexandre.gramfort@telecom-paristech.fr>
# Matti Hamalainen <msh@nmr.mgh.harvard.edu>
# Martin Luessi <mluessi@nmr.mgh.harvard.edu>
#
# License: BSD (3-clause)
from os import path
import numpy as np
from .io.meas_info import Info
from .io.pick import _pick_data_channels, pick_types
from .utils import logger, verbose, _get_stim_channel
_SELECTIONS = ['Vertex', 'Left-temporal', 'Right-temporal', 'Left-parietal',
'Right-parietal', 'Left-occipital', 'Right-occipital',
'Left-frontal', 'Right-frontal']
_EEG_SELECTIONS = ['EEG 1-32', 'EEG 33-64', 'EEG 65-96', 'EEG 97-128']
@verbose
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.
Additional selections can be added by specifying a selection file (e.g.
produced using ``mne_browse_raw``) using the ``fname`` parameter.
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 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'``
Parameters
----------
name : str or list of str
Name of the selection. If is a list, the selections are combined.
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 :func:`mne.verbose`
and :ref:`Logging documentation <tut_logging>` for more).
Returns
-------
sel : list of string
List with channel names in the selection.
"""
# convert name to list of string
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.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 = 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 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
def _divide_to_regions(info, add_stim=True):
"""Divide channels to regions by positions."""
from scipy.stats import zscore
picks = _pick_data_channels(info, exclude=[])
chs_in_lobe = len(picks) // 4
pos = np.array([ch['loc'][:3] for ch in info['chs']])
x, y, z = pos.T
frontal = picks[np.argsort(y[picks])[-chs_in_lobe:]]
picks = np.setdiff1d(picks, frontal)
occipital = picks[np.argsort(y[picks])[:chs_in_lobe]]
picks = np.setdiff1d(picks, occipital)
temporal = picks[np.argsort(z[picks])[:chs_in_lobe]]
picks = np.setdiff1d(picks, temporal)
lt, rt = _divide_side(temporal, x)
lf, rf = _divide_side(frontal, x)
lo, ro = _divide_side(occipital, x)
lp, rp = _divide_side(picks, x) # Parietal lobe from the remaining picks.
# Because of the way the sides are divided, there may be outliers in the
# temporal lobes. Here we switch the sides for these outliers. For other
# lobes it is not a big problem because of the vicinity of the lobes.
with np.errstate(invalid='ignore'): # invalid division, greater compare
zs = np.abs(zscore(x[rt]))
outliers = np.array(rt)[np.where(zs > 2.)[0]]
rt = list(np.setdiff1d(rt, outliers))
with np.errstate(invalid='ignore'): # invalid division, greater compare
zs = np.abs(zscore(x[lt]))
outliers = np.append(outliers, (np.array(lt)[np.where(zs > 2.)[0]]))
lt = list(np.setdiff1d(lt, outliers))
l_mean = np.mean(x[lt])
r_mean = np.mean(x[rt])
for outlier in outliers:
if abs(l_mean - x[outlier]) < abs(r_mean - x[outlier]):
lt.append(outlier)
else:
rt.append(outlier)
if add_stim:
stim_ch = _get_stim_channel(None, info, raise_error=False)
if len(stim_ch) > 0:
for region in [lf, rf, lo, ro, lp, rp, lt, rt]:
region.append(info['ch_names'].index(stim_ch[0]))
return {'Left-frontal': lf, 'Right-frontal': rf, 'Left-parietal': lp,
'Right-parietal': rp, 'Left-occipital': lo, 'Right-occipital': ro,
'Left-temporal': lt, 'Right-temporal': rt}
def _divide_side(lobe, x):
"""Make a separation between left and right lobe evenly."""
lobe = np.asarray(lobe)
median = np.median(x[lobe])
left = lobe[np.where(x[lobe] < median)[0]]
right = lobe[np.where(x[lobe] > median)[0]]
medians = np.where(x[lobe] == median)[0]
left = np.sort(np.concatenate([left, lobe[medians[1::2]]]))
right = np.sort(np.concatenate([right, lobe[medians[::2]]]))
return list(left), list(right)