feat(edge): main-loop wiring (run_once) + pipeline factory

This commit is contained in:
유창욱 2026-07-11 21:46:43 +09:00
parent e74cc7f898
commit b621627863
2 changed files with 103 additions and 0 deletions

55
edge/app.py Normal file
View file

@ -0,0 +1,55 @@
from edge.config import Config
from edge.tracking import IouTracker
from edge.smoothing import KeypointSmoother
from edge.detectors.fall import FallDetector
from edge.detectors.immobility import ImmobilityWatchdog
from edge.detectors.ghost import GhostIncidentDetector
from edge.detectors.zone import ZoneIntrusionDetector
from edge.engine import EventEngine
from edge.snapshot import RingBuffer, FaceBlurrer, Snapshotter
from edge.models import Person, BBox
def _head_boxes(persons: list) -> list:
# 배경인물 머리영역 근사: 코 주변 작은 박스 (MVP; 실제는 얼굴 검출 포트로 교체)
boxes = []
for p in persons:
nose = p.keypoints[0]
boxes.append(BBox(nose.x - 20, nose.y - 25, nose.x + 20, nose.y + 15))
return boxes
def run_once(source, pose, tracker, smoother, engine, ring, snapshotter, sink, now_fn):
ok, frame = source.read()
if not ok:
return []
now = now_fn()
persons = pose.infer(frame)
smoothed = [Person(p.bbox, smoother.smooth(p.keypoints, now)) for p in persons]
tracks = tracker.update(smoothed, now)
lost = tracker.lost()
ring.push(now, frame)
events = engine.process(tracks, lost, now)
if events:
snaps = snapshotter.capture([frame], [_head_boxes(persons)])
for e in events:
sink.try_send(e, snaps)
return events
def build_pipeline(config: Config, source, pose) -> dict:
return {
"source": source,
"pose": pose,
"tracker": IouTracker(),
"smoother": KeypointSmoother(),
"engine": EventEngine(
FallDetector(config.fall),
ImmobilityWatchdog(config.immobility),
GhostIncidentDetector(config.ghost),
ZoneIntrusionDetector([], config.zone),
config.engine,
),
"ring": RingBuffer(),
"snapshotter": Snapshotter(FaceBlurrer()),
}

48
tests/test_app.py Normal file
View file

@ -0,0 +1,48 @@
import numpy as np
from edge.models import Person, BBox, Keypoint
from edge.tracking import IouTracker
from edge.smoothing import KeypointSmoother
from edge.detectors.fall import FallDetector, FallConfig
from edge.detectors.immobility import ImmobilityWatchdog, ImmobilityConfig
from edge.detectors.ghost import GhostIncidentDetector, GhostConfig
from edge.detectors.zone import ZoneIntrusionDetector, ZoneConfig
from edge.engine import EventEngine, EngineConfig
from edge.snapshot import RingBuffer, FaceBlurrer, Snapshotter
from edge.events import EventType
from edge.app import run_once
def _lying_person():
kps = [Keypoint(150, 300, 0.9) for _ in range(17)]
kps[5] = Keypoint(100, 300, 0.9); kps[6] = Keypoint(100, 310, 0.9)
kps[11] = Keypoint(200, 300, 0.9); kps[12] = Keypoint(200, 310, 0.9)
kps[0] = Keypoint(90, 320, 0.9)
return Person(BBox(90, 290, 210, 320), kps)
class _Source:
def read(self): return True, np.full((360, 640, 3), 128, np.uint8)
class _Pose:
def infer(self, frame): return [_lying_person()]
class _Sink:
def __init__(self): self.events = []
def try_send(self, event, snapshots):
self.events.append(event); return True
def test_run_once_pipeline_emits_fall_after_prone():
src, pose = _Source(), _Pose()
tracker = IouTracker()
smoother = KeypointSmoother()
engine = EventEngine(
FallDetector(FallConfig(prone_confirm_s=0.5)),
ImmobilityWatchdog(ImmobilityConfig(immobile_s=999)),
GhostIncidentDetector(GhostConfig()),
ZoneIntrusionDetector([], ZoneConfig()),
EngineConfig(camera_id="cam-01"),
)
ring = RingBuffer(); snap = Snapshotter(FaceBlurrer()); sink = _Sink()
times = iter([0.0, 0.6])
def now_fn(): return next(times)
run_once(src, pose, tracker, smoother, engine, ring, snap, sink, now_fn)
run_once(src, pose, tracker, smoother, engine, ring, snap, sink, now_fn)
assert any(e.event_type == EventType.FALL for e in sink.events)