fix(pose): clear iphone hands on stream drop

iphone_usb_source.py: after stream ends, clear
persons_hands_iphone, persons_hands_chirality, and
persons_hands (when write_hands=True) under state.lock()
to prevent renderer display freeze.

test_iphone_usb_reconnect.py: add
test_hands_cleared_on_stream_drop to verify lists are
empty after simulated USB disconnect.
This commit is contained in:
L'électron rare
2026-07-02 14:23:53 +02:00
parent 0126fc97a8
commit d337ed9b29
2 changed files with 48 additions and 0 deletions
+8
View File
@@ -239,6 +239,14 @@ class IphoneUSBSource:
# USB hiccup ends iter_frames; we re-establish :7000).
with self._lock:
self._frame = None
# Clear stale hand state so the renderer doesn't freeze on the
# last known hands after a stream drop.
if self.state is not None:
with self.state.lock():
self.state.persons_hands_iphone = []
self.state.persons_hands_chirality = []
if self._write_hands:
self.state.persons_hands = []
self._opened = False
sock = None # forces the reconnect at the loop top
if not self._stop.is_set():
@@ -4,6 +4,7 @@ race (app not ready when data_only_viz starts), not only a mid-session drop."""
import time
import data_only_viz.iphone_usb_source as m
from data_only_viz.state import PoseKp, State
def test_start_keeps_retrying_when_no_device(monkeypatch):
@@ -23,3 +24,42 @@ def test_start_keeps_retrying_when_no_device(monkeypatch):
"start() should keep retrying; connect_device called %d time(s)" % calls["n"])
finally:
src.release()
def test_hands_cleared_on_stream_drop(monkeypatch):
"""After a USB stream drop, persons_hands* lists must be emptied so the
renderer does not freeze on the last known hand positions."""
class FakeSock:
def close(self): pass
def shutdown(self, *a): pass
connect_calls = [0]
def fake_connect():
connect_calls[0] += 1
if connect_calls[0] == 1:
return FakeSock() # initial connection succeeds
return None # reconnect fails; release() will interrupt the wait
def fake_iter_frames(sock):
raise OSError("stream dropped") # simulate immediate disconnect
state = State()
dummy_hand = [PoseKp(x=0.5, y=0.5, z=0.0, c=1.0)] * 21
with state.lock():
state.persons_hands = [dummy_hand]
state.persons_hands_iphone = [dummy_hand]
state.persons_hands_chirality = [1]
monkeypatch.setattr(m, "connect_device", fake_connect)
monkeypatch.setattr(m, "iter_frames", fake_iter_frames)
src = m.IphoneUSBSource(state=state, write_hands=True)
src.start()
src.release() # joins thread; ensures clearing happened before we check
with state.lock():
assert state.persons_hands == [], "persons_hands not cleared on drop"
assert state.persons_hands_iphone == [], "persons_hands_iphone not cleared on drop"
assert state.persons_hands_chirality == [], "persons_hands_chirality not cleared on drop"