Files
feedmill-recounter/src/interfaces.py
T

99 lines
2.5 KiB
Python

"""Abstract interfaces — all components code against these, never concretions.
Keeps Interface Segregation (I) and Dependency Inversion (D) satisfied.
Each protocol is tiny and single-purpose (S).
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Protocol, runtime_checkable
import numpy as np
# ── Data transfer objects ────────────────────────────────────────────────
@dataclass
class Detection:
"""Single object detection."""
bbox: tuple[float, float, float, float] # x1, y1, x2, y2
confidence: float
class_id: int
class_name: str
track_id: int | None = None
mask: np.ndarray | None = None # segmentation mask (optional)
@dataclass
class FrameResult:
"""All detections for one frame."""
detections: list[Detection] = field(default_factory=list)
frame_index: int = 0
timestamp: float = 0.0
# ── Protocols ────────────────────────────────────────────────────────────
@runtime_checkable
class StreamSource(Protocol):
"""Reads frames from a video source."""
def open(self) -> bool: ...
def read(self) -> tuple[bool, np.ndarray | None]: ...
def release(self) -> None: ...
@property
def fps(self) -> float: ...
@property
def frame_size(self) -> tuple[int, int]: ...
@runtime_checkable
class Detector(Protocol):
"""Runs inference on a frame and returns detections."""
def detect(self, frame: np.ndarray) -> list[Detection]: ...
@runtime_checkable
class Tracker(Protocol):
"""Assigns persistent IDs to detections across frames."""
def update(
self, frame: np.ndarray, detections: list[Detection]
) -> list[Detection]: ...
def reset(self) -> None: ...
@runtime_checkable
class Counter(Protocol):
"""Counts objects crossing a virtual boundary."""
def update(self, detections: list[Detection]) -> None: ...
@property
def loading_count(self) -> int: ...
@property
def unloading_count(self) -> int: ...
def reset(self) -> None: ...
@runtime_checkable
class BatchManager(Protocol):
"""Manages batch lifecycle based on truck presence."""
def update(self, truck_detected: bool, timestamp: float) -> None: ...
@property
def current_batch_id(self) -> int | None: ...
@property
def is_active(self) -> bool: ...