pixelflow 0.1.2__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 (70) hide show
  1. {pixelflow-0.1.2 → pixelflow-0.3.0}/PKG-INFO +104 -37
  2. {pixelflow-0.1.2 → pixelflow-0.3.0}/README.md +101 -35
  3. pixelflow-0.3.0/pixelflow/__init__.py +91 -0
  4. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/__init__.py +7 -2
  5. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/anchors.py +3 -3
  6. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/blur.py +1 -1
  7. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/box.py +4 -4
  8. pixelflow-0.3.0/pixelflow/annotators/classification.py +122 -0
  9. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/crossing.py +7 -7
  10. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/filled_box.py +5 -5
  11. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/fps_counter.py +9 -6
  12. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/grid_overlay.py +5 -5
  13. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/keypoint.py +17 -13
  14. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/keypoint_skeleton.py +17 -10
  15. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/label.py +18 -15
  16. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/mask.py +4 -4
  17. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/oval.py +4 -4
  18. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/pixelate.py +1 -1
  19. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/polygon.py +10 -6
  20. pixelflow-0.3.0/pixelflow/annotators/utils.py +71 -0
  21. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/zones.py +1 -1
  22. pixelflow-0.3.0/pixelflow/arrays.py +20 -0
  23. pixelflow-0.3.0/pixelflow/assets.py +378 -0
  24. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/buffer.py +29 -30
  25. pixelflow-0.3.0/pixelflow/classifications/__init__.py +16 -0
  26. pixelflow-0.3.0/pixelflow/classifications/classifications.py +314 -0
  27. pixelflow-0.3.0/pixelflow/classifications/converters.py +171 -0
  28. pixelflow-0.3.0/pixelflow/colors.py +108 -0
  29. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/crossings.py +4 -4
  30. pixelflow-0.3.0/pixelflow/detections/__init__.py +45 -0
  31. pixelflow-0.3.0/pixelflow/detections/converters.py +1398 -0
  32. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/detections/detections.py +219 -323
  33. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/detections/filters.py +36 -350
  34. pixelflow-0.3.0/pixelflow/labels.py +73 -0
  35. pixelflow-0.3.0/pixelflow/media.py +458 -0
  36. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/slicer.py +4 -2
  37. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/smoother.py +13 -6
  38. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/strategies.py +2 -2
  39. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/timer.py +5 -5
  40. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/tracker/bytetrack.py +7 -7
  41. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/transforms/detections.py +38 -95
  42. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/transforms/image.py +14 -14
  43. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/transforms/inverse.py +42 -127
  44. pixelflow-0.3.0/pixelflow/validators.py +137 -0
  45. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/zones.py +6 -6
  46. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow.egg-info/PKG-INFO +104 -37
  47. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow.egg-info/SOURCES.txt +7 -0
  48. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow.egg-info/requires.txt +1 -0
  49. {pixelflow-0.1.2 → pixelflow-0.3.0}/pyproject.toml +7 -2
  50. pixelflow-0.1.2/pixelflow/__init__.py +0 -68
  51. pixelflow-0.1.2/pixelflow/annotators/utils.py +0 -48
  52. pixelflow-0.1.2/pixelflow/colors.py +0 -100
  53. pixelflow-0.1.2/pixelflow/detections/__init__.py +0 -91
  54. pixelflow-0.1.2/pixelflow/detections/converters.py +0 -2214
  55. pixelflow-0.1.2/pixelflow/media.py +0 -427
  56. pixelflow-0.1.2/pixelflow/validators.py +0 -139
  57. {pixelflow-0.1.2 → pixelflow-0.3.0}/LICENSE +0 -0
  58. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/dot.py +0 -0
  59. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/heatmap.py +0 -0
  60. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/motion_dots.py +0 -0
  61. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/motion_trails.py +0 -0
  62. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/annotators/scale_bar.py +0 -0
  63. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/tracker/__init__.py +0 -0
  64. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/tracker/kalman_filter.py +0 -0
  65. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/tracker/matching.py +0 -0
  66. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/tracker/track.py +0 -0
  67. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow/transforms/__init__.py +0 -0
  68. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow.egg-info/dependency_links.txt +0 -0
  69. {pixelflow-0.1.2 → pixelflow-0.3.0}/pixelflow.egg-info/top_level.txt +0 -0
  70. {pixelflow-0.1.2 → 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.2
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
@@ -29,6 +29,7 @@ Requires-Dist: shapely>=2.0.0
29
29
  Requires-Dist: numpy>=1.21.0
30
30
  Requires-Dist: opencv-python>=4.8.0
31
31
  Requires-Dist: scipy>=1.10.0
32
+ Requires-Dist: Pillow>=9.0.0
32
33
  Provides-Extra: dev
33
34
  Requires-Dist: pytest>=7.0.0; extra == "dev"
34
35
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -50,13 +51,13 @@ import pixelflow as pf
50
51
  from ultralytics import YOLO
51
52
 
52
53
  model = YOLO("yolo11n.pt")
53
- media = pf.Media("traffic.mp4")
54
+ video = pf.VideoReader("traffic.mp4", width=640)
54
55
  tracker = pf.tracker.ByteTracker()
55
56
  zones = pf.Zones()
56
57
  zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
57
58
 
58
- for frame in media.frames:
59
- detections = pf.detections.from_ultralytics(model.predict(frame))
59
+ for frame in video:
60
+ detections = pf.from_ultralytics(model.predict(frame))
60
61
  detections = tracker.update(detections)
61
62
  zones.update(detections)
62
63
 
@@ -64,7 +65,8 @@ for frame in media.frames:
64
65
  frame = pf.annotate.label(frame, detections)
65
66
  frame = pf.annotate.zones(frame, zones)
66
67
 
67
- pf.show_frame("Live", frame)
68
+ if pf.show_frame("Live", frame) == ord('q'):
69
+ break
68
70
  ```
69
71
 
70
72
  ## Installation
@@ -75,6 +77,12 @@ pip install pixelflow
75
77
 
76
78
  ## Core Concepts
77
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
+
78
86
  ### Detections - One Format, Every Framework
79
87
 
80
88
  Convert outputs from any ML framework into a unified format:
@@ -85,28 +93,80 @@ import pixelflow as pf
85
93
  # Ultralytics YOLO
86
94
  from ultralytics import YOLO
87
95
  model = YOLO("yolo11n.pt")
88
- detections = pf.detections.from_ultralytics(model.predict(image))
96
+ detections = pf.from_ultralytics(model.predict(image))
89
97
 
90
98
  # Detectron2
91
99
  from detectron2.engine import DefaultPredictor
92
100
  predictor = DefaultPredictor(cfg)
93
- 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)
94
107
 
95
108
  # HuggingFace Transformers
96
109
  from transformers import pipeline
97
110
  detector = pipeline("object-detection")
98
- detections = pf.detections.from_transformers(detector(image))
111
+ detections = pf.from_transformers(detector(image))
99
112
 
100
- # Florence-2
101
- 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>")
102
115
 
103
116
  # SAM (Segment Anything)
104
- 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)
105
124
 
106
- # OCR Engines
107
- detections = pf.detections.from_tesseract(tesseract_output)
108
- detections = pf.detections.from_paddleocr(paddleocr_output)
109
- detections = pf.detections.from_easyocr(easyocr_output)
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)
153
+
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)
110
170
  ```
111
171
 
112
172
  ### Powerful Filtering
@@ -139,29 +199,32 @@ english_text = detections.filter_by_text_language("en")
139
199
 
140
200
  ### Media Handling
141
201
 
142
- Unified interface for videos, images, webcams, and streams:
202
+ Purpose-built classes for videos, cameras, and images:
143
203
 
144
204
  ```python
145
205
  import pixelflow as pf
146
206
 
147
- # Video file with automatic resizing
148
- media = pf.Media("video.mp4", width=640)
149
- print(media.info) # MediaInfo(resolution=1920x1080, fps=30.00, frames=900, duration=30.00s)
207
+ # Read a video file
208
+ video = pf.VideoReader("video.mp4", width=640)
209
+ print(f"{video.width}x{video.height}, {video.fps}fps, {len(video)} frames")
150
210
 
151
- for frame in media.frames: # Lazy loading, memory efficient
152
- # Process frame
153
- pf.show_frame("Preview", frame)
211
+ for frame in video: # Replayable — resets on each iteration
212
+ if pf.show_frame("Preview", frame) == ord('q'):
213
+ break
154
214
 
155
- # Webcam
156
- webcam = pf.Media(0)
215
+ # Load a single image
216
+ image = pf.read_image("photo.jpg", width=640)
157
217
 
158
- # Network stream
159
- stream = pf.Media("rtsp://camera.local/stream")
218
+ # Webcam / network stream
219
+ cam = pf.CameraStream(0, width=640)
220
+ cam = pf.CameraStream("rtsp://camera.local/stream")
160
221
 
161
222
  # Write processed video
162
- for frame in pf.Media("input.mp4").frames:
223
+ writer = pf.VideoWriter("output.mp4", fps=video.fps)
224
+ for frame in video:
163
225
  processed = process(frame)
164
- pf.write_frame("output.mp4", processed, media.info)
226
+ writer.write(processed)
227
+ writer.close()
165
228
  ```
166
229
 
167
230
  ### Zone-Based Analytics
@@ -217,7 +280,7 @@ import pixelflow as pf
217
280
  tracker = pf.tracker.ByteTracker()
218
281
 
219
282
  for frame in media.frames:
220
- detections = pf.detections.from_ultralytics(model.predict(frame))
283
+ detections = pf.from_ultralytics(model.predict(frame))
221
284
  detections = tracker.update(detections)
222
285
 
223
286
  for det in detections:
@@ -297,7 +360,7 @@ slicer = pf.SlicedInference(
297
360
  )
298
361
 
299
362
  def detector(image):
300
- return pf.detections.from_ultralytics(model.predict(image))
363
+ return pf.from_ultralytics(model.predict(image))
301
364
 
302
365
  # Run on 4K image - automatically slices, detects, and merges
303
366
  large_image = cv2.imread("satellite.jpg") # 4000x3000
@@ -320,7 +383,7 @@ crossings.add_line(
320
383
  )
321
384
 
322
385
  for frame in media.frames:
323
- detections = tracker.update(pf.detections.from_ultralytics(model.predict(frame)))
386
+ detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
324
387
  crossings.update(detections)
325
388
 
326
389
  print(f"Crossed: {crossings.get_counts()}")
@@ -347,15 +410,19 @@ report = detections.to_json_with_metrics()
347
410
 
348
411
  | Framework | Converter | Features |
349
412
  |-----------|-----------|----------|
413
+ | None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
350
414
  | Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
351
415
  | Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
416
+ | Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
352
417
  | HuggingFace Transformers | `from_transformers()` | Boxes, scores |
353
- | Florence-2 | `from_florence2()` | Boxes, phrases |
418
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
354
419
  | SAM | `from_sam()` | Masks, scores |
355
- | Tesseract OCR | `from_tesseract()` | Text, boxes, confidence |
356
- | PaddleOCR | `from_paddleocr()` | Text, boxes, structure |
357
- | EasyOCR | `from_easyocr()` | Text, boxes |
358
- | 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 |
359
426
 
360
427
  ## Documentation
361
428
 
@@ -13,13 +13,13 @@ import pixelflow as pf
13
13
  from ultralytics import YOLO
14
14
 
15
15
  model = YOLO("yolo11n.pt")
16
- media = pf.Media("traffic.mp4")
16
+ video = pf.VideoReader("traffic.mp4", width=640)
17
17
  tracker = pf.tracker.ByteTracker()
18
18
  zones = pf.Zones()
19
19
  zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
20
20
 
21
- for frame in media.frames:
22
- detections = pf.detections.from_ultralytics(model.predict(frame))
21
+ for frame in video:
22
+ detections = pf.from_ultralytics(model.predict(frame))
23
23
  detections = tracker.update(detections)
24
24
  zones.update(detections)
25
25
 
@@ -27,7 +27,8 @@ for frame in media.frames:
27
27
  frame = pf.annotate.label(frame, detections)
28
28
  frame = pf.annotate.zones(frame, zones)
29
29
 
30
- pf.show_frame("Live", frame)
30
+ if pf.show_frame("Live", frame) == ord('q'):
31
+ break
31
32
  ```
32
33
 
33
34
  ## Installation
@@ -38,6 +39,12 @@ pip install pixelflow
38
39
 
39
40
  ## Core Concepts
40
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
+
41
48
  ### Detections - One Format, Every Framework
42
49
 
43
50
  Convert outputs from any ML framework into a unified format:
@@ -48,28 +55,80 @@ import pixelflow as pf
48
55
  # Ultralytics YOLO
49
56
  from ultralytics import YOLO
50
57
  model = YOLO("yolo11n.pt")
51
- detections = pf.detections.from_ultralytics(model.predict(image))
58
+ detections = pf.from_ultralytics(model.predict(image))
52
59
 
53
60
  # Detectron2
54
61
  from detectron2.engine import DefaultPredictor
55
62
  predictor = DefaultPredictor(cfg)
56
- 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)
57
69
 
58
70
  # HuggingFace Transformers
59
71
  from transformers import pipeline
60
72
  detector = pipeline("object-detection")
61
- detections = pf.detections.from_transformers(detector(image))
73
+ detections = pf.from_transformers(detector(image))
62
74
 
63
- # Florence-2
64
- 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>")
65
77
 
66
78
  # SAM (Segment Anything)
67
- 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)
68
86
 
69
- # OCR Engines
70
- detections = pf.detections.from_tesseract(tesseract_output)
71
- detections = pf.detections.from_paddleocr(paddleocr_output)
72
- detections = pf.detections.from_easyocr(easyocr_output)
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)
115
+
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)
73
132
  ```
74
133
 
75
134
  ### Powerful Filtering
@@ -102,29 +161,32 @@ english_text = detections.filter_by_text_language("en")
102
161
 
103
162
  ### Media Handling
104
163
 
105
- Unified interface for videos, images, webcams, and streams:
164
+ Purpose-built classes for videos, cameras, and images:
106
165
 
107
166
  ```python
108
167
  import pixelflow as pf
109
168
 
110
- # Video file with automatic resizing
111
- media = pf.Media("video.mp4", width=640)
112
- print(media.info) # MediaInfo(resolution=1920x1080, fps=30.00, frames=900, duration=30.00s)
169
+ # Read a video file
170
+ video = pf.VideoReader("video.mp4", width=640)
171
+ print(f"{video.width}x{video.height}, {video.fps}fps, {len(video)} frames")
113
172
 
114
- for frame in media.frames: # Lazy loading, memory efficient
115
- # Process frame
116
- pf.show_frame("Preview", frame)
173
+ for frame in video: # Replayable — resets on each iteration
174
+ if pf.show_frame("Preview", frame) == ord('q'):
175
+ break
117
176
 
118
- # Webcam
119
- webcam = pf.Media(0)
177
+ # Load a single image
178
+ image = pf.read_image("photo.jpg", width=640)
120
179
 
121
- # Network stream
122
- stream = pf.Media("rtsp://camera.local/stream")
180
+ # Webcam / network stream
181
+ cam = pf.CameraStream(0, width=640)
182
+ cam = pf.CameraStream("rtsp://camera.local/stream")
123
183
 
124
184
  # Write processed video
125
- for frame in pf.Media("input.mp4").frames:
185
+ writer = pf.VideoWriter("output.mp4", fps=video.fps)
186
+ for frame in video:
126
187
  processed = process(frame)
127
- pf.write_frame("output.mp4", processed, media.info)
188
+ writer.write(processed)
189
+ writer.close()
128
190
  ```
129
191
 
130
192
  ### Zone-Based Analytics
@@ -180,7 +242,7 @@ import pixelflow as pf
180
242
  tracker = pf.tracker.ByteTracker()
181
243
 
182
244
  for frame in media.frames:
183
- detections = pf.detections.from_ultralytics(model.predict(frame))
245
+ detections = pf.from_ultralytics(model.predict(frame))
184
246
  detections = tracker.update(detections)
185
247
 
186
248
  for det in detections:
@@ -260,7 +322,7 @@ slicer = pf.SlicedInference(
260
322
  )
261
323
 
262
324
  def detector(image):
263
- return pf.detections.from_ultralytics(model.predict(image))
325
+ return pf.from_ultralytics(model.predict(image))
264
326
 
265
327
  # Run on 4K image - automatically slices, detects, and merges
266
328
  large_image = cv2.imread("satellite.jpg") # 4000x3000
@@ -283,7 +345,7 @@ crossings.add_line(
283
345
  )
284
346
 
285
347
  for frame in media.frames:
286
- detections = tracker.update(pf.detections.from_ultralytics(model.predict(frame)))
348
+ detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
287
349
  crossings.update(detections)
288
350
 
289
351
  print(f"Crossed: {crossings.get_counts()}")
@@ -310,15 +372,19 @@ report = detections.to_json_with_metrics()
310
372
 
311
373
  | Framework | Converter | Features |
312
374
  |-----------|-----------|----------|
375
+ | None (plain numpy/torch arrays) | `from_arrays()` | Boxes, masks, keypoints |
313
376
  | Ultralytics (YOLO) | `from_ultralytics()` | Boxes, masks, keypoints |
314
377
  | Detectron2 | `from_detectron2()` | Boxes, masks, keypoints |
378
+ | Mayaku | `from_mayaku()` | Boxes, masks, keypoints |
315
379
  | HuggingFace Transformers | `from_transformers()` | Boxes, scores |
316
- | Florence-2 | `from_florence2()` | Boxes, phrases |
380
+ | Florence-2 | `from_florence2()` | Boxes, quads, polygons, captions |
317
381
  | SAM | `from_sam()` | Masks, scores |
318
- | Tesseract OCR | `from_tesseract()` | Text, boxes, confidence |
319
- | PaddleOCR | `from_paddleocr()` | Text, boxes, structure |
320
- | EasyOCR | `from_easyocr()` | Text, boxes |
321
- | 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 |
322
388
 
323
389
  ## Documentation
324
390
 
@@ -0,0 +1,91 @@
1
+ __version__ = "0.3.0"
2
+ __author__ = "Datamarkin"
3
+
4
+ # Import core modules
5
+ from . import media
6
+ from . import annotators as annotate
7
+ from . import transforms as transform
8
+ from . import colors
9
+ from . import slicer
10
+ from . import smoother
11
+ from . import timer
12
+ from . import tracker
13
+ from . import assets
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
27
+
28
+ # Import specific functions for top-level access
29
+ from .media import (
30
+ DisplayExit, VideoReader, CameraStream, VideoWriter,
31
+ read_image, read_video, display_video, display_image, save_image,
32
+ close_display, to_pil, from_pil,
33
+ )
34
+ from .zones import Zones
35
+ from .crossings import Crossings
36
+ from .slicer import SlicedInference, auto_slice_size
37
+ from .buffer import Buffer
38
+ from .smoother import smooth
39
+ from .timer import TimeTracker
40
+
41
+ # Define the public API
42
+ __all__ = [
43
+ # Result types and converters, declared by the subpackages that own them
44
+ *_DETECTION_EXPORTS,
45
+ *_CLASSIFICATION_EXPORTS,
46
+
47
+ # Visual components
48
+ "annotate",
49
+ "colors",
50
+
51
+ # Image transformations
52
+ "transform",
53
+
54
+ # Media handling
55
+ "media",
56
+ "DisplayExit",
57
+ "VideoReader",
58
+ "CameraStream",
59
+ "VideoWriter",
60
+ "read_image",
61
+ "read_video",
62
+ "display_video",
63
+ "display_image",
64
+ "save_image",
65
+ "close_display",
66
+ "to_pil",
67
+ "from_pil",
68
+
69
+ # Spatial analysis
70
+ "zones",
71
+ "Zones",
72
+ "crossings",
73
+ "Crossings",
74
+
75
+ # Processing utilities
76
+ "assets",
77
+ "slicer",
78
+ "SlicedInference",
79
+ "auto_slice_size",
80
+ "Buffer",
81
+ "smooth",
82
+
83
+ # Performance & tracking
84
+ "timer",
85
+ "TimeTracker",
86
+ "tracker",
87
+
88
+ # Metadata
89
+ "__version__",
90
+ "__author__"
91
+ ]
@@ -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
@@ -23,7 +24,7 @@ from .zones import zones
23
24
  # TODO: Import these once implemented
24
25
  from .filled_box import filled_box
25
26
  # from .dot import dot
26
- # from .fps_counter import fps_counter
27
+ from .fps_counter import fps_counter
27
28
  from .grid_overlay import grid_overlay
28
29
  # from .heatmap import heatmap
29
30
  from .keypoint import keypoint
@@ -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',
@@ -56,5 +58,8 @@ __all__ = [
56
58
 
57
59
  # Zone annotators
58
60
  'zones',
59
- 'crossings'
61
+ 'crossings',
62
+
63
+ # Overlay annotators
64
+ 'fps_counter',
60
65
  ]
@@ -38,7 +38,7 @@ def anchors(
38
38
  Supports drawing individual anchor points or multiple points simultaneously.
39
39
 
40
40
  Args:
41
- image (np.ndarray): Input image to draw anchor points on in BGR format.
41
+ image (np.ndarray): Input image to draw anchor points on in RGB format.
42
42
  Image is modified in-place.
43
43
  detections (Detections): Detection results containing bounding boxes.
44
44
  Each detection must have a 'bbox' attribute with (x1, y1, x2, y2) coordinates.
@@ -51,7 +51,7 @@ def anchors(
51
51
  thickness (Optional[int]): Thickness of circle outline in pixels.
52
52
  Use -1 for filled circles.
53
53
  If None, defaults to -1 (filled).
54
- colors (Optional[List[tuple]]): List of BGR color tuples for custom colors.
54
+ colors (Optional[List[tuple]]): List of RGB color tuples for custom colors.
55
55
  Colors mapped to unique class_ids in order of appearance.
56
56
  If None, uses default ColorManager colors.
57
57
 
@@ -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)
@@ -22,13 +22,13 @@ def box(
22
22
  Automatically adapts line thickness based on image dimensions for optimal visibility.
23
23
 
24
24
  Args:
25
- image (np.ndarray): Input image to draw boxes on (BGR format).
25
+ image (np.ndarray): Input image to draw boxes on (RGB format).
26
26
  Modified in-place with drawn bounding boxes.
27
27
  detections (Detections): Detections object containing bounding boxes.
28
28
  Each detection must have a 'bbox' attribute with (x1, y1, x2, y2) coordinates.
29
29
  thickness (Optional[int]): Line thickness for bounding boxes in pixels.
30
30
  If None, automatically determined based on image size.
31
- colors (Optional[List[tuple]]): List of BGR color tuples to override default colors.
31
+ colors (Optional[List[tuple]]): List of RGB color tuples to override default colors.
32
32
  Colors are mapped to unique class_ids in order of appearance.
33
33
  If None, uses default ColorManager colors.
34
34
 
@@ -48,13 +48,13 @@ 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)
55
55
  >>>
56
56
  >>> # Override with custom colors for specific classes
57
- >>> custom_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # Blue, Green, Red
57
+ >>> custom_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # Red, Green, Blue
58
58
  >>> annotated = pf.annotators.box(image, detections, colors=custom_colors)
59
59
  >>>
60
60
  >>> # Use custom thickness for fine control