Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a85093c
adding unify channels to preproc
anaradanovic Sep 25, 2023
02ecd49
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2023
0284caa
Update mne/preprocessing/unify_bads.py
anaradanovic Sep 25, 2023
c3e9794
Update mne/preprocessing/unify_bads.py
anaradanovic Sep 25, 2023
aeb7a42
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2023
32f8b35
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2023
c222956
updates to inprogress work unify bads
anaradanovic Sep 25, 2023
0fa5b18
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2023
2c200a1
Move unify_bad_channels function to preprocessing/bads.py
anaradanovic Sep 26, 2023
80c329d
add function to namespace
anaradanovic Sep 26, 2023
fc91787
style fixes
anaradanovic Sep 26, 2023
d77cfc1
Merge branch 'unifying_bads' of github.com:anaradanovic/mne-python in…
anaradanovic Sep 26, 2023
58b96c3
test draft
nordme Sep 26, 2023
7c28118
make functions work
nordme Sep 26, 2023
d89431e
style fixes
nordme Sep 26, 2023
9b745cb
style fix
nordme Sep 27, 2023
b32f318
fixes
nordme Sep 27, 2023
023db18
adding ch_name check to function
anaradanovic Sep 27, 2023
ce03592
moving len check to first check
anaradanovic Sep 27, 2023
dc8a71a
reconcile with Ana
nordme Sep 27, 2023
d64c00c
Merge remote-tracking branch 'anaradanovic/unifying_bads' into unifych
nordme Sep 27, 2023
7e38dd5
changing ch_name check
anaradanovic Sep 27, 2023
10b2e99
Merge remote-tracking branch 'anaradanovic/unifying_bads' into unifych
nordme Sep 27, 2023
2d4528e
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
a525f33
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
ccba935
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
e005014
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
adfb46b
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
cfa1c74
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
3085fa0
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
12b8e84
Update mne/preprocessing/bads.py
anaradanovic Sep 27, 2023
9cb0cf3
further changes on unify_bad_channels
anaradanovic Sep 27, 2023
52c4d21
Apply suggestions from code review
drammock Sep 27, 2023
83407f6
fixes for Ana fixes
nordme Sep 27, 2023
fe15459
Merge remote-tracking branch 'anaradanovic/unifying_bads' into unifych
nordme Sep 27, 2023
df45b86
move tests file
nordme Sep 27, 2023
8b37d1e
Update mne/channels/channels.py
drammock Sep 27, 2023
2cfc6da
Merge remote-tracking branch 'anaradanovic/unifying_bads' into unifych
nordme Sep 27, 2023
b7834d5
Apply more suggestions from code review
drammock Sep 27, 2023
47db7e6
Merge remote-tracking branch 'anaradanovic/unifying_bads' into unifych
nordme Sep 27, 2023
0309925
Apply suggestions from code review
nordme Sep 27, 2023
6c610d1
pytest fix
nordme Sep 27, 2023
292c3e4
Update mne/channels/channels.py
larsoner Sep 28, 2023
2fa2fdf
Merge pull request #1 from nordme/unifych
anaradanovic Sep 28, 2023
7f80011
tst:ping CIs
anaradanovic Sep 28, 2023
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/preprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Projections:
get_builtin_ch_adjacencies
read_ch_adjacency
equalize_channels
unify_bad_channels
rename_channels
generate_2d_layout
make_1020_channel_selections
Expand Down
1 change: 1 addition & 0 deletions mne/channels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"_EEG_SELECTIONS",
"_divide_to_regions",
"get_builtin_ch_adjacencies",
"unify_bad_channels",
],
"layout": [
"Layout",
Expand Down
79 changes: 79 additions & 0 deletions mne/channels/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# Andrew Dykstra <andrew.r.dykstra@gmail.com>
# Teon Brooks <teon.brooks@gmail.com>
# Daniel McCloy <dan.mccloy@gmail.com>
# Ana Radanovic <radanovica@protonmail.com>
# Erica Peterson <nordme@uw.edu>
#
# License: BSD-3-Clause

Expand Down Expand Up @@ -206,6 +208,83 @@ def equalize_channels(instances, copy=True, verbose=None):
return equalized_instances


def unify_bad_channels(insts):
"""Unify bad channels across a list of instances.

All instances must be of the same type and have matching channel names and channel
order. The ``.info["bads"]`` of each instance will be set to the union of
``.info["bads"]`` across all instances.

Parameters
----------
insts : list
List of instances (:class:`~mne.io.Raw`, :class:`~mne.Epochs`,
:class:`~mne.Evoked`, :class:`~mne.time_frequency.Spectrum`,
:class:`~mne.time_frequency.EpochsSpectrum`) across which to unify bad channels.

Returns
-------
insts : list
List of instances with bad channels unified across instances.

See Also
--------
mne.channels.equalize_channels
mne.channels.rename_channels
mne.channels.combine_channels

Notes
-----
This function modifies the instances in-place.

.. versionadded:: 1.6
"""
from ..io import BaseRaw
from ..epochs import Epochs
from ..evoked import Evoked
from ..time_frequency.spectrum import BaseSpectrum

# ensure input is list-like
_validate_type(insts, (list, tuple), "insts")
# ensure non-empty
if len(insts) == 0:
raise ValueError("insts must not be empty")
# ensure all insts are MNE objects, and all the same type
inst_type = type(insts[0])
valid_types = (BaseRaw, Epochs, Evoked, BaseSpectrum)
for inst in insts:
_validate_type(inst, valid_types, "each object in insts")
if type(inst) != inst_type:
raise ValueError("All insts must be the same type")

# ensure all insts have the same channels and channel order
ch_names = insts[0].ch_names
for inst in insts[1:]:
dif = set(inst.ch_names) ^ set(ch_names)
if len(dif):
raise ValueError(
"Channels do not match across the objects in insts. Consider calling "
"equalize_channels before calling this function."
)
elif inst.ch_names != ch_names:
raise ValueError(
"Channel names are sorted differently across instances. Please use "
"mne.channels.equalize_channels."
)

# collect bads as dict keys so that insertion order is preserved, then cast to list
all_bads = dict()
for inst in insts:
all_bads.update(dict.fromkeys(inst.info["bads"]))
all_bads = list(all_bads)

# update bads on all instances
for inst in insts:
inst.info["bads"] = all_bads

return insts


class ReferenceMixin(MontageMixin):
"""Mixin class for Raw, Evoked, Epochs."""

Expand Down
53 changes: 53 additions & 0 deletions mne/channels/tests/test_unify_bads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest
from mne.channels import unify_bad_channels


def test_error_raising(raw, epochs):
"""Tests input checking."""
with pytest.raises(TypeError, match=r"must be an instance of list"):
unify_bad_channels("bad input")
with pytest.raises(ValueError, match=r"insts must not be empty"):
unify_bad_channels([])
with pytest.raises(TypeError, match=r"each object in insts must be an instance of"):
unify_bad_channels(["bad_instance"])
with pytest.raises(ValueError, match=r"same type"):
unify_bad_channels([raw, epochs])
with pytest.raises(ValueError, match=r"Channels do not match across"):
raw_alt1 = raw.copy()
raw_alt1.drop_channels(raw.info["ch_names"][-1])
unify_bad_channels([raw, raw_alt1]) # ch diff preserving order
with pytest.raises(ValueError, match=r"sorted differently"):
raw_alt2 = raw.copy()
new_order = [raw.ch_names[-1]] + raw.ch_names[:-1]
raw_alt2.reorder_channels(new_order)
unify_bad_channels([raw, raw_alt2])


def test_bads_compilation(raw):
"""Tests that bads are compiled properly.

Tests two cases: a) single instance passed to function with an existing
bad, and b) multiple instances passed to function with varying compilation
scenarios including empty bads, unique bads, and partially duplicated bads
listed out-of-order.

Only the Raw instance type is tested, since bad channel implementation is
controlled across instance types with a MixIn class.
"""
assert raw.info["bads"] == []
chns = raw.ch_names[:3]
no_bad = raw.copy()
one_bad = raw.copy()
one_bad.info["bads"] = [chns[1]]
three_bad = raw.copy()
three_bad.info["bads"] = chns
# scenario 1: single instance passed with actual bads
s_out = unify_bad_channels([one_bad])
assert len(s_out) == 1, len(s_out)
assert s_out[0].info["bads"] == [chns[1]], (s_out[0].info["bads"], chns[1])
# scenario 2: multiple instances passed
m_out = unify_bad_channels([one_bad, no_bad, three_bad])
assert len(m_out) == 3, len(m_out)
expected_order = [chns[1], chns[0], chns[2]]
for inst in m_out:
assert inst.info["bads"] == expected_order