Skip to content

Commit 7a34d29

Browse files
committed
Expose information about clock segments
1 parent d62ca7d commit 7a34d29

File tree

4 files changed

+92
-11
lines changed

4 files changed

+92
-11
lines changed

src/pyxdf/pyxdf.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ def __init__(self, xml, stream_id):
6464
# list of segments corresponding to detected time-stamp breaks
6565
# (each a tuple of start_idx, end_idx)
6666
self.segments = []
67+
# list of segments corresponding to detected clock resets (each
68+
# a tuple of start_idx, end_idx)
69+
self.clock_segments = []
6770
# pre-calc some parsing parameters for efficiency
6871
if self.fmt != "string":
6972
self.dtype = np.dtype(fmts[self.fmt])
@@ -396,9 +399,15 @@ def load_xdf(
396399
"Using the 'stream_id' value {} from the beginning of the StreamHeader "
397400
"chunk instead.".format(stream["info"]["stream_id"], k)
398401
)
402+
if synchronize_clocks:
403+
if tmp.segments != tmp.clock_segments:
404+
logger.warning(
405+
f"Stream {tmp.stream_id}: Segments and clock-segments differ"
406+
)
399407
stream["info"]["stream_id"] = k
400408
stream["info"]["effective_srate"] = tmp.effective_srate
401409
stream["info"]["segments"] = tmp.segments
410+
stream["info"]["clock_segments"] = tmp.clock_segments
402411
stream["time_series"] = tmp.time_series
403412
stream["time_stamps"] = tmp.time_stamps
404413
stream["clock_times"] = tmp.clock_times
@@ -677,6 +686,9 @@ def _clock_sync(
677686
# Apply the correction to all time-stamps
678687
if len(ranges) == 1:
679688
stream.time_stamps += coef[0][0] + (coef[0][1] * stream.time_stamps)
689+
stream.clock_segments.append(
690+
(0, len(stream.time_stamps) - 1) # inclusive
691+
)
680692
else:
681693
# Assumes time-stamps are monotonically increasing.
682694
ts_start = 0
@@ -702,6 +714,7 @@ def _clock_sync(
702714
else:
703715
# Include all time-stamps from the last break until the end.
704716
ts_stop = len(stream.time_stamps)
717+
stream.clock_segments.append((ts_start, ts_stop - 1))
705718
ts_slice = slice(ts_start, ts_stop)
706719
ts_start = ts_stop
707720
stream.time_stamps[ts_slice] += (

test/mock_data_stream.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def __init__(
6262
self.clock_times = clock_times
6363
self.clock_values = clock_values
6464
self.segments = []
65+
self.clock_segments = []
6566

6667

6768
def test_mock_stream_default_timeseries_num():

test/test_clock_sync.py

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def test_sync_empty_stream(n_clock_offsets, handle_clock_resets):
3131
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
3232
np.testing.assert_equal(streams[1].clock_times, clock_times)
3333
np.testing.assert_equal(streams[1].clock_values, clock_values)
34+
np.testing.assert_equal(streams[1].clock_segments, [])
3435

3536

3637
@pytest.mark.parametrize("n_time_stamps", list(range(0, 5)))
@@ -55,6 +56,7 @@ def test_sync_empty_offsets(n_time_stamps, handle_clock_resets):
5556
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
5657
np.testing.assert_equal(streams[1].clock_times, clock_times)
5758
np.testing.assert_equal(streams[1].clock_values, clock_values)
59+
np.testing.assert_equal(streams[1].clock_segments, [])
5860

5961

6062
@pytest.mark.parametrize("n_clock_offsets", list(range(2, 5)))
@@ -87,6 +89,7 @@ def test_sync_no_resets(n_clock_offsets, clock_value, handle_clock_resets):
8789
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
8890
np.testing.assert_equal(streams[1].clock_times, clock_times)
8991
np.testing.assert_equal(streams[1].clock_values, clock_values)
92+
np.testing.assert_equal(streams[1].clock_segments, [(0, len(time_stamps) - 1)])
9093

9194

9295
@pytest.mark.parametrize("n_clock_offsets", list(range(2, 4)))
@@ -119,6 +122,7 @@ def test_sync_no_resets_bounds(n_clock_offsets, clock_value, handle_clock_resets
119122
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
120123
np.testing.assert_equal(streams[1].clock_times, clock_times)
121124
np.testing.assert_equal(streams[1].clock_values, clock_values)
125+
np.testing.assert_equal(streams[1].clock_segments, [(0, len(time_stamps) - 1)])
122126

123127

124128
@pytest.mark.parametrize("n_clock_offsets", list(range(2, 4)))
@@ -165,6 +169,7 @@ def test_sync_no_resets_bounds_jitter(
165169
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
166170
np.testing.assert_equal(streams[1].clock_times, clock_times)
167171
np.testing.assert_equal(streams[1].clock_values, clock_values)
172+
np.testing.assert_equal(streams[1].clock_segments, [(0, len(time_stamps) - 1)])
168173

169174

170175
# Clock resets.
@@ -191,6 +196,13 @@ def test_sync_clock_jumps_forward_break_at_reset():
191196
expected,
192197
atol=1e-13,
193198
)
199+
np.testing.assert_equal(
200+
streams[1].clock_segments,
201+
[
202+
(0, 4),
203+
(5, 9),
204+
],
205+
)
194206

195207

196208
def test_sync_clock_jumps_forward_break_between_reset():
@@ -214,6 +226,13 @@ def test_sync_clock_jumps_forward_break_between_reset():
214226
expected,
215227
atol=1e-13,
216228
)
229+
np.testing.assert_equal(
230+
streams[1].clock_segments,
231+
[
232+
(0, 7),
233+
(8, 15),
234+
],
235+
)
217236

218237

219238
def test_sync_clock_jumps_backward_break_at_reset():
@@ -237,6 +256,13 @@ def test_sync_clock_jumps_backward_break_at_reset():
237256
expected,
238257
atol=1e-13,
239258
)
259+
np.testing.assert_equal(
260+
streams[1].clock_segments,
261+
[
262+
(0, 4),
263+
(5, 9),
264+
],
265+
)
240266

241267

242268
def test_sync_clock_jumps_backward_break_between_reset():
@@ -260,6 +286,13 @@ def test_sync_clock_jumps_backward_break_between_reset():
260286
expected,
261287
atol=1e-13,
262288
)
289+
np.testing.assert_equal(
290+
streams[1].clock_segments,
291+
[
292+
(0, 7),
293+
(8, 15),
294+
],
295+
)
263296

264297

265298
@pytest.mark.parametrize(
@@ -296,9 +329,16 @@ def test_sync_clock_jumps_forward_tdiffs(clock_offsets, tdiff, clock_tdiff):
296329
time_stamps = np.hstack(
297330
[np.arange(start, stop, tdiff) for start, stop in clock_reset_times]
298331
)
299-
expected = np.hstack(
300-
[np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range]
301-
)
332+
expected = [
333+
np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range
334+
]
335+
expected_clock_segments = []
336+
seg_start_idx = 0
337+
for segment in expected:
338+
seg_end_idx = seg_start_idx + len(segment) - 1
339+
expected_clock_segments.append((seg_start_idx, seg_end_idx))
340+
seg_start_idx = seg_end_idx + 1
341+
expected = np.hstack(expected)
302342
streams = {
303343
1: MockStreamData(
304344
time_stamps=time_stamps,
@@ -318,6 +358,7 @@ def test_sync_clock_jumps_forward_tdiffs(clock_offsets, tdiff, clock_tdiff):
318358
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
319359
np.testing.assert_equal(streams[1].clock_times, clock_times)
320360
np.testing.assert_equal(streams[1].clock_values, clock_values)
361+
np.testing.assert_equal(streams[1].clock_segments, expected_clock_segments)
321362

322363

323364
@pytest.mark.parametrize(
@@ -354,9 +395,16 @@ def test_sync_clock_jumps_backward_tdiffs(clock_offsets, tdiff, clock_tdiff):
354395
time_stamps = np.hstack(
355396
[np.arange(start, stop, tdiff) for start, stop in clock_reset_times]
356397
)
357-
expected = np.hstack(
358-
[np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range]
359-
)
398+
expected = [
399+
np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range
400+
]
401+
expected_clock_segments = []
402+
seg_start_idx = 0
403+
for segment in expected:
404+
seg_end_idx = seg_start_idx + len(segment) - 1
405+
expected_clock_segments.append((seg_start_idx, seg_end_idx))
406+
seg_start_idx = seg_end_idx + 1
407+
expected = np.hstack(expected)
360408
streams = {
361409
1: MockStreamData(
362410
time_stamps=time_stamps,
@@ -375,6 +423,7 @@ def test_sync_clock_jumps_backward_tdiffs(clock_offsets, tdiff, clock_tdiff):
375423
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
376424
np.testing.assert_equal(streams[1].clock_times, clock_times)
377425
np.testing.assert_equal(streams[1].clock_values, clock_values)
426+
np.testing.assert_equal(streams[1].clock_segments, expected_clock_segments)
378427

379428

380429
@pytest.mark.parametrize(
@@ -411,9 +460,16 @@ def test_sync_clock_jumps_forward_backward_tdiffs(clock_offsets, tdiff, clock_td
411460
time_stamps = np.hstack(
412461
[np.arange(start, stop, tdiff) for start, stop in clock_reset_times]
413462
)
414-
expected = np.hstack(
415-
[np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range]
416-
)
463+
expected = [
464+
np.arange(0, clock_tdiff * offsets, tdiff) for offsets in offsets_per_range
465+
]
466+
expected_clock_segments = []
467+
seg_start_idx = 0
468+
for segment in expected:
469+
seg_end_idx = seg_start_idx + len(segment) - 1
470+
expected_clock_segments.append((seg_start_idx, seg_end_idx))
471+
seg_start_idx = seg_end_idx + 1
472+
expected = np.hstack(expected)
417473
streams = {
418474
1: MockStreamData(
419475
time_stamps=time_stamps,
@@ -433,3 +489,4 @@ def test_sync_clock_jumps_forward_backward_tdiffs(clock_offsets, tdiff, clock_td
433489
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
434490
np.testing.assert_equal(streams[1].clock_times, clock_times)
435491
np.testing.assert_equal(streams[1].clock_values, clock_values)
492+
np.testing.assert_equal(streams[1].clock_segments, expected_clock_segments)

test/test_data.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def test_minimal_file():
3737
assert streams[0]["info"]["channel_format"][0] == "int16"
3838
assert streams[0]["info"]["stream_id"] == 0
3939
assert streams[0]["info"]["segments"] == [(0, 8)]
40+
assert streams[0]["info"]["clock_segments"] == [(0, 8)]
4041

4142
s = np.array(
4243
[
@@ -101,17 +102,19 @@ def test_minimal_file():
101102
path, jitter_break_threshold_seconds=0.001, jitter_break_threshold_samples=1
102103
)
103104
assert streams[0]["info"]["segments"] == [(0, 0), (1, 3), (4, 8)]
105+
assert streams[0]["info"]["clock_segments"] == [(0, 8)]
104106

105107

106108
@pytest.mark.parametrize("dejitter_timestamps", [False, True])
107-
def test_empty_streams_file(dejitter_timestamps):
109+
@pytest.mark.parametrize("synchronize_clocks", [False, True])
110+
def test_empty_streams_file(synchronize_clocks, dejitter_timestamps):
108111
path = Path("example-files/data_with_empty_streams.xdf")
109112
if not path.exists():
110113
pytest.skip(f"File not available: {path}.")
111114

112115
streams, header = load_xdf(
113116
path,
114-
synchronize_clocks=False,
117+
synchronize_clocks=synchronize_clocks,
115118
dejitter_timestamps=dejitter_timestamps,
116119
)
117120

@@ -127,6 +130,9 @@ def test_empty_streams_file(dejitter_timestamps):
127130
assert streams[1]["info"]["nominal_srate"][0] == "1.000000000000000"
128131
assert streams[1]["info"]["stream_id"] == 1
129132
assert streams[1]["info"]["segments"] == [(0, 9)]
133+
assert streams[1]["info"]["clock_segments"] == (
134+
[(0, 9)] if synchronize_clocks else []
135+
)
130136

131137
s = np.array([[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]], dtype=np.int32)
132138
t = np.array(
@@ -155,6 +161,7 @@ def test_empty_streams_file(dejitter_timestamps):
155161
assert streams[0]["info"]["nominal_srate"][0] == "1.000000000000000"
156162
assert streams[0]["info"]["stream_id"] == 2
157163
assert streams[0]["info"]["segments"] == []
164+
assert streams[0]["info"]["clock_segments"] == []
158165

159166
s = np.zeros((0, 1), dtype=np.int32)
160167
t = np.array([], dtype=np.float64)
@@ -186,6 +193,9 @@ def test_empty_streams_file(dejitter_timestamps):
186193
assert streams[3]["info"]["channel_format"][0] == "string"
187194
assert streams[3]["info"]["stream_id"] == 4
188195
assert streams[3]["info"]["segments"] == [(0, 0)]
196+
assert streams[3]["info"]["clock_segments"] == (
197+
[(0, 0)] if synchronize_clocks else []
198+
)
189199

190200
s = [['{"state": 2}']]
191201
t = np.array([401340.59709634])

0 commit comments

Comments
 (0)