File size: 3,036 Bytes
07392e1
 
 
 
 
 
 
719b8f7
07392e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719b8f7
07392e1
 
 
 
 
719b8f7
07392e1
 
 
 
 
 
 
 
 
719b8f7
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
91
92
93
94
95
96
97
"""
Pydantic models and data structures for the UI module.

This module contains all the data models used for region selection,
including bounding boxes, view configurations, and selection state.
"""

from typing import Any, List, Optional, Tuple

import numpy as np
from pydantic import BaseModel, ConfigDict


class BBox(BaseModel):
    """Bounding box with x, y, width, height coordinates."""

    x: int
    y: int
    width: int
    height: int

    @property
    def x2(self) -> int:
        """Get the right edge x coordinate."""
        return self.x + self.width

    @property
    def y2(self) -> int:
        """Get the bottom edge y coordinate."""
        return self.y + self.height

    def to_tuple(self) -> Tuple[int, int, int, int]:
        """Convert to (x, y, width, height) tuple."""
        return (self.x, self.y, self.width, self.height)

    def scaled(self, factor: int) -> "BBox":
        """Return a new BBox scaled by the given factor."""
        return BBox(x=self.x * factor, y=self.y * factor, width=self.width * factor, height=self.height * factor)

    def unscaled(self, factor: int) -> "BBox":
        """Return a new BBox divided by the given scale factor."""
        return BBox(x=self.x // factor, y=self.y // factor, width=self.width // factor, height=self.height // factor)

    def offset(self, dx: int, dy: int) -> "BBox":
        """Return a new BBox offset by dx, dy."""
        return BBox(x=self.x + dx, y=self.y + dy, width=self.width, height=self.height)

    @classmethod
    def from_points(cls, p1: Tuple[int, int], p2: Tuple[int, int]) -> "BBox":
        """Create BBox from two corner points."""
        x = min(p1[0], p2[0])
        y = min(p1[1], p2[1])
        w = abs(p2[0] - p1[0])
        h = abs(p2[1] - p1[1])
        return cls(x=x, y=y, width=w, height=h)

    @classmethod
    def from_tuple(cls, t: Tuple[int, int, int, int]) -> "BBox":
        """Create BBox from (x, y, width, height) tuple."""
        return cls(x=t[0], y=t[1], width=t[2], height=t[3])


class SelectionViewConfig(BaseModel):
    """Configuration for display/view settings during selection."""

    scale_factor: int = 1
    padding: int = 0
    window_width: int = 1280
    window_height: int = 720


class SelectionState(BaseModel):
    """Mutable state for a region selection session."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    frame_idx: int = 0
    frames: List[Tuple[float, np.ndarray[Any, Any]]] = []
    should_quit: bool = False
    should_confirm: bool = False
    video_path: Optional[str] = None

    @property
    def current_frame(self) -> Tuple[float, np.ndarray[Any, Any]]:
        """Get the current (timestamp, frame) tuple."""
        return self.frames[self.frame_idx]

    @property
    def timestamp(self) -> float:
        """Get the current frame timestamp."""
        return self.frames[self.frame_idx][0]

    @property
    def frame(self) -> np.ndarray[Any, Any]:
        """Get the current frame."""
        return self.frames[self.frame_idx][1]