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 <noreply@anthropic.com>
This commit is contained in:
1 parent
22815e9b0f
commit
e11565de04
3 files changed
+11
-5
No files matched your search
@@ -245,10 +245,12 @@ def _next_frame_id() -> int:
|
|||||||
|
|
||||||
|
|
||||||
def _frame_to_dict(frame: CameraFrame, include_images: bool = True) -> dict:
|
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)
|
enc = encoded(frame)
|
||||||
result = {
|
result = {
|
||||||
"frame_id": _next_frame_id(),
|
"frame_id": _next_frame_id(),
|
||||||
|
"frameset_seq": frame.frameset_seq,
|
||||||
"timestamp": frame.timestamp,
|
"timestamp": frame.timestamp,
|
||||||
"rgb_shape": list(frame.rgb.shape),
|
"rgb_shape": list(frame.rgb.shape),
|
||||||
"depth_shape": list(frame.depth.shape),
|
"depth_shape": list(frame.depth.shape),
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class CameraFrame:
|
|||||||
rgb: np.ndarray # (H, W, 3) uint8 BGR
|
rgb: np.ndarray # (H, W, 3) uint8 BGR
|
||||||
depth: np.ndarray # (H, W) uint16 millimeters
|
depth: np.ndarray # (H, W) uint16 millimeters
|
||||||
timestamp: float
|
timestamp: float
|
||||||
|
frameset_seq: int = 0 # number of the frameset it was aligned from (as /api/raw/latest); 0 = unknown
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -548,7 +549,8 @@ class RealSenseD435:
|
|||||||
# Own copies: frames live on after the align (latest frame, 100 ms cache,
|
# 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.
|
# shared encodings), and views would keep the SDK's frame pool busy.
|
||||||
with timer.phase("copy"):
|
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:
|
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)."""
|
"""BGR image of an SDK color frame (YUYV decoded on USB 2; else a view on the SDK buffer)."""
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ from src.backend.main import app # noqa: E402
|
|||||||
from src.backend.services.camera import RealSenseD435 # noqa: E402
|
from src.backend.services.camera import RealSenseD435 # noqa: E402
|
||||||
from tests.fake_realsense import FakePipeline, open_fake_camera # 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 = {
|
CAPTURE_FIELDS = {
|
||||||
"frame_id", "timestamp", "rgb_shape", "depth_shape", "depth_min_mm", "depth_max_mm",
|
"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",
|
"depth_valid_pixels", "rgb_jpeg_b64", "depth_png_b64", "depth_color_b64",
|
||||||
}
|
}
|
||||||
|
ADDED_FIELDS = {"frameset_seq"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -67,7 +69,7 @@ def test_capture_fields_unchanged_and_one_frame_shared(fake_camera):
|
|||||||
bodies = asyncio.run(run())
|
bodies = asyncio.run(run())
|
||||||
assert all(b["success"] for b in bodies)
|
assert all(b["success"] for b in bodies)
|
||||||
data = [b["data"] 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["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["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
|
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")
|
ws.send_text("start")
|
||||||
first, second = ws.receive_json(), ws.receive_json()
|
first, second = ws.receive_json(), ws.receive_json()
|
||||||
ws.send_text("close")
|
ws.send_text("close")
|
||||||
assert set(first) == CAPTURE_FIELDS
|
assert set(first) == CAPTURE_FIELDS | ADDED_FIELDS
|
||||||
assert second["timestamp"] > first["timestamp"]
|
assert second["timestamp"] > first["timestamp"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user