From 649b4d191fb3512f49a1a268103184897eebe33d Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Sun, 7 Jun 2020 21:59:36 +0200 Subject: [PATCH 1/5] FIX : EDF+ Annotation Timestamps missing submillisecond accuracy --- mne/io/edf/edf.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 23698f8b07a..eeeaa560d46 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -1375,12 +1375,23 @@ def _read_annotations_edf(annotations): triggers = re.findall(pat, tals.decode('latin-1')) events = [] - for ev in triggers: - onset = float(ev[0]) + offset = 0. + for k, ev in enumerate(triggers): + onset = float(ev[0]) + offset duration = float(ev[2]) if ev[2] else 0 for description in ev[3].split('\x14')[1:]: if description: events.append([onset, duration, description]) + elif k == 0: + # "The startdate/time of a file is specified in the EDF+ header + # fields 'startdate of recording' and 'starttime of recording'. + # These fields must indicate the absolute second in which the + # start of the first data record falls. So, the first TAL in + # the first data record always starts with +0.X2020, indicating + # that the first data record starts a fraction, X, of a second + # after the startdate/time that is specified in the EDF+ + # header. If X=0, then the .X may be omitted." + offset = -onset return zip(*events) if events else (list(), list(), list()) From e9ab21a45443d0f19ff43ea14801d4bc08df0e4e Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Mon, 8 Jun 2020 08:52:35 +0200 Subject: [PATCH 2/5] review from @cbrnr --- mne/io/edf/edf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index eeeaa560d46..c302904bde6 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -1383,14 +1383,14 @@ def _read_annotations_edf(annotations): if description: events.append([onset, duration, description]) elif k == 0: - # "The startdate/time of a file is specified in the EDF+ header + # The startdate/time of a file is specified in the EDF+ header # fields 'startdate of recording' and 'starttime of recording'. # These fields must indicate the absolute second in which the # start of the first data record falls. So, the first TAL in - # the first data record always starts with +0.X2020, indicating + # the first data record always starts with +0.X, indicating # that the first data record starts a fraction, X, of a second # after the startdate/time that is specified in the EDF+ - # header. If X=0, then the .X may be omitted." + # header. If X=0, then the .X may be omitted. offset = -onset return zip(*events) if events else (list(), list(), list()) From 50fdcd7b6898f37235427779abf04de0ca20579e Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Mon, 8 Jun 2020 14:30:40 +0200 Subject: [PATCH 3/5] udpate test + testing data --- mne/datasets/utils.py | 4 ++-- mne/io/edf/tests/test_edf.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mne/datasets/utils.py b/mne/datasets/utils.py index 9c3c1514924..cfe5f58dd28 100644 --- a/mne/datasets/utils.py +++ b/mne/datasets/utils.py @@ -239,7 +239,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, path = _get_path(path, key, name) # To update the testing or misc dataset, push commits, then make a new # release on GitHub. Then update the "releases" variable: - releases = dict(testing='0.91', misc='0.6') + releases = dict(testing='0.92', misc='0.6') # And also update the "md5_hashes['testing']" variable below. # To update any other dataset, update the data archive itself (upload @@ -326,7 +326,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, sample='12b75d1cb7df9dfb4ad73ed82f61094f', somato='ea825966c0a1e9b2f84e3826c5500161', spm='9f43f67150e3b694b523a21eb929ea75', - testing='f87f04fcdf56a7a55fb73b1b260b5b5b', + testing='42daafd1b882da2ef041de860ca6e771', multimodal='26ec847ae9ab80f58f204d09e2c08367', fnirs_motor='c4935d19ddab35422a69f3326a01fef8', opm='370ad1dcfd5c47e029e692c85358a374', diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py index 57c9135caa0..3b02d83fbb2 100644 --- a/mne/io/edf/tests/test_edf.py +++ b/mne/io/edf/tests/test_edf.py @@ -56,6 +56,7 @@ 'multiple_annotation_chans.bdf') test_generator_bdf = op.join(data_path, 'BDF', 'test_generator_2.bdf') test_generator_edf = op.join(data_path, 'EDF', 'test_generator_2.edf') +edf_annot_sub_ms_path = op.join(data_path, 'EDF', 'subsecond_starttime.edf') eog = ['REOG', 'LEOG', 'IEOG'] misc = ['EXG1', 'EXG5', 'EXG8', 'M1', 'M2'] @@ -385,3 +386,10 @@ def test_edf_lowpass_zero(): with pytest.warns(RuntimeWarning, match='too long.*truncated'): raw = read_raw_edf(edf_stim_resamp_path) assert_allclose(raw.info["lowpass"], raw.info["sfreq"] / 2) + + +@testing.requires_testing_data +def test_edf_annot_ms_onset(): + """Test reading of sub-milisecond annotation onsets.""" + raw = read_raw_edf(edf_annot_sub_ms_path) + assert_allclose(raw.annotations.onset, [1.951172, 3.492188]) From 1ff8aff6d720bcf7a75be22c254b103a23c86f7c Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Mon, 8 Jun 2020 15:57:16 +0200 Subject: [PATCH 4/5] rephrase --- mne/io/edf/tests/test_edf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py index 3b02d83fbb2..a266e1cd558 100644 --- a/mne/io/edf/tests/test_edf.py +++ b/mne/io/edf/tests/test_edf.py @@ -390,6 +390,6 @@ def test_edf_lowpass_zero(): @testing.requires_testing_data def test_edf_annot_ms_onset(): - """Test reading of sub-milisecond annotation onsets.""" + """Test reading of sub-second annotation onsets.""" raw = read_raw_edf(edf_annot_sub_ms_path) assert_allclose(raw.annotations.onset, [1.951172, 3.492188]) From 200eae95c4a84bc5f8c45ebf9b4e75319b33a545 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 8 Jun 2020 17:01:03 +0200 Subject: [PATCH 5/5] Rename data --- mne/io/edf/tests/test_edf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py index a266e1cd558..8c2ece8cd35 100644 --- a/mne/io/edf/tests/test_edf.py +++ b/mne/io/edf/tests/test_edf.py @@ -56,7 +56,7 @@ 'multiple_annotation_chans.bdf') test_generator_bdf = op.join(data_path, 'BDF', 'test_generator_2.bdf') test_generator_edf = op.join(data_path, 'EDF', 'test_generator_2.edf') -edf_annot_sub_ms_path = op.join(data_path, 'EDF', 'subsecond_starttime.edf') +edf_annot_sub_s_path = op.join(data_path, 'EDF', 'subsecond_starttime.edf') eog = ['REOG', 'LEOG', 'IEOG'] misc = ['EXG1', 'EXG5', 'EXG8', 'M1', 'M2'] @@ -389,7 +389,7 @@ def test_edf_lowpass_zero(): @testing.requires_testing_data -def test_edf_annot_ms_onset(): +def test_edf_annot_sub_s_onset(): """Test reading of sub-second annotation onsets.""" - raw = read_raw_edf(edf_annot_sub_ms_path) + raw = read_raw_edf(edf_annot_sub_s_path) assert_allclose(raw.annotations.onset, [1.951172, 3.492188])