ff7614a6a0
保留旧格式和 cal_path,校验 SN、特征身份及坐标附件,按帧返回逐轴状态。相关测试 263 项通过,已核对存量预测并实测 180 帧录像。
121 lines
5.6 KiB
Python
121 lines
5.6 KiB
Python
"""File selection, SN isolation and legacy compatibility at the public loader boundary."""
|
|
|
|
import json
|
|
import time
|
|
from types import SimpleNamespace
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from orisys import calibration
|
|
from orisys.calibration_context import build_context
|
|
from orisys.sensors.calibration import resolve_calibration
|
|
|
|
|
|
def model_doc():
|
|
config = {"type": "single_camera", "camera": {"resolution_stitch": [64, 64]},
|
|
"tracker": {"dis_mode": 1, "hhd_downsample": 3}}
|
|
return {"schema_version": 3, "unit": "N", "identity": {"sensor_type": "finger", "sn": "A1"},
|
|
"pipeline": build_context(config), "model": {
|
|
"type": "global_gain/1", "input_profile": "force_delta/1", "output_frame": "sdk_force/1",
|
|
"outputs": ["fn"], "params": {"gains_N_per_raw": {"fn": .001}, "valid_force_range_N": [0, 3]}}}
|
|
|
|
|
|
def test_schema3_public_load_write_and_fn_only_sn(tmp_path):
|
|
path = tmp_path / "finger" / "A1.json"
|
|
path.parent.mkdir()
|
|
path.write_text(json.dumps(model_doc()))
|
|
model = calibration.load_for_device(tmp_path, "finger", "A1")
|
|
assert tuple(model.channels) == ("fn",)
|
|
assert calibration.check(model).ok
|
|
target = tmp_path / "copy.json"
|
|
calibration.write(model, target)
|
|
assert json.loads(target.read_text())["schema_version"] == 3
|
|
assert calibration.load(target).fingerprint == model.fingerprint
|
|
with pytest.raises(calibration.CalibrationError, match="identity mismatch"):
|
|
calibration.validate_for_device(model, "finger", "another-SN")
|
|
|
|
|
|
def test_missing_sn_does_not_pick_any_neighbor_or_default(tmp_path):
|
|
(tmp_path / "finger").mkdir()
|
|
(tmp_path / "finger" / "A1.json").write_text(json.dumps(model_doc()))
|
|
(tmp_path / "force_calibration.json").write_text(json.dumps(model_doc()))
|
|
assert calibration.load_for_device(tmp_path, "finger", "B2") is None
|
|
with pytest.raises(calibration.CalibrationError, match="directory"):
|
|
calibration.load_for_device(tmp_path / "missing", "finger", "B2")
|
|
|
|
|
|
@pytest.mark.parametrize("change", ["unknown_type", "unknown_schema", "malformed"])
|
|
def test_selected_bad_model_never_falls_back(tmp_path, change):
|
|
data = model_doc()
|
|
if change == "unknown_type":
|
|
data["model"]["type"] = "onnx/1"
|
|
elif change == "unknown_schema":
|
|
data["schema_version"] = 99
|
|
else:
|
|
data["model"]["params"]["gains_N_per_raw"] = {}
|
|
path = tmp_path / "selected.json"
|
|
path.write_text(json.dumps(data))
|
|
with pytest.raises(calibration.CalibrationError):
|
|
calibration.load(path, sensor_type="box")
|
|
|
|
|
|
def test_invalid_default_schema3_does_not_silently_become_raw(monkeypatch, tmp_path):
|
|
from orisys import configs
|
|
|
|
data = model_doc()
|
|
data["model"]["type"] = "onnx/1"
|
|
path = tmp_path / "force_calibration.json"
|
|
path.write_text(json.dumps(data))
|
|
monkeypatch.setattr(configs, "resolve_sensor_bundle", lambda name: SimpleNamespace(force_calibration_json=path))
|
|
with pytest.raises(calibration.CalibrationError, match="unsupported calibration model"):
|
|
calibration.load(sensor_type="finger")
|
|
|
|
|
|
def test_path_selection_keeps_explicit_and_sn_modes_separate(tmp_path):
|
|
with pytest.raises(calibration.CalibrationError, match="mutually exclusive"):
|
|
calibration.select_calibration_path(cal_path="file.json", calibration_dir=tmp_path)
|
|
with pytest.raises(calibration.CalibrationError, match="requires"):
|
|
calibration.select_calibration_path(device_id="A1")
|
|
assert calibration.select_calibration_path(cal_path=tmp_path / "A1.json") == tmp_path / "A1.json"
|
|
|
|
|
|
def test_profile_declares_only_available_axes_and_checks_family(tmp_path):
|
|
path = tmp_path / "selected.json"
|
|
path.write_text(json.dumps(model_doc()))
|
|
options = dict(bundle_root=tmp_path, declared_default=None, profile_id="test", selection=path)
|
|
resolved = resolve_calibration(product_family="finger", **options)
|
|
assert resolved.axes == ("fn",) and resolved.document is not None
|
|
assert resolved.fingerprint == resolved.document.fingerprint
|
|
wrong = resolve_calibration(product_family="box", **options)
|
|
assert wrong.document is None and wrong.issues[0].code == "calibration.model-family-mismatch"
|
|
|
|
|
|
def test_processor_applies_resolved_selection_including_none(monkeypatch):
|
|
from orisys.sensors import backends
|
|
|
|
monkeypatch.setattr(backends, "validate_profile", lambda profile: SimpleNamespace(usable=True))
|
|
selected = calibration.parse(model_doc())
|
|
calls = []
|
|
sensor = SimpleNamespace(set_calibration=calls.append)
|
|
for model in (selected, None):
|
|
profile = SimpleNamespace(processing_backend="single_camera", config={"camera": {}},
|
|
calibration=SimpleNamespace(document=model), profile_id="test")
|
|
session = backends.create_processor(profile, sensor=sensor)
|
|
assert session.sensor is sensor
|
|
assert calls == [selected, None]
|
|
|
|
|
|
def test_snapshot_keeps_per_frame_units_and_validity_copy():
|
|
from orisys.pipeline import StreamPipeline
|
|
|
|
sensor = SimpleNamespace(info=object(), force_unit="N", force_units=("N", "raw", "raw"),
|
|
calibration_status={"fn": "outside_coverage"}, fnormal=float("nan"),
|
|
fshearx=2., fsheary=3., _force_raw=(1., 2., 3.))
|
|
pipeline = StreamPipeline(sensor, lambda: None, include=())
|
|
snapshot = pipeline._build(np.zeros((2, 2)), None, 0., 0., 0., time.perf_counter())
|
|
assert snapshot.force_units == ("N", "raw", "raw")
|
|
assert snapshot.calibration_status == {"fn": "outside_coverage"}
|
|
sensor.calibration_status["fn"] = "ok"
|
|
assert snapshot.calibration_status["fn"] == "outside_coverage"
|