File size: 9,459 Bytes
220e5fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1386d71
220e5fb
 
 
 
 
 
6c65498
 
 
220e5fb
1cd70c6
 
 
220e5fb
 
 
 
4267e68
 
 
 
220e5fb
 
 
 
 
 
 
 
6c65498
220e5fb
 
 
 
 
 
6c65498
220e5fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c65498
220e5fb
 
 
 
 
6c65498
220e5fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c65498
220e5fb
 
 
 
 
 
 
 
 
6c65498
220e5fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c65498
 
220e5fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#!/usr/bin/env python3
"""
Test script to validate play clock reading accuracy.

This script extracts frames with varying play clock values from a video segment,
runs the PlayClockReader on each, and outputs a comparison grid for visual inspection.

Usage:
    python scripts/test_play_clock_reader.py

Output:
    - Console output with reading results
    - Visual grid saved to output/play_clock_test_results.png
"""

import logging
import sys
from pathlib import Path
from typing import List, Tuple, Any

import cv2
import numpy as np

from detection import DetectScoreBug
from readers import PlayClockReading
from setup import PlayClockRegionExtractor

# Path reference for constants
PROJECT_ROOT = Path(__file__).parent.parent.parent

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# Constants
VIDEO_PATH = PROJECT_ROOT / "full_videos" / "OSU vs Tenn 12.21.24.mkv"
TEMPLATE_PATH = PROJECT_ROOT / "data" / "templates" / "scorebug_template_main.png"
CONFIG_PATH = PROJECT_ROOT / "data" / "config" / "play_clock_region.json"
OUTPUT_DIR = PROJECT_ROOT / "output"

# Test segment: 38:40 to 41:40 (3 minutes = ~5 plays)
START_TIME_SECONDS = 38 * 60 + 40  # 38:40
END_TIME_SECONDS = 41 * 60 + 40  # 41:40
SAMPLE_INTERVAL_SECONDS = 0.5  # Sample every 0.5 seconds for detailed analysis


def extract_test_frames(
    video_path: Path, detector: DetectScoreBug, start_time: float, end_time: float, interval: float, max_frames: int = 50
) -> List[Tuple[float, Any, Tuple[int, int, int, int]]]:
    """
    Extract frames from video where scorebug is detected for testing.

    Args:
        video_path: Path to video file
        detector: DetectScoreBug instance
        start_time: Start time in seconds
        end_time: End time in seconds
        interval: Sampling interval in seconds
        max_frames: Maximum number of frames to extract

    Returns:
        List of (timestamp, frame, scorebug_bbox) tuples
    """
    cap = cv2.VideoCapture(str(video_path))
    if not cap.isOpened():
        raise ValueError("Could not open video: %s" % video_path)

    fps = cap.get(cv2.CAP_PROP_FPS)
    logger.info("Video FPS: %.2f", fps)

    frames = []
    current_time = start_time

    while current_time < end_time and len(frames) < max_frames:
        # Seek to current time
        frame_number = int(current_time * fps)
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)

        ret, frame = cap.read()
        if not ret:
            current_time += interval
            continue

        # Detect scorebug
        detection = detector.detect(frame)
        if detection.detected and detection.bbox:
            frames.append((current_time, frame, detection.bbox))

        current_time += interval

    cap.release()
    logger.info("Extracted %d frames with scorebug", len(frames))
    return frames


def run_reading_tests(frames: List[Tuple[float, Any, Tuple[int, int, int, int]]], reader: PlayClockRegionExtractor) -> List[Tuple[float, PlayClockReading, Any]]:
    """
    Run play clock reader on all extracted frames.

    Args:
        frames: List of (timestamp, frame, scorebug_bbox) tuples
        reader: PlayClockRegionExtractor instance

    Returns:
        List of (timestamp, reading, frame) tuples
    """
    results = []
    for timestamp, frame, scorebug_bbox in frames:
        reading = reader.read(frame, scorebug_bbox)
        results.append((timestamp, reading, frame))

        # Log each reading
        if reading.detected:
            logger.info("%.1fs: Clock = %d (conf: %.0f%%)", timestamp, reading.value, reading.confidence * 100)
        else:
            logger.warning("%.1fs: Failed to read (raw: '%s')", timestamp, reading.raw_text)

    return results


