191 lines
7.0 KiB
Python
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)
|