"""Tests for orisys.calibration load/parse/write and Sensor apply.""" from __future__ import annotations import json from pathlib import Path import pytest from orisys import Sensor from orisys.calibration import ( CalSample, CalibrationError, build, check, load, parse, write, ) def _box_cal_path() -> Path: # Editable checkout: repo_root/sensors/box/force_calibration.json here = Path(__file__).resolve() candidate = here.parents[1] / "sensors" / "box" / "force_calibration.json" assert candidate.is_file(), f"missing shipped cal: {candidate}" return candidate def test_parse_box_v1_piecewise(): cal = load(sensor_type="box") assert cal is not None assert cal.get("FN") is not None assert cal.get("FN").convert(10000.0) == pytest.approx(0.377) assert cal.get("FX").convert_component(36000.0) == pytest.approx(1.19) assert cal.get("FX").convert_component(-36000.0) == pytest.approx(-1.19) assert cal.get("FY").convert_component(36000.0) == pytest.approx(1.19) def test_parse_v2_tmp_file(tmp_path: Path): doc = { "schema_version": 2, "unit": "N", "channels": { "normal": { "method": "piecewise_linear", "points": [[0.0, 0.0], [1000.0, 1.0]], "extrapolate": True, "clamp_output_min": 0.0, }, "shear_x": { "method": "piecewise_linear", "signed": True, "points": [[0.0, 0.0], [500.0, 2.0]], "extrapolate": True, "clamp_output_min": 0.0, }, "shear_y": { "method": "piecewise_linear", "signed": True, "points": [[0.0, 0.0], [500.0, 3.0]], "extrapolate": True, "clamp_output_min": 0.0, }, }, "meta": {"sensor_type": "box", "sn": "TEST-001"}, } path = tmp_path / "force_calibration_v2.json" path.write_text(json.dumps(doc), encoding="utf-8") cal = load(path) assert cal is not None assert check(cal).ok assert cal.convert(1000.0, 500.0, -500.0) == pytest.approx((1.0, 2.0, -3.0)) def test_build_write_load_roundtrip(tmp_path: Path): samples = { "fn": [ CalSample(weight_n=0.0, raw_mean=0.0, n=10), CalSample(weight_n=1.0, raw_mean=1000.0, n=10), ], "fx": [ CalSample(weight_n=0.0, raw_mean=0.0, n=10), CalSample(weight_n=2.0, raw_mean=500.0, n=10), ], "fy": [ CalSample(weight_n=0.0, raw_mean=0.0, n=10), CalSample(weight_n=3.0, raw_mean=500.0, n=10), ], } cal = build( samples, identity={"sensor_type": "box", "sn": "RT-1"}, pipeline={"dis_mode": 1}, origin="user", ) path = tmp_path / "out.json" write(cal, path, overwrite=True) loaded = load(path) assert loaded is not None assert loaded.convert(1000.0, 500.0, -500.0) == pytest.approx((1.0, 2.0, -3.0)) def test_check_rejects_bad_schema_and_short_points(): bad_schema = {"schema_version": 99, "channels": {"normal": {"method": "linear", "slope": 1, "offset": 0}}} result = check(bad_schema) assert not result.ok assert any("schema_version" in e for e in result.errors) short = { "schema_version": 2, "channels": { "normal": { "method": "piecewise_linear", "points": [[0.0, 0.0]], } }, } result2 = check(short) assert not result2.ok def test_sensor_cal_path_v2_applies_to_read_info(tmp_path: Path): doc = { "schema_version": 2, "unit": "N", "channels": { "normal": { "method": "piecewise_linear", "points": [[0.0, 0.0], [100.0, 5.0]], "extrapolate": True, "clamp_output_min": 0.0, }, "shear_x": { "method": "piecewise_linear", "signed": True, "points": [[0.0, 0.0], [100.0, 7.0]], "extrapolate": True, "clamp_output_min": 0.0, }, "shear_y": { "method": "piecewise_linear", "signed": True, "points": [[0.0, 0.0], [100.0, 9.0]], "extrapolate": True, "clamp_output_min": 0.0, }, }, } path = tmp_path / "v2.json" path.write_text(json.dumps(doc), encoding="utf-8") sensor = Sensor( "external", config_name="box", cal_path=str(path), verbose=False, backend="cpu", ) try: assert sensor.force_calibrated assert sensor.force_unit == "N" sensor._force_raw = (100.0, 100.0, -100.0) sensor._apply_force_filter() fn, fx, fy = sensor.read_info( sensor.info.FNORMAL, sensor.info.FSHEARX, sensor.info.FSHEARY, ) fn_raw, fx_raw, fy_raw = sensor.read_info( sensor.info.FNORMAL_RAW, sensor.info.FSHEARX_RAW, sensor.info.FSHEARY_RAW, ) assert (fn_raw, fx_raw, fy_raw) == pytest.approx((100.0, 100.0, -100.0)) assert (fn, fx, fy) == pytest.approx((5.0, 7.0, -9.0)) finally: sensor.disconnect() def test_set_calibration_swaps_curve(tmp_path: Path): path_a = tmp_path / "a.json" path_b = tmp_path / "b.json" path_a.write_text( json.dumps( { "schema_version": 2, "channels": { "normal": { "method": "piecewise_linear", "points": [[0.0, 0.0], [10.0, 1.0]], } }, } ), encoding="utf-8", ) path_b.write_text( json.dumps( { "schema_version": 2, "channels": { "normal": { "method": "piecewise_linear", "points": [[0.0, 0.0], [10.0, 2.0]], } }, } ), encoding="utf-8", ) sensor = Sensor( "external", config_name="box", cal_path=str(path_a), verbose=False, backend="cpu", ) try: sensor._force_raw = (10.0, 0.0, 0.0) sensor._apply_force_filter() assert sensor.fnormal == pytest.approx(1.0) sensor.set_calibration(path_b) sensor._apply_force_filter() assert sensor.fnormal == pytest.approx(2.0) finally: sensor.disconnect() def test_missing_cal_path_raises(): with pytest.raises(CalibrationError): Sensor( "external", config_name="box", cal_path="D:/no/such/force_calibration.json", verbose=False, backend="cpu", ) def test_parse_shipped_box_file_directly(): cal = parse(json.loads(_box_cal_path().read_text(encoding="utf-8")), source_path=_box_cal_path()) assert cal.schema_version == 1 assert cal.convert(10000.0, 36000.0, -36000.0) == pytest.approx((0.377, 1.19, -1.19))