Task 1 — Presence: ping family phones, derive household state (EMPTY/KIDS_HOME/ADULTS_HOME/ALL_HOME), configurable departure delay, per-member roles, auto-arm actions via MQTT. Task 2 — Detection: MobileNet-SSD v2 via OpenCV DNN for person/vehicle classification. Vehicle color/size fingerprinting for known car matching. Zone-based filtering per camera. Model download script. Task 3 — Health: periodic disk/MQTT/subsystem checks, auto-prune oldest non-starred recordings on disk pressure, daily digest builder. 126 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""Tests for person and vehicle detection."""
|
|
|
|
import numpy as np
|
|
|
|
from vigilar.detection.person import Detection, PersonDetector
|
|
from vigilar.detection.vehicle import classify_dominant_color, classify_size
|
|
from vigilar.detection.zones import filter_detections_by_zone
|
|
|
|
|
|
class TestPersonDetector:
|
|
def test_detector_initializes_without_model(self):
|
|
detector = PersonDetector(model_path="nonexistent.pb", config_path="nonexistent.pbtxt")
|
|
assert not detector.is_loaded
|
|
|
|
def test_detect_returns_empty_when_not_loaded(self):
|
|
detector = PersonDetector(model_path="nonexistent.pb", config_path="nonexistent.pbtxt")
|
|
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
|
detections = detector.detect(frame)
|
|
assert detections == []
|
|
|
|
def test_detection_dataclass(self):
|
|
d = Detection(class_name="person", class_id=1, confidence=0.85, bbox=(10, 20, 100, 200))
|
|
assert d.class_name == "person"
|
|
assert d.confidence == 0.85
|
|
|
|
|
|
class TestVehicleColor:
|
|
def test_white_detection(self):
|
|
white_region = np.full((100, 100, 3), 240, dtype=np.uint8)
|
|
color = classify_dominant_color(white_region)
|
|
assert color == "white"
|
|
|
|
def test_black_detection(self):
|
|
black_region = np.full((100, 100, 3), 15, dtype=np.uint8)
|
|
color = classify_dominant_color(black_region)
|
|
assert color == "black"
|
|
|
|
def test_size_compact(self):
|
|
assert classify_size(bbox_area=5000, zone_area=100000) == "compact"
|
|
|
|
def test_size_midsize(self):
|
|
assert classify_size(bbox_area=15000, zone_area=100000) == "midsize"
|
|
|
|
def test_size_large(self):
|
|
assert classify_size(bbox_area=30000, zone_area=100000) == "large"
|
|
|
|
|
|
class TestZoneFiltering:
|
|
def test_detection_inside_zone(self):
|
|
detections = [Detection("person", 1, 0.9, (50, 50, 80, 80))]
|
|
zone_region = (0, 0, 200, 200)
|
|
filtered = filter_detections_by_zone(detections, zone_region, ["person"])
|
|
assert len(filtered) == 1
|
|
|
|
def test_detection_outside_zone(self):
|
|
detections = [Detection("person", 1, 0.9, (300, 300, 50, 50))]
|
|
zone_region = (0, 0, 200, 200)
|
|
filtered = filter_detections_by_zone(detections, zone_region, ["person"])
|
|
assert len(filtered) == 0
|
|
|
|
def test_filter_by_class(self):
|
|
detections = [
|
|
Detection("person", 1, 0.9, (50, 50, 80, 80)),
|
|
Detection("car", 3, 0.8, (50, 50, 80, 80)),
|
|
]
|
|
zone_region = (0, 0, 200, 200)
|
|
filtered = filter_detections_by_zone(detections, zone_region, ["person"])
|
|
assert len(filtered) == 1
|
|
assert filtered[0].class_name == "person"
|
|
|
|
def test_no_zone_returns_all(self):
|
|
detections = [Detection("person", 1, 0.9, (50, 50, 80, 80))]
|
|
filtered = filter_detections_by_zone(detections, None, ["person", "car"])
|
|
assert len(filtered) == 1
|