test: restore calibration regression baseline

This commit is contained in:
lxp
2026-08-31 17:58:11 +08:00
parent 286581bcba
commit 06c050e446
@@ -60,8 +60,7 @@ from g20_thumb_apriltag_calibration.pnp import SquareTagPose
from g20_thumb_apriltag_calibration.urdf_zero import (
RIGHT_19_MECHANICAL_ENDPOINT_JOINTS,
JointAxisMeasurement,
axis_line_cycle_rms_m,
cross_view_side_line_source,
axis_line_uses_depth_free_interpretation_plane,
get_zero_calibration_profile,
)
@@ -142,6 +141,7 @@ def test_finalize_publishes_the_frozen_validated_endpoint_state(
)
node = SimpleNamespace(
zero_result=zero_result,
standalone_thumb_calibration=False,
validated_endpoint_zero_offsets_rad=dict(endpoint_offsets),
validation_errors_rad=[],
combination_validation_enabled=False,
@@ -4079,6 +4079,20 @@ def _cross_view_roll_node(
"pinky_mcp_roll_side": side,
}
node.raw_path = tmp_path / "raw_samples.jsonl"
# The production node always owns validated camera extrinsics. Keep the
# synthetic camera centre away from the synthetic axis origin so the
# interpretation-plane geometry is well defined as it is on real data.
node.extrinsics = SimpleNamespace(
transform=lambda _view: np.asarray(
[
[1.0, 0.0, 0.0, 0.1],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
dtype=float,
)
)
return node
@@ -4092,8 +4106,12 @@ def test_cross_view_roll_axis_disagreement_skips_fusion(tmp_path) -> None:
# and record a diagnostic instead of failing the joint.
assert measurement.axis_direction_source != "cross_view_weighted_fusion"
assert measurement.pose_axis_line_source_joints == (
"pinky_mcp_roll_side",
"pinky_mcp_roll",
)
assert measurement.axis_point_source == (
"front_interpretation_plane_cross_view_validated"
)
assert axis_line_uses_depth_free_interpretation_plane(measurement)
diagnostics = [
json.loads(line)
for line in node.raw_path.read_text().splitlines()
@@ -4105,7 +4123,7 @@ def test_cross_view_roll_axis_disagreement_skips_fusion(tmp_path) -> None:
assert 10.0 < diagnostics[-1]["axis_difference_deg"] < 12.5
def test_cross_view_axis_group_uses_one_physical_radius(tmp_path) -> None:
def test_cross_view_depth_free_axis_group_is_not_radius_refitted(tmp_path) -> None:
node = _cross_view_roll_node(tmp_path, side_tilt_deg=11.0)
measurements = [
node._fit_axis_measurement("pinky_mcp_roll", cycle)
@@ -4116,20 +4134,14 @@ def test_cross_view_axis_group_uses_one_physical_radius(tmp_path) -> None:
node, "pinky_mcp_roll", measurements
)
assert refined == measurements
assert all(
measurement.axis_point_source
== "side_circle_shared_radius_cross_view"
axis_line_uses_depth_free_interpretation_plane(measurement)
for measurement in refined
)
assert all(
cross_view_side_line_source(measurement)
== "pinky_mcp_roll_side"
for measurement in refined
)
assert axis_line_cycle_rms_m(refined) < 1.0e-6
def test_cross_view_side_line_failure_rescans_only_side_alias(
def test_cross_view_depth_free_axis_skips_point_repeatability_rescan(
tmp_path, monkeypatch
) -> None:
node = _cross_view_roll_node(tmp_path, side_tilt_deg=11.0)
@@ -4147,18 +4159,14 @@ def test_cross_view_side_line_failure_rescans_only_side_alias(
return measurement
node._fit_axis_measurement = displaced_side_line
# This test isolates the downstream repeatability gate. The production
# shared-radius refit legitimately removes an artificial point-only
# displacement that is absent from the underlying trajectory records.
monkeypatch.setattr(
G20ThreeCameraCalibrationNode,
"_refit_cross_view_axis_line_group",
lambda _self, _name, measurements: list(measurements),
)
# A depth-free interpretation plane does not define one Euclidean line
# point, so point-only cycle RMS must not trigger a misleading rescan.
monkeypatch.setitem(
G20ThreeCameraCalibrationNode._provisional_fit_failures.__globals__,
"_isolated_axis_line_cycle_outliers",
lambda measurements, limit_m: {3},
lambda measurements, limit_m: pytest.fail(
"depth-free axes must not enter the line-point outlier gate"
),
)
spec = next(
item
@@ -4175,26 +4183,10 @@ def test_cross_view_side_line_failure_rescans_only_side_alias(
if failure["metric"] == "axis_line_cycle_rms_mm"
]
assert line_failures
assert {
failure["joint"] for failure in line_failures
} == {"pinky_mcp_roll_side"}
assert all(
failure["model_joint"] == "pinky_mcp_roll"
and failure["quality_source_joints"] == ["pinky_mcp_roll_side"]
for failure in line_failures
)
assert all(failure["cycle"] == 4 for failure in line_failures)
assert all(
failure["inlier_cycles"] == [1, 2, 3]
for failure in line_failures
)
assert _fit_retry_joint_names(
RIGHT_19_HAND_PROFILE, spec, line_failures
) == {"pinky_mcp_roll_side"}
assert not line_failures
def test_cross_view_side_pose_residual_uses_line_repeatability_gate(
def test_cross_view_front_pose_residual_remains_a_hard_gate(
tmp_path,
) -> None:
node = _cross_view_roll_node(tmp_path, side_tilt_deg=11.0)
@@ -4217,11 +4209,21 @@ def test_cross_view_side_pose_residual_uses_line_repeatability_gate(
node, spec, include_view_validity=False
)
pose_failures = [
failure
for failure in failures
if failure["metric"] == "axis_pose_line_rms_mm"
]
assert pose_failures
assert all(
failure["joint"] == "pinky_mcp_roll"
and failure["quality_source_joints"] == ["pinky_mcp_roll"]
for failure in pose_failures
)
assert not [
failure
for failure in failures
if failure["metric"]
in {"axis_pose_line_rms_mm", "axis_line_cycle_rms_mm"}
if failure["metric"] == "axis_line_cycle_rms_mm"
]
@@ -4316,7 +4318,10 @@ def test_cross_view_roll_axis_agreement_keeps_published_primary(tmp_path) -> Non
assert measurement.axis_direction_source == "rotation"
assert measurement.pose_axis_line_source_joints == (
"pinky_mcp_roll_side",
"pinky_mcp_roll",
)
assert measurement.axis_point_source == (
"front_interpretation_plane_cross_view_validated"
)
@@ -4348,6 +4353,17 @@ def test_cross_view_roll_side_pose_never_replaces_primary_direction(
zero_maximum_axis_cone_mismatch_rad=math.radians(5.0),
axis_maximum_pose_line_rms_m=0.001,
cross_view_roll_maximum_axis_difference_rad=math.radians(15.0),
extrinsics=SimpleNamespace(
transform=lambda _view: np.asarray(
[
[1.0, 0.0, 0.0, 0.1],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
dtype=float,
)
),
)
primary_degrees = (8.0078, 8.1710, 8.0712, 8.0236)
secondary_degrees = (4.9043, 4.9136, 5.0374, 4.9389)
@@ -5398,10 +5414,9 @@ def test_live_task_still_uses_window_when_not_resumed() -> None:
]
def test_cross_view_skip_keeps_front_direction_with_side_line(tmp_path) -> None:
# The front roll-link centre trajectory carries the ~21 mm screw
# translation, so on a fusion skip the axis LINE must come from the
# side PIP-link circle while the direction stays front-only.
def test_cross_view_skip_keeps_front_depth_free_geometry(tmp_path) -> None:
# The side PIP-link pose validates the observation, but its planar-pose
# depth and orientation bias must not replace the trusted front geometry.
node = _cross_view_roll_node(
tmp_path,
side_tilt_deg=11.0,
@@ -5411,23 +5426,19 @@ def test_cross_view_skip_keeps_front_direction_with_side_line(tmp_path) -> None:
measurement = node._fit_axis_measurement("pinky_mcp_roll", 0)
assert measurement.axis_direction_source != "cross_view_weighted_fusion"
assert measurement.axis_point_source == "side_circle_cross_view"
side_point = np.asarray(
node.records_by_joint["pinky_mcp_roll_side"][0][
"parent_pose_common"
]["translation_xyz_m"],
dtype=float,
)
measured_point = np.asarray(
measurement.point_common_xyz_m, dtype=float
assert measurement.axis_point_source == (
"front_interpretation_plane_cross_view_validated"
)
assert measurement.pose_axis_line_source_joints == ("pinky_mcp_roll",)
assert axis_line_uses_depth_free_interpretation_plane(measurement)
front_direction = np.asarray(
measurement.axis_common_xyz, dtype=float
)
# The published line keeps the front direction and takes the side
# view's line position (offset ~21 mm by construction), instead of the
# front link's own displaced line.
assert float(np.dot(front_direction, [0.0, 0.0, 1.0])) > math.cos(
math.radians(2.0)
)
assert 0.015 < float(measured_point[0]) < 0.027
assert measurement.axis_point_camera_center_common_xyz_m is not None
assert (
measurement.axis_point_interpretation_plane_normal_common_xyz
is not None
)