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.
Files changed (67) hide show
  1. {pixelflow-0.1.3 → pixelflow-0.3.1}/PKG-INFO +85 -22
  2. {pixelflow-0.1.3 → pixelflow-0.3.1}/README.md +83 -20
  3. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/__init__.py +17 -11
  4. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/__init__.py +2 -0
  5. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/anchors.py +1 -1
  6. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/blur.py +1 -1
  7. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/box.py +1 -1
  8. pixelflow-0.3.1/pixelflow/annotators/classification.py +122 -0
  9. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/crossing.py +1 -1
  10. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/filled_box.py +1 -1
  11. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/grid_overlay.py +1 -1
  12. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/keypoint.py +14 -10
  13. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/keypoint_skeleton.py +15 -8
  14. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/label.py +14 -11
  15. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/mask.py +1 -1
  16. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/oval.py +1 -1
  17. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/pixelate.py +1 -1
  18. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/polygon.py +6 -2
  19. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/utils.py +24 -1
  20. pixelflow-0.3.1/pixelflow/arrays.py +20 -0
  21. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/assets.py +18 -4
  22. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/buffer.py +5 -5
  23. pixelflow-0.3.1/pixelflow/classifications/__init__.py +16 -0
  24. pixelflow-0.3.1/pixelflow/classifications/classifications.py +314 -0
  25. pixelflow-0.3.1/pixelflow/classifications/converters.py +171 -0
  26. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/colors.py +12 -4
  27. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/crossings.py +4 -4
  28. pixelflow-0.3.1/pixelflow/detections/__init__.py +45 -0
  29. pixelflow-0.3.1/pixelflow/detections/converters.py +1398 -0
  30. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/detections/detections.py +222 -124
  31. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/detections/filters.py +5 -5
  32. pixelflow-0.3.1/pixelflow/labels.py +73 -0
  33. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/slicer.py +4 -2
  34. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/smoother.py +3 -1
  35. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/strategies.py +2 -2
  36. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/timer.py +5 -5
  37. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/bytetrack.py +7 -7
  38. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/detections.py +32 -89
  39. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/inverse.py +42 -127
  40. pixelflow-0.3.1/pixelflow/validators.py +137 -0
  41. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/zones.py +6 -6
  42. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/PKG-INFO +85 -22
  43. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/SOURCES.txt +6 -1
  44. {pixelflow-0.1.3 → pixelflow-0.3.1}/pyproject.toml +5 -1
  45. pixelflow-0.1.3/pixelflow/classes.py +0 -86
  46. pixelflow-0.1.3/pixelflow/detections/__init__.py +0 -76
  47. pixelflow-0.1.3/pixelflow/detections/converters.py +0 -1072
  48. pixelflow-0.1.3/pixelflow/validators.py +0 -139
  49. {pixelflow-0.1.3 → pixelflow-0.3.1}/LICENSE +0 -0
  50. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/dot.py +0 -0
  51. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/fps_counter.py +0 -0
  52. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/heatmap.py +0 -0
  53. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/motion_dots.py +0 -0
  54. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/motion_trails.py +0 -0
  55. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/scale_bar.py +0 -0
  56. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/annotators/zones.py +0 -0
  57. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/media.py +0 -0
  58. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/__init__.py +0 -0
  59. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/kalman_filter.py +0 -0
  60. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/matching.py +0 -0
  61. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/tracker/track.py +0 -0
  62. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/__init__.py +0 -0
  63. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow/transforms/image.py +0 -0
  64. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/dependency_links.txt +0 -0
  65. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/requires.txt +0 -0
  66. {pixelflow-0.1.3 → pixelflow-0.3.1}/pixelflow.egg-info/top_level.txt +0 -0
  67. {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
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
  [![PyPI version](https://badge.fury.io/py/pixelflow.svg)](https://badge.fury.io/py/pixelflow)
42
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
42
+ [![CI](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml/badge.svg)](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml)
43
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
43
44
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_detectron2(predictor(image), class_names=["person", "car"])
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.detections.from_transformers(detector(image))
112
+ detections = pf.from_transformers(detector(image))
101
113
 
102
- # Florence-2
103
- detections = pf.detections.from_florence2(model_output)
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.detections.from_sam(masks, scores)
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
- # OCR Engines
109
- detections = pf.detections.from_tesseract(tesseract_output)
110
- detections = pf.detections.from_paddleocr(paddleocr_output)
111
- detections = pf.detections.from_easyocr(easyocr_output)
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_ultralytics(model.predict(frame)))
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, phrases |
419
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
359
420
  | SAM | `from_sam()` | Masks, scores |
360
- | Tesseract OCR | `from_tesseract()` | Text, boxes, confidence |
361
- | PaddleOCR | `from_paddleocr()` | Text, boxes, structure |
362
- | EasyOCR | `from_easyocr()` | Text, boxes |
363
- | PP-Structure | `from_ppstructure()` | Tables, formulas, layouts |
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
  [![PyPI version](https://badge.fury.io/py/pixelflow.svg)](https://badge.fury.io/py/pixelflow)
4
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
4
+ [![CI](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml/badge.svg)](https://github.com/datamarkin/pixelflow/actions/workflows/ci.yml)
5
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_detectron2(predictor(image), class_names=["person", "car"])
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.detections.from_transformers(detector(image))
74
+ detections = pf.from_transformers(detector(image))
63
75
 
64
- # Florence-2
65
- detections = pf.detections.from_florence2(model_output)
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.detections.from_sam(masks, scores)
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
- # OCR Engines
71
- detections = pf.detections.from_tesseract(tesseract_output)
72
- detections = pf.detections.from_paddleocr(paddleocr_output)
73
- detections = pf.detections.from_easyocr(easyocr_output)
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_ultralytics(model.predict(frame)))
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, phrases |
381
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
321
382
  | SAM | `from_sam()` | Masks, scores |
322
- | Tesseract OCR | `from_tesseract()` | Text, boxes, confidence |
323
- | PaddleOCR | `from_paddleocr()` | Text, boxes, structure |
324
- | EasyOCR | `from_easyocr()` | Text, boxes |
325
- | PP-Structure | `from_ppstructure()` | Tables, formulas, layouts |
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.3"
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
- from .classes import COCO_CLASSES
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
- # Core data structures
36
- "detections",
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.results.from_ultralytics(outputs)
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.results.from_ultralytics(outputs) # Convert to PixelFlow format
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.results.from_ultralytics(outputs) # Convert to PixelFlow format
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.detections.from_ultralytics(outputs)
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.results.from_ultralytics(outputs) # Convert to PixelFlow format
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.results.from_ultralytics(outputs) # Convert to PixelFlow format
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)