forked from mne-tools/mne-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MRG: selection.read_selection() ➡️ channels.read_vectorview_selection…
…() (mne-tools#8870) * Move read_selection() to channels.read_neuromag_selection() Fixes mne-tools#8865 * Fix Sphinx warning * read_neuromag_selection -> read_vectorview_selection * Use pytest context manager * Add API reference * Fix changelog * Update docstring * Update mne/channels/channels.py Co-authored-by: Eric Larson <[email protected]> * Docstring, import * fix changelog [skip azp][skip github] Co-authored-by: Eric Larson <[email protected]>
- Loading branch information
1 parent
24377ad
commit 53f1b85
Showing
16 changed files
with
213 additions
and
224 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
# Authors: Alexandre Gramfort <[email protected]> | ||
# Matti Hämäläinen <[email protected]> | ||
# Martin Luessi <[email protected]> | ||
# Denis Engemann <[email protected]> | ||
# Andrew Dykstra <[email protected]> | ||
# Teon Brooks <[email protected]> | ||
# Daniel McCloy <[email protected]> | ||
# | ||
# License: BSD (3-clause) | ||
|
||
|
||
import os | ||
import os.path as op | ||
import sys | ||
|
@@ -19,14 +21,16 @@ | |
from ..defaults import HEAD_SIZE_DEFAULT, _handle_default | ||
from ..transforms import _frame_to_str | ||
from ..utils import (verbose, logger, warn, | ||
_check_preload, _validate_type, fill_doc, _check_option) | ||
_check_preload, _validate_type, fill_doc, _check_option, | ||
_get_stim_channel, _check_fname) | ||
from ..io.compensator import get_current_comp | ||
from ..io.constants import FIFF | ||
from ..io.meas_info import anonymize_info, Info, MontageMixin, create_info | ||
from ..io.pick import (channel_type, pick_info, pick_types, _picks_by_type, | ||
_check_excludes_includes, _contains_ch_type, | ||
channel_indices_by_type, pick_channels, _picks_to_idx, | ||
_get_channel_types, get_channel_type_constants) | ||
_get_channel_types, get_channel_type_constants, | ||
_pick_data_channels) | ||
from ..io.write import DATE_NONE | ||
from ..io._digitization import _get_data_as_dict_from_dig | ||
|
||
|
@@ -1762,3 +1766,163 @@ def combine_channels(inst, groups, method='mean', keep_stim=False, | |
verbose=inst.verbose) | ||
|
||
return combined_inst | ||
|
||
|
||
# NeuroMag channel groupings | ||
_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'] | ||
|
||
|
||
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 OrderedDict([('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) | ||
|
||
|
||
@verbose | ||
def read_vectorview_selection(name, fname=None, info=None, verbose=None): | ||
"""Read Neuromag Vector View channel selection from a file. | ||
Parameters | ||
---------- | ||
name : str | list of str | ||
Name of the selection. If a list, the selections are combined. | ||
Supported selections are: ``'Vertex'``, ``'Left-temporal'``, | ||
``'Right-temporal'``, ``'Left-parietal'``, ``'Right-parietal'``, | ||
``'Left-occipital'``, ``'Right-occipital'``, ``'Left-frontal'`` and | ||
``'Right-frontal'``. Selections can also be matched and combined by | ||
spcecifying common substrings. For example, ``name='temporal`` will | ||
produce a combination of ``'Left-temporal'`` and ``'Right-temporal'``. | ||
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)s | ||
Returns | ||
------- | ||
sel : list of str | ||
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 = op.join(op.dirname(__file__), '..', 'data', 'mne_analyze.sel') | ||
|
||
fname = _check_fname(fname, must_exist=True, overwrite='read') | ||
|
||
# use this to make sure we find at least one match for each name | ||
name_found = {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 |
Oops, something went wrong.