def create_visualization_grid(
    results: List[Tuple[float, PlayClockReading, Any]],
    reader: PlayClockRegionExtractor,
    scorebug_bboxes: List[Tuple[int, int, int, int]],
    output_path: Path,
    grid_cols: int = 5,
) -> None:
    """
    Create a visual grid of test results for easy inspection.

    Args:
        results: List of (timestamp, reading, frame) tuples
        reader: PlayClockRegionExtractor for visualization
        scorebug_bboxes: List of scorebug bounding boxes
        output_path: Path to save the visualization
        grid_cols: Number of columns in the grid
    """
    if not results:
        logger.warning("No results to visualize")
        return

    # Calculate grid dimensions
    n_images = len(results)
    grid_rows = (n_images + grid_cols - 1) // grid_cols

    # Get thumbnail size (scale down for grid)
    thumbnail_width = 384
    thumbnail_height = 216

    # Create grid canvas
    grid_width = grid_cols * thumbnail_width
    grid_height = grid_rows * thumbnail_height
    grid_image = np.zeros((grid_height, grid_width, 3), dtype=np.uint8)

    # Fill grid with thumbnails
    for idx, (timestamp, reading, frame) in enumerate(results):
        row = idx // grid_cols
        col = idx % grid_cols

        # Get corresponding scorebug bbox
        scorebug_bbox = scorebug_bboxes[idx]

        # Create visualization with play clock region highlighted
        vis_frame = reader.visualize_region(frame, scorebug_bbox, reading)

        # Add timestamp and reading to frame
        status_color = (0, 255, 0) if reading.detected else (0, 0, 255)
        cv2.putText(vis_frame, "%.1fs" % timestamp, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, status_color, 2)
        if reading.detected:
            cv2.putText(vis_frame, "Clock: %d" % reading.value, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1.0, status_color, 2)
        else:
            cv2.putText(vis_frame, "FAILED", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1.0, status_color, 2)

        # Resize to thumbnail
        thumbnail = cv2.resize(vis_frame, (thumbnail_width, thumbnail_height))

        # Place in grid
        y1 = row * thumbnail_height
        y2 = y1 + thumbnail_height
        x1 = col * thumbnail_width
        x2 = x1 + thumbnail_width
        grid_image[y1:y2, x1:x2] = thumbnail

    # Save grid
    output_path.parent.mkdir(parents=True, exist_ok=True)
    cv2.imwrite(str(output_path), grid_image)
    logger.info("Saved visualization grid to %s", output_path)


def print_summary(results: List[Tuple[float, PlayClockReading, Any]]) -> None:
    """Print summary statistics of the test results."""
    total = len(results)
    detected = sum(1 for _, r, _ in results if r.detected)
    failed = total - detected

    logger.info("=" * 50)
    logger.info("SUMMARY")
    logger.info("=" * 50)
    logger.info("Total frames tested: %d", total)
    logger.info("Successfully read: %d (%.1f%%)", detected, 100 * detected / total if total > 0 else 0)
    logger.info("Failed to read: %d (%.1f%%)", failed, 100 * failed / total if total > 0 else 0)

    if detected > 0:
        confidences = [r.confidence for _, r, _ in results if r.detected]
        avg_conf = sum(confidences) / len(confidences)
        min_conf = min(confidences)
        max_conf = max(confidences)
        logger.info("Confidence: avg=%.1f%%, min=%.1f%%, max=%.1f%%", avg_conf * 100, min_conf * 100, max_conf * 100)

        values = [r.value for _, r, _ in results if r.detected and r.value is not None]
        if values:
            logger.info("Clock values: min=%d, max=%d", min(values), max(values))

    # Print sequence of readings for pattern analysis
    logger.info("-" * 50)
    logger.info("Reading sequence (for pattern analysis):")
    sequence = []
    for _, reading, _ in results:
        if reading.detected:
            sequence.append("%d" % reading.value)
        else:
            sequence.append("?")
    logger.info(" -> ".join(sequence))


def main():
    """Main entry point for play clock reader testing."""
    logger.info("Play Clock Reader Test")
    logger.info("=" * 50)

    # Verify paths exist
    if not VIDEO_PATH.exists():
        logger.error("Video not found: %s", VIDEO_PATH)
        return 1

    if not TEMPLATE_PATH.exists():
        logger.error("Template not found: %s", TEMPLATE_PATH)
        return 1

    if not CONFIG_PATH.exists():
        logger.error("Play clock region config not found: %s", CONFIG_PATH)
        logger.info("Run identify_play_clock_region.py first to create the config")
        return 1

    # Initialize detectors
    logger.info("Initializing detectors...")
    scorebug_detector = DetectScoreBug(template_path=str(TEMPLATE_PATH))
    play_clock_reader = PlayClockRegionExtractor(region_config_path=str(CONFIG_PATH))

    # Extract test frames
    logger.info("Extracting test frames from %.1fs to %.1fs...", START_TIME_SECONDS, END_TIME_SECONDS)
    frames = extract_test_frames(VIDEO_PATH, scorebug_detector, START_TIME_SECONDS, END_TIME_SECONDS, SAMPLE_INTERVAL_SECONDS)

    if not frames:
        logger.error("No frames with scorebug detected!")
        return 1

    # Run reading tests
    logger.info("Running play clock reader on %d frames...", len(frames))
    results = run_reading_tests(frames, play_clock_reader)

    # Print summary
    print_summary(results)

    # Create visualization
    scorebug_bboxes = [bbox for _, _, bbox in frames]
    output_path = OUTPUT_DIR / "play_clock_test_results.png"
    create_visualization_grid(results, play_clock_reader, scorebug_bboxes, output_path)

    logger.info("Test complete!")
    return 0


if __name__ == "__main__":
    sys.exit(main())