# Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. import copy as cp import numpy as np from .._fiff.pick import pick_types from .._fiff.reference import make_eeg_average_ref_proj from ..epochs import Epochs from ..proj import compute_proj_epochs, compute_proj_evoked from ..utils import _validate_type, logger, verbose, warn from .ecg import find_ecg_events from .eog import find_eog_events def _safe_del_key(dict_, key): """Aux function. Use this function when preparing rejection parameters instead of directly deleting keys. """ if key in dict_: del dict_[key] def _compute_exg_proj( mode, raw, raw_event, tmin, tmax, n_grad, n_mag, n_eeg, l_freq, h_freq, average, filter_length, n_jobs, ch_name, reject, flat, bads, avg_ref, no_proj, event_id, exg_l_freq, exg_h_freq, tstart, qrs_threshold, filter_method, iir_params, return_drop_log, copy, meg, verbose, ): """Compute SSP/PCA projections for ECG or EOG artifacts.""" raw = raw.copy() if copy else raw del copy raw.load_data() # we will filter it later if no_proj: projs = [] else: projs = cp.deepcopy(raw.info["projs"]) logger.info(f"Including {len(projs)} SSP projectors from raw file") if avg_ref: eeg_proj = make_eeg_average_ref_proj(raw.info) projs.append(eeg_proj) if raw_event is None: raw_event = raw assert mode in ("ECG", "EOG") # internal function logger.info(f"Running {mode} SSP computation") if mode == "ECG": events, _, _ = find_ecg_events( raw_event, ch_name=ch_name, event_id=event_id, l_freq=exg_l_freq, h_freq=exg_h_freq, tstart=tstart, qrs_threshold=qrs_threshold, filter_length=filter_length, ) else: # mode == 'EOG': events = find_eog_events( raw_event, event_id=event_id, l_freq=exg_l_freq, h_freq=exg_h_freq, filter_length=filter_length, ch_name=ch_name, tstart=tstart, ) # Check to make sure we actually got at least one usable event if events.shape[0] < 1: warn(f"No {mode} events found") return ([], events) + (([],) if return_drop_log else ()) logger.info("Computing projector") my_info = cp.deepcopy(raw.info) my_info["bads"] += bads # Handler rejection parameters _validate_type(reject, (None, dict), "reject") _validate_type(flat, (None, dict), "flat") if reject is not None: # make sure they didn't pass None reject = reject.copy() # must make a copy or we modify default! if ( len( pick_types( my_info, meg="grad", eeg=False, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(reject, "grad") if ( len( pick_types( my_info, meg="mag", eeg=False, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(reject, "mag") if ( len( pick_types( my_info, meg=False, eeg=True, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(reject, "eeg") if ( len( pick_types( my_info, meg=False, eeg=False, eog=True, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(reject, "eog") if flat is not None: # make sure they didn't pass None flat = flat.copy() if ( len( pick_types( my_info, meg="grad", eeg=False, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(flat, "grad") if ( len( pick_types( my_info, meg="mag", eeg=False, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(flat, "mag") if ( len( pick_types( my_info, meg=False, eeg=True, eog=False, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(flat, "eeg") if ( len( pick_types( my_info, meg=False, eeg=False, eog=True, ref_meg=False, exclude="bads", ) ) == 0 ): _safe_del_key(flat, "eog") # exclude bad channels from projection # keep reference channels if compensation channels are present ref_meg = len(my_info["comps"]) > 0 picks = pick_types( my_info, meg=True, eeg=True, eog=True, ecg=True, ref_meg=ref_meg, exclude="bads" ) raw.filter( l_freq, h_freq, picks=picks, filter_length=filter_length, n_jobs=n_jobs, method=filter_method, iir_params=iir_params, l_trans_bandwidth=0.5, h_trans_bandwidth=0.5, phase="zero-double", fir_design="firwin2", ) epochs = Epochs( raw, events, None, tmin, tmax, baseline=None, preload=True, picks=picks, reject=reject, flat=flat, proj=True, ) drop_log = epochs.drop_log if epochs.events.shape[0] < 1: warn("No good epochs found") return ([], events) + ((drop_log,) if return_drop_log else ()) if average: evoked = epochs.average() ev_projs = compute_proj_evoked( evoked, n_grad=n_grad, n_mag=n_mag, n_eeg=n_eeg, meg=meg ) else: ev_projs = compute_proj_epochs( epochs, n_grad=n_grad, n_mag=n_mag, n_eeg=n_eeg, n_jobs=n_jobs, meg=meg ) for p in ev_projs: p["desc"] = mode + "-" + p["desc"] projs.extend(ev_projs) logger.info("Done.") return (projs, events) + ((drop_log,) if return_drop_log else ()) @verbose def compute_proj_ecg( raw, raw_event=None, tmin=-0.2, tmax=0.4, n_grad=2, n_mag=2, n_eeg=2, l_freq=1.0, h_freq=35.0, average=True, filter_length="10s", n_jobs=None, ch_name=None, reject=dict(grad=2000e-13, mag=3000e-15, eeg=50e-6, eog=250e-6), # noqa: B006 flat=None, bads=(), avg_ref=False, no_proj=False, event_id=999, ecg_l_freq=5, ecg_h_freq=35, tstart=0.0, qrs_threshold="auto", filter_method="fir", iir_params=None, copy=True, return_drop_log=False, meg="separate", verbose=None, ): """Compute SSP (signal-space projection) vectors for ECG artifacts. %(compute_proj_ecg)s .. note:: Raw data will be loaded if it hasn't been preloaded already. Parameters ---------- raw : mne.io.Raw Raw input file. raw_event : mne.io.Raw or None Raw file to use for event detection (if None, raw is used). tmin : float Time before event in seconds. tmax : float Time after event in seconds. n_grad : int Number of SSP vectors for gradiometers. n_mag : int Number of SSP vectors for magnetometers. n_eeg : int Number of SSP vectors for EEG. l_freq : float | None Filter low cut-off frequency for the data channels in Hz. h_freq : float | None Filter high cut-off frequency for the data channels in Hz. average : bool Compute SSP after averaging. Default is True. filter_length : str | int | None Number of taps to use for filtering. %(n_jobs)s ch_name : str | None Channel to use for ECG detection (Required if no ECG found). reject : dict | None Epoch rejection configuration (see Epochs). flat : dict | None Epoch flat configuration (see Epochs). bads : list List with (additional) bad channels. avg_ref : bool Add EEG average reference proj. no_proj : bool Exclude the SSP projectors currently in the fiff file. event_id : int ID to use for events. ecg_l_freq : float Low pass frequency applied to the ECG channel for event detection. ecg_h_freq : float High pass frequency applied to the ECG channel for event detection. tstart : float Start artifact detection after tstart seconds. qrs_threshold : float | str Between 0 and 1. qrs detection threshold. Can also be "auto" to automatically choose the threshold that generates a reasonable number of heartbeats (40-160 beats / min). filter_method : str Method for filtering ('iir' or 'fir'). iir_params : dict | None Dictionary of parameters to use for IIR filtering. See mne.filter.construct_iir_filter for details. If iir_params is None and method="iir", 4th order Butterworth will be used. copy : bool If False, filtering raw data is done in place. Defaults to True. return_drop_log : bool If True, return the drop log. .. versionadded:: 0.15 meg : str Can be ``'separate'`` (default) or ``'combined'`` to compute projectors for magnetometers and gradiometers separately or jointly. If ``'combined'``, ``n_mag == n_grad`` is required and the number of projectors computed for MEG will be ``n_mag``. .. versionadded:: 0.18 %(verbose)s Returns ------- %(projs)s ecg_events : ndarray Detected ECG events. drop_log : list The drop log, if requested. See Also -------- find_ecg_events create_ecg_epochs Notes ----- Filtering is applied to the ECG channel while finding events using ``ecg_l_freq`` and ``ecg_h_freq``, and then to the ``raw`` instance using ``l_freq`` and ``h_freq`` before creation of the epochs used to create the projectors. """ return _compute_exg_proj( "ECG", raw, raw_event, tmin, tmax, n_grad, n_mag, n_eeg, l_freq, h_freq, average, filter_length, n_jobs, ch_name, reject, flat, bads, avg_ref, no_proj, event_id, ecg_l_freq, ecg_h_freq, tstart, qrs_threshold, filter_method, iir_params, return_drop_log, copy, meg, verbose, ) @verbose def compute_proj_eog( raw, raw_event=None, tmin=-0.2, tmax=0.2, n_grad=2, n_mag=2, n_eeg=2, l_freq=1.0, h_freq=35.0, average=True, filter_length="10s", n_jobs=None, reject=dict(grad=2000e-13, mag=3000e-15, eeg=500e-6, eog=np.inf), # noqa: B006 flat=None, bads=(), avg_ref=False, no_proj=False, event_id=998, eog_l_freq=1, eog_h_freq=10, tstart=0.0, filter_method="fir", iir_params=None, ch_name=None, copy=True, return_drop_log=False, meg="separate", verbose=None, ): """Compute SSP (signal-space projection) vectors for EOG artifacts. %(compute_proj_eog)s .. note:: Raw data must be preloaded. Parameters ---------- raw : mne.io.Raw Raw input file. raw_event : mne.io.Raw or None Raw file to use for event detection (if None, raw is used). tmin : float Time before event in seconds. tmax : float Time after event in seconds. n_grad : int Number of SSP vectors for gradiometers. n_mag : int Number of SSP vectors for magnetometers. n_eeg : int Number of SSP vectors for EEG. l_freq : float | None Filter low cut-off frequency for the data channels in Hz. h_freq : float | None Filter high cut-off frequency for the data channels in Hz. average : bool Compute SSP after averaging. Default is True. filter_length : str | int | None Number of taps to use for filtering. %(n_jobs)s reject : dict | None Epoch rejection configuration (see Epochs). flat : dict | None Epoch flat configuration (see Epochs). bads : list List with (additional) bad channels. avg_ref : bool Add EEG average reference proj. no_proj : bool Exclude the SSP projectors currently in the fiff file. event_id : int ID to use for events. eog_l_freq : float Low pass frequency applied to the E0G channel for event detection. eog_h_freq : float High pass frequency applied to the EOG channel for event detection. tstart : float Start artifact detection after tstart seconds. filter_method : str Method for filtering ('iir' or 'fir'). iir_params : dict | None Dictionary of parameters to use for IIR filtering. See mne.filter.construct_iir_filter for details. If iir_params is None and method="iir", 4th order Butterworth will be used. ch_name : str | None If not None, specify EOG channel name. copy : bool If False, filtering raw data is done in place. Defaults to True. return_drop_log : bool If True, return the drop log. .. versionadded:: 0.15 meg : str Can be 'separate' (default) or 'combined' to compute projectors for magnetometers and gradiometers separately or jointly. If 'combined', ``n_mag == n_grad`` is required and the number of projectors computed for MEG will be ``n_mag``. .. versionadded:: 0.18 %(verbose)s Returns ------- %(projs)s eog_events: ndarray Detected EOG events. drop_log : list The drop log, if requested. See Also -------- find_eog_events create_eog_epochs Notes ----- Filtering is applied to the EOG channel while finding events using ``eog_l_freq`` and ``eog_h_freq``, and then to the ``raw`` instance using ``l_freq`` and ``h_freq`` before creation of the epochs used to create the projectors. """ return _compute_exg_proj( "EOG", raw, raw_event, tmin, tmax, n_grad, n_mag, n_eeg, l_freq, h_freq, average, filter_length, n_jobs, ch_name, reject, flat, bads, avg_ref, no_proj, event_id, eog_l_freq, eog_h_freq, tstart, "auto", filter_method, iir_params, return_drop_log, copy, meg, verbose, )