Source code for pycrostates.preprocessing.extract_gfp_peaks
"""Preprocessing functions to extract gfp peaks."""
from __future__ import annotations # c.f. PEP 563, PEP 649
from typing import TYPE_CHECKING
import numpy as np
from mne import BaseEpochs, pick_info
from mne._fiff.pick import _picks_to_idx
from mne.io import BaseRaw
from scipy.signal import find_peaks
from ..utils._checks import (
_check_picks_uniqueness,
_check_reject_by_annotation,
_check_tmin_tmax,
_check_type,
_check_value,
)
from ..utils._docs import fill_doc
from ..utils._logs import logger, verbose
if TYPE_CHECKING:
from collections.abc import Callable
from .._typing import Picks
from ..io import ChData
_GFP_FUNC: dict[str, Callable] = {
"eeg": lambda x: np.std(x, axis=0),
"grad": lambda x: np.sqrt(np.mean(x**2, axis=0)),
"mag": lambda x: np.sqrt(np.mean(x**2, axis=0)),
}
[docs]
@fill_doc
@verbose
def extract_gfp_peaks(
inst: BaseRaw | BaseEpochs,
picks: Picks = "eeg",
return_all: bool = False,
min_peak_distance: int = 1,
tmin: float | None = None,
tmax: float | None = None,
reject_by_annotation: bool = True,
verbose=None,
) -> ChData:
""":term:`Global Field Power` (:term:`GFP`) peaks extraction.
Extract :term:`Global Field Power` (:term:`GFP`) peaks from :class:`~mne.Epochs` or
:class:`~mne.io.Raw`.
Parameters
----------
inst : Raw | Epochs
Instance from which to extract :term:`global field power` (GFP) peaks.
picks : str | list | slice | None
Channels to use for GFP computation. Note that all channels selected must have
the same type. Slices and lists of integers will be interpreted as channel
indices. In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the
given channels. Can also be the string values ``“all”`` to pick all channels, or
``“data”`` to pick data channels. ``"eeg"`` (default) will pick all eeg
channels. Note that channels in ``info['bads']`` will be included if their
names or indices are explicitly provided.
return_all : bool
If True, the returned `~pycrostates.io.ChData` instance will include all
channels. If False (default), the returned `~pycrostates.io.ChData` instance
will only include channels used for GFP computation (i.e ``picks``).
min_peak_distance : int
Required minimal horizontal distance (``≥ 1`) in samples between neighboring
peaks. Smaller peaks are removed first until the condition is fulfilled for all
remaining peaks. Default to ``1``.
%(tmin_raw)s
%(tmax_raw)s
%(reject_by_annotation_raw)s
%(verbose)s
Returns
-------
ch_data : ChData
Samples at global field power peaks.
Notes
-----
The :term:`Global Field Power` (:term:`GFP`) peaks are extracted with
:func:`scipy.signal.find_peaks`. Only the ``distance`` argument is filled with the
value provided in ``min_peak_distance``. The other arguments are set to their
default values.
.. versionchanged:: 0.7
The global field power is now computed with different functions depending
on data type. ``eeg`` uses the standard deviation, while ``grad`` and
``mag`` use the root mean square.
"""
from ..io import ChData
_check_type(inst, (BaseRaw, BaseEpochs), "inst")
_check_type(min_peak_distance, ("int",), "min_peak_distance")
if min_peak_distance < 1:
raise ValueError(
"Argument 'min_peak_distance' must be superior or "
f"equal to 1. Provided: {min_peak_distance}."
)
tmin, tmax = _check_tmin_tmax(inst, tmin, tmax)
if isinstance(inst, BaseRaw):
reject_by_annotation = _check_reject_by_annotation(reject_by_annotation)
# retrieve picks
picks = _picks_to_idx(inst.info, picks, none="all", exclude="bads")
picks_all = _picks_to_idx(inst.info, inst.ch_names, none="all", exclude="bads")
_check_picks_uniqueness(inst.info, picks)
# gfp function
ch_type = inst.get_channel_types(picks, unique=True)[0]
_check_value(ch_type, _GFP_FUNC, "ch_type")
gfp_function = _GFP_FUNC[ch_type]
# set kwargs for .get_data()
kwargs = dict(tmin=tmin, tmax=tmax)
if isinstance(inst, BaseRaw):
kwargs["reject_by_annotation"] = reject_by_annotation
# extract GFP peaks
if isinstance(inst, BaseRaw):
# retrieve data array on which we look for GFP peaks
data = inst.get_data(picks=picks, **kwargs)
# retrieve indices of GFP peaks
gfp = gfp_function(data)
ind_peaks, _ = find_peaks(gfp, distance=min_peak_distance)
# retrieve the peaks data
if return_all:
del data # free up memory
data = inst.get_data(picks=picks_all, **kwargs)
peaks = data[:, ind_peaks]
elif isinstance(inst, BaseEpochs):
peaks = list() # run epoch per epoch
for k in range(len(inst)): # pylint: disable=consider-using-enumerate
data = inst[k].get_data(picks=picks, **kwargs)[0, :, :]
# data is 2D, of shape (n_channels, n_samples)
gfp = gfp_function(data)
ind_peaks, _ = find_peaks(gfp, distance=min_peak_distance)
if return_all:
del data # free up memory
data = inst[k].get_data(picks=picks_all, **kwargs)[0, :, :]
peaks.append(data[:, ind_peaks])
peaks = np.hstack(peaks)
n_samples = inst.times.size
if isinstance(inst, BaseEpochs):
n_samples *= len(inst)
logger.info(
"%s GFP peaks extracted out of %s samples (%.2f%% of the original data).",
peaks.shape[1],
n_samples,
peaks.shape[1] / n_samples * 100,
)
info = pick_info(inst.info, picks_all if return_all else picks)
return ChData(peaks, info)