Store the depth channel in an analysis result and serve it to the 2D depth view
A replay could draw arrows but not depth, and the recording view said there was nothing to draw even when a result existed. Depth is now quantised per frame (14 B empty, 486 B at the worst frame seen), read from the cache by the same draw path live uses, and the raw-recording note appears only when no result exists. Format version 2; see apps/studio/docs/replay-analysis.md.
This commit is contained in:
@@ -38,7 +38,11 @@ from orisys_app_shared.analysis.cache import (
|
||||
evict,
|
||||
scan_results,
|
||||
)
|
||||
from orisys_app_shared.analysis.model import frame_payload_bytes, result_key
|
||||
from orisys_app_shared.analysis.model import (
|
||||
FRAME_HEADER,
|
||||
frame_payload_bytes,
|
||||
result_key,
|
||||
)
|
||||
from orisys_app_shared.analysis.reader import (
|
||||
AnalysisIncompleteError,
|
||||
AnalysisReader,
|
||||
@@ -477,24 +481,43 @@ def test_the_key_follows_the_inputs_that_change_the_result_and_no_others(finger_
|
||||
|
||||
# ------------------------------------------------------------------ size
|
||||
def test_the_stored_size_is_the_measured_encoding(box_pass, finger_pass):
|
||||
"""State the encoding, then hold it: 4,034 B per frame at the finger grid.
|
||||
"""State the encoding, then hold it: 4,048 B per frame at the finger grid.
|
||||
|
||||
Implicit candidate grid (a keyframe) + float32 ``dx``/``dy``/``magnitude`` +
|
||||
bit-packed ``active`` + a 24-byte header + the 8-byte validity digest:
|
||||
``24 + 330*12 + 42 + 8 = 4,034`` B. The alternative measured encodings are float16
|
||||
``dxy``+magnitude (2,022 B) and dropping magnitude (4,002 B with the grid, no
|
||||
header); float32 is chosen because the round trip has to be *exact*, and the
|
||||
magnitude is what serves a changed threshold.
|
||||
bit-packed ``active`` + a 32-byte header + the 8-byte validity digest + the 6-byte
|
||||
depth header: ``32 + 330*12 + 42 + 8 + 6 = 4,048`` B, of which the depth part is
|
||||
``14`` B over the 4,034 B this record cost before the depth channel was stored. A
|
||||
frame whose depth map is *not* all zeros adds its own zlib'd uint8 plane on top.
|
||||
The alternative measured encodings are float16 ``dxy``+magnitude (2,022 B) and
|
||||
dropping magnitude (4,002 B with the grid, no header); float32 is chosen because
|
||||
the round trip has to be *exact*, and the magnitude is what serves a changed
|
||||
threshold.
|
||||
"""
|
||||
assert frame_payload_bytes(330, with_validity=True) == 4034
|
||||
assert frame_payload_bytes(330, with_validity=False) == 4026
|
||||
assert frame_payload_bytes(400, with_validity=False) == 4874
|
||||
fixed = FRAME_HEADER.size + 330 * 12 + 42 + 8
|
||||
assert fixed == 4042
|
||||
assert frame_payload_bytes(330, with_validity=True) == 4042
|
||||
assert frame_payload_bytes(330, with_validity=True, with_depth=True) == 4048
|
||||
assert frame_payload_bytes(330, with_validity=False, with_depth=True) == 4040
|
||||
assert frame_payload_bytes(400, with_validity=False) == 4882
|
||||
assert frame_payload_bytes(400, with_validity=False, with_depth=True) == 4888
|
||||
|
||||
_, finger = finger_pass
|
||||
reader = AnalysisReader(finger.directory)
|
||||
try:
|
||||
assert reader.bytes_per_frame == 4034.0
|
||||
assert reader.frame_record_bytes == 33 * 4034
|
||||
# Every frame states the depth channel; the ones whose map is all zeros pay the
|
||||
# 6-byte header and nothing else.
|
||||
frames = list(reader.frames())
|
||||
emptied = [item for item in frames if item.has_depth and not item.depth_nonzero]
|
||||
carrying = [item for item in frames if item.depth_nonzero]
|
||||
assert len(emptied) + len(carrying) == len(frames)
|
||||
assert reader.frame_record_bytes == sum(
|
||||
fixed + item.depth_stored_bytes for item in frames
|
||||
)
|
||||
assert reader.frame_record_bytes == (
|
||||
len(emptied) * 4048
|
||||
+ len(carrying) * fixed
|
||||
+ sum(item.depth_stored_bytes for item in carrying)
|
||||
)
|
||||
assert reader.bytes_per_frame <= 4096, "the stated per-frame bound"
|
||||
total_per_frame = _directory_bytes(finger.directory) / len(reader)
|
||||
assert total_per_frame <= 4600, "keyframes and metadata included"
|
||||
@@ -505,8 +528,12 @@ def test_the_stored_size_is_the_measured_encoding(box_pass, finger_pass):
|
||||
box_reader = AnalysisReader(box.directory)
|
||||
try:
|
||||
# The box chain has no validity mask, so there is no digest and every frame
|
||||
# carries its own grid keyframe: 4,874 B of record per frame at 400 candidates.
|
||||
assert box_reader.frame_record_bytes == 26 * 4874
|
||||
# carries its own grid keyframe: 4,882 B of record per frame at 400 candidates,
|
||||
# plus whatever depth it carries.
|
||||
assert box_reader.frame_record_bytes == sum(
|
||||
32 + 400 * 12 + 50 + item.depth_stored_bytes
|
||||
for item in box_reader.frames()
|
||||
)
|
||||
assert box_reader.keyframe_counts == {"grid": 26}
|
||||
assert box_reader.mask() is None
|
||||
finally:
|
||||
@@ -608,7 +635,12 @@ def test_a_result_without_metadata_is_never_served(tmp_path):
|
||||
AnalysisReader(directory)
|
||||
|
||||
(directory / META_NAME).write_text(
|
||||
json.dumps({"format": analysis_model.ANALYSIS_FORMAT, "format_version": 1}),
|
||||
json.dumps(
|
||||
{
|
||||
"format": analysis_model.ANALYSIS_FORMAT,
|
||||
"format_version": analysis_model.ANALYSIS_FORMAT_VERSION,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with pytest.raises(AnalysisIncompleteError):
|
||||
|
||||
Reference in New Issue
Block a user