Module Overview¶
Argus is nine modules and one entry point. Each owns a single responsibility. The data flows one direction — frames in, alerts out.
Dependency Graph¶
main.py
│
├──► config (loads TOML → models)
├──► targets (loads faces → models)
│
├──► alert (uses models, webhooks, screenshots)
├──► detection (uses models, face_recognition)
├──► display (uses models, cv2)
│
└──► manager (orchestrates stream, detection, alert, display)
│
└──► stream (one per camera, daemon threads)
models.py ◄──── Every module imports from here. Zero dependencies on others.
▲
│
config.py ──► models.py (CameraConfig, Settings, WebhookConfig)
targets.py ──► models.py (Target)
stream.py ──► models.py (CameraConfig)
detection.py ──► models.py (MatchEvent, Settings)
alert.py ──► models.py (MatchEvent, Settings, WebhookConfig)
display.py ──► models.py (CameraConfig, MatchEvent)
manager.py ──► stream, detection, alert, display, models
main.py ──► config, targets, detection, alert, display, manager
Module Responsibilities¶
| Module | File | Responsibility |
|---|---|---|
| models | argus/models.py |
Dataclass definitions. The contract between every other module. Zero logic beyond __post_init__ validation. |
| config | argus/config.py |
TOML parsing and validation. Loads cameras.toml and webhooks.toml into typed dataclasses. Fails loud on bad config. |
| targets | argus/targets.py |
Scans the target database, reads info.json, computes 128-D face encodings via face_recognition, and builds a flattened index for vectorized matching. |
| stream | argus/stream.py |
One resilient RTSP reader per camera. Daemon thread, auto-reconnect with exponential backoff, lock-protected frame delivery. The eyes never close. |
| detection | argus/detection.py |
Face detection (HOG) and recognition (128-D encoding comparison). Serializes all face_recognition calls behind a global lock because dlib's singletons aren't thread-safe. |
| alert | argus/alert.py |
Match event processing: structured logging, annotated screenshot capture, webhook dispatch. Enforces per-(target, camera) cooldown to kill spam. |
| display | argus/display.py |
Optional GUI. One OpenCV window per camera with bounding box overlays. All cv2 calls enforced to the main thread. |
| manager | argus/manager.py |
The orchestrator. Spawns streams, runs the main loop, time-gates detection, wires everything together. Owns the process lifecycle. |
| main | main.py |
Entry point. Argument parsing, component wiring, banner. Imports happen inside main() to avoid side effects at import time. |
Thread Ownership¶
| Thread | Owner | Purpose |
|---|---|---|
| Main thread | manager.py |
Runs _main_loop(), calls display.update() and display.tick(). Must be the main thread if GUI is enabled — cv2.imshow/cv2.waitKey enforce this. |
stream-{id} (×N) |
stream.py |
One per camera. Daemon thread. Continuously reads RTSP frames into a lock-protected buffer. |
ThreadPool workers |
alert.py |
Fire-and-forget webhook calls in daemon threads. Spawned per webhook per match event. |
Lock contention points:
RTSPStream._lock— protects_framebetween the reader thread and the main thread'slatest_frame()call.FaceDetector._DETECTION_LOCK— module-level, serializes allface_recognitioncalls across all threads (dlib global state).AlertHandler._cooldown_lock— protects the_cooldownsdict between the main thread and any future concurrenthandle()calls.
Startup Sequence¶
main.py::main()
1. parse_args()
2. load_cameras_config() → Settings, list[CameraConfig]
3. load_webhooks_config() → list[WebhookConfig]
4. AlertHandler() → sets up loguru sinks, creates dirs
5. load_targets() → list[Target]
6. build_encoding_index() → (N,128) array, names list
7. FaceDetector() → ready to detect
8. Display() [if --gui] → creates windows
9. StreamManager() → stores references
10. manager.start() → spawns threads, enters main loop (blocks)