From ff5e3dcca791bbb35dfa1f60005724006481fc16 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 09:29:40 +0100 Subject: [PATCH 01/18] TST: add test for chunk_duration --- mne/tests/test_annotations.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index cfdc217c285..2d248366e73 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -185,6 +185,26 @@ def test_crop(): assert len(raw_read.annotations.onset) == 0 # XXX to be fixed in #5416 +def test_chunk_duration(): + """Test chunk_duration. + + If chunk_duration parameter in events_from_annotations is None, events + correspond to the annotation onsets. If not, events_from_annotations + returns as many events as they fit within the annotation duration spaced + according to `chunk_duraiton`. + """ + # create dummy raw + raw = RawArray(data=np.empty([10, 10], dtype=np.float64), + info=create_info(ch_names=10, sfreq=1000.), + first_samp=0) + raw.info['meas_date'] = 0 + raw.set_annotations(Annotations(description='foo', onset=[0], + duration=[10], orig_time=None)) + + events = events_from_annotations(raw, chunk_duration=1) + assert_array_equal(events, np.arrange(10)) + + def test_crop_more(): """Test more cropping.""" raw = mne.io.read_raw_fif(fif_fname).crop(0, 11).load_data() From 97fd76df8b293b89abd2df8265daee573bcc5666 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 10:38:35 +0100 Subject: [PATCH 02/18] simplify the code --- mne/annotations.py | 98 +++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 83a64a1c97c..a240936fce3 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -700,16 +700,60 @@ def _ensure_annotation_object(obj): raise ValueError('Annotations must be an instance of ' 'mne.Annotations. Got %s.' % obj) +def _select_annotations_based_on_description(descriptions, event_id=None, + regexp=None): + """Gets a collection of descriptions and returns index of selected. + """ + # Filter out the annotations that do not match regexp + regexp_comp = re.compile('.*' if regexp is None else regexp) + + if event_id is None: + event_id = Counter() + + event_id_ = dict() + dropped = [] + for desc in descriptions: + if desc in event_id_: + continue + + if regexp_comp.match(desc) is None: + continue + + if isinstance(event_id, dict): + if desc in event_id: + event_id_[desc] = event_id[desc] + else: + continue + else: + trigger = event_id(desc) + if trigger is not None: + event_id_[desc] = trigger + else: + dropped.append(desc) + + event_sel = [ii for ii, kk in enumerate(descriptions) + if kk in event_id_] + + if len(event_sel) == 0 and regexp is not None: + raise ValueError('Could not find any of the events you specified.') + + return event_sel, event_id_ + @verbose -def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, - verbose=None): +def events_from_annotations(raw, chunk_duration=None, event_id=None, + regexp=None, use_rounding=True, verbose=None): """Get events and event_id from an Annotations object. Parameters ---------- raw : instance of Raw The raw data for which Annotations are defined. + chunk_duration: int | None + If chunk_duration parameter in events_from_annotations is None, events + correspond to the annotation onsets. If not, events_from_annotations + returns as many events as they fit within the annotation duration spaced + according to `chunk_duraiton`. event_id : dict | Callable | None Dictionary of string keys and integer values as used in mne.Epochs to map annotation descriptions to integer event codes. Only the @@ -740,48 +784,20 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, return np.empty((0, 3), dtype=int), event_id annotations = raw.annotations + + event_sel, event_id_ = _select_annotations_based_on_description( + annotations.description, event_id=event_id, regexp=regexp) - inds = raw.time_as_index(annotations.onset, use_rounding=use_rounding, - origin=annotations.orig_time) + raw.first_samp - - # Filter out the annotations that do not match regexp - regexp_comp = re.compile('.*' if regexp is None else regexp) - - if event_id is None: - event_id = Counter() - - event_id_ = dict() - dropped = [] - for desc in annotations.description: - if desc in event_id_: - continue - - if regexp_comp.match(desc) is None: - continue - - if isinstance(event_id, dict): - if desc in event_id: - event_id_[desc] = event_id[desc] - else: - continue - else: - trigger = event_id(desc) - if trigger is not None: - event_id_[desc] = trigger - else: - dropped.append(desc) - - event_sel = [ii for ii, kk in enumerate(annotations.description) - if kk in event_id_] + if chunk_duration is None: + inds = raw.time_as_index(annotations.onset, use_rounding=use_rounding, + origin=annotations.orig_time) + raw.first_samp - if len(event_sel) == 0 and regexp is not None: - raise ValueError('Could not find any of the events you specified.') + values = [event_id_[kk] for kk in annotations.description[event_sel]] + inds = inds[event_sel] + else: + pass - values = [event_id_[kk] for kk in - annotations.description[event_sel]] - previous_value = np.zeros(len(event_sel)) - inds = inds[event_sel] - events = np.c_[inds, previous_value, values].astype(int) + events = np.c_[inds, np.zeros(len(inds)), values].astype(int) logger.info('Used Annotations descriptions: %s' % (list(event_id_.keys()),)) From 16cfd9a78330558af28c89112b0bbdf40e36cc35 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 11:00:00 +0100 Subject: [PATCH 03/18] add functionality --- mne/annotations.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index a240936fce3..baa1714ecf7 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -784,7 +784,7 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, return np.empty((0, 3), dtype=int), event_id annotations = raw.annotations - + event_sel, event_id_ = _select_annotations_based_on_description( annotations.description, event_id=event_id, regexp=regexp) @@ -795,7 +795,21 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, values = [event_id_[kk] for kk in annotations.description[event_sel]] inds = inds[event_sel] else: - pass + inds = [] + values = [] + iterator = zip(annotations.onset[event_sel], + annotations.duration[event_sel], + annotations.description[event_sel]) + for onset, duration, description in iterator: + _onsets = np.arange(start=onset, stop=onset+duration, + step=chunk_duration) + _inds = raw.time_as_index(_onsets, + use_rounding=use_rounding, + origin=annotations.orig_time) + _inds += raw.first_samp + inds.append(_inds) + values.append(np.full(shape=len(_inds), + fill_value=event_id_[description])) events = np.c_[inds, np.zeros(len(inds)), values].astype(int) From 65d349754c09f4063d19497b169de216c4924424 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 12:06:08 +0100 Subject: [PATCH 04/18] FIX: chunk_duration --- mne/annotations.py | 15 ++++++++------- mne/tests/test_annotations.py | 10 +++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index baa1714ecf7..62f28be30a4 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -795,11 +795,11 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, values = [event_id_[kk] for kk in annotations.description[event_sel]] inds = inds[event_sel] else: - inds = [] - values = [] - iterator = zip(annotations.onset[event_sel], + inds = values = np.array([]).astype(int) + iterator = list(zip(annotations.onset[event_sel], annotations.duration[event_sel], - annotations.description[event_sel]) + annotations.description[event_sel])) + for onset, duration, description in iterator: _onsets = np.arange(start=onset, stop=onset+duration, step=chunk_duration) @@ -807,9 +807,10 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, use_rounding=use_rounding, origin=annotations.orig_time) _inds += raw.first_samp - inds.append(_inds) - values.append(np.full(shape=len(_inds), - fill_value=event_id_[description])) + inds = np.append(inds, _inds) + _values = np.full(shape=len(_inds), + fill_value=event_id_[description]) + values = np.append(values, _values) events = np.c_[inds, np.zeros(len(inds)), values].astype(int) diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 2d248366e73..461f0e59f48 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -195,14 +195,18 @@ def test_chunk_duration(): """ # create dummy raw raw = RawArray(data=np.empty([10, 10], dtype=np.float64), - info=create_info(ch_names=10, sfreq=1000.), + info=create_info(ch_names=10, sfreq=1.), first_samp=0) raw.info['meas_date'] = 0 raw.set_annotations(Annotations(description='foo', onset=[0], duration=[10], orig_time=None)) - events = events_from_annotations(raw, chunk_duration=1) - assert_array_equal(events, np.arrange(10)) + EXPECTED_EVENT_ONSETS = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, + 8, 8, 9, 9] + + events, events_id = events_from_annotations(raw, chunk_duration=.5, + use_rounding=False) + assert_array_equal(events[:,0], EXPECTED_EVENT_ONSETS) def test_crop_more(): From ac9eb3408950656118970244c5fbea6ae4949e3d Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 12:57:17 +0100 Subject: [PATCH 05/18] TST: use entire events not only the onsets for test_chunk_dureation --- mne/tests/test_annotations.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 461f0e59f48..67b6ef028d7 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -201,12 +201,15 @@ def test_chunk_duration(): raw.set_annotations(Annotations(description='foo', onset=[0], duration=[10], orig_time=None)) - EXPECTED_EVENT_ONSETS = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, - 8, 8, 9, 9] + # expected_events = [[0, 0, 1], [0, 0, 1], [1, 0, 1], [1, 0, 1], .. + # [9, 0, 1], [9, 0, 1]] + expected_events = np.atleast_2d(np.repeat(range(10), repeats=2)).T + expected_events = np.insert(expected_events, 1, 0, axis=1) + expected_events = np.insert(expected_events, 2, 1, axis=1) events, events_id = events_from_annotations(raw, chunk_duration=.5, use_rounding=False) - assert_array_equal(events[:,0], EXPECTED_EVENT_ONSETS) + assert_array_equal(events, expected_events) def test_crop_more(): From 81089dea2ca062750a74247b927e322bd4a5e19c Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 14:11:15 +0100 Subject: [PATCH 06/18] Update whatsnew --- doc/whats_new.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 756b3cc4cb5..f10ab558915 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -19,9 +19,11 @@ Current Changelog ~~~~~~~~~ +- Add ``chunk_duration`` parameter to :func:`mne.events_from_annotations` to allow multiple events from a single annotation by `Joan Massich`_ + - :func:`mne.io.read_raw_edf` now detects analog stim channels labeled ``'STATUS'`` and sets them as stim channel. :func:`mne.io.read_raw_edf` no longer synthesize TAL annotations into stim channel but stores them in ``raw.annotations`` when reading by `Joan Massich`_ -- Add ``drop_refs=True`` parameter to :func:`set_bipolar_reference` to drop/keep anode and cathode channels after applying the reference by `Cristóbal Moënne-Loccoz`_. +- Add ``drop_refs=True`` parameter to :func:`set_bipolar_reference` to drop/keep anode and cathode channels after applying the reference by `Cristóbal Moënne-Loccoz`_. - Add 448-labels subdivided aparc cortical parcellation by `Denis Engemann`_ and `Sheraz Khan`_ From 5ce12312a5ecb37b8f3b97fe7b832f2dccd89fb4 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 15:46:23 +0100 Subject: [PATCH 07/18] FIX: PEP8 (damn pep8) --- mne/annotations.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 62f28be30a4..8c6f1b8ee7d 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -700,10 +700,10 @@ def _ensure_annotation_object(obj): raise ValueError('Annotations must be an instance of ' 'mne.Annotations. Got %s.' % obj) + def _select_annotations_based_on_description(descriptions, event_id=None, regexp=None): - """Gets a collection of descriptions and returns index of selected. - """ + """Get a collection of descriptions and returns index of selected.""" # Filter out the annotations that do not match regexp regexp_comp = re.compile('.*' if regexp is None else regexp) @@ -751,9 +751,10 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, The raw data for which Annotations are defined. chunk_duration: int | None If chunk_duration parameter in events_from_annotations is None, events - correspond to the annotation onsets. If not, events_from_annotations - returns as many events as they fit within the annotation duration spaced - according to `chunk_duraiton`. + correspond to the annotation onsets. + If not, :func:`mne.events_from_annotations` returns as many events as + they fit within the annotation duration spaced according to + `chunk_duration`, which is given in seconds. event_id : dict | Callable | None Dictionary of string keys and integer values as used in mne.Epochs to map annotation descriptions to integer event codes. Only the @@ -786,22 +787,22 @@ def events_from_annotations(raw, chunk_duration=None, event_id=None, annotations = raw.annotations event_sel, event_id_ = _select_annotations_based_on_description( - annotations.description, event_id=event_id, regexp=regexp) + annotations.description, event_id=event_id, regexp=regexp) if chunk_duration is None: inds = raw.time_as_index(annotations.onset, use_rounding=use_rounding, - origin=annotations.orig_time) + raw.first_samp + origin=annotations.orig_time) + raw.first_samp values = [event_id_[kk] for kk in annotations.description[event_sel]] inds = inds[event_sel] else: inds = values = np.array([]).astype(int) iterator = list(zip(annotations.onset[event_sel], - annotations.duration[event_sel], - annotations.description[event_sel])) + annotations.duration[event_sel], + annotations.description[event_sel])) for onset, duration, description in iterator: - _onsets = np.arange(start=onset, stop=onset+duration, + _onsets = np.arange(start=onset, stop=(onset + duration), step=chunk_duration) _inds = raw.time_as_index(_onsets, use_rounding=use_rounding, From 0bf99c8c130966ced5bd8c58105ba4e9579eaddd Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 15:47:31 +0100 Subject: [PATCH 08/18] reorder parameters to avoid API change --- mne/annotations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 8c6f1b8ee7d..bf4d51db514 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -741,8 +741,8 @@ def _select_annotations_based_on_description(descriptions, event_id=None, @verbose -def events_from_annotations(raw, chunk_duration=None, event_id=None, - regexp=None, use_rounding=True, verbose=None): +def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, + chunk_duration=None, verbose=None): """Get events and event_id from an Annotations object. Parameters From 569bcc3a8ce65f8daf0dba3056f8cfc19c0af6c8 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 17:15:03 +0100 Subject: [PATCH 09/18] Move the docstring where its due --- mne/annotations.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index bf4d51db514..b018342b1da 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -749,12 +749,6 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, ---------- raw : instance of Raw The raw data for which Annotations are defined. - chunk_duration: int | None - If chunk_duration parameter in events_from_annotations is None, events - correspond to the annotation onsets. - If not, :func:`mne.events_from_annotations` returns as many events as - they fit within the annotation duration spaced according to - `chunk_duration`, which is given in seconds. event_id : dict | Callable | None Dictionary of string keys and integer values as used in mne.Epochs to map annotation descriptions to integer event codes. Only the @@ -769,6 +763,12 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, use_rounding : boolean If True, use rounding (instead of truncation) when converting times to indices. This can help avoid non-unique indices. + chunk_duration: int | None + If chunk_duration parameter in events_from_annotations is None, events + correspond to the annotation onsets. + If not, :func:`mne.events_from_annotations` returns as many events as + they fit within the annotation duration spaced according to + `chunk_duration`, which is given in seconds. verbose : bool, str, int, or None If not None, override default verbose level (see :func:`mne.verbose` and :ref:`Logging documentation ` From 4f72212dcff9b00ecaa5ef088d578f00bcf2ae0c Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 17:18:26 +0100 Subject: [PATCH 10/18] avoid future warning --- mne/annotations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/annotations.py b/mne/annotations.py index b018342b1da..654280b88f2 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -810,7 +810,8 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, _inds += raw.first_samp inds = np.append(inds, _inds) _values = np.full(shape=len(_inds), - fill_value=event_id_[description]) + fill_value=event_id_[description], + dtype=int) values = np.append(values, _values) events = np.c_[inds, np.zeros(len(inds)), values].astype(int) From 5ed0ce36d46497a3ab90295efe599e6f7ddd50c5 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 17:57:14 +0100 Subject: [PATCH 11/18] Change regexp default in events_from_annotations --- mne/annotations.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 654280b88f2..00e88252fb2 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -704,8 +704,8 @@ def _ensure_annotation_object(obj): def _select_annotations_based_on_description(descriptions, event_id=None, regexp=None): """Get a collection of descriptions and returns index of selected.""" - # Filter out the annotations that do not match regexp - regexp_comp = re.compile('.*' if regexp is None else regexp) + + regexp_comp = re.compile(regexp) if event_id is None: event_id = Counter() @@ -741,7 +741,7 @@ def _select_annotations_based_on_description(descriptions, event_id=None, @verbose -def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, +def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, chunk_duration=None, verbose=None): """Get events and event_id from an Annotations object. @@ -757,7 +757,7 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, a string or that returns None for an event to ignore. If None, all descriptions of annotations are mapped and assigned arbitrary unique integer values. - regexp : str | None + regexp : str Regular expression used to filter the annotations whose descriptions is a match. use_rounding : boolean @@ -786,6 +786,9 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, annotations = raw.annotations + # regexp None support for 0.17 backward compat, not exposed to users + regexp = '.*' if regexp is None else regexp + event_sel, event_id_ = _select_annotations_based_on_description( annotations.description, event_id=event_id, regexp=regexp) From 7406b290b438a0a3961e39851726f4091ec01a37 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 17 Dec 2018 18:07:46 +0100 Subject: [PATCH 12/18] docstring --- mne/tests/test_annotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 67b6ef028d7..1d7fea294c0 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -191,7 +191,7 @@ def test_chunk_duration(): If chunk_duration parameter in events_from_annotations is None, events correspond to the annotation onsets. If not, events_from_annotations returns as many events as they fit within the annotation duration spaced - according to `chunk_duraiton`. + according to `chunk_duration` parameter in `events_from_annotations`. """ # create dummy raw raw = RawArray(data=np.empty([10, 10], dtype=np.float64), From a8d8b9fe1da7830842ee71ec762d42135a713c03 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Tue, 18 Dec 2018 11:03:56 +0100 Subject: [PATCH 13/18] ups --- mne/annotations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 00e88252fb2..946e93d49e0 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -702,9 +702,8 @@ def _ensure_annotation_object(obj): def _select_annotations_based_on_description(descriptions, event_id=None, - regexp=None): + regexp='.*'): """Get a collection of descriptions and returns index of selected.""" - regexp_comp = re.compile(regexp) if event_id is None: From 764e7f0e8053c3a1cdc781bbff06ff2204178ce6 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Wed, 19 Dec 2018 16:57:51 +0100 Subject: [PATCH 14/18] [skip ci] DOC: fix minor stuff --- mne/annotations.py | 2 +- mne/tests/test_annotations.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 946e93d49e0..8ccb58438ce 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -762,7 +762,7 @@ def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, use_rounding : boolean If True, use rounding (instead of truncation) when converting times to indices. This can help avoid non-unique indices. - chunk_duration: int | None + chunk_duration: float | None If chunk_duration parameter in events_from_annotations is None, events correspond to the annotation onsets. If not, :func:`mne.events_from_annotations` returns as many events as diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 1d7fea294c0..c5bd5047845 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -186,13 +186,7 @@ def test_crop(): def test_chunk_duration(): - """Test chunk_duration. - - If chunk_duration parameter in events_from_annotations is None, events - correspond to the annotation onsets. If not, events_from_annotations - returns as many events as they fit within the annotation duration spaced - according to `chunk_duration` parameter in `events_from_annotations`. - """ + """Test chunk_duration.""" # create dummy raw raw = RawArray(data=np.empty([10, 10], dtype=np.float64), info=create_info(ch_names=10, sfreq=1.), From d8c17545f830899e6332b43781c6d2564d7f8904 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Wed, 19 Dec 2018 17:39:11 +0100 Subject: [PATCH 15/18] ENH: Add raise/warn/log on_missing --- mne/annotations.py | 26 +++++++++++++++++++++----- mne/tests/test_annotations.py | 5 ++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 8ccb58438ce..4b8bbfad04c 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -702,7 +702,7 @@ def _ensure_annotation_object(obj): def _select_annotations_based_on_description(descriptions, event_id=None, - regexp='.*'): + regexp='.*', on_missing='ignore'): """Get a collection of descriptions and returns index of selected.""" regexp_comp = re.compile(regexp) @@ -733,15 +733,24 @@ def _select_annotations_based_on_description(descriptions, event_id=None, event_sel = [ii for ii, kk in enumerate(descriptions) if kk in event_id_] - if len(event_sel) == 0 and regexp is not None: - raise ValueError('Could not find any of the events you specified.') + if len(event_sel) == 0 and regexp is not '.*': + msg = 'Could not find any of the events you specified.' + if on_missing == 'error': + raise ValueError(msg) + elif on_missing == 'warning': + warn(msg) + elif on_missing == 'log': + logger.info(msg) + else: # on_missing == 'ignore': + pass return event_sel, event_id_ @verbose def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, - chunk_duration=None, verbose=None): + chunk_duration=None, on_missing='error', + verbose=None): """Get events and event_id from an Annotations object. Parameters @@ -768,6 +777,12 @@ def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, If not, :func:`mne.events_from_annotations` returns as many events as they fit within the annotation duration spaced according to `chunk_duration`, which is given in seconds. + on_missing : str + What to do if no events are found based on `event_id` and `regexp` + parameters. + Valid keys are 'error' | 'warning' | 'log' | 'ignore' + Default is 'error'. If on_missing is 'warning' it will proceed but + warn, if 'ignore' it will proceed silently. verbose : bool, str, int, or None If not None, override default verbose level (see :func:`mne.verbose` and :ref:`Logging documentation ` @@ -789,7 +804,8 @@ def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, regexp = '.*' if regexp is None else regexp event_sel, event_id_ = _select_annotations_based_on_description( - annotations.description, event_id=event_id, regexp=regexp) + annotations.description, event_id=event_id, regexp=regexp, + on_missing=on_missing) if chunk_duration is None: inds = raw.time_as_index(annotations.onset, use_rounding=use_rounding, diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index c5bd5047845..0ff3942b62d 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -551,7 +551,10 @@ def test_events_from_annot_in_raw_objects(): assert_array_equal(expected_events5, events5) with pytest.raises(ValueError, match='not find any of the events'): - events_from_annotations(raw, regexp='not_there') + events_from_annotations(raw, regexp='not_there', on_missing='error') + + with pytest.warns(RuntimeWarning, match='not find any of the events'): + events_from_annotations(raw, regexp='not_there', on_missing='warning') raw.set_annotations(None) events7, _ = events_from_annotations(raw) From 93eb415cbf593afc7b7429f09d3d6a52762db7c9 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Wed, 19 Dec 2018 18:37:15 +0100 Subject: [PATCH 16/18] Revert regexp='.*' in favor of regexp=None --- mne/annotations.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 4b8bbfad04c..628aa606f10 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -702,9 +702,9 @@ def _ensure_annotation_object(obj): def _select_annotations_based_on_description(descriptions, event_id=None, - regexp='.*', on_missing='ignore'): + regexp=None, on_missing='ignore'): """Get a collection of descriptions and returns index of selected.""" - regexp_comp = re.compile(regexp) + regexp_comp = re.compile('.*' if regexp is None else regexp) if event_id is None: event_id = Counter() @@ -733,7 +733,7 @@ def _select_annotations_based_on_description(descriptions, event_id=None, event_sel = [ii for ii, kk in enumerate(descriptions) if kk in event_id_] - if len(event_sel) == 0 and regexp is not '.*': + if len(event_sel) == 0 and regexp is not None: msg = 'Could not find any of the events you specified.' if on_missing == 'error': raise ValueError(msg) @@ -748,7 +748,7 @@ def _select_annotations_based_on_description(descriptions, event_id=None, @verbose -def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, +def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, chunk_duration=None, on_missing='error', verbose=None): """Get events and event_id from an Annotations object. @@ -765,7 +765,7 @@ def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, a string or that returns None for an event to ignore. If None, all descriptions of annotations are mapped and assigned arbitrary unique integer values. - regexp : str + regexp : str | None Regular expression used to filter the annotations whose descriptions is a match. use_rounding : boolean @@ -800,9 +800,6 @@ def events_from_annotations(raw, event_id=None, regexp='.*', use_rounding=True, annotations = raw.annotations - # regexp None support for 0.17 backward compat, not exposed to users - regexp = '.*' if regexp is None else regexp - event_sel, event_id_ = _select_annotations_based_on_description( annotations.description, event_id=event_id, regexp=regexp, on_missing=on_missing) From 71623e89148c2b4d14a343970ab723e55f09c77d Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Wed, 19 Dec 2018 18:39:53 +0100 Subject: [PATCH 17/18] revert on_missing --- mne/annotations.py | 19 ++----------------- mne/tests/test_annotations.py | 3 --- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 628aa606f10..b8048b6d90e 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -734,23 +734,14 @@ def _select_annotations_based_on_description(descriptions, event_id=None, if kk in event_id_] if len(event_sel) == 0 and regexp is not None: - msg = 'Could not find any of the events you specified.' - if on_missing == 'error': - raise ValueError(msg) - elif on_missing == 'warning': - warn(msg) - elif on_missing == 'log': - logger.info(msg) - else: # on_missing == 'ignore': - pass + raise ValueError('Could not find any of the events you specified.') return event_sel, event_id_ @verbose def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, - chunk_duration=None, on_missing='error', - verbose=None): + chunk_duration=None, verbose=None): """Get events and event_id from an Annotations object. Parameters @@ -777,12 +768,6 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, If not, :func:`mne.events_from_annotations` returns as many events as they fit within the annotation duration spaced according to `chunk_duration`, which is given in seconds. - on_missing : str - What to do if no events are found based on `event_id` and `regexp` - parameters. - Valid keys are 'error' | 'warning' | 'log' | 'ignore' - Default is 'error'. If on_missing is 'warning' it will proceed but - warn, if 'ignore' it will proceed silently. verbose : bool, str, int, or None If not None, override default verbose level (see :func:`mne.verbose` and :ref:`Logging documentation ` diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 0ff3942b62d..a63fde44b4e 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -553,9 +553,6 @@ def test_events_from_annot_in_raw_objects(): with pytest.raises(ValueError, match='not find any of the events'): events_from_annotations(raw, regexp='not_there', on_missing='error') - with pytest.warns(RuntimeWarning, match='not find any of the events'): - events_from_annotations(raw, regexp='not_there', on_missing='warning') - raw.set_annotations(None) events7, _ = events_from_annotations(raw) assert_array_equal(events7, np.empty((0, 3), dtype=int)) From d4c21a4fe0d7da507ecc855f98a21ad0b7c0f4db Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 19 Dec 2018 14:18:04 -0500 Subject: [PATCH 18/18] FIX: No more on_missing --- mne/annotations.py | 6 ++---- mne/tests/test_annotations.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index b8048b6d90e..7def64b65f3 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -701,8 +701,7 @@ def _ensure_annotation_object(obj): 'mne.Annotations. Got %s.' % obj) -def _select_annotations_based_on_description(descriptions, event_id=None, - regexp=None, on_missing='ignore'): +def _select_annotations_based_on_description(descriptions, event_id, regexp): """Get a collection of descriptions and returns index of selected.""" regexp_comp = re.compile('.*' if regexp is None else regexp) @@ -786,8 +785,7 @@ def events_from_annotations(raw, event_id=None, regexp=None, use_rounding=True, annotations = raw.annotations event_sel, event_id_ = _select_annotations_based_on_description( - annotations.description, event_id=event_id, regexp=regexp, - on_missing=on_missing) + annotations.description, event_id=event_id, regexp=regexp) if chunk_duration is None: inds = raw.time_as_index(annotations.onset, use_rounding=use_rounding, diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index a63fde44b4e..c5bd5047845 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -551,7 +551,7 @@ def test_events_from_annot_in_raw_objects(): assert_array_equal(expected_events5, events5) with pytest.raises(ValueError, match='not find any of the events'): - events_from_annotations(raw, regexp='not_there', on_missing='error') + events_from_annotations(raw, regexp='not_there') raw.set_annotations(None) events7, _ = events_from_annotations(raw)