Skip to content

Commit dc0d578

Browse files
feat(low fps cameras): adding support for low fps cameras by returning the latest recorded frame in case of a reading new frame timeout
1 parent f6b16f6 commit dc0d578

File tree

1 file changed

+19
-7
lines changed

1 file changed

+19
-7
lines changed

src/lerobot/cameras/opencv/camera_opencv.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def __init__(self, config: OpenCVCameraConfig):
123123
self.stop_event: Event | None = None
124124
self.frame_lock: Lock = Lock()
125125
self.latest_frame: NDArray[Any] | None = None
126+
self.cached_frame: NDArray[Any] | None = None
126127
self.new_frame_event: Event = Event()
127128

128129
self.rotation: int | None = get_cv2_rotation(config.rotation)
@@ -475,7 +476,7 @@ def _stop_read_thread(self) -> None:
475476
self.thread = None
476477
self.stop_event = None
477478

478-
def async_read(self, timeout_ms: float = 200) -> NDArray[Any]:
479+
def async_read(self, timeout_ms: float = 30) -> NDArray[Any]:
479480
"""
480481
Reads the latest available frame asynchronously.
481482
@@ -485,7 +486,7 @@ def async_read(self, timeout_ms: float = 200) -> NDArray[Any]:
485486
486487
Args:
487488
timeout_ms (float): Maximum time in milliseconds to wait for a frame
488-
to become available. Defaults to 200ms (0.2 seconds).
489+
to become available. Defaults to 33 ms (30 fps).
489490
490491
Returns:
491492
np.ndarray: The latest captured frame as a NumPy array in the format
@@ -502,21 +503,32 @@ def async_read(self, timeout_ms: float = 200) -> NDArray[Any]:
502503
if self.thread is None or not self.thread.is_alive():
503504
self._start_read_thread()
504505

506+
# Wait for the first recorded frame to be available
507+
if self.cached_frame is None:
508+
self.new_frame_event.wait()
509+
505510
if not self.new_frame_event.wait(timeout=timeout_ms / 1000.0):
506511
thread_alive = self.thread is not None and self.thread.is_alive()
507-
raise TimeoutError(
508-
f"Timed out waiting for frame from camera {self} after {timeout_ms} ms. "
509-
f"Read thread alive: {thread_alive}."
510-
)
512+
if thread_alive:
513+
logger.warning(
514+
f"{self} async_read timed out after {timeout_ms} ms but camera is still running."
515+
)
516+
else:
517+
raise TimeoutError(
518+
f"{self} async_read timed out after {timeout_ms} ms: camera is not responding !"
519+
)
520+
return self.cached_frame
511521

512522
with self.frame_lock:
513523
frame = self.latest_frame
514524
self.new_frame_event.clear()
515525

516526
if frame is None:
517527
raise RuntimeError(f"Internal error: Event set but no frame available for {self}.")
528+
else:
529+
self.cached_frame = frame
518530

519-
return frame
531+
return self.cached_frame
520532

521533
def disconnect(self) -> None:
522534
"""

0 commit comments

Comments
 (0)