"""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"