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.
- {pixelflow-0.3.2 → pixelflow-0.6.0}/LICENSE +1 -1
- {pixelflow-0.3.2 → pixelflow-0.6.0}/PKG-INFO +72 -32
- {pixelflow-0.3.2 → pixelflow-0.6.0}/README.md +65 -24
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/__init__.py +38 -6
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/detections.py +177 -8
- pixelflow-0.6.0/pixelflow/masks.py +212 -0
- pixelflow-0.6.0/pixelflow/media.py +961 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/__init__.py +3 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/detections.py +6 -2
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/image.py +74 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/validators.py +12 -24
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/PKG-INFO +72 -32
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/SOURCES.txt +1 -1
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pyproject.toml +6 -8
- pixelflow-0.3.2/pixelflow/assets.py +0 -378
- pixelflow-0.3.2/pixelflow/media.py +0 -458
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/__init__.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/anchors.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/blur.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/box.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/classification.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/crossing.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/dot.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/filled_box.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/fps_counter.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/grid_overlay.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/heatmap.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/keypoint.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/keypoint_skeleton.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/label.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/mask.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/motion_dots.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/motion_trails.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/oval.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/pixelate.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/polygon.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/scale_bar.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/utils.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/annotators/zones.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/arrays.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/buffer.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/__init__.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/classifications.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/classifications/converters.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/colors.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/crossings.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/__init__.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/converters.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/detections/filters.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/labels.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/slicer.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/smoother.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/strategies.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/timer.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/__init__.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/bytetrack.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/kalman_filter.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/matching.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/tracker/track.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/transforms/inverse.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow/zones.py +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/dependency_links.txt +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/requires.txt +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/pixelflow.egg-info/top_level.txt +0 -0
- {pixelflow-0.3.2 → pixelflow-0.6.0}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixelflow
|
|
3
|
-
Version: 0.
|
|
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
|
|
5
|
+
Author-email: Emrah Nazif <emrah@datamarkin.com>
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Repository, https://github.com/
|
|
9
|
-
Project-URL: Issues, https://github.com/
|
|
10
|
-
Project-URL: Documentation, https://pixelflow
|
|
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.
|
|
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
|
[](https://badge.fury.io/py/pixelflow)
|
|
42
|
-
[](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml)
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
44
43
|
[](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"
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
210
|
-
print(f"{video.width}x{video.height}, {video.fps}fps, {
|
|
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:
|
|
213
|
-
|
|
214
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
224
|
-
writer = pf.VideoWriter("output.mp4", fps=video.fps)
|
|
243
|
+
```python
|
|
225
244
|
for frame in video:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
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
|
|
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://
|
|
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 [
|
|
482
|
+
Built with love by [Emrah Nazif](https://emrah.fr)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# PixelFlow
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/pixelflow)
|
|
4
|
-
[](https://github.com/emrahnazif/pixelflow/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
6
|
[](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"
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
172
|
-
print(f"{video.width}x{video.height}, {video.fps}fps, {
|
|
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:
|
|
175
|
-
|
|
176
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
186
|
-
writer = pf.VideoWriter("output.mp4", fps=video.fps)
|
|
206
|
+
```python
|
|
187
207
|
for frame in video:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
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
|
|
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://
|
|
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 [
|
|
445
|
+
Built with love by [Emrah Nazif](https://emrah.fr)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
__version__ = "0.
|
|
2
|
-
__author__ = "
|
|
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",
|