diff --git a/data_only_viz/action_head_pub.py b/data_only_viz/action_head_pub.py index d08a46f..d6398e8 100644 --- a/data_only_viz/action_head_pub.py +++ b/data_only_viz/action_head_pub.py @@ -243,8 +243,7 @@ class ActionHeadPublisher(threading.Thread): without going through _tick). """ hands, chir, _, _ = self._pick_hands() - from data_only_viz.hand_slots import route_hands as _rh - raw = _rh( + raw = route_hands( hands, chir or None, swap=getattr(self, "_hand_swap_lr", False), near_min=0.0, diff --git a/data_only_viz/hand_display.py b/data_only_viz/hand_display.py index 17987da..625d76a 100644 --- a/data_only_viz/hand_display.py +++ b/data_only_viz/hand_display.py @@ -271,8 +271,11 @@ class HandPersistenceGate: Known limit: matching is greedy nearest-track — two hands crossing within `radius` can inherit each other's track (and a ghost appearing - exactly where a real hand just left inherits its count). Acceptable - for a display gate; do not reuse for gesture-state tracking. + exactly where a real hand just left inherits its count). Grace widens + that inheritance window to `grace` frames after the hand leaves, and a + ghost re-flashing at one spot every (grace+1)th frame can accumulate to + min_frames. Acceptable for a display gate; do not reuse for + gesture-state tracking. """ def __init__(self, min_frames: int = 3, radius: float = 0.15, grace: int = 0) -> None: diff --git a/data_only_viz/tests/test_config.py b/data_only_viz/tests/test_config.py index b27f6ba..2978cdb 100644 --- a/data_only_viz/tests/test_config.py +++ b/data_only_viz/tests/test_config.py @@ -66,6 +66,10 @@ def test_defaults_hand_display(): assert cfg.hand_near_min == pytest.approx(0.10) assert cfg.arkit_bone_max == pytest.approx(0.5) assert cfg.arkit_full_skeleton is True + # detection v3 stabilizer knobs + assert cfg.hand_persist_grace == 2 + assert cfg.hand_near_off == pytest.approx(0.08) + assert cfg.hand_hold_frames == 2 def test_defaults_misc():