Files
orisys-app/apps/studio/tests/test_session_metadata.py
2026-09-27 19:20:43 +08:00

208 lines
8.1 KiB
Python

"""Acceptance for the facts a recording states about its own session.
``session.json`` is the only witness a reader has later, so these are the rules that
keep it honest: which frame rate a container may declare, that the filters are read back
from the worker that ran them, that no metadata read can fail a recording, and that the
3D pane's own description is passed through untouched.
"""
from __future__ import annotations
import hashlib
from pathlib import Path
from types import SimpleNamespace
import numpy as np
import pytest
from orisys_app_shared.force.filters import FilterSettings
from orisys_app_shared.record import RecordConfig
from orisys_studio import session_metadata as sm
CONFIG = (
Path(__file__).resolve().parents[1]
/ "config"
/ "sensors"
/ "finger"
/ "sensor.json"
)
# --------------------------------------------------------------------- config hash
def test_the_config_hash_is_the_file_content_or_nothing(tmp_path):
path = tmp_path / "sensor.json"
path.write_text('{"camera": {}}', encoding="utf-8")
assert sm.config_sha256(path) == hashlib.sha256(path.read_bytes()).hexdigest()
assert sm.config_sha256(tmp_path / "gone.json") == ""
assert sm.config_sha256("") == ""
def test_the_sdk_version_is_a_string_even_with_no_sdk():
value = sm.sdk_version()
assert isinstance(value, str) and value
# ------------------------------------------------------------------- device serial
def test_the_device_serial_comes_from_whichever_field_the_sdk_uses():
assert sm.device_serial(SimpleNamespace(sn="A1")) == "A1"
assert sm.device_serial(SimpleNamespace(serial="B2")) == "B2"
assert sm.device_serial(SimpleNamespace(serial_number="C3")) == "C3"
assert sm.device_serial(SimpleNamespace(info=SimpleNamespace(SN="D4"))) == "D4"
assert sm.device_serial(SimpleNamespace()) == ""
assert sm.device_serial(None) == ""
# -------------------------------------------------------------- capture / filters
def test_capture_metadata_copies_what_the_worker_says_and_survives_failure():
assert sm.capture_metadata(None) == {}
assert sm.capture_metadata(SimpleNamespace()) == {}
worker = SimpleNamespace(capture_status=lambda: {"requested_fps": 30.0})
assert sm.capture_metadata(worker) == {"requested_fps": 30.0}
def explode():
raise RuntimeError("driver gone")
# A recording must not fail because the camera cannot describe itself.
assert sm.capture_metadata(SimpleNamespace(capture_status=explode)) == {}
def test_filter_metadata_names_what_the_sdk_could_not_apply():
settings = FilterSettings()
worker = SimpleNamespace(
filter_state=lambda: {"flow_ema_alpha": 1.0},
filter_settings=lambda: settings,
filters_unsupported=lambda: ("force_lowpass_order",),
)
block = sm.filter_metadata(worker)
assert block["unsupported_by_sdk"] == ["force_lowpass_order"]
assert block # the applied settings are stated too
# No worker, and a worker that raises, both give a block rather than an exception.
assert sm.filter_metadata(None)
def explode():
raise RuntimeError("worker is gone")
assert sm.filter_metadata(SimpleNamespace(filter_state=explode))
def test_filter_settings_default_to_nothing_filters():
"""The shipped configs state "no filters"; an empty preference must agree."""
empty = sm.filter_settings_from_prefs({})
assert empty == FilterSettings()
stored = sm.filter_settings_from_prefs(
{"plot_force_median_window": 3, "plot_flow_ema_alpha": 0.5, "unrelated": 1}
)
assert stored != empty
assert stored.median_window == 3
assert stored.flow_ema_alpha == pytest.approx(0.5)
def test_a_broken_preferences_object_is_not_fatal():
class Broken(dict):
def get(self, key, default=None): # noqa: D102 - deliberately broken
raise RuntimeError("corrupt preferences")
assert sm.filter_settings_from_prefs(Broken()) == FilterSettings()
# ------------------------------------------------------------------- frame rates
def test_a_camera_declares_its_configured_rate_and_a_file_its_measured_one():
"""The container's fps must describe the spacing of the pictures it holds."""
if CONFIG.is_file():
assert sm.video_fps(48.0, source_is_file=False, config_ref=CONFIG) == 30.0
# A file source has no configured rate to honour.
assert sm.video_fps(17.5, source_is_file=True, config_ref=CONFIG) == 17.5
# An unreadable config falls back to the measured rate, never to zero.
assert sm.video_fps(12.0, source_is_file=False, config_ref="missing.json") == 12.0
assert sm.video_fps(0.0, source_is_file=True, config_ref=None) == float(
RecordConfig().fps
)
def test_the_filter_dialog_gets_a_rate_it_can_check_nyquist_against():
assert sm.device_sample_rate(25.0, None, default=30.0) == 25.0
assert sm.device_sample_rate(None, "15", default=30.0) == 15.0
assert sm.device_sample_rate("garbage", "also garbage", default=30.0) == 30.0
assert sm.device_sample_rate(None, 0, default=30.0) == 30.0
# ------------------------------------------------------------- the whole metadata
def test_the_session_block_states_the_device_the_filters_and_the_picture():
info = SimpleNamespace(
raw_frame=np.zeros((48, 64, 3), dtype=np.uint8), force_calibrated=True, sn="SN-9"
)
worker = SimpleNamespace(
capture_status=lambda: {"requested_fps": 30.0, "measured_fps": 21.0},
filter_state=dict,
filter_settings=lambda: FilterSettings(),
filters_unsupported=lambda: (),
)
facts = sm.SessionFacts(
sensor_type="finger",
config_ref=str(CONFIG) if CONFIG.is_file() else "missing.json",
worker=worker,
info=info,
video_fps=30.0,
measured_fps=21.0,
active_calibration_id="cal-7",
operator_sn=" OPERATOR-1 ",
recording_mode="video",
visualization3d={"block_version": 1, "enabled": True},
)
config = RecordConfig()
block = sm.build_session_metadata(config, facts)
assert block["device"]["sensor_type"] == "finger"
assert block["device"]["frame"] == {
"width": 64,
"height": 48,
"fps": 30.0,
"measured_fps": 21.0,
}
assert block["versions"]["sdk"]
assert block["capture"] == {"requested_fps": 30.0, "measured_fps": 21.0}
assert block["calibration"]["force_calibrated"] is True
assert block["calibration"]["version"] == "cal-7"
assert block["calibration"]["sn"] == "OPERATOR-1"
assert block["calibration"]["device_sn"] == "SN-9"
assert block["depth"]["stored"] is False
assert block["depth"]["config_sha256"] == block["device"]["config_sha256"]
assert block["filter"]
assert block["recording_mode"] == "video"
assert block["video_config"] == config.as_dict()
assert block["visualization3d"] == {"block_version": 1, "enabled": True}
assert block["conventions"]["axis_order"] == ["FN", "FX", "FY"]
def test_the_block_survives_a_worker_that_is_gone_and_a_packet_that_never_came():
"""Metadata that cannot be read is empty, never an exception (a record is running)."""
block = sm.build_session_metadata(
RecordConfig(),
sm.SessionFacts(sensor_type="box", config_ref="", worker=None, info=None),
)
assert block["device"]["frame"]["width"] is None
assert block["capture"] == {}
assert block["filter"]
assert block["device"]["config_sha256"] == ""
def test_the_3d_pane_description_is_passed_through_untouched():
described = {"scene": {"asset_id": "x"}, "gl": {}, "display": {}, "stream": {}}
block = sm.build_session_metadata(
RecordConfig(),
sm.SessionFacts(
sensor_type="finger",
config_ref="",
visualization3d={"visualization3d": described, "block_version": 1},
),
)
assert block["visualization3d"]["visualization3d"] == described
@pytest.mark.parametrize("mode", ("video", "raw", "lossless"))
def test_every_recording_mode_is_recorded_as_stated(mode):
block = sm.build_session_metadata(
RecordConfig(),
sm.SessionFacts(sensor_type="finger", config_ref="", recording_mode=mode),
)
assert block["recording_mode"] == mode