""" Mouse-based region selector using OpenCV callbacks. This module provides the low-level RegionSelector class that handles mouse events for interactive region selection. """ from typing import Callable, List, Optional, Tuple import cv2 from .models import BBox, SelectionState # Type alias for key handler functions KeyHandler = Callable[[SelectionState, "RegionSelector"], None] class RegionSelector: """ Interactive region selector using OpenCV mouse callbacks. Supports two selection modes: - Two-click selection: click top-left, then click bottom-right - Click-and-drag selection: click, drag, and release """ def __init__(self, window_name: str, mode: str = "two_click"): """Initialize the region selector.""" self.window_name = window_name self.mode = mode # Two-click mode state self.points: List[Tuple[int, int]] = [] self.current_point: Optional[Tuple[int, int]] = None # Drag mode state self.start_point: Optional[Tuple[int, int]] = None self.end_point: Optional[Tuple[int, int]] = None self.drawing = False self.selection_complete = False def mouse_callback(self, event: int, x: int, y: int, _flags: int, _param: object) -> None: """Handle mouse events for region selection.""" if self.mode == "two_click": if event == cv2.EVENT_MOUSEMOVE: self.current_point = (x, y) elif event == cv2.EVENT_LBUTTONDOWN: self.points.append((x, y)) if len(self.points) >= 2: self.selection_complete = True else: if event == cv2.EVENT_LBUTTONDOWN: self.start_point = (x, y) self.end_point = (x, y) self.drawing = True self.selection_complete = False elif event == cv2.EVENT_MOUSEMOVE and self.drawing: self.end_point = (x, y) elif event == cv2.EVENT_LBUTTONUP: self.end_point = (x, y) self.drawing = False self.selection_complete = True def get_bbox(self) -> Optional[BBox]: """Get the selected bounding box.""" if self.mode == "two_click": if len(self.points) < 2: return None p1, p2 = self.points[0], self.points[1] else: if self.start_point is None or self.end_point is None: return None p1, p2 = self.start_point, self.end_point bbox = BBox.from_points(p1, p2) if bbox.width == 0 or bbox.height == 0: return None return bbox def reset(self) -> None: """Reset the selection state.""" self.points = [] self.current_point = None self.start_point = None self.end_point = None self.drawing = False self.selection_complete = False