Spaces:
Sleeping
Sleeping
| # Flag Detection Implementation Plan | |
| ## Overview | |
| This document outlines the implementation plan for detecting penalty flags ("FLAG" indicator) on the scorebug and using this information to enhance play capture. When a flag is thrown, the system should persist video capture beyond normal play boundaries to include the referee's penalty announcement. | |
| --- | |
| ## Requirements | |
| ### Core Requirements | |
| 1. **FLAG Region Selection**: User selects the "FLAG" region during initialization (same location as "1st and 10" marker on scorebug) | |
| 2. **Yellow Detection**: Search for strong yellow presence in the FLAG region throughout the video to detect when flags are thrown | |
| 3. **Decision Override**: FLAG detection should override any other play-capturing decisions: | |
| - If a flag is detected but it looks like "weird clock behavior" (normally rejected), capture it anyway | |
| - If a flag is detected during enforced quiet time, capture it anyway | |
| - **All FLAG events should be captured, no matter what** | |
| 4. **Extended Capture Duration**: When a flag is thrown, persist video capture until EITHER: | |
| - The yellow "FLAG" indicator disappears (scorebug is present but FLAG is gone) | |
| - **OR** a configurable timeout (default: 15 seconds) after the play normally ends | |
| - Note: If FLAG appears β replay β FLAG still visible when scorebug returns, continue capturing | |
| 5. **Pre-Play Penalties**: Capture penalties that occur before a play (false start, delay of game) even though no "real football play" occurred | |
| 6. **Single-Pass Detection**: FLAG detection must be done in the same pass as normal play detection (no second video pass) | |
| ### Expected FLAG Scenarios | |
| | Scenario | Expected Behavior | | |
| |----------|------------------| | |
| | Flag during play | Extend capture until FLAG clears or timeout | | |
| | Flag before play (false start, delay of game) | Capture as play even without snap | | |
| | Flag on replay | Ignore FLAG during replay (no scorebug) | | |
| | Multiple flags on same play | Capture extends to last FLAG clearing | | |
| | Flag during quiet time | Override quiet time and capture | | |
| | Flag with weird clock behavior | Override rejection and capture | | |
| --- | |
| ## Implementation Steps | |
| ### Step 1: Test Script for FLAG Region Selection β COMPLETE | |
| **Goal**: Create a short standalone script to interactively select the FLAG region and save it. | |
| **File**: `scripts/test_flag_region_selection.py` | |
| **Tasks**: | |
| - [x] Load sample frames from the test video | |
| - [x] Display frame with existing scorebug region highlighted for reference | |
| - [x] Prompt user to click/drag to select the FLAG region (typically where "1st & 10" appears) | |
| - [x] Save the selected region to `data/config/flag_region.json` | |
| - [x] Display a cropped preview of the selected region for verification | |
| **Output**: `data/config/flag_region.json` | |
| ```json | |
| { | |
| "flag_region": { | |
| "x_offset": 701, | |
| "y_offset": -27, | |
| "width": 260, | |
| "height": 35 | |
| } | |
| } | |
| ``` | |
| ### Step 2: Integrate FLAG Region into Main Pipeline | |
| **Goal**: Add FLAG region selection to the `main.py` interactive setup flow. | |
| **File Changes**: | |
| - `src/config/models.py` - Add FLAG region fields to `SessionConfig` | |
| - `src/ui/__init__.py` - Export new `select_flag_region` function | |
| - `src/ui/api.py` - Add `select_flag_region` function (similar to `select_playclock_region`) | |
| - `main.py` - Add FLAG region selection after timeout region selection (respect `--use-saved-regions`) | |
| **SessionConfig Additions**: | |
| ```python | |
| # FLAG region (relative to scorebug, like play clock) | |
| flag_x_offset: int = 0 | |
| flag_y_offset: int = 0 | |
| flag_width: int = 0 | |
| flag_height: int = 0 | |
| ``` | |
| ### Step 3: FLAG Detection Ground Truth Script β COMPLETE | |
| **Goal**: Create a script to scan the video and identify potential FLAG timestamps for manual verification. | |
| **File**: `scripts/find_flag_ground_truth.py` | |
| **Tasks**: | |
| - [x] Load FLAG region from config | |
| - [x] Scan video at 2 FPS | |
| - [x] For each frame, extract FLAG region and compute "yellow-ness" score | |
| - [x] Apply section-level filters (duration, peak yellow, avg yellow, mean hue) | |
| - [x] Merge sections into FLAG events with gap tolerance for replays | |
| - [x] Output candidates to `output/cache/flag_candidates.json` | |
| **Results** (from `docs/flag_ground_truth.md`): | |
| - **Precision**: 100% (8/8 true positives, 0 false positives) | |
| - **Recall**: 100% | |
| - **Key filters**: min 3s duration, peak>=70%, avg>=60%, hue>=22 (rejects orange) | |
| ### Step 4: Create FlagReader Module | |
| **Goal**: Implement the core FLAG reading logic. | |
| **File**: `src/readers/flags.py` (NOT detection/flags.py - FlagReader goes in readers) | |
| **Class**: `FlagReader` | |
| **Key Methods**: | |
| ```python | |
| class FlagReader: | |
| """Reads FLAG indicator from scorebug using yellow color detection.""" | |
| # Detection parameters (from ground truth analysis) | |
| YELLOW_HUE_MIN = 15 | |
| YELLOW_HUE_MAX = 35 | |
| YELLOW_SAT_MIN = 100 | |
| YELLOW_VAL_MIN = 100 | |
| YELLOW_RATIO_THRESHOLD = 0.25 | |
| MIN_MEAN_HUE = 22 # Distinguishes yellow from orange | |
| def __init__(self, flag_region_offset: Tuple[int, int, int, int]): | |
| """Initialize with configured FLAG region offset (relative to scorebug).""" | |
| def read(self, frame: np.ndarray, scorebug_bbox: Tuple[int, int, int, int]) -> FlagReading: | |
| """ | |
| Read FLAG status from a frame. | |
| Args: | |
| frame: Full video frame (BGR) | |
| scorebug_bbox: Current scorebug bounding box (x, y, w, h) | |
| Returns: | |
| FlagReading with detected status, yellow_ratio, and mean_hue | |
| """ | |
| ``` | |
| **Reading Model**: | |
| ```python | |
| class FlagReading(BaseModel): | |
| """Result of reading FLAG region.""" | |
| detected: bool | |
| yellow_ratio: float | |
| mean_hue: float | |
| is_valid_yellow: bool # True if mean_hue >= MIN_MEAN_HUE (not orange) | |
| ``` | |
| ### Step 5: Integrate FLAG Detection into Play Tracking | |
| **Goal**: Modify the play tracker to use FLAG detection for extended capture. | |
| **File Changes**: | |
| - `src/tracking/models.py` - Add `FlagInfo` model and flag-related fields to state | |
| - `src/tracking/play_tracker.py` - Add FLAG state tracking | |
| - `src/tracking/normal_play_tracker.py` - Modify end-of-play logic for FLAG override | |
| - `src/pipeline/play_extractor.py` - Integrate FlagReader into frame processing | |
| **Key Logic Changes**: | |
| 1. **FlagInfo Model**: | |
| ```python | |
| class FlagInfo(BaseModel): | |
| """Flag information for a frame.""" | |
| detected: bool = False | |
| yellow_ratio: float = 0.0 | |
| mean_hue: float = 0.0 | |
| is_valid_yellow: bool = False | |
| ``` | |
| 2. **State Tracking Additions**: | |
| ```python | |
| # In NormalTrackerState | |
| flag_detected_at: Optional[float] = None # When FLAG first appeared | |
| flag_last_seen_at: Optional[float] = None # Last frame with FLAG visible | |
| flag_cleared_at: Optional[float] = None # When FLAG disappeared (scorebug present, no FLAG) | |
| ``` | |
| 3. **Play End Override Logic**: | |
| ```python | |
| def _should_extend_for_flag(self, timestamp: float, scorebug_visible: bool, flag_detected: bool) -> bool: | |
| """ | |
| Determine if play capture should extend due to FLAG. | |
| Returns True if: | |
| - FLAG was seen during this play AND | |
| - Either FLAG is still visible OR we're within the flag timeout window | |
| """ | |
| ``` | |
| 4. **Override quiet time and weird clock behavior**: | |
| - If FLAG detected, ignore quiet_time restrictions | |
| - If FLAG detected, capture even if clock behavior looks weird | |
| ### Step 6: Add Configuration Options | |
| **Goal**: Make FLAG-related parameters configurable. | |
| **File Changes**: | |
| - `src/tracking/models.py` - Add to `TrackPlayStateConfig` | |
| **Configuration Additions**: | |
| ```python | |
| class TrackPlayStateConfig(BaseModel): | |
| # ... existing fields ... | |
| # FLAG detection settings | |
| flag_extension_timeout: float = Field(15.0, description="Max seconds to extend capture after FLAG detected") | |
| flag_yellow_threshold: float = Field(0.25, description="Yellow pixel ratio threshold for FLAG detection") | |
| flag_min_mean_hue: float = Field(22.0, description="Min mean hue to distinguish yellow from orange") | |
| capture_pre_play_penalties: bool = Field(True, description="Capture penalties without plays (false start, etc.)") | |
| ``` | |
| ### Step 7: Test FLAG Detection | |
| **Goal**: Validate FLAG detection against ground truth. | |
| **File**: `scripts/test_flag_detection.py` | |
| **Note**: Ground truth already validated in Step 3 with 100% precision/recall. | |
| ### Step 8: Integration Testing | |
| **Goal**: Test full pipeline with FLAG detection enabled. | |
| **Test Cases**: | |
| 1. Standard play with flag β extended capture includes penalty announcement | |
| 2. Pre-play penalty (false start) β captured as play | |
| 3. Multiple flags in quick succession β handles correctly | |
| 4. Flag during replay β not double-counted | |
| 5. Plays without flags β unchanged behavior | |
| --- | |
| ## File Structure After Implementation | |
| ``` | |
| src/ | |
| βββ config/ | |
| β βββ models.py # + flag region fields in SessionConfig | |
| βββ readers/ | |
| β βββ __init__.py # + export FlagReader | |
| β βββ flags.py # NEW: FlagReader, FlagReading | |
| β βββ models.py # + FlagReading model | |
| βββ tracking/ | |
| β βββ models.py # + FlagInfo, flag state fields | |
| β βββ play_tracker.py # + FLAG coordination | |
| β βββ normal_play_tracker.py # + FLAG override logic | |
| βββ ui/ | |
| β βββ __init__.py # + export select_flag_region | |
| β βββ api.py # + select_flag_region function | |
| βββ pipeline/ | |
| βββ play_extractor.py # + FlagReader integration | |
| data/config/ | |
| βββ flag_region.json # Default FLAG region config | |
| scripts/ | |
| βββ test_flag_region_selection.py # Step 1 β | |
| βββ find_flag_ground_truth.py # Step 3 β | |
| ``` | |
| --- | |
| ## Implementation Order | |
| | Step | Description | Dependencies | Status | | |
| |------|-------------|--------------|--------| | |
| | 1 | Test FLAG region selection script | None | β Complete | | |
| | 2 | Integrate into main.py | Step 1 | In Progress | | |
| | 3 | Ground truth scanning script | Step 1 | β Complete | | |
| | 4 | FlagReader module | Step 1 | In Progress | | |
| | 5 | Play tracker integration | Steps 3, 4 | In Progress | | |
| | 6 | Configuration options | Step 5 | In Progress | | |
| | 7 | Detection testing | Steps 3, 4 | Skipped (100% in Step 3) | | |
| | 8 | Integration testing | Steps 5, 6 | Pending | | |
| --- | |
| ## Success Criteria | |
| 1. **Region Selection**: User can select FLAG region in <30 seconds during setup β | |
| 2. **Detection Accuracy**: β₯90% recall on verified ground truth FLAGS β (100%) | |
| 3. **False Positive Rate**: <10% of detected FLAGS are false positives β (0%) | |
| 4. **Extended Capture**: FLAG plays include penalty announcement in 90%+ of cases | |
| 5. **Pre-Play Penalties**: False starts and delay of game penalties captured | |
| 6. **No Regressions**: Existing play detection performance unchanged for non-FLAG plays | |
| 7. **Single Pass**: No second video pass required β (integrated into main processing) | |
| --- | |
| ## Validated Detection Parameters | |
| From ground truth analysis (`docs/flag_ground_truth.md`): | |
| ```python | |
| # Yellow detection (HSV color space) | |
| YELLOW_HUE_MIN = 15 | |
| YELLOW_HUE_MAX = 35 | |
| YELLOW_SAT_MIN = 100 | |
| YELLOW_VAL_MIN = 100 | |
| # Detection threshold | |
| YELLOW_RATIO_THRESHOLD = 0.25 # 25% of region must be yellow | |
| # Orange discrimination | |
| MIN_MEAN_HUE = 22 # Rejects orange (hue ~16-17), accepts yellow (hue ~28-29) | |
| ``` | |
| ### Orange vs Yellow Discrimination | |
| | Metric | True FLAG Yellow | False Positive Orange | | |
| |--------|-----------------|----------------------| | |
| | Mean Hue | 27-29 | 16-17 | | |
| | Mean Value | 205-207 (bright) | 161-162 (darker) | | |
| The MIN_MEAN_HUE filter is essential for distinguishing true FLAG yellow from team colors (e.g., Tennessee orange). | |