244 lines
7.1 KiB
Python
244 lines
7.1 KiB
Python
"""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))
|