diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 6b28aacc19f..4046e0eaed1 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -23,7 +23,9 @@ Current (1.4.dev0) Enhancements ~~~~~~~~~~~~ -- None yet +- Added ability to read stimulus durations from SNIRF files when using :func:`mne.io.read_raw_snirf` (:gh:`11397` by `Robert Luke`_) +- Add :meth:`mne.Info.save` to save an :class:`mne.Info` object to a fif file (:gh:`11401` by `Alex Rockhill`_) +- Add support for ``skip_by_annotation`` in :func:`mne.io.Raw.notch_filter` (:gh:`11388` by `Mainak Jas`_) Bugs ~~~~ diff --git a/mne/filter.py b/mne/filter.py index 97df52eebf6..099f0975017 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -1935,15 +1935,7 @@ def filter(self, l_freq, h_freq, picks=None, filter_length='auto', %(phase)s %(fir_window)s %(fir_design)s - skip_by_annotation : str | list of str - If a string (or list of str), any annotation segment that begins - with the given string will not be included in filtering, and - segments on either side of the given excluded annotated segment - will be filtered separately (i.e., as independent signals). - The default (``('edge', 'bad_acq_skip')`` will separately filter - any segments that were concatenated by :func:`mne.concatenate_raws` - or :meth:`mne.io.Raw.append`, or separated during acquisition. - To disable, provide an empty list. Only used if ``inst`` is raw. + %(skip_by_annotation)s .. versionadded:: 0.16. %(pad_fir)s diff --git a/mne/io/base.py b/mne/io/base.py index 39669870505..b920d097aca 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -47,7 +47,8 @@ copy_function_doc_to_method_doc, _validate_type, _check_preload, _get_argvalues, _check_option, _build_data_frame, _convert_times, _scale_dataframe_data, - _check_time_format, _arange_div, TimeMixin, repr_html) + _check_time_format, _arange_div, TimeMixin, repr_html, + _pl) from ..defaults import _handle_default from ..viz import plot_raw, _RAW_CLIP_DEF from ..event import find_events, concatenate_events @@ -987,7 +988,9 @@ def notch_filter(self, freqs, picks=None, filter_length='auto', notch_widths=None, trans_bandwidth=1.0, n_jobs=None, method='fir', iir_params=None, mt_bandwidth=None, p_value=0.05, phase='zero', fir_window='hamming', - fir_design='firwin', pad='reflect_limited', verbose=None): + fir_design='firwin', pad='reflect_limited', + skip_by_annotation=('edge', 'bad_acq_skip'), + verbose=None): """Notch filter a subset of channels. Parameters @@ -1024,6 +1027,7 @@ def notch_filter(self, freqs, picks=None, filter_length='auto', The default is ``'reflect_limited'``. .. versionadded:: 0.15 + %(skip_by_annotation)s %(verbose)s Returns @@ -1053,13 +1057,19 @@ def notch_filter(self, freqs, picks=None, filter_length='auto', fs = float(self.info['sfreq']) picks = _picks_to_idx(self.info, picks, exclude=(), none='data_or_ica') _check_preload(self, 'raw.notch_filter') - self._data = notch_filter( - self._data, fs, freqs, filter_length=filter_length, - notch_widths=notch_widths, trans_bandwidth=trans_bandwidth, - method=method, iir_params=iir_params, mt_bandwidth=mt_bandwidth, - p_value=p_value, picks=picks, n_jobs=n_jobs, copy=False, - phase=phase, fir_window=fir_window, fir_design=fir_design, - pad=pad) + onsets, ends = _annotations_starts_stops( + self, skip_by_annotation, invert=True) + logger.info('Filtering raw data in %d contiguous segment%s' + % (len(onsets), _pl(onsets))) + for si, (start, stop) in enumerate(zip(onsets, ends)): + self._data = notch_filter( + self._data[:, start:stop], fs, freqs, + filter_length=filter_length, notch_widths=notch_widths, + trans_bandwidth=trans_bandwidth, method=method, + iir_params=iir_params, mt_bandwidth=mt_bandwidth, + p_value=p_value, picks=picks, n_jobs=n_jobs, copy=False, + phase=phase, fir_window=fir_window, fir_design=fir_design, + pad=pad) return self @verbose diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 6966fd2451c..ad5d0ea9da8 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -461,6 +461,16 @@ def test_annotation_filtering(first_samp): raws_concat_stop = raws_concat.copy().filter(skip_by_annotation='edge', **kwargs_stop) assert_allclose(raws_zero[0][0], raws_concat_stop[0][0], atol=1e-14) + + # test notch_filtering + raw_notch = concatenate_raws([raws_concat.copy(), raws_concat.copy()]) + raw_notch.annotations.append(7. + raw_notch._first_time, 1., 'foo_notch') + with catch_logging() as log: + raw_notch.notch_filter(60., fir_design='firwin', + skip_by_annotation='foo_notch', verbose='info') + log = log.getvalue() + assert '1 contiguous segment' in log + # one last test: let's cut out a section entirely: # here the 1-3 second window should be skipped raw = raws_concat.copy() diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 93205ccd1b0..0fcc6666d4f 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -3224,6 +3224,18 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): Side length of each subplot in inches. """ +docdict['skip_by_annotation'] = """ +skip_by_annotation : str | list of str + If a string (or list of str), any annotation segment that begins + with the given string will not be included in filtering, and + segments on either side of the given excluded annotated segment + will be filtered separately (i.e., as independent signals). + The default (``('edge', 'bad_acq_skip')`` will separately filter + any segments that were concatenated by :func:`mne.concatenate_raws` + or :meth:`mne.io.Raw.append`, or separated during acquisition. + To disable, provide an empty list. Only used if ``inst`` is raw. +""" + docdict['skip_by_annotation_maxwell'] = """ skip_by_annotation : str | list of str If a string (or list of str), any annotation segment that begins