Files
pose_tracking_exp/tests/test_actual_test_parquet.py
T
crosstyan 0bfeec77e4 feat(tracking): add recursive lifecycle updates and quality diagnostics
Implement the next tracker tranche around a recursive articulated state rather than per-frame ad hoc updates.

Track state now propagates full pose/velocity/shape covariance, uses process noise during prediction, and drives active-to-lost transitions from both miss counts and recursive score thresholds. The multiview update path replaces the generic SciPy least_squares call with a bounded LM/GN loop that returns parameter and beta covariance blocks, accepted-joint counts, mean reprojection error, and iteration diagnostics.

Lost-track handling is stricter and safer: proposal-based reacquisition now requires same-frame 2D support and articulated refinement before a track can return to active. Proposal clusters retain contributing detection indices, the tracker searches broadly within contributing views, and proposal-compatible lost frames are surfaced explicitly instead of silently reviving a track. Old scene JSONs with imgpaths now default to the RPT camera-pose convention so proposal reprojection gating works on the sample scenes.

Add ActualTest support diagnostics that summarize event counts, accepted support, reprojection quality, and tracker diagnostics, plus focused regressions for camera conventions, score-driven demotion, covariance behavior, proposal-compatible lost handling, and broader proposal-backed matching.
2026-03-27 15:36:48 +08:00

189 lines
7.0 KiB
Python

