File size: 2,945 Bytes
07392e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f3bac1
07392e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f3bac1
07392e1
 
 
 
 
 
 
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
"""
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