diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 56f44f1ee4a..d761632babc 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -157,6 +157,8 @@ Changelog - Speed up :func:`mne.stats.summarize_clusters_stc` using Numba by `Yu-Han Luo`_ +- Add ``reject_by_annotation=True`` to :func:`mne.make_fixed_length_epochs` and :meth:`mne.preprocessing.ICA.plot_properties` to reject bad data segments based on annotation by `Yu-Han Luo`_ + Bug ~~~ @@ -291,6 +293,8 @@ Bug - Fix bug in :meth:`mne.preprocessing.ICA.plot_properties` where time series plot doesn't start at the proper tmin by `Teon Brooks`_ +- Fix bug with :meth:`mne.preprocessing.ICA.plot_properties` where a :class:`mne.io.Raw` object with annotations would lead to an error by `Yu-Han Luo`_ + API ~~~ diff --git a/mne/cov.py b/mne/cov.py index f98203e51f5..2a4f72b0df9 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -425,10 +425,7 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None, method equals 'auto' or is a list of str. Defaults to False. .. versionadded:: 0.12 - reject_by_annotation : bool - Whether to reject based on annotations. If True (default), epochs - overlapping with segments whose description begins with ``'bad'`` are - rejected. If False, no rejection based on annotations is performed. + %(reject_by_annotation_epochs)s .. versionadded:: 0.14 %(rank_None)s diff --git a/mne/epochs.py b/mne/epochs.py index b27ffe6eb05..886f5528487 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -2002,10 +2002,7 @@ class Epochs(BaseEpochs): warn, if 'ignore' it will proceed silently. Note. If none of the event ids are found in the data, an error will be automatically generated irrespective of this parameter. - reject_by_annotation : bool - Whether to reject based on annotations. If True (default), epochs - overlapping with segments whose description begins with ``'bad'`` are - rejected. If False, no rejection based on annotations is performed. + %(reject_by_annotation_epochs)s metadata : instance of pandas.DataFrame | None A :class:`pandas.DataFrame` specifying metadata about each epoch. If given, ``len(metadata)`` must equal ``len(events)``. The DataFrame @@ -3277,8 +3274,8 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None, @verbose -def make_fixed_length_epochs(raw, duration=1., - preload=False, verbose=None): +def make_fixed_length_epochs(raw, duration=1., preload=False, + reject_by_annotation=True, verbose=None): """Divide continuous raw data into equal-sized consecutive epochs. Parameters @@ -3288,6 +3285,9 @@ def make_fixed_length_epochs(raw, duration=1., duration : float Duration of each epoch in seconds. Defaults to 1. %(preload)s + %(reject_by_annotation_epochs)s + + .. versionadded:: 0.21.0 %(verbose)s Returns @@ -3301,6 +3301,6 @@ def make_fixed_length_epochs(raw, duration=1., """ events = make_fixed_length_events(raw, 1, duration=duration) delta = 1. / raw.info['sfreq'] - return Epochs(raw, events, event_id=[1], tmin=0., - tmax=duration - delta, - verbose=verbose, baseline=None) + return Epochs(raw, events, event_id=[1], tmin=0, tmax=duration - delta, + baseline=None, preload=preload, + reject_by_annotation=reject_by_annotation, verbose=verbose) diff --git a/mne/preprocessing/ecg.py b/mne/preprocessing/ecg.py index 8742c42fc5f..ccc8c453dad 100644 --- a/mne/preprocessing/ecg.py +++ b/mne/preprocessing/ecg.py @@ -165,8 +165,7 @@ def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0, return_ecg : bool Return ecg channel if synthesized. Defaults to False. If True and and ecg exists this will yield None. - reject_by_annotation : bool - Whether to omit data that is annotated as bad. + %(reject_by_annotation_all)s .. versionadded:: 0.18 %(verbose)s @@ -329,10 +328,7 @@ def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5, When ECG is synthetically created (after picking), should it be added to the epochs? Must be False when synthetic channel is not used. Defaults to False. - reject_by_annotation : bool - Whether to reject based on annotations. If True (default), epochs - overlapping with segments whose description begins with ``'bad'`` are - rejected. If False, no rejection based on annotations is performed. + %(reject_by_annotation_epochs)s .. versionadded:: 0.14.0 %(verbose)s diff --git a/mne/preprocessing/eog.py b/mne/preprocessing/eog.py index 9481eef862d..1fcadb9cfc9 100644 --- a/mne/preprocessing/eog.py +++ b/mne/preprocessing/eog.py @@ -210,11 +210,7 @@ def create_eog_epochs(raw, ch_name=None, event_id=998, picks=None, tmin=-0.5, interval is used. If None, no correction is applied. preload : bool Preload epochs or not. - reject_by_annotation : bool - Whether to reject based on annotations. If True (default), segments - whose description begins with ``'bad'`` are not used for finding - artifacts and epochs overlapping with them are rejected. If False, no - rejection based on annotations is performed. + %(reject_by_annotation_epochs)s .. versionadded:: 0.14.0 thresh : float diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py index 7974608203a..3f38a8ef55b 100644 --- a/mne/preprocessing/ica.py +++ b/mne/preprocessing/ica.py @@ -474,11 +474,7 @@ def fit(self, inst, picks=None, start=None, stop=None, decim=None, tstep : float Length of data chunks for artifact rejection in seconds. It only applies if ``inst`` is of type Raw. - reject_by_annotation : bool - Whether to omit bad segments from the data before fitting. If True, - annotated segments with a description that starts with 'bad' are - omitted. Has no effect if ``inst`` is an Epochs or Evoked object. - Defaults to True. + %(reject_by_annotation_raw)s .. versionadded:: 0.14.0 %(verbose_meth)s @@ -986,8 +982,7 @@ def score_sources(self, inst, target=None, score_func='pearsonr', Low pass frequency. h_freq : float High pass frequency. - reject_by_annotation : bool - If True, data annotated as bad will be omitted. Defaults to True. + %(reject_by_annotation_all)s .. versionadded:: 0.14.0 %(verbose_meth)s @@ -1196,8 +1191,7 @@ def find_bads_ecg(self, inst, ch_name=None, threshold=None, start=None, threshold components will be masked and the z-score will be recomputed until no supra-threshold component remains. Defaults to 'ctps'. - reject_by_annotation : bool - If True, data annotated as bad will be omitted. Defaults to True. + %(reject_by_annotation_all)s .. versionadded:: 0.14.0 measure : 'zscore' | 'correlation' @@ -1316,8 +1310,7 @@ def find_bads_ref(self, inst, ch_name=None, threshold=3.0, start=None, Low pass frequency. h_freq : float High pass frequency. - reject_by_annotation : bool - If True, data annotated as bad will be omitted. Defaults to True. + %(reject_by_annotation_all)s method : 'together' | 'separate' Method to use to identify reference channel related components. Defaults to ``'together'``. See notes. @@ -1446,8 +1439,7 @@ def find_bads_eog(self, inst, ch_name=None, threshold=3.0, start=None, Low pass frequency. h_freq : float High pass frequency. - reject_by_annotation : bool - If True, data annotated as bad will be omitted. Defaults to True. + %(reject_by_annotation_all)s .. versionadded:: 0.14.0 measure : 'zscore' | 'correlation' @@ -1749,12 +1741,14 @@ def plot_components(self, picks=None, ch_type=None, res=64, @copy_function_doc_to_method_doc(plot_ica_properties) def plot_properties(self, inst, picks=None, axes=None, dB=True, plot_std=True, topomap_args=None, image_args=None, - psd_args=None, figsize=None, show=True, reject='auto'): + psd_args=None, figsize=None, show=True, reject='auto', + reject_by_annotation=True): return plot_ica_properties(self, inst, picks=picks, axes=axes, dB=dB, plot_std=plot_std, topomap_args=topomap_args, image_args=image_args, psd_args=psd_args, - figsize=figsize, show=show, reject=reject) + figsize=figsize, show=show, reject=reject, + reject_by_annotation=reject_by_annotation) @copy_function_doc_to_method_doc(plot_ica_sources) def plot_sources(self, inst, picks=None, start=None, diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py index 2badb34a60e..c9bc58f3f82 100644 --- a/mne/tests/test_epochs.py +++ b/mne/tests/test_epochs.py @@ -22,7 +22,7 @@ from mne import (Epochs, Annotations, read_events, pick_events, read_epochs, equalize_channels, pick_types, pick_channels, read_evokeds, write_evokeds, create_info, make_fixed_length_events, - combine_evoked) + make_fixed_length_epochs, combine_evoked) from mne.baseline import rescale from mne.fixes import rfft, rfftfreq from mne.preprocessing import maxwell_filter @@ -3019,4 +3019,19 @@ def test_pick_types_reject_flat_keys(): assert sorted(epochs.flat.keys()) == ['grad', 'mag'] +@testing.requires_testing_data +def test_make_fixed_length_epochs(): + """Test dividing raw data into equal-sized consecutive epochs.""" + raw = read_raw_fif(raw_fname, preload=True) + epochs = make_fixed_length_epochs(raw, duration=1, preload=True) + # Test Raw with annotations + annot = Annotations(onset=[0], duration=[5], description=['BAD']) + raw_annot = raw.set_annotations(annot) + epochs_annot = make_fixed_length_epochs(raw_annot, duration=1.0, + preload=True) + assert len(epochs) > 10 + assert len(epochs_annot) > 10 + assert len(epochs) > len(epochs_annot) + + run_tests_if_main() diff --git a/mne/time_frequency/psd.py b/mne/time_frequency/psd.py index c7c2131e41a..6adaa310876 100644 --- a/mne/time_frequency/psd.py +++ b/mne/time_frequency/psd.py @@ -206,11 +206,7 @@ def psd_welch(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None, n_fft=256, proj : bool Apply SSP projection vectors. If inst is ndarray this is not used. %(n_jobs)s - reject_by_annotation : bool - Whether to omit bad segments from the data while computing the - PSD. If True, annotated segments with a description that starts - with 'bad' are omitted. Has no effect if ``inst`` is an Epochs or - Evoked object. Defaults to True. + %(reject_by_annotation_raw)s .. versionadded:: 0.15.0 average : str | None diff --git a/mne/utils/docs.py b/mne/utils/docs.py index d40f2a12057..a1d5e30a702 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -72,6 +72,24 @@ End time of the raw data to use in seconds (cannot exceed data duration). """ + +# Reject by annotation +docdict['reject_by_annotation_all'] = """ +reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. +""" +docdict['reject_by_annotation_epochs'] = """ +reject_by_annotation : bool + Whether to reject based on annotations. If ``True`` (default), epochs + overlapping with segments whose description begins with ``'bad'`` are + rejected. If ``False``, no rejection based on annotations is performed. +""" +docdict['reject_by_annotation_raw'] = docdict['reject_by_annotation_all'] + """ + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object.""" + + # General plotting docdict["show"] = """ show : bool diff --git a/mne/viz/ica.py b/mne/viz/ica.py index 5643badbae7..96e55a0f93a 100644 --- a/mne/viz/ica.py +++ b/mne/viz/ica.py @@ -258,7 +258,8 @@ def _get_psd_label_and_std(this_psd, dB, ica, num_std): @fill_doc def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True, plot_std=True, topomap_args=None, image_args=None, - psd_args=None, figsize=None, show=True, reject='auto'): + psd_args=None, figsize=None, show=True, reject='auto', + reject_by_annotation=True): """Display component properties. Properties include the topography, epochs image, ERP/ERF, power @@ -309,6 +310,9 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True, If None, no rejection is applied. The default is 'auto', which applies the rejection parameters used when fitting the ICA object. + %(reject_by_annotation_raw)s + + .. versionadded:: 0.21.0 Returns ------- @@ -390,12 +394,18 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True, # break up continuous signal into segments from ..epochs import make_fixed_length_epochs - inst_rejected = make_fixed_length_epochs(inst_rejected, - duration=2., - verbose=False, - preload=True) - inst = make_fixed_length_epochs(inst, duration=2., verbose=False, - preload=True) + inst_rejected = make_fixed_length_epochs( + inst_rejected, + duration=2, + preload=True, + reject_by_annotation=reject_by_annotation, + verbose=False) + inst = make_fixed_length_epochs( + inst, + duration=2, + preload=True, + reject_by_annotation=reject_by_annotation, + verbose=False) kind = "Segment" else: drop_inds = None diff --git a/mne/viz/raw.py b/mne/viz/raw.py index 6d3952dcae4..861965d2e3b 100644 --- a/mne/viz/raw.py +++ b/mne/viz/raw.py @@ -604,11 +604,7 @@ def plot_raw_psd(raw, fmin=0, fmax=np.inf, tmin=None, tmax=None, proj=False, n_overlap : int The number of points of overlap between blocks. The default value is 0 (no overlap). - reject_by_annotation : bool - Whether to omit bad segments from the data while computing the - PSD. If True, annotated segments with a description that starts - with 'bad' are omitted. Has no effect if ``inst`` is an Epochs or - Evoked object. Defaults to True. + %(reject_by_annotation_raw)s %(plot_psd_picks_good_data)s ax : instance of Axes | None Axes to plot into. If None, axes will be created. diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py index 26a5fd6ce3e..c41d072f281 100644 --- a/mne/viz/tests/test_ica.py +++ b/mne/viz/tests/test_ica.py @@ -181,6 +181,25 @@ def test_plot_ica_properties(): ica.plot_properties(epochs) plt.close('all') + # Test Raw with annotations + annot = Annotations(onset=[1], duration=[1], description=['BAD']) + raw_annot = _get_raw(preload=True).set_annotations(annot) + + with pytest.warns(UserWarning, match='did not converge'): + ica.fit(raw_annot) + # drop bad data segments + ica.plot_properties(raw_annot) + # don't drop + ica.plot_properties(raw_annot, reject_by_annotation=False) + # fitting with bad data + with pytest.warns(UserWarning, match='did not converge'): + ica.fit(raw_annot, reject_by_annotation=False) + # drop bad data when plotting + ica.plot_properties(raw_annot) + # don't drop bad data when plotting + ica.plot_properties(raw_annot, reject_by_annotation=False) + plt.close('all') + @requires_sklearn def test_plot_ica_sources():