接入力标定 schema 3 与多点空间增益模型
保留旧格式和 cal_path,校验 SN、特征身份及坐标附件,按帧返回逐轴状态。相关测试 263 项通过,已核对存量预测并实测 180 帧录像。
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user