test: restore calibration regression baseline
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user