added calibration data handling functions

This commit is contained in:
ykcheungab
2026-09-10 21:47:36 +08:00
parent 9cbba08d68
commit 96f4428230
13 changed files with 1399 additions and 175 deletions
+243
View File
@@ -0,0 +1,243 @@
"""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))
+48
View File
@@ -0,0 +1,48 @@
"""Tests for sensor-bundle export helpers."""
from __future__ import annotations
from pathlib import Path
import pytest
from orisys.configs import (
export_sensor_bundle,
export_sensor_bundles,
list_sensor_bundles,
resolve_sensor_bundle,
)
def test_export_sensor_bundle_json_only(tmp_path: Path) -> None:
assert resolve_sensor_bundle("box") is not None
dest = export_sensor_bundle("box", tmp_path, include_assets=False)
assert dest == tmp_path / "box"
assert (dest / "sensor.json").is_file()
assert (dest / "force_calibration.json").is_file()
assert not (dest / "assets").exists()
def test_export_sensor_bundles_default_names(tmp_path: Path) -> None:
available = list_sensor_bundles()
assert "box" in available
exported = export_sensor_bundles(tmp_path, include_assets=False)
names = {path.name for path in exported}
assert "box" in names
assert "mini" in names
for path in exported:
assert (path / "sensor.json").is_file()
assert not (path / "assets").exists()
def test_export_sensor_bundle_no_overwrite(tmp_path: Path) -> None:
export_sensor_bundle("box", tmp_path, overwrite=True)
with pytest.raises(FileExistsError):
export_sensor_bundle("box", tmp_path, overwrite=False)
def test_export_sensor_bundle_missing_name(tmp_path: Path) -> None:
with pytest.raises(FileNotFoundError):
export_sensor_bundle("not-a-real-sensor", tmp_path)
+3 -2
View File
@@ -224,12 +224,13 @@ def test_box_read_info_applies_force_calibration_json():
def test_read_info_force_stays_raw_without_calibration_json(monkeypatch):
monkeypatch.setattr(
"orisys.sensor.configs.load_force_calibration",
lambda _name: None,
"orisys.calibration.load",
lambda *args, **kwargs: None,
)
sensor = Sensor("external", config_name="box", verbose=False, backend="cpu")
try:
assert sensor._force_cal_sensor_name is None
assert sensor.calibration is None
sensor._force_raw = (111.0, -22.0, 33.0)
sensor._apply_force_filter()
fn, fx, fy = sensor.read_info(