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,595 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Tuple, TYPE_CHECKING
4
+
5
+ import numpy as np
6
+
7
+ from edgefirst.validator.metrics import Metrics, Plots
8
+
9
+ if TYPE_CHECKING:
10
+ from edgefirst.validator.evaluators import ValidationParameters
11
+ from edgefirst.validator.metrics import (DetectionStats,
12
+ DetectionLabelData)
13
+
14
+
15
+ def compute_precision(tp: int, fp: int) -> float:
16
+ """
17
+ Calculates the precision = tp / (tp + fp).
18
+
19
+ Parameters
20
+ ----------
21
+ tp: int
22
+ The number of true positives.
23
+ fp: int
24
+ The number of false positives.
25
+
26
+ Returns
27
+ -------
28
+ float
29
+ Resulting value is the result of tp / (tp + fp).
30
+ """
31
+ if tp + fp == 0:
32
+ return 0.0
33
+ return tp / (tp + fp)
34
+
35
+
36
+ def compute_recall(tp: int, fn: int) -> float:
37
+ """
38
+ Calculates recall = tp / (tp + fn).
39
+
40
+ Parameters
41
+ ----------
42
+ tp: int
43
+ The number of true positives.
44
+ fn: int
45
+ The number of false negatives.
46
+
47
+ Returns
48
+ -------
49
+ float
50
+ Resulting value is the result of tp / (tp + fn).
51
+ """
52
+ if tp + fn == 0:
53
+ return 0.0
54
+ return tp / (tp + fn)
55
+
56
+
57
+ def compute_accuracy(tp: int, fp: int, fn: int) -> float:
58
+ """
59
+ Calculates the accuracy = tp / (tp + fp + fn).
60
+
61
+ Parameters
62
+ ----------
63
+ tp: int
64
+ The number of true positives.
65
+ fp: int
66
+ The number of false positives.
67
+ fn: int
68
+ The number of false negatives.
69
+
70
+ Returns
71
+ -------
72
+ float
73
+ Resulting value is the result of tp / (tp + fp + fn).
74
+ """
75
+ if tp + fp + fn == 0:
76
+ return 0.0
77
+ return tp / (tp + fp + fn)
78
+
79
+
80
+ def compute_ap(
81
+ recall: list,
82
+ precision: list,
83
+ v5_metric: bool = True,
84
+ method: str = "interp"
85
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
86
+ """
87
+ Compute the average precision, given the recall and precision curves.
88
+
89
+ Parameters
90
+ ----------
91
+ recall: list
92
+ The recall curve.
93
+ precision: list
94
+ The precision curve.
95
+ v5_metric: bool
96
+ Assume maximum recall to be 1.0, as in YOLOv5, MMDetetion etc.
97
+ method: str
98
+ Type of area integration to perform, other forms
99
+ include 'continuous', by default use 'interp'.
100
+
101
+ Returns
102
+ -------
103
+ ap: np.ndarray
104
+ The average precision values based on the area under the curve.
105
+ mpre: np.ndarray
106
+ Mean precision from the precision curve.
107
+ mrec: np.ndarray
108
+ Mean recall from the recall curve.
109
+ """
110
+ # Append sentinel values to beginning and end
111
+ if v5_metric: # New YOLOv5 metric, same as MMDetection and Detectron2 repositories
112
+ mrec = np.concatenate(([0.0], recall, [1.0]))
113
+ else: # Old YOLOv5 metric, i.e. default YOLOv7 metric
114
+ mrec = np.concatenate(([0.0], recall, [recall[-1] + 0.01]))
115
+ mpre = np.concatenate(([1.0], precision, [0.0]))
116
+
117
+ # Compute the precision envelope
118
+ mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
119
+
120
+ # Integrate area under curve
121
+ # methods: 'continuous', 'interp'
122
+ if method.lower() == "interp":
123
+ x = np.linspace(0, 1, 101) # 101-point interp (COCO)
124
+ ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
125
+ else: # 'continuous'
126
+ # points where x axis (recall) changes
127
+ i = np.nonzero(mrec[1:] != mrec[:-1])[0]
128
+ ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
129
+ return ap, mpre, mrec
130
+
131
+
132
+ def smooth(y: np.ndarray, f: float = 0.05) -> np.ndarray:
133
+ """
134
+ Applies box filter smoothing to array `y`
135
+ with fraction `f`, yielding a smoothed array.
136
+
137
+ Parameters
138
+ ----------
139
+ y: np.ndarray
140
+ The array to apply a smoothening effect.
141
+ f: float
142
+ Smooth fraction.
143
+
144
+ Returns
145
+ -------
146
+ np.ndarray
147
+ The array with a smoothening effect.
148
+ """
149
+ nf = round(len(y) * f * 2) // 2 + \
150
+ 1 # number of filter elements (must be odd)
151
+ p = np.ones(nf // 2) # ones padding
152
+ yp = np.concatenate((p * y[0], y, p * y[-1]), 0) # y padded
153
+ return np.convolve(yp, np.ones(nf) / nf, mode="valid") # y-smoothed
154
+
155
+
156
+ class DetectionMetrics:
157
+ """
158
+ Runs the metric computations for object detection. The resulting
159
+ metrics will be populated in the `Metrics` object that is created once
160
+ initialized.
161
+
162
+ Parameters
163
+ ----------
164
+ parameters: ValidationParameters
165
+ This contains validation parameters set from the command line.
166
+ detection_stats: DetectionStats
167
+ This is container of the pre-metrics computations per class.
168
+ model_name: str
169
+ The base name of the model being validated.
170
+ dataset_name: str
171
+ The base name of the validation dataset.
172
+ save_path: str
173
+ The path to save the metrics on disk.
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ parameters: ValidationParameters,
179
+ detection_stats: DetectionStats,
180
+ model_name: str = "Model",
181
+ dataset_name: str = "Dataset",
182
+ save_path: str = None,
183
+ labels: list = []
184
+ ):
185
+ self.parameters = parameters
186
+ self.plots = Plots(labels=labels)
187
+ self.detection_stats = detection_stats
188
+ self.metrics = Metrics(model=model_name, dataset=dataset_name)
189
+ self.metrics.save_path = save_path
190
+ self.labels = labels
191
+
192
+ def run_metrics(self):
193
+ """
194
+ Method process for gathering all metrics used for the detection
195
+ validation. Either runs methods to reproduce metrics in Ultralytics (default)
196
+ or other forms of methods in EdgeFirst or YOLOv7.
197
+ """
198
+ if self.parameters.method in ["ultralytics", "yolov7"]:
199
+ self.compute_yolo_metrics()
200
+ else:
201
+ self.compute_edgefirst_metrics()
202
+
203
+ def compute_yolo_metrics(self):
204
+ """
205
+ Calculates metrics based on YOLO (Ultralytics and YOLOv7)
206
+ validation methods.
207
+ """
208
+ p, r, ap, f1 = self.ap_per_class(
209
+ tp=np.concatenate(self.detection_stats.stats["tp"], axis=0),
210
+ conf=np.concatenate(self.detection_stats.stats["conf"], axis=0),
211
+ pred_cls=np.concatenate(
212
+ self.detection_stats.stats["pred_cls"], axis=0),
213
+ target_cls=np.concatenate(
214
+ self.detection_stats.stats["target_cls"], axis=0),
215
+ v5_metric=self.parameters.method == "ultralytics"
216
+ )
217
+ ap50, ap75, ap = ap[:, 0], ap[:, 5], ap.mean(1) # AP@0.5, AP@0.5:0.95
218
+
219
+ self.metrics.precision["map"] = {"0.50": ap50.mean(),
220
+ "0.75": ap75.mean(),
221
+ "0.50:0.95": ap.mean()}
222
+ self.metrics.precision["mean"] = p.mean()
223
+ self.metrics.recall["mean"] = r.mean()
224
+ self.metrics.f1["mean"] = f1.mean()
225
+
226
+ def compute_edgefirst_metrics(self):
227
+ """
228
+ Calculates the EdgeFirst metrics. Populates the
229
+ results in the `Metrics` object.
230
+ """
231
+ nc = len(self.detection_stats.stats)
232
+ _, _, ap, _ = self.ap_per_class(
233
+ np.array(self.detection_stats.tp),
234
+ np.array(self.detection_stats.conf),
235
+ np.array(self.detection_stats.pred_cls),
236
+ np.array(self.detection_stats.target_cls),
237
+ v5_metric=True
238
+ )
239
+ ap50, ap75, ap = ap[:, 0], ap[:, 5], ap.mean(1) # AP@0.5, AP@0.5:0.95
240
+ # These arrays contain the metrics for each class where the rows
241
+ # represent the class and the columns represent the IoU thresholds:
242
+ # 0.00 to 1.00 in 0.05 intervals.
243
+ maps, mars, maccs = np.zeros((nc, 20)), np.zeros(
244
+ (nc, 20)), np.zeros((nc, 20))
245
+
246
+ if nc > 0:
247
+ for ic, label_data in enumerate(self.detection_stats.stats):
248
+ self.metrics.tp += label_data.get_tp_count(
249
+ iou_threshold=self.parameters.iou_threshold,
250
+ score_threshold=self.parameters.score_threshold)
251
+ self.metrics.cfp += label_data.get_cfp_count(
252
+ iou_threshold=self.parameters.iou_threshold,
253
+ score_threshold=self.parameters.score_threshold)
254
+ self.metrics.lfp += label_data.get_lfp_count(
255
+ iou_threshold=self.parameters.iou_threshold,
256
+ score_threshold=self.parameters.score_threshold)
257
+
258
+ # Build TP vs FP scores.
259
+ self.plots.tp_scores.append(
260
+ label_data.get_tp_scores(self.parameters.iou_threshold))
261
+ self.plots.fp_scores.append(
262
+ label_data.get_lfp_scores(self.parameters.iou_threshold))
263
+
264
+ # Build TP vs FP IoUs.
265
+ self.plots.tp_ious.append(
266
+ label_data.get_tp_iou(self.parameters.iou_threshold))
267
+ self.plots.fp_ious.append(
268
+ label_data.get_lfp_iou(self.parameters.iou_threshold))
269
+
270
+ class_metrics, class_truth_values = self.compute_class_metrics(
271
+ label_data,
272
+ self.parameters.iou_threshold
273
+ )
274
+
275
+ data = {
276
+ 'precision': class_metrics[0],
277
+ 'recall': class_metrics[1],
278
+ 'accuracy': class_metrics[2],
279
+ 'tp': class_truth_values[0],
280
+ 'fn': class_truth_values[3],
281
+ 'fp': class_truth_values[1] + class_truth_values[2],
282
+ 'gt': label_data.ground_truths
283
+ }
284
+
285
+ # Convert labels to string
286
+ cls = str(label_data.label)
287
+ if self.labels is not None and len(self.labels) > 0:
288
+ if not isinstance(cls, str) or (
289
+ isinstance(cls, str) and cls.isdigit()):
290
+ cls = self.labels[int(cls)]
291
+
292
+ self.plots.append_class_histogram_data(cls, data)
293
+
294
+ for it, iou_threshold in enumerate(np.arange(0.00, 1, 0.05)):
295
+ class_metrics, _ = self.compute_class_metrics(
296
+ label_data,
297
+ iou_threshold
298
+ )
299
+
300
+ # The index of the class ic and the index of the IoU
301
+ # threshold it will contain the metric: precision, recall,
302
+ # and accuracy of the class.
303
+ mars[ic][it] = class_metrics[1]
304
+ maccs[ic][it] = class_metrics[2]
305
+ # This mAP computation is the average of the precision
306
+ # of each class.
307
+ # maps[ic][it] = class_metrics[0] #NOSONAR
308
+
309
+ mean_metrics = self.compute_mean_average_metrics(
310
+ maps, mars, maccs, nc)
311
+ self.metrics.precision["map"] = {
312
+ "0.50": 0.0 if np.isnan(ap50.mean()) else ap50.mean(),
313
+ "0.75": 0.0 if np.isnan(ap75.mean()) else ap75.mean(),
314
+ "0.50:0.95": 0.0 if np.isnan(ap.mean()) else ap.mean()
315
+ }
316
+ self.metrics.recall["mar"] = {
317
+ "0.50": mean_metrics[1][0],
318
+ "0.75": mean_metrics[1][1],
319
+ "0.50:0.95": mean_metrics[1][2]
320
+ }
321
+ self.metrics.accuracy["macc"] = {
322
+ "0.50": mean_metrics[2][0],
323
+ "0.75": mean_metrics[2][1],
324
+ "0.50:0.95": mean_metrics[2][2]
325
+ }
326
+
327
+ overall_metrics = self.compute_overall_metrics()
328
+ self.metrics.precision["overall"] = overall_metrics[0]
329
+ self.metrics.recall["overall"] = overall_metrics[1]
330
+ self.metrics.accuracy["overall"] = overall_metrics[2]
331
+
332
+ else:
333
+ data = {
334
+ 'precision': 0,
335
+ 'recall': 0,
336
+ 'accuracy': 0,
337
+ 'tp': 0,
338
+ 'fn': 0,
339
+ 'fp': 0,
340
+ 'gt': 0
341
+ }
342
+ self.plots.append_class_histogram_data("No label", data)
343
+
344
+ def ap_per_class(
345
+ self,
346
+ tp: np.ndarray,
347
+ conf: np.ndarray,
348
+ pred_cls: np.ndarray,
349
+ target_cls: np.ndarray,
350
+ eps: float = 1e-16,
351
+ v5_metric: bool = True
352
+ ):
353
+ """
354
+ Compute the average precision, given the recall and precision curves.
355
+ Source:: https://github.com/rafaelpadilla/Object-Detection-Metrics.
356
+ Source:: https://github.com/ultralytics/yolov5/blob/master/utils/metrics.py#L29
357
+
358
+ Parameters
359
+ ----------
360
+ tp: np.ndarray
361
+ True positives (nparray, nx1 or nx10).
362
+ conf: np.ndarray
363
+ Objectness value from 0-1 (nparray).
364
+ pred_cls: np.ndarray
365
+ Predicted object classes (nparray).
366
+ target_cls: np.ndarray
367
+ True object classes (nparray).
368
+ eps: float
369
+ Minimum value to avoid division by zero.
370
+ v5_metrc: bool
371
+ Assume maximum recall to be 1.0, as in YOLOv5, MMDetetion etc.
372
+
373
+ Returns
374
+ -------
375
+ Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
376
+ The average precision as computed in py-faster-rcnn.
377
+ """
378
+ # Sort by objectness
379
+ i = np.argsort(-conf)
380
+ tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
381
+
382
+ # Find unique classes
383
+ unique_classes, nt = np.unique(target_cls, return_counts=True)
384
+ nc = unique_classes.shape[0] # number of classes, number of detections
385
+
386
+ # Create Precision-Recall curve and compute AP for each class
387
+ px, py = np.linspace(0, 1, 1000), [] # for plotting
388
+ ap, p, r = np.zeros((nc, 10)), np.zeros(
389
+ (nc, 1000)), np.zeros((nc, 1000))
390
+
391
+ for ci, c in enumerate(unique_classes):
392
+ # NOTE: Do not use np.nonzero, otherwise i.sum() will fail.
393
+ i = pred_cls == c
394
+ n_l = nt[ci] # number of labels
395
+ n_p = i.sum() # number of predictions
396
+
397
+ if n_p == 0 or n_l == 0:
398
+ continue
399
+
400
+ # Accumulate FPs and TPs
401
+ fpc = (1 - tp[i]).cumsum(0)
402
+ tpc = tp[i].cumsum(0)
403
+
404
+ # Recall
405
+ recall = tpc / (n_l + eps) # recall curve
406
+ # negative x, xp because xp decreases
407
+ r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0)
408
+
409
+ # Precision
410
+ precision = tpc / (tpc + fpc) # precision curve
411
+ p[ci] = np.interp(-px, -conf[i], precision[:, 0],
412
+ left=1) # p at pr_score
413
+
414
+ # AP from recall-precision curve
415
+ for j in range(tp.shape[1]):
416
+ ap[ci, j], mpre, mrec = compute_ap(recall[:, j],
417
+ precision[:, j],
418
+ v5_metric=v5_metric)
419
+ if j == 0:
420
+ # precision at mAP@0.5
421
+ py.append(np.interp(px, mrec, mpre))
422
+
423
+ # Compute F1 (harmonic mean of precision and recall)
424
+ f1 = 2 * p * r / (p + r + eps)
425
+
426
+ # Used for plotting precision recall curve.
427
+ self.plots.px = px
428
+ self.plots.py = py
429
+ self.plots.precision = p
430
+ self.plots.recall = r
431
+ self.plots.average_precision = ap
432
+ self.plots.curve_labels = unique_classes
433
+ self.plots.f1 = f1
434
+
435
+ if v5_metric:
436
+ i = smooth(f1.mean(0), 0.1).argmax() # max F1 index YOLOv5
437
+ else:
438
+ i = f1.mean(0).argmax() # max F1 index # YOLOv7
439
+ return p[:, i], r[:, i], ap, f1[:, i]
440
+
441
+ def compute_class_metrics(
442
+ self,
443
+ label_data: DetectionLabelData,
444
+ iou_threshold: float,
445
+ ) -> Tuple[np.ndarray, list]:
446
+ """
447
+ This is an EdgeFirst validation method.
448
+
449
+ Returns the precision, recall, and accuracy metrics of a
450
+ specific class at the specified validation IoU threshold.
451
+
452
+ Parameters
453
+ ----------
454
+ label_data: DetectionLabelData
455
+ This is a container of the truth values of a specific class.
456
+ iou_threshold: float
457
+ The validation IoU threshold to consider as true positives.
458
+
459
+ Returns
460
+ -------
461
+ class_metrics: np.ndarray (1, 3)
462
+ This contains the values for precision, recall, and accuracy
463
+ of the class representing the label data container.
464
+ class_truth_values: list
465
+ This contains the values for true positives, classification
466
+ false positives, localization false positives, and
467
+ false negatives for the class representing the label data
468
+ container.
469
+ """
470
+ # These are the truth values just for the specified class in the
471
+ # data container: true positives, false positives, and false negatives.
472
+ tp = label_data.get_tp_count(iou_threshold=iou_threshold,
473
+ score_threshold=self.parameters.score_threshold)
474
+ cfp = label_data.get_cfp_count(iou_threshold=iou_threshold,
475
+ score_threshold=self.parameters.score_threshold)
476
+ lfp = label_data.get_lfp_count(iou_threshold=iou_threshold,
477
+ score_threshold=self.parameters.score_threshold)
478
+ fn = label_data.get_fn_count(iou_threshold=iou_threshold,
479
+ score_threshold=self.parameters.score_threshold)
480
+
481
+ class_metrics = np.zeros(3)
482
+ class_truth_values = [tp, cfp, lfp, fn]
483
+
484
+ if tp == 0:
485
+ if cfp + lfp == 0:
486
+ class_metrics[0] = np.nan
487
+ if fn == 0:
488
+ class_metrics[1] = np.nan
489
+ if cfp + lfp + fn == 0:
490
+ class_metrics[2] = np.nan
491
+ else:
492
+ class_metrics[0] = compute_precision(tp, cfp + lfp)
493
+ class_metrics[1] = compute_recall(tp, fn)
494
+ class_metrics[2] = compute_accuracy(tp, cfp + lfp, fn)
495
+
496
+ return class_metrics, class_truth_values
497
+
498
+ def compute_mean_average_metrics(
499
+ self,
500
+ maps: np.ndarray,
501
+ mars: np.ndarray,
502
+ maccs: np.ndarray,
503
+ nc: int
504
+ ) -> Tuple[list, list, list]:
505
+ """
506
+ This is an EdgeFirst validation method.
507
+
508
+ Given an array of precision, recall, and accuracy at 0.50 to 0.95 IoU
509
+ thresholds, this will return values only at IoU thresholds,
510
+ 0.50, 0.75, and 0.50-0.95 averages.
511
+
512
+ Parameters
513
+ ----------
514
+ maps: np.ndarray (1,20)
515
+ precision values from different IoU thresholds.
516
+ mars: np.ndarray (1,20)
517
+ recall values from different IoU thresholds.
518
+ maccs: np.ndarray (1,20)
519
+ accuracy values from different IoU thresholds.
520
+ nc: int
521
+ The number of classes.
522
+
523
+ Returns
524
+ -------
525
+ metric_map: list
526
+ This contains the precision values at IoU thresholds
527
+ 0.50, 0.75, 0.50-0.95
528
+ metric_mar: list
529
+ This contains the recall values at IoU thresholds
530
+ 0.50, 0.75, 0.50-0.95
531
+ metric_maccuracy: list
532
+ This contains the accuracy values at IoU thresholds
533
+ 0.50, 0.75, 0.50-0.95
534
+ """
535
+ # These arrays are essentially the mAP, mAR, and mACC across the
536
+ # IoU thresholds 0.00 to 1.00 in 0.05 intervals with shape (1, 20).
537
+ maps = np.nansum(maps, axis=0) / nc
538
+ mars = np.nansum(mars, axis=0) / nc
539
+ maccs = np.nansum(maccs, axis=0) / nc
540
+
541
+ # These are the mAP, mAR, and mACC 0.5-0.95 IoU thresholds.
542
+ map_5095 = np.sum(maps[10:]) / 10
543
+ mar_5095 = np.sum(mars[10:]) / 10
544
+ macc_5095 = np.sum(maccs[10:]) / 10
545
+
546
+ # This list contains mAP, mAR, mACC 0.50, 0.75, and 0.5-0.95.
547
+ metric_map = [maps[10], maps[15], map_5095]
548
+ metric_mar = [mars[10], mars[15], mar_5095]
549
+ metric_maccuracy = [maccs[10], maccs[15], macc_5095]
550
+
551
+ return metric_map, metric_mar, metric_maccuracy
552
+
553
+ def compute_overall_metrics(self) -> Tuple[float, float, float]:
554
+ """
555
+ This is an EdgeFirst Validation method.
556
+
557
+ Computes the overall metrics.
558
+
559
+ Returns
560
+ -------
561
+ precision: float
562
+ overall precision = sum tp /
563
+ (sum tp + sum fp (localization + classification)).
564
+ recall: float
565
+ overall recall = sum tp /
566
+ (sum tp + sum fn + sum fp (localization)).
567
+ accuracy: float
568
+ overall accuracy = sum tp /
569
+ (sum tp + sum fn + sum fp (localization + classification)).
570
+ """
571
+ precision, recall, accuracy = 0., 0., 0.
572
+
573
+ if self.metrics.tp > 0:
574
+ precision = compute_precision(
575
+ self.metrics.tp,
576
+ self.metrics.cfp + self.metrics.lfp
577
+ )
578
+ recall = compute_recall(
579
+ self.metrics.tp,
580
+ self.metrics.fn + self.metrics.cfp
581
+ )
582
+ accuracy = compute_accuracy(
583
+ self.metrics.tp,
584
+ self.metrics.cfp + self.metrics.lfp,
585
+ self.metrics.fn
586
+ )
587
+ return precision, recall, accuracy
588
+
589
+ def reset(self):
590
+ """
591
+ Reset the metric containers.
592
+ """
593
+ self.plots.reset()
594
+ self.detection_stats.reset()
595
+ self.metrics.reset()