CamGuard / tests /test_pipeline.py
Pabloler21's picture
Claude Opus 5.5
Mostrar a los intrusos en una tabla con su foto a medida que se analizan
0c44a5a
Raw History Blame Contribute Delete
3.84 kB
import numpy as np
import pytest
import supervision as sv
from camguard.config import PERSON_ID
from camguard.events import Event
from camguard.pipeline import analyze
from camguard.video import H264Writer, probe
SIZE = 100 # video de 100x100; la zona es el cuarto inferior derecho
def last(results):
"""El resultado final de analyze, que va devolviendo parciales."""
*_, result = results
return result
class FakeTracker:
"""Reemplaza al modelo: una persona (#1) camina afuera, entra en los cuadros 2 a 5 y sale."""
def __init__(self, fps):
self.frame = 0
def update(self, frame_bgr):
x = 75 if 2 <= self.frame <= 5 else 25 # centro del cuerpo en (x, 65)
self.frame += 1
# Como el tracker real: solo personas (class_id = PERSON_ID) y con número de seguimiento.
return sv.Detections(
xyxy=np.array([[x - 5, 55.0, x + 5, 75.0]]),
class_id=np.array([PERSON_ID]),
tracker_id=np.array([1]),
)
@pytest.fixture
def video(tmp_path):
"""2 s de video gris a 5 fps (10 cuadros): se analizan todos."""
path = tmp_path / "video.mp4"
with H264Writer(path, fps=5, width=SIZE, height=SIZE) as writer:
for _ in range(10):
writer.write(np.full((SIZE, SIZE, 3), 128, np.uint8))
return path
@pytest.fixture
def zone_layers():
layer = np.zeros((SIZE, SIZE, 4), np.uint8)
layer[50:, 50:] = (255, 0, 0, 255)
return [layer]
def test_analyze_detects_the_intrusion(video, zone_layers):
*partials, result = analyze(video, zone_layers, make_tracker=FakeTracker)
assert result.events == [Event(track_id=1, enter_s=0.4, leave_s=1.2)]
assert [(s.track_id, s.time_s) for s in result.snapshots] == [(1, 0.4)]
assert probe(result.video_path).frame_count == 10
# Un parcial por cuadro, sin video; el intruso aparece desde el cuadro en que entra.
assert [p.progress for p in partials] == [(i + 1) / 10 for i in range(10)]
assert all(p.video_path is None for p in partials)
assert [len(p.events) for p in partials] == [0, 0] + [1] * 8
def test_snapshot_is_the_person_cropped(video, zone_layers):
photo = last(analyze(video, zone_layers, make_tracker=FakeTracker)).snapshots[0].image
# Recuadro de 10x20 en (70, 55), con 10% de aire: 1 px a los costados, 2 arriba y abajo.
assert photo.shape == (24, 12, 3)
class StraddlingTracker:
"""Una persona parada sobre el borde de arriba: pies apenas adentro, cuerpo afuera."""
def __init__(self, fps):
pass
def update(self, frame_bgr):
return sv.Detections(
xyxy=np.array([[65.0, 20.0, 85.0, 55.0]]), # centro en y=37 (afuera); pies en y=55
class_id=np.array([PERSON_ID]),
tracker_id=np.array([1]),
)
def test_person_counts_by_body_center_not_feet(video, zone_layers):
# En la imagen se la ve afuera de la zona, que es lo que importa a quien mira el video.
result = last(analyze(video, zone_layers, make_tracker=StraddlingTracker))
assert result.events == []
def test_analyze_writes_the_result_inside_out_dir(video, zone_layers, tmp_path):
# Así la app puede borrar todos los videos generados (son grabaciones reales).
out_dir = tmp_path / "resultados"
result = last(analyze(video, zone_layers, make_tracker=FakeTracker, out_dir=out_dir))
assert out_dir in result.video_path.parents
def test_analyze_respects_max_seconds(video, zone_layers, monkeypatch):
monkeypatch.setattr("camguard.pipeline.MAX_SECONDS", 1)
result = last(analyze(video, zone_layers, make_tracker=FakeTracker))
assert probe(result.video_path).frame_count == 5
def test_analyze_without_zone_raises(video):
with pytest.raises(ValueError, match="Pintá la zona"):
next(analyze(video, [], make_tracker=FakeTracker))