pixelflow 0.6.0__tar.gz → 0.7.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.6.0 → pixelflow-0.7.0}/PKG-INFO +6 -2
- {pixelflow-0.6.0 → pixelflow-0.7.0}/README.md +5 -1
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/__init__.py +1 -1
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/classifications.py +76 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/detections.py +190 -0
- pixelflow-0.7.0/pixelflow/serialization.py +72 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/PKG-INFO +6 -2
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/SOURCES.txt +1 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/LICENSE +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/__init__.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/anchors.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/blur.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/box.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/classification.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/crossing.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/dot.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/filled_box.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/fps_counter.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/grid_overlay.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/heatmap.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/keypoint.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/keypoint_skeleton.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/label.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/mask.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/motion_dots.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/motion_trails.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/oval.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/pixelate.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/polygon.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/scale_bar.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/utils.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/zones.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/arrays.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/buffer.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/__init__.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/converters.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/colors.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/crossings.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/__init__.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/converters.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/filters.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/labels.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/masks.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/media.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/slicer.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/smoother.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/strategies.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/timer.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/__init__.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/bytetrack.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/kalman_filter.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/matching.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/track.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/__init__.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/detections.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/image.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/inverse.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/validators.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/zones.py +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/dependency_links.txt +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/requires.txt +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/top_level.txt +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/pyproject.toml +0 -0
- {pixelflow-0.6.0 → pixelflow-0.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixelflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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>
|
|
6
6
|
License: MIT
|
|
@@ -156,7 +156,7 @@ result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
|
156
156
|
|
|
157
157
|
result.top1.class_name # 'golden retriever'
|
|
158
158
|
result.top_k(5) # -> Classifications, best first
|
|
159
|
-
result.to_json()
|
|
159
|
+
result.to_json() # pf.Classifications.from_json() reads it back
|
|
160
160
|
|
|
161
161
|
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
162
162
|
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
@@ -445,6 +445,10 @@ df = pd.DataFrame(data)
|
|
|
445
445
|
|
|
446
446
|
# With metrics
|
|
447
447
|
report = detections.to_json_with_metrics()
|
|
448
|
+
|
|
449
|
+
# And back - masks decoded, keypoints rebuilt, every field restored
|
|
450
|
+
detections = pf.Detections.from_json(json_str)
|
|
451
|
+
detections = pf.Detections.from_dict(data)
|
|
448
452
|
```
|
|
449
453
|
|
|
450
454
|
## Supported Frameworks
|
|
@@ -119,7 +119,7 @@ result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
|
119
119
|
|
|
120
120
|
result.top1.class_name # 'golden retriever'
|
|
121
121
|
result.top_k(5) # -> Classifications, best first
|
|
122
|
-
result.to_json()
|
|
122
|
+
result.to_json() # pf.Classifications.from_json() reads it back
|
|
123
123
|
|
|
124
124
|
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
125
125
|
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
@@ -408,6 +408,10 @@ df = pd.DataFrame(data)
|
|
|
408
408
|
|
|
409
409
|
# With metrics
|
|
410
410
|
report = detections.to_json_with_metrics()
|
|
411
|
+
|
|
412
|
+
# And back - masks decoded, keypoints rebuilt, every field restored
|
|
413
|
+
detections = pf.Detections.from_json(json_str)
|
|
414
|
+
detections = pf.Detections.from_dict(data)
|
|
411
415
|
```
|
|
412
416
|
|
|
413
417
|
## Supported Frameworks
|
|
@@ -11,6 +11,7 @@ localisation that never happened.
|
|
|
11
11
|
import copy as copy_module
|
|
12
12
|
import json
|
|
13
13
|
|
|
14
|
+
from pixelflow.serialization import constructor_fields, own
|
|
14
15
|
from pixelflow.validators import round_to_decimal
|
|
15
16
|
from typing import Any, Dict, Iterator, List, Optional, Union
|
|
16
17
|
|
|
@@ -94,6 +95,10 @@ class Classification:
|
|
|
94
95
|
>>> import pixelflow as pf
|
|
95
96
|
>>> pf.Classification(207, "golden retriever", 0.94).to_dict()
|
|
96
97
|
{'class_id': 207, 'class_name': 'golden retriever', 'confidence': 0.94, 'metadata': None}
|
|
98
|
+
|
|
99
|
+
Notes:
|
|
100
|
+
- `metadata` is the classification's own dictionary, not a copy. Treat
|
|
101
|
+
the result as read-only, or rebuild with `from_dict()`, which copies it
|
|
97
102
|
"""
|
|
98
103
|
return {
|
|
99
104
|
"class_id": self.class_id,
|
|
@@ -102,6 +107,29 @@ class Classification:
|
|
|
102
107
|
"metadata": self.metadata,
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Classification":
|
|
112
|
+
"""
|
|
113
|
+
Rebuild a Classification from the dictionary `to_dict()` produces.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
data (Dict[str, Any]): A classification dictionary. Unknown keys are
|
|
117
|
+
ignored and missing keys take the constructor's defaults, so
|
|
118
|
+
dictionaries written by other pixelflow versions still read.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Classification: A new classification with its own metadata.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> import pixelflow as pf
|
|
125
|
+
>>> row = pf.Classification(class_id=207, class_name="golden retriever", confidence=0.94)
|
|
126
|
+
>>> pf.Classification.from_dict(row.to_dict()).to_dict() == row.to_dict()
|
|
127
|
+
True
|
|
128
|
+
>>> pf.Classification.from_dict({"class_name": "cat"}).confidence is None
|
|
129
|
+
True
|
|
130
|
+
"""
|
|
131
|
+
return cls(**own(constructor_fields(cls, data)))
|
|
132
|
+
|
|
105
133
|
def __repr__(self) -> str:
|
|
106
134
|
return (f"Classification(class_id={self.class_id!r}, "
|
|
107
135
|
f"class_name={self.class_name!r}, confidence={self.confidence!r})")
|
|
@@ -310,5 +338,53 @@ class Classifications:
|
|
|
310
338
|
"""
|
|
311
339
|
return json.dumps(self.to_dict(), indent=4)
|
|
312
340
|
|
|
341
|
+
@classmethod
|
|
342
|
+
def from_dict(cls, items: List[Dict[str, Any]]) -> "Classifications":
|
|
343
|
+
"""
|
|
344
|
+
Rebuild a Classifications collection from the list `to_dict()` produces.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
items (List[Dict[str, Any]]): Classification dictionaries, each read
|
|
348
|
+
with `Classification.from_dict`.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Classifications: A new collection holding the rows in the order given.
|
|
352
|
+
|
|
353
|
+
Example:
|
|
354
|
+
>>> import pixelflow as pf
|
|
355
|
+
>>> result = pf.from_scores([0.1, 0.9], labels=["cat", "dog"])
|
|
356
|
+
>>> restored = pf.Classifications.from_dict(result.to_dict())
|
|
357
|
+
>>> restored.top1.class_name
|
|
358
|
+
'dog'
|
|
359
|
+
|
|
360
|
+
Notes:
|
|
361
|
+
- `to_dict()` writes the rows only. The collection's own `inference_id`
|
|
362
|
+
and `metadata` are not part of it, so they come back as None; set
|
|
363
|
+
them on the result if you stored them yourself.
|
|
364
|
+
"""
|
|
365
|
+
return cls([Classification.from_dict(item) for item in items])
|
|
366
|
+
|
|
367
|
+
@classmethod
|
|
368
|
+
def from_json(cls, text: Union[str, bytes]) -> "Classifications":
|
|
369
|
+
"""
|
|
370
|
+
Rebuild a Classifications collection from the JSON string `to_json()` produces.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
text (Union[str, bytes]): JSON array of classification dictionaries.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Classifications: A new collection holding the rows in the order given.
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
json.JSONDecodeError: If `text` is not valid JSON.
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
>>> import pixelflow as pf
|
|
383
|
+
>>> result = pf.from_scores([0.1, 0.9], labels=["cat", "dog"])
|
|
384
|
+
>>> pf.Classifications.from_json(result.to_json()).top1.class_name
|
|
385
|
+
'dog'
|
|
386
|
+
"""
|
|
387
|
+
return cls.from_dict(json.loads(text))
|
|
388
|
+
|
|
313
389
|
def __repr__(self) -> str:
|
|
314
390
|
return f"Classifications({len(self.classifications)} rows)"
|
|
@@ -19,6 +19,7 @@ from pixelflow.validators import (validate_bbox,
|
|
|
19
19
|
round_coord,
|
|
20
20
|
round_to_decimal)
|
|
21
21
|
from pixelflow.masks import mask_to_polygon, simplify_polygon
|
|
22
|
+
from pixelflow.serialization import constructor_fields, own
|
|
22
23
|
from typing import (List,
|
|
23
24
|
Iterator, Optional, Union, Any, Dict)
|
|
24
25
|
|
|
@@ -155,6 +156,32 @@ class KeyPoint:
|
|
|
155
156
|
"confidence": float(self.confidence) if self.confidence is not None else None,
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_dict(cls, data: Dict[str, Any]) -> "KeyPoint":
|
|
161
|
+
"""
|
|
162
|
+
Rebuild a KeyPoint from the dictionary `to_dict()` produces.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
data (Dict[str, Any]): A keypoint dictionary. `x`, `y` and `id` are
|
|
166
|
+
required; `name` and `confidence` default to None.
|
|
167
|
+
Unknown keys are ignored.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
KeyPoint: A new keypoint with the dictionary's values.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
TypeError: If `x`, `y` or `id` is missing.
|
|
174
|
+
ValueError: If a coordinate is not a finite number.
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> import pixelflow as pf
|
|
178
|
+
>>> keypoint = pf.KeyPoint(100, 200, 0, "nose", 0.9)
|
|
179
|
+
>>> restored = pf.KeyPoint.from_dict(keypoint.to_dict())
|
|
180
|
+
>>> restored.to_dict() == keypoint.to_dict()
|
|
181
|
+
True
|
|
182
|
+
"""
|
|
183
|
+
return cls(**own(constructor_fields(cls, data)))
|
|
184
|
+
|
|
158
185
|
|
|
159
186
|
class Detection:
|
|
160
187
|
"""
|
|
@@ -363,6 +390,9 @@ class Detection:
|
|
|
363
390
|
- All NumPy numeric types (int32, float32, etc.) converted to native Python types
|
|
364
391
|
- All optional fields are included even if None for consistent API responses
|
|
365
392
|
- Output is fully JSON-serializable (can use json.dumps() directly)
|
|
393
|
+
- Lists and dictionaries such as `metadata`, `zones` and `segments` are
|
|
394
|
+
the detection's own objects, not copies. Treat the result as read-only,
|
|
395
|
+
or rebuild with `from_dict()`, which copies them
|
|
366
396
|
"""
|
|
367
397
|
# Helper function to convert numpy types to native Python types
|
|
368
398
|
def to_python_type(value):
|
|
@@ -495,6 +525,78 @@ class Detection:
|
|
|
495
525
|
else:
|
|
496
526
|
raise ValueError(f"Unknown mask format: {mask_dict['format']}")
|
|
497
527
|
|
|
528
|
+
@classmethod
|
|
529
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Detection":
|
|
530
|
+
"""
|
|
531
|
+
Rebuild a Detection from the dictionary `to_dict()` produces.
|
|
532
|
+
|
|
533
|
+
The inverse of `to_dict()`: masks are decoded back into arrays, keypoints
|
|
534
|
+
become `KeyPoint` objects again, and every other field is handed to the
|
|
535
|
+
constructor. Reads stored or transmitted results without the caller having
|
|
536
|
+
to know which fields a detection has or how its masks were encoded.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
data (Dict[str, Any]): A detection dictionary, as written by `to_dict()`
|
|
540
|
+
or parsed from `to_json()`. Unknown keys are
|
|
541
|
+
ignored and missing keys take the constructor's
|
|
542
|
+
defaults, so dictionaries written by other
|
|
543
|
+
pixelflow versions still read.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
Detection: A new detection, independent of `data`.
|
|
547
|
+
|
|
548
|
+
Raises:
|
|
549
|
+
ValueError: If a mask uses a format `decode_mask` does not know, or a
|
|
550
|
+
keypoint coordinate is not a finite number.
|
|
551
|
+
TypeError: If a keypoint is missing `x`, `y` or `id`.
|
|
552
|
+
|
|
553
|
+
Example:
|
|
554
|
+
>>> import numpy as np
|
|
555
|
+
>>> import pixelflow as pf
|
|
556
|
+
>>>
|
|
557
|
+
>>> mask = np.zeros((480, 640), dtype=bool)
|
|
558
|
+
>>> mask[100:200, 50:300] = True
|
|
559
|
+
>>> detection = pf.Detection(bbox=[50, 100, 300, 200], masks=[mask],
|
|
560
|
+
... class_name="car", confidence=0.9)
|
|
561
|
+
>>>
|
|
562
|
+
>>> # Round trip through a plain dictionary
|
|
563
|
+
>>> restored = pf.Detection.from_dict(detection.to_dict())
|
|
564
|
+
>>> np.array_equal(restored.masks[0], mask)
|
|
565
|
+
True
|
|
566
|
+
>>>
|
|
567
|
+
>>> # Partial dictionaries read too; the rest take defaults
|
|
568
|
+
>>> pf.Detection.from_dict({"bbox": [10, 20, 30, 40]}).class_name is None
|
|
569
|
+
True
|
|
570
|
+
>>>
|
|
571
|
+
>>> # Keys this version does not know are ignored
|
|
572
|
+
>>> pf.Detection.from_dict({"bbox": [10, 20, 30, 40], "added_later": 1}).bbox
|
|
573
|
+
[10.0, 20.0, 30.0, 40.0]
|
|
574
|
+
|
|
575
|
+
Notes:
|
|
576
|
+
- Values pass through the same validation as any new detection: an
|
|
577
|
+
invalid `bbox` becomes None, coordinates round to two decimals and
|
|
578
|
+
confidence to three. Values that came out of `to_dict()` were already
|
|
579
|
+
rounded, so they come back unchanged
|
|
580
|
+
- Raster masks come back with the pixels, dtype and shape they were
|
|
581
|
+
written with
|
|
582
|
+
- A mask entry that is not an encoded dictionary is taken as already
|
|
583
|
+
decoded: a polygon list is copied, an array is kept as it is
|
|
584
|
+
- Lists and dictionaries are copied, so changing the restored
|
|
585
|
+
detection never reaches `data`, or the detection `data` came from
|
|
586
|
+
"""
|
|
587
|
+
fields = constructor_fields(cls, data)
|
|
588
|
+
# Each keypoint is rebuilt from its own dictionary below, and copies it there.
|
|
589
|
+
keypoints = fields.pop("keypoints", None)
|
|
590
|
+
fields = own(fields)
|
|
591
|
+
|
|
592
|
+
if fields.get("masks") is not None:
|
|
593
|
+
fields["masks"] = [cls.decode_mask(mask) if isinstance(mask, dict) else mask
|
|
594
|
+
for mask in fields["masks"]]
|
|
595
|
+
if keypoints is not None:
|
|
596
|
+
fields["keypoints"] = [KeyPoint.from_dict(kp) for kp in keypoints]
|
|
597
|
+
|
|
598
|
+
return cls(**fields)
|
|
599
|
+
|
|
498
600
|
def simplify_masks(self, tolerance: float = 2.0, preserve_topology: bool = True) -> None:
|
|
499
601
|
"""
|
|
500
602
|
Simplify polygon masks to reduce complexity while preserving essential shape characteristics.
|
|
@@ -1043,6 +1145,94 @@ class Detections:
|
|
|
1043
1145
|
"""
|
|
1044
1146
|
return [detection.to_dict() for detection in self.detections]
|
|
1045
1147
|
|
|
1148
|
+
@classmethod
|
|
1149
|
+
def from_dict(cls, items: List[Dict[str, Any]]) -> "Detections":
|
|
1150
|
+
"""
|
|
1151
|
+
Rebuild a Detections collection from the list `to_dict()` produces.
|
|
1152
|
+
|
|
1153
|
+
The inverse of `to_dict()`. Reads results that were stored or sent as plain
|
|
1154
|
+
data back into a collection that filters, annotates and converts like one
|
|
1155
|
+
that came straight from a model.
|
|
1156
|
+
|
|
1157
|
+
Args:
|
|
1158
|
+
items (List[Dict[str, Any]]): Detection dictionaries, as written by
|
|
1159
|
+
`to_dict()`. Each is read with
|
|
1160
|
+
`Detection.from_dict`.
|
|
1161
|
+
|
|
1162
|
+
Returns:
|
|
1163
|
+
Detections: A new collection holding the detections in the order given.
|
|
1164
|
+
|
|
1165
|
+
Raises:
|
|
1166
|
+
ValueError: If a mask uses an unknown format, or a keypoint coordinate
|
|
1167
|
+
is not a finite number.
|
|
1168
|
+
TypeError: If a keypoint is missing `x`, `y` or `id`.
|
|
1169
|
+
|
|
1170
|
+
Example:
|
|
1171
|
+
>>> import pixelflow as pf
|
|
1172
|
+
>>>
|
|
1173
|
+
>>> detections = pf.from_arrays(boxes, scores, masks=masks)
|
|
1174
|
+
>>>
|
|
1175
|
+
>>> # Store as plain data, read back later
|
|
1176
|
+
>>> stored = detections.to_dict()
|
|
1177
|
+
>>> restored = pf.Detections.from_dict(stored)
|
|
1178
|
+
>>> len(restored) == len(detections)
|
|
1179
|
+
True
|
|
1180
|
+
>>>
|
|
1181
|
+
>>> # A restored result behaves like any other
|
|
1182
|
+
>>> confident = restored.filter_by_confidence(0.5)
|
|
1183
|
+
>>> restored.masks_to_segments()
|
|
1184
|
+
>>>
|
|
1185
|
+
>>> # Hand-written rows work too
|
|
1186
|
+
>>> pf.Detections.from_dict([{"bbox": [10, 20, 30, 40], "class_name": "car"}])
|
|
1187
|
+
|
|
1188
|
+
Notes:
|
|
1189
|
+
- Everything `to_dict()` writes comes back: boxes, masks, segments,
|
|
1190
|
+
keypoints, classes, text, tracking, zones and timing
|
|
1191
|
+
- Detections are added through `add_detection`, so a dictionary without
|
|
1192
|
+
a valid `bbox`, or with a NaN confidence, is dropped as it would be
|
|
1193
|
+
from any converter. Nothing `to_dict()` wrote is affected: a
|
|
1194
|
+
collection never holds such a detection
|
|
1195
|
+
- Raster masks are decoded eagerly, one array per mask
|
|
1196
|
+
"""
|
|
1197
|
+
detections = cls()
|
|
1198
|
+
for item in items:
|
|
1199
|
+
detections.add_detection(Detection.from_dict(item))
|
|
1200
|
+
return detections
|
|
1201
|
+
|
|
1202
|
+
@classmethod
|
|
1203
|
+
def from_json(cls, text: Union[str, bytes]) -> "Detections":
|
|
1204
|
+
"""
|
|
1205
|
+
Rebuild a Detections collection from the JSON string `to_json()` produces.
|
|
1206
|
+
|
|
1207
|
+
Args:
|
|
1208
|
+
text (Union[str, bytes]): JSON array of detection dictionaries.
|
|
1209
|
+
|
|
1210
|
+
Returns:
|
|
1211
|
+
Detections: A new collection holding the detections in the order given.
|
|
1212
|
+
|
|
1213
|
+
Raises:
|
|
1214
|
+
json.JSONDecodeError: If `text` is not valid JSON.
|
|
1215
|
+
ValueError: If a mask uses an unknown format, or a keypoint coordinate
|
|
1216
|
+
is not a finite number.
|
|
1217
|
+
|
|
1218
|
+
Example:
|
|
1219
|
+
>>> import pixelflow as pf
|
|
1220
|
+
>>>
|
|
1221
|
+
>>> # Write results to disk, read them back in another process
|
|
1222
|
+
>>> with open("results.json", "w") as f:
|
|
1223
|
+
... f.write(detections.to_json())
|
|
1224
|
+
>>> with open("results.json") as f:
|
|
1225
|
+
... restored = pf.Detections.from_json(f.read())
|
|
1226
|
+
>>>
|
|
1227
|
+
>>> # Or straight from an HTTP response body
|
|
1228
|
+
>>> restored = pf.Detections.from_json(response.content)
|
|
1229
|
+
|
|
1230
|
+
Notes:
|
|
1231
|
+
- Parses the JSON and hands the result to `from_dict`; see it for what
|
|
1232
|
+
is restored and what is dropped
|
|
1233
|
+
"""
|
|
1234
|
+
return cls.from_dict(json.loads(text))
|
|
1235
|
+
|
|
1046
1236
|
def to_json_with_metrics(self) -> str:
|
|
1047
1237
|
"""
|
|
1048
1238
|
Convert detections to JSON with additional analytics metrics for comprehensive reporting.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Reading a result type back from the plain dictionary its ``to_dict()`` wrote.
|
|
2
|
+
|
|
3
|
+
Every result type pixelflow serializes - KeyPoint, Detection, Classification - is
|
|
4
|
+
restored by the same two rules: take the keys its constructor accepts and leave the
|
|
5
|
+
rest, and share no list or dictionary with the data it was read from. Both rules live
|
|
6
|
+
here so the three ``from_dict`` methods cannot come to disagree about them.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
import inspect
|
|
11
|
+
from typing import Any, Dict, Tuple
|
|
12
|
+
|
|
13
|
+
__all__ = ["constructor_fields", "own"]
|
|
14
|
+
|
|
15
|
+
_CONTAINERS = (list, dict)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@functools.lru_cache(maxsize=None)
|
|
19
|
+
def _constructor_arguments(cls: type) -> Tuple[str, ...]:
|
|
20
|
+
"""The argument names `cls` is built from, read once per class."""
|
|
21
|
+
return tuple(inspect.signature(cls).parameters)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def constructor_fields(cls: type, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Pick out of `data` the entries that `cls`'s constructor accepts.
|
|
27
|
+
|
|
28
|
+
The constructor is the single statement of what a result type is made of, so the
|
|
29
|
+
field list is read from it rather than kept as a second copy: a field added to the
|
|
30
|
+
class is restored without anyone remembering to list it. Keys the constructor does
|
|
31
|
+
not know are left behind, which is what lets a dictionary written by a newer
|
|
32
|
+
pixelflow still read, and keys that are absent are simply not passed, so they take
|
|
33
|
+
the constructor's defaults.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
cls (type): The class about to be constructed.
|
|
37
|
+
data (Dict[str, Any]): A dictionary as written by that class's `to_dict()`.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict[str, Any]: A new dictionary of keyword arguments for `cls`. Its values
|
|
41
|
+
are still the ones in `data`; pass it through `own` to separate them.
|
|
42
|
+
"""
|
|
43
|
+
return {key: data[key] for key in _constructor_arguments(cls) if key in data}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def own(value: Any) -> Any:
|
|
47
|
+
"""
|
|
48
|
+
Copy a list or dictionary and every list and dictionary inside it.
|
|
49
|
+
|
|
50
|
+
`to_dict()` hands containers such as `metadata` out by reference - it runs once
|
|
51
|
+
per frame and usually feeds `json.dumps`, where a copy would be wasted - so the
|
|
52
|
+
copy is made on the way back in, and a restored object never shares state with
|
|
53
|
+
the one it was written from. Copying whatever is a container, not a named few
|
|
54
|
+
fields, keeps that true for fields added later.
|
|
55
|
+
|
|
56
|
+
Serialized data is lists, dictionaries and scalars, and for that shape this is
|
|
57
|
+
several times faster than `copy.deepcopy`, which tracks every object it visits
|
|
58
|
+
in case the structure refers back to itself. Anything that is not a container -
|
|
59
|
+
a string, a number, an array a caller put in by hand - is shared, not copied.
|
|
60
|
+
Scalars are tested for in the loop, not by a call that returns them unchanged:
|
|
61
|
+
they are nearly all of what a result holds.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: A list or dictionary of plain data.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A new container of the same shape, sharing no list or dictionary with `value`.
|
|
68
|
+
"""
|
|
69
|
+
if isinstance(value, list):
|
|
70
|
+
return [own(item) if isinstance(item, _CONTAINERS) else item for item in value]
|
|
71
|
+
return {key: own(item) if isinstance(item, _CONTAINERS) else item
|
|
72
|
+
for key, item in value.items()}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixelflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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>
|
|
6
6
|
License: MIT
|
|
@@ -156,7 +156,7 @@ result = pf.from_ultralytics_classification(model.predict("dog.jpg"))
|
|
|
156
156
|
|
|
157
157
|
result.top1.class_name # 'golden retriever'
|
|
158
158
|
result.top_k(5) # -> Classifications, best first
|
|
159
|
-
result.to_json()
|
|
159
|
+
result.to_json() # pf.Classifications.from_json() reads it back
|
|
160
160
|
|
|
161
161
|
# Plain scores - CLIP zero-shot, a vendored ResNet, anything with a score vector
|
|
162
162
|
prompts = ["a photo of a cat", "a photo of a dog"]
|
|
@@ -445,6 +445,10 @@ df = pd.DataFrame(data)
|
|
|
445
445
|
|
|
446
446
|
# With metrics
|
|
447
447
|
report = detections.to_json_with_metrics()
|
|
448
|
+
|
|
449
|
+
# And back - masks decoded, keypoints rebuilt, every field restored
|
|
450
|
+
detections = pf.Detections.from_json(json_str)
|
|
451
|
+
detections = pf.Detections.from_dict(data)
|
|
448
452
|
```
|
|
449
453
|
|
|
450
454
|
## Supported Frameworks
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|