1
- # Author: Mark Wronkiewicz <wronk@uw.edu>
1
+ # Authors: Mark Wronkiewicz <wronk@uw.edu>
2
+ # Qian Chu <qianchu99@gmail.com>
2
3
#
3
4
# License: BSD-3-Clause
4
5
7
8
from scipy .interpolate import interp1d
8
9
import pytest
9
10
10
- from mne import create_info , find_events , Epochs
11
+ from mne import create_info , find_events , Epochs , Annotations
11
12
from mne .io import RawArray
12
13
from mne .preprocessing import realign_raw
13
14
14
15
15
- @pytest .mark .parametrize ("ratio_other" , (1.0 , 0.999 , 1.001 )) # drifts
16
+ @pytest .mark .parametrize ("ratio_other" , (0.9 , 0.999 , 1 , 1 .001, 1.1 )) # drifts
16
17
@pytest .mark .parametrize ("start_raw, start_other" , [(0 , 0 ), (0 , 3 ), (3 , 0 )])
17
18
@pytest .mark .parametrize ("stop_raw, stop_other" , [(0 , 0 ), (0 , 3 ), (3 , 0 )])
18
19
def test_realign (ratio_other , start_raw , start_other , stop_raw , stop_other ):
@@ -22,6 +23,8 @@ def test_realign(ratio_other, start_raw, start_other, stop_raw, stop_other):
22
23
duration = 50
23
24
stop_raw = duration - stop_raw
24
25
stop_other = duration - stop_other
26
+ signal_len = 0.2
27
+ box_len = 0.5
25
28
signal = np .zeros (int (round ((duration + 1 ) * sfreq )))
26
29
orig_events = np .round (
27
30
np .arange (max (start_raw , start_other ) + 2 , min (stop_raw , stop_other ) - 2 )
@@ -30,8 +33,10 @@ def test_realign(ratio_other, start_raw, start_other, stop_raw, stop_other):
30
33
signal [orig_events ] = 1.0
31
34
n_events = len (orig_events )
32
35
times = np .arange (len (signal )) / sfreq
33
- stim = np .convolve (signal , np .ones (int (round (0.02 * sfreq ))))[: len (times )]
34
- signal = np .convolve (signal , np .hanning (int (round (0.2 * sfreq ))))[: len (times )]
36
+ stim = np .convolve (signal , np .ones (int (round (box_len * sfreq ))))[: len (times )]
37
+ signal = np .convolve (signal , np .hanning (int (round (signal_len * sfreq ))))[
38
+ : len (times )
39
+ ]
35
40
36
41
# construct our sampled versions of these signals (linear interp is fine)
37
42
sfreq_raw = sfreq
@@ -45,45 +50,84 @@ def test_realign(ratio_other, start_raw, start_other, stop_raw, stop_other):
45
50
data_raw = np .array (
46
51
[
47
52
interp1d (times , d , kind )(raw_times )
48
- for d , kind in ((signal , "linear" ), (stim , "nearest" ))
53
+ for d , kind in (
54
+ (stim , "nearest" ),
55
+ (signal , "linear" ),
56
+ )
49
57
]
50
58
)
51
59
data_other = np .array (
52
60
[
53
61
interp1d (times , d , kind )(other_times )
54
- for d , kind in ((signal , "linear" ), (stim , "nearest" ))
62
+ for d , kind in (
63
+ (stim , "nearest" ),
64
+ (signal , "linear" ),
65
+ )
55
66
]
56
67
)
57
- info_raw = create_info (["raw_data " , "raw_stim " ], sfreq , ["eeg " , "stim " ])
58
- info_other = create_info (["other_data " , "other_stim " ], sfreq , ["eeg " , "stim " ])
59
- raw = RawArray (data_raw , info_raw , first_samp = 111 )
68
+ info_raw = create_info (["raw_stim " , "raw_signal " ], sfreq , ["stim " , "eeg " ])
69
+ info_other = create_info (["other_stim " , "other_signal " ], sfreq , ["stim " , "eeg " ])
70
+ raw = RawArray (data_raw , info_raw , first_samp = 111 ) # first_samp shouldn't matter
60
71
other = RawArray (data_other , info_other , first_samp = 222 )
72
+ raw .set_meas_date ((0 , 0 )) # meas_date shouldn't matter
73
+ other .set_meas_date ((100 , 0 ))
61
74
62
- # naive processing
63
- evoked_raw , events_raw , _ , events_other = _assert_similarity (raw , other , n_events )
64
- if start_raw == start_other : # can just naively crop
65
- a , b = data_raw [0 ], data_other [0 ]
66
- n = min (len (a ), len (b ))
67
- corr = np .corrcoef (a [:n ], b [:n ])[0 , 1 ]
68
- min_ , max_ = (0.99999 , 1.0 ) if sfreq_raw == sfreq_other else (0.8 , 0.9 )
69
- assert min_ <= corr <= max_
75
+ # find events and do basic checks
76
+ evoked_raw , events_raw , _ , events_other = _assert_similarity (
77
+ raw , other , n_events , ratio_other
78
+ )
79
+
80
+ # construct annotations
81
+ onsets_raw = (events_raw [:, 0 ] - raw .first_samp ) / raw .info ["sfreq" ]
82
+ dur_raw = [box_len ] * len (onsets_raw )
83
+ desc_raw = ["raw_box" ] * len (onsets_raw )
84
+ annot_raw = Annotations (onsets_raw , dur_raw , desc_raw )
85
+ raw .set_annotations (annot_raw )
86
+
87
+ onsets_other = (events_other [:, 0 ] - other .first_samp ) / other .info ["sfreq" ]
88
+ dur_other = [box_len * ratio_other ] * len (onsets_other )
89
+ desc_other = ["other_box" ] * len (onsets_other )
90
+ annot_other = Annotations (onsets_other , dur_other , desc_other )
91
+ other .set_annotations (annot_other )
92
+
93
+ # onsets/offsets correspond to 0/1 transition in boxcar signals
94
+ _assert_boxcar_annot_similarity (raw , other )
70
95
71
96
# realign
72
- t_raw = (events_raw [:, 0 ] - raw .first_samp ) / other .info ["sfreq" ]
97
+ t_raw = (events_raw [:, 0 ] - raw .first_samp ) / raw .info ["sfreq" ]
73
98
t_other = (events_other [:, 0 ] - other .first_samp ) / other .info ["sfreq" ]
74
99
assert duration - 10 <= len (events_raw ) < duration
75
100
raw_orig , other_orig = raw .copy (), other .copy ()
76
101
realign_raw (raw , other , t_raw , t_other )
77
102
78
- # old events should still work for raw and produce the same result
79
- evoked_raw_2 , _ , _ , _ = _assert_similarity (
80
- raw , other , n_events , events_raw = events_raw
103
+ # old events should still work for raw and produce the same evoked data
104
+ evoked_raw_2 , events_raw , _ , events_other = _assert_similarity (
105
+ raw , other , n_events , ratio_other , events_raw = events_raw
81
106
)
82
107
assert_allclose (evoked_raw .data , evoked_raw_2 .data )
83
108
assert_allclose (raw .times , other .times )
109
+
84
110
# raw data now aligned
85
- corr = np .corrcoef (raw .get_data ([0 ])[0 ], other .get_data ([0 ])[0 ])[0 , 1 ]
86
- assert 0.99 < corr <= 1.0
111
+ corr = np .corrcoef (raw .get_data ("data" ), other .get_data ("data" ))
112
+ assert 0.99 < corr [0 , 1 ] <= 1.0
113
+
114
+ # onsets derived from stim and annotations are the same
115
+ atol = 2 / sfreq
116
+ assert_allclose (
117
+ raw .annotations .onset , events_raw [:, 0 ] / raw .info ["sfreq" ], atol = atol
118
+ )
119
+ assert_allclose (
120
+ other .annotations .onset , events_other [:, 0 ] / other .info ["sfreq" ], atol = atol
121
+ )
122
+
123
+ # onsets/offsets still correspond to 0/1 transition in boxcar signals
124
+ _assert_boxcar_annot_similarity (raw , other )
125
+
126
+ # onsets and durations now aligned
127
+ onsets_raw , dur_raw , onsets_other , dur_other = _annot_to_onset_dur (raw , other )
128
+ assert len (onsets_raw ) == len (onsets_other ) == len (events_raw )
129
+ assert_allclose (onsets_raw , onsets_other , atol = atol )
130
+ assert_allclose (dur_raw , dur_other , atol = atol )
87
131
88
132
# Degenerate conditions -- only test in one run
89
133
test_degenerate = (
@@ -103,17 +147,44 @@ def test_realign(ratio_other, start_raw, start_other, stop_raw, stop_other):
103
147
realign_raw (raw_orig , other_orig , raw_times + rand_times * 1000 , other_times )
104
148
105
149
106
- def _assert_similarity (raw , other , n_events , events_raw = None ):
150
+ def _assert_similarity (raw , other , n_events , ratio_other , events_raw = None ):
107
151
if events_raw is None :
108
- events_raw = find_events (raw )
109
- events_other = find_events (other )
110
- assert len (events_raw ) == n_events
111
- assert len (events_other ) == n_events
152
+ events_raw = find_events (raw , output = "onset" )
153
+ events_other = find_events (other , output = "onset" )
154
+ assert len (events_raw ) == len (events_other ) == n_events
112
155
kwargs = dict (baseline = None , tmin = 0 , tmax = 0.2 )
113
156
evoked_raw = Epochs (raw , events_raw , ** kwargs ).average ()
114
157
evoked_other = Epochs (other , events_other , ** kwargs ).average ()
115
158
assert evoked_raw .nave == evoked_other .nave == len (events_raw )
116
159
assert len (evoked_raw .data ) == len (evoked_other .data ) == 1 # just EEG
117
- corr = np .corrcoef (evoked_raw .data [0 ], evoked_other .data [0 ])[0 , 1 ]
118
- assert 0.9 <= corr <= 1.0
160
+ if 0.99 <= ratio_other <= 1.01 : # when drift is not too large
161
+ corr = np .corrcoef (evoked_raw .data [0 ], evoked_other .data [0 ])[0 , 1 ]
162
+ assert 0.9 <= corr <= 1.0
119
163
return evoked_raw , events_raw , evoked_other , events_other
164
+
165
+
166
+ def _assert_boxcar_annot_similarity (raw , other ):
167
+ onsets_raw , dur_raw , onsets_other , dur_other = _annot_to_onset_dur (raw , other )
168
+
169
+ n_events = len (onsets_raw )
170
+ onsets_samp_raw = raw .time_as_index (onsets_raw )
171
+ offsets_samp_raw = raw .time_as_index (onsets_raw + dur_raw )
172
+ assert_allclose (raw .get_data ("stim" )[0 , onsets_samp_raw - 2 ], [0 ] * n_events )
173
+ assert_allclose (raw .get_data ("stim" )[0 , onsets_samp_raw + 2 ], [1 ] * n_events )
174
+ assert_allclose (raw .get_data ("stim" )[0 , offsets_samp_raw - 2 ], [1 ] * n_events )
175
+ assert_allclose (raw .get_data ("stim" )[0 , offsets_samp_raw + 2 ], [0 ] * n_events )
176
+ onsets_samp_other = other .time_as_index (onsets_other )
177
+ offsets_samp_other = other .time_as_index (onsets_other + dur_other )
178
+ assert_allclose (other .get_data ("stim" )[0 , onsets_samp_other - 2 ], [0 ] * n_events )
179
+ assert_allclose (other .get_data ("stim" )[0 , onsets_samp_other + 2 ], [1 ] * n_events )
180
+ assert_allclose (other .get_data ("stim" )[0 , offsets_samp_other - 2 ], [1 ] * n_events )
181
+ assert_allclose (other .get_data ("stim" )[0 , offsets_samp_other + 2 ], [0 ] * n_events )
182
+
183
+
184
+ def _annot_to_onset_dur (raw , other ):
185
+ onsets_raw = raw .annotations .onset - raw .first_time
186
+ dur_raw = raw .annotations .duration
187
+
188
+ onsets_other = other .annotations .onset - other .first_time
189
+ dur_other = other .annotations .duration
190
+ return onsets_raw , dur_raw , onsets_other , dur_other
0 commit comments