from pathlib import Path
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
from pose_tracking_exp.common.joints import BODY20_INDEX_BY_NAME
from pose_tracking_exp.schema import TrackUpdateEvent, TrackerDiagnostics, TrackedFrameResult
from tests.support.actual_test import (
format_frame_summary_lines,
load_actual_test_scene,
load_actual_test_segment_bundles,
summarize_tracking_results,
)
def _write_parquet(path: Path, rows: list[dict[str, object]]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
pq.write_table(pa.Table.from_pylist(rows), path)
def _sample_rtmpose_detection() -> tuple[list[float], list[list[float]], list[float]]:
keypoints_xy = np.zeros((133, 2), dtype=np.float64)
scores = np.zeros((133,), dtype=np.float64)
keypoints_xy[5] = [10.0, 20.0]
keypoints_xy[6] = [30.0, 20.0]
keypoints_xy[11] = [12.0, 60.0]
keypoints_xy[12] = [28.0, 60.0]
keypoints_xy[0] = [20.0, 8.0]
scores[[0, 5, 6, 11, 12]] = 1.0
return [8.0, 4.0, 32.0, 64.0], keypoints_xy.tolist(), scores.tolist()
def test_load_actual_test_parquet_scene_and_segment(tmp_path: Path) -> None:
root = tmp_path / "ActualTest_WeiHua"
_write_parquet(
root / "camera_params" / "camera_params.parquet",
[
{
"name": "AF_02",
"port": 5602,
"intrinsic": {
"camera_matrix": [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]],
"distortion_coefficients": [0.0, 0.0, 0.0, 0.0, 0.0],
},
"extrinsic": {"rvec": [0.0, 0.0, 0.0], "tvec": [0.0, 0.0, 0.0]},
"resolution": {"width": 640, "height": 480},
},
{
"name": "AF_03",
"port": 5603,
"intrinsic": {
"camera_matrix": [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]],
"distortion_coefficients": [0.0, 0.0, 0.0, 0.0, 0.0],
},
"extrinsic": {"rvec": [0.0, 0.0, 0.0], "tvec": [1.0, 0.0, 0.0]},
"resolution": {"width": 640, "height": 480},
},
],
)
box, keypoints_xy, scores = _sample_rtmpose_detection()
for camera_name in ("5602", "5603"):
_write_parquet(
root / "Segment_1" / f"{camera_name}_detected.parquet",
[
{"frame_index": 689, "boxes": [], "kps": [], "kps_scores": []},
{"frame_index": 690, "boxes": [box], "kps": [keypoints_xy], "kps_scores": [scores]},
],
)
scene = load_actual_test_scene(root)
bundles = load_actual_test_segment_bundles(root, "Segment_1", frame_start=690, max_frames=1)
assert [camera.name for camera in scene.cameras] == ["5602", "5603"]
np.testing.assert_allclose(scene.cameras[0].pose_T, [0.0, 0.0, 0.0])
np.testing.assert_allclose(scene.cameras[1].pose_T, [-1.0, 0.0, 0.0])
assert len(bundles) == 1
assert [view.camera_name for view in bundles[0].views] == ["5602", "5603"]
assert bundles[0].views[0].frame_index == 690
np.testing.assert_allclose(
bundles[0].views[0].detections[0].keypoints[BODY20_INDEX_BY_NAME["hip_middle"], :2],
[20.0, 60.0],
)
def test_load_actual_test_keeps_partial_camera_frames(tmp_path: Path) -> None:
root = tmp_path / "ActualTest_WeiHua"
_write_parquet(
root / "camera_params" / "camera_params.parquet",
[
{
"name": "AF_02",
"port": 5602,
"intrinsic": {
"camera_matrix": [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]],
"distortion_coefficients": [0.0, 0.0, 0.0, 0.0, 0.0],
},
"extrinsic": {"rvec": [0.0, 0.0, 0.0], "tvec": [0.0, 0.0, 0.0]},
"resolution": {"width": 640, "height": 480},
},
{
"name": "AF_03",
"port": 5603,
"intrinsic": {
"camera_matrix": [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]],
"distortion_coefficients": [0.0, 0.0, 0.0, 0.0, 0.0],
},
"extrinsic": {"rvec": [0.0, 0.0, 0.0], "tvec": [1.0, 0.0, 0.0]},
"resolution": {"width": 640, "height": 480},
},
],
)
box, keypoints_xy, scores = _sample_rtmpose_detection()
_write_parquet(
root / "Segment_1" / "5602_detected.parquet",
[
{"frame_index": 690, "boxes": [box], "kps": [keypoints_xy], "kps_scores": [scores]},
{"frame_index": 691, "boxes": [box], "kps": [keypoints_xy], "kps_scores": [scores]},
],
)
_write_parquet(
root / "Segment_1" / "5603_detected.parquet",
[
{"frame_index": 690, "boxes": [box], "kps": [keypoints_xy], "kps_scores": [scores]},
],
)
bundles = load_actual_test_segment_bundles(root, "Segment_1", frame_start=690)
assert [bundle.views[0].frame_index for bundle in bundles] == [690, 691]
assert [view.camera_name for view in bundles[1].views] == ["5602", "5603"]
assert len(bundles[1].views[0].detections) == 1
assert bundles[1].views[1].detections == ()
def test_actual_test_summary_reports_event_counts() -> None:
results = [
TrackedFrameResult(
bundle_index=0,
timestamp_unix_ns=0,
tentative_tracks=(),
active_tracks=(),
lost_tracks=(),
proposals=(),
update_events=(
TrackUpdateEvent(
track_id=1,
action="direct_update",
accepted_view_count=2,
accepted_joint_count=14,
mean_reprojection_error=6.0,
),
),
),
TrackedFrameResult(
bundle_index=1,
timestamp_unix_ns=1,
tentative_tracks=(),
active_tracks=(),
lost_tracks=(),
proposals=(),
update_events=(
TrackUpdateEvent(track_id=1, action="predict_only"),
TrackUpdateEvent(
track_id=1,
action="proposal_compatible",
proposal_view_count=2,
proposal_support_size=3,
mean_reprojection_error=12.0,
),
),
),
]
summary = summarize_tracking_results(
results,
TrackerDiagnostics(promotions=1, proposal_compatible_lost_frames=1),
)
lines = format_frame_summary_lines(results)
assert summary.bundle_count == 2
assert summary.update_action_counts["direct_update"] == 1
assert summary.update_action_counts["proposal_compatible"] == 1
assert summary.mean_accepted_views == 2.0
assert summary.mean_accepted_joints == 14.0
assert summary.mean_reprojection_error == 9.0
assert len(lines) == 2
assert "proposal_compatible" in lines[1]