cfb40 / src /ui /selector.py
andytaylor-smg's picture
restoring missing clock reset detection functions
1f3bac1
"""
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