Files
orisys-app/apps/autocal/tests/test_session_review.py
T
ykcheungab 04adf17f60 完善 Autocal 多测点标定流程及会话复核
统一测点坐标、保持窗口和试验目录,补齐逐点初步分析。定向测试与真实视频闭环通过;两项轮询失败在修改前版本同样复现。
2026-10-11 17:43:16 +08:00

125 lines
5.6 KiB
Python

"""Saved-session review uses frame identity and excludes ramps and invalid data."""
from __future__ import annotations
import csv
import json
import pytest
from orisys_autocal.calibration_session import write_json
from orisys_autocal.session_review import review_session, save_review
def _csv(path, rows):
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
@pytest.fixture
def session(tmp_path):
manifest = {
"format_version": "autocal-session-1.0", "session_id": "test-session",
"status": "incomplete", "uv_definition": {"mapping_sha256": "test-map"},
"sites": [{"site_id": "site-001", "uv_xy_px": [1, -2]},
{"site_id": "site-002", "uv_xy_px": [-3, 4]}],
"trials": [],
}
for i, sensitivity in enumerate((2.0, 4.0), 1):
trial_id, site_id = f"trial-{i:03d}", f"site-{i:03d}"
entry = {"trial_id": trial_id, "site_id": site_id,
"status": "complete", "directory": f"trials/{trial_id}"}
manifest["trials"].append(entry)
path = tmp_path / entry["directory"]
path.mkdir(parents=True)
write_json(path / "session.json", {
"trial_id": trial_id, "site_id": site_id, "session_id": 7,
"calibration_session_id": "test-session", "trial_status": "complete",
"reference_alignment_possible": False, "uv_definition": manifest["uv_definition"],
})
frames, forces, references = [], [], []
for step, (branch, load) in enumerate((
("zero", 0), ("load", 5), ("load", 10), ("unload", 5), ("unload", 0))):
start = (step * 20 + 1) * 1_000_000_000
end = start + 10_000_000_000
references.append({"step_index": step, "branch": branch,
"t_start_host_mono_ns": start, "t_end_host_mono_ns": end,
"F_ref_normal_N": load, "hold_complete": 1, "stability_verified": 0})
raw = 1 + sensitivity * load + (1 if branch == "unload" and load else 0)
for offset, value, valid, dropped in (
(0, raw, 1, 0), (1_000_000_000, raw, 1, 0),
(2_000_000_000, 999, 0, 0), (3_000_000_000, 999, 1, 1),
(10_000_000_000, 999, 1, 0)):
frame_id = len(frames) + 1
frames.append({"frame_id": frame_id, "t_host_mono_ns": start + offset,
"dropped": dropped})
forces.append({"session_id": 7, "frame_id": frame_id, "FN_raw": value, "valid": valid})
_csv(path / "frames.csv", frames)
_csv(path / "forces.csv", list(reversed(forces)))
_csv(path / "reference.csv", references)
manifest["trials"].append({"trial_id": "trial-003", "status": "incomplete"})
write_json(tmp_path / "session.json", manifest)
return tmp_path
def test_review_fits_each_site_without_ramps_invalid_samples_or_unloading_bias(session):
result = review_session(session)
assert result["status"] == "exploratory"
assert result["issues"] == [{"trial_id": "trial-003", "reason": "incomplete_trial"}]
first, second = result["sites"]
assert first["fit"]["gain_n_per_raw"] == pytest.approx(.5)
assert first["fit"]["offset_n"] == pytest.approx(-.5)
assert second["fit"]["sensitivity_raw_per_n"] == pytest.approx(4)
assert all(w["samples"] == 2 and w["raw_std"] == 0 for s in result["sites"] for w in s["windows"])
assert first["windows"][3]["raw_mean"] == 12
assert not first["windows"][0]["reference_alignment_possible"]
assert not first["windows"][0]["stability_verified"]
before = (session / "session.json").read_bytes()
a, b = save_review(result), save_review(result)
assert a != b and a.is_file() and b.is_file()
assert (session / "session.json").read_bytes() == before
def test_no_samples_does_not_produce_a_coefficient(session):
path = session / "trials/trial-001/forces.csv"
_csv(path, [{"session_id": 7, "frame_id": 1, "FN_raw": "", "valid": 0}])
first = review_session(session)["sites"][0]
assert first["fit"] is None
assert all(w["raw_mean"] is None and w["status"] == "insufficient_samples" for w in first["windows"])
def test_review_refuses_mismatched_identity_and_paths_outside_session(session):
path = session / "trials/trial-001/session.json"
document = json.loads(path.read_text(encoding="utf-8"))
document["site_id"] = "wrong-site"
write_json(path, document)
with pytest.raises(ValueError, match="mismatch"):
review_session(session)
manifest_path = session / "session.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["trials"][0]["directory"] = "../outside"
write_json(manifest_path, manifest)
with pytest.raises(ValueError, match="outside"):
review_session(session)
def test_review_dialog_shows_saved_windows_charts_and_new_export(qt_app, session, tmp_path):
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog
dialog = SessionReviewDialog(session)
assert dialog.sites.count() == 2
assert dialog.table.rowCount() == 5
assert len(dialog.chart.series()) == 4
assert len(dialog.uv_chart.series()) == 2
dialog.sites.setCurrentIndex(1)
assert "4" in dialog.summary.text()
dialog.uv_chart.series()[0].clicked.emit(dialog.uv_chart.series()[0].at(0))
assert dialog.sites.currentIndex() == 0
dialog.save_button.click()
assert len(list(session.glob("derived/*/review.json"))) == 1
dialog.show()
qt_app.processEvents()
assert dialog.grab().save(str(tmp_path / "review.png"))
dialog.close()