Cooked I Am
This commit is contained in:
1 parent
7b2813be20
commit
d57a48eec6
20 files changed
+665
-276
No files matched your search
@@ -3,10 +3,14 @@
|
||||
from pipeline.sinks.alerts import FileAlertStore
|
||||
from pipeline.sinks.display import WindowDisplaySink
|
||||
from pipeline.sinks.events import SmartEventSink
|
||||
from pipeline.sinks.metrics import MetricsSink
|
||||
from pipeline.sinks.screenshots import ScreenshotSink
|
||||
from pipeline.sinks.video import VideoFileStore
|
||||
|
||||
__all__ = [
|
||||
"FileAlertStore",
|
||||
"MetricsSink",
|
||||
"ScreenshotSink",
|
||||
"SmartEventSink",
|
||||
"VideoFileStore",
|
||||
"WindowDisplaySink",
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"""Shared drawing utilities for sinks."""
|
||||
|
||||
from typing import List
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from pipeline.types import Detection
|
||||
|
||||
|
||||
def draw_detections(image: np.ndarray, detections: List[Detection]):
|
||||
"""Draw detection boxes with labels and color coding."""
|
||||
for det in detections:
|
||||
x1, y1, x2, y2 = det.box.x1, det.box.y1, det.box.x2, det.box.y2
|
||||
label = det.label
|
||||
score = det.score
|
||||
|
||||
# Color coding
|
||||
color = (255, 255, 255) # White default
|
||||
if "person" in label:
|
||||
color = (255, 0, 0) # Blue
|
||||
elif "candidate" in label:
|
||||
color = (0, 255, 255) # Yellow
|
||||
elif "verified" in label:
|
||||
color = (0, 0, 255) # Red (Confirmed Smoke)
|
||||
|
||||
# Draw Box
|
||||
cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
|
||||
|
||||
# Draw Label
|
||||
text = f"{label} {score:.2f}"
|
||||
(tw, th), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
||||
cv2.rectangle(image, (x1, y1 - 20), (x1 + tw, y1), color, -1)
|
||||
cv2.putText(
|
||||
image, text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1
|
||||
)
|
||||
|
||||
|
||||
def draw_frame_overlay(image: np.ndarray, frame_id: int):
|
||||
"""Draw frame counter overlay."""
|
||||
cv2.putText(
|
||||
image,
|
||||
f"Frame: {frame_id}",
|
||||
(10, 30),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
1.0,
|
||||
(0, 255, 0),
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
def draw_alert_banner(image: np.ndarray, text: str = "SMOKING DETECTED"):
|
||||
"""Draw a red alert banner at the top."""
|
||||
cv2.rectangle(image, (0, 0), (image.shape[1], 40), (0, 0, 255), -1)
|
||||
cv2.putText(
|
||||
image,
|
||||
text,
|
||||
(10, 30),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
1,
|
||||
(255, 255, 255),
|
||||
2,
|
||||
)
|
||||
@@ -4,8 +4,8 @@ import logging
|
||||
|
||||
import cv2
|
||||
|
||||
from pipeline.ops import draw_detections, draw_frame_overlay
|
||||
from pipeline.protocols import Sink
|
||||
from pipeline.sinks.base import draw_detections, draw_frame_overlay
|
||||
from pipeline.types import Frame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Metrics sink - tracks pipeline performance stats."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from typing import Dict
|
||||
|
||||
from pipeline.protocols import Sink
|
||||
from pipeline.types import Frame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MetricsSink(Sink):
|
||||
"""
|
||||
Tracks pipeline performance metrics:
|
||||
- FPS (frames per second)
|
||||
- Detection counts by label
|
||||
- Alert count
|
||||
- Processing latency
|
||||
"""
|
||||
|
||||
def __init__(self, log_interval: int = 100):
|
||||
self.log_interval = log_interval
|
||||
|
||||
# Timing
|
||||
self.start_time = time.time()
|
||||
self.frame_count = 0
|
||||
self.last_log_time = self.start_time
|
||||
|
||||
# Detection stats
|
||||
self.detection_counts: Dict[str, int] = defaultdict(int)
|
||||
self.alert_count = 0
|
||||
|
||||
# Latency tracking
|
||||
self.frame_timestamps: list[float] = []
|
||||
|
||||
logger.info("MetricsSink initialized. Logging every %d frames.", log_interval)
|
||||
|
||||
def handle(self, frame: Frame):
|
||||
self.frame_count += 1
|
||||
now = time.time()
|
||||
|
||||
# Track latency (time from capture to processing)
|
||||
latency = now - frame.timestamp
|
||||
self.frame_timestamps.append(latency)
|
||||
# Keep only last 100 for rolling average
|
||||
if len(self.frame_timestamps) > 100:
|
||||
self.frame_timestamps.pop(0)
|
||||
|
||||
# Count detections by label
|
||||
for det in frame.detections:
|
||||
self.detection_counts[det.label] += 1
|
||||
|
||||
# Count alerts
|
||||
if frame.ticket:
|
||||
self.alert_count += 1
|
||||
|
||||
# Periodic logging
|
||||
if self.frame_count % self.log_interval == 0:
|
||||
self._log_stats(now)
|
||||
|
||||
def _log_stats(self, now: float):
|
||||
elapsed = now - self.start_time
|
||||
fps = self.frame_count / elapsed if elapsed > 0 else 0
|
||||
avg_latency = (
|
||||
sum(self.frame_timestamps) / len(self.frame_timestamps)
|
||||
if self.frame_timestamps
|
||||
else 0
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[METRICS] Frames: %d | FPS: %.1f | Latency: %.1fms | Alerts: %d",
|
||||
self.frame_count,
|
||||
fps,
|
||||
avg_latency * 1000,
|
||||
self.alert_count,
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""Print final summary."""
|
||||
elapsed = time.time() - self.start_time
|
||||
fps = self.frame_count / elapsed if elapsed > 0 else 0
|
||||
avg_latency = (
|
||||
sum(self.frame_timestamps) / len(self.frame_timestamps)
|
||||
if self.frame_timestamps
|
||||
else 0
|
||||
)
|
||||
|
||||
logger.info("=" * 50)
|
||||
logger.info("PIPELINE METRICS SUMMARY")
|
||||
logger.info("=" * 50)
|
||||
logger.info("Total Frames: %d", self.frame_count)
|
||||
logger.info("Runtime: %.1f seconds", elapsed)
|
||||
logger.info("Average FPS: %.1f", fps)
|
||||
logger.info("Avg Latency: %.1f ms", avg_latency * 1000)
|
||||
logger.info("Total Alerts: %d", self.alert_count)
|
||||
logger.info("-" * 50)
|
||||
logger.info("Detections by Label:")
|
||||
for label, count in sorted(self.detection_counts.items()):
|
||||
logger.info(" %-25s %d", label, count)
|
||||
logger.info("=" * 50)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Screenshot sink - saves evidence images on alerts."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
from pipeline.ops.drawing import draw_alert_banner, draw_detections
|
||||
from pipeline.protocols import Sink
|
||||
from pipeline.types import Frame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScreenshotSink(Sink):
|
||||
"""
|
||||
Saves a screenshot when a ticket (alert) fires.
|
||||
Useful for evidence/audit trails without full video recording.
|
||||
"""
|
||||
|
||||
def __init__(self, output_dir: str = "screenshots", annotate: bool = True):
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.annotate = annotate
|
||||
self.count = 0
|
||||
|
||||
logger.info("ScreenshotSink initialized. Saving to %s", output_dir)
|
||||
|
||||
def handle(self, frame: Frame):
|
||||
if not frame.ticket:
|
||||
return
|
||||
|
||||
self.count += 1
|
||||
ticket = frame.ticket
|
||||
|
||||
# Build filename with timestamp and confidence
|
||||
filename = f"alert_{ticket.frame_id}_{ticket.confidence:.2f}.jpg"
|
||||
filepath = self.output_dir / filename
|
||||
|
||||
# Prepare image
|
||||
if self.annotate:
|
||||
img = frame.data.copy()
|
||||
draw_detections(img, frame.detections)
|
||||
draw_alert_banner(img, f"SMOKING {ticket.confidence:.0%}")
|
||||
else:
|
||||
img = frame.data
|
||||
|
||||
# Save
|
||||
cv2.imwrite(str(filepath), img)
|
||||
logger.info("Saved screenshot: %s", filepath)
|
||||
|
||||
def close(self):
|
||||
logger.info("ScreenshotSink closed. Total screenshots: %d", self.count)
|
||||
@@ -9,8 +9,8 @@ from typing import Optional
|
||||
|
||||
import cv2
|
||||
|
||||
from pipeline.ops import draw_detections, draw_frame_overlay
|
||||
from pipeline.protocols import Sink
|
||||
from pipeline.sinks.base import draw_detections, draw_frame_overlay
|
||||
from pipeline.types import Frame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Reference in new issue
Block a user