pixelflow 0.1.3__tar.gz → 0.3.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.
- {pixelflow-0.1.3 → pixelflow-0.3.0}/PKG-INFO +83 -21
- {pixelflow-0.1.3 → pixelflow-0.3.0}/README.md +81 -19
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/__init__.py +17 -11
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/__init__.py +2 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/anchors.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/blur.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/box.py +1 -1
- pixelflow-0.3.0/pixelflow/annotators/classification.py +122 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/crossing.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/filled_box.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/grid_overlay.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/keypoint.py +14 -10
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/keypoint_skeleton.py +15 -8
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/label.py +14 -11
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/mask.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/oval.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/pixelate.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/polygon.py +6 -2
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/utils.py +24 -1
- pixelflow-0.3.0/pixelflow/arrays.py +20 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/assets.py +18 -4
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/buffer.py +5 -5
- pixelflow-0.3.0/pixelflow/classifications/__init__.py +16 -0
- pixelflow-0.3.0/pixelflow/classifications/classifications.py +314 -0
- pixelflow-0.3.0/pixelflow/classifications/converters.py +171 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/colors.py +12 -4
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/crossings.py +4 -4
- pixelflow-0.3.0/pixelflow/detections/__init__.py +45 -0
- pixelflow-0.3.0/pixelflow/detections/converters.py +1398 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/detections/detections.py +222 -124
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/detections/filters.py +5 -5
- pixelflow-0.3.0/pixelflow/labels.py +73 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/slicer.py +4 -2
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/smoother.py +3 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/strategies.py +2 -2
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/timer.py +5 -5
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/bytetrack.py +7 -7
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/detections.py +32 -89
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/inverse.py +42 -127
- pixelflow-0.3.0/pixelflow/validators.py +137 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/zones.py +6 -6
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/PKG-INFO +83 -21
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/SOURCES.txt +6 -1
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pyproject.toml +5 -1
- pixelflow-0.1.3/pixelflow/classes.py +0 -86
- pixelflow-0.1.3/pixelflow/detections/__init__.py +0 -76
- pixelflow-0.1.3/pixelflow/detections/converters.py +0 -1072
- pixelflow-0.1.3/pixelflow/validators.py +0 -139
- {pixelflow-0.1.3 → pixelflow-0.3.0}/LICENSE +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/dot.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/fps_counter.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/heatmap.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/motion_dots.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/motion_trails.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/scale_bar.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/zones.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/media.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/__init__.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/kalman_filter.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/matching.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/track.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/__init__.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/image.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/dependency_links.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/requires.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/top_level.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixelflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Computer vision processing library for visualizing and analyzing detection results from ML models
|
|
5
5
|
Author-email: Emrah Nazif <emrah@datamarkin.com>, Datamarkin <support@datamarkin.com>
|
|
6
6
|
License: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/datamarkin/pixelflow
|
|
|
8
8
|
Project-URL: Repository, https://github.com/datamarkin/pixelflow
|
|
9
9
|
Project-URL: Issues, https://github.com/datamarkin/pixelflow/issues
|
|
10
10
|
Project-URL: Documentation, https://pixelflow.datamarkin.com
|
|
11
|
-
Keywords: computer-vision,visualization,annotation,post-processing,tracking,opencv,yolo,detectron2,ml-results
|
|
11
|
+
Keywords: computer-vision,visualization,annotation,post-processing,tracking,opencv,yolo,detectron2,mayaku,ml-results
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -57,7 +57,7 @@ zones = pf.Zones()
|
|
|
57
57
|
zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
|
|
58
58
|
|
|
59
59
|
for frame in video:
|
|
60
|
-
detections = pf.
|
|
60
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
61
61
|
detections = tracker.update(detections)
|
|
62
62
|
zones.update(detections)
|
|
63
63
|
|
|
@@ -77,6 +77,12 @@ pip install pixelflow
|
|
|
77
77
|
|
|
78
78
|
## Core Concepts
|
|
79
79
|
|
|
80
|
+
### Two Result Types
|
|
81
|
+
|
|
82
|
+
Every model output becomes one of two things. **Detections** for anything that localises — detection, segmentation, keypoints, OCR. **Classifications** for anything that only names.
|
|
83
|
+
|
|
84
|
+
They are peers, not variants of each other: three detections are three objects, while three classifications are three competing answers about one image.
|
|
85
|
+
|
|
80
86
|
### Detections - One Format, Every Framework
|
|
81
87
|
|
|
82
88
|
Convert outputs from any ML framework into a unified format:
|
|
@@ -87,28 +93,80 @@ import pixelflow as pf
|
|
|
87
93
|
# Ultralytics YOLO
|
|
88
94
|
from ultralytics import YOLO
|
|
89
95
|
model = YOLO("yolo11n.pt")
|
|
90
|
-
detections = pf.
|
|
96
|
+
detections = pf.from_ultralytics(model.predict(image))
|
|
91
97
|
|
|
92
98
|
# Detectron2
|
|
93
99
|
from detectron2.engine import DefaultPredictor
|
|
94
100
|
predictor = DefaultPredictor(cfg)
|
|
95
|
-
detections = pf.
|
|
101
|
+
detections = pf.from_detectron2(predictor(image), labels=["person", "car"])
|
|
102
|
+
|
|
103
|
+
# Mayaku (returns Instances directly; take labels from the checkpoint)
|
|
104
|
+
from mayaku import from_pretrained
|
|
105
|
+
predictor = from_pretrained("mayaku-n-det")
|
|
106
|
+
detections = pf.from_mayaku(predictor("photo.jpg"), labels=predictor.class_names)
|
|
96
107
|
|
|
97
108
|
# HuggingFace Transformers
|
|
98
109
|
from transformers import pipeline
|
|
99
110
|
detector = pipeline("object-detection")
|
|
100
|
-
detections = pf.
|
|
111
|
+
detections = pf.from_transformers(detector(image))
|
|
101
112
|
|
|
102
|
-
# Florence-2
|
|
103
|
-
detections = pf.
|
|
113
|
+
# Florence-2 - captions and OCR reads land in det.text, <OD> classes in det.class_name
|
|
114
|
+
detections = pf.from_florence2(model_output, task_prompt="<OD>")
|
|
104
115
|
|
|
105
116
|
# SAM (Segment Anything)
|
|
106
|
-
detections = pf.
|
|
117
|
+
detections = pf.from_sam(masks, scores)
|
|
118
|
+
|
|
119
|
+
# RF-DETR
|
|
120
|
+
detections = pf.from_rfdetr(model_output)
|
|
121
|
+
|
|
122
|
+
# Supervision
|
|
123
|
+
detections = pf.from_supervision(sv_detections)
|
|
124
|
+
|
|
125
|
+
# EasyOCR - text lands in det.text, the quad in det.segments
|
|
126
|
+
import easyocr
|
|
127
|
+
reader = easyocr.Reader(['en'])
|
|
128
|
+
detections = pf.from_easyocr(reader.readtext("sign.jpg"))
|
|
129
|
+
|
|
130
|
+
# Plain arrays (numpy or torch) - no framework container to convert from
|
|
131
|
+
detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Classifications - When the Model Only Names
|
|
135
|
+
|
|
136
|
+
A classifier localises nothing, so its result carries no geometry at all:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import pixelflow as pf
|
|
140
|
+
|
|
141
|
+
# Ultralytics -cls checkpoints
|
|
142
|
+
from ultralytics import YOLO
|
|
143
|
+
model = YOLO("yolo11n-cls.pt")
|
|
144
|
+
result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
145
|
+
|
|
146
|
+
result.top1.class_name # 'golden retriever'
|
|
147
|
+
result.top_k(5) # -> Classifications, best first
|
|
148
|
+
result.to_json()
|
|
149
|
+
|
|
150
|
+
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
151
|
+
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
152
|
+
result = pf.from_scores(similarities, labels=prompts)
|
|
107
153
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
154
|
+
# Multi-label: however many labels genuinely match, not a fixed count
|
|
155
|
+
matches = result.filter_by_confidence(0.3)
|
|
156
|
+
|
|
157
|
+
# Draw it
|
|
158
|
+
frame = pf.annotate.classification(frame, result, top_k=3)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Scores are reported exactly as the model emitted them. PixelFlow does not normalise,
|
|
162
|
+
re-softmax, or assume they sum to 1, so a softmax over a fixed class list and an
|
|
163
|
+
independent per-label similarity both survive intact. A model emitting logits should
|
|
164
|
+
be converted by the caller, who is the only one who knows which convention applies.
|
|
165
|
+
|
|
166
|
+
Per-frame paths can skip building a row per class:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
result = pf.from_ultralytics_classification(model.predict(frame), top_k=5)
|
|
112
170
|
```
|
|
113
171
|
|
|
114
172
|
### Powerful Filtering
|
|
@@ -222,7 +280,7 @@ import pixelflow as pf
|
|
|
222
280
|
tracker = pf.tracker.ByteTracker()
|
|
223
281
|
|
|
224
282
|
for frame in media.frames:
|
|
225
|
-
detections = pf.
|
|
283
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
226
284
|
detections = tracker.update(detections)
|
|
227
285
|
|
|
228
286
|
for det in detections:
|
|
@@ -302,7 +360,7 @@ slicer = pf.SlicedInference(
|
|
|
302
360
|
)
|
|
303
361
|
|
|
304
362
|
def detector(image):
|
|
305
|
-
return pf.
|
|
363
|
+
return pf.from_ultralytics(model.predict(image))
|
|
306
364
|
|
|
307
365
|
# Run on 4K image - automatically slices, detects, and merges
|
|
308
366
|
large_image = cv2.imread("satellite.jpg") # 4000x3000
|
|
@@ -325,7 +383,7 @@ crossings.add_line(
|
|
|
325
383
|
)
|
|
326
384
|
|
|
327
385
|
for frame in media.frames:
|
|
328
|
-
detections = tracker.update(pf.
|
|
386
|
+
detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
|
|
329
387
|
crossings.update(detections)
|
|
330
388
|
|
|
331
389
|
print(f"Crossed: {crossings.get_counts()}")
|
|
@@ -352,15 +410,19 @@ report = detections.to_json_with_metrics()
|
|
|
352
410
|
|
|
353
411
|
| Framework | Converter | Features |
|
|
354
412
|
|-----------|-----------|----------|
|
|
413
|
+
| None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
|
|
355
414
|
| Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
|
|
356
415
|
| Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
|
|
416
|
+
| Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
|
|
357
417
|
| HuggingFace Transformers | `from_transformers()` | Boxes, scores |
|
|
358
|
-
| Florence-2 | `from_florence2()` | Boxes,
|
|
418
|
+
| Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
|
|
359
419
|
| SAM | `from_sam()` | Masks, scores |
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
|
|
|
420
|
+
| EfficientTAM | `from_efficienttam()` | Masks, scores |
|
|
421
|
+
| RF-DETR | `from_rfdetr()` | Boxes, masks |
|
|
422
|
+
| Supervision | `from_supervision()` | Boxes, masks, keypoints |
|
|
423
|
+
| Falcon Perception | `from_falcon_perception()` | Boxes, masks |
|
|
424
|
+
| EasyOCR | `from_easyocr()` | Quads, text, scores |
|
|
425
|
+
| Datamarkin API | `from_datamarkin()` | Boxes, masks, keypoints |
|
|
364
426
|
|
|
365
427
|
## Documentation
|
|
366
428
|
|
|
@@ -19,7 +19,7 @@ zones = pf.Zones()
|
|
|
19
19
|
zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
|
|
20
20
|
|
|
21
21
|
for frame in video:
|
|
22
|
-
detections = pf.
|
|
22
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
23
23
|
detections = tracker.update(detections)
|
|
24
24
|
zones.update(detections)
|
|
25
25
|
|
|
@@ -39,6 +39,12 @@ pip install pixelflow
|
|
|
39
39
|
|
|
40
40
|
## Core Concepts
|
|
41
41
|
|
|
42
|
+
### Two Result Types
|
|
43
|
+
|
|
44
|
+
Every model output becomes one of two things. **Detections** for anything that localises — detection, segmentation, keypoints, OCR. **Classifications** for anything that only names.
|
|
45
|
+
|
|
46
|
+
They are peers, not variants of each other: three detections are three objects, while three classifications are three competing answers about one image.
|
|
47
|
+
|
|
42
48
|
### Detections - One Format, Every Framework
|
|
43
49
|
|
|
44
50
|
Convert outputs from any ML framework into a unified format:
|
|
@@ -49,28 +55,80 @@ import pixelflow as pf
|
|
|
49
55
|
# Ultralytics YOLO
|
|
50
56
|
from ultralytics import YOLO
|
|
51
57
|
model = YOLO("yolo11n.pt")
|
|
52
|
-
detections = pf.
|
|
58
|
+
detections = pf.from_ultralytics(model.predict(image))
|
|
53
59
|
|
|
54
60
|
# Detectron2
|
|
55
61
|
from detectron2.engine import DefaultPredictor
|
|
56
62
|
predictor = DefaultPredictor(cfg)
|
|
57
|
-
detections = pf.
|
|
63
|
+
detections = pf.from_detectron2(predictor(image), labels=["person", "car"])
|
|
64
|
+
|
|
65
|
+
# Mayaku (returns Instances directly; take labels from the checkpoint)
|
|
66
|
+
from mayaku import from_pretrained
|
|
67
|
+
predictor = from_pretrained("mayaku-n-det")
|
|
68
|
+
detections = pf.from_mayaku(predictor("photo.jpg"), labels=predictor.class_names)
|
|
58
69
|
|
|
59
70
|
# HuggingFace Transformers
|
|
60
71
|
from transformers import pipeline
|
|
61
72
|
detector = pipeline("object-detection")
|
|
62
|
-
detections = pf.
|
|
73
|
+
detections = pf.from_transformers(detector(image))
|
|
63
74
|
|
|
64
|
-
# Florence-2
|
|
65
|
-
detections = pf.
|
|
75
|
+
# Florence-2 - captions and OCR reads land in det.text, <OD> classes in det.class_name
|
|
76
|
+
detections = pf.from_florence2(model_output, task_prompt="<OD>")
|
|
66
77
|
|
|
67
78
|
# SAM (Segment Anything)
|
|
68
|
-
detections = pf.
|
|
79
|
+
detections = pf.from_sam(masks, scores)
|
|
80
|
+
|
|
81
|
+
# RF-DETR
|
|
82
|
+
detections = pf.from_rfdetr(model_output)
|
|
83
|
+
|
|
84
|
+
# Supervision
|
|
85
|
+
detections = pf.from_supervision(sv_detections)
|
|
86
|
+
|
|
87
|
+
# EasyOCR - text lands in det.text, the quad in det.segments
|
|
88
|
+
import easyocr
|
|
89
|
+
reader = easyocr.Reader(['en'])
|
|
90
|
+
detections = pf.from_easyocr(reader.readtext("sign.jpg"))
|
|
91
|
+
|
|
92
|
+
# Plain arrays (numpy or torch) - no framework container to convert from
|
|
93
|
+
detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Classifications - When the Model Only Names
|
|
97
|
+
|
|
98
|
+
A classifier localises nothing, so its result carries no geometry at all:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import pixelflow as pf
|
|
102
|
+
|
|
103
|
+
# Ultralytics -cls checkpoints
|
|
104
|
+
from ultralytics import YOLO
|
|
105
|
+
model = YOLO("yolo11n-cls.pt")
|
|
106
|
+
result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
107
|
+
|
|
108
|
+
result.top1.class_name # 'golden retriever'
|
|
109
|
+
result.top_k(5) # -> Classifications, best first
|
|
110
|
+
result.to_json()
|
|
111
|
+
|
|
112
|
+
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
113
|
+
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
114
|
+
result = pf.from_scores(similarities, labels=prompts)
|
|
69
115
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
# Multi-label: however many labels genuinely match, not a fixed count
|
|
117
|
+
matches = result.filter_by_confidence(0.3)
|
|
118
|
+
|
|
119
|
+
# Draw it
|
|
120
|
+
frame = pf.annotate.classification(frame, result, top_k=3)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Scores are reported exactly as the model emitted them. PixelFlow does not normalise,
|
|
124
|
+
re-softmax, or assume they sum to 1, so a softmax over a fixed class list and an
|
|
125
|
+
independent per-label similarity both survive intact. A model emitting logits should
|
|
126
|
+
be converted by the caller, who is the only one who knows which convention applies.
|
|
127
|
+
|
|
128
|
+
Per-frame paths can skip building a row per class:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
result = pf.from_ultralytics_classification(model.predict(frame), top_k=5)
|
|
74
132
|
```
|
|
75
133
|
|
|
76
134
|
### Powerful Filtering
|
|
@@ -184,7 +242,7 @@ import pixelflow as pf
|
|
|
184
242
|
tracker = pf.tracker.ByteTracker()
|
|
185
243
|
|
|
186
244
|
for frame in media.frames:
|
|
187
|
-
detections = pf.
|
|
245
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
188
246
|
detections = tracker.update(detections)
|
|
189
247
|
|
|
190
248
|
for det in detections:
|
|
@@ -264,7 +322,7 @@ slicer = pf.SlicedInference(
|
|
|
264
322
|
)
|
|
265
323
|
|
|
266
324
|
def detector(image):
|
|
267
|
-
return pf.
|
|
325
|
+
return pf.from_ultralytics(model.predict(image))
|
|
268
326
|
|
|
269
327
|
# Run on 4K image - automatically slices, detects, and merges
|
|
270
328
|
large_image = cv2.imread("satellite.jpg") # 4000x3000
|
|
@@ -287,7 +345,7 @@ crossings.add_line(
|
|
|
287
345
|
)
|
|
288
346
|
|
|
289
347
|
for frame in media.frames:
|
|
290
|
-
detections = tracker.update(pf.
|
|
348
|
+
detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
|
|
291
349
|
crossings.update(detections)
|
|
292
350
|
|
|
293
351
|
print(f"Crossed: {crossings.get_counts()}")
|
|
@@ -314,15 +372,19 @@ report = detections.to_json_with_metrics()
|
|
|
314
372
|
|
|
315
373
|
| Framework | Converter | Features |
|
|
316
374
|
|-----------|-----------|----------|
|
|
375
|
+
| None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
|
|
317
376
|
| Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
|
|
318
377
|
| Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
|
|
378
|
+
| Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
|
|
319
379
|
| HuggingFace Transformers | `from_transformers()` | Boxes, scores |
|
|
320
|
-
| Florence-2 | `from_florence2()` | Boxes,
|
|
380
|
+
| Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
|
|
321
381
|
| SAM | `from_sam()` | Masks, scores |
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
|
325
|
-
|
|
|
382
|
+
| EfficientTAM | `from_efficienttam()` | Masks, scores |
|
|
383
|
+
| RF-DETR | `from_rfdetr()` | Boxes, masks |
|
|
384
|
+
| Supervision | `from_supervision()` | Boxes, masks, keypoints |
|
|
385
|
+
| Falcon Perception | `from_falcon_perception()` | Boxes, masks |
|
|
386
|
+
| EasyOCR | `from_easyocr()` | Quads, text, scores |
|
|
387
|
+
| Datamarkin API | `from_datamarkin()` | Boxes, masks, keypoints |
|
|
326
388
|
|
|
327
389
|
## Documentation
|
|
328
390
|
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
|
2
2
|
__author__ = "Datamarkin"
|
|
3
3
|
|
|
4
4
|
# Import core modules
|
|
5
|
-
from . import detections
|
|
6
|
-
|
|
7
5
|
from . import media
|
|
8
6
|
from . import annotators as annotate
|
|
9
7
|
from . import transforms as transform
|
|
10
8
|
from . import colors
|
|
11
|
-
# from . import zones
|
|
12
|
-
# from . import crossings
|
|
13
9
|
from . import slicer
|
|
14
10
|
from . import smoother
|
|
15
11
|
from . import timer
|
|
16
12
|
from . import tracker
|
|
17
13
|
from . import assets
|
|
18
|
-
|
|
14
|
+
|
|
15
|
+
# Result types and their converters. Every model output PixelFlow understands becomes
|
|
16
|
+
# one of two things: Detections for anything that localises, Classifications for
|
|
17
|
+
# anything that only names. They are peers, not variants of each other.
|
|
18
|
+
#
|
|
19
|
+
# Converters are flat -- pf.from_ultralytics(...) -- and each subpackage owns its own
|
|
20
|
+
# export list, so a new one is declared once rather than re-typed here. Detection is
|
|
21
|
+
# the unmarked case because almost every model that produces a container localises
|
|
22
|
+
# something; only classification needs the suffix.
|
|
23
|
+
from .detections import *
|
|
24
|
+
from .detections import __all__ as _DETECTION_EXPORTS
|
|
25
|
+
from .classifications import *
|
|
26
|
+
from .classifications import __all__ as _CLASSIFICATION_EXPORTS
|
|
19
27
|
|
|
20
28
|
# Import specific functions for top-level access
|
|
21
29
|
from .media import (
|
|
@@ -32,8 +40,9 @@ from .timer import TimeTracker
|
|
|
32
40
|
|
|
33
41
|
# Define the public API
|
|
34
42
|
__all__ = [
|
|
35
|
-
#
|
|
36
|
-
|
|
43
|
+
# Result types and converters, declared by the subpackages that own them
|
|
44
|
+
*_DETECTION_EXPORTS,
|
|
45
|
+
*_CLASSIFICATION_EXPORTS,
|
|
37
46
|
|
|
38
47
|
# Visual components
|
|
39
48
|
"annotate",
|
|
@@ -76,9 +85,6 @@ __all__ = [
|
|
|
76
85
|
"TimeTracker",
|
|
77
86
|
"tracker",
|
|
78
87
|
|
|
79
|
-
# Constants
|
|
80
|
-
"COCO_CLASSES",
|
|
81
|
-
|
|
82
88
|
# Metadata
|
|
83
89
|
"__version__",
|
|
84
90
|
"__author__"
|
|
@@ -12,6 +12,7 @@ allowing easy chaining of annotation operations.
|
|
|
12
12
|
from .anchors import anchors
|
|
13
13
|
from .blur import blur
|
|
14
14
|
from .box import box
|
|
15
|
+
from .classification import classification
|
|
15
16
|
from .label import label
|
|
16
17
|
from .crossing import crossings
|
|
17
18
|
from .mask import mask
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
# Core annotators
|
|
42
43
|
'anchors',
|
|
43
44
|
'box',
|
|
45
|
+
'classification',
|
|
44
46
|
'label',
|
|
45
47
|
'mask',
|
|
46
48
|
'keypoint',
|
|
@@ -71,7 +71,7 @@ def anchors(
|
|
|
71
71
|
>>> image = cv2.imread("path/to/image.jpg")
|
|
72
72
|
>>> model = YOLO("yolo11n.pt")
|
|
73
73
|
>>> outputs = model.predict(image)
|
|
74
|
-
>>> detections = pf.
|
|
74
|
+
>>> detections = pf.from_ultralytics(outputs)
|
|
75
75
|
>>>
|
|
76
76
|
>>> # Draw all main anchor points (default)
|
|
77
77
|
>>> annotated = pf.annotators.anchors(image, detections)
|
|
@@ -61,7 +61,7 @@ def blur(
|
|
|
61
61
|
>>> image = cv2.imread("faces_in_crowd.jpg")
|
|
62
62
|
>>> model = YOLO("yolo11n.pt")
|
|
63
63
|
>>> outputs = model.predict(image) # Raw model outputs
|
|
64
|
-
>>> detections = pf.
|
|
64
|
+
>>> detections = pf.from_ultralytics(outputs) # Convert to PixelFlow format
|
|
65
65
|
>>>
|
|
66
66
|
>>> # Apply blur with default adaptive settings
|
|
67
67
|
>>> blurred_image = pf.annotators.blur(image, detections)
|
|
@@ -48,7 +48,7 @@ def box(
|
|
|
48
48
|
>>> image = cv2.imread("path/to/image.jpg")
|
|
49
49
|
>>> model = YOLO("yolo11n.pt")
|
|
50
50
|
>>> outputs = model.predict(image) # Raw model outputs
|
|
51
|
-
>>> detections = pf.
|
|
51
|
+
>>> detections = pf.from_ultralytics(outputs) # Convert to PixelFlow format
|
|
52
52
|
>>>
|
|
53
53
|
>>> # Draw boxes with default colors and adaptive thickness
|
|
54
54
|
>>> annotated = pf.annotators.box(image, detections)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, Tuple, Union
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from ..classifications import Classifications
|
|
5
|
+
|
|
6
|
+
import cv2
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ..colors import _get_color_for_prediction
|
|
10
|
+
from .utils import _caption_with_score, _get_adaptive_params
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def classification(image: np.ndarray,
|
|
14
|
+
classifications: 'Classifications',
|
|
15
|
+
top_k: int = 1,
|
|
16
|
+
position: str = 'top_left',
|
|
17
|
+
font_scale: Optional[float] = None,
|
|
18
|
+
text_color: Union[tuple, str] = (255, 255, 255),
|
|
19
|
+
bg_color: Optional[tuple] = None,
|
|
20
|
+
padding: Optional[int] = None) -> np.ndarray:
|
|
21
|
+
"""
|
|
22
|
+
Draw a classification result as a stacked panel in a corner of the image.
|
|
23
|
+
|
|
24
|
+
A classification describes the whole image rather than a region of it, so it has
|
|
25
|
+
nowhere to anchor the way a box label does. The panel goes in a corner and lists
|
|
26
|
+
the best answers top down, one row per candidate, each row carrying its class
|
|
27
|
+
colour so several rows stay distinguishable at a glance.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
image (np.ndarray): Image to annotate, modified in place.
|
|
31
|
+
classifications (Classifications): PixelFlow classifications object.
|
|
32
|
+
top_k (int): How many candidates to draw, best first. Default is 1.
|
|
33
|
+
position (str): Corner to draw in. One of 'top_left', 'top_right',
|
|
34
|
+
'bottom_left', 'bottom_right'. Default is 'top_left'.
|
|
35
|
+
font_scale (Optional[float]): Font scale. Computed from image size when None.
|
|
36
|
+
text_color (Union[tuple, str]): RGB text color. Default is white.
|
|
37
|
+
bg_color (Optional[tuple]): RGB background for every row. When None each row
|
|
38
|
+
takes the color of its own class.
|
|
39
|
+
padding (Optional[int]): Padding in pixels inside each row. Computed from image
|
|
40
|
+
size when None.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
np.ndarray: The annotated image.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> import pixelflow as pf
|
|
47
|
+
>>> from ultralytics import YOLO
|
|
48
|
+
>>>
|
|
49
|
+
>>> model = YOLO("yolo11n-cls.pt")
|
|
50
|
+
>>> result = pf.from_ultralytics_classification(model.predict(image))
|
|
51
|
+
>>>
|
|
52
|
+
>>> # Just the answer
|
|
53
|
+
>>> annotated = pf.annotate.classification(image, result)
|
|
54
|
+
>>>
|
|
55
|
+
>>> # Top 3, bottom right
|
|
56
|
+
>>> annotated = pf.annotate.classification(image, result, top_k=3,
|
|
57
|
+
... position='bottom_right')
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
- An empty result returns the image unchanged.
|
|
61
|
+
- A row with no class_name falls back to its class_id, which is always
|
|
62
|
+
meaningful even when no vocabulary was supplied.
|
|
63
|
+
- Rows are ranked by confidence, so the panel reads best-first regardless of
|
|
64
|
+
the order the converter produced.
|
|
65
|
+
"""
|
|
66
|
+
rows = classifications.top_k(top_k)
|
|
67
|
+
if len(rows) == 0:
|
|
68
|
+
return image
|
|
69
|
+
|
|
70
|
+
params = _get_adaptive_params(image)
|
|
71
|
+
if font_scale is None:
|
|
72
|
+
font_scale = params['font_scale']
|
|
73
|
+
if padding is None:
|
|
74
|
+
padding = params['padding']
|
|
75
|
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
|
76
|
+
font_thickness = params['font_thickness']
|
|
77
|
+
|
|
78
|
+
# Measure every row first: the panel is one block, so its width is set by the
|
|
79
|
+
# widest line rather than by each row independently.
|
|
80
|
+
entries, widths, heights = [], [], []
|
|
81
|
+
for row in rows:
|
|
82
|
+
caption = row.class_name or (str(row.class_id) if row.class_id is not None else None)
|
|
83
|
+
text = _caption_with_score(caption, row.confidence)
|
|
84
|
+
(width, height), _ = cv2.getTextSize(text, font, font_scale, font_thickness)
|
|
85
|
+
entries.append((text, row))
|
|
86
|
+
widths.append(width)
|
|
87
|
+
heights.append(height)
|
|
88
|
+
|
|
89
|
+
# One height for every row. Measuring each row's own baseline left the text
|
|
90
|
+
# jittering up and down inside a panel whose rows are all the same height.
|
|
91
|
+
text_height = max(heights)
|
|
92
|
+
row_height = text_height + 2 * padding
|
|
93
|
+
panel_width = max(widths) + 2 * padding
|
|
94
|
+
panel_height = row_height * len(entries)
|
|
95
|
+
|
|
96
|
+
image_height, image_width = image.shape[:2]
|
|
97
|
+
margin = params['margin']
|
|
98
|
+
x = margin if 'left' in position else image_width - panel_width - margin
|
|
99
|
+
y = margin if position.startswith('top') else image_height - panel_height - margin
|
|
100
|
+
x, y = max(0, x), max(0, y)
|
|
101
|
+
|
|
102
|
+
for index, (text, row) in enumerate(entries):
|
|
103
|
+
row_top = y + index * row_height
|
|
104
|
+
cv2.rectangle(
|
|
105
|
+
image,
|
|
106
|
+
(x, row_top),
|
|
107
|
+
(x + panel_width, row_top + row_height),
|
|
108
|
+
bg_color if bg_color is not None else _get_color_for_prediction(row),
|
|
109
|
+
-1
|
|
110
|
+
)
|
|
111
|
+
cv2.putText(
|
|
112
|
+
image,
|
|
113
|
+
text,
|
|
114
|
+
(x + padding, row_top + padding + text_height),
|
|
115
|
+
font,
|
|
116
|
+
font_scale,
|
|
117
|
+
text_color,
|
|
118
|
+
font_thickness,
|
|
119
|
+
cv2.LINE_AA
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return image
|
|
@@ -252,7 +252,7 @@ def crossings(image: np.ndarray, crossings_manager) -> np.ndarray:
|
|
|
252
252
|
>>> tracker = pf.tracker.ByteTracker()
|
|
253
253
|
>>> for frame in frames:
|
|
254
254
|
... outputs = yolo_model.predict(frame)
|
|
255
|
-
... results = pf.
|
|
255
|
+
... results = pf.from_ultralytics(outputs)
|
|
256
256
|
... results = tracker.update(results)
|
|
257
257
|
... results = crossings_manager.update(results) # Update counts
|
|
258
258
|
... annotated = pf.annotate.crossings(frame, crossings_manager)
|
|
@@ -57,7 +57,7 @@ def filled_box(
|
|
|
57
57
|
>>> image = cv2.imread("people.jpg")
|
|
58
58
|
>>> model = YOLO("yolo11n.pt")
|
|
59
59
|
>>> outputs = model.predict(image) # Raw YOLO outputs
|
|
60
|
-
>>> detections = pf.
|
|
60
|
+
>>> detections = pf.from_ultralytics(outputs) # Convert to PixelFlow format
|
|
61
61
|
>>>
|
|
62
62
|
>>> # Draw filled boxes with automatic opacity (recommended for most cases)
|
|
63
63
|
>>> annotated = pf.annotate.filled_box(image, detections)
|
|
@@ -68,7 +68,7 @@ def grid_overlay(
|
|
|
68
68
|
>>> image = cv2.imread("path/to/image.jpg")
|
|
69
69
|
>>> model = YOLO("yolo11n.pt")
|
|
70
70
|
>>> outputs = model.predict(image) # Raw model outputs
|
|
71
|
-
>>> detections = pf.
|
|
71
|
+
>>> detections = pf.from_ultralytics(outputs) # Convert to PixelFlow format
|
|
72
72
|
>>>
|
|
73
73
|
>>> # Draw grids with automatic sizing based on box dimensions
|
|
74
74
|
>>> annotated = pf.annotate.grid_overlay(image, detections)
|