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 중심 assert ghosts[0]["track_id"] == 1 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) # 인근에 새 트랙(손실 시점엔 없던 id) 재출현 -> 같은 사람이 다시 잡힌 것 -> 취소 assert d.update([_track(2, 105, 105, 0.5)], now=0.5) == [] assert d.update([], now=2.0) == [] # 취소되어 방출 없음 def test_bystander_present_at_loss_does_not_cancel_ghost(): # A colleague already being tracked when the victim was lost must NOT cancel the incident # by walking over the loss point — otherwise a worker dragged/collapsed under a machine, # with a co-worker nearby, is never reported. Only a track that did NOT exist at loss time # (a genuine re-appearance of the victim) may cancel. d = GhostIncidentDetector(GhostConfig(reappear_s=1.0, reappear_dist=2.0)) d.note_state(1, "FALLEN") d.note_state(2, "STANDING") # colleague id 2 is active at loss time d.on_lost([_track(1, 100, 100, 0.0)], now=0.0) assert d.update([_track(2, 105, 105, 0.5)], now=0.5) == [] # bystander near, not cancelled ghosts = d.update([_track(2, 105, 105, 1.5)], now=1.5) assert len(ghosts) == 1 # incident still fires assert ghosts[0]["track_id"] == 1