""" Region extraction utilities for play clock digit processing. This module provides utilities for extracting digit regions from preprocessed play clock images. The regions are used for both template building and matching. Play clock display layouts: - Single-digit (0-9): Digit is CENTER-aligned, tens position is blank - Double-digit (10-40): Tens on LEFT, ones on RIGHT These utilities are shared across: - readers/playclock.py (template matching) - setup/template_builder.py (template building) """ from typing import Any import cv2 import numpy as np from .color import normalize_to_grayscale def extract_left_region(preprocessed: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]: """ Extract left region for tens digit in double-digit displays. Args: preprocessed: Preprocessed play clock image (scaled up) Returns: Left region image for tens digit """ _, w = preprocessed.shape[:2] mid_x = w // 2 return preprocessed[:, : mid_x - 2] # Small gap in middle def extract_right_region(preprocessed: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]: """ Extract right region for ones digit in double-digit displays. Args: preprocessed: Preprocessed play clock image (scaled up) Returns: Right region image for ones digit """ _, w = preprocessed.shape[:2] mid_x = w // 2 return preprocessed[:, mid_x + 2 :] def extract_center_region(preprocessed: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]: """ Extract center region for ones digit in single-digit displays. The center region spans approximately 60% of the width, centered. Args: preprocessed: Preprocessed play clock image (scaled up) Returns: Center region image for centered single digit """ _, w = preprocessed.shape[:2] center_start = int(w * 0.20) center_end = int(w * 0.80) return preprocessed[:, center_start:center_end] def extract_far_left_region(preprocessed: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]: """ Extract far left region for blank detection in single-digit displays. This narrow region (0%-20% of width) doesn't overlap with a centered digit, so it should be truly empty when the clock shows a single digit. Args: preprocessed: Preprocessed play clock image (scaled up) Returns: Far left region image (should be mostly black for single digits) """ _, w = preprocessed.shape[:2] far_left_end = int(w * 0.20) return preprocessed[:, :far_left_end] def preprocess_playclock_region(region: np.ndarray[Any, Any], scale_factor: int = 4) -> np.ndarray[Any, Any]: """ Preprocess play clock region for template matching or building. Uses color normalization to handle both red and white digits uniformly, then scales and binarizes the image. Args: region: Play clock region (BGR format) scale_factor: Scale factor for upscaling (default: 4) Returns: Preprocessed binary image (white digits on black background) """ # Normalize color (red → white conversion happens here) gray = normalize_to_grayscale(region) # Scale up for better template quality/matching scaled = cv2.resize(gray, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR) # Use Otsu's thresholding _, binary = cv2.threshold(scaled, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # Ensure white digits on black background (digits should be bright) mean_intensity = np.mean(np.asarray(binary)) if mean_intensity > 128: binary = cv2.bitwise_not(binary) return binary