From 5165d77dfca3e22cdad3679e59bd6cfc21ca2695 Mon Sep 17 00:00:00 2001 From: Olivia Casadei Date: Thu, 1 Oct 2026 10:14:55 +0200 Subject: [PATCH] test: raw frameset, frameset_seq, calibration keys, host-align comparison On the fake camera: /api/raw/latest holds color and depth of one frameset (frame number in the pixels), waits with after=N and times out with 504, shares its color JPEG with the color channel; /api/capture's frameset_seq; /api/intrinsics has sizes, models and depth_scale_m; compare_depths counts and differences; the comparison script end to end through the API with identical geometry (median |diff| 0, > 90% exact: rs.align itself changes ~3% of the fake's pure-noise depth where pixel footprints overlap). Co-Authored-By: Claude Opus 5.5 --- tests/test_raw_frameset.py | 119 +++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/test_raw_frameset.py diff --git a/tests/test_raw_frameset.py b/tests/test_raw_frameset.py new file mode 100644 index 0000000..e077bf5 --- /dev/null +++ b/tests/test_raw_frameset.py @@ -0,0 +1,119 @@ +"""Raw frameset endpoint, frameset_seq, full calibration and the host-align comparison, on the fake camera.""" + +import base64 +import importlib.util +from pathlib import Path + +import cv2 +import numpy as np +import pytest + +pytest.importorskip("pyrealsense2") + +from starlette.testclient import TestClient # noqa: E402 + +from src.backend.api import routes, websocket # noqa: E402 +from src.backend.main import app # noqa: E402 +from src.backend.services.camera import (CameraExtrinsics, CameraIntrinsics, Distortion, # noqa: E402 + RealSenseD435) +from tests.fake_realsense import FakeAlign, FakePipeline, frame_number_of_color, open_fake_camera # noqa: E402 + +SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "compare_host_align.py" + + +def _load_script(): + spec = importlib.util.spec_from_file_location("compare_host_align", SCRIPT) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _identity_calibration(cam): + """Same intrinsics for both streams, identity extrinsics: host align must reproduce the depth.""" + cam.intrinsics = CameraIntrinsics(610.0, 610.0, 320.0, 240.0, 610.0, 610.0, 320.0, 240.0) + cam.distortion_ir = cam.distortion_rgb = Distortion(0, 0, 0, 0, 0) + cam.extrinsics = CameraExtrinsics(rotation=(1, 0, 0, 0, 1, 0, 0, 0, 1), translation=(0.0, 0.0, 0.0)) + cam.stream_sizes = {"ir": (640, 480), "rgb": (640, 480)} + cam.distortion_models = {"ir": "brown_conrady", "rgb": "brown_conrady"} + + +@pytest.fixture +def client(): + cam = RealSenseD435() + open_fake_camera(cam, FakePipeline(fps=30), FakeAlign(delay_s=0.0)) + _identity_calibration(cam) + routes.set_camera(cam) + websocket.set_camera(cam) + yield TestClient(app) + cam.close() + routes.set_camera(None) + websocket.set_camera(None) + + +def _decode(b64: str, flags: int) -> np.ndarray: + return cv2.imdecode(np.frombuffer(base64.b64decode(b64), np.uint8), flags) + + +def test_raw_frameset_holds_color_and_depth_of_one_frameset(client): + data = client.get("/api/raw/latest").json()["data"] + color = _decode(data["color_jpeg_b64"], cv2.IMREAD_COLOR) + depth = _decode(data["depth_png_b64"], cv2.IMREAD_UNCHANGED) + assert color.shape == tuple(data["color_shape"]) and depth.shape == tuple(data["depth_shape"]) + assert depth.dtype == np.uint16 and data["depth_units"] == "mm" + assert frame_number_of_color(color) == int(depth[0, 0]) % 256 # one frameset + assert {"frameset_seq", "camera_timestamp_ms", "timestamp_domain", "host_timestamp"} <= set(data) + + +def test_raw_frameset_waits_for_a_newer_one_and_times_out(client): + seq = client.get("/api/raw/latest").json()["data"]["frameset_seq"] + newer = client.get("/api/raw/latest", params={"after": seq, "timeout_ms": 1000}).json()["data"] + assert newer["frameset_seq"] > seq + assert client.get("/api/raw/latest", params={"after": seq + 10_000, "timeout_ms": 50}).status_code == 504 + + +def test_raw_and_color_channel_share_the_color_jpeg(client): + """The raw payload's color JPEG is the color channel's encoding of that frameset (encoded once).""" + data = client.get("/api/raw/latest").json()["data"] + shared = [enc for enc in routes._encoded_colors if enc.seq == data["frameset_seq"]] + assert len(shared) == 1 + assert base64.b64decode(data["color_jpeg_b64"]) == shared[0].jpeg() + + +def test_capture_carries_the_frameset_seq_of_its_frameset(client): + capture = client.get("/api/capture").json()["data"] + assert capture["frameset_seq"] >= 1 + color = _decode(capture["rgb_jpeg_b64"], cv2.IMREAD_COLOR) + depth = _decode(capture["depth_png_b64"], cv2.IMREAD_UNCHANGED) + assert frame_number_of_color(color) == int(depth[0, 0]) % 256 + + +def test_intrinsics_have_what_an_external_align_needs(client): + data = client.get("/api/intrinsics").json()["data"] + for stream in ("ir", "rgb"): + assert {"fx", "fy", "cx", "cy", "distortion", "width", "height", "model"} <= set(data[stream]) + assert data["depth_scale_m"] == pytest.approx(0.001) + assert len(data["extrinsics_ir_to_rgb"]["rotation"]) == 9 + + +def test_compare_depths_counts_and_differences(): + cha = _load_script() + pc2 = np.array([[100, 0], [300, 400]], np.uint16) + host = np.array([[100, 200], [0, 410]], np.uint16) + result = cha.compare_depths(pc2, host) + assert (result["valid_both"], result["only_pc2"], result["only_host"]) == (2, 1, 1) + assert result["abs_diff_mm"]["max"] == 10 and result["equal_fraction_of_valid_both"] == 0.5 + + +def test_comparison_script_end_to_end_on_the_fake_camera(client): + """With identical geometry the host align reproduces the PC2 ("fake align" = identity) depth. + + Not every pixel: rs.align splats each depth pixel's footprint and keeps the + nearest where they overlap, which on the fake's pure-noise depth changes ~3%. + """ + cha = _load_script() + calibration = cha._data(client.get("/api/intrinsics")) + with cha.HostAligner(calibration) as aligner: + result = cha.compare_one(client, aligner, tries=20) + assert result["valid_both"] > 0.9 * result["pixels"] + assert result["abs_diff_mm"]["median"] == 0 + assert result["equal_fraction_of_valid_both"] > 0.9