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.
Files changed (67) hide show
  1. {pixelflow-0.1.3 → pixelflow-0.3.0}/PKG-INFO +83 -21
  2. {pixelflow-0.1.3 → pixelflow-0.3.0}/README.md +81 -19
  3. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/__init__.py +17 -11
  4. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/__init__.py +2 -0
  5. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/anchors.py +1 -1
  6. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/blur.py +1 -1
  7. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/box.py +1 -1
  8. pixelflow-0.3.0/pixelflow/annotators/classification.py +122 -0
  9. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/crossing.py +1 -1
  10. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/filled_box.py +1 -1
  11. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/grid_overlay.py +1 -1
  12. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/keypoint.py +14 -10
  13. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/keypoint_skeleton.py +15 -8
  14. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/label.py +14 -11
  15. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/mask.py +1 -1
  16. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/oval.py +1 -1
  17. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/pixelate.py +1 -1
  18. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/polygon.py +6 -2
  19. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/utils.py +24 -1
  20. pixelflow-0.3.0/pixelflow/arrays.py +20 -0
  21. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/assets.py +18 -4
  22. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/buffer.py +5 -5
  23. pixelflow-0.3.0/pixelflow/classifications/__init__.py +16 -0
  24. pixelflow-0.3.0/pixelflow/classifications/classifications.py +314 -0
  25. pixelflow-0.3.0/pixelflow/classifications/converters.py +171 -0
  26. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/colors.py +12 -4
  27. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/crossings.py +4 -4
  28. pixelflow-0.3.0/pixelflow/detections/__init__.py +45 -0
  29. pixelflow-0.3.0/pixelflow/detections/converters.py +1398 -0
  30. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/detections/detections.py +222 -124
  31. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/detections/filters.py +5 -5
  32. pixelflow-0.3.0/pixelflow/labels.py +73 -0
  33. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/slicer.py +4 -2
  34. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/smoother.py +3 -1
  35. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/strategies.py +2 -2
  36. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/timer.py +5 -5
  37. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/bytetrack.py +7 -7
  38. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/detections.py +32 -89
  39. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/inverse.py +42 -127
  40. pixelflow-0.3.0/pixelflow/validators.py +137 -0
  41. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/zones.py +6 -6
  42. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/PKG-INFO +83 -21
  43. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/SOURCES.txt +6 -1
  44. {pixelflow-0.1.3 → pixelflow-0.3.0}/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.0}/LICENSE +0 -0
  50. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/dot.py +0 -0
  51. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/fps_counter.py +0 -0
  52. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/heatmap.py +0 -0
  53. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/motion_dots.py +0 -0
  54. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/motion_trails.py +0 -0
  55. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/scale_bar.py +0 -0
  56. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/annotators/zones.py +0 -0
  57. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/media.py +0 -0
  58. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/__init__.py +0 -0
  59. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/kalman_filter.py +0 -0
  60. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/matching.py +0 -0
  61. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/tracker/track.py +0 -0
  62. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/__init__.py +0 -0
  63. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow/transforms/image.py +0 -0
  64. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/dependency_links.txt +0 -0
  65. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/requires.txt +0 -0
  66. {pixelflow-0.1.3 → pixelflow-0.3.0}/pixelflow.egg-info/top_level.txt +0 -0
  67. {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.1.3
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_detectron2(predictor(image), class_names=["person", "car"])
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.detections.from_transformers(detector(image))
111
+ detections = pf.from_transformers(detector(image))
101
112
 
102
- # Florence-2
103
- detections = pf.detections.from_florence2(model_output)
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.detections.from_sam(masks, scores)
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
- # 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)
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_ultralytics(model.predict(frame)))
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, phrases |
418
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
359
419
  | 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 |
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_detectron2(predictor(image), class_names=["person", "car"])
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.detections.from_transformers(detector(image))
73
+ detections = pf.from_transformers(detector(image))
63
74
 
64
- # Florence-2
65
- detections = pf.detections.from_florence2(model_output)
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.detections.from_sam(masks, scores)
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
- # 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)
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.detections.from_ultralytics(model.predict(frame))
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.detections.from_ultralytics(model.predict(image))
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.detections.from_ultralytics(model.predict(frame)))
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, phrases |
380
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
321
381
  | 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 |
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.3"
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
- 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)