完善Phase-A会话级联合优化并修正雷达相位中心高度先验

This commit is contained in:
lichun.qu
2026-08-19 09:41:39 +08:00
parent 5ac50ad71f
commit 1233f8aafd
20 changed files with 3621 additions and 140 deletions
+29 -14
View File
@@ -54,7 +54,7 @@ def _log(rotation: np.ndarray) -> np.ndarray:
return so3_log(rotation)
def test_synthetic_pipeline_rotation_and_time_offset(tmp_path: Path):
def test_synthetic_pipeline_rejects_noisy_icp_but_keeps_time_audit(tmp_path: Path):
meta = generate_synthetic_session(tmp_path, delta_t_s=0.17, yaw_extrinsic_deg=25.0)
config = Path(__file__).resolve().parents[1] / "config" / "vehicle_installation.template.yaml"
out = tmp_path / "out"
@@ -74,17 +74,28 @@ def test_synthetic_pipeline_rotation_and_time_offset(tmp_path: Path):
min_pair_rotation_deg=2.0,
min_pair_translation_m=0.05,
)
result = run_calibration(request)
assert result.status.value == "rotation_only_accepted"
assert result.time_offset_s is not None
assert abs(result.time_offset_s - meta["delta_t_s"]) < 0.05
assert result.T_IMU_lidar is not None
# End-to-end uses approximate ICP; allow moderate absolute error but require consistency.
r_true = so3_exp(np.deg2rad(np.array([2.0, -1.5, meta["yaw_extrinsic_deg"]])))
err_deg = np.degrees(np.linalg.norm(_log(r_true.T @ result.T_IMU_lidar[:3, :3])))
assert err_deg < 15.0
progress_events: list[dict] = []
result = run_calibration(request, progress_callback=progress_events.append)
# The lightweight synthetic point cloud uses approximate ICP and has a
# roughly 3-degree P95 residual. The production gate must reject it rather
# than expose a plausible-looking extrinsic.
assert result.status.value == "blocked"
assert result.T_IMU_lidar is None
session0 = result.details["sessions"][0]
assert session0["handeye"]["residual_rms_deg"] < 5.0
assert abs(session0["time_offset_s"] - meta["delta_t_s"]) < 0.05
assert result.details["joint_handeye"]["residual_p95_deg"] > 1.5
assert not result.details["joint_handeye"]["ok"]
assert progress_events[0]["event"] == "pipeline_start"
assert any(
event["stage"] == "motion_pairs" and event["event"] == "complete"
for event in progress_events
)
assert any(
event["stage"] == "joint_optimizer" and event["event"] == "phase_a_complete"
for event in progress_events
)
assert progress_events[-1]["stage"] == "finalize"
assert progress_events[-1]["event"] == "complete"
def test_time_offset_on_synthetic(tmp_path: Path):
@@ -176,13 +187,17 @@ def test_synthetic_pipeline_full_se3_smoke(tmp_path: Path):
"full_se3_accepted",
"full_se3_rejected_due_to_observability",
"rotation_only_accepted",
"blocked",
}
assert result.T_IMU_lidar is not None
session0 = result.details["sessions"][0]
assert "delta_v" in session0.get("pair_notes", []) or session0.get("pair_count", 0) >= 0
# Phase-C fields appear when joint ran successfully on pairs.
if session0.get("ok"):
# Phase-C fields appear only when the strict rotation gate passed.
if result.status.value != "blocked":
assert result.T_IMU_lidar is not None
assert "gyro_bias_rad_s" in session0["joint"]
else:
assert result.T_IMU_lidar is None
assert not result.details["joint_handeye"]["ok"]
def test_signed_time_offset_refine_improves_or_keeps(tmp_path: Path):