cfb40 / src /detection /models.py
andytaylor-smg's picture
in a good spot, I think
5d257ae
"""
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)")