pixelflow 0.1.3__tar.gz → 0.3.1__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.1}/PKG-INFO +85 -22
- {pixelflow-0.1.3 → pixelflow-0.3.1}/README.md +83 -20
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/__init__.py +17 -11
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/__init__.py +2 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/anchors.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/blur.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/box.py +1 -1
- pixelflow-0.3.1/pixelflow/annotators/classification.py +122 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/crossing.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/filled_box.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/grid_overlay.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/keypoint.py +14 -10
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/keypoint_skeleton.py +15 -8
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/label.py +14 -11
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/mask.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/oval.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/pixelate.py +1 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/polygon.py +6 -2
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/utils.py +24 -1
- pixelflow-0.3.1/pixelflow/arrays.py +20 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/assets.py +18 -4
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/buffer.py +5 -5
- pixelflow-0.3.1/pixelflow/classifications/__init__.py +16 -0
- pixelflow-0.3.1/pixelflow/classifications/classifications.py +314 -0
- pixelflow-0.3.1/pixelflow/classifications/converters.py +171 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/colors.py +12 -4
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/crossings.py +4 -4
- pixelflow-0.3.1/pixelflow/detections/__init__.py +45 -0
- pixelflow-0.3.1/pixelflow/detections/converters.py +1398 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/detections/detections.py +222 -124
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/detections/filters.py +5 -5
- pixelflow-0.3.1/pixelflow/labels.py +73 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/slicer.py +4 -2
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/smoother.py +3 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/strategies.py +2 -2
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/timer.py +5 -5
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/bytetrack.py +7 -7
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/detections.py +32 -89
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/inverse.py +42 -127
- pixelflow-0.3.1/pixelflow/validators.py +137 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/zones.py +6 -6
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/PKG-INFO +85 -22
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/SOURCES.txt +6 -1
- {pixelflow-0.1.3 → pixelflow-0.3.1}/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.1}/LICENSE +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/dot.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/fps_counter.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/heatmap.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/motion_dots.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/motion_trails.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/scale_bar.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/zones.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/media.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/__init__.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/kalman_filter.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/matching.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/track.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/__init__.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/image.py +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/dependency_links.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/requires.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/top_level.txt +0 -0
- {pixelflow-0.1.3 → pixelflow-0.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixelflow
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -39,7 +39,8 @@ Dynamic: license-file
|
|
|
39
39
|
# PixelFlow
|
|
40
40
|
|
|
41
41
|
[](https://badge.fury.io/py/pixelflow)
|
|
42
|
-
[](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml)
|
|
43
|
+
[](https://www.python.org/downloads/)
|
|
43
44
|
[](https://opensource.org/licenses/MIT)
|
|
44
45
|
|
|
45
46
|
**The computer vision library that gets out of your way.**
|
|
@@ -57,7 +58,7 @@ zones = pf.Zones()
|
|
|
57
58
|
zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
|
|
58
59
|
|
|
59
60
|
for frame in video:
|
|
60
|
-
detections = pf.
|
|
61
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
61
62
|
detections = tracker.update(detections)
|
|
62
63
|
zones.update(detections)
|
|
63
64
|
|
|
@@ -77,6 +78,12 @@ pip install pixelflow
|
|
|
77
78
|
|
|
78
79
|
## Core Concepts
|
|
79
80
|
|
|
81
|
+
### Two Result Types
|
|
82
|
+
|
|
83
|
+
Every model output becomes one of two things. **Detections** for anything that localises — detection, segmentation, keypoints, OCR. **Classifications** for anything that only names.
|
|
84
|
+
|
|
85
|
+
They are peers, not variants of each other: three detections are three objects, while three classifications are three competing answers about one image.
|
|
86
|
+
|
|
80
87
|
### Detections - One Format, Every Framework
|
|
81
88
|
|
|
82
89
|
Convert outputs from any ML framework into a unified format:
|
|
@@ -87,28 +94,80 @@ import pixelflow as pf
|
|
|
87
94
|
# Ultralytics YOLO
|
|
88
95
|
from ultralytics import YOLO
|
|
89
96
|
model = YOLO("yolo11n.pt")
|
|
90
|
-
detections = pf.
|
|
97
|
+
detections = pf.from_ultralytics(model.predict(image))
|
|
91
98
|
|
|
92
99
|
# Detectron2
|
|
93
100
|
from detectron2.engine import DefaultPredictor
|
|
94
101
|
predictor = DefaultPredictor(cfg)
|
|
95
|
-
detections = pf.
|
|
102
|
+
detections = pf.from_detectron2(predictor(image), labels=["person", "car"])
|
|
103
|
+
|
|
104
|
+
# Mayaku (returns Instances directly; take labels from the checkpoint)
|
|
105
|
+
from mayaku import from_pretrained
|
|
106
|
+
predictor = from_pretrained("mayaku-n-det")
|
|
107
|
+
detections = pf.from_mayaku(predictor("photo.jpg"), labels=predictor.class_names)
|
|
96
108
|
|
|
97
109
|
# HuggingFace Transformers
|
|
98
110
|
from transformers import pipeline
|
|
99
111
|
detector = pipeline("object-detection")
|
|
100
|
-
detections = pf.
|
|
112
|
+
detections = pf.from_transformers(detector(image))
|
|
101
113
|
|
|
102
|
-
# Florence-2
|
|
103
|
-
detections = pf.
|
|
114
|
+
# Florence-2 - captions and OCR reads land in det.text, <OD> classes in det.class_name
|
|
115
|
+
detections = pf.from_florence2(model_output, task_prompt="<OD>")
|
|
104
116
|
|
|
105
117
|
# SAM (Segment Anything)
|
|
106
|
-
detections = pf.
|
|
118
|
+
detections = pf.from_sam(masks, scores)
|
|
119
|
+
|
|
120
|
+
# RF-DETR
|
|
121
|
+
detections = pf.from_rfdetr(model_output)
|
|
122
|
+
|
|
123
|
+
# Supervision
|
|
124
|
+
detections = pf.from_supervision(sv_detections)
|
|
125
|
+
|
|
126
|
+
# EasyOCR - text lands in det.text, the quad in det.segments
|
|
127
|
+
import easyocr
|
|
128
|
+
reader = easyocr.Reader(['en'])
|
|
129
|
+
detections = pf.from_easyocr(reader.readtext("sign.jpg"))
|
|
130
|
+
|
|
131
|
+
# Plain arrays (numpy or torch) - no framework container to convert from
|
|
132
|
+
detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Classifications - When the Model Only Names
|
|
136
|
+
|
|
137
|
+
A classifier localises nothing, so its result carries no geometry at all:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import pixelflow as pf
|
|
141
|
+
|
|
142
|
+
# Ultralytics -cls checkpoints
|
|
143
|
+
from ultralytics import YOLO
|
|
144
|
+
model = YOLO("yolo11n-cls.pt")
|
|
145
|
+
result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
146
|
+
|
|
147
|
+
result.top1.class_name # 'golden retriever'
|
|
148
|
+
result.top_k(5) # -> Classifications, best first
|
|
149
|
+
result.to_json()
|
|
150
|
+
|
|
151
|
+
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
152
|
+
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
153
|
+
result = pf.from_scores(similarities, labels=prompts)
|
|
107
154
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
# Multi-label: however many labels genuinely match, not a fixed count
|
|
156
|
+
matches = result.filter_by_confidence(0.3)
|
|
157
|
+
|
|
158
|
+
# Draw it
|
|
159
|
+
frame = pf.annotate.classification(frame, result, top_k=3)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Scores are reported exactly as the model emitted them. PixelFlow does not normalise,
|
|
163
|
+
re-softmax, or assume they sum to 1, so a softmax over a fixed class list and an
|
|
164
|
+
independent per-label similarity both survive intact. A model emitting logits should
|
|
165
|
+
be converted by the caller, who is the only one who knows which convention applies.
|
|
166
|
+
|
|
167
|
+
Per-frame paths can skip building a row per class:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
result = pf.from_ultralytics_classification(model.predict(frame), top_k=5)
|
|
112
171
|
```
|
|
113
172
|
|
|
114
173
|
### Powerful Filtering
|
|
@@ -222,7 +281,7 @@ import pixelflow as pf
|
|
|
222
281
|
tracker = pf.tracker.ByteTracker()
|
|
223
282
|
|
|
224
283
|
for frame in media.frames:
|
|
225
|
-
detections = pf.
|
|
284
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
226
285
|
detections = tracker.update(detections)
|
|
227
286
|
|
|
228
287
|
for det in detections:
|
|
@@ -302,7 +361,7 @@ slicer = pf.SlicedInference(
|
|
|
302
361
|
)
|
|
303
362
|
|
|
304
363
|
def detector(image):
|
|
305
|
-
return pf.
|
|
364
|
+
return pf.from_ultralytics(model.predict(image))
|
|
306
365
|
|
|
307
366
|
# Run on 4K image - automatically slices, detects, and merges
|
|
308
367
|
large_image = cv2.imread("satellite.jpg") # 4000x3000
|
|
@@ -325,7 +384,7 @@ crossings.add_line(
|
|
|
325
384
|
)
|
|
326
385
|
|
|
327
386
|
for frame in media.frames:
|
|
328
|
-
detections = tracker.update(pf.
|
|
387
|
+
detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
|
|
329
388
|
crossings.update(detections)
|
|
330
389
|
|
|
331
390
|
print(f"Crossed: {crossings.get_counts()}")
|
|
@@ -352,15 +411,19 @@ report = detections.to_json_with_metrics()
|
|
|
352
411
|
|
|
353
412
|
| Framework | Converter | Features |
|
|
354
413
|
|-----------|-----------|----------|
|
|
414
|
+
| None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
|
|
355
415
|
| Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
|
|
356
416
|
| Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
|
|
417
|
+
| Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
|
|
357
418
|
| HuggingFace Transformers | `from_transformers()` | Boxes, scores |
|
|
358
|
-
| Florence-2 | `from_florence2()` | Boxes,
|
|
419
|
+
| Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
|
|
359
420
|
| SAM | `from_sam()` | Masks, scores |
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
|
|
|
421
|
+
| EfficientTAM | `from_efficienttam()` | Masks, scores |
|
|
422
|
+
| RF-DETR | `from_rfdetr()` | Boxes, masks |
|
|
423
|
+
| Supervision | `from_supervision()` | Boxes, masks, keypoints |
|
|
424
|
+
| Falcon Perception | `from_falcon_perception()` | Boxes, masks |
|
|
425
|
+
| EasyOCR | `from_easyocr()` | Quads, text, scores |
|
|
426
|
+
| Datamarkin API | `from_datamarkin()` | Boxes, masks, keypoints |
|
|
364
427
|
|
|
365
428
|
## Documentation
|
|
366
429
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# PixelFlow
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/pixelflow)
|
|
4
|
-
[](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
8
|
**The computer vision library that gets out of your way.**
|
|
@@ -19,7 +20,7 @@ zones = pf.Zones()
|
|
|
19
20
|
zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
|
|
20
21
|
|
|
21
22
|
for frame in video:
|
|
22
|
-
detections = pf.
|
|
23
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
23
24
|
detections = tracker.update(detections)
|
|
24
25
|
zones.update(detections)
|
|
25
26
|
|
|
@@ -39,6 +40,12 @@ pip install pixelflow
|
|
|
39
40
|
|
|
40
41
|
## Core Concepts
|
|
41
42
|
|
|
43
|
+
### Two Result Types
|
|
44
|
+
|
|
45
|
+
Every model output becomes one of two things. **Detections** for anything that localises — detection, segmentation, keypoints, OCR. **Classifications** for anything that only names.
|
|
46
|
+
|
|
47
|
+
They are peers, not variants of each other: three detections are three objects, while three classifications are three competing answers about one image.
|
|
48
|
+
|
|
42
49
|
### Detections - One Format, Every Framework
|
|
43
50
|
|
|
44
51
|
Convert outputs from any ML framework into a unified format:
|
|
@@ -49,28 +56,80 @@ import pixelflow as pf
|
|
|
49
56
|
# Ultralytics YOLO
|
|
50
57
|
from ultralytics import YOLO
|
|
51
58
|
model = YOLO("yolo11n.pt")
|
|
52
|
-
detections = pf.
|
|
59
|
+
detections = pf.from_ultralytics(model.predict(image))
|
|
53
60
|
|
|
54
61
|
# Detectron2
|
|
55
62
|
from detectron2.engine import DefaultPredictor
|
|
56
63
|
predictor = DefaultPredictor(cfg)
|
|
57
|
-
detections = pf.
|
|
64
|
+
detections = pf.from_detectron2(predictor(image), labels=["person", "car"])
|
|
65
|
+
|
|
66
|
+
# Mayaku (returns Instances directly; take labels from the checkpoint)
|
|
67
|
+
from mayaku import from_pretrained
|
|
68
|
+
predictor = from_pretrained("mayaku-n-det")
|
|
69
|
+
detections = pf.from_mayaku(predictor("photo.jpg"), labels=predictor.class_names)
|
|
58
70
|
|
|
59
71
|
# HuggingFace Transformers
|
|
60
72
|
from transformers import pipeline
|
|
61
73
|
detector = pipeline("object-detection")
|
|
62
|
-
detections = pf.
|
|
74
|
+
detections = pf.from_transformers(detector(image))
|
|
63
75
|
|
|
64
|
-
# Florence-2
|
|
65
|
-
detections = pf.
|
|
76
|
+
# Florence-2 - captions and OCR reads land in det.text, <OD> classes in det.class_name
|
|
77
|
+
detections = pf.from_florence2(model_output, task_prompt="<OD>")
|
|
66
78
|
|
|
67
79
|
# SAM (Segment Anything)
|
|
68
|
-
detections = pf.
|
|
80
|
+
detections = pf.from_sam(masks, scores)
|
|
81
|
+
|
|
82
|
+
# RF-DETR
|
|
83
|
+
detections = pf.from_rfdetr(model_output)
|
|
84
|
+
|
|
85
|
+
# Supervision
|
|
86
|
+
detections = pf.from_supervision(sv_detections)
|
|
87
|
+
|
|
88
|
+
# EasyOCR - text lands in det.text, the quad in det.segments
|
|
89
|
+
import easyocr
|
|
90
|
+
reader = easyocr.Reader(['en'])
|
|
91
|
+
detections = pf.from_easyocr(reader.readtext("sign.jpg"))
|
|
92
|
+
|
|
93
|
+
# Plain arrays (numpy or torch) - no framework container to convert from
|
|
94
|
+
detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Classifications - When the Model Only Names
|
|
98
|
+
|
|
99
|
+
A classifier localises nothing, so its result carries no geometry at all:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import pixelflow as pf
|
|
103
|
+
|
|
104
|
+
# Ultralytics -cls checkpoints
|
|
105
|
+
from ultralytics import YOLO
|
|
106
|
+
model = YOLO("yolo11n-cls.pt")
|
|
107
|
+
result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
108
|
+
|
|
109
|
+
result.top1.class_name # 'golden retriever'
|
|
110
|
+
result.top_k(5) # -> Classifications, best first
|
|
111
|
+
result.to_json()
|
|
112
|
+
|
|
113
|
+
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
114
|
+
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
115
|
+
result = pf.from_scores(similarities, labels=prompts)
|
|
69
116
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
# Multi-label: however many labels genuinely match, not a fixed count
|
|
118
|
+
matches = result.filter_by_confidence(0.3)
|
|
119
|
+
|
|
120
|
+
# Draw it
|
|
121
|
+
frame = pf.annotate.classification(frame, result, top_k=3)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Scores are reported exactly as the model emitted them. PixelFlow does not normalise,
|
|
125
|
+
re-softmax, or assume they sum to 1, so a softmax over a fixed class list and an
|
|
126
|
+
independent per-label similarity both survive intact. A model emitting logits should
|
|
127
|
+
be converted by the caller, who is the only one who knows which convention applies.
|
|
128
|
+
|
|
129
|
+
Per-frame paths can skip building a row per class:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
result = pf.from_ultralytics_classification(model.predict(frame), top_k=5)
|
|
74
133
|
```
|
|
75
134
|
|
|
76
135
|
### Powerful Filtering
|
|
@@ -184,7 +243,7 @@ import pixelflow as pf
|
|
|
184
243
|
tracker = pf.tracker.ByteTracker()
|
|
185
244
|
|
|
186
245
|
for frame in media.frames:
|
|
187
|
-
detections = pf.
|
|
246
|
+
detections = pf.from_ultralytics(model.predict(frame))
|
|
188
247
|
detections = tracker.update(detections)
|
|
189
248
|
|
|
190
249
|
for det in detections:
|
|
@@ -264,7 +323,7 @@ slicer = pf.SlicedInference(
|
|
|
264
323
|
)
|
|
265
324
|
|
|
266
325
|
def detector(image):
|
|
267
|
-
return pf.
|
|
326
|
+
return pf.from_ultralytics(model.predict(image))
|
|
268
327
|
|
|
269
328
|
# Run on 4K image - automatically slices, detects, and merges
|
|
270
329
|
large_image = cv2.imread("satellite.jpg") # 4000x3000
|
|
@@ -287,7 +346,7 @@ crossings.add_line(
|
|
|
287
346
|
)
|
|
288
347
|
|
|
289
348
|
for frame in media.frames:
|
|
290
|
-
detections = tracker.update(pf.
|
|
349
|
+
detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
|
|
291
350
|
crossings.update(detections)
|
|
292
351
|
|
|
293
352
|
print(f"Crossed: {crossings.get_counts()}")
|
|
@@ -314,15 +373,19 @@ report = detections.to_json_with_metrics()
|
|
|
314
373
|
|
|
315
374
|
| Framework | Converter | Features |
|
|
316
375
|
|-----------|-----------|----------|
|
|
376
|
+
| None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
|
|
317
377
|
| Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
|
|
318
378
|
| Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
|
|
379
|
+
| Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
|
|
319
380
|
| HuggingFace Transformers | `from_transformers()` | Boxes, scores |
|
|
320
|
-
| Florence-2 | `from_florence2()` | Boxes,
|
|
381
|
+
| Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
|
|
321
382
|
| SAM | `from_sam()` | Masks, scores |
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
|
325
|
-
|
|
|
383
|
+
| EfficientTAM | `from_efficienttam()` | Masks, scores |
|
|
384
|
+
| RF-DETR | `from_rfdetr()` | Boxes, masks |
|
|
385
|
+
| Supervision | `from_supervision()` | Boxes, masks, keypoints |
|
|
386
|
+
| Falcon Perception | `from_falcon_perception()` | Boxes, masks |
|
|
387
|
+
| EasyOCR | `from_easyocr()` | Quads, text, scores |
|
|
388
|
+
| Datamarkin API | `from_datamarkin()` | Boxes, masks, keypoints |
|
|
326
389
|
|
|
327
390
|
## Documentation
|
|
328
391
|
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
__version__ = "0.1
|
|
1
|
+
__version__ = "0.3.1"
|
|
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)
|