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:
1 parent
441e0bd917
commit
9aa63e4e02
6 files changed
+166
-22
No files matched your search
+30
-16
@@ -12,6 +12,7 @@ from src.backend.api.dependencies import verify_api_key
|
||||
from src.backend.models.api import ok, fail
|
||||
from src.backend.services.camera import RealSenseD435, CameraFrame
|
||||
from src.backend.services.image_saver import ImageSaver
|
||||
from src.backend.services.phase_timing import timer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,16 +38,23 @@ def set_image_saver(saver: ImageSaver) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _encode_rgb_jpeg(rgb: np.ndarray, quality: int = 85) -> bytes:
|
||||
_, buf = cv2.imencode(".jpg", rgb, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
||||
with timer.phase(f"jpeg_q{quality}"):
|
||||
_, buf = cv2.imencode(".jpg", rgb, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
||||
return buf.tobytes()
|
||||
|
||||
|
||||
def _encode_depth_png(depth: np.ndarray) -> bytes:
|
||||
_, buf = cv2.imencode(".png", depth)
|
||||
with timer.phase("png_depth"):
|
||||
_, buf = cv2.imencode(".png", depth)
|
||||
return buf.tobytes()
|
||||
|
||||
|
||||
def _depth_colormap(depth: np.ndarray) -> np.ndarray:
|
||||
with timer.phase("colormap"):
|
||||
return _jet_colormap(depth)
|
||||
|
||||
|
||||
def _jet_colormap(depth: np.ndarray) -> np.ndarray:
|
||||
valid = depth[depth > 0]
|
||||
if len(valid) == 0:
|
||||
return np.zeros((*depth.shape, 3), dtype=np.uint8)
|
||||
@@ -63,21 +71,25 @@ def _frame_to_dict(frame: CameraFrame, include_images: bool = True) -> dict:
|
||||
global _frame_count
|
||||
_frame_count += 1
|
||||
|
||||
result = {
|
||||
"frame_id": _frame_count,
|
||||
"timestamp": frame.timestamp,
|
||||
"rgb_shape": list(frame.rgb.shape),
|
||||
"depth_shape": list(frame.depth.shape),
|
||||
"depth_min_mm": int(frame.depth[frame.depth > 0].min()) if (frame.depth > 0).any() else 0,
|
||||
"depth_max_mm": int(frame.depth.max()),
|
||||
"depth_valid_pixels": int((frame.depth > 0).sum()),
|
||||
}
|
||||
with timer.phase("depth_stats"):
|
||||
result = {
|
||||
"frame_id": _frame_count,
|
||||
"timestamp": frame.timestamp,
|
||||
"rgb_shape": list(frame.rgb.shape),
|
||||
"depth_shape": list(frame.depth.shape),
|
||||
"depth_min_mm": int(frame.depth[frame.depth > 0].min()) if (frame.depth > 0).any() else 0,
|
||||
"depth_max_mm": int(frame.depth.max()),
|
||||
"depth_valid_pixels": int((frame.depth > 0).sum()),
|
||||
}
|
||||
|
||||
if include_images:
|
||||
result["rgb_jpeg_b64"] = base64.b64encode(_encode_rgb_jpeg(frame.rgb)).decode()
|
||||
result["depth_png_b64"] = base64.b64encode(_encode_depth_png(frame.depth)).decode()
|
||||
colormap = _depth_colormap(frame.depth)
|
||||
result["depth_color_b64"] = base64.b64encode(_encode_rgb_jpeg(colormap, 90)).decode()
|
||||
rgb_jpeg = _encode_rgb_jpeg(frame.rgb)
|
||||
depth_png = _encode_depth_png(frame.depth)
|
||||
color_jpeg = _encode_rgb_jpeg(_depth_colormap(frame.depth), 90)
|
||||
with timer.phase("base64"):
|
||||
result["rgb_jpeg_b64"] = base64.b64encode(rgb_jpeg).decode()
|
||||
result["depth_png_b64"] = base64.b64encode(depth_png).decode()
|
||||
result["depth_color_b64"] = base64.b64encode(color_jpeg).decode()
|
||||
|
||||
return result
|
||||
|
||||
@@ -221,7 +233,9 @@ async def capture():
|
||||
if frame is None:
|
||||
return fail("CAPTURE_TIMEOUT", "Frame capture timeout")
|
||||
|
||||
return JSONResponse(ok(_frame_to_dict(frame)))
|
||||
data = _frame_to_dict(frame)
|
||||
with timer.phase("json"):
|
||||
return JSONResponse(ok(data))
|
||||
|
||||
|
||||
@router.get("/rgb")
|
||||
|
||||
Reference in new issue
Block a user