From e11565de0444299eb9ae4c95aa3bd6d76b5d97fd Mon Sep 17 00:00:00 2001 From: Olivia Casadei Date: Thu, 1 Oct 2026 10:11:43 +0200 Subject: [PATCH] feat: frameset_seq in /api/capture and /ws, added field The number of the camera frameset an aligned frame comes from, in the same numbering as /api/raw/latest and /api/color/latest: it lets a tool compare Intel's align on PC2 with an align on the host on the SAME frameset. Existing fields unchanged (frame_id still counts responses); the field-set tests now say "the R01 fields plus frameset_seq only". Co-Authored-By: Claude Opus 5.5 --- src/backend/api/routes.py | 4 +++- src/backend/services/camera.py | 4 +++- tests/test_parallel_capture.py | 8 +++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/backend/api/routes.py b/src/backend/api/routes.py index a26580a..40a819c 100644 --- a/src/backend/api/routes.py +++ b/src/backend/api/routes.py @@ -245,10 +245,12 @@ def _next_frame_id() -> int: def _frame_to_dict(frame: CameraFrame, include_images: bool = True) -> dict: - """/api/capture and /ws JSON of a frame. frame_id counts responses, as before.""" + """/api/capture and /ws JSON of a frame. frame_id counts responses, as before; + frameset_seq (added field) numbers the camera frameset, as /api/raw/latest does.""" enc = encoded(frame) result = { "frame_id": _next_frame_id(), + "frameset_seq": frame.frameset_seq, "timestamp": frame.timestamp, "rgb_shape": list(frame.rgb.shape), "depth_shape": list(frame.depth.shape), diff --git a/src/backend/services/camera.py b/src/backend/services/camera.py index c5595bb..744fd91 100644 --- a/src/backend/services/camera.py +++ b/src/backend/services/camera.py @@ -46,6 +46,7 @@ class CameraFrame: rgb: np.ndarray # (H, W, 3) uint8 BGR depth: np.ndarray # (H, W) uint16 millimeters timestamp: float + frameset_seq: int = 0 # number of the frameset it was aligned from (as /api/raw/latest); 0 = unknown @dataclass @@ -548,7 +549,8 @@ class RealSenseD435: # Own copies: frames live on after the align (latest frame, 100 ms cache, # shared encodings), and views would keep the SDK's frame pool busy. with timer.phase("copy"): - return CameraFrame(rgb=_owned(frame.rgb), depth=_owned(frame.depth), timestamp=frame.timestamp) + return CameraFrame(rgb=_owned(frame.rgb), depth=_owned(frame.depth), timestamp=frame.timestamp, + frameset_seq=seq) def _color_bgr(self, color_frame) -> np.ndarray: """BGR image of an SDK color frame (YUYV decoded on USB 2; else a view on the SDK buffer).""" diff --git a/tests/test_parallel_capture.py b/tests/test_parallel_capture.py index d542a71..3670809 100644 --- a/tests/test_parallel_capture.py +++ b/tests/test_parallel_capture.py @@ -18,11 +18,13 @@ from src.backend.main import app # noqa: E402 from src.backend.services.camera import RealSenseD435 # noqa: E402 from tests.fake_realsense import FakePipeline, open_fake_camera # noqa: E402 -# Fields of /api/capture before optimize-streams R01 (the multi-camera service parses them). +# Fields of /api/capture before optimize-streams R01 (the multi-camera service parses them), +# and the only one added since (R05): existing fields never change. CAPTURE_FIELDS = { "frame_id", "timestamp", "rgb_shape", "depth_shape", "depth_min_mm", "depth_max_mm", "depth_valid_pixels", "rgb_jpeg_b64", "depth_png_b64", "depth_color_b64", } +ADDED_FIELDS = {"frameset_seq"} @pytest.fixture @@ -67,7 +69,7 @@ def test_capture_fields_unchanged_and_one_frame_shared(fake_camera): bodies = asyncio.run(run()) assert all(b["success"] for b in bodies) data = [b["data"] for b in bodies] - assert all(set(d) == CAPTURE_FIELDS for d in data) + assert all(set(d) == CAPTURE_FIELDS | ADDED_FIELDS for d in data) assert len({d["frame_id"] for d in data}) == 3 # frame_id still counts responses assert len({d["timestamp"] for d in data}) == 1 # ... all three served by one frame assert len({d["rgb_jpeg_b64"] for d in data}) == 1 # ... with the same bytes @@ -89,7 +91,7 @@ def test_websocket_streams_frames(fake_camera): ws.send_text("start") first, second = ws.receive_json(), ws.receive_json() ws.send_text("close") - assert set(first) == CAPTURE_FIELDS + assert set(first) == CAPTURE_FIELDS | ADDED_FIELDS assert second["timestamp"] > first["timestamp"]