Fix minor issues: enum types, backup path, JS URLs, status field, timestamp docs
- CameraConfig.location now uses CameraLocation enum (Pydantic v2 coerces TOML strings) - Wildlife classifier returns ThreatLevel enum values with correct return type annotation - Model backup path fixed: pet_id_backup.pt instead of pet_id.backup.pt - Dashboard submitLabel JS now posts to /pets/<sighting_id>/label matching Flask route - Pet status API computes status field (safe/unknown) based on last-seen recency - digest.py comment explains timestamp unit difference between tables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9858738e82
commit
0b82105179
@ -20,6 +20,7 @@ from vigilar.constants import (
|
||||
DEFAULT_RETENTION_DAYS,
|
||||
DEFAULT_UPS_POLL_INTERVAL_S,
|
||||
DEFAULT_WEB_PORT,
|
||||
CameraLocation,
|
||||
)
|
||||
|
||||
# --- Camera Config ---
|
||||
@ -42,7 +43,7 @@ class CameraConfig(BaseModel):
|
||||
resolution_capture: list[int] = Field(default_factory=lambda: [1920, 1080])
|
||||
resolution_motion: list[int] = Field(default_factory=lambda: [640, 360])
|
||||
zones: list["CameraZone"] = Field(default_factory=list)
|
||||
location: str = "INTERIOR" # EXTERIOR | INTERIOR | TRANSITION
|
||||
location: CameraLocation = CameraLocation.INTERIOR
|
||||
|
||||
|
||||
# --- Sensor Config ---
|
||||
|
||||
@ -122,7 +122,9 @@ class PetTrainer:
|
||||
epoch + 1, epochs, running_loss / len(loader), accuracy)
|
||||
|
||||
if self._model_output_path.exists():
|
||||
backup_path = self._model_output_path.with_suffix(".backup.pt")
|
||||
backup_path = self._model_output_path.with_name(
|
||||
self._model_output_path.stem + "_backup.pt"
|
||||
)
|
||||
shutil.copy2(self._model_output_path, backup_path)
|
||||
log.info("Backed up previous model to %s", backup_path)
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Wildlife threat level classification."""
|
||||
|
||||
from vigilar.config import WildlifeConfig
|
||||
from vigilar.constants import ThreatLevel
|
||||
from vigilar.detection.person import Detection
|
||||
|
||||
|
||||
@ -8,7 +9,7 @@ def classify_wildlife_threat(
|
||||
detection: Detection,
|
||||
config: WildlifeConfig,
|
||||
frame_area: int,
|
||||
) -> tuple[str, str]:
|
||||
) -> tuple[ThreatLevel, str]:
|
||||
"""Classify a wildlife detection into threat level and species.
|
||||
|
||||
Returns (threat_level, species_name).
|
||||
@ -18,11 +19,11 @@ def classify_wildlife_threat(
|
||||
|
||||
# Direct COCO class mapping first
|
||||
if species in threat_map.predator:
|
||||
return "PREDATOR", species
|
||||
return ThreatLevel.PREDATOR, species
|
||||
if species in threat_map.nuisance:
|
||||
return "NUISANCE", species
|
||||
return ThreatLevel.NUISANCE, species
|
||||
if species in threat_map.passive:
|
||||
return "PASSIVE", species
|
||||
return ThreatLevel.PASSIVE, species
|
||||
|
||||
# Fallback to size heuristics for unknown species
|
||||
_, _, w, h = detection.bbox
|
||||
@ -31,8 +32,8 @@ def classify_wildlife_threat(
|
||||
|
||||
heuristics = config.size_heuristics
|
||||
if area_ratio < heuristics.small:
|
||||
return "NUISANCE", species
|
||||
return ThreatLevel.NUISANCE, species
|
||||
elif area_ratio < heuristics.medium:
|
||||
return "PREDATOR", species
|
||||
return ThreatLevel.PREDATOR, species
|
||||
else:
|
||||
return "PASSIVE", species
|
||||
return ThreatLevel.PASSIVE, species
|
||||
|
||||
@ -36,6 +36,8 @@ def build_digest(engine: Engine, data_dir: str, since_hours: int = 12) -> dict:
|
||||
.where(recordings.c.started_at >= since_ts // 1000)
|
||||
).scalar() or 0
|
||||
|
||||
# pet_sightings/wildlife_sightings use float seconds (time.time()),
|
||||
# while events table uses integer milliseconds (time.time() * 1000)
|
||||
pet_count = conn.execute(
|
||||
select(func.count()).select_from(pet_sightings)
|
||||
.where(pet_sightings.c.ts >= since_ts / 1000)
|
||||
|
||||
@ -58,13 +58,20 @@ def pet_status():
|
||||
|
||||
from vigilar.storage.queries import get_all_pets, get_pet_last_location
|
||||
pets_list = get_all_pets(engine)
|
||||
now = time.time()
|
||||
result = []
|
||||
for pet in pets_list:
|
||||
last = get_pet_last_location(engine, pet["id"])
|
||||
if last:
|
||||
last_seen_ago = now - last["ts"]
|
||||
status = "safe" if last_seen_ago < 300 else "unknown" # seen in last 5 min
|
||||
else:
|
||||
status = "unknown"
|
||||
result.append({
|
||||
**pet,
|
||||
"last_seen_ts": last["ts"] if last else None,
|
||||
"last_camera": last["camera_id"] if last else None,
|
||||
"status": status,
|
||||
})
|
||||
return jsonify({"pets": result})
|
||||
|
||||
|
||||
@ -392,10 +392,10 @@
|
||||
|
||||
function submitLabel(petId) {
|
||||
if (!pendingLabelCropId) return;
|
||||
fetch('/pets/api/label', {
|
||||
fetch(`/pets/${pendingLabelCropId}/label`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ crop_id: pendingLabelCropId, pet_id: petId }),
|
||||
body: JSON.stringify({ sighting_id: pendingLabelCropId, pet_id: petId }),
|
||||
}).finally(() => {
|
||||
bootstrap.Modal.getOrCreateInstance(document.getElementById('labelModal')).hide();
|
||||
loadUnlabeled();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user