initial commit
This commit is contained in:
342
mne/preprocessing/eog.py
Normal file
342
mne/preprocessing/eog.py
Normal file
@@ -0,0 +1,342 @@
|
||||
# Authors: The MNE-Python contributors.
|
||||
# License: BSD-3-Clause
|
||||
# Copyright the MNE-Python contributors.
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .._fiff.pick import pick_channels, pick_types
|
||||
from ..epochs import Epochs
|
||||
from ..filter import filter_data
|
||||
from ..utils import _pl, _validate_type, logger, verbose
|
||||
from ._peak_finder import peak_finder
|
||||
|
||||
|
||||
@verbose
|
||||
def find_eog_events(
|
||||
raw,
|
||||
event_id=998,
|
||||
l_freq=1,
|
||||
h_freq=10,
|
||||
filter_length="10s",
|
||||
ch_name=None,
|
||||
tstart=0,
|
||||
reject_by_annotation=False,
|
||||
thresh=None,
|
||||
verbose=None,
|
||||
):
|
||||
"""Locate EOG artifacts.
|
||||
|
||||
.. note:: To control true-positive and true-negative detection rates, you
|
||||
may adjust the ``thresh`` parameter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw : instance of Raw
|
||||
The raw data.
|
||||
event_id : int
|
||||
The index to assign to found events.
|
||||
l_freq : float
|
||||
Low cut-off frequency to apply to the EOG channel in Hz.
|
||||
h_freq : float
|
||||
High cut-off frequency to apply to the EOG channel in Hz.
|
||||
filter_length : str | int | None
|
||||
Number of taps to use for filtering.
|
||||
%(ch_name_eog)s
|
||||
tstart : float
|
||||
Start detection after tstart seconds.
|
||||
reject_by_annotation : bool
|
||||
Whether to omit data that is annotated as bad.
|
||||
thresh : float | None
|
||||
Threshold to trigger the detection of an EOG event. This controls the
|
||||
thresholding of the underlying peak-finding algorithm. Larger values
|
||||
mean that fewer peaks (i.e., fewer EOG events) will be detected.
|
||||
If ``None``, use the default of ``(max(eog) - min(eog)) / 4``,
|
||||
with ``eog`` being the filtered EOG signal.
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
eog_events : array
|
||||
Events.
|
||||
|
||||
See Also
|
||||
--------
|
||||
create_eog_epochs
|
||||
compute_proj_eog
|
||||
"""
|
||||
# Getting EOG Channel
|
||||
eog_inds = _get_eog_channel_index(ch_name, raw)
|
||||
eog_names = np.array(raw.ch_names)[eog_inds] # for logging
|
||||
logger.info(f"EOG channel index for this subject is: {eog_inds}")
|
||||
|
||||
# Reject bad segments.
|
||||
reject_by_annotation = "omit" if reject_by_annotation else None
|
||||
eog, times = raw.get_data(
|
||||
picks=eog_inds, reject_by_annotation=reject_by_annotation, return_times=True
|
||||
)
|
||||
times = times * raw.info["sfreq"] + raw.first_samp
|
||||
|
||||
eog_events = _find_eog_events(
|
||||
eog,
|
||||
ch_names=eog_names,
|
||||
event_id=event_id,
|
||||
l_freq=l_freq,
|
||||
h_freq=h_freq,
|
||||
sampling_rate=raw.info["sfreq"],
|
||||
first_samp=raw.first_samp,
|
||||
filter_length=filter_length,
|
||||
tstart=tstart,
|
||||
thresh=thresh,
|
||||
verbose=verbose,
|
||||
)
|
||||
# Map times to corresponding samples.
|
||||
eog_events[:, 0] = np.round(times[eog_events[:, 0] - raw.first_samp]).astype(int)
|
||||
return eog_events
|
||||
|
||||
|
||||
@verbose
|
||||
def _find_eog_events(
|
||||
eog,
|
||||
*,
|
||||
ch_names,
|
||||
event_id,
|
||||
l_freq,
|
||||
h_freq,
|
||||
sampling_rate,
|
||||
first_samp,
|
||||
filter_length="10s",
|
||||
tstart=0.0,
|
||||
thresh=None,
|
||||
verbose=None,
|
||||
):
|
||||
"""Find EOG events."""
|
||||
logger.info(
|
||||
"Filtering the data to remove DC offset to help "
|
||||
"distinguish blinks from saccades"
|
||||
)
|
||||
|
||||
# filtering to remove dc offset so that we know which is blink and saccades
|
||||
# hardcode verbose=False to suppress filter param messages (since this
|
||||
# filter is not under user control)
|
||||
fmax = np.minimum(45, sampling_rate / 2.0 - 0.75) # protect Nyquist
|
||||
filteog = np.array(
|
||||
[
|
||||
filter_data(
|
||||
x,
|
||||
sampling_rate,
|
||||
2,
|
||||
fmax,
|
||||
None,
|
||||
filter_length,
|
||||
0.5,
|
||||
0.5,
|
||||
phase="zero-double",
|
||||
fir_window="hann",
|
||||
fir_design="firwin2",
|
||||
verbose=False,
|
||||
)
|
||||
for x in eog
|
||||
]
|
||||
)
|
||||
temp = np.sqrt(np.sum(filteog**2, axis=1))
|
||||
indexmax = np.argmax(temp)
|
||||
if ch_names is not None: # it can be None if called from ica_find_eog_events
|
||||
logger.info(f"Selecting channel {ch_names[indexmax]} for blink detection")
|
||||
|
||||
# easier to detect peaks with filtering.
|
||||
filteog = filter_data(
|
||||
eog[indexmax],
|
||||
sampling_rate,
|
||||
l_freq,
|
||||
h_freq,
|
||||
None,
|
||||
filter_length,
|
||||
0.5,
|
||||
0.5,
|
||||
phase="zero-double",
|
||||
fir_window="hann",
|
||||
fir_design="firwin2",
|
||||
)
|
||||
|
||||
# detecting eog blinks and generating event file
|
||||
|
||||
logger.info("Now detecting blinks and generating corresponding events")
|
||||
|
||||
temp = filteog - np.mean(filteog)
|
||||
n_samples_start = int(sampling_rate * tstart)
|
||||
if np.abs(np.max(temp)) > np.abs(np.min(temp)):
|
||||
eog_events, _ = peak_finder(filteog[n_samples_start:], thresh, extrema=1)
|
||||
else:
|
||||
eog_events, _ = peak_finder(filteog[n_samples_start:], thresh, extrema=-1)
|
||||
|
||||
eog_events += n_samples_start
|
||||
n_events = len(eog_events)
|
||||
logger.info(f"Number of EOG events detected: {n_events}")
|
||||
eog_events = np.array(
|
||||
[
|
||||
eog_events + first_samp,
|
||||
np.zeros(n_events, int),
|
||||
event_id * np.ones(n_events, int),
|
||||
]
|
||||
).T
|
||||
|
||||
return eog_events
|
||||
|
||||
|
||||
def _get_eog_channel_index(ch_name, inst):
|
||||
"""Get EOG channel indices."""
|
||||
_validate_type(ch_name, types=(None, str, list), item_name="ch_name")
|
||||
|
||||
if ch_name is None:
|
||||
eog_inds = pick_types(
|
||||
inst.info,
|
||||
meg=False,
|
||||
eeg=False,
|
||||
stim=False,
|
||||
eog=True,
|
||||
ecg=False,
|
||||
emg=False,
|
||||
ref_meg=False,
|
||||
exclude="bads",
|
||||
)
|
||||
if eog_inds.size == 0:
|
||||
raise RuntimeError("No EOG channel(s) found")
|
||||
ch_names = [inst.ch_names[i] for i in eog_inds]
|
||||
elif isinstance(ch_name, str):
|
||||
ch_names = [ch_name]
|
||||
else: # it's a list
|
||||
ch_names = ch_name.copy()
|
||||
|
||||
# ensure the specified channels are present in the data
|
||||
if ch_name is not None:
|
||||
not_found = [ch_name for ch_name in ch_names if ch_name not in inst.ch_names]
|
||||
if not_found:
|
||||
raise ValueError(
|
||||
f"The specified EOG channel{_pl(not_found)} "
|
||||
f'cannot be found: {", ".join(not_found)}'
|
||||
)
|
||||
|
||||
eog_inds = pick_channels(inst.ch_names, include=ch_names)
|
||||
|
||||
logger.info(f'Using EOG channel{_pl(ch_names)}: {", ".join(ch_names)}')
|
||||
return eog_inds
|
||||
|
||||
|
||||
@verbose
|
||||
def create_eog_epochs(
|
||||
raw,
|
||||
ch_name=None,
|
||||
event_id=998,
|
||||
picks=None,
|
||||
tmin=-0.5,
|
||||
tmax=0.5,
|
||||
l_freq=1,
|
||||
h_freq=10,
|
||||
reject=None,
|
||||
flat=None,
|
||||
baseline=None,
|
||||
preload=True,
|
||||
reject_by_annotation=True,
|
||||
thresh=None,
|
||||
decim=1,
|
||||
verbose=None,
|
||||
):
|
||||
"""Conveniently generate epochs around EOG artifact events.
|
||||
|
||||
%(create_eog_epochs)s
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw : instance of Raw
|
||||
The raw data.
|
||||
%(ch_name_eog)s
|
||||
event_id : int
|
||||
The index to assign to found events.
|
||||
%(picks_all)s
|
||||
tmin : float
|
||||
Start time before event.
|
||||
tmax : float
|
||||
End time after event.
|
||||
l_freq : float
|
||||
Low pass frequency to apply to the EOG channel while finding events.
|
||||
h_freq : float
|
||||
High pass frequency to apply to the EOG channel while finding events.
|
||||
reject : dict | None
|
||||
Rejection parameters based on peak-to-peak amplitude.
|
||||
Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'.
|
||||
If reject is None then no rejection is done. Example::
|
||||
|
||||
reject = dict(grad=4000e-13, # T / m (gradiometers)
|
||||
mag=4e-12, # T (magnetometers)
|
||||
eeg=40e-6, # V (EEG channels)
|
||||
eog=250e-6 # V (EOG channels)
|
||||
)
|
||||
|
||||
flat : dict | None
|
||||
Rejection parameters based on flatness of signal.
|
||||
Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg', and values
|
||||
are floats that set the minimum acceptable peak-to-peak amplitude.
|
||||
If flat is None then no rejection is done.
|
||||
baseline : tuple or list of length 2, or None
|
||||
The time interval to apply rescaling / baseline correction.
|
||||
If None do not apply it. If baseline is (a, b)
|
||||
the interval is between "a (s)" and "b (s)".
|
||||
If a is None the beginning of the data is used
|
||||
and if b is None then b is set to the end of the interval.
|
||||
If baseline is equal to (None, None) all the time
|
||||
interval is used. If None, no correction is applied.
|
||||
preload : bool
|
||||
Preload epochs or not.
|
||||
%(reject_by_annotation_epochs)s
|
||||
|
||||
.. versionadded:: 0.14.0
|
||||
thresh : float
|
||||
Threshold to trigger EOG event.
|
||||
%(decim)s
|
||||
|
||||
.. versionadded:: 0.21.0
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
eog_epochs : instance of Epochs
|
||||
Data epoched around EOG events.
|
||||
|
||||
See Also
|
||||
--------
|
||||
find_eog_events
|
||||
compute_proj_eog
|
||||
|
||||
Notes
|
||||
-----
|
||||
Filtering is only applied to the EOG channel while finding events.
|
||||
The resulting ``eog_epochs`` will have no filtering applied (i.e., have
|
||||
the same filter properties as the input ``raw`` instance).
|
||||
"""
|
||||
events = find_eog_events(
|
||||
raw,
|
||||
ch_name=ch_name,
|
||||
event_id=event_id,
|
||||
l_freq=l_freq,
|
||||
h_freq=h_freq,
|
||||
reject_by_annotation=reject_by_annotation,
|
||||
thresh=thresh,
|
||||
)
|
||||
|
||||
# create epochs around EOG events
|
||||
eog_epochs = Epochs(
|
||||
raw,
|
||||
events=events,
|
||||
event_id=event_id,
|
||||
tmin=tmin,
|
||||
tmax=tmax,
|
||||
proj=False,
|
||||
reject=reject,
|
||||
flat=flat,
|
||||
picks=picks,
|
||||
baseline=baseline,
|
||||
preload=preload,
|
||||
reject_by_annotation=reject_by_annotation,
|
||||
decim=decim,
|
||||
)
|
||||
return eog_epochs
|
||||
Reference in New Issue
Block a user