Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |