File size: 9,699 Bytes
aee009f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
"""
Test the padded region approach for shift-invariant play clock reading.

Compares clock detection rates with different padding values to determine
the optimal padding for handling translational shifts in the broadcast.

Usage:
    python scripts/test_padded_playclock.py
"""

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

import cv2

# Add src to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from readers import ReadPlayClock
from setup import DigitTemplateLibrary
from detection.scorebug import DetectScoreBug

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


def seconds_to_timestamp(seconds: float) -> str:
    """Convert seconds to timestamp string (H:MM:SS)."""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    if hours > 0:
        return f"{hours}:{minutes:02d}:{secs:02d}"
    return f"{minutes}:{secs:02d}"


def load_oregon_config() -> Dict[str, Any]:
    """Load the saved config for Oregon video."""
    config_path = Path("output/OSU_vs_Oregon_01_01_25_config.json")
    with open(config_path, "r") as f:
        return json.load(f)


def test_padding_values(
    video_path: str,
    playclock_coords: Tuple[int, int, int, int],
    scorebug_coords: Tuple[int, int, int, int],
    template_dir: str,
    template_path: str,
    padding_values: List[int],
    start_time: float,
    end_time: float,
    sample_interval: float = 5.0,
) -> Dict[int, Dict[str, Any]]:
    """
    Test different padding values and compare detection rates.

    Args:
        video_path: Path to video file
        playclock_coords: Absolute play clock coordinates (x, y, w, h)
        scorebug_coords: Scorebug coordinates (x, y, w, h)
        template_dir: Path to digit templates
        template_path: Path to scorebug template
        padding_values: List of padding values to test
        start_time: Start time in seconds
        end_time: End time in seconds
        sample_interval: Seconds between samples

    Returns:
        Dictionary mapping padding value to results
    """
    # Load template library
    logger.info("Loading template library from %s", template_dir)
    library = DigitTemplateLibrary()
    if not library.load(template_dir):
        raise RuntimeError(f"Failed to load templates from {template_dir}")

    # Create play clock reader
    pc_w, pc_h = playclock_coords[2], playclock_coords[3]
    reader = ReadPlayClock(library, region_width=pc_w, region_height=pc_h)

    # Create scorebug detector
    scorebug_detector = DetectScoreBug(
        template_path=template_path,
        fixed_region=scorebug_coords,
        use_split_detection=True,
    )

    # Open video
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        raise RuntimeError(f"Failed to open video: {video_path}")

    fps = cap.get(cv2.CAP_PROP_FPS)

    # Collect sample timestamps
    timestamps = []
    current_time = start_time
    while current_time < end_time:
        timestamps.append(current_time)
        current_time += sample_interval

    logger.info("Testing %d padding values on %d samples from %s to %s", len(padding_values), len(timestamps), seconds_to_timestamp(start_time), seconds_to_timestamp(end_time))

    # Results for each padding value
    results: Dict[int, Dict[str, Any]] = {}

    for padding in padding_values:
        detections = 0
        scorebug_present = 0
        total_confidence = 0.0
        sample_results: List[Dict[str, Any]] = []

        for ts in timestamps:
            frame_num = int(ts * fps)
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
            ret, frame = cap.read()

            if not ret or frame is None:
                continue

            # Check scorebug
            sb_result = scorebug_detector.detect(frame)
            if not sb_result.detected:
                continue

            scorebug_present += 1

            # Try play clock reading with this padding
            pc_result = reader.read_from_fixed_location(frame, playclock_coords, padding=padding)

            sample_result = {
                "timestamp": ts,
                "timestamp_str": seconds_to_timestamp(ts),
                "detected": pc_result.detected,
                "value": pc_result.value,
                "confidence": pc_result.confidence,
            }
            sample_results.append(sample_result)

            if pc_result.detected:
                detections += 1
                total_confidence += pc_result.confidence

        detection_rate = (detections / scorebug_present * 100) if scorebug_present > 0 else 0
        avg_confidence = (total_confidence / detections) if detections > 0 else 0

        results[padding] = {
            "padding": padding,
            "samples": len(timestamps),
            "scorebug_present": scorebug_present,
            "detections": detections,
            "detection_rate": detection_rate,
            "avg_confidence": avg_confidence,
            "sample_results": sample_results,
        }

        logger.info(
            "  padding=%d: %d/%d detected (%.1f%%), avg conf=%.3f",
            padding,
            detections,
            scorebug_present,
            detection_rate,
            avg_confidence,
        )

    cap.release()
    return results


