Skip to content

Commit 39447f6

Browse files
committed
_clock_sync: Handle LinAlgError and empty clock offset regions
* time-stamps within an unusable clock offset region (LinAlgError) are not synchronized but the clock_segment is retained * empty clock offset regions are discarded because they do not map to any time-stamp indices
1 parent 4cb6102 commit 39447f6

File tree

2 files changed

+107
-10
lines changed

2 files changed

+107
-10
lines changed

src/pyxdf/pyxdf.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -676,11 +676,19 @@ def _clock_sync(
676676
]
677677
)
678678
y = np.array(clock_values[start:stop]) / winsor_threshold
679-
# noinspection PyTypeChecker
680-
_coefs = _robust_fit(X, y)
681-
_coefs[0] *= winsor_threshold
679+
try:
680+
# noinspection PyTypeChecker
681+
_coefs = _robust_fit(X, y)
682+
_coefs[0] *= winsor_threshold
683+
except np.linalg.LinAlgError:
684+
logger.warning(
685+
f"Stream {stream.stream_id}: "
686+
f"Clock offsets {range_i} cannot be used for synchronization"
687+
)
688+
_coefs = [0, 0]
682689
coef.append(_coefs)
683690
else:
691+
# Intercept for single sample segments
684692
coef.append((clock_values[range_i[0]], 0))
685693

686694
# Apply the correction to all time-stamps
@@ -714,12 +722,20 @@ def _clock_sync(
714722
else:
715723
# Include all time-stamps from the last break until the end
716724
ts_stop = len(stream.time_stamps)
717-
stream.clock_segments.append((ts_start, ts_stop - 1))
718-
ts_slice = slice(ts_start, ts_stop)
719-
ts_start = ts_stop
720-
stream.time_stamps[ts_slice] += (
721-
coef_i[0] + coef_i[1] * stream.time_stamps[ts_slice]
722-
)
725+
if ts_start == ts_stop:
726+
logger.warning(
727+
(
728+
f"Stream {stream.stream_id}: "
729+
f"No samples in clock offsets {range_i}, skipping..."
730+
)
731+
)
732+
else:
733+
stream.clock_segments.append((ts_start, ts_stop - 1))
734+
ts_slice = slice(ts_start, ts_stop)
735+
ts_start = ts_stop
736+
stream.time_stamps[ts_slice] += (
737+
coef_i[0] + coef_i[1] * stream.time_stamps[ts_slice]
738+
)
723739
return streams
724740

725741

test/test_clock_sync.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import logging
2+
13
import numpy as np
24
import pytest
35
from pyxdf.pyxdf import _clock_sync
46

57
from mock_data_stream import MockStreamData
68

7-
89
# No clock resets
910

1011

@@ -172,6 +173,86 @@ def test_sync_no_resets_bounds_jitter(
172173
np.testing.assert_equal(streams[1].clock_segments, [(0, len(time_stamps) - 1)])
173174

174175

176+
# Invalid clock offsets: LinAlgError
177+
178+
179+
def test_clock_sync_error_keep_segment(caplog):
180+
caplog.set_level(logging.WARNING)
181+
expected = [0, 1, 2, 3, 4] + [15, 16, 17, 18, 19]
182+
time_stamps = np.arange(0, len(expected))
183+
# Clock offsets (0, 1) are unusable: LinAlgError
184+
clock_times = [0, 0] + [10, 15]
185+
clock_values = [0, 0] + [10, 10]
186+
streams = {
187+
1: MockStreamData(
188+
time_stamps=time_stamps,
189+
clock_times=clock_times,
190+
clock_values=clock_values,
191+
)
192+
}
193+
_clock_sync(
194+
streams,
195+
reset_threshold_stds=0,
196+
reset_threshold_seconds=4,
197+
reset_threshold_offset_stds=0,
198+
reset_threshold_offset_seconds=9,
199+
)
200+
assert "Clock offsets (0, 1) cannot be used for synchronization" in caplog.text
201+
np.testing.assert_allclose(
202+
streams[1].time_stamps,
203+
expected,
204+
)
205+
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
206+
np.testing.assert_equal(streams[1].clock_times, clock_times)
207+
np.testing.assert_equal(streams[1].clock_values, clock_values)
208+
np.testing.assert_equal(
209+
streams[1].clock_segments,
210+
[
211+
(0, 4),
212+
(5, 9),
213+
],
214+
)
215+
216+
217+
def test_clock_sync_error_skip_segment(caplog):
218+
caplog.set_level(logging.WARNING)
219+
expected = [15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
220+
# All time-stamps should be synchronized by clock offsets (2, 3)
221+
time_stamps = np.arange(5, 5 + len(expected))
222+
# Clock offsets (0, 1) are unusable: LinAlgError
223+
clock_times = [0, 0] + [10, 15]
224+
clock_values = [0, 0] + [10, 10]
225+
streams = {
226+
1: MockStreamData(
227+
time_stamps=time_stamps,
228+
clock_times=clock_times,
229+
clock_values=clock_values,
230+
)
231+
}
232+
_clock_sync(
233+
streams,
234+
reset_threshold_stds=0,
235+
reset_threshold_seconds=4,
236+
reset_threshold_offset_stds=0,
237+
reset_threshold_offset_seconds=9,
238+
)
239+
assert "Clock offsets (0, 1) cannot be used for synchronization" in caplog.text
240+
assert "No samples in clock offsets (0, 1), skipping..." in caplog.text
241+
np.testing.assert_allclose(
242+
streams[1].time_stamps,
243+
expected,
244+
)
245+
np.testing.assert_equal(streams[1].time_series[:, 0], time_stamps)
246+
np.testing.assert_equal(streams[1].clock_times, clock_times)
247+
np.testing.assert_equal(streams[1].clock_values, clock_values)
248+
np.testing.assert_equal(
249+
streams[1].clock_segments,
250+
[
251+
(0, 9),
252+
],
253+
)
254+
255+
175256
# Clock resets
176257

177258

0 commit comments

Comments
 (0)