""" Pydantic models for per-frame detection. These models represent the results of detecting various elements in video frames: scorebug presence and timeout indicators. """ from typing import Any, Optional, Tuple, List from pydantic import BaseModel, Field class ScorebugDetection(BaseModel): """Results from scorebug detection.""" detected: bool = Field(..., description="Whether scorebug was detected (for play tracking)") confidence: float = Field(..., description="Confidence score (0.0 to 1.0)") bbox: Optional[Tuple[int, int, int, int]] = Field(None, description="Bounding box (x, y, width, height)") method: str = Field("unknown", description="Detection method used") left_confidence: Optional[float] = Field(None, description="Left half confidence (when split detection enabled)") right_confidence: Optional[float] = Field(None, description="Right half confidence (when split detection enabled)") # In fixed coords mode, detected=True (assumed present for play tracking), # but template_matched indicates actual visibility (for special play end detection) template_matched: Optional[bool] = Field(None, description="Actual template match result (None if not using fixed coords mode)") class TimeoutRegionConfig(BaseModel): """Configuration for a team's timeout indicator region.""" team_name: str = Field(..., description="'home' or 'away'") bbox: Tuple[int, int, int, int] = Field(..., description="x, y, width, height for the 3-oval group") def to_dict(self) -> dict[str, object]: """Convert to dictionary for JSON serialization.""" return { "team_name": self.team_name, "bbox": {"x": self.bbox[0], "y": self.bbox[1], "width": self.bbox[2], "height": self.bbox[3]}, } @classmethod def from_dict(cls, data: dict[str, Any]) -> "TimeoutRegionConfig": """Create from dictionary.""" bbox = (data["bbox"]["x"], data["bbox"]["y"], data["bbox"]["width"], data["bbox"]["height"]) return cls(team_name=data["team_name"], bbox=bbox) class OvalLocation(BaseModel): """Precise location of a single timeout oval within a team's timeout region.""" x: int = Field(..., description="X position within timeout region (relative to region top-left)") y: int = Field(..., description="Y position within timeout region (relative to region top-left)") width: int = Field(..., description="Width of the oval bounding box") height: int = Field(..., description="Height of the oval bounding box") baseline_brightness: float = Field(0.0, description="Mean brightness when oval is 'available' (calibrated)") def to_dict(self) -> dict[str, object]: """Convert to dictionary for JSON serialization.""" return { "x": self.x, "y": self.y, "width": self.width, "height": self.height, "baseline_brightness": self.baseline_brightness, } @classmethod def from_dict(cls, data: dict[str, Any]) -> "OvalLocation": """Create from dictionary.""" return cls( x=data["x"], y=data["y"], width=data["width"], height=data["height"], baseline_brightness=data.get("baseline_brightness", 0.0), ) class CalibratedTimeoutRegion(BaseModel): """Timeout region with discovered oval sub-coordinates from calibration.""" team_name: str = Field(..., description="'home' or 'away'") bbox: Tuple[int, int, int, int] = Field(..., description="Overall region: x, y, width, height") ovals: List[OvalLocation] = Field(default_factory=list, description="3 oval sub-coordinates within the region") calibration_timestamp: float = Field(0.0, description="Video timestamp when calibration was performed") def to_dict(self) -> dict[str, object]: """Convert to dictionary for JSON serialization.""" return { "team_name": self.team_name, "bbox": {"x": self.bbox[0], "y": self.bbox[1], "width": self.bbox[2], "height": self.bbox[3]}, "ovals": [oval.to_dict() for oval in self.ovals], "calibration_timestamp": self.calibration_timestamp, } @classmethod def from_dict(cls, data: dict[str, Any]) -> "CalibratedTimeoutRegion": """Create from dictionary.""" bbox = (data["bbox"]["x"], data["bbox"]["y"], data["bbox"]["width"], data["bbox"]["height"]) ovals = [OvalLocation.from_dict(o) for o in data.get("ovals", [])] return cls( team_name=data["team_name"], bbox=bbox, ovals=ovals, calibration_timestamp=data.get("calibration_timestamp", 0.0), ) class TimeoutReading(BaseModel): """Results from timeout indicator reading.""" home_timeouts: int = Field(..., description="0-3 timeouts remaining") away_timeouts: int = Field(..., description="0-3 timeouts remaining") confidence: float = Field(..., description="Overall confidence in reading") home_oval_states: Optional[List[bool]] = Field(None, description="True = white (available), False = dark (used)") away_oval_states: Optional[List[bool]] = Field(None, description="True = white (available), False = dark (used)")