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.
Files changed (64) hide show
  1. {pixelflow-0.6.0 → pixelflow-0.7.0}/PKG-INFO +6 -2
  2. {pixelflow-0.6.0 → pixelflow-0.7.0}/README.md +5 -1
  3. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/__init__.py +1 -1
  4. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/classifications.py +76 -0
  5. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/detections.py +190 -0
  6. pixelflow-0.7.0/pixelflow/serialization.py +72 -0
  7. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/PKG-INFO +6 -2
  8. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/SOURCES.txt +1 -0
  9. {pixelflow-0.6.0 → pixelflow-0.7.0}/LICENSE +0 -0
  10. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/__init__.py +0 -0
  11. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/anchors.py +0 -0
  12. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/blur.py +0 -0
  13. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/box.py +0 -0
  14. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/classification.py +0 -0
  15. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/crossing.py +0 -0
  16. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/dot.py +0 -0
  17. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/filled_box.py +0 -0
  18. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/fps_counter.py +0 -0
  19. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/grid_overlay.py +0 -0
  20. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/heatmap.py +0 -0
  21. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/keypoint.py +0 -0
  22. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/keypoint_skeleton.py +0 -0
  23. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/label.py +0 -0
  24. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/mask.py +0 -0
  25. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/motion_dots.py +0 -0
  26. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/motion_trails.py +0 -0
  27. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/oval.py +0 -0
  28. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/pixelate.py +0 -0
  29. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/polygon.py +0 -0
  30. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/scale_bar.py +0 -0
  31. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/utils.py +0 -0
  32. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/annotators/zones.py +0 -0
  33. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/arrays.py +0 -0
  34. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/buffer.py +0 -0
  35. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/__init__.py +0 -0
  36. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/classifications/converters.py +0 -0
  37. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/colors.py +0 -0
  38. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/crossings.py +0 -0
  39. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/__init__.py +0 -0
  40. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/converters.py +0 -0
  41. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/detections/filters.py +0 -0
  42. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/labels.py +0 -0
  43. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/masks.py +0 -0
  44. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/media.py +0 -0
  45. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/slicer.py +0 -0
  46. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/smoother.py +0 -0
  47. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/strategies.py +0 -0
  48. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/timer.py +0 -0
  49. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/__init__.py +0 -0
  50. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/bytetrack.py +0 -0
  51. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/kalman_filter.py +0 -0
  52. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/matching.py +0 -0
  53. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/tracker/track.py +0 -0
  54. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/__init__.py +0 -0
  55. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/detections.py +0 -0
  56. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/image.py +0 -0
  57. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/transforms/inverse.py +0 -0
  58. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/validators.py +0 -0
  59. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow/zones.py +0 -0
  60. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/dependency_links.txt +0 -0
  61. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/requires.txt +0 -0
  62. {pixelflow-0.6.0 → pixelflow-0.7.0}/pixelflow.egg-info/top_level.txt +0 -0
  63. {pixelflow-0.6.0 → pixelflow-0.7.0}/pyproject.toml +0 -0
  64. {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.6.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
@@ -1,4 +1,4 @@
1
- __version__ = "0.6.0"
1
+ __version__ = "0.7.0"
2
2
  __author__ = "Emrah NAZIF"
3
3
 
4
4
  # Import core modules
@@ -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.6.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
@@ -9,6 +9,7 @@ pixelflow/crossings.py
9
9
  pixelflow/labels.py
10
10
  pixelflow/masks.py
11
11
  pixelflow/media.py
12
+ pixelflow/serialization.py
12
13
  pixelflow/slicer.py
13
14
  pixelflow/smoother.py
14
15
  pixelflow/strategies.py
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