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,749 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import TYPE_CHECKING, List
5
+
6
+ import numpy as np
7
+ import matplotlib.figure
8
+
9
+ from edgefirst.validator.metrics.utils.math import batch_iou
10
+ from edgefirst.validator.visualize.utils.plots import (figure2numpy,
11
+ plot_pr_curve,
12
+ plot_mc_curve,
13
+ plot_score_histogram,
14
+ plot_confusion_matrix,
15
+ plot_classification_detection)
16
+ from edgefirst.validator.datasets.utils.transformations import (clamp_boxes,
17
+ ignore_boxes)
18
+ from edgefirst.validator.datasets.utils.fetch import get_shape
19
+ from edgefirst.validator.visualize import DetectionDrawer
20
+ from edgefirst.validator.datasets import DetectionInstance
21
+ from edgefirst.validator.visualize.utils.plots import ConfusionMatrix
22
+ from edgefirst.validator.runners import (OfflineRunner, DeepViewRTRunner)
23
+ from edgefirst.validator.evaluators import Evaluator, Matcher, DetectionClassifier
24
+ from edgefirst.validator.metrics import YOLOStats, DetectionStats, DetectionMetrics
25
+
26
+ if TYPE_CHECKING:
27
+ from edgefirst.validator.evaluators import CombinedParameters
28
+ from edgefirst.validator.datasets import Dataset
29
+ from edgefirst.validator.runners import Runner
30
+
31
+
32
+ class YOLOValidator(Evaluator):
33
+ """
34
+ Reproduce the validation methods implemented in Ultralytics and other
35
+ variations such as YOLOv7 for detection. Reproduces the metrics in
36
+ Ultralytics to allow comparable metrics between EdgeFirst models
37
+ and Ultralytics models.
38
+
39
+ Parameters
40
+ ----------
41
+ parameters: CombinedParameters
42
+ This is a container for the model, dataset, and validation parameters
43
+ set from the command line.
44
+ runner: Runner
45
+ A type of model runner object responsible for running the model
46
+ for inference provided with an input image to produce bounding boxes.
47
+ dataset: Dataset
48
+ A type of dataset object responsible for reading different types
49
+ of datasets such as Darknet, TFRecords, or EdgeFirst Datasets.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ parameters: CombinedParameters,
55
+ runner: Runner = None,
56
+ dataset: Dataset = None,
57
+ ):
58
+ super(YOLOValidator, self).__init__(
59
+ parameters=parameters, runner=runner, dataset=dataset)
60
+
61
+ self.detection_stats = YOLOStats()
62
+ self.confusion_matrix = ConfusionMatrix(
63
+ nc=len(self.parameters.dataset.labels),
64
+ iou_thres=self.parameters.validation.iou_threshold,
65
+ offset=int(not any(s.lower() == "background"
66
+ for s in self.parameters.dataset.labels))
67
+ )
68
+ self.metrics = DetectionMetrics(
69
+ parameters=self.parameters.validation,
70
+ detection_stats=self.detection_stats,
71
+ model_name=self.model_name,
72
+ dataset_name=self.dataset_name,
73
+ save_path=self.save_path,
74
+ labels=self.parameters.dataset.labels
75
+ )
76
+ self.metrics.plots.initialize_confusion_matrix()
77
+ self.drawer = DetectionDrawer()
78
+ self.matcher = None
79
+
80
+ def instance_collector(self):
81
+ """
82
+ Collects the instances from the ground truth and runs
83
+ model inference on a single image to collect the instance for
84
+ the model predictions.
85
+
86
+ Yields
87
+ ------
88
+ dict
89
+ This yields one image instance from the ground truth
90
+ and model predictions with keys "gt_instance" and "dt_instance".
91
+ """
92
+
93
+ gt_instance: DetectionInstance
94
+ for gt_instance in self.dataset:
95
+ if isinstance(self.runner, (OfflineRunner, DeepViewRTRunner)):
96
+ detections = self.runner.run_single_instance(
97
+ image=gt_instance.image_path
98
+ )
99
+ else:
100
+ detections = self.runner.run_single_instance(
101
+ image=gt_instance.image
102
+ )
103
+ self.filter_gt(gt_instance)
104
+
105
+ if detections is None:
106
+ yield {
107
+ "gt_instance": gt_instance,
108
+ "dt_instance": None
109
+ }
110
+
111
+ dt_instance = DetectionInstance(gt_instance.image_path)
112
+ boxes, labels, scores = detections
113
+ dt_instance.height = gt_instance.height
114
+ dt_instance.width = gt_instance.width
115
+ dt_instance.boxes = boxes
116
+ dt_instance.labels = labels
117
+ dt_instance.scores = scores
118
+ self.filter_dt(dt_instance)
119
+
120
+ yield {
121
+ "gt_instance": gt_instance,
122
+ "dt_instance": dt_instance,
123
+ }
124
+
125
+ def filter_dt(self, dt_instance: DetectionInstance):
126
+ """
127
+ Apply validation filters to the prediction bounding boxes.
128
+
129
+ Parameters
130
+ ----------
131
+ dt_instance: DetectionInstance
132
+ The model detections container of the bounding boxes, labels,
133
+ and scores for a single image/sample.
134
+ """
135
+ if self.parameters.validation.ignore_boxes:
136
+ boxes, labels, scores = ignore_boxes(
137
+ ignore=self.parameters.validation.ignore_boxes,
138
+ boxes=dt_instance.boxes,
139
+ labels=dt_instance.labels,
140
+ scores=dt_instance.scores,
141
+ shape=(dt_instance.height, dt_instance.width)
142
+ )
143
+ dt_instance.boxes = boxes
144
+ dt_instance.labels = labels
145
+ dt_instance.scores = scores
146
+ if self.parameters.validation.clamp_boxes:
147
+ dt_instance.boxes = clamp_boxes(
148
+ boxes=dt_instance.boxes,
149
+ clamp=self.parameters.validation.clamp_boxes,
150
+ shape=(dt_instance.height, dt_instance.width)
151
+ )
152
+
153
+ # Prediction bounding boxes are already centered around objects
154
+ # in images with letterbox, padding, or resize transformations.
155
+ # This operation will only denormalize the bounding box coordinates.
156
+ if len(dt_instance.boxes) and dt_instance.shapes is not None:
157
+ # The model input shape.
158
+ height, width = get_shape(self.parameters.model.common.shape)
159
+ dt_instance.boxes *= np.array([width, height, width, height])
160
+
161
+ # If the model and dataset labels are not equal, it is required
162
+ # to map the indices properly to match the ground truth and the
163
+ # detections.
164
+ if self.parameters.model.labels != self.parameters.dataset.labels:
165
+ try:
166
+ dt_instance.labels = np.array([
167
+ self.parameters.dataset.labels.index(
168
+ self.parameters.model.labels[int(cls)])
169
+ if self.parameters.model.labels[int(cls)]
170
+ in self.parameters.dataset.labels else cls for cls in
171
+ dt_instance.labels])
172
+ except IndexError:
173
+ raise IndexError("Model index out of range. " +
174
+ "Try specifying the path to the model's " +
175
+ "labels via `--model-labels <path to labels.txt>`.")
176
+
177
+ def filter_gt(self, gt_instance: DetectionInstance):
178
+ """
179
+ Apply validation filters for the ground truth bounding boxes.
180
+
181
+ Parameters
182
+ ----------
183
+ gt_instance: DetectionInstance
184
+ The ground truth container for the bounding boxes, labels
185
+ for a single image instance.
186
+ """
187
+
188
+ if self.parameters.validation.ignore_boxes:
189
+ boxes, labels, scores = ignore_boxes(
190
+ ignore=self.parameters.validation.ignore_boxes,
191
+ boxes=gt_instance.boxes,
192
+ labels=gt_instance.labels,
193
+ scores=gt_instance.scores,
194
+ shape=(gt_instance.height, gt_instance.width)
195
+ )
196
+ gt_instance.boxes = boxes
197
+ gt_instance.labels = labels
198
+ gt_instance.scores = scores
199
+ if self.parameters.validation.clamp_boxes:
200
+ gt_instance.boxes = clamp_boxes(
201
+ boxes=gt_instance.boxes,
202
+ clamp=self.parameters.validation.clamp_boxes,
203
+ shape=(gt_instance.height, gt_instance.width)
204
+ )
205
+
206
+ def match_predictions(
207
+ self,
208
+ pred_classes: np.ndarray,
209
+ true_classes: np.ndarray,
210
+ iou: np.ndarray
211
+ ) -> np.ndarray:
212
+ """
213
+ Match predictions to ground truth using IoU.
214
+ Function implementation was taken from Ultralytics::
215
+ https://github.com/ultralytics/ultralytics/blob/main/ultralytics/engine/validator.py#L251
216
+
217
+ Parameters
218
+ ----------
219
+ pred_classes: np.ndarray
220
+ Predicted class indices of shape (N,).
221
+ true_classes: np.ndarray
222
+ Target class indices of shape (M,).
223
+ iou: np.ndarray
224
+ An NxM tensor containing the pairwise IoU
225
+ values for predictions and ground truth.
226
+
227
+ Returns
228
+ -------
229
+ np.ndarray
230
+ Correct tensor of shape (N, 10) for 10 IoU thresholds.
231
+ """
232
+ correct = np.zeros(
233
+ (pred_classes.shape[0], self.detection_stats.ious.shape[0]),
234
+ dtype=bool
235
+ )
236
+
237
+ correct_class = (true_classes[:, None] == pred_classes).astype(
238
+ np.float32) # shape (N, M)
239
+ iou = iou * correct_class
240
+
241
+ for i, threshold in enumerate(self.detection_stats.ious):
242
+ # IoU > threshold and classes match
243
+ matches = np.nonzero(iou >= threshold)
244
+ matches = np.array(matches).T
245
+ if matches.shape[0]:
246
+ if matches.shape[0] > 1:
247
+ matches = matches[iou[matches[:, 0],
248
+ matches[:, 1]].argsort()[::-1]]
249
+ matches = matches[np.unique(
250
+ matches[:, 1], return_index=True)[1]]
251
+ matches = matches[np.unique(
252
+ matches[:, 0], return_index=True)[1]]
253
+ correct[matches[:, 1].astype(int), i] = True
254
+ return correct
255
+
256
+ def process_batch_v5(
257
+ self,
258
+ dt_instance: DetectionInstance,
259
+ gt_instance: DetectionInstance
260
+ ) -> np.ndarray:
261
+ """
262
+ Return the correct prediction matrix. Function implementation was taken
263
+ from YOLOv5: https://github.com/ultralytics/yolov5/blob/master/val.py#L94.
264
+
265
+ Parameters
266
+ -----------
267
+ dt_instance: DetectionInstance
268
+ A prediction instance container of the boxes, labels, and scores.
269
+ gt_instance: DetectionInstance
270
+ A ground truth instance container of the boxes and the labels.
271
+
272
+ Returns
273
+ -------
274
+ np.ndarray
275
+ (array[n, 10]) where n denotes the classes for 10 IoU levels. This
276
+ is a true positive array.
277
+ """
278
+ iou = batch_iou(gt_instance.boxes, dt_instance.boxes)
279
+ return self.match_predictions(
280
+ pred_classes=dt_instance.labels,
281
+ true_classes=gt_instance.labels if len(
282
+ gt_instance.boxes) else np.array([]),
283
+ iou=iou
284
+ )
285
+
286
+ def process_batch_v7(
287
+ self,
288
+ dt_instance: DetectionInstance,
289
+ gt_instance: DetectionInstance
290
+ ) -> np.ndarray:
291
+ """
292
+ Return the correct prediction matrix. Function implementation was taken
293
+ from YOLOv7: https://github.com/WongKinYiu/yolov7/blob/main/test.py#L179
294
+
295
+ Parameters
296
+ -----------
297
+ dt_instance: DetectionInstance
298
+ A prediction instance container of the boxes, labels, and scores.
299
+ gt_instance: DetectionInstance
300
+ A ground truth instance container of the boxes and the labels.
301
+
302
+ Returns
303
+ -------
304
+ np.ndarray
305
+ (array[N, 10]) where n denotes the classes for 10 IoU levels. This
306
+ is a true positive array..
307
+ """
308
+ correct = np.zeros(
309
+ (dt_instance.boxes.shape[0],
310
+ self.detection_stats.ious.shape[0])).astype(bool)
311
+ gt_labels = gt_instance.labels if len(
312
+ gt_instance.boxes) else np.array([])
313
+ detected = [] # target indices
314
+ # Generate a similar format to YOLOv5 for visualization purposes.
315
+ matches = []
316
+ for cls in np.unique(gt_labels):
317
+ ti = np.flatnonzero(cls == gt_labels) # target indices
318
+ # prediction indices
319
+ pi = np.flatnonzero(cls == dt_instance.labels)
320
+
321
+ # Search for detections
322
+ if pi.shape[0]:
323
+ ious = batch_iou(dt_instance.boxes[pi], gt_instance.boxes[ti])
324
+ i = ious.argmax(1)
325
+ ious = ious.max(axis=1)
326
+ detected_set = set()
327
+ for j in np.flatnonzero(ious > self.detection_stats.ious[0]):
328
+ d = ti[i[j]] # detected target
329
+ # The ious[j] is always a tensor of 1 value.
330
+ matches.append([ti[i[j]], pi[j], ious[j]])
331
+ if d.item() not in detected_set:
332
+ detected_set.add(d.item())
333
+ detected.append(d)
334
+ # iou_thres is 1xn
335
+ correct[pi[j]] = ious[j] > self.detection_stats.ious
336
+ # all targets already located in image
337
+ if len(detected) == len(gt_instance.boxes):
338
+ break
339
+ return correct
340
+
341
+ def evaluate(self, instance: dict):
342
+ """
343
+ Run model evaluation using Ultralytics or YOLOv7 validation methods.
344
+
345
+ Parameters
346
+ ----------
347
+ instance: dict
348
+ This contains the ground truth and model prediction instances
349
+ with keys "gt_instance", "dt_instance".
350
+ """
351
+ gt_instance: DetectionInstance = instance.get("gt_instance")
352
+ dt_instance: DetectionInstance = instance.get("dt_instance")
353
+
354
+ niou = len(self.detection_stats.ious)
355
+ nl = len(gt_instance.labels) # The number of ground truths.
356
+ nd = len(dt_instance.labels) # The number of predictions.
357
+ tcls = gt_instance.labels.tolist() if nl else [] # target class.
358
+
359
+ self.metrics.metrics.add_ground_truths(nl)
360
+ self.metrics.metrics.add_predictions(nd)
361
+
362
+ if nl:
363
+ if nd == 0:
364
+ if self.parameters.validation.plots:
365
+ self.confusion_matrix.process_batch(dt_instance=None,
366
+ gt_instance=gt_instance)
367
+ self.detection_stats.stats["tp"].append(
368
+ np.zeros((0, niou), dtype=bool))
369
+ self.detection_stats.stats["conf"].append(np.array([]))
370
+ self.detection_stats.stats["pred_cls"].append(np.array([]))
371
+ self.detection_stats.stats["target_cls"].append(tcls)
372
+ return
373
+
374
+ # Ultralytics method.
375
+ if self.parameters.validation.method == "ultralytics":
376
+ correct = self.process_batch_v5(dt_instance=dt_instance,
377
+ gt_instance=gt_instance)
378
+ # YOLOv7 method.
379
+ elif self.parameters.validation.method == "yolov7":
380
+ correct = self.process_batch_v7(dt_instance=dt_instance,
381
+ gt_instance=gt_instance)
382
+
383
+ if self.parameters.validation.plots:
384
+ self.confusion_matrix.process_batch(dt_instance, gt_instance)
385
+ else:
386
+ correct = np.zeros((dt_instance.boxes.shape[0], niou)).astype(bool)
387
+
388
+ if nd:
389
+ self.detection_stats.stats["tp"].append(correct)
390
+ self.detection_stats.stats["conf"].append(dt_instance.scores)
391
+ self.detection_stats.stats["pred_cls"].append(dt_instance.labels)
392
+ self.detection_stats.stats["target_cls"].append(tcls)
393
+ else:
394
+ self.detection_stats.stats["tp"].append(
395
+ np.zeros((0, niou), dtype=bool))
396
+ self.detection_stats.stats["conf"].append(np.array([]))
397
+ self.detection_stats.stats["pred_cls"].append(np.array([]))
398
+ self.detection_stats.stats["target_cls"].append(tcls)
399
+
400
+ def visualize(
401
+ self,
402
+ gt_instance: DetectionInstance,
403
+ dt_instance: DetectionInstance,
404
+ epoch: int = 0
405
+ ):
406
+ """
407
+ Draw bounding box results on the image and save the results in disk
408
+ or publish into Tensorboard.
409
+
410
+ Parameters
411
+ ----------
412
+ gt_instance: DetectionInstance
413
+ This is the ground truth instance which contains bounding
414
+ boxes and labels to draw.
415
+ dt_instance: DetectionInstance
416
+ This is the model detection instance which contains the
417
+ bounding boxes, labels, and confidence scores to draw.
418
+ epoch: int
419
+ This is the training epoch number. This
420
+ parameter is internal for ModelPack usage.
421
+ Standalone validation does not use this parameter.
422
+ """
423
+
424
+ image = self.drawer.draw_2d_bounding_boxes(
425
+ gt_instance=gt_instance,
426
+ dt_instance=dt_instance,
427
+ matcher=self.matcher,
428
+ validation_iou=self.parameters.validation.iou_threshold,
429
+ validation_score=self.parameters.validation.score_threshold,
430
+ method=self.parameters.validation.method,
431
+ labels=self.parameters.dataset.labels
432
+ )
433
+ if self.parameters.validation.visualize:
434
+ image.save(os.path.join(self.parameters.validation.visualize,
435
+ os.path.basename(gt_instance.image_path)))
436
+ elif self.tensorboard_writer:
437
+ self.tensorboard_writer(
438
+ np.asarray(image), gt_instance.image_path, step=epoch)
439
+
440
+ def get_plots(self) -> List[matplotlib.figure.Figure]:
441
+ """
442
+ Reproduces the validation charts from Ultralytics.
443
+ These plots are Matplotlib figures.
444
+
445
+ Returns
446
+ -------
447
+ List[matplotlib.figure.Figure]
448
+ This contains matplotlib figures of the plots.
449
+ """
450
+ fig_confusion_matrix = self.confusion_matrix.plot(
451
+ names=self.metrics.plots.confusion_labels
452
+ )
453
+ fig_prec_rec_curve = plot_pr_curve(
454
+ precision=self.metrics.plots.py,
455
+ recall=self.metrics.plots.px,
456
+ ap=self.metrics.plots.average_precision,
457
+ names=self.parameters.dataset.labels,
458
+ model=self.metrics.metrics.model,
459
+ iou_threshold=self.parameters.validation.iou_threshold
460
+ )
461
+ fig_f1_curve = plot_mc_curve(
462
+ px=self.metrics.plots.px,
463
+ py=self.metrics.plots.f1,
464
+ names=self.parameters.dataset.labels,
465
+ model=self.metrics.metrics.model,
466
+ ylabel='F1'
467
+ )
468
+ fig_prec_curve = plot_mc_curve(
469
+ px=self.metrics.plots.px,
470
+ py=self.metrics.plots.precision,
471
+ names=self.parameters.dataset.labels,
472
+ model=self.metrics.metrics.model,
473
+ ylabel='Precision'
474
+ )
475
+ fig_rec_curve = plot_mc_curve(
476
+ px=self.metrics.plots.px,
477
+ py=self.metrics.plots.recall,
478
+ names=self.parameters.dataset.labels,
479
+ model=self.metrics.metrics.model,
480
+ ylabel='Recall'
481
+ )
482
+ return [fig_confusion_matrix,
483
+ fig_prec_rec_curve,
484
+ fig_f1_curve,
485
+ fig_prec_curve,
486
+ fig_rec_curve]
487
+
488
+ def save_plots(self, plots: List[matplotlib.figure.Figure]):
489
+ """
490
+ Saves the validation plots as image files in disk.
491
+
492
+ Parameters
493
+ ----------
494
+ plots: List[matplotlib.figure.Figure]
495
+ This is the list of matplotlib figures to save.
496
+ """
497
+ plots[0].savefig(
498
+ f"{self.parameters.validation.visualize}/confusion_matrix.png",
499
+ bbox_inches="tight")
500
+
501
+ plots[1].savefig(
502
+ f"{self.parameters.validation.visualize}/prec_rec_curve.png",
503
+ bbox_inches="tight")
504
+ plots[2].savefig(
505
+ f"{self.parameters.validation.visualize}/F1_curve.png",
506
+ bbox_inches="tight"
507
+ )
508
+ plots[3].savefig(
509
+ f"{self.parameters.validation.visualize}/P_curve.png",
510
+ bbox_inches="tight"
511
+ )
512
+ plots[4].savefig(
513
+ f"{self.parameters.validation.visualize}/R_curve.png",
514
+ bbox_inches="tight"
515
+ )
516
+
517
+ def publish_plots(
518
+ self, plots: List[matplotlib.figure.Figure], epoch: int = 0):
519
+ """
520
+ Publishes the validation plots into Tensorboard.
521
+
522
+ Parameters
523
+ ----------
524
+ plots: List[matplotlib.figure.Figure]
525
+ This is the list of matplotlib figures to save.
526
+ epoch: int
527
+ The training epoch number used for ModelPack training usage.
528
+ """
529
+ nimage_confusion_matrix = figure2numpy(plots[0])
530
+ nimage_precision_recall = figure2numpy(plots[1])
531
+ nimage_f1 = figure2numpy(plots[2])
532
+ nimage_prec = figure2numpy(plots[3])
533
+ nimage_rec = figure2numpy(plots[4])
534
+
535
+ self.tensorboard_writer(
536
+ nimage_confusion_matrix,
537
+ f"{self.metrics.metrics.model}_confusion_matrix.png",
538
+ step=epoch
539
+ )
540
+ self.tensorboard_writer(
541
+ nimage_precision_recall,
542
+ f"{self.metrics.metrics.model}_precision_recall.png",
543
+ step=epoch
544
+ )
545
+ self.tensorboard_writer(
546
+ nimage_f1,
547
+ f"{self.metrics.metrics.model}_F1_curve.png",
548
+ step=epoch
549
+ )
550
+ self.tensorboard_writer(
551
+ nimage_prec,
552
+ f"{self.metrics.metrics.model}_P_curve.png",
553
+ step=epoch
554
+ )
555
+ self.tensorboard_writer(
556
+ nimage_rec,
557
+ f"{self.metrics.metrics.model}_R_curve.png",
558
+ step=epoch
559
+ )
560
+
561
+
562
+ class EdgeFirstValidator(YOLOValidator):
563
+ """
564
+ Define the validation methods for EdgeFirst. Reproduces EdgeFirst matching
565
+ and metrics for object detection.
566
+
567
+ Parameters
568
+ ----------
569
+ parameters: CombinedParameters
570
+ This is a container for the model, dataset, and validation parameters
571
+ set from the command line.
572
+ runner: Runner
573
+ A type of model runner object responsible for running the model
574
+ for inference provided with an input image to produce bounding boxes.
575
+ dataset: Dataset
576
+ A type of dataset object responsible for reading different types
577
+ of datasets such as Darknet, TFRecords, or EdgeFirst Datasets.
578
+ """
579
+
580
+ def __init__(
581
+ self,
582
+ parameters: CombinedParameters,
583
+ runner: Runner = None,
584
+ dataset: Dataset = None,
585
+ ):
586
+ super(EdgeFirstValidator, self).__init__(
587
+ parameters=parameters, runner=runner, dataset=dataset)
588
+
589
+ self.detection_stats = DetectionStats()
590
+ self.matcher = Matcher(parameters=parameters.validation)
591
+ self.metrics = DetectionMetrics(
592
+ parameters=self.parameters.validation,
593
+ detection_stats=self.detection_stats,
594
+ model_name=self.model_name,
595
+ dataset_name=self.dataset_name,
596
+ save_path=self.save_path,
597
+ labels=self.parameters.dataset.labels
598
+ )
599
+ self.classifier = DetectionClassifier(
600
+ parameters=self.parameters.validation,
601
+ detection_stats=self.detection_stats,
602
+ matcher=self.matcher,
603
+ plots=self.metrics.plots
604
+ )
605
+ self.metrics.plots.initialize_confusion_matrix()
606
+
607
+ def evaluate(self, instance: dict):
608
+ """
609
+ Run model evaluation using EdgeFirst validation methods.
610
+
611
+ Parameters
612
+ ----------
613
+ instance: dict
614
+ This contains the ground truth and model predictions instances
615
+ with keys "gt_instance" and "dt_instance".
616
+ """
617
+ gt_instance: DetectionInstance = instance.get("gt_instance")
618
+ dt_instance: DetectionInstance = instance.get("dt_instance")
619
+
620
+ self.detection_stats.capture_class(dt_instance.labels)
621
+ self.detection_stats.capture_class(gt_instance.labels)
622
+
623
+ self.metrics.metrics.add_ground_truths(len(gt_instance.labels))
624
+ self.metrics.metrics.add_predictions(len(dt_instance.labels))
625
+
626
+ self.matcher.match(
627
+ gt_boxes=gt_instance.boxes,
628
+ gt_labels=gt_instance.labels,
629
+ dt_boxes=dt_instance.boxes,
630
+ dt_labels=dt_instance.labels,
631
+ )
632
+ self.classifier.classify(
633
+ gt_instance=gt_instance,
634
+ dt_instance=dt_instance
635
+ )
636
+
637
+ def get_plots(self) -> List[matplotlib.figure.Figure]:
638
+ """
639
+ Generate EdgeFirst validation plots.
640
+
641
+ Returns
642
+ -------
643
+ List[matplotlib.figure.Figure]
644
+ This contains matplotlib figures of the plots.
645
+ """
646
+ fig_confusion_matrix = plot_confusion_matrix(
647
+ confusion_data=self.metrics.plots.confusion_matrix,
648
+ labels=self.metrics.plots.confusion_labels,
649
+ model=self.metrics.metrics.model
650
+ )
651
+ fig_prec_rec_curve = plot_pr_curve(
652
+ precision=self.metrics.plots.py,
653
+ recall=self.metrics.plots.px,
654
+ ap=self.metrics.plots.average_precision,
655
+ names=self.parameters.dataset.labels,
656
+ model=self.metrics.metrics.model,
657
+ iou_threshold=self.parameters.validation.iou_threshold
658
+ )
659
+ fig_class_metrics = plot_classification_detection(
660
+ class_histogram_data=self.metrics.plots.class_histogram_data,
661
+ model=self.metrics.metrics.model,
662
+ )
663
+ fig_score_metrics = plot_score_histogram(
664
+ tp_scores=np.concatenate(self.metrics.plots.tp_scores, axis=0),
665
+ fp_scores=np.concatenate(self.metrics.plots.fp_scores, axis=0),
666
+ model=self.metrics.metrics.model
667
+ )
668
+ fig_iou_metrics = plot_score_histogram(
669
+ tp_scores=np.concatenate(self.metrics.plots.tp_ious, axis=0),
670
+ fp_scores=np.concatenate(self.metrics.plots.fp_ious, axis=0),
671
+ model=self.metrics.metrics.model,
672
+ title="Histogram of TP vs FP IoUs",
673
+ xlabel="IoU"
674
+ )
675
+ return [fig_confusion_matrix, fig_prec_rec_curve,
676
+ fig_class_metrics, fig_score_metrics, fig_iou_metrics]
677
+
678
+ def save_plots(self, plots: List[matplotlib.figure.Figure]):
679
+ """
680
+ Saves the validation plots as image files in disk.
681
+
682
+ Parameters
683
+ ----------
684
+ plots: List[matplotlib.figure.Figure]
685
+ This is the list of matplotlib figures to save.
686
+ """
687
+ plots[0].savefig(
688
+ f"{self.parameters.validation.visualize}/confusion_matrix.png",
689
+ bbox_inches="tight")
690
+
691
+ plots[1].savefig(
692
+ f"{self.parameters.validation.visualize}/prec_rec_curve.png",
693
+ bbox_inches="tight")
694
+
695
+ plots[2].savefig(
696
+ f"{self.parameters.validation.visualize}/class_scores.png",
697
+ bbox_inches="tight")
698
+
699
+ plots[3].savefig(
700
+ f"{self.parameters.validation.visualize}/histogram_scores.png",
701
+ bbox_inches="tight")
702
+
703
+ plots[4].savefig(
704
+ f"{self.parameters.validation.visualize}/histogram_ious.png",
705
+ bbox_inches="tight")
706
+
707
+ def publish_plots(
708
+ self, plots: List[matplotlib.figure.Figure], epoch: int = 0):
709
+ """
710
+ Publishes the validation plots into Tensorboard.
711
+
712
+ Parameters
713
+ ----------
714
+ plots: List[matplotlib.figure.Figure]
715
+ This is the list of matplotlib figures to save.
716
+ epoch: int
717
+ The training epoch number used for ModelPack training usage.
718
+ """
719
+ nimage_confusion_matrix = figure2numpy(plots[0])
720
+ nimage_precision_recall = figure2numpy(plots[1])
721
+ nimage_class = figure2numpy(plots[2])
722
+ nimage_score = figure2numpy(plots[3])
723
+ nimage_iou = figure2numpy(plots[4])
724
+
725
+ self.tensorboard_writer(
726
+ nimage_confusion_matrix,
727
+ f"{self.metrics.metrics.model}_confusion_matrix.png",
728
+ step=epoch
729
+ )
730
+ self.tensorboard_writer(
731
+ nimage_precision_recall,
732
+ f"{self.metrics.metrics.model}_precision_recall.png",
733
+ step=epoch
734
+ )
735
+ self.tensorboard_writer(
736
+ nimage_class,
737
+ f"{self.metrics.metrics.model}_scores.png",
738
+ step=epoch
739
+ )
740
+ self.tensorboard_writer(
741
+ nimage_score,
742
+ f"{self.metrics.metrics.model}_histogram_scores.png",
743
+ step=epoch
744
+ )
745
+ self.tensorboard_writer(
746
+ nimage_iou,
747
+ f"{self.metrics.metrics.model}_histogram_ious.png",
748
+ step=epoch
749
+ )