Add pet detection, wildlife, and activity config models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-04-03 13:08:40 -04:00
parent aae857ec53
commit 6e3ef1dcdc
2 changed files with 103 additions and 0 deletions

View File

@ -1,6 +1,7 @@
"""Tests for config loading and validation.""" """Tests for config loading and validation."""
from vigilar.config import CameraConfig, VigilarConfig from vigilar.config import CameraConfig, VigilarConfig
from vigilar.config import PetsConfig, WildlifeThreatMap, WildlifeSizeHeuristics, PetActivityConfig
def test_default_config(): def test_default_config():
@ -33,3 +34,61 @@ def test_camera_sensitivity_bounds():
import pytest import pytest
with pytest.raises(Exception): with pytest.raises(Exception):
CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://localhost", motion_sensitivity=1.5) CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://localhost", motion_sensitivity=1.5)
class TestPetsConfig:
def test_defaults(self):
cfg = PetsConfig()
assert cfg.enabled is False
assert cfg.model == "yolov8s"
assert cfg.confidence_threshold == 0.5
assert cfg.pet_id_threshold == 0.7
assert cfg.pet_id_low_confidence == 0.5
assert cfg.min_training_images == 20
assert cfg.crop_retention_days == 7
def test_custom_values(self):
cfg = PetsConfig(enabled=True, model="yolov8m", confidence_threshold=0.6)
assert cfg.enabled is True
assert cfg.model == "yolov8m"
assert cfg.confidence_threshold == 0.6
class TestWildlifeThreatMap:
def test_defaults(self):
tm = WildlifeThreatMap()
assert "bear" in tm.predator
assert "bird" in tm.passive
def test_custom_mapping(self):
tm = WildlifeThreatMap(predator=["bear", "wolf"], nuisance=["raccoon"])
assert "wolf" in tm.predator
assert "raccoon" in tm.nuisance
class TestWildlifeSizeHeuristics:
def test_defaults(self):
sh = WildlifeSizeHeuristics()
assert sh.small == 0.02
assert sh.medium == 0.08
assert sh.large == 0.15
class TestPetActivityConfig:
def test_defaults(self):
cfg = PetActivityConfig()
assert cfg.daily_digest is True
assert cfg.highlight_clips is True
assert cfg.zoomie_threshold == 0.8
class TestCameraConfigLocation:
def test_default_location_is_interior(self):
from vigilar.config import CameraConfig
cfg = CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://x")
assert cfg.location == "INTERIOR"
def test_exterior_location(self):
from vigilar.config import CameraConfig
cfg = CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://x", location="EXTERIOR")
assert cfg.location == "EXTERIOR"

View File

@ -43,6 +43,7 @@ class CameraConfig(BaseModel):
resolution_capture: list[int] = Field(default_factory=lambda: [1920, 1080]) resolution_capture: list[int] = Field(default_factory=lambda: [1920, 1080])
resolution_motion: list[int] = Field(default_factory=lambda: [640, 360]) resolution_motion: list[int] = Field(default_factory=lambda: [640, 360])
zones: list["CameraZone"] = Field(default_factory=list) zones: list["CameraZone"] = Field(default_factory=list)
location: str = "INTERIOR" # EXTERIOR | INTERIOR | TRANSITION
# --- Sensor Config --- # --- Sensor Config ---
@ -239,6 +240,48 @@ class HealthConfig(BaseModel):
daily_digest_time: str = "08:00" daily_digest_time: str = "08:00"
# --- Pet Detection Config ---
class WildlifeThreatMap(BaseModel):
predator: list[str] = Field(default_factory=lambda: ["bear"])
nuisance: list[str] = Field(default_factory=list)
passive: list[str] = Field(default_factory=lambda: ["bird", "horse", "cow", "sheep"])
class WildlifeSizeHeuristics(BaseModel):
small: float = 0.02 # < 2% of frame → nuisance
medium: float = 0.08 # 2-8% → predator
large: float = 0.15 # > 8% → passive (deer-sized)
class WildlifeConfig(BaseModel):
threat_map: WildlifeThreatMap = Field(default_factory=WildlifeThreatMap)
size_heuristics: WildlifeSizeHeuristics = Field(default_factory=WildlifeSizeHeuristics)
class PetActivityConfig(BaseModel):
daily_digest: bool = True
highlight_clips: bool = True
zoomie_threshold: float = 0.8
class PetsConfig(BaseModel):
enabled: bool = False
model: str = "yolov8s"
model_path: str = "/var/vigilar/models/yolov8s.pt"
confidence_threshold: float = 0.5
pet_id_enabled: bool = True
pet_id_model_path: str = "/var/vigilar/models/pet_id.pt"
pet_id_threshold: float = 0.7
pet_id_low_confidence: float = 0.5
training_dir: str = "/var/vigilar/pets/training"
crop_staging_dir: str = "/var/vigilar/pets/staging"
crop_retention_days: int = 7
min_training_images: int = 20
wildlife: WildlifeConfig = Field(default_factory=WildlifeConfig)
activity: PetActivityConfig = Field(default_factory=PetActivityConfig)
# --- Rule Config --- # --- Rule Config ---
class RuleCondition(BaseModel): class RuleCondition(BaseModel):
@ -284,6 +327,7 @@ class VigilarConfig(BaseModel):
detection: DetectionConfig = Field(default_factory=DetectionConfig) detection: DetectionConfig = Field(default_factory=DetectionConfig)
vehicles: VehicleConfig = Field(default_factory=VehicleConfig) vehicles: VehicleConfig = Field(default_factory=VehicleConfig)
health: HealthConfig = Field(default_factory=HealthConfig) health: HealthConfig = Field(default_factory=HealthConfig)
pets: PetsConfig = Field(default_factory=PetsConfig)
cameras: list[CameraConfig] = Field(default_factory=list) cameras: list[CameraConfig] = Field(default_factory=list)
sensors: list[SensorConfig] = Field(default_factory=list) sensors: list[SensorConfig] = Field(default_factory=list)
sensor_gpio: SensorGPIOConfig = Field(default_factory=SensorGPIOConfig, alias="sensors.gpio") sensor_gpio: SensorGPIOConfig = Field(default_factory=SensorGPIOConfig, alias="sensors.gpio")