-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
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?
mne-python/mne/preprocessing/interpolate.py
Lines 75 to 155 in 5ee892f
| 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').
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_electrodeswhere 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?
