Skip to content

Commit f1ed273

Browse files
committed
feat: auto-detect sampling rate from ZMQ stream headers (fs=0 default)
1 parent 4e4f90c commit f1ed273

1 file changed

Lines changed: 29 additions & 11 deletions

File tree

examples/joint_angle_regression/open_ephys_lsl_streamer.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def __init__(
111111
self,
112112
host="127.0.0.1",
113113
port=5556,
114-
expected_fs=5000.0,
114+
expected_fs=0.0,
115115
emg_channels=0,
116116
emg_stream_name="OpenEphys_EMG",
117117
imu_stream_name="OpenEphys_IMU",
@@ -148,6 +148,7 @@ def __init__(
148148
self.last_mag_std = 0.0
149149
self.last_chunk = 0
150150
self.last_error = ""
151+
self.detected_fs = 0.0 # filled after connect
151152
self._prev_written = 0 # track ref-channel total_samples_written
152153

153154
def _wait_for_channels(self, timeout=3.0):
@@ -214,7 +215,15 @@ def start(self):
214215
ch_idx = detected[: self.emg_channels]
215216
self.client.set_channel_index(ch_idx)
216217

217-
fs = float(self.client.fs) if float(self.client.fs) > 0 else self.expected_fs
218+
# Infer sampling rate from the stream (client.fs is updated from ZMQ headers)
219+
client_fs = float(self.client.fs)
220+
if client_fs > 0 and (self.expected_fs <= 0 or self.expected_fs == 5000.0):
221+
self.detected_fs = client_fs
222+
elif self.expected_fs > 0:
223+
self.detected_fs = self.expected_fs
224+
else:
225+
self.detected_fs = client_fs if client_fs > 0 else 2000.0
226+
fs = self.detected_fs
218227
self.emg_outlet, self.imu_outlet = build_outlets(
219228
self.emg_stream_name, self.imu_stream_name, fs, self.emg_channels
220229
)
@@ -461,7 +470,10 @@ def _init_ui(self):
461470
cg.addWidget(self.ch_edit, 1, 1)
462471

463472
cg.addWidget(QLabel("Fs (Hz)"), 1, 2)
464-
self.fs_edit = QLineEdit(str(int(self.args.fs)))
473+
self.fs_edit = QSpinBox()
474+
self.fs_edit.setRange(0, 100000)
475+
self.fs_edit.setSpecialValueText("Auto")
476+
self.fs_edit.setValue(int(self.args.fs))
465477
self.fs_edit.setFixedWidth(80)
466478
cg.addWidget(self.fs_edit, 1, 3)
467479

@@ -531,10 +543,7 @@ def _build_streamer(self):
531543
host = self.host_edit.text().strip() or self.args.host
532544
port = self.port_edit.value()
533545
channels = self.ch_edit.value()
534-
try:
535-
fs = float(self.fs_edit.text().strip())
536-
except ValueError:
537-
fs = self.args.fs
546+
fs = float(self.fs_edit.value())
538547
emg_name = self.emg_name.text().strip() or self.args.emg_stream_name
539548
imu_name = self.imu_name.text().strip() or self.args.imu_stream_name
540549
return OpenEphysLSLStreamer(
@@ -577,8 +586,10 @@ def _on_start(self):
577586

578587
self.status.setText("Streaming")
579588
self.status.setStyleSheet("color: #44ff44; font-weight: bold; font-size: 14px;")
580-
# Update channel count from auto-detection
589+
# Update channel count and fs from auto-detection
581590
self.ch_edit.setValue(self.streamer.emg_channels)
591+
if self.streamer.detected_fs > 0:
592+
self.fs_edit.setValue(int(self.streamer.detected_fs))
582593
self.reminder.hide()
583594
self.btn_start.setEnabled(False)
584595
self.btn_stop.setEnabled(True)
@@ -620,8 +631,9 @@ def _tick(self):
620631
try:
621632
info = self.streamer.poll_once()
622633
ch = info["channels"]
634+
fs_str = f"{self.streamer.detected_fs:.0f}" if self.streamer.detected_fs > 0 else "?"
623635
self.samples.setText(
624-
f"Samples: {info['total_emg']:,} | chunk ({ch}, {info['chunk']})"
636+
f"Samples: {info['total_emg']:,} | chunk ({ch}ch, {info['chunk']}) @ {fs_str} Hz"
625637
)
626638
if info["chunk"] > 0:
627639
self.emg_stats.setText(
@@ -659,7 +671,10 @@ def run_cli(args):
659671
imu_transport=args.imu_transport,
660672
)
661673
streamer.start()
662-
print(f"Streaming LSL: EMG='{args.emg_stream_name}', IMU='{args.imu_stream_name}'")
674+
print(
675+
f"Streaming LSL: EMG='{args.emg_stream_name}', IMU='{args.imu_stream_name}'"
676+
f" | {streamer.emg_channels}ch @ {streamer.detected_fs:.0f} Hz"
677+
)
663678
try:
664679
while True:
665680
info = streamer.poll_once()
@@ -682,7 +697,10 @@ def build_arg_parser():
682697
p.add_argument("--host", default="127.0.0.1", help="Open Ephys ZMQ host")
683698
p.add_argument("--port", type=int, default=5556, help="Open Ephys ZMQ data port")
684699
p.add_argument(
685-
"--fs", type=float, default=5000.0, help="Expected EMG sampling rate"
700+
"--fs",
701+
type=float,
702+
default=0.0,
703+
help="Sampling rate in Hz (0 = auto-detect from stream)",
686704
)
687705
p.add_argument(
688706
"--channels", type=int, default=0, help="EMG channel count (0 = auto-detect)"

0 commit comments

Comments
 (0)