def main():
    """Main function to test padded play clock reading."""
    config = load_oregon_config()

    video_path = config["video_path"]
    template_path = config["template_path"]

    playclock_coords = (
        config["scorebug_x"] + config["playclock_x_offset"],
        config["scorebug_y"] + config["playclock_y_offset"],
        config["playclock_width"],
        config["playclock_height"],
    )

    scorebug_coords = (
        config["scorebug_x"],
        config["scorebug_y"],
        config["scorebug_width"],
        config["scorebug_height"],
    )

    # Padding values to test (in pixels)
    padding_values = [0, 2, 3, 4, 5, 6, 8, 10]

    logger.info("=" * 70)
    logger.info("PADDED PLAY CLOCK REGION TEST")
    logger.info("=" * 70)
    logger.info("Video: %s", video_path)
    logger.info("Play clock region: %s", playclock_coords)
    logger.info("")

    # Test on the problematic segment (after 12 min where templates fail)
    logger.info("SEGMENT 1: After 12-minute mark (known failure zone)")
    logger.info("-" * 70)
    results_late = test_padding_values(
        video_path=video_path,
        playclock_coords=playclock_coords,
        scorebug_coords=scorebug_coords,
        template_dir="output/debug/digit_templates",
        template_path=template_path,
        padding_values=padding_values,
        start_time=900.0,  # 15:00
        end_time=2400.0,  # 40:00
        sample_interval=10.0,  # Sample every 10 seconds
    )

    # Also test on early segment to ensure no regression
    logger.info("")
    logger.info("SEGMENT 2: Early game (should already work)")
    logger.info("-" * 70)
    results_early = test_padding_values(
        video_path=video_path,
        playclock_coords=playclock_coords,
        scorebug_coords=scorebug_coords,
        template_dir="output/debug/digit_templates",
        template_path=template_path,
        padding_values=padding_values,
        start_time=330.0,  # 5:30
        end_time=720.0,  # 12:00
        sample_interval=10.0,
    )

    # Print summary
    logger.info("")
    logger.info("=" * 70)
    logger.info("SUMMARY")
    logger.info("=" * 70)
    logger.info("")
    logger.info("%-10s | %-25s | %-25s", "Padding", "Late Game (15:00-40:00)", "Early Game (5:30-12:00)")
    logger.info("-" * 70)

    for padding in padding_values:
        late = results_late[padding]
        early = results_early[padding]
        logger.info(
            "%-10d | %3d/%3d = %5.1f%% (conf %.2f) | %3d/%3d = %5.1f%% (conf %.2f)",
            padding,
            late["detections"],
            late["scorebug_present"],
            late["detection_rate"],
            late["avg_confidence"],
            early["detections"],
            early["scorebug_present"],
            early["detection_rate"],
            early["avg_confidence"],
        )

    # Find best padding
    best_padding = max(padding_values, key=lambda p: results_late[p]["detection_rate"])
    best_late = results_late[best_padding]
    best_early = results_early[best_padding]

    logger.info("")
    logger.info("RECOMMENDATION:")
    logger.info(
        "  Best padding = %d pixels (late game: %.1f%%, early game: %.1f%%)",
        best_padding,
        best_late["detection_rate"],
        best_early["detection_rate"],
    )

    # Check for false positive risk - confidence should stay high
    if best_late["avg_confidence"] < 0.6:
        logger.warning("  WARNING: Average confidence is low (%.2f) - may indicate false positives", best_late["avg_confidence"])

    # Save detailed results
    output_path = Path("output/debug/padding_test_results.json")
    output_path.parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, "w") as f:
        json.dump(
            {
                "late_game": {str(k): {kk: vv for kk, vv in v.items() if kk != "sample_results"} for k, v in results_late.items()},
                "early_game": {str(k): {kk: vv for kk, vv in v.items() if kk != "sample_results"} for k, v in results_early.items()},
                "recommendation": {"best_padding": best_padding},
            },
            f,
            indent=2,
        )
    logger.info("")
    logger.info("Detailed results saved to: %s", output_path)


if __name__ == "__main__":
    main()