pixpick 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pixpick-0.1.0/LICENSE +21 -0
- pixpick-0.1.0/PKG-INFO +164 -0
- pixpick-0.1.0/README.md +143 -0
- pixpick-0.1.0/pixpick/__init__.py +106 -0
- pixpick-0.1.0/pixpick/backends/base.py +46 -0
- pixpick-0.1.0/pixpick/backends/cv2_backend.py +269 -0
- pixpick-0.1.0/pixpick/core/selection.py +362 -0
- pixpick-0.1.0/pixpick/selectors/box.py +64 -0
- pixpick-0.1.0/pixpick/selectors/polygon.py +60 -0
- pixpick-0.1.0/pixpick/utils.py +33 -0
- pixpick-0.1.0/pixpick.egg-info/PKG-INFO +164 -0
- pixpick-0.1.0/pixpick.egg-info/SOURCES.txt +15 -0
- pixpick-0.1.0/pixpick.egg-info/dependency_links.txt +1 -0
- pixpick-0.1.0/pixpick.egg-info/requires.txt +2 -0
- pixpick-0.1.0/pixpick.egg-info/top_level.txt +1 -0
- pixpick-0.1.0/pyproject.toml +32 -0
- pixpick-0.1.0/setup.cfg +4 -0
pixpick-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Saif Khan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pixpick-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pixpick
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive coordinate picker for Computer Vision frameworks
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/K-saif/pixpick
|
|
7
|
+
Project-URL: Documentation, https://github.com/K-saif/pixpick/tree/main/docs
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/K-saif/pixpick/issues
|
|
9
|
+
Keywords: computer-vision,yolo,sam2,supervision,roi,annotation
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: opencv-python>=4.5
|
|
19
|
+
Requires-Dist: numpy>=1.21
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
<div align="center">
|
|
23
|
+
|
|
24
|
+
# pixpick 🎯
|
|
25
|
+
|
|
26
|
+
**Interactive coordinate picker for Computer Vision — no external tools needed.**
|
|
27
|
+
|
|
28
|
+
[](https://badge.fury.io/py/pixpick)
|
|
29
|
+
[](https://www.python.org/downloads/)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## The problem
|
|
37
|
+
|
|
38
|
+
Every major CV framework needs coordinates before it can run.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
regioncounter = RegionCounter(region=[120, 80, 640, 480]) # YOLO — where does this region come from?
|
|
42
|
+
predictor.predict(box=np.array([120, 80, 640, 480])) # SAM2 — same problem
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The standard workflow: open CVAT or Roboflow → grab coordinates → paste them back into code. Every. Single. Time.
|
|
46
|
+
|
|
47
|
+
## The fix
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import pixpick
|
|
51
|
+
|
|
52
|
+
region = pixpick.box("frame.jpg") # drag a box on the image
|
|
53
|
+
zone = pixpick.polygon("frame.jpg") # click polygon vertices
|
|
54
|
+
|
|
55
|
+
# coordinates are ready — unpack directly into any framework
|
|
56
|
+
# YOLO:
|
|
57
|
+
regioncounter = RegionCounter(
|
|
58
|
+
region=region.yolo_region, # pass region points
|
|
59
|
+
model="yolo26n.pt",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# same for YOLOE
|
|
63
|
+
model.predict("frame.jpg", visual_prompt= region.yolo_prompt())
|
|
64
|
+
|
|
65
|
+
# SAM1/SAM2:
|
|
66
|
+
predictor.predict(box=region.sam())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A window opens on your image. You interact. You get framework-ready coordinates back in Python. No round-trips.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install pixpick
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Selectors
|
|
82
|
+
|
|
83
|
+
| Selector | How to use | Returns |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `pixpick.box()` | Left-click + drag | `Box` |
|
|
86
|
+
| `pixpick.polygon()` | Click vertices → `Enter` to confirm | `Polygon` |
|
|
87
|
+
|
|
88
|
+
**Box controls** — `drag` to draw · `R` to reset · `Esc` to cancel
|
|
89
|
+
|
|
90
|
+
**Polygon controls** — `LMB` add point · `RMB` undo · `Z` clear · `Enter` confirm · `Esc` cancel
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Output formats
|
|
95
|
+
|
|
96
|
+
Every selection object carries all the formats you'll ever need.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# ── Box ──────────────────────────────────────────────────────
|
|
100
|
+
region = pixpick.box("frame.jpg")
|
|
101
|
+
|
|
102
|
+
region.xyxy # [x1, y1, x2, y2] absolute pixels
|
|
103
|
+
region.xywh # [x, y, w, h] absolute pixels
|
|
104
|
+
region.norm_xywh # [x, y, w, h] 0.0 – 1.0 ← YOLO label format
|
|
105
|
+
region.center # (cx, cy)
|
|
106
|
+
region.area # pixels²
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ── Polygon ───────────────────────────────────────────────────
|
|
110
|
+
zone = pixpick.polygon("frame.jpg")
|
|
111
|
+
|
|
112
|
+
zone.points # [(x0,y0), (x1,y1), ...] absolute pixels
|
|
113
|
+
zone.as_numpy # np.array shape (N, 2)
|
|
114
|
+
zone.norm # [(x0n,y0n), ...] 0.0 – 1.0
|
|
115
|
+
zone.bbox # → Box tight bbox around the polygon
|
|
116
|
+
zone.npoints # int
|
|
117
|
+
```
|
|
118
|
+
For more details, see [Selectors](docs/selectors.md).
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Framework integration
|
|
122
|
+
|
|
123
|
+
| Framework | Selector | Method |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Ultralytics YOLOE — visual prompt | `Box` | `region.yolo_prompt()` |
|
|
126
|
+
| Ultralytics YOLO — region | `Box` | `region.yolo_region()` |
|
|
127
|
+
| SAM / SAM2 — box prompt | `Box` | `region.sam()` |
|
|
128
|
+
| Any other format | `Box` / `Polygon` | `region.to_raw()` |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Persistence
|
|
133
|
+
|
|
134
|
+
Pick once, reuse forever.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
region.save("zone.json")
|
|
138
|
+
region = pixpick.load("zone.json") # Box and Polygon both work
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Production pattern — pick interactively the first time, load on every subsequent run:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from pathlib import Path
|
|
145
|
+
import pixpick
|
|
146
|
+
|
|
147
|
+
ZONE = "config/count_zone.json"
|
|
148
|
+
|
|
149
|
+
zone = pixpick.load(ZONE) if Path(ZONE).exists() else pixpick.polygon("frame.jpg")
|
|
150
|
+
zone.save(ZONE)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Docs
|
|
156
|
+
|
|
157
|
+
| | |
|
|
158
|
+
|---|---|
|
|
159
|
+
| 🚀 [Getting Started](docs/getting-started.md) | Installation, first selection, controls |
|
|
160
|
+
| 🎯 [Selectors](docs/selectors.md) | All properties and methods for Box and Polygon |
|
|
161
|
+
| 🔌 [Framework Integration](docs/frameworks.md) | YOLO, SAM2 and more |
|
|
162
|
+
| 💾 [Persistence](docs/persistence.md) | Save, load, JSON schema |
|
|
163
|
+
| 🏗️ [Architecture](docs/architecture.md) | How it's built and how to extend it |
|
|
164
|
+
| 🗺️ [Roadmap](docs/roadmap.md) | What's coming next |
|
pixpick-0.1.0/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# pixpick 🎯
|
|
4
|
+
|
|
5
|
+
**Interactive coordinate picker for Computer Vision — no external tools needed.**
|
|
6
|
+
|
|
7
|
+
[](https://badge.fury.io/py/pixpick)
|
|
8
|
+
[](https://www.python.org/downloads/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The problem
|
|
16
|
+
|
|
17
|
+
Every major CV framework needs coordinates before it can run.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
regioncounter = RegionCounter(region=[120, 80, 640, 480]) # YOLO — where does this region come from?
|
|
21
|
+
predictor.predict(box=np.array([120, 80, 640, 480])) # SAM2 — same problem
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The standard workflow: open CVAT or Roboflow → grab coordinates → paste them back into code. Every. Single. Time.
|
|
25
|
+
|
|
26
|
+
## The fix
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import pixpick
|
|
30
|
+
|
|
31
|
+
region = pixpick.box("frame.jpg") # drag a box on the image
|
|
32
|
+
zone = pixpick.polygon("frame.jpg") # click polygon vertices
|
|
33
|
+
|
|
34
|
+
# coordinates are ready — unpack directly into any framework
|
|
35
|
+
# YOLO:
|
|
36
|
+
regioncounter = RegionCounter(
|
|
37
|
+
region=region.yolo_region, # pass region points
|
|
38
|
+
model="yolo26n.pt",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# same for YOLOE
|
|
42
|
+
model.predict("frame.jpg", visual_prompt= region.yolo_prompt())
|
|
43
|
+
|
|
44
|
+
# SAM1/SAM2:
|
|
45
|
+
predictor.predict(box=region.sam())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A window opens on your image. You interact. You get framework-ready coordinates back in Python. No round-trips.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install pixpick
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Selectors
|
|
61
|
+
|
|
62
|
+
| Selector | How to use | Returns |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `pixpick.box()` | Left-click + drag | `Box` |
|
|
65
|
+
| `pixpick.polygon()` | Click vertices → `Enter` to confirm | `Polygon` |
|
|
66
|
+
|
|
67
|
+
**Box controls** — `drag` to draw · `R` to reset · `Esc` to cancel
|
|
68
|
+
|
|
69
|
+
**Polygon controls** — `LMB` add point · `RMB` undo · `Z` clear · `Enter` confirm · `Esc` cancel
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Output formats
|
|
74
|
+
|
|
75
|
+
Every selection object carries all the formats you'll ever need.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# ── Box ──────────────────────────────────────────────────────
|
|
79
|
+
region = pixpick.box("frame.jpg")
|
|
80
|
+
|
|
81
|
+
region.xyxy # [x1, y1, x2, y2] absolute pixels
|
|
82
|
+
region.xywh # [x, y, w, h] absolute pixels
|
|
83
|
+
region.norm_xywh # [x, y, w, h] 0.0 – 1.0 ← YOLO label format
|
|
84
|
+
region.center # (cx, cy)
|
|
85
|
+
region.area # pixels²
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ── Polygon ───────────────────────────────────────────────────
|
|
89
|
+
zone = pixpick.polygon("frame.jpg")
|
|
90
|
+
|
|
91
|
+
zone.points # [(x0,y0), (x1,y1), ...] absolute pixels
|
|
92
|
+
zone.as_numpy # np.array shape (N, 2)
|
|
93
|
+
zone.norm # [(x0n,y0n), ...] 0.0 – 1.0
|
|
94
|
+
zone.bbox # → Box tight bbox around the polygon
|
|
95
|
+
zone.npoints # int
|
|
96
|
+
```
|
|
97
|
+
For more details, see [Selectors](docs/selectors.md).
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Framework integration
|
|
101
|
+
|
|
102
|
+
| Framework | Selector | Method |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Ultralytics YOLOE — visual prompt | `Box` | `region.yolo_prompt()` |
|
|
105
|
+
| Ultralytics YOLO — region | `Box` | `region.yolo_region()` |
|
|
106
|
+
| SAM / SAM2 — box prompt | `Box` | `region.sam()` |
|
|
107
|
+
| Any other format | `Box` / `Polygon` | `region.to_raw()` |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Persistence
|
|
112
|
+
|
|
113
|
+
Pick once, reuse forever.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
region.save("zone.json")
|
|
117
|
+
region = pixpick.load("zone.json") # Box and Polygon both work
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Production pattern — pick interactively the first time, load on every subsequent run:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pathlib import Path
|
|
124
|
+
import pixpick
|
|
125
|
+
|
|
126
|
+
ZONE = "config/count_zone.json"
|
|
127
|
+
|
|
128
|
+
zone = pixpick.load(ZONE) if Path(ZONE).exists() else pixpick.polygon("frame.jpg")
|
|
129
|
+
zone.save(ZONE)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Docs
|
|
135
|
+
|
|
136
|
+
| | |
|
|
137
|
+
|---|---|
|
|
138
|
+
| 🚀 [Getting Started](docs/getting-started.md) | Installation, first selection, controls |
|
|
139
|
+
| 🎯 [Selectors](docs/selectors.md) | All properties and methods for Box and Polygon |
|
|
140
|
+
| 🔌 [Framework Integration](docs/frameworks.md) | YOLO, SAM2 and more |
|
|
141
|
+
| 💾 [Persistence](docs/persistence.md) | Save, load, JSON schema |
|
|
142
|
+
| 🏗️ [Architecture](docs/architecture.md) | How it's built and how to extend it |
|
|
143
|
+
| 🗺️ [Roadmap](docs/roadmap.md) | What's coming next |
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pixpick
|
|
3
|
+
-------
|
|
4
|
+
Interactive coordinate picker for Computer Vision frameworks.
|
|
5
|
+
|
|
6
|
+
Quick start
|
|
7
|
+
-----------
|
|
8
|
+
import pixpick
|
|
9
|
+
|
|
10
|
+
# Box
|
|
11
|
+
region = pixpick.box("frame.jpg")
|
|
12
|
+
model.predict("frame.jpg", **region.yolo_region())
|
|
13
|
+
print(region.xyxy) # [x1, y1, x2, y2]
|
|
14
|
+
print(region.norm) # [0.12, 0.08, 0.64, 0.48]
|
|
15
|
+
|
|
16
|
+
# Polygon
|
|
17
|
+
zone = pixpick.polygon("frame.jpg")
|
|
18
|
+
sv.PolygonZone(**zone.to_supervision())
|
|
19
|
+
print(zone.points) # [(x0,y0), (x1,y1), ...]
|
|
20
|
+
|
|
21
|
+
# Save and reload either type
|
|
22
|
+
region.save("zone.json")
|
|
23
|
+
region = pixpick.load("zone.json")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from pixpick.selectors.box import BoxSelector
|
|
27
|
+
from pixpick.selectors.polygon import PolygonSelector, SelectionCancelled
|
|
28
|
+
from pixpick.core.selection import Box, Polygon
|
|
29
|
+
from pixpick.utils import ImageSource
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def box(source: ImageSource, title: str = "pixpick") -> Box:
|
|
33
|
+
"""
|
|
34
|
+
Open an interactive window on `source`, drag a rectangle, return a Box.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
source : str | Path | np.ndarray
|
|
39
|
+
Image file path or BGR numpy array.
|
|
40
|
+
title : str
|
|
41
|
+
Window title shown to the user.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
Box
|
|
46
|
+
|
|
47
|
+
Raises
|
|
48
|
+
------
|
|
49
|
+
SelectionCancelled
|
|
50
|
+
If the user pressed Esc.
|
|
51
|
+
"""
|
|
52
|
+
return BoxSelector().select(source, title=title)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def polygon(source: ImageSource, title: str = "pixpick") -> Polygon:
|
|
56
|
+
"""
|
|
57
|
+
Open an interactive window on `source`, click vertices, return a Polygon.
|
|
58
|
+
|
|
59
|
+
Controls: LMB=add point RMB=undo last Enter=confirm Z=clear Esc=cancel
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
source : str | Path | np.ndarray
|
|
64
|
+
Image file path or BGR numpy array.
|
|
65
|
+
title : str
|
|
66
|
+
Window title shown to the user.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
Polygon
|
|
71
|
+
|
|
72
|
+
Raises
|
|
73
|
+
------
|
|
74
|
+
SelectionCancelled
|
|
75
|
+
If the user pressed Esc.
|
|
76
|
+
"""
|
|
77
|
+
return PolygonSelector().select(source, title=title)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load(path: str) -> Box | Polygon:
|
|
81
|
+
"""
|
|
82
|
+
Load a previously saved selection from a JSON file.
|
|
83
|
+
Dispatches to Box.load or Polygon.load based on the 'type' field.
|
|
84
|
+
"""
|
|
85
|
+
import json
|
|
86
|
+
from pathlib import Path
|
|
87
|
+
data = json.loads(Path(path).read_text())
|
|
88
|
+
sel_type = data.get("type")
|
|
89
|
+
if sel_type == "box":
|
|
90
|
+
return Box.load(path)
|
|
91
|
+
elif sel_type == "polygon":
|
|
92
|
+
return Polygon.load(path)
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError(f"Unknown selection type in JSON: '{sel_type}'")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = [
|
|
98
|
+
"box",
|
|
99
|
+
"polygon",
|
|
100
|
+
"load",
|
|
101
|
+
"Box",
|
|
102
|
+
"Polygon",
|
|
103
|
+
"BoxSelector",
|
|
104
|
+
"PolygonSelector",
|
|
105
|
+
"SelectionCancelled",
|
|
106
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseBackend(ABC):
|
|
6
|
+
"""
|
|
7
|
+
A backend owns the UI layer — it opens a window (or widget),
|
|
8
|
+
captures user interactions, and returns raw pixel coordinates.
|
|
9
|
+
|
|
10
|
+
It knows nothing about Selection objects or adapters.
|
|
11
|
+
The selector calls the backend and wraps the raw result in the
|
|
12
|
+
appropriate Selection type.
|
|
13
|
+
|
|
14
|
+
Adding a new environment (Jupyter, Gradio, …) means adding a new
|
|
15
|
+
backend — zero changes to selectors or adapters.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def select_box(
|
|
20
|
+
self,
|
|
21
|
+
image: np.ndarray,
|
|
22
|
+
title: str = "pixpick",
|
|
23
|
+
) -> tuple[int, int, int, int] | None:
|
|
24
|
+
"""
|
|
25
|
+
Let the user drag a rectangle on the image.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
(x1, y1, x2, y2) in absolute pixels, or None if cancelled.
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def select_polygon(
|
|
35
|
+
self,
|
|
36
|
+
image: np.ndarray,
|
|
37
|
+
title: str = "pixpick",
|
|
38
|
+
) -> list[tuple[int, int]] | None:
|
|
39
|
+
"""
|
|
40
|
+
Let the user click polygon vertices on the image.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
List of (x, y) tuples (≥ 3 points), or None if cancelled.
|
|
45
|
+
"""
|
|
46
|
+
...
|