edgefirst-validator 4.2.1__py3-none-any.whl

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 (73) hide show
  1. deepview/modelpack/utils/argmax.py +16 -0
  2. edgefirst/validator/__init__.py +1 -0
  3. edgefirst/validator/__main__.py +375 -0
  4. edgefirst/validator/datasets/__init__.py +118 -0
  5. edgefirst/validator/datasets/cache.py +296 -0
  6. edgefirst/validator/datasets/core.py +250 -0
  7. edgefirst/validator/datasets/darknet.py +446 -0
  8. edgefirst/validator/datasets/database.py +1067 -0
  9. edgefirst/validator/datasets/instance/__init__.py +4 -0
  10. edgefirst/validator/datasets/instance/core.py +222 -0
  11. edgefirst/validator/datasets/instance/detection.py +145 -0
  12. edgefirst/validator/datasets/instance/multitask.py +80 -0
  13. edgefirst/validator/datasets/instance/segmentation.py +120 -0
  14. edgefirst/validator/datasets/utils/fetch.py +682 -0
  15. edgefirst/validator/datasets/utils/readers.py +425 -0
  16. edgefirst/validator/datasets/utils/transformations.py +1695 -0
  17. edgefirst/validator/evaluators/__init__.py +17 -0
  18. edgefirst/validator/evaluators/callbacks/__init__.py +3 -0
  19. edgefirst/validator/evaluators/callbacks/core.py +192 -0
  20. edgefirst/validator/evaluators/callbacks/plots.py +900 -0
  21. edgefirst/validator/evaluators/callbacks/studio.py +234 -0
  22. edgefirst/validator/evaluators/core.py +257 -0
  23. edgefirst/validator/evaluators/detection.py +749 -0
  24. edgefirst/validator/evaluators/multitask.py +270 -0
  25. edgefirst/validator/evaluators/parameters/__init__.py +53 -0
  26. edgefirst/validator/evaluators/parameters/core.py +554 -0
  27. edgefirst/validator/evaluators/parameters/dataset.py +239 -0
  28. edgefirst/validator/evaluators/parameters/model.py +338 -0
  29. edgefirst/validator/evaluators/parameters/validation.py +528 -0
  30. edgefirst/validator/evaluators/segmentation.py +729 -0
  31. edgefirst/validator/evaluators/utils/__init__.py +3 -0
  32. edgefirst/validator/evaluators/utils/classify.py +292 -0
  33. edgefirst/validator/evaluators/utils/match.py +262 -0
  34. edgefirst/validator/evaluators/utils/timer.py +132 -0
  35. edgefirst/validator/metrics/__init__.py +9 -0
  36. edgefirst/validator/metrics/data/__init__.py +7 -0
  37. edgefirst/validator/metrics/data/label.py +668 -0
  38. edgefirst/validator/metrics/data/metrics.py +759 -0
  39. edgefirst/validator/metrics/data/plots.py +476 -0
  40. edgefirst/validator/metrics/data/stats.py +507 -0
  41. edgefirst/validator/metrics/detection.py +595 -0
  42. edgefirst/validator/metrics/segmentation.py +173 -0
  43. edgefirst/validator/metrics/utils/math.py +717 -0
  44. edgefirst/validator/publishers/__init__.py +3 -0
  45. edgefirst/validator/publishers/console.py +147 -0
  46. edgefirst/validator/publishers/studio.py +128 -0
  47. edgefirst/validator/publishers/tensorboard.py +119 -0
  48. edgefirst/validator/publishers/utils/logger.py +111 -0
  49. edgefirst/validator/publishers/utils/table.py +403 -0
  50. edgefirst/validator/runners/__init__.py +8 -0
  51. edgefirst/validator/runners/core.py +727 -0
  52. edgefirst/validator/runners/deepviewrt.py +177 -0
  53. edgefirst/validator/runners/hailo.py +263 -0
  54. edgefirst/validator/runners/keras.py +150 -0
  55. edgefirst/validator/runners/kinara.py +265 -0
  56. edgefirst/validator/runners/offline.py +228 -0
  57. edgefirst/validator/runners/onnx.py +241 -0
  58. edgefirst/validator/runners/processing/decode.py +320 -0
  59. edgefirst/validator/runners/processing/dvapi.py +4192 -0
  60. edgefirst/validator/runners/processing/nms.py +637 -0
  61. edgefirst/validator/runners/processing/outputs.py +507 -0
  62. edgefirst/validator/runners/tensorrt.py +321 -0
  63. edgefirst/validator/runners/tflite.py +221 -0
  64. edgefirst/validator/validate.py +843 -0
  65. edgefirst/validator/visualize/__init__.py +3 -0
  66. edgefirst/validator/visualize/detection.py +623 -0
  67. edgefirst/validator/visualize/segmentation.py +281 -0
  68. edgefirst/validator/visualize/utils/plots.py +635 -0
  69. edgefirst_validator-4.2.1.dist-info/METADATA +111 -0
  70. edgefirst_validator-4.2.1.dist-info/RECORD +73 -0
  71. edgefirst_validator-4.2.1.dist-info/WHEEL +5 -0
  72. edgefirst_validator-4.2.1.dist-info/entry_points.txt +2 -0
  73. edgefirst_validator-4.2.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,507 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Union, List
