From 69abd8baad4b121679b4fa155ebb54fb45628844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Sun, 16 Apr 2023 17:35:57 +0200 Subject: [PATCH] Allow retrieval of channel names via make_1020_channel_selections() Fixes #11597 --- doc/changes/latest.inc | 1 + .../contralateral_referencing.py | 9 ++-- mne/channels/channels.py | 45 ++++++++++++------- mne/channels/tests/test_channels.py | 5 +++ 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 84fb0e35d6b..9dc28d470f4 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -43,6 +43,7 @@ Enhancements - Improve performance of raw data browsing with many annotations (:gh:`11614` by `Eric Larson`_) - Add support for :func:`mne.preprocessing.maxwell_filter` with gradient-compensated CTF data, e.g., for tSSS-only mode (:gh:`10554` by `Eric Larson`_) - Add support for eyetracking data using :func:`mne.io.read_raw_eyelink` (:gh:`11152` by `Dominik Welke`_ and `Scott Huberty`_) +- :func:`mne.channels.make_1020_channel_selections` gained a new parameter, ``return_ch_names``, to allow for easy retrieval of EEG channel names corresponding to the left, right, and midline portions of the montage (:gh:`11632` by `Richard Höchenberger`_) Bugs ~~~~ diff --git a/examples/preprocessing/contralateral_referencing.py b/examples/preprocessing/contralateral_referencing.py index ad31d94f742..2c04ccc7c8f 100644 --- a/examples/preprocessing/contralateral_referencing.py +++ b/examples/preprocessing/contralateral_referencing.py @@ -12,7 +12,6 @@ contralateral EEG reference. """ -import numpy as np import mne ssvep_folder = mne.datasets.ssvep.data_path() @@ -31,11 +30,9 @@ }) # this splits electrodes into 3 groups; left, midline, and right -ch_indices = mne.channels.make_1020_channel_selections(raw.info) - -# convert indices to names -orig_names = np.array(raw.ch_names) -ch_names = {key: orig_names[idxs].tolist() for key, idxs in ch_indices.items()} +ch_names = mne.channels.make_1020_channel_selections( + raw.info, return_ch_names=True +) # remove the ref channels from the lists of to-be-rereferenced channels ch_names['Left'].remove('M1') diff --git a/mne/channels/channels.py b/mne/channels/channels.py index c3c86d20a34..d0ae5f01673 100644 --- a/mne/channels/channels.py +++ b/mne/channels/channels.py @@ -1837,31 +1837,39 @@ def _get_ch_info(info): @fill_doc -def make_1020_channel_selections(info, midline="z"): - """Return dict mapping from ROI names to lists of picks for 10/20 setups. - - This passes through all channel names, and uses a simple heuristic to - separate channel names into three Region of Interest-based selections: - Left, Midline and Right. The heuristic is that channels ending on any of - the characters in ``midline`` are filed under that heading, otherwise those - ending in odd numbers under "Left", those in even numbers under "Right". - Other channels are ignored. This is appropriate for 10/20 files, but not - for other channel naming conventions. - If an info object is provided, lists are sorted from posterior to anterior. +def make_1020_channel_selections(info, midline="z", *, return_ch_names=False): + """Map hemisphere names to corresponding EEG channel names or indices. + + This function uses a simple heuristic to separate channel names into three + Region of Interest-based selections: ``Left``, ``Midline`` and ``Right``. + + The heuristic is that any of the channel names ending + with odd numbers are filed under ``Left``; those ending with even numbers + are filed under ``Right``; and those ending with the character(s) specified + in ``midline`` are filed under ``Midline``. Other channels are ignored. + + This is appropriate for 10/20, 10/10, 10/05, …, sensor arrangements, but + not for other naming conventions. Parameters ---------- - %(info_not_none)s If possible, the channel lists will be sorted - posterior-to-anterior; otherwise they default to the order specified in - ``info["ch_names"]``. + %(info_not_none)s If channel locations are present, the channel lists will + be sorted from posterior to anterior; otherwise, the order specified in + ``info["ch_names"]`` will be kept. midline : str Names ending in any of these characters are stored under the - ``Midline`` key. Defaults to 'z'. Note that capitalization is ignored. + ``Midline`` key. Defaults to ``'z'``. Capitalization is ignored. + return_ch_names : bool + Whether to return channel names instead of channel indices. + + .. versionadded:: 1.4.0 Returns ------- selections : dict - A dictionary mapping from ROI names to lists of picks (integers). + A dictionary mapping from region of interest name to a list of channel + indices (if ``return_ch_names=False``) or to a list of channel names + (if ``return_ch_names=True``). """ _validate_type(info, "info") @@ -1891,6 +1899,11 @@ def make_1020_channel_selections(info, midline="z"): selections = {selection: np.array(picks)[pos[picks, 1].argsort()] for selection, picks in selections.items()} + # convert channel indices to names if requested + if return_ch_names: + for selection, ch_indices in selections.items(): + selections[selection] = [info.ch_names[idx] for idx in ch_indices] + return selections diff --git a/mne/channels/tests/test_channels.py b/mne/channels/tests/test_channels.py index 7634100afbe..585ce9b43cc 100644 --- a/mne/channels/tests/test_channels.py +++ b/mne/channels/tests/test_channels.py @@ -384,6 +384,11 @@ def test_1020_selection(): for channel, roi in zip(fz_c3_c4, ("Midline", "Left", "Right")): assert channel in sels[roi] + # ensure returning channel names works as expected + sels_names = make_1020_channel_selections(raw.info, return_ch_names=True) + for selection, ch_names in sels_names.items(): + assert ch_names == [raw.ch_names[idx] for idx in sels[selection]] + @testing.requires_testing_data def test_find_ch_adjacency():