完善Phase-A会话级联合优化并修正雷达相位中心高度先验
This commit is contained in:
+29
-14
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user