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.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
@@ -8,12 +10,92 @@ from beartype import beartype
|
||||
from loguru import logger
|
||||
|
||||
from pose_tracking_exp.common.normalization import infer_bbox_from_keypoints, normalize_rtmpose_body20
|
||||
from pose_tracking_exp.schema import CameraCalibration, CameraFrame, FrameBundle, PoseDetection, SceneConfig, TrackerConfig
|
||||
from pose_tracking_exp.schema import (
|
||||
CameraCalibration,
|
||||
CameraFrame,
|
||||
FrameBundle,
|
||||
PoseDetection,
|
||||
SceneConfig,
|
||||
TrackerConfig,
|
||||
TrackerDiagnostics,
|
||||
TrackedFrameResult,
|
||||
)
|
||||
from pose_tracking_exp.tracking import PoseTracker
|
||||
|
||||
_NOMINAL_FRAME_PERIOD_NS = 33_333_333
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ActualTestTrackingSummary:
|
||||
bundle_count: int
|
||||
active_frames: int
|
||||
proposal_frames: int
|
||||
max_active_tracks: int
|
||||
max_lost_tracks: int
|
||||
update_action_counts: dict[str, int]
|
||||
mean_accepted_views: float
|
||||
mean_accepted_joints: float
|
||||
mean_reprojection_error: float
|
||||
diagnostics: TrackerDiagnostics
|
||||
|
||||
|
||||
def _finite_mean(values: list[float]) -> float:
|
||||
finite = [value for value in values if np.isfinite(value)]
|
||||
if not finite:
|
||||
return np.inf
|
||||
return float(np.mean(np.asarray(finite, dtype=np.float64)))
|
||||
|
||||
|
||||
@beartype
|
||||
def summarize_tracking_results(
|
||||
results: list[TrackedFrameResult],
|
||||
diagnostics: TrackerDiagnostics,
|
||||
) -> ActualTestTrackingSummary:
|
||||
update_events = [event for result in results for event in result.update_events]
|
||||
action_counts = Counter(event.action for event in update_events)
|
||||
accepted_view_samples = [float(event.accepted_view_count) for event in update_events if event.accepted_view_count > 0]
|
||||
accepted_joint_samples = [float(event.accepted_joint_count) for event in update_events if event.accepted_joint_count > 0]
|
||||
reprojection_samples = [float(event.mean_reprojection_error) for event in update_events]
|
||||
return ActualTestTrackingSummary(
|
||||
bundle_count=len(results),
|
||||
active_frames=sum(1 for result in results if result.active_tracks),
|
||||
proposal_frames=sum(1 for result in results if result.proposals),
|
||||
max_active_tracks=max((len(result.active_tracks) for result in results), default=0),
|
||||
max_lost_tracks=max((len(result.lost_tracks) for result in results), default=0),
|
||||
update_action_counts=dict(action_counts),
|
||||
mean_accepted_views=_finite_mean(accepted_view_samples),
|
||||
mean_accepted_joints=_finite_mean(accepted_joint_samples),
|
||||
mean_reprojection_error=_finite_mean(reprojection_samples),
|
||||
diagnostics=diagnostics,
|
||||
)
|
||||
|
||||
|
||||
@beartype
|
||||
def format_frame_summary_lines(results: list[TrackedFrameResult]) -> tuple[str, ...]:
|
||||
lines: list[str] = []
|
||||
for result in results:
|
||||
action_counts = Counter(event.action for event in result.update_events)
|
||||
finite_reprojection_errors = [
|
||||
float(event.mean_reprojection_error)
|
||||
for event in result.update_events
|
||||
if np.isfinite(event.mean_reprojection_error)
|
||||
]
|
||||
lines.append(
|
||||
"bundle={} proposals={} active_ids={} lost_ids={} tentative_ids={} actions={} mean_event_reproj={}".format(
|
||||
result.bundle_index,
|
||||
len(result.proposals),
|
||||
[track.track_id for track in result.active_tracks],
|
||||
[track.track_id for track in result.lost_tracks],
|
||||
[track.track_id for track in result.tentative_tracks],
|
||||
dict(action_counts),
|
||||
"{:.2f}".format(float(np.mean(np.asarray(finite_reprojection_errors, dtype=np.float64))))
|
||||
if finite_reprojection_errors
|
||||
else "nan",
|
||||
)
|
||||
)
|
||||
return tuple(lines)
|
||||
|
||||
|
||||
@beartype
|
||||
def load_actual_test_scene(root: Path) -> SceneConfig:
|
||||
# ActualTest parquet comes from the ChArUco/OpenCV side, so `rvec` / `tvec`
|
||||
@@ -148,6 +230,7 @@ def load_actual_test_segment_bundles(
|
||||
@click.option("--max-frames", type=click.IntRange(min=1))
|
||||
@click.option("--min-camera-rows", default=1, type=click.IntRange(min=1), show_default=True)
|
||||
@click.option("--max-active-tracks", default=1, type=click.IntRange(min=1), show_default=True)
|
||||
@click.option("--verbose-frames/--no-verbose-frames", default=False, show_default=True)
|
||||
def main(
|
||||
root_path: Path,
|
||||
segment_name: str,
|
||||
@@ -156,6 +239,7 @@ def main(
|
||||
max_frames: int | None,
|
||||
min_camera_rows: int,
|
||||
max_active_tracks: int,
|
||||
verbose_frames: bool,
|
||||
) -> None:
|
||||
logger.remove()
|
||||
logger.add(
|
||||
@@ -174,12 +258,34 @@ def main(
|
||||
)
|
||||
tracker = PoseTracker(scene, TrackerConfig(max_active_tracks=max_active_tracks))
|
||||
results = tracker.run(bundles)
|
||||
summary = summarize_tracking_results(results, tracker.diagnostics_snapshot())
|
||||
logger.info(
|
||||
"actual_test bundles={} active_frames={} proposal_frames={}",
|
||||
len(results),
|
||||
sum(1 for result in results if result.active_tracks),
|
||||
sum(1 for result in results if result.proposals),
|
||||
"actual_test bundles={} active_frames={} proposal_frames={} max_active_tracks={} max_lost_tracks={} "
|
||||
"mean_accepted_views={} mean_accepted_joints={} mean_reprojection_error={}",
|
||||
summary.bundle_count,
|
||||
summary.active_frames,
|
||||
summary.proposal_frames,
|
||||
summary.max_active_tracks,
|
||||
summary.max_lost_tracks,
|
||||
"{:.2f}".format(summary.mean_accepted_views) if np.isfinite(summary.mean_accepted_views) else "nan",
|
||||
"{:.2f}".format(summary.mean_accepted_joints) if np.isfinite(summary.mean_accepted_joints) else "nan",
|
||||
"{:.2f}".format(summary.mean_reprojection_error) if np.isfinite(summary.mean_reprojection_error) else "nan",
|
||||
)
|
||||
logger.info(
|
||||
"actual_test actions={} promotions={} reacquisitions={} predict_only_updates={} proposal_reacquisition_attempts={} "
|
||||
"proposal_compatible_lost_frames={} nonlinear_refinements={} lm_iterations={}",
|
||||
summary.update_action_counts,
|
||||
summary.diagnostics.promotions,
|
||||
summary.diagnostics.reacquisitions,
|
||||
summary.diagnostics.predict_only_updates,
|
||||
summary.diagnostics.proposal_reacquisition_attempts,
|
||||
summary.diagnostics.proposal_compatible_lost_frames,
|
||||
summary.diagnostics.nonlinear_refinements,
|
||||
summary.diagnostics.lm_iterations,
|
||||
)
|
||||
if verbose_frames:
|
||||
for line in format_frame_summary_lines(results):
|
||||
logger.info("actual_test_frame {}", line)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,7 +5,13 @@ import pyarrow as pa
|
||||
import pyarrow.parquet as pq
|
||||
|
||||
from pose_tracking_exp.common.joints import BODY20_INDEX_BY_NAME
|
||||
from tests.support.actual_test import load_actual_test_scene, load_actual_test_segment_bundles
|
||||
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:
|
||||
@@ -125,3 +131,58 @@ def test_load_actual_test_keeps_partial_camera_frames(tmp_path: Path) -> None:
|
||||
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]
|
||||
|
||||
@@ -105,6 +105,32 @@ def test_load_scene_file_supports_explicit_rpt_pose(tmp_path: Path) -> None:
|
||||
np.testing.assert_allclose(scene.cameras[0].T, [-1.0, -2.0, -3.0])
|
||||
|
||||
|
||||
def test_load_scene_file_defaults_imgpaths_payloads_to_rpt_pose(tmp_path: Path) -> None:
|
||||
scene_path = tmp_path / "scene.json"
|
||||
payload = {
|
||||
"imgpaths": ["/tmp/cam0.jpg"],
|
||||
"room_size": [6.0, 4.0, 3.0],
|
||||
"room_center": [0.0, 0.0, 1.0],
|
||||
"cameras": [
|
||||
{
|
||||
"name": "cam0",
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"K": [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]],
|
||||
"DC": [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
"R": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
|
||||
"T": [[1.0], [2.0], [3.0]],
|
||||
}
|
||||
],
|
||||
}
|
||||
scene_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
scene = load_scene_file(scene_path)
|
||||
|
||||
np.testing.assert_allclose(scene.cameras[0].pose_T, [1.0, 2.0, 3.0])
|
||||
np.testing.assert_allclose(scene.cameras[0].T, [-1.0, -2.0, -3.0])
|
||||
|
||||
|
||||
def test_build_rpt_config_uses_pose_convention(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
args = _camera_args()
|
||||
camera = CameraCalibration.from_opencv_extrinsics(
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("rpt")
|
||||
|
||||
from pose_tracking_exp.common.camera_math import project_pose
|
||||
from pose_tracking_exp.common.joints import BODY20_INDEX_BY_NAME
|
||||
from pose_tracking_exp.schema import CameraCalibration, CameraFrame, FrameBundle, ProposalCluster, SceneConfig, TrackerConfig
|
||||
from pose_tracking_exp.tracking import PoseTracker
|
||||
from pose_tracking_exp.schema import (
|
||||
ActiveTrackState,
|
||||
CameraCalibration,
|
||||
CameraFrame,
|
||||
FrameBundle,
|
||||
PoseDetection,
|
||||
ProposalCluster,
|
||||
SceneConfig,
|
||||
TRACK_COVARIANCE_DIMENSION,
|
||||
TrackerConfig,
|
||||
)
|
||||
from pose_tracking_exp.tracking import PoseTracker, seed_state_from_pose3d
|
||||
|
||||
|
||||
def _make_scene() -> SceneConfig:
|
||||
@@ -89,6 +101,26 @@ def _make_proposal(root_x: float, *, score: float = 1.0) -> ProposalCluster:
|
||||
source_views=frozenset({"cam0", "cam1"}),
|
||||
support_size=2,
|
||||
mean_score=score,
|
||||
support_detection_indices={"cam0": (0,), "cam1": (0,)},
|
||||
)
|
||||
|
||||
|
||||
def _fake_detection() -> PoseDetection:
|
||||
return PoseDetection(
|
||||
bbox=np.asarray([0.0, 0.0, 1.0, 1.0], dtype=np.float64),
|
||||
bbox_confidence=1.0,
|
||||
keypoints=np.zeros((20, 3), dtype=np.float64),
|
||||
)
|
||||
|
||||
|
||||
def _detection_from_projection(projected: np.ndarray, *, confidence: float = 1.0) -> PoseDetection:
|
||||
keypoints = np.zeros((20, 3), dtype=np.float64)
|
||||
keypoints[:, :2] = projected[:, :2]
|
||||
keypoints[:, 2] = confidence
|
||||
return PoseDetection(
|
||||
bbox=np.asarray([0.0, 0.0, 1.0, 1.0], dtype=np.float64),
|
||||
bbox_confidence=confidence,
|
||||
keypoints=keypoints,
|
||||
)
|
||||
|
||||
|
||||
@@ -147,6 +179,32 @@ def test_single_person_mode_reuses_lost_track_id(monkeypatch) -> None:
|
||||
"_build_proposals",
|
||||
lambda bundle, unmatched: proposals_by_bundle[bundle.bundle_index],
|
||||
)
|
||||
fake_detection = _fake_detection()
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_proposal_support_matches",
|
||||
lambda bundle, track, proposal, seeded_state: {"cam0": fake_detection, "cam1": fake_detection},
|
||||
)
|
||||
update_result = SimpleNamespace(
|
||||
state=seed_state_from_pose3d(_make_proposal(0.05, score=0.96).pose3d),
|
||||
parameter_covariance=np.eye(31, dtype=np.float64) * 0.1,
|
||||
beta_covariance=np.eye(8, dtype=np.float64) * 0.01,
|
||||
accepted_joint_masks={"cam0": np.ones((20,), dtype=bool), "cam1": np.ones((20,), dtype=bool)},
|
||||
accepted_joint_counts_by_view={"cam0": 20, "cam1": 20},
|
||||
accepted_joint_count=20,
|
||||
accepted_view_count=2,
|
||||
mean_reprojection_error=5.0,
|
||||
lm_iterations=2,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_refine_track_state",
|
||||
lambda track, predicted_state, matched: (
|
||||
update_result,
|
||||
np.full((20,), 9.0, dtype=np.float64),
|
||||
{"cam0": np.full((20,), 9.0, dtype=np.float64), "cam1": np.full((20,), 9.0, dtype=np.float64)},
|
||||
),
|
||||
)
|
||||
|
||||
results = tracker.run([_make_bundle(0), _make_bundle(1), _make_bundle(2)])
|
||||
|
||||
@@ -154,3 +212,256 @@ def test_single_person_mode_reuses_lost_track_id(monkeypatch) -> None:
|
||||
assert [track.track_id for track in results[1].lost_tracks] == [1]
|
||||
assert [track.track_id for track in results[2].active_tracks] == [1]
|
||||
assert tracker.diagnostics_snapshot().reacquisitions >= 1
|
||||
|
||||
|
||||
def test_active_track_is_not_reseeded_from_proposals(monkeypatch) -> None:
|
||||
tracker = PoseTracker(
|
||||
_make_scene(),
|
||||
TrackerConfig(
|
||||
max_active_tracks=1,
|
||||
tentative_min_age=1,
|
||||
tentative_hits_required=1,
|
||||
tentative_promote_score=0.0,
|
||||
active_miss_to_lost=3,
|
||||
proposal_min_score=0.5,
|
||||
),
|
||||
)
|
||||
proposals_by_bundle = {
|
||||
0: (_make_proposal(0.0, score=0.95),),
|
||||
1: (_make_proposal(0.8, score=0.99),),
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_build_proposals",
|
||||
lambda bundle, unmatched: proposals_by_bundle[bundle.bundle_index],
|
||||
)
|
||||
|
||||
results = tracker.run([_make_bundle(0), _make_bundle(1)])
|
||||
|
||||
assert [track.track_id for track in results[1].active_tracks] == [1]
|
||||
active_track = results[1].active_tracks[0]
|
||||
assert active_track.last_update_kind == "predict_only"
|
||||
assert abs(float(active_track.skeleton.pose3d[BODY20_INDEX_BY_NAME["hip_middle"], 0])) < 0.2
|
||||
assert not any(event.action == "proposal_reacquire" for event in results[1].update_events)
|
||||
|
||||
|
||||
def test_lost_track_deleted_by_covariance_trace() -> None:
|
||||
tracker = PoseTracker(_make_scene(), TrackerConfig(max_active_tracks=1, lost_covariance_trace_max=10.0))
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
tracker._lost[1] = ActiveTrackState(
|
||||
track_id=1,
|
||||
status="lost",
|
||||
lost_age=1,
|
||||
skeleton=seed_state_from_pose3d(proposal.pose3d),
|
||||
covariance=np.eye(TRACK_COVARIANCE_DIMENSION, dtype=np.float64) * 1_000.0,
|
||||
)
|
||||
|
||||
result = tracker.step(_make_bundle(0))
|
||||
|
||||
assert not result.lost_tracks
|
||||
assert any(event.action == "deleted_lost" for event in result.update_events)
|
||||
|
||||
|
||||
def test_track_beta_freezes_after_grace_update(monkeypatch) -> None:
|
||||
tracker = PoseTracker(_make_scene(), TrackerConfig(max_active_tracks=1, beta_grace_frames=1))
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
skeleton = seed_state_from_pose3d(proposal.pose3d)
|
||||
tracker._active[1] = ActiveTrackState(track_id=1, status="active", skeleton=skeleton, score=1.0)
|
||||
fake_detection = PoseDetection(
|
||||
bbox=np.asarray([0.0, 0.0, 1.0, 1.0], dtype=np.float64),
|
||||
bbox_confidence=1.0,
|
||||
keypoints=np.zeros((20, 3), dtype=np.float64),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_match_existing_tracks",
|
||||
lambda bundle, predicted: ({1: {"cam0": fake_detection, "cam1": fake_detection}}, {"cam0": [], "cam1": []}),
|
||||
)
|
||||
|
||||
updated_state = seed_state_from_pose3d(proposal.pose3d, beta=np.full((8,), 1.1, dtype=np.float64))
|
||||
update_result = SimpleNamespace(
|
||||
state=updated_state,
|
||||
parameter_covariance=np.eye(31, dtype=np.float64) * 0.1,
|
||||
beta_covariance=np.eye(8, dtype=np.float64) * 0.01,
|
||||
accepted_joint_masks={"cam0": np.ones((20,), dtype=bool), "cam1": np.ones((20,), dtype=bool)},
|
||||
accepted_joint_counts_by_view={"cam0": 20, "cam1": 20},
|
||||
accepted_joint_count=20,
|
||||
accepted_view_count=2,
|
||||
mean_reprojection_error=4.0,
|
||||
lm_iterations=1,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_refine_track_state",
|
||||
lambda track, predicted_state, matched: (
|
||||
update_result,
|
||||
np.full((20,), 9.0, dtype=np.float64),
|
||||
{"cam0": np.full((20,), 9.0, dtype=np.float64), "cam1": np.full((20,), 9.0, dtype=np.float64)},
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(tracker, "_build_proposals", lambda bundle, unmatched: ())
|
||||
|
||||
result = tracker.step(_make_bundle(0))
|
||||
|
||||
assert result.active_tracks[0].beta_frozen
|
||||
np.testing.assert_allclose(result.active_tracks[0].skeleton.beta, np.full((8,), 1.1, dtype=np.float64))
|
||||
|
||||
|
||||
def test_active_track_demotes_to_lost_on_score_floor() -> None:
|
||||
tracker = PoseTracker(
|
||||
_make_scene(),
|
||||
TrackerConfig(max_active_tracks=1, active_miss_to_lost=10, active_score_lost_threshold=0.0),
|
||||
)
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
tracker._active[1] = ActiveTrackState(
|
||||
track_id=1,
|
||||
status="active",
|
||||
score=0.1,
|
||||
skeleton=seed_state_from_pose3d(proposal.pose3d),
|
||||
covariance=np.eye(TRACK_COVARIANCE_DIMENSION, dtype=np.float64),
|
||||
)
|
||||
|
||||
result = tracker.step(_make_bundle(0))
|
||||
|
||||
assert not result.active_tracks
|
||||
assert [track.track_id for track in result.lost_tracks] == [1]
|
||||
|
||||
|
||||
def test_proposal_compatible_lost_track_stays_lost_without_enough_support(monkeypatch) -> None:
|
||||
tracker = PoseTracker(
|
||||
_make_scene(),
|
||||
TrackerConfig(max_active_tracks=1, active_miss_to_lost=1, lost_delete_age=10),
|
||||
)
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
tracker._lost[1] = ActiveTrackState(
|
||||
track_id=1,
|
||||
status="lost",
|
||||
lost_age=1,
|
||||
score=1.0,
|
||||
skeleton=seed_state_from_pose3d(proposal.pose3d),
|
||||
covariance=np.eye(TRACK_COVARIANCE_DIMENSION, dtype=np.float64),
|
||||
)
|
||||
monkeypatch.setattr(tracker, "_build_proposals", lambda bundle, unmatched: (proposal,))
|
||||
monkeypatch.setattr(tracker, "_proposal_support_matches", lambda bundle, track, proposal, seeded_state: {"cam0": _fake_detection()})
|
||||
|
||||
result = tracker.step(_make_bundle(0))
|
||||
|
||||
assert not result.active_tracks
|
||||
assert [track.track_id for track in result.lost_tracks] == [1]
|
||||
assert any(event.action == "proposal_compatible" for event in result.update_events)
|
||||
|
||||
|
||||
def test_proposal_support_matches_search_all_view_detections() -> None:
|
||||
scene = _make_scene()
|
||||
tracker = PoseTracker(_make_scene(), TrackerConfig(max_active_tracks=1, lost_min_accepted_core_joints=2))
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
track = ActiveTrackState(track_id=1, status="lost", skeleton=seed_state_from_pose3d(proposal.pose3d))
|
||||
seeded_state = seed_state_from_pose3d(proposal.pose3d)
|
||||
projected_cam0 = project_pose(scene.cameras[0], seeded_state.pose3d)
|
||||
projected_cam1 = project_pose(scene.cameras[1], seeded_state.pose3d)
|
||||
good_cam0 = _detection_from_projection(projected_cam0)
|
||||
good_cam1 = _detection_from_projection(projected_cam1)
|
||||
bad_detection = _fake_detection()
|
||||
bundle = FrameBundle(
|
||||
bundle_index=0,
|
||||
timestamp_unix_ns=0,
|
||||
views=(
|
||||
CameraFrame(
|
||||
camera_name="cam0",
|
||||
frame_index=0,
|
||||
timestamp_unix_ns=0,
|
||||
detections=(bad_detection, good_cam0),
|
||||
source_size=(640, 480),
|
||||
),
|
||||
CameraFrame(
|
||||
camera_name="cam1",
|
||||
frame_index=0,
|
||||
timestamp_unix_ns=0,
|
||||
detections=(bad_detection, good_cam1),
|
||||
source_size=(640, 480),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
matched = tracker._proposal_support_matches(bundle, track, proposal, seeded_state)
|
||||
|
||||
assert matched["cam0"] is good_cam0
|
||||
assert matched["cam1"] is good_cam1
|
||||
|
||||
|
||||
def test_covariance_grows_on_predict_only_and_shrinks_on_update(monkeypatch) -> None:
|
||||
tracker = PoseTracker(_make_scene(), TrackerConfig(max_active_tracks=1, active_miss_to_lost=10))
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
tracker._active[1] = ActiveTrackState(
|
||||
track_id=1,
|
||||
status="active",
|
||||
score=1.0,
|
||||
skeleton=seed_state_from_pose3d(proposal.pose3d),
|
||||
covariance=np.eye(TRACK_COVARIANCE_DIMENSION, dtype=np.float64),
|
||||
)
|
||||
no_detection_bundle = _make_bundle(0)
|
||||
predict_only_result = tracker.step(no_detection_bundle)
|
||||
predict_only_cov_trace = float(np.trace(predict_only_result.active_tracks[0].covariance))
|
||||
|
||||
fake_detection = _fake_detection()
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_match_existing_tracks",
|
||||
lambda bundle, predicted: ({1: {"cam0": fake_detection, "cam1": fake_detection}}, {"cam0": [], "cam1": []}),
|
||||
)
|
||||
update_result = SimpleNamespace(
|
||||
state=seed_state_from_pose3d(proposal.pose3d, beta=np.ones((8,), dtype=np.float64)),
|
||||
parameter_covariance=np.eye(31, dtype=np.float64) * 0.01,
|
||||
beta_covariance=np.eye(8, dtype=np.float64) * 0.001,
|
||||
accepted_joint_masks={"cam0": np.ones((20,), dtype=bool), "cam1": np.ones((20,), dtype=bool)},
|
||||
accepted_joint_counts_by_view={"cam0": 20, "cam1": 20},
|
||||
accepted_joint_count=20,
|
||||
accepted_view_count=2,
|
||||
mean_reprojection_error=3.0,
|
||||
lm_iterations=1,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tracker,
|
||||
"_refine_track_state",
|
||||
lambda track, predicted_state, matched: (
|
||||
update_result,
|
||||
np.full((20,), 9.0, dtype=np.float64),
|
||||
{"cam0": np.full((20,), 9.0, dtype=np.float64), "cam1": np.full((20,), 9.0, dtype=np.float64)},
|
||||
),
|
||||
)
|
||||
update_result_frame = tracker.step(_make_bundle(1))
|
||||
updated_cov_trace = float(np.trace(update_result_frame.active_tracks[0].covariance))
|
||||
|
||||
assert predict_only_cov_trace > float(TRACK_COVARIANCE_DIMENSION)
|
||||
assert updated_cov_trace < predict_only_cov_trace
|
||||
|
||||
|
||||
def test_proposal_compatible_lost_track_gets_score_relief(monkeypatch) -> None:
|
||||
tracker = PoseTracker(
|
||||
_make_scene(),
|
||||
TrackerConfig(
|
||||
max_active_tracks=1,
|
||||
active_miss_to_lost=1,
|
||||
lost_delete_age=10,
|
||||
lost_score_decay=1.0,
|
||||
lost_score_miss_penalty=0.5,
|
||||
proposal_compatible_score_relief=0.4,
|
||||
),
|
||||
)
|
||||
proposal = _make_proposal(0.0, score=0.95)
|
||||
tracker._lost[1] = ActiveTrackState(
|
||||
track_id=1,
|
||||
status="lost",
|
||||
lost_age=1,
|
||||
score=1.0,
|
||||
skeleton=seed_state_from_pose3d(proposal.pose3d),
|
||||
covariance=np.eye(TRACK_COVARIANCE_DIMENSION, dtype=np.float64),
|
||||
)
|
||||
monkeypatch.setattr(tracker, "_build_proposals", lambda bundle, unmatched: (proposal,))
|
||||
monkeypatch.setattr(tracker, "_proposal_support_matches", lambda bundle, track, proposal, seeded_state: {})
|
||||
|
||||
result = tracker.step(_make_bundle(0))
|
||||
|
||||
assert result.lost_tracks[0].score > 0.4
|
||||
|
||||
Reference in New Issue
Block a user