Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dictionary-type ref_channels in set_eeg_reference() #12366

Open
wants to merge 71 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
1bce965
init the PR draft
qian-chu Jan 16, 2024
f42d5fb
Merge branch 'main' into dict_ref
qian-chu Jan 16, 2024
278fdf3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2024
f3cff5a
Create 12366.newfeature.rst
qian-chu Jan 16, 2024
b7b5c0c
Update 12366.newfeature.rst
qian-chu Jan 17, 2024
7ece510
Merge branch 'mne-tools:main' into dict_ref
qian-chu May 2, 2024
8d4516d
Add custom reference based on dict
May 3, 2024
42f45b8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 3, 2024
95a1434
BF: use isintance to check if dict
May 3, 2024
c921d69
BF: remove extra copy of data
May 3, 2024
b0c91d2
Add custom reference
May 3, 2024
78a5c7e
change doc (add Alex)
May 3, 2024
ca6908c
Add warning if bad channels in re-referencing scheme
May 3, 2024
6ac7bed
Merge branch 'dict_ref' of https://github.com/qian-chu/mne-python int…
May 3, 2024
b1165b9
add _check_before_dict_reference and enrich set_eeg_reference_see_als…
qian-chu May 3, 2024
ed56c97
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 3, 2024
d27ce12
Update reference.py
qian-chu May 31, 2024
073ca9d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2024
8d744ca
Update test_reference.py
qian-chu May 31, 2024
9e9507d
Update reference.py
qian-chu Jun 3, 2024
24d16a5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
2a6db40
Update test_reference.py
qian-chu Jun 3, 2024
6699de0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
8a5232a
formatting
qian-chu Jun 3, 2024
73ad30d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
3474370
dict does not accept repeated keys, no need to test
qian-chu Jun 5, 2024
cd213ee
add test for warnings and raises
qian-chu Jun 5, 2024
6c79a60
Update test_reference.py
qian-chu Jun 5, 2024
e36ddd2
Update docs.py
qian-chu Jun 5, 2024
41893f3
Merge branch 'mne-tools:main' into dict_ref
qian-chu Jun 5, 2024
5b8bd94
Data check test
AlexLepauvre Jun 7, 2024
f354d0a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 7, 2024
6dc0633
Add check of reference flag and bug correction
AlexLepauvre Jun 7, 2024
0293e7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 7, 2024
32fa6ac
Add tests for epochs object
AlexLepauvre Jun 7, 2024
1fa6a4e
Merge branch 'dict_ref' of https://github.com/qian-chu/mne-python int…
AlexLepauvre Jun 7, 2024
18cf0d9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 7, 2024
aee05a5
Merge branch 'main' into dict_ref
qian-chu Jun 7, 2024
dcd8f9d
formatting
qian-chu Jun 10, 2024
89a4e43
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2024
c9a52d4
Add warning for re-referencing electrode by itself
AlexLepauvre Jun 19, 2024
e5cb792
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 19, 2024
f0cac35
Refactorize tests of epochs and raws
AlexLepauvre Jun 19, 2024
12ae73a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 19, 2024
10da1dd
Apply suggestions from code review
qian-chu Jun 19, 2024
d284912
Re-organize warnings and errors
qian-chu Jun 19, 2024
bc09e77
Merge branch 'main' into dict_ref
qian-chu Jun 21, 2024
0122aeb
Merge branch 'main' into dict_ref
qian-chu Jul 2, 2024
4432adc
new dict check function as suggested
qian-chu Jul 3, 2024
b3fade6
Merge branch 'main' into dict_ref
qian-chu Jul 3, 2024
d5770b0
simplify (now that we guarantee list-like dict vals)
drammock Jul 9, 2024
85d1eb1
warn when keys (not just vals) are bad chs
drammock Jul 9, 2024
eab984b
clearer var name; only compute mismatch pairs if needed
drammock Jul 9, 2024
1a57807
refactor: convert to ch indices in helper func
drammock Jul 9, 2024
dfaab21
return None (like REST/proj references) instead of copy of inst data
drammock Jul 9, 2024
6ff72b6
slightly less misleading docstring
drammock Jul 9, 2024
2ec411f
modularize check_ssp and adds the check to dict-based reference
qian-chu Jul 29, 2024
2d6eeff
Merge branch 'main' into dict_ref
qian-chu Jul 29, 2024
1025e98
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 29, 2024
8858bfc
doc improvement
qian-chu Jul 30, 2024
d39c160
Merge branch 'main' into dict_ref
qian-chu Jul 30, 2024
7dae296
Merge branch 'main' into dict_ref
qian-chu Aug 10, 2024
71b31bb
Alex not new contributor now
qian-chu Aug 23, 2024
79f5bc4
Merge branch 'main' into dict_ref
qian-chu Aug 23, 2024
7704e35
Merge branch 'dict_ref' of https://github.com/qian-chu/mne-python int…
qian-chu Aug 23, 2024
2af639b
Update 12366.newfeature.rst
qian-chu Aug 23, 2024
f677570
Update 12366.newfeature.rst
qian-chu Aug 23, 2024
85b90ff
Merge branch 'main' into dict_ref
qian-chu Sep 13, 2024
c1d0339
MAINT: Reorder
larsoner Sep 20, 2024
8297a5f
Merge branch 'main' into dict_ref
larsoner Sep 20, 2024
2ce1e8d
Merge branch 'main' into dict_ref
qian-chu Sep 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changes/devel/12366.newfeature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for `dict` type argument ``ref_channels`` to :func:`mne.set_eeg_reference`, to allow flexible re-referencing (e.g. ``raw.set_eeg_reference(ref_channels={'A1': ['A2', 'A3']})`` will set the new A1 data to be ``A1 - (A2 + A3)/2``), by :newcontrib:`Alex Lepauvre` and `Qian Chu`_
2 changes: 2 additions & 0 deletions doc/changes/names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

