pixelflow 0.3.2__tar.gz → 0.6.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 (65) hide show
  1. {pixelflow-0.3.2 → pixelflow-0.6.0}/LICENSE +1 -1
  2. {pixelflow-0.3.2 → pixelflow-0.6.0}/PKG-INFO +72 -32
  3. {pixelflow-0.3.2 → pixelflow-0.6.0}/README.md +65 -24
  4. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/__init__.py +38 -6
  5. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/detections.py +177 -8
  6. pixelflow-0.6.0/pixelflow/masks.py +212 -0
  7. pixelflow-0.6.0/pixelflow/media.py +961 -0
  8. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/__init__.py +3 -0
  9. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/detections.py +6 -2
  10. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/image.py +74 -0
  11. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/validators.py +12 -24
  12. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/PKG-INFO +72 -32
  13. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/SOURCES.txt +1 -1
  14. {pixelflow-0.3.2 → pixelflow-0.6.0}/pyproject.toml +6 -8
  15. pixelflow-0.3.2/pixelflow/assets.py +0 -378
  16. pixelflow-0.3.2/pixelflow/media.py +0 -458
  17. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/__init__.py +0 -0
  18. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/anchors.py +0 -0
  19. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/blur.py +0 -0
  20. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/box.py +0 -0
  21. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/classification.py +0 -0
  22. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/crossing.py +0 -0
  23. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/dot.py +0 -0
  24. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/filled_box.py +0 -0
  25. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/fps_counter.py +0 -0
  26. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/grid_overlay.py +0 -0
  27. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/heatmap.py +0 -0
  28. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/keypoint.py +0 -0
  29. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/keypoint_skeleton.py +0 -0
  30. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/label.py +0 -0
  31. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/mask.py +0 -0
  32. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/motion_dots.py +0 -0
  33. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/motion_trails.py +0 -0
  34. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/oval.py +0 -0
  35. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/pixelate.py +0 -0
  36. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/polygon.py +0 -0
  37. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/scale_bar.py +0 -0
  38. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/utils.py +0 -0
  39. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/zones.py +0 -0
  40. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/arrays.py +0 -0
  41. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/buffer.py +0 -0
  42. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/__init__.py +0 -0
  43. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/classifications.py +0 -0
  44. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/converters.py +0 -0
  45. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/colors.py +0 -0
  46. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/crossings.py +0 -0
  47. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/__init__.py +0 -0
  48. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/converters.py +0 -0
  49. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/filters.py +0 -0
  50. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/labels.py +0 -0
  51. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/slicer.py +0 -0
  52. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/smoother.py +0 -0
  53. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/strategies.py +0 -0
  54. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/timer.py +0 -0
  55. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/__init__.py +0 -0
  56. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/bytetrack.py +0 -0
  57. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/kalman_filter.py +0 -0
  58. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/matching.py +0 -0
  59. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/track.py +0 -0
  60. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/inverse.py +0 -0
  61. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/zones.py +0 -0
  62. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/dependency_links.txt +0 -0
  63. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/requires.txt +0 -0
  64. {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/top_level.txt +0 -0
  65. {pixelflow-0.3.2 → pixelflow-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 datamarkin
3
+ Copyright (c) 2025 Emrah NAZIF
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelflow
3
- Version: 0.3.2
3
+ Version: 0.6.0
4
4
  Summary: Computer vision processing library for visualizing and analyzing detection results from ML models
5
- Author-email: Emrah Nazif <emrah@datamarkin.com>, Datamarkin <support@datamarkin.com>
5
+ Author-email: Emrah Nazif <emrah@datamarkin.com>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/datamarkin/pixelflow
8
- Project-URL: Repository, https://github.com/datamarkin/pixelflow
9
- Project-URL: Issues, https://github.com/datamarkin/pixelflow/issues
10
- Project-URL: Documentation, https://pixelflow.datamarkin.com
7
+ Project-URL: Homepage, https://github.com/emrahnazif/pixelflow
8
+ Project-URL: Repository, https://github.com/emrahnazif/pixelflow
9
+ Project-URL: Issues, https://github.com/emrahnazif/pixelflow/issues
10
+ Project-URL: Documentation, https://emrah.fr/pixelflow/
11
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
@@ -15,14 +15,13 @@ Classifier: Intended Audience :: Science/Research
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
22
  Classifier: Topic :: Scientific/Engineering :: Image Recognition
24
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
- Requires-Python: >=3.9
24
+ Requires-Python: >=3.10
26
25
  Description-Content-Type: text/markdown
27
26
  License-File: LICENSE
28
27
  Requires-Dist: shapely>=2.0.0
@@ -39,8 +38,8 @@ Dynamic: license-file
39
38
  # PixelFlow
40
39
 
41
40
  [![PyPI version](https://badge.fury.io/py/pixelflow.svg)](https://badge.fury.io/py/pixelflow)
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/)
41
+ [![CI](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml/badge.svg)](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml)
42
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
44
43
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
44
 
46
45
  **The computer vision library that gets out of your way.**
@@ -52,12 +51,13 @@ import pixelflow as pf
52
51
  from ultralytics import YOLO
53
52
 
54
53
  model = YOLO("yolo11n.pt")
55
- video = pf.VideoReader("traffic.mp4", width=640)
54
+ video = pf.VideoReader("traffic.mp4")
56
55
  tracker = pf.tracker.ByteTracker()
57
56
  zones = pf.Zones()
58
57
  zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
59
58
 
60
59
  for frame in video:
60
+ frame = pf.transform.resize(frame, width=640)
61
61
  detections = pf.from_ultralytics(model.predict(frame))
62
62
  detections = tracker.update(detections)
63
63
  zones.update(detections)
@@ -66,8 +66,7 @@ for frame in video:
66
66
  frame = pf.annotate.label(frame, detections)
67
67
  frame = pf.annotate.zones(frame, zones)
68
68
 
69
- if pf.show_frame("Live", frame) == ord('q'):
70
- break
69
+ pf.display_video(frame, "Live") # raises DisplayExit when 'q' is pressed
71
70
  ```
72
71
 
73
72
  ## Installation
@@ -132,6 +131,17 @@ detections = pf.from_easyocr(reader.readtext("sign.jpg"))
132
131
  detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
133
132
  ```
134
133
 
134
+ Segmentation models answer with raster masks. One call turns them into polygons,
135
+ whichever model they came from:
136
+
137
+ ```python
138
+ detections = pf.from_arrays(boxes, scores, masks=masks) # boolean (N, H, W)
139
+ detections.masks_to_segments() # fills det.segments
140
+ detections.masks_to_segments(keep_masks=False) # ...and drops the rasters
141
+
142
+ polygon = pf.mask_to_polygon(mask) # or one mask at a time
143
+ ```
144
+
135
145
  ### Classifications - When the Model Only Names
136
146
 
137
147
  A classifier localises nothing, so its result carries no geometry at all:
@@ -200,34 +210,64 @@ english_text = detections.filter_by_text_language("en")
200
210
 
201
211
  ### Media Handling
202
212
 
203
- Purpose-built classes for videos, cameras, and images:
213
+ A source is anything that yields RGB `uint8` `HxWx3` arrays. `VideoReader` and
214
+ `CameraStream` are conveniences, not gates — a list, a generator, or your own reader
215
+ for the one camera that needs special handling all work identically everywhere else.
204
216
 
205
217
  ```python
206
218
  import pixelflow as pf
207
219
 
208
220
  # Read a video file
209
- video = pf.VideoReader("video.mp4", width=640)
210
- print(f"{video.width}x{video.height}, {video.fps}fps, {len(video)} frames")
221
+ video = pf.VideoReader("video.mp4")
222
+ print(f"{video.width}x{video.height}, {video.fps}fps, {video.frames} frames")
211
223
 
212
- for frame in video: # Replayable — resets on each iteration
213
- if pf.show_frame("Preview", frame) == ord('q'):
214
- break
224
+ for frame in video:
225
+ pf.display_video(frame, "Preview")
226
+
227
+ video.seek(0) # replay is explicit; iteration never rewinds on its own
228
+
229
+ # Every 5th frame. fps and frames are divided to match, so a writer built from
230
+ # this source stays correct.
231
+ video = pf.VideoReader("video.mp4", stride=5)
215
232
 
216
- # Load a single image
217
- image = pf.read_image("photo.jpg", width=640)
233
+ # Load a single image (EXIF orientation applied, always RGB uint8 HxWx3)
234
+ image = pf.read_image("photo.jpg")
218
235
 
219
- # Webcam / network stream
220
- cam = pf.CameraStream(0, width=640)
236
+ # Webcam / network stream — same facts, same iteration, is_live is True
237
+ cam = pf.CameraStream(0)
221
238
  cam = pf.CameraStream("rtsp://camera.local/stream")
239
+ ```
240
+
241
+ Reading and resizing are two jobs — compose them:
222
242
 
223
- # Write processed video
224
- writer = pf.VideoWriter("output.mp4", fps=video.fps)
243
+ ```python
225
244
  for frame in video:
226
- processed = process(frame)
227
- writer.write(processed)
228
- writer.close()
245
+ frame = pf.transform.resize(frame, width=640) # or height=
246
+ ```
247
+
248
+ **Writing.** `like=` carries the frame rate across from a source, which is the one
249
+ value that has to and the one that is easy to get wrong — a strided reader yields
250
+ fewer frames per second than its file contains. Size is *not* carried: it comes from
251
+ the first frame written, because the loop in between is allowed to resize.
252
+
253
+ ```python
254
+ video = pf.VideoReader("input.mp4", stride=2)
255
+
256
+ with pf.VideoWriter("output.mp4", like=video) as writer:
257
+ for frame in video:
258
+ writer.write(process(frame))
229
259
  ```
230
260
 
261
+ Every source reports the same facts, so consumers never branch on the source type:
262
+
263
+ | | `VideoReader` | `CameraStream` |
264
+ |---|---|---|
265
+ | `width` / `height` | from the file | from a frame decoded at open |
266
+ | `fps` | rate yielded, `None` if unknown | usually `None` — devices report 0 |
267
+ | `frames` | estimate, divided by stride | always `None` |
268
+ | `duration` | seconds, unaffected by stride | always `None` |
269
+ | `is_live` | `False` | `True` |
270
+
231
271
  ### Zone-Based Analytics
232
272
 
233
273
  Define spatial regions and track what enters them:
@@ -280,7 +320,7 @@ import pixelflow as pf
280
320
 
281
321
  tracker = pf.tracker.ByteTracker()
282
322
 
283
- for frame in media.frames:
323
+ for frame in video:
284
324
  detections = pf.from_ultralytics(model.predict(frame))
285
325
  detections = tracker.update(detections)
286
326
 
@@ -383,7 +423,7 @@ crossings.add_line(
383
423
  direction="down" # Only count downward crossings
384
424
  )
385
425
 
386
- for frame in media.frames:
426
+ for frame in video:
387
427
  detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
388
428
  crossings.update(detections)
389
429
 
@@ -427,7 +467,7 @@ report = detections.to_json_with_metrics()
427
467
 
428
468
  ## Documentation
429
469
 
430
- Full documentation available at [https://datamarkin.com/docs/pixelflow/](https://datamarkin.com/docs/pixelflow/)
470
+ Full documentation available at [https://emrah.fr/pixelflow/](https://emrah.fr/pixelflow/)
431
471
 
432
472
  ## Contributing
433
473
 
@@ -439,4 +479,4 @@ MIT License - see LICENSE file for details.
439
479
 
440
480
  ---
441
481
 
442
- Built with love by [Datamarkin](https://datamarkin.com)
482
+ Built with love by [Emrah Nazif](https://emrah.fr)
@@ -1,8 +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
- [![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/)
4
+ [![CI](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml/badge.svg)](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  **The computer vision library that gets out of your way.**
@@ -14,12 +14,13 @@ import pixelflow as pf
14
14
  from ultralytics import YOLO
15
15
 
16
16
  model = YOLO("yolo11n.pt")
17
- video = pf.VideoReader("traffic.mp4", width=640)
17
+ video = pf.VideoReader("traffic.mp4")
18
18
  tracker = pf.tracker.ByteTracker()
19
19
  zones = pf.Zones()
20
20
  zones.add_zone([(100, 400), (500, 400), (500, 600), (100, 600)], zone_id="entrance")
21
21
 
22
22
  for frame in video:
23
+ frame = pf.transform.resize(frame, width=640)
23
24
  detections = pf.from_ultralytics(model.predict(frame))
24
25
  detections = tracker.update(detections)
25
26
  zones.update(detections)
@@ -28,8 +29,7 @@ for frame in video:
28
29
  frame = pf.annotate.label(frame, detections)
29
30
  frame = pf.annotate.zones(frame, zones)
30
31
 
31
- if pf.show_frame("Live", frame) == ord('q'):
32
- break
32
+ pf.display_video(frame, "Live") # raises DisplayExit when 'q' is pressed
33
33
  ```
34
34
 
35
35
  ## Installation
@@ -94,6 +94,17 @@ detections = pf.from_easyocr(reader.readtext("sign.jpg"))
94
94
  detections = pf.from_arrays(boxes, scores, class_ids, labels=model.class_names)
95
95
  ```
96
96
 
97
+ Segmentation models answer with raster masks. One call turns them into polygons,
98
+ whichever model they came from:
99
+
100
+ ```python
101
+ detections = pf.from_arrays(boxes, scores, masks=masks) # boolean (N, H, W)
102
+ detections.masks_to_segments() # fills det.segments
103
+ detections.masks_to_segments(keep_masks=False) # ...and drops the rasters
104
+
105
+ polygon = pf.mask_to_polygon(mask) # or one mask at a time
106
+ ```
107
+
97
108
  ### Classifications - When the Model Only Names
98
109
 
99
110
  A classifier localises nothing, so its result carries no geometry at all:
@@ -162,34 +173,64 @@ english_text = detections.filter_by_text_language("en")
162
173
 
163
174
  ### Media Handling
164
175
 
165
- Purpose-built classes for videos, cameras, and images:
176
+ A source is anything that yields RGB `uint8` `HxWx3` arrays. `VideoReader` and
177
+ `CameraStream` are conveniences, not gates — a list, a generator, or your own reader
178
+ for the one camera that needs special handling all work identically everywhere else.
166
179
 
167
180
  ```python
168
181
  import pixelflow as pf
169
182
 
170
183
  # Read a video file
171
- video = pf.VideoReader("video.mp4", width=640)
172
- print(f"{video.width}x{video.height}, {video.fps}fps, {len(video)} frames")
184
+ video = pf.VideoReader("video.mp4")
185
+ print(f"{video.width}x{video.height}, {video.fps}fps, {video.frames} frames")
173
186
 
174
- for frame in video: # Replayable — resets on each iteration
175
- if pf.show_frame("Preview", frame) == ord('q'):
176
- break
187
+ for frame in video:
188
+ pf.display_video(frame, "Preview")
189
+
190
+ video.seek(0) # replay is explicit; iteration never rewinds on its own
191
+
192
+ # Every 5th frame. fps and frames are divided to match, so a writer built from
193
+ # this source stays correct.
194
+ video = pf.VideoReader("video.mp4", stride=5)
177
195
 
178
- # Load a single image
179
- image = pf.read_image("photo.jpg", width=640)
196
+ # Load a single image (EXIF orientation applied, always RGB uint8 HxWx3)
197
+ image = pf.read_image("photo.jpg")
180
198
 
181
- # Webcam / network stream
182
- cam = pf.CameraStream(0, width=640)
199
+ # Webcam / network stream — same facts, same iteration, is_live is True
200
+ cam = pf.CameraStream(0)
183
201
  cam = pf.CameraStream("rtsp://camera.local/stream")
202
+ ```
203
+
204
+ Reading and resizing are two jobs — compose them:
184
205
 
185
- # Write processed video
186
- writer = pf.VideoWriter("output.mp4", fps=video.fps)
206
+ ```python
187
207
  for frame in video:
188
- processed = process(frame)
189
- writer.write(processed)
190
- writer.close()
208
+ frame = pf.transform.resize(frame, width=640) # or height=
209
+ ```
210
+
211
+ **Writing.** `like=` carries the frame rate across from a source, which is the one
212
+ value that has to and the one that is easy to get wrong — a strided reader yields
213
+ fewer frames per second than its file contains. Size is *not* carried: it comes from
214
+ the first frame written, because the loop in between is allowed to resize.
215
+
216
+ ```python
217
+ video = pf.VideoReader("input.mp4", stride=2)
218
+
219
+ with pf.VideoWriter("output.mp4", like=video) as writer:
220
+ for frame in video:
221
+ writer.write(process(frame))
191
222
  ```
192
223
 
224
+ Every source reports the same facts, so consumers never branch on the source type:
225
+
226
+ | | `VideoReader` | `CameraStream` |
227
+ |---|---|---|
228
+ | `width` / `height` | from the file | from a frame decoded at open |
229
+ | `fps` | rate yielded, `None` if unknown | usually `None` — devices report 0 |
230
+ | `frames` | estimate, divided by stride | always `None` |
231
+ | `duration` | seconds, unaffected by stride | always `None` |
232
+ | `is_live` | `False` | `True` |
233
+
193
234
  ### Zone-Based Analytics
194
235
 
195
236
  Define spatial regions and track what enters them:
@@ -242,7 +283,7 @@ import pixelflow as pf
242
283
 
243
284
  tracker = pf.tracker.ByteTracker()
244
285
 
245
- for frame in media.frames:
286
+ for frame in video:
246
287
  detections = pf.from_ultralytics(model.predict(frame))
247
288
  detections = tracker.update(detections)
248
289
 
@@ -345,7 +386,7 @@ crossings.add_line(
345
386
  direction="down" # Only count downward crossings
346
387
  )
347
388
 
348
- for frame in media.frames:
389
+ for frame in video:
349
390
  detections = tracker.update(pf.from_ultralytics(model.predict(frame)))
350
391
  crossings.update(detections)
351
392
 
@@ -389,7 +430,7 @@ report = detections.to_json_with_metrics()
389
430
 
390
431
  ## Documentation
391
432
 
392
- Full documentation available at [https://datamarkin.com/docs/pixelflow/](https://datamarkin.com/docs/pixelflow/)
433
+ Full documentation available at [https://emrah.fr/pixelflow/](https://emrah.fr/pixelflow/)
393
434
 
394
435
  ## Contributing
395
436
 
@@ -401,4 +442,4 @@ MIT License - see LICENSE file for details.
401
442
 
402
443
  ---
403
444
 
404
- Built with love by [Datamarkin](https://datamarkin.com)
445
+ Built with love by [Emrah Nazif](https://emrah.fr)
@@ -1,5 +1,5 @@
1
- __version__ = "0.3.2"
2
- __author__ = "Datamarkin"
1
+ __version__ = "0.6.0"
2
+ __author__ = "Emrah NAZIF"
3
3
 
4
4
  # Import core modules
5
5
  from . import media
@@ -9,8 +9,6 @@ from . import colors
9
9
  from . import slicer
10
10
  from . import smoother
11
11
  from . import timer
12
- from . import tracker
13
- from . import assets
14
12
 
15
13
  # Result types and their converters. Every model output PixelFlow understands becomes
16
14
  # one of two things: Detections for anything that localises, Classifications for
@@ -28,9 +26,10 @@ from .classifications import __all__ as _CLASSIFICATION_EXPORTS
28
26
  # Import specific functions for top-level access
29
27
  from .media import (
30
28
  DisplayExit, VideoReader, CameraStream, VideoWriter,
31
- read_image, read_video, display_video, display_image, save_image,
29
+ read_image, read_video, encode_image, display_video, display_image, save_image,
32
30
  close_display, to_pil, from_pil,
33
31
  )
32
+ from .masks import mask_to_polygon
34
33
  from .zones import Zones
35
34
  from .crossings import Crossings
36
35
  from .slicer import SlicedInference, auto_slice_size
@@ -38,6 +37,36 @@ from .buffer import Buffer
38
37
  from .smoother import smooth
39
38
  from .timer import TimeTracker
40
39
 
40
+ # ByteTrack is the only thing in PixelFlow that needs SciPy, and SciPy is most of what importing
41
+ # PixelFlow costs: 436 ms of 631 ms, from scipy.linalg in the Kalman filter and scipy.optimize in
42
+ # the assignment step. Every caller that only draws boxes or converts a model's output paid it.
43
+ #
44
+ # So `tracker` loads on first use instead of at import. This is PEP 562, the language feature for
45
+ # exactly this, and it is what SciPy and NumPy do with their own subpackages -- `pf.tracker`,
46
+ # `from pixelflow import tracker` and `import pixelflow.tracker` all still work, and the module is
47
+ # cached in globals() after the first access so the cost is paid once.
48
+ #
49
+ # Nothing else in PixelFlow imports tracker, and it publishes no top-level names of its own, which
50
+ # is what makes this a one-line deferral rather than a lazy-name table.
51
+ _LAZY = {"tracker"}
52
+
53
+
54
+ def __getattr__(name):
55
+ """Load a deferred subpackage on first access. See the note above."""
56
+ if name in _LAZY:
57
+ import importlib
58
+
59
+ module = importlib.import_module(f".{name}", __name__)
60
+ globals()[name] = module
61
+ return module
62
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
63
+
64
+
65
+ def __dir__():
66
+ """Keep the deferred subpackages visible to `dir()` and to tab completion."""
67
+ return sorted(set(globals()) | _LAZY)
68
+
69
+
41
70
  # Define the public API
42
71
  __all__ = [
43
72
  # Result types and converters, declared by the subpackages that own them
@@ -59,6 +88,7 @@ __all__ = [
59
88
  "VideoWriter",
60
89
  "read_image",
61
90
  "read_video",
91
+ "encode_image",
62
92
  "display_video",
63
93
  "display_image",
64
94
  "save_image",
@@ -66,6 +96,9 @@ __all__ = [
66
96
  "to_pil",
67
97
  "from_pil",
68
98
 
99
+ # Mask geometry
100
+ "mask_to_polygon",
101
+
69
102
  # Spatial analysis
70
103
  "zones",
71
104
  "Zones",
@@ -73,7 +106,6 @@ __all__ = [
73
106
  "Crossings",
74
107
 
75
108
  # Processing utilities
76
- "assets",
77
109
  "slicer",
78
110
  "SlicedInference",
79
111
  "auto_slice_size",