"""The depth channel in an analysis result: what it costs, and that it comes back. What is being defended: 1. **The round trip is within the stated error, frame for frame, on real footage.** The chain is walked once more, independently of the pass, and every stored depth map is compared with the map the chain produced for the same frame id. The accepted error is half one quantisation step of that frame's own range, ``(max - min) / 510`` — below half a step of the 256-entry colour ramp the depth view paints with, so a reconstruction cannot be coloured differently from the original. 2. **The size is stated and held.** 14 B per frame whose depth map is exactly all zeros (the frame header's statistics plus a 6-byte payload header) and the header plus a zlib'd uint8 plane for a frame that has one. The alternative encodings were measured and rejected; the numbers are in the module docstring of ``analysis.model``. 3. **"Empty" and "absent" are different answers.** A frame whose depth map is all zeros still carries the channel and states its shape; a frame whose chain exposed no depth channel at all carries none. A view that draws the same blank picture for both is the one that misleads, so the reader distinguishes them and the statistics travel with the record. 4. **A corrupt depth payload is refused, not decoded into a plausible array.** The fixtures use real footage — the 33-frame box recording under ``dist/`` and the 264-frame eight-times-slow clip under ``.scratch/sdk-stream-pipeline``, both driven through the **finger** chain. Where the SDK or the footage is absent the tests skip rather than fake a result: a synthetic array would defend neither the statistics observed on this footage nor the size they produce. """ from __future__ import annotations import json import os import tempfile from pathlib import Path import numpy as np import pytest from orisys_app_shared.analysis.cache import ResultCache, scan_results from orisys_app_shared.analysis.model import ( CODEC_EMPTY, CODEC_RAW, CODEC_ZLIB, DEPTH_HEADER, decode_depth, encode_depth, encode_frame, frame_payload_bytes, ) from orisys_app_shared.analysis.reader import AnalysisReader from orisys_app_shared.analysis.service import ( AnalysisService, ChainSpec, _OpenChain, ) REPO = Path(__file__).resolve().parents[2] STUDIO = REPO / "dist" / "OrisysStudio" RECORDING = ( STUDIO / "outputs" / "recordings" / "20260930T015028Z_session0002_40cb8e7441434feab48bb646afdba011" ) FINGER_CONFIG = STUDIO / "config" / "sensors" / "finger" / "sensor.json" FINGER_EXPORT = STUDIO / "config" / "sensors" / "finger" / "assets" / "30fps-0703" #: The 264-frame clip Step 0 measured the pass's throughput on. It lives in another #: task's scratch, so it is optional: without it the clip half of the round trip is #: reported as skipped and the box recording still runs. CLIP = REPO / ".scratch" / "sdk-stream-pipeline" / "long_8x.mp4" CLIP_FRAMES = 264 CLIP_FPS = 30.0 #: Measured on the two recordings, at the finger chain's own 300 x 280 depth grid. #: Empty frames and the worst non-empty frame are the two numbers the encoding is #: chosen on, so they are asserted rather than described. DEPTH_GRID = (300, 280) EMPTY_PAYLOAD_BYTES = DEPTH_HEADER.size EMPTY_RECORD_BYTES = frame_payload_bytes(330, with_validity=True, with_depth=True) FIXED_RECORD_BYTES = EMPTY_RECORD_BYTES - DEPTH_HEADER.size #: The worst non-empty frame either recording produced: 336 of 84,000 pixels on the clip, #: whose quantised plane zlib's to 486 B. The bound below is what the format promises; #: the measured number is what the recordings delivered, and both are asserted. WORST_NONEMPTY_PIXELS = 336 WORST_NONEMPTY_PAYLOAD_BYTES = 486 def _require_inputs() -> None: missing = [ str(path) for path in (RECORDING / "frames.csv", RECORDING / "raw_0001.mp4", FINGER_CONFIG) if not path.exists() ] if missing: pytest.skip("analysis inputs are not present: " + ", ".join(missing)) pytest.importorskip("orisys", reason="the SDK is needed to run the analysis pass") def _finger_chain() -> ChainSpec: return ChainSpec( sensor_type="finger", config_ref=FINGER_CONFIG, export_dir=FINGER_EXPORT ) def _result_directory(root: Path) -> Path: directories = [entry.directory for entry in scan_results(root) if entry.complete] assert directories, f"no complete result under {root}" return directories[0] def _clip_recording(tmp_path: Path) -> Path: """The clip as a one-segment recording: a link to the video plus a frames index. A bare container has no ``frames.csv``, and frame identity comes from the recording's own counter (Part A, section 6). Writing the index here is what makes the clip addressable by the same code path as a real recording instead of by an index standing in for an id. """ directory = tmp_path / "clip-recording" directory.mkdir(parents=True, exist_ok=True) video = directory / "raw_0001.mp4" if not video.exists(): try: os.link(CLIP, video) except OSError: video.write_bytes(CLIP.read_bytes()) index = directory / "frames.csv" rows = ["frame_id,t_rel_s,segment,segment_frame,in_video,dropped"] rows.extend( f"{number},{number / CLIP_FPS:.6f},1,{number},1,0" for number in range(1, CLIP_FRAMES + 1) ) index.write_text("\n".join(rows) + "\n", encoding="utf-8") return directory def _chain_depth(recording: Path, video: str = "raw_0001.mp4") -> list[np.ndarray]: """Walk the chain once more and return the depth map it produced for every frame. Deliberately a *separate* walk from the pass: comparing a stored result with the object the pass held would only prove the writer copied what it was given. """ from orisys_app_shared.acquisition.sdk_worker import process_chain produced: list[np.ndarray] = [] with tempfile.TemporaryDirectory() as working: with _OpenChain(_finger_chain(), recording / video, Path(working)) as opened: while True: ok, frame = opened.read() if not ok or frame is None: break if opened.feed(frame) is None: raise AssertionError("the chain could not unroll a frame") depth, error = process_chain(opened.sensor) assert not error, error assert depth is not None, "the finger chain exposed no depth channel" array = np.asarray(depth, dtype=np.float32) while array.ndim > 2 and array.shape[0] == 1: array = array[0] produced.append(np.ascontiguousarray(array)) return produced def _tolerance(frame) -> float: """Half one quantisation step of this frame's own stored range.""" return (float(frame.depth_max) - float(frame.depth_min)) / 510.0 def _compare(stored, produced: list[np.ndarray]) -> dict[str, int]: """Compare every stored depth map with the chain's, frame for frame. The order is the pass's own: the finger chain consumes no frame before the first readable one, so decoded frame *k* is stored frame *k*. """ frames = list(stored) assert len(frames) == len(produced), ( f"{len(frames)} stored frames against {len(produced)} decoded" ) stats = {"frames": len(frames), "empty": 0, "nonempty": 0, "worst_pixels": 0} for frame, original in zip(frames, produced): assert frame.has_depth, f"frame {frame.frame_id} lost its depth channel" assert frame.depth.shape == DEPTH_GRID, frame.depth.shape assert original.shape == frame.depth.shape if frame.depth_nonzero: stats["nonempty"] += 1 stats["worst_pixels"] = max(stats["worst_pixels"], frame.depth_nonzero) else: stats["empty"] += 1 assert not bool(np.any(original)), ( f"frame {frame.frame_id} is stored as an empty depth map but the chain " "produced a non-zero one" ) # The statistic the record states is the statistic of the map the chain made. assert frame.depth_nonzero == int(np.count_nonzero(original)) assert float(frame.depth_max) == float(original.max()) assert float(frame.depth_min) == float(original.min()) error = float(np.max(np.abs(np.asarray(frame.depth) - original))) assert error <= _tolerance(frame), ( f"frame {frame.frame_id} is {error} away from the chain's map, over the " f"{_tolerance(frame)} the encoding accepts" ) # The reconstruction carries the same zero/non-zero verdict everywhere: every # observed non-zero value is far above the half step, so the count survives too. assert int(np.count_nonzero(frame.depth)) == frame.depth_nonzero return stats # ------------------------------------------------------------------ the round trip @pytest.fixture(scope="module") def box_depth(tmp_path_factory): """One real pass over the evidence recording, kept for the round trip and the size.""" _require_inputs() root = tmp_path_factory.mktemp("analysis-depth-box") outcome = AnalysisService( RECORDING, chain=_finger_chain(), cache=ResultCache(root) ).run() assert outcome.complete, outcome.reason return root def test_the_box_recording_round_trips_its_depth_within_the_stated_error(box_depth): produced = _chain_depth(RECORDING) reader = AnalysisReader(_result_directory(box_depth)) try: stats = _compare(reader.frames(), produced) # The observed split, not a hope: 32 of the 33 frames carry a map that is # exactly all zeros, and the one that does not holds 76 pixels of 84,000. assert stats == { "frames": 33, "empty": 32, "nonempty": 1, "worst_pixels": 76, } finally: reader.close() def test_the_clip_round_trips_its_depth_within_the_stated_error(tmp_path): if not CLIP.is_file(): pytest.skip(f"the clip is not present: {CLIP}") _require_inputs() recording = _clip_recording(tmp_path) root = tmp_path / "cache" outcome = AnalysisService( recording, chain=_finger_chain(), cache=ResultCache(root) ).run() assert outcome.complete, outcome.reason produced = _chain_depth(recording) reader = AnalysisReader(_result_directory(root)) try: stats = _compare(reader.frames(), produced) assert stats == { "frames": CLIP_FRAMES, "empty": 241, "nonempty": 23, "worst_pixels": WORST_NONEMPTY_PIXELS, } finally: reader.close() # ------------------------------------------------------------------------- the size def test_the_depth_cost_is_the_measured_encoding(box_depth): """14 B per empty frame, the plane's own zlib'd bytes for a frame that has one. The record without depth is 4,034 B at the finger grid; the depth channel adds the eight bytes of per-frame statistics the header now carries (``depth_min``, ``depth_max``) and the six-byte payload header, and then whatever the plane costs. Ten minutes at 30 fps is 18,000 frames, so the empty case is 72,864,000 B = 69.5 MiB against 69.3 MiB before: the number is small because the *common* case is a map that is all zeros and a payload that is a header. """ assert DEPTH_HEADER.size == 6 assert FIXED_RECORD_BYTES == 4042 assert EMPTY_RECORD_BYTES == 4048 reader = AnalysisReader(_result_directory(box_depth)) try: frames = list(reader.frames()) emptied = [item for item in frames if item.depth_nonzero == 0] carrying = [item for item in frames if item.depth_nonzero] assert len(emptied) + len(carrying) == len(frames) assert {item.depth_stored_bytes for item in emptied} == {EMPTY_PAYLOAD_BYTES} for item in carrying: assert item.depth_stored_bytes == DEPTH_HEADER.size + len( _payload_after_header(item.depth, item.depth_min, item.depth_max) ) assert reader.frame_record_bytes == sum( FIXED_RECORD_BYTES + item.depth_stored_bytes for item in frames ) assert reader.bytes_per_frame <= 4096.0, "the stated per-frame bound" # Worst case, every frame the worst non-empty frame observed on the clip. worst = FIXED_RECORD_BYTES + DEPTH_HEADER.size + WORST_NONEMPTY_PAYLOAD_BYTES assert worst == 4534 assert worst * 18_000 == 81_612_000, "81.6 MB per ten minutes, upper bound" # The rate the footage actually produced, over the 264-frame clip: 241 empty # frames and 23 at the worst non-empty size. typical = (241 * EMPTY_RECORD_BYTES + 23 * worst) / 264 assert typical == pytest.approx(4090.34, abs=0.01) assert typical * 18_000 == pytest.approx(73_626_136, abs=1.0) assert typical * 18_000 < 73_700_000, "70.2 MB per ten minutes at the observed rate" assert typical < 4096.0 finally: reader.close() def _payload_after_header(depth: np.ndarray, low: float, high: float) -> bytes: """What ``encode_depth`` would store for a map, payload only. Shared with the codec.""" return encode_depth(depth)[0][DEPTH_HEADER.size :] # ------------------------------------------------------------------- the codec itself def test_an_empty_map_costs_a_header_and_states_its_shape(): depth = np.zeros(DEPTH_GRID, dtype=np.float32) payload, nonzero, low, high = encode_depth(depth) assert (nonzero, low, high) == (0, 0.0, 0.0) assert payload == DEPTH_HEADER.pack(CODEC_EMPTY, 8, *DEPTH_GRID) assert len(payload) == EMPTY_PAYLOAD_BYTES decoded = decode_depth(payload) assert decoded.is_empty and decoded.nonzero == 0 assert (decoded.height, decoded.width) == DEPTH_GRID from orisys_app_shared.analysis.model import depth_values restored = depth_values(decoded, low=low, high=high) assert restored.shape == DEPTH_GRID assert not bool(np.any(restored)), "an empty map comes back as zeros, not as NaNs" def test_a_single_plane_is_quantised_over_its_own_range_and_stays_within_half_a_step(): rng = np.random.default_rng(20261004) depth = np.zeros(DEPTH_GRID, dtype=np.float32) rows = rng.integers(0, DEPTH_GRID[0], 500) cols = rng.integers(0, DEPTH_GRID[1], 500) depth[rows, cols] = rng.random(500).astype(np.float32) * 0.05 payload, nonzero, low, high = encode_depth(depth) assert nonzero == int(np.count_nonzero(depth)) assert (low, high) == (float(depth.min()), float(depth.max())) assert len(payload) < 4096, "a 500-pixel plane is not 84 kB of payload" decoded = decode_depth(payload, low=low, high=high) assert decoded.codec in (CODEC_ZLIB, CODEC_RAW) from orisys_app_shared.analysis.model import depth_values restored = depth_values(decoded, low=low, high=high) step = (high - low) / 255.0 assert float(np.max(np.abs(restored - depth))) <= step / 2.0 + 1e-12 # The count survives for every value the quantiser can actually represent: a value # above half a step lands on a code of at least one. A value *below* half a step can # round to zero, and that is the accepted error rather than a lost measurement — it # is smaller than the error the encoding already allows. assert int(np.count_nonzero(restored[depth > step / 2.0])) == int( np.count_nonzero(depth > step / 2.0) ) assert not bool(np.any(restored[depth == 0.0])) # The display paints this range onto a 256-entry ramp, so half a quantisation step # is half a ramp step: the reconstruction cannot change a pixel's colour. assert step / 2.0 <= (high - low) / 255.0 def test_a_plane_that_does_not_compress_is_stored_raw_rather_than_larger(): rng = np.random.default_rng(7) depth = rng.random(DEPTH_GRID).astype(np.float32) payload, nonzero, low, high = encode_depth(depth) decoded = decode_depth(payload, low=low, high=high) assert decoded.codec == CODEC_RAW assert decoded.stored_bytes == depth.size assert len(payload) == DEPTH_HEADER.size + depth.size def test_depth_statistics_are_per_frame_because_the_range_is(): """Two frames with different ranges each keep all 256 levels.""" flat = np.zeros(DEPTH_GRID, dtype=np.float32) flat[10, 10] = 0.001 steep = np.zeros(DEPTH_GRID, dtype=np.float32) steep[10, 10] = 0.0 steep[20, 20] = 1.0 for frame in (flat, steep): payload, _, low, high = encode_depth(frame) decoded = decode_depth(payload, low=low, high=high) from orisys_app_shared.analysis.model import depth_values restored = depth_values(decoded, low=low, high=high) assert float(np.max(np.abs(restored - frame))) <= (high - low) / 510.0 + 1e-12 def test_a_corrupt_depth_payload_is_refused_rather_than_decoded(): good, _, low, high = encode_depth( np.full(DEPTH_GRID, 0.25, dtype=np.float32) ) # A plane that decodes to the wrong number of bytes for the shape it declares. with pytest.raises(ValueError): decode_depth(good[:-1], low=low, high=high) # A shape no sensor has, refused before it becomes an allocation. with pytest.raises(ValueError): decode_depth(DEPTH_HEADER.pack(CODEC_EMPTY, 8, 0xFFFF, 0xFFFF)) # A bit depth this build does not store. with pytest.raises(ValueError): decode_depth(DEPTH_HEADER.pack(CODEC_EMPTY, 16, *DEPTH_GRID)) # And a header that is not even there. with pytest.raises(ValueError): decode_depth(b"") def test_a_frame_without_a_depth_channel_is_not_a_frame_with_an_empty_one(): """The two facts have to stay apart: only one of them can be drawn as a measurement.""" blank = encode_frame( frame_id=1, t_rel_s=0.0, grid_id=0, dx=np.zeros(2, np.float32), dy=np.zeros(2, np.float32), magnitude=np.zeros(2, np.float32), active=np.zeros(2, bool), depth=np.zeros(DEPTH_GRID, np.float32), ) absent = encode_frame( frame_id=2, t_rel_s=0.0, grid_id=0, dx=np.zeros(2, np.float32), dy=np.zeros(2, np.float32), magnitude=np.zeros(2, np.float32), active=np.zeros(2, bool), ) assert len(blank) - len(absent) == EMPTY_PAYLOAD_BYTES from orisys_app_shared.analysis.model import decode_frame empty_frame = decode_frame(blank) absent_frame = decode_frame(absent) assert empty_frame.depth is not None and empty_frame.depth.is_empty assert empty_frame.depth_map() is not None assert absent_frame.depth is None and absent_frame.depth_map() is None def test_the_provenance_counts_what_was_empty(box_depth): """The operator's "there is no depth here" has to be answerable from the result.""" reader = AnalysisReader(_result_directory(box_depth)) try: counts = reader.counts assert counts["depth_frames"] == 33 assert counts["depth_nonzero_frames"] == 1 assert counts["depth_nonzero_max"] == 76 assert counts["depth_pixels"] == DEPTH_GRID[0] * DEPTH_GRID[1] assert counts["frame_record_bytes"] == reader.frame_record_bytes provenance = json.loads( (_result_directory(box_depth) / "meta.json").read_text(encoding="utf-8") )["provenance"] assert provenance["sources"]["depth"] == "recomputed" finally: reader.close()