fix: /api/capture no longer moves the frame /api/rgb and /api/depth share

The 100 ms cache of get_rgb/get_depth/cached_frame served the latest
aligned frame, so an /api/capture from another client between /api/rgb and
/api/depth made the depth come from a newer frame than the color (with two
clients on PC2, the multi-camera service and the pestle detector, this can
happen). Older than R01: capture() already refreshed _last_frame then.
The cache is now its own slot, renewed only by the cached readers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
OliviaCandClaude Opus 5.5 committed 2026-09-30 16:55:19 +02:00
1 parent 6a442e9f32
commit 5c97d166be
1 file changed
+16 -7
+16 -7
View File
@@ -151,6 +151,8 @@ class RealSenseD435:
self._raw: Optional[FrameAcquisition[RawFrameset]] = None self._raw: Optional[FrameAcquisition[RawFrameset]] = None
self._aligned: Optional[FrameAcquisition[CameraFrame]] = None self._aligned: Optional[FrameAcquisition[CameraFrame]] = None
self._last_aligned_raw_seq = 0 self._last_aligned_raw_seq = 0
# The frame get_rgb/get_depth/cached_frame share for frame_cache_ms (see _cached_or_capture()).
self._cached: Optional[CameraFrame] = None
self._color_available: bool = True self._color_available: bool = True
self._color_format = rs.format.bgr8 self._color_format = rs.format.bgr8
self._usb_type: str = "unknown" self._usb_type: str = "unknown"
@@ -364,6 +366,7 @@ class RealSenseD435:
for thread in threads: for thread in threads:
thread.join(STOP_JOIN_S) thread.join(STOP_JOIN_S)
self.device_index = None self.device_index = None
self._cached = None
logger.info("RealSense camera closed") logger.info("RealSense camera closed")
def _start_acquisition(self) -> None: def _start_acquisition(self) -> None:
@@ -554,13 +557,19 @@ class RealSenseD435:
return CameraFrame(rgb=rgb, depth=depth_mm, timestamp=time.time()) return CameraFrame(rgb=rgb, depth=depth_mm, timestamp=time.time())
def _cached_or_capture(self) -> Optional[CameraFrame]: def _cached_or_capture(self) -> Optional[CameraFrame]:
"""The latest ALIGNED frame if younger than frame_cache_ms, else the next one.""" """The frame the cached readers share if younger than frame_cache_ms, else the next aligned one.
aligned = self._aligned
if aligned is not None: Only these readers renew it: a capture() in between (e.g. /api/capture
_, last = aligned.latest() from another client) does not move it, so /api/rgb then /api/depth
if last is not None and (time.time() - last.timestamp) * 1000.0 < self._frame_cache_ms: within frame_cache_ms return color and depth of ONE frame.
return last """
return self.capture() cached = self._cached
if cached is not None and (time.time() - cached.timestamp) * 1000.0 < self._frame_cache_ms:
return cached
frame = self.capture()
if frame is not None:
self._cached = frame
return frame
def cached_frame(self) -> Optional[CameraFrame]: def cached_frame(self) -> Optional[CameraFrame]:
"""Like get_rgb()/get_depth(), but the whole frame (so its encodings can be shared).""" """Like get_rgb()/get_depth(), but the whole frame (so its encodings can be shared)."""