.. _Alex Kiefer: https://home.alexk101.dev

.. _Alex Lepauvre: https://github.com/AlexLepauvre

.. _Alex Rockhill: https://github.com/alexrockhill/

.. _Alexander Rudiuk: https://github.com/ARudiuk
Expand Down
80 changes: 79 additions & 1 deletion mne/_fiff/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,56 @@ def _check_before_reference(inst, ref_from, ref_to, ch_type):
return ref_to


def _check_before_dict_reference(inst, ref_dict):
qian-chu marked this conversation as resolved.
Show resolved Hide resolved
ref_from_channels = set()
for key, value in ref_dict.items():
# Check keys
# Check that keys are strings
assert isinstance(key, str), (
"Keys in dict-type ref_channels must be strings, " f"got {type(key)}"
)
# Check that keys are not repeated
assert key not in ref_from_channels, (
"Keys in dict-type ref_channels must be unique, " f"got repeated key {key}"
)
# Check that keys are in ch_names
assert (
key in inst.ch_names
), f"Channel {key} in ref_channels is not in the instance"
ref_from_channels.add(key)

# Check values
if isinstance(value, str):
# Check that value is in ch_names
assert (
value in inst.ch_names
), f"Channel {value} in ref_channels is not in the instance"
# If value is a bad channel, issue a warning
if value in inst.info["bads"]:
msg = f"Channel {value} in ref_channels is marked as bad!"
_on_missing("warns", msg)
elif isinstance(value, list):
for val in value:
# Check that values are strings
assert isinstance(val, str), (
"Values in dict-type ref_channels must be strings or "
f"lists of strings, got {type(val)}"
)
# Check that values are in ch_names
assert (
val in inst.ch_names
), f"Channel {val} in ref_channels is not in the instance"
# If value is a bad channel, issue a warning
if val in inst.info["bads"]:
msg = f"Channel {val} in ref_channels is marked as bad!"
_on_missing("warns", msg)
else:
raise ValueError(
"Values in dict-type ref_channels must be strings or "
f"lists of strings, got {type(value)}"
)


