Skip to content

Commit

Permalink
ENH: add skip_by_annotation to notch_filter
Browse files Browse the repository at this point in the history
  • Loading branch information
jasmainak committed Jan 3, 2023
1 parent ea88c55 commit 3eab3c8
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 18 deletions.
1 change: 1 addition & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Enhancements
~~~~~~~~~~~~
- 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
~~~~
Expand Down
10 changes: 1 addition & 9 deletions mne/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 19 additions & 9 deletions mne/io/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions mne/tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions mne/utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3eab3c8

Please sign in to comment.