4
+
5
+ import numpy as np
6
+
7
+ if TYPE_CHECKING:
8
+ from edgefirst.validator.evaluators import ModelParameters
9
+
10
+
11
+ class Outputs:
12
+ """
13
+ Store the metadata output details needed for decoding the model outputs.
14
+
15
+ If the metadata exists, then parse the metadata to store the output details.
16
+ Otherwise, rely on a separate logic to define the output types based on shape.
17
+
18
+ If the metadata exists, reinitialize the index based on the existing
19
+ output tensors, as we cannot rely on the current output index stored in
20
+ the metadata.
21
+
22
+ Prioritize decoding the variant types such as "detection" and "segmentation".
23
+ Otherwise, decode the known types such as "boxes", "scores", and "masks".
24
+
25
+ Parameters
26
+ ----------
27
+ metadata: dict
28
+ The contents of the model metadata for decoding the outputs.
29
+ parameters: ModelParameters
30
+ These are the model parameters set from the command line.
31
+ outputs: Union[List[dict], List[np.ndarray]]
32
+ This is either a List[dict] from a TFLite output details
33
+ or a List[np.ndarray] containing the shapes from the model outputs.
34
+ """
35
+
36
+ def __init__(self,
37
+ metadata: dict,
38
+ parameters: ModelParameters,
39
+ outputs: Union[List[dict], List[np.ndarray]]):
40
+
41
+ self.metadata = metadata
42
+ self.parameters = parameters
43
+ self.outputs = outputs
44
+
45
+ self.boxes = {
46
+ "anchors": None,
47
+ "decode": False,
48
+ "decoder": None,
49
+ "index": None,
50
+ "shape": None,
51
+ "type": "boxes",
52
+ "dtype": None,
53
+ "quantization": None
54
+ }
55
+
56
+ self.scores = {
57
+ "anchors": None,
58
+ "decode": False,
59
+ "decoder": None,
60
+ "index": None,
61
+ "shape": None,
62
+ "type": "scores",
63
+ "dtype": None,
64
+ "quantization": None
65
+ }
66
+
67
+ self.classes = {
68
+ "anchors": None,
69
+ "decode": False,
70
+ "decoder": None,
71
+ "index": None,
72
+ "shape": None,
73
+ "type": "classes",
74
+ "dtype": None,
75
+ "quantization": None
76
+ }
77
+
78
+ self.masks = {
79
+ "anchors": None,
80
+ "decode": False,
81
+ "decoder": None,
82
+ "index": None,
83
+ "shape": None,
84
+ "type": "masks",
85
+ "dtype": None,
86
+ "quantization": None
87
+ }
88
+
89
+ # Variant types from the metadata to decode modelpack.
90
+ # Currently contains types such as "detection" or "segmentation"
91
+ # which can appear common for multiple outputs.
92
+ self.mpk_types = []
93
+ self.num_boxes = 0 # The number of boxes in the model output shape.
94
+
95
+ # Parse the metadata if it exists to determine the output types.
96
+ # Otherwise check the output shapes to automatically determine the
97
+ # types.
98
+ if self.metadata is not None and "outputs" in self.metadata.keys():
99
+ self.parse_metadata()
100
+ else:
101
+ self.create_metadata()
102
+
103
+ def parse_metadata(self):
104
+ """
105
+ Parses the contents of the metadata and redefines the output
106
+ index based on the existing output as we cannot rely on the
107
+ output index stored in the metadata.
108
+ """
109
+ # Get the model output types.
110
+ box_index = self.get_boxes_index()
111
+ mask_index = self.get_masks_index()
112
+ scores_index = self.get_scores_index()
113
+ decoded_masks_index = self.get_decoded_masks_index()
114
+
115
+ for output_details in self.metadata["outputs"]:
116
+ config_shape = output_details["shape"]
117
+ output_index = output_details["output_index"]
118
+ decoder = output_details["decoder"]
119
+ output_type = output_details["type"]
120
+ dtype = output_details["dtype"]
121
+ anchors = output_details["anchors"]
122
+ anchors = np.array(anchors) if anchors is not None else None
123
+ quantization = None
124
+
125
+ # Redefine the output index based on the actual model outputs.
126
+ for i, output in enumerate(self.outputs):
127
+ if isinstance(output, dict):
128
+ shape = output["shape"].tolist()
129
+ quantization = output["quantization"]
130
+ else:
131
+ shape = list(output.shape)
132
+
133
+ # Check for edge case in Ultralytics ONNX [1, 32, 160, 160] vs.
134
+ # TFLite [1, 160, 160, 32].
135
+ if decoder == "yolov8" and len(
136
+ config_shape) == len(shape) == 4:
137
+ output_index = i
138
+ break
139
+ elif config_shape == shape:
140
+ output_index = i
141
+ break
142
+
143
+ context = {
144
+ "index": output_index,
145
+ "decode": output_details["decode"],
146
+ "decoder": decoder,
147
+ "anchors": anchors,
148
+ "shape": config_shape,
149
+ "type": output_type,
150
+ "dtype": dtype,
151
+ "quantization": quantization
152
+ }
153
+
154
+ if output_type == "boxes":
155
+ self.boxes = context
156
+ self.boxes["index"] = box_index
157
+ self.parameters.common.with_boxes = True
158
+ elif output_type == "scores":
159
+ self.scores = context
160
+ self.scores["index"] = scores_index
161
+ self.parameters.common.with_boxes = True
162
+ elif output_type == "masks":
163
+ self.masks = context
164
+ self.masks["index"] = (decoded_masks_index if
165
+ decoded_masks_index is not None
166
+ else mask_index)
167
+ # Segmentation models from Ultralytics are multitask.
168
+ if decoder == "yolov8":
169
+ self.masks["index"] = mask_index
170
+ self.parameters.common.with_boxes = True
171
+ self.parameters.common.semantic = False
172
+ self.parameters.common.with_masks = True
173
+ else:
174
+ if decoder == "yolov8":
175
+ self.parameters.common.with_boxes = True
176
+ self.parameters.common.semantic = False
177
+ # shape [1, 160, 160, 32] are the mask protos outputs.
178
+ if (len(config_shape) > 3 or output_type == "protos"):
179
+ self.masks = context
180
+ self.masks["index"] = mask_index
181
+ self.parameters.common.with_masks = True
182
+ # shape [1, 116, 8400], [1, 85, 115200] are the score
183
+ # and/or mask outputs.
184
+ else:
185
+ self.scores = context
186
+ self.scores["index"] = scores_index
187
+ else:
188
+ context["type"] = output_type
189
+ if output_type == "detection":
190
+ self.parameters.common.with_boxes = True
191
+ elif output_type == "segmentation":
192
+ self.parameters.common.with_masks = True
193
+ self.mpk_types.append(context)
194
+
195
+ def create_metadata(self):
196
+ """
197
+ Defines the output types based on the output shape
198
+ as either a boxes, scores, classes, or masks tensors.
199
+ This is called when the model metadata does not exist. The model
200
+ metadata is created here based on the logic for specifying the
201
+ model outputs.
202
+
203
+ This method currently only supports finding decoded output types
204
+ for ModelPack.
205
+ """
206
+ # Get the model output types.
207
+ self.boxes["index"] = self.get_boxes_index()
208
+ self.masks["index"] = self.get_masks_index()
209
+ self.scores["index"] = self.get_scores_index()
210
+
211
+ decoded_masks_index = self.get_decoded_masks_index()
212
+ if decoded_masks_index is not None:
213
+ self.masks["index"] = decoded_masks_index
214
+ self.masks["decode"] = False
215
+ self.masks["type"] = "masks"
216
+ else:
217
+ self.masks["decode"] = True
218
+ self.masks["type"] = "segmentation"
219
+ self.classes["index"] = self.get_classes_index()
220
+
221
+ # Get the model tasks.
222
+ if (self.boxes["index"] is not None or
223
+ self.scores["index"] is not None or
224
+ self.classes["index"] is not None):
225
+ self.parameters.common.with_boxes = True
226
+ else:
227
+ self.parameters.common.with_boxes = False
228
+
229
+ if self.masks["index"] is not None:
230
+ self.parameters.common.with_masks = True
231
+ else:
232
+ self.parameters.common.with_masks = False
233
+
234
+ # MobileNet SSD has embedded NMS.
235
+ if self.classes["index"] is not None:
236
+ self.parameters.nms = "embedded"
237
+
238
+ # Formulate model metadata format.
239
+ self.metadata = {"outputs": []}
240
+ # ModelPack or Kinara
241
+ if (None not in [self.boxes["index"], self.scores["index"]]):
242
+ boxes = self.outputs[self.boxes["index"]]
243
+ if isinstance(boxes, dict):
244
+ shape = boxes["shape"].tolist()
245
+ dtype = np.dtype(boxes["dtype"]).name
246
+ self.boxes["quantization"] = boxes["quantization"]
247
+ else:
248
+ shape = list(boxes.shape)
249
+ dtype = boxes.dtype.name if hasattr(
250
+ boxes, "dtype") and hasattr(
251
+ boxes.dtype, "name") else boxes.dtype if hasattr(
252
+ boxes, "dtype") else boxes.type
253
+ self.boxes["shape"] = shape
254
+ self.boxes["dtype"] = dtype
255
+
256
+ scores = self.outputs[self.scores["index"]]
257
+ if isinstance(scores, dict):
258
+ shape = scores["shape"].tolist()
259
+ dtype = np.dtype(scores["dtype"]).name
260
+ self.scores["quantization"] = scores["quantization"]
261
+ else:
262
+ shape = list(scores.shape)
263
+ dtype = scores.dtype.name if hasattr(
264
+ scores, "dtype") and hasattr(
265
+ scores.dtype, "name") else scores.dtype if hasattr(
266
+ scores, "dtype") else scores.type
267
+ self.scores["shape"] = shape
268
+ self.scores["dtype"] = dtype
269
+
270
+ # Kinara YOLOv8
271
+ if len(self.boxes["shape"]) == 3:
272
+ self.boxes["decode"] = True
273
+ self.scores["decode"] = True
274
+ self.boxes["decoder"] = "yolov8"
275
+ self.scores["decoder"] = "yolov8"
276
+ self.boxes["channels_first"] = False
277
+ self.scores["channels_first"] = False
278
+ else:
279
+ # Creating metadata assumes ModelPack already has decoded
280
+ # outputs.
281
+ self.boxes["decode"] = False
282
+ self.scores["decode"] = False
283
+ self.boxes["decoder"] = "modelpack"
284
+ self.scores["decoder"] = "modelpack"
285
+
286
+ self.metadata["outputs"].append(self.boxes)
287
+ self.metadata["outputs"].append(self.scores)
288
+
289
+ if self.masks["index"] is not None:
290
+ self.masks["decoder"] = "modelpack"
291
+
292
+ masks = self.outputs[self.masks["index"]]
293
+ if isinstance(masks, dict):
294
+ shape = masks["shape"].tolist()
295
+ dtype = np.dtype(masks["dtype"]).name
296
+ self.masks["quantization"] = masks["quantization"]
297
+ else:
298
+ shape = list(masks.shape)
299
+ dtype = masks.dtype.name if hasattr(
300
+ masks, "dtype") and hasattr(
301
+ masks.dtype, "name") else masks.dtype if hasattr(
302
+ masks, "dtype") else masks.type
303
+ self.masks["shape"] = shape
304
+ self.masks["dtype"] = dtype
305
+ self.metadata["outputs"].append(self.masks)
306
+
307
+ # YOLOv5, YOLOv8, YOLOv11
308
+ elif self.scores["index"] is not None:
309
+ self.scores["decode"] = True
310
+ self.scores["decoder"] = "yolov8"
311
+ self.scores["type"] = "detection"
312
+
313
+ scores = self.outputs[self.scores["index"]]
314
+ if isinstance(scores, dict):
315
+ shape = scores["shape"].tolist()
316
+ dtype = np.dtype(scores["dtype"]).name
317
+ self.scores["quantization"] = scores["quantization"]
318
+ else:
319
+ shape = list(scores.shape)
320
+ dtype = scores.dtype.name if hasattr(
321
+ scores, "dtype") and hasattr(
322
+ scores.dtype, "name") else scores.dtype if hasattr(
323
+ scores, "dtype") else scores.type
324
+ self.scores["shape"] = shape
325
+ self.scores["dtype"] = dtype
326
+
327
+ if self.masks["index"] is not None:
328
+ self.parameters.common.semantic = False
329
+ self.scores["type"] = "segmentation"
330
+ self.masks["decoder"] = "yolov8"
331
+
332
+ masks = self.outputs[self.masks["index"]]
333
+ if isinstance(masks, dict):
334
+ shape = masks["shape"].tolist()
335
+ dtype = np.dtype(masks["dtype"]).name
336
+ self.masks["quantization"] = masks["quantization"]
337
+ else:
338
+ shape = list(masks.shape)
339
+ dtype = masks.dtype.name if hasattr(
340
+ masks, "dtype") and hasattr(
341
+ masks.dtype, "name") else masks.dtype if hasattr(
342
+ masks, "dtype") else masks.type
343
+ # NOTE: HAL decoder requires shape [1, 160, 160, 32]
344
+ if shape[1] == 32:
345
+ shape = [shape[0], shape[2], shape[3], shape[1]]
346
+ self.masks["shape"] = shape
347
+ self.masks["dtype"] = dtype
348
+ self.masks["type"] = "protos"
349
+
350
+ self.metadata["outputs"].append(self.masks)
351
+ self.metadata["outputs"].append(self.scores)
352
+ # ModelPack segmentation
353
+ else:
354
+ if self.masks["index"] is not None:
355
+ self.masks["decoder"] = "modelpack"
356
+
357
+ masks = self.outputs[self.masks["index"]]
358
+ if isinstance(masks, dict):
359
+ shape = masks["shape"].tolist()
360
+ dtype = np.dtype(masks["dtype"]).name
361
+ self.masks["quantization"] = masks["quantization"]
362
+ else:
363
+ shape = list(masks.shape)
364
+ dtype = masks.dtype.name if hasattr(
365
+ masks, "dtype") and hasattr(
366
+ masks.dtype, "name") else masks.dtype if hasattr(
367
+ masks, "dtype") else masks.type
368
+ self.masks["shape"] = shape
369
+ self.masks["dtype"] = dtype
370
+ self.metadata["outputs"].append(self.masks)
371
+
372
+ def get_boxes_index(self) -> Union[int, None]:
373
+ """
374
+ Get the index of the bounding box outputs from the model.
375
+ Checking for Ultralytics and ModelPack variations.
376
+ Box output shapes can be in these variations:
377
+ [1, 6000, 1, 4], [1, 6000, 4], [1, 4, 8400]
378
+
379
+ Returns
380
+ -------
381
+ Union[int, None]
382
+ The index is returned if the bounding box output shape exists.
383
+ Otherwise None is returned.
384
+ """
385
+ # Checking ModelPack outputs.
386
+ for i, output in enumerate(self.outputs):
387
+ if isinstance(output, dict):
388
+ shape = output["shape"]
389
+ else:
390
+ shape = output.shape
391
+
392
+ if (len(shape) == 4 and shape[-1] == 4):
393
+ self.num_boxes = shape[1]
394
+ return i
395
+ elif len(shape) == 3:
396
+ if shape[1] == 4:
397
+ self.num_boxes = shape[-1]
398
+ return i
399
+ elif shape[-1] == 4:
400
+ self.num_boxes = shape[1]
401
+ return i
402
+ return None
403
+
404
+ def get_masks_index(self) -> Union[int, None]:
405
+ """
406
+ Get the index of the encoded mask outputs from the model.
407
+ Checking for ModelPack variations only.
408
+ Mask shapes are typically [1, h, w, nc].
409
+
410
+ Returns
411
+ -------
412
+ Union[int, None]
413
+ The index is returned if the mask output shape exists.
414
+ Otherwise None is returned.
415
+ """
416
+ for i, output in enumerate(self.outputs):
417
+ if isinstance(output, dict):
418
+ shape = output["shape"]
419
+ else:
420
+ shape = output.shape
421
+ if len(shape) == 4 and shape[-2] != 1: # Avoid shape of the boxes.
422
+ return i
423
+ return None
424
+
425
+ def get_scores_index(self) -> Union[int, None]:
426
+ """
427
+ Get the index of the score outputs from the model.
428
+ Checking for ModelPack and Ultralytics variations.
429
+ Score output shapes can be in these variations:
430
+ [1, 6000, 14], [1, 37, 8400], [1, 25200, 85], [1, 80, 8400]
431
+
432
+ Returns
433
+ -------
434
+ Union[int, None]
435
+ The index is returned if the score output shape exists.
436
+ Otherwise None is returned.
437
+ """
438
+ for i, output in enumerate(self.outputs):
439
+ if isinstance(output, dict):
440
+ shape = output["shape"]
441
+ else:
442
+ shape = output.shape
443
+ if self.num_boxes != 0:
444
+ if len(shape) == 3 and self.num_boxes in [shape[1], shape[-1]]:
445
+ return i
446
+ # MobileNet SSD [1, 10]
447
+ elif len(shape) == 2 and i == 2 and shape[1] == self.num_boxes:
448
+ return i
449
+ else:
450
+ if len(shape) == 3:
451
+ if (((shape[1] > shape[2]) and (shape[1] / shape[2] > 5))
452
+ or ((shape[1] < shape[2]) and (shape[2] / shape[1] > 5))
453
+ and i != self.boxes["index"]):
454
+ return i
455
+ return None
456
+
457
+ def get_decoded_masks_index(self) -> Union[dict, None]:
458
+ """
459
+ Get the index of the decoded mask outputs from the model.
460
+ Checking for ModelPack variations only.
461
+
462
+ Returns
463
+ -------
464
+ Union[int, None]
465
+ The index is returned if the decoded mask output shape exists.
466
+ Otherwise None is returned.
467
+ """
468
+ # Segmentation will contain both encoded and decoded masks.
469
+ if len(self.outputs) > 1:
470
+ for i, output in enumerate(self.outputs):
471
+ if isinstance(output, dict):
472
+ shape = output["shape"]
473
+ else:
474
+ shape = output.shape
475
+ if self.num_boxes != 0:
476
+ if len(shape) == 3 and self.num_boxes not in [
477
+ shape[1], shape[-1]]:
478
+ return i
479
+ else:
480
+ if len(shape) == 3:
481
+ if ((shape[1] >= shape[2]) and (shape[1] / shape[2] < 5)) or (
482
+ (shape[1] <= shape[2]) and (shape[2] / shape[1] < 5)):
483
+ return i
484
+ return None
485
+
486
+ def get_classes_index(self) -> Union[int, None]:
487
+ """
488
+ Get the index of the class outputs. This is primarily seen
489
+ in MobileNet SSD models.
490
+
491
+ Returns
492
+ -------
493
+ Union[int, None]
494
+ The index is returned if the class output shape exists.
495
+ Otherwise None is returned.
496
+ """
497
+ # Score outputs are in these variations: [1, 10].
498
+ for i, output in enumerate(self.outputs):
499
+ if isinstance(output, dict):
500
+ shape = output["shape"]
501
+ else:
502
+ shape = output.shape
503
+ if self.num_boxes != 0:
504
+ # MobileNet SSD [1, 10]
505
+ if len(shape) == 2 and i == 1 and shape[1] == self.num_boxes:
506
+ return i
507
+ return None