feat: per-phase timings of the frame path in the log

One log line per phase every PHASE_LOG_EVERY samples (default 100, 0 = off):
mean and max of wait_frames, align, convert, each JPEG/PNG encoding,
colormap, depth_stats, base64, json and ws_send. Meant for the measurement
on PC2 via `docker logs` (optimize-streams R01): where a capture spends its
~130 ms of CPU is not known yet.

capture() moves its numpy conversion into _frame_from() unchanged, only so
the block can be timed; _depth_colormap() likewise wraps _jet_colormap().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
OliviaCandClaude Opus 5.5 committed 2026-09-29 12:57:25 +02:00
1 parent 441e0bd917
commit 9aa63e4e02
6 files changed
+166 -22

No files matched your search

+14 -5
View File
@@ -30,6 +30,8 @@ import cv2
import numpy as np
import pyrealsense2 as rs
from src.backend.services.phase_timing import timer
logger = logging.getLogger(__name__)
@@ -376,19 +378,28 @@ class RealSenseD435:
if not self._open:
return None
try:
frames = self._pipeline.wait_for_frames(timeout_ms)
with timer.phase("wait_frames"):
frames = self._pipeline.wait_for_frames(timeout_ms)
except Exception as e:
logger.warning(f"wait_for_frames failed: {e}")
return None
if self._align is not None:
frames = self._align.process(frames)
with timer.phase("align"):
frames = self._align.process(frames)
color_frame = frames.get_color_frame()
depth_frame = frames.get_depth_frame()
if not color_frame or not depth_frame:
return None
with timer.phase("convert"):
frame = self._frame_from(color_frame, depth_frame)
self._last_frame = frame
return frame
def _frame_from(self, color_frame, depth_frame) -> CameraFrame:
"""numpy BGR + depth in mm from the SDK frames (YUYV decode, USB 2 resize, depth scale)."""
if self._color_format == rs.format.yuyv:
# SDK exposes YUYV as a 1-channel view of the packed buffer;
# rebuild it as (H, W, 2) uint8 so cv2 can decode it to BGR.
@@ -417,9 +428,7 @@ class RealSenseD435:
else:
depth_mm = (depth_raw.astype(np.float32) * self._depth_scale * 1000.0).astype(np.uint16)
frame = CameraFrame(rgb=rgb, depth=depth_mm, timestamp=time.time())
self._last_frame = frame
return frame
return CameraFrame(rgb=rgb, depth=depth_mm, timestamp=time.time())
def _cached_or_capture(self) -> Optional[CameraFrame]:
if (