Skip to content

Interpolate bridge electrodes when more than 2 electrodes are bridged together #11079

@mscheltienne

Description

@mscheltienne

Following @alexrockhill addition of https://mne.tools/stable/auto_examples/preprocessing/eeg_bridging.html and #10587

What about the case where more than 2 electrodes are bridged together, e.g. where a trio of electrodes are bridged together?

def interpolate_bridged_electrodes(inst, bridged_idx):
"""Interpolate bridged electrode pairs.
Because bridged electrodes contain brain signal, it's just that the
signal is spatially smeared between the two electrodes, we can
make a virtual channel midway between the bridged pairs and use
that to aid in interpolation rather than completely discarding the
data from the two channels.
Parameters
----------
inst : instance of Epochs, Evoked, or Raw
The data object with channels that are to be interpolated.
bridged_idx : list of tuple
The indices of channels marked as bridged with each bridged
pair stored as a tuple.
Returns
-------
inst : instance of Epochs, Evoked, or Raw
The modified data object.
See Also
--------
mne.preprocessing.compute_bridged_electrodes
"""
_validate_type(inst, (BaseRaw, BaseEpochs, Evoked))
montage = inst.get_montage()
if montage is None:
raise RuntimeError('No channel positions found in ``inst``')
pos = montage.get_positions()
if pos['coord_frame'] != 'head':
raise RuntimeError('Montage channel positions must be in ``head``'
'got {}'.format(pos['coord_frame']))
ch_pos = pos['ch_pos']
# store bads orig to put back at the end
bads_orig = inst.info['bads']
inst.info['bads'] = list()
# make virtual channels
virtual_chs = dict()
bads = set()
data = inst.get_data()
for idx0, idx1 in bridged_idx:
ch0 = inst.ch_names[idx0]
ch1 = inst.ch_names[idx1]
bads = bads.union([ch0, ch1])
# compute midway position in spherical coordinates in "head"
# (more accurate than cutting though the scalp by using cartesian)
pos0 = _cart_to_sph(ch_pos[ch0])
pos1 = _cart_to_sph(ch_pos[ch1])
pos_virtual = _sph_to_cart((pos0 + pos1) / 2)
virtual_info = create_info(
[f'{ch0}-{ch1} virtual'], inst.info['sfreq'], 'eeg')
virtual_info['chs'][0]['loc'][:3] = pos_virtual
if isinstance(inst, BaseRaw):
virtual_ch = RawArray(
(data[idx0:idx0 + 1] + data[idx1:idx1 + 1]) / 2,
virtual_info, first_samp=inst.first_samp)
elif isinstance(inst, BaseEpochs):
virtual_ch = EpochsArray(
(data[:, idx0:idx0 + 1] + data[:, idx1:idx1 + 1]) / 2,
virtual_info, tmin=inst.tmin)
else: # evoked
virtual_ch = EvokedArray(
(data[idx0:idx0 + 1] + data[idx1:idx1 + 1]) / 2,
virtual_info, tmin=inst.tmin, nave=inst.nave, kind=inst.kind)
virtual_chs[f'{ch0}-{ch1} virtual'] = virtual_ch
# add the virtual channels
inst.add_channels(list(virtual_chs.values()), force_update_info=True)
# use the virtual channels to interpolate
inst.info['bads'] = list(bads)
inst.interpolate_bads()
# drop virtual channels
inst.drop_channels(list(virtual_chs.keys()))
inst.info['bads'] = bads_orig
return inst

interpolate_bridged_electrodes only works on bridge pair by pair. But if we take the example below, we can do better for the bridge ('P5', 'PO7', 'PO3', 'O1').

image

I propose 2 changes to interpolate_bridged_electrodes:

  • handles a group of electrodes bridged together as a single bridge and computes a single virtual channel per group at the average location.
  • add a warning if you use interpolate_bridged_electrodes where you have bridges including more than N electrodes.

I think a good number would be N=3, a trio of electrodes is usually either a straight line on a row/column of the cap, or a triangle. If it's a row/column (e.g. PO8, PO6, PO4) the average location is actually the center electrode (PO6). If it's a triangle, the average location is more or less equidistant from the 3 electrodes. In both cases, intuitively, I think the virtual channel will improve interpolation. Above 3 electrodes, I'm not convinced.


To retrieve the group of electrodes, I can think of an implementation as a graph with networkx, something along the lines of:

G = nx.Graph()
for bridge in bridged_idx:
    G.add_edge(*bridge)
groups_idx = nx.connected_components(G)

Any other idea to avoid the dependency on networkx?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions