Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
4 changes: 4 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Enhancements

- Add keyboard shortcuts to toggle :meth:`mne.preprocessing.ICA.plot_properties` topomap channel types ('t') and power spectral density log-scale ('l') (:gh:`10557` by `Alex Rockhill`_)

- Add :func:`mne.preprocessing.compute_bridged_electrodes` to detect EEG electrodes with shared spatial sources due to a conductive medium connecting two or more electrodes, add :ref:`ex-eeg-bridging` for an example and :func:`mne.viz.plot_bridged_electrodes` to help visualize (:gh:`10571` by `Alex Rockhill`_)

- Add ``'voronoi'`` as an option for the ``image_interp`` argument in :func:`mne.viz.plot_topomap` to plot a topomap without interpolation using a Voronoi parcelation (:gh:`10571` by `Alex Rockhill`_)

Bugs
~~~~
- Fix bug in :func:`mne.io.read_raw_brainvision` when BrainVision data are acquired with the Brain Products "V-Amp" amplifier and disabled lowpass filter is marked with value ``0`` (:gh:`10517` by :newcontrib:`Alessandro Tonin`)
Expand Down
1 change: 1 addition & 0 deletions doc/preprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Projections:
annotate_nan
compute_average_dev_head_t
compute_current_source_density
compute_bridged_electrodes
compute_fine_calibration
compute_maxwell_basis
compute_proj_ecg
Expand Down
49 changes: 49 additions & 0 deletions doc/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,23 @@ @article{DarvasEtAl2006
year = {2006}
}

@article{DelormeMakeig2004,
title = {{EEGLAB}: an open source toolbox for analysis of single-trial {EEG} dynamics including independent component analysis},
volume = {134},
issn = {0165-0270},
shorttitle = {{EEGLAB}},
doi = {10.1016/j.jneumeth.2003.10.009},
language = {eng},
number = {1},
journal = {Journal of Neuroscience Methods},
author = {Delorme, Arnaud and Makeig, Scott},
month = mar,
year = {2004},
pmid = {15102499},
keywords = {Computer Simulation, Electroencephalography, Evoked Potentials, Software},
pages = {9--21}
}

@article{DestrieuxEtAl2010,
author = {Destrieux, Christophe and Fischl, Bruce and Dale, Anders and Halgren, Eric},
doi = {10.1016/j.neuroimage.2010.06.010},
Expand Down Expand Up @@ -579,6 +596,22 @@ @article{GramfortEtAl2013b
year = {2013}
}

@article{GreischarEtAl2004,
title = {Effects of electrode density and electrolyte spreading in dense array electroencephalographic recording},
volume = {115},
issn = {13882457},
url = {https://linkinghub.elsevier.com/retrieve/pii/S1388245703003833},
doi = {10.1016/j.clinph.2003.10.028},
language = {en},
number = {3},
urldate = {2022-04-25},
journal = {Clinical Neurophysiology},
author = {Greischar, Lawrence L. and Burghy, Cory A. and van Reekum, Carien M. and Jackson, Daren C. and Pizzagalli, Diego A. and Mueller, Corrina and Davidson, Richard J.},
month = mar,
year = {2004},
pages = {710--720}
}

@article{GreveEtAl2013,
author = {Greve, Douglas N. and {Van der Haegen}, Lise and Cai, Qing and Stufflebeam, Steven and Sabuncu, Mert R. and Fischl, Bruce and Brysbaert, Marc},
doi = {10.1162/jocn_a_00405},
Expand Down Expand Up @@ -1737,6 +1770,22 @@ @article{TauluSimola2006
year = {2006}
}

@article{TenkeKayser2001,
title = {A convenient method for detecting electrolyte bridges in multichannel electroencephalogram and event-related potential recordings},
volume = {112},
issn = {1388-2457},
doi = {10.1016/s1388-2457(00)00553-8},
language = {eng},
number = {3},
journal = {Clinical Neurophysiology: Official Journal of the International Federation of Clinical Neurophysiology},
author = {Tenke, C. E. and Kayser, J.},
month = mar,
year = {2001},
pmid = {11222978},
keywords = {Algorithms, Artifacts, Brain, Electrodes, Electroencephalography, Electrolytes, Evoked Potentials, Humans, Scalp},
pages = {545--550}
}

@article{TescheEtAl1995,
author = {Tesche, Claudia D. and Uusitalo, Mikko A. and Ilmoniemi, Risto J. and Huotilainen, Minna and Kajola, Matti J. and Salonen, Oili L. M.},
doi = {10.1016/0013-4694(95)00064-6},
Expand Down
1 change: 1 addition & 0 deletions doc/visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Visualization
mne_analyze_colormap
plot_bem
plot_brain_colorbar
plot_bridged_electrodes
plot_chpi_snr
plot_cov
plot_channel_labels_circle
Expand Down
255 changes: 255 additions & 0 deletions examples/preprocessing/eeg_bridging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
"""
.. _ex-eeg-bridging:

===============================================
Identify EEG Electrodes Bridged by too much Gel
===============================================

Research-grade EEG often uses a gel based system, and when too much gel is
applied the gel conducting signal from the scalp to the electrode for one
electrode connects with the gel conducting signal from another electrode
"bridging" the two signals. This is undesirable because the signals from the
two (or more) electrodes are not as independent as they would otherwise be;
they are more similar to each other than they would otherwise be causing
Comment thread
alexrockhill marked this conversation as resolved.
Outdated
spatial smearing. An algorithm has been developed to detect electrode
bridging :footcite:`TenkeKayser2001`, which has been implemented in EEGLAB
:footcite:`DelormeMakeig2004`. Unfortunately, there is not a lot to be
done about electrode brigding once the data has been collected as far as
preprocessing. Therefore, the recommendation is to check for electrode
bridging early in data collection and address the problem. Or, if the data
has already been collected, quantify the extent of the bridging so as not
to introduce bias into the data from this effect and exclude subjects with
bridging that might effect the outcome of a study. Preventing electrode
bridging is ideal but awareness of the problem at least will mitigate its
potential as a confound to a study. This tutorial follows
https://psychophysiology.cpmc.columbia.edu/software/eBridge/tutorial.html.
"""
# Authors: Alex Rockhill <aprockhill@mailbox.org>
#
# License: BSD-3-Clause

# %%

# sphinx_gallery_thumbnail_number = 2

import numpy as np
import matplotlib.pyplot as plt

import mne

print(__doc__)

# %%
# Compute Electrical Distance Metric
# ----------------------------------
# First, let's compute electrical distance metrics for a group of example
# subjects from the EEGBCI dataset in order to estimate electrode bridging.
# The electrical distance is just the variance of signals subtracted
Comment thread
mmagnuski marked this conversation as resolved.
# pairwise. Channels with activity that mirror another channel nearly
# exactly will have very low electrical distance. By inspecting the
# distribution of electrical distances, we can look for pairwise distances
# that are consistently near zero which are indicative of bridging.
#
# .. note:: It is likely to be sufficient to run this algorithm on a
# small portion (~3 minutes is probably plenty) of the data but
# that gel might settle over the course of a study causing more
# bridging so using the last segment of the data will
# give the most conservative estimate.

ed_data = dict() # electrical distance/bridging data
raw_data = dict() # store infos for electrode positions
for sub in range(1, 11):
print(f'Computing electrode bridges for subject {sub}')
raw = mne.io.read_raw(mne.datasets.eegbci.load_data(
subject=sub, runs=(1,))[0], preload=True, verbose=False)
Comment thread
alexrockhill marked this conversation as resolved.
Outdated
mne.datasets.eegbci.standardize(raw) # set channel names
montage = mne.channels.make_standard_montage('standard_1005')
Comment thread
alexrockhill marked this conversation as resolved.
Outdated
raw.set_montage(montage, verbose=False)
raw_data[sub] = raw
ed_data[sub] = mne.preprocessing.compute_bridged_electrodes(raw)


# %%
# Examine an Electrical Distance Matrix
# -------------------------------------
# Before we look at the electrical distance distributions across subjects,
# let's look at the distance matrix for one subject and try and understand
# how the algorithm works. We'll use subject 6 as it is a good example of
# bridging. In the zoomed out color scale version on the right, we can see
# that there is a distribution of electrical distances that are specific to
# that subject's head physiology/geometry and brain activity during the
# recording. On the right, when we zoom in, we can see several electrical
Comment thread
alexrockhill marked this conversation as resolved.
Outdated
# distance outliers that are near zero; these indicate bridging.

bridged_idx, ed_matrix = ed_data[6]
Comment thread
alexrockhill marked this conversation as resolved.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
fig.suptitle('Subject 6 Electrical Distance Matrix')
# take median across epochs, only use upper triangular, lower is NaNs
ed_plot = np.zeros(ed_matrix.shape[1:]) * np.nan
triu_idx = np.triu_indices(ed_plot.shape[0], 1)
for idx0, idx1 in np.array(triu_idx).T:
ed_plot[idx0, idx1] = np.nanmedian(ed_matrix[:, idx0, idx1])
im1 = ax1.imshow(ed_plot, aspect='auto')
cax1 = fig.colorbar(im1, ax=ax1)
cax1.set_label(r'Electrical Distance ($\mu$$V^2$)')
# zoomed in colors
im2 = ax2.imshow(ed_plot, aspect='auto', vmax=5)
cax2 = fig.colorbar(im2, ax=ax2)
cax2.set_label(r'Electrical Distance ($\mu$$V^2$)')
for ax in (ax1, ax2):
ax.set_xlabel('Channel Index')
ax.set_ylabel('Channel Index')
fig.tight_layout()

# %%
# Examine the Distribution of Electrical Distances
# ------------------------------------------------
# Now let's plot a histogram of the electrical distance matrix. Note that the
# electrical distance matrix is upper triangular but does not include the
# diagonal from the previous plot. This means that the pairwise electrical
Comment thread
alexrockhill marked this conversation as resolved.
Outdated
# distances are not computed between the same channel (which makes sense as
# the differences between a channel and itself would just be zero). The initial
# peak near zero therefore represents pairs of different channels that
# are nearly identical which is indicative of bridging. EEG recordings
# without bridged electrodes do not have a peak near zero.

fig, ax = plt.subplots(figsize=(5, 5))
fig.suptitle('Subject 6 Electrical Distance Matrix Distribution')
ax.hist(ed_matrix[~np.isnan(ed_matrix)], bins=np.linspace(0, 500, 51))
ax.set_xlabel(r'Electrical Distance ($\mu$$V^2$)')
ax.set_ylabel('Count')
Comment thread
alexrockhill marked this conversation as resolved.
Outdated

# %%
# Plot Electrical Distances on a Topomap
# --------------------------------------
# Now, let's look at the topography of the electrical distance matrix and
# see where our bridged channels are and check that their spatial
# arrangement makes sense. Here, we are looking at the minimum electrical
# distance for each channel and taking the median across all epochs
# (the raw data is epoched into 2 second non-overlapping intervals).
# This example is of the subject from the EEGBCI dataset with the most
# bridged channels so there are many light areas and red lines. They are
# generally grouped together and are biased toward horizontal connections
# (this may be because the EEG experimenter usually stands to the side and
# may have inserted the gel syringe tip in too far).

mne.viz.plot_bridged_electrodes(
raw_data[6].info, bridged_idx, ed_matrix,
title='Subject 6 Bridged Electrodes', topomap_args=dict(vmax=5))

# %%
# Plot the Raw Voltage Time Series for Bridged Electrodes
# -------------------------------------------------------
# Finally, let's do a sanity check and make sure that the bridged electrodes
# are indeed implausibly similar. We'll plot two bridged electrode pairs:
# F2-F4 and FC2-FC4, for subject 6 where they are bridged and subject 1
# where they are not. As we can see, the pairs are nearly identical for
# subject 6 confirming that they are likely bridged. Interestingly, even
# though the two pairs are adjacent to each other, there are two distinctive
# pairs, meaning that it is unlikely that all four of these electrodes are
# bridged.

raw = raw_data[6].copy().pick_channels(['F2', 'F4', 'FC2', 'FC4'])
raw.add_channels([mne.io.RawArray(
raw.get_data(ch1) - raw.get_data(ch2),
mne.create_info([f'{ch1}-{ch2}'], raw.info['sfreq'], 'eeg'),
raw.first_samp) for ch1, ch2 in [('F2', 'F4'), ('FC2', 'FC4')]])
raw.plot(duration=20, scalings=dict(eeg=2e-4))

raw = raw_data[1].copy().pick_channels(['F2', 'F4', 'FC2', 'FC4'])
raw.add_channels([mne.io.RawArray(
raw.get_data(ch1) - raw.get_data(ch2),
mne.create_info([f'{ch1}-{ch2}'], raw.info['sfreq'], 'eeg'),
raw.first_samp) for ch1, ch2 in [('F2', 'F4'), ('FC2', 'FC4')]])
raw.plot(duration=20, scalings=dict(eeg=2e-4))

# %%
# Compare Bridging Across Subjects in the EEGBCI Dataset
# -------------------------------------------------------
# Now, let's look at the histograms of electrical distances for the whole
# EEGBCI dataset. As we can see in the zoomed in insert on the right,
# for subjects 6, 7 and 8 (and to a lesser extent 2 and 4), there is a
# different shape of the distribution of electrical distances around
# 0 :math:`{\mu}V^2` than for the other subjects. These subjects'
# distributions have a peak around 0 :math:`{\mu}V^2` distance
# and a trough around 5 :math:`{\mu}V^2` which is indicative of
# electrode bridging. The rest of the subjects' distributions increase
# monotonically, indicating normal spatial separation of sources. The
# large discrepancy in shapes of distributions is likely driven primarily by
# artifacts such as blinks which are an order of magnitude larger than
# neural data since this data has not been preprocessed but likely
# reflect neural or at least anatomical differences as well (i.e. the
# distance from the sensors to the brain).

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
fig.suptitle('Electrical Distance Distribution for EEGBCI Subjects')
for ax in (ax1, ax2):
ax.set_ylabel('Count')
ax.set_xlabel(r'Electrical Distance ($\mu$$V^2$)')

for sub, (bridged_idx, ed_matrix) in ed_data.items():
# ed_matrix is upper triangular so exclude bottom half of NaNs
hist, edges = np.histogram(ed_matrix[~np.isnan(ed_matrix)].flatten(),
bins=np.linspace(0, 1000, 101))
centers = (edges[1:] + edges[:-1]) / 2
ax1.plot(centers, hist)
hist, edges = np.histogram(ed_matrix[~np.isnan(ed_matrix)].flatten(),
bins=np.linspace(0, 30, 21))
centers = (edges[1:] + edges[:-1]) / 2
ax2.plot(centers, hist, label=f'Sub {sub} #={len(bridged_idx)}')

ax1.axvspan(0, 30, color='r', alpha=0.5)
ax2.legend(loc=(1.04, 0))
fig.subplots_adjust(right=0.725, bottom=0.15, wspace=0.4)

# %%
# For the group of subjects, let's look at their electrical distances
# and bridging. Especially since this is the same task, the lack of
# low electrical distances in many of the subjects is compelling
# evidence that the low electrical distance is caused by bridging
# and that it is avoidable given more judicious application of gel or
# other conductive electrolyte solution.

for sub, (bridged_idx, ed_matrix) in ed_data.items():
mne.viz.plot_bridged_electrodes(
raw_data[sub].info, bridged_idx, ed_matrix,
title=f'Subject {sub} Bridged Electrodes', topomap_args=dict(vmax=5))

# %%
# The Relationship Between Bridging and Impedances
# ------------------------------------------------
# Electrode bridging is often brought about by inserting more gel in order
# to bring impendances down. Thus it can be helpful to compare bridging
# to impedances in the quest to be an ideal EEG technician! Low
# impedances lead to less noisy data and EEG without bridging is more
# spatially precise. Brain Imaging Data Structure (BIDS) recommends that
# impedances be stored in an EEG dataset in the `electrodes.tsv
# <https://bids-specification.readthedocs.io/en/stable/\
# 04-modality-specific-files/03-electroencephalography.html\
# #electrodes-description-_electrodestsv>`_ file.
# Since the impedances are not stored for this dataset, we will fake
# them to demonstrate how they would be plotted.

rng = np.random.default_rng(11) # seed for reproducibility
raw = raw_data[1]
impedances = rng.random((len(raw.ch_names,))) * 10
fig, ax = plt.subplots(figsize=(5, 5))
im, cn = mne.viz.plot_topomap(impedances, raw.info, axes=ax)
ax.set_title('Electrode Impendances')
cax = fig.colorbar(im, ax=ax)
cax.set_label(r'Impedance (k$\Omega$)')

# %%
# Summary
# -------
# In this example, we have shown a dataset where electrical bridging occurred
# during the EEG setup for several subjects. Hopefully this is convincing as to
# the importance of proper technique as well as checking your work to
# learn and improve as an EEG experimenter, and hopefully this tool will help
# us all collect better EEG data in the future.

# %%
# References
# ----------
# .. footbibliography::
5 changes: 5 additions & 0 deletions examples/preprocessing/muscle_ica.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,8 @@
print(f'Manually found muscle artifact ICA components: {muscle_idx}\n'
'Automatically found muscle artifact ICA components: '
f'{muscle_idx_auto}')

# %%
# References
# ----------
# .. footbibliography::
4 changes: 2 additions & 2 deletions mne/preprocessing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
compute_maxwell_basis, maxwell_filter_prepare_emptyroom)
from .realign import realign_raw
from .xdawn import Xdawn
from ._csd import compute_current_source_density
from ._csd import compute_current_source_density, compute_bridged_electrodes
from . import nirs
from .artifact_detection import (annotate_movement, compute_average_dev_head_t,
annotate_muscle_zscore, annotate_break)
from ._regress import regress_artifact
from ._fine_cal import (compute_fine_calibration, read_fine_calibration,
from ._fine_cal import (compute_fine_calibration, read_fine_calibration,
write_fine_calibration)
from .annotate_nan import annotate_nan
from .interpolate import equalize_bads
Expand Down
Loading