from collections import deque from edge.models import Keypoint, BBox from edge.tracking import Track, TrackFrame from edge.detectors.ghost import GhostIncidentDetector, GhostConfig def _track(tid, x, y, t): fr = TrackFrame(t, BBox(x, y, x + 20, y + 60), [Keypoint(x, y, 0.9) for _ in range(17)]) return Track(tid, deque([fr], maxlen=90)) def test_lost_dangerous_track_becomes_ghost_after_wait(): d = GhostIncidentDetector(GhostConfig(reappear_s=1.0)) d.note_state(1, "FALLEN") d.on_lost([_track(1, 100, 100, 0.0)], now=0.0) assert d.update([], now=0.5) == [] # 아직 대기 ghosts = d.update([], now=1.5) # 재출현 없음 > 1.0s assert len(ghosts) == 1 assert ghosts[0]["last_state"] == "FALLEN" assert ghosts[0]["spatial"] == (110.0, 130.0) # bbox 중심 def test_non_dangerous_lost_track_is_ignored(): d = GhostIncidentDetector(GhostConfig(reappear_s=1.0)) d.note_state(1, "STANDING") d.on_lost([_track(1, 100, 100, 0.0)], now=0.0) assert d.update([], now=2.0) == [] def test_reappearance_near_location_cancels_ghost(): d = GhostIncidentDetector(GhostConfig(reappear_s=1.0, reappear_dist=2.0)) d.note_state(1, "FALLEN") d.on_lost([_track(1, 100, 100, 0.0)], now=0.0) # 인근에 새 트랙 재출현 assert d.update([_track(2, 105, 105, 0.5)], now=0.5) == [] assert d.update([], now=2.0) == [] # 취소되어 방출 없음