def _apply_reference(inst, ref_from, ref_to=None, forward=None, ch_type="auto"):
"""Apply a custom EEG referencing scheme."""
ref_to = _check_before_reference(inst, ref_from, ref_to, ch_type)
Expand Down Expand Up @@ -155,6 +205,31 @@ def _apply_reference(inst, ref_from, ref_to=None, forward=None, ch_type="auto"):
return inst, ref_data


def _apply_dict_reference(inst, ref_dict):
"""Apply a dict-based custom EEG referencing scheme."""
_check_before_dict_reference(inst, ref_dict)

# Copy the data instance to re-reference:
ref_to_data = inst.copy()._data
if len(ref_dict) > 0:
qian-chu marked this conversation as resolved.
Show resolved Hide resolved
# Loop through each channel to re-reference:
for ch in ref_dict.keys():
assert len(ref_dict[ch]) > 0, f"No channel to re-reference ch-{ch}"
# Get indices of the channels to use as reference
ref_from = pick_channels(inst.ch_names, ref_dict[ch], ordered=True)
# Get indice of channel to re.reference:
ref_to = pick_channels(inst.ch_names, ch, ordered=True)
qian-chu marked this conversation as resolved.
Show resolved Hide resolved
# Compute the reference data:
ref_data = inst._data[..., ref_from, :].mean(-2, keepdims=True)
# Subtract the reference data to the channel to re-reference:
ref_to_data[..., ref_to, :] -= ref_data
# Add the data back to the instance:
inst._data = ref_to_data
# Set that custom reference was applied:
inst.info["custom_ref_applied"] = FIFF.FIFFV_MNE_CUSTOM_REF_ON
return inst, ref_to_data


@fill_doc
def add_reference_channels(inst, ref_channels, copy=True):
"""Add reference channels to data that consists of all zeros.
Expand Down Expand Up @@ -430,7 +505,10 @@ def set_eeg_reference(
"reference."
)

return _apply_reference(inst, ref_channels, ch_sel, forward, ch_type=ch_type)
if isinstance(ref_channels, dict):
return _apply_dict_reference(inst, ref_channels)
else:
return _apply_reference(inst, ref_channels, ch_sel, forward, ch_type=ch_type)


def _get_ch_type(inst, ch_type):
Expand Down
22 changes: 20 additions & 2 deletions mne/utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3660,13 +3660,20 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75):
"""

docdict["ref_channels_set_eeg_reference"] = """
ref_channels : list of str | str
ref_channels : list of str | str | dict
Can be:

- The name(s) of the channel(s) used to construct the reference.
- The name(s) of the channel(s) used to construct the reference for
every channel of ``ch_type``.
- ``'average'`` to apply an average reference (default)
- ``'REST'`` to use the Reference Electrode Standardization Technique
infinity reference :footcite:`Yao2001`.
- A dictionary mapping names of channels to be referenced to (a list of)
names of channels to use as reference. This is the most flexible
re-referencing approaching. For example, {'A1': 'A3'} would replace the
data in channel 'A1' with the difference between 'A1' and 'A3'. To take
the average of multiple channels as reference, supply a list of channel
names as the dictionary value, e.g. {'A1': ['A2', 'A3']}.
qian-chu marked this conversation as resolved.
Show resolved Hide resolved
- An empty list, in which case MNE will not attempt any re-referencing of
the data
"""
Expand Down Expand Up @@ -3995,6 +4002,17 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75):
The given EEG electrodes are referenced to a point at infinity using the
lead fields in ``forward``, which helps standardize the signals.

- Different references for different channels
Set ``ref_channels`` to a dictionary mapping source channel names (str)
to the reference channel names (str or list of str). Unlike the other
approaches where the same reference is applied globally, you can set
different references for different channels with this method. For example,
to re-reference channel 'A1' to 'A2' and 'B1' to the average of 'B2' and
'B3', set ``ref_channels={'A1': 'A2', 'B1': ['B2', 'B3']}``. Keys in the
dictionary must be unique. Warnings are issued when a bad channel is
used as a reference and when a mapping involves channels of different
types.

1. If a reference is requested that is not the average reference, this
function removes any pre-existing average reference projections.

Expand Down
Loading