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,3 @@
1
+ from edgefirst.validator.visualize.segmentation import Colors
2
+ from edgefirst.validator.visualize.detection import DetectionDrawer
3
+ from edgefirst.validator.visualize.segmentation import SegmentationDrawer
@@ -0,0 +1,623 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Tuple, Union
4
+
5
+ import numpy as np
6
+ from PIL import Image, ImageDraw, ImageFont
7
+
8
+ from edgefirst.validator.visualize import Colors
9
+
10
+ if TYPE_CHECKING:
11
+ from edgefirst.validator.datasets import DetectionInstance
12
+ from edgefirst.validator.evaluators import Matcher
13
+
14
+
15
+ class DetectionDrawer:
16
+ """
17
+ This class draws detection bounding boxes on the image showing
18
+ the validation results from the ground truth and model prediction
19
+ matches.
20
+ """
21
+
22
+ def __init__(self):
23
+
24
+ self.messages = {
25
+ "Match": "%s %.2f%% %.2f", # Format (label, score, IoU)
26
+ "Match Loc": "LOC: %s %.2f%% %.2f", # Format (label, score, IoU)
27
+ "Loc": "LOC: %s %.2f%%", # Format (label, score)
28
+ "Clf": "CLF: %s %.2f%% %.2f", # Format (label, score, IoU)
29
+ "Basic": "%s %.2f%%", # Format (label, score)
30
+ }
31
+
32
+ self.font = ImageFont.load_default()
33
+ self.image_draw: ImageDraw.ImageDraw = None
34
+ self.colors = Colors()
35
+
36
+ def draw_2d_gt_boxes(
37
+ self,
38
+ image: Union[Image.Image, np.ndarray],
39
+ gt_instance: DetectionInstance,
40
+ method: str = "edgefirst",
41
+ labels: list = None,
42
+ ) -> Image.Image:
43
+ """
44
+ Draw the 2D ground truth bounding boxes on the image.
45
+
46
+ Parameters
47
+ ----------
48
+ image: Union[Image.Image, np.ndarray]
49
+ The image to overlay with boxes and texts.
50
+ gt_instance: DetectionInstance
51
+ This is the ground truth instance containing the
52
+ bounding boxes and their labels as normalized (xyxy) format.
53
+ method: str
54
+ The type of visualization method. By default, visualization
55
+ of "edgefirst" validation results are used. Otherwise,
56
+ "ultralytics" visualizations are used.
57
+ labels: list
58
+ A list of unique string labels to designate
59
+ a specific color for the label.
60
+
61
+ Returns
62
+ -------
63
+ Image.Image
64
+ The image with 2D ground truth boxes.
65
+ """
66
+ if isinstance(image, np.ndarray):
67
+ image = Image.fromarray(image)
68
+ self.image_draw = ImageDraw.Draw(image)
69
+
70
+ # Draw ground truths
71
+ for label, bounding_box in zip(gt_instance.labels, gt_instance.boxes):
72
+ if method == "edgefirst":
73
+ box_position = self.format_box_position(
74
+ box_position=bounding_box
75
+ )
76
+ self.draw_2d_bounding_box(box_position)
77
+ color = "RoyalBlue"
78
+ else:
79
+ if labels is not None:
80
+ color = self.colors(labels.index(label))
81
+ else:
82
+ color = self.colors(0)
83
+ box_position = self.format_box_position(
84
+ box_position=bounding_box
85
+ )
86
+ self.draw_2d_bounding_box(
87
+ box_position=box_position,
88
+ color=color
89
+ )
90
+
91
+ text = str(label)
92
+ background_position, text_position =\
93
+ self.position_2d_text_background(
94
+ text,
95
+ (box_position[0][0], box_position[1][1]),
96
+ box_position,
97
+ portion=0.10
98
+ )
99
+ self.draw_text(
100
+ text,
101
+ text_position,
102
+ background_position=background_position,
103
+ background_color=color
104
+ )
105
+
106
+ return image
107
+
108
+ def draw_2d_dt_boxes(
109
+ self,
110
+ image: Union[Image.Image, np.ndarray],
111
+ dt_instance: DetectionInstance,
112
+ gt_instance: DetectionInstance = None,
113
+ matcher: Matcher = None,
114
+ validation_iou: float = 0.50,
115
+ validation_score: float = 0.25,
116
+ method: str = "edgefirst",
117
+ labels: list = None
118
+ ) -> Image.Image:
119
+ """
120
+ Draw the 2D detection bounding boxes on the image.
121
+
122
+ Parameters
123
+ ----------
124
+ image: Union[Image.Image, np.ndarray]
125
+ The image to overlay with boxes and texts.
126
+ dt_instance: Instance
127
+ This is the prediction instance containing the bounding boxes
128
+ and their scores and labels.
129
+ gt_instance: Instance
130
+ This is the ground truth instance containing the
131
+ bounding boxes and their labels as normalized (xyxy) format.
132
+ matcher: Matcher
133
+ This contains the bounding box matches from EdgeFirst validation
134
+ for assigning colors to true positives, false positives, and
135
+ false negatives.
136
+ validation_iou: float
137
+ This is the validation IoU threshold which determines the point
138
+ between classifying a prediction bounding box as either a
139
+ true positive or a localization false positive.
140
+ validation_score: float
141
+ Filter to visualize the predictions with confident scores
142
+ only, score greater than this threshold set.
143
+ method: str
144
+ The type of visualization method. By default, visualization
145
+ of "edgefirst" validation results are used. Otherwise,
146
+ "ultralytics" visualizations are used.
147
+ labels: list
148
+ A list of unique string labels to designate
149
+ a specific color for the label.
150
+
151
+ Returns
152
+ -------
153
+ Image.Image
154
+ The image with 2D prediction boxes.
155
+ """
156
+ if isinstance(image, np.ndarray):
157
+ image = Image.fromarray(image)
158
+ self.image_draw = ImageDraw.Draw(image)
159
+
160
+ # Visualize EdgeFirst results.
161
+ if method == "edgefirst":
162
+ # Draw extra predictions
163
+ for extra in matcher.index_unmatched_dt:
164
+ dt_label = dt_instance.labels[extra]
165
+ score = dt_instance.scores[extra]
166
+ if score < validation_score:
167
+ continue
168
+
169
+ score *= 100
170
+ text = self.messages["Loc"] % (dt_label, score)
171
+
172
+ bounding_box = dt_instance.boxes[extra]
173
+ box_position = self.format_box_position(bounding_box)
174
+ self.draw_2d_bounding_box(box_position, "OrangeRed")
175
+
176
+ background_position, text_position = self.position_2d_text_background(
177
+ text,
178
+ (box_position[0][0], box_position[0][1]),
179
+ box_position
180
+ )
181
+ self.draw_text(
182
+ text,
183
+ text_position,
184
+ background_position=background_position,
185
+ background_color="OrangeRed"
186
+ )
187
+
188
+ # Draw matches
189
+ for match in matcher.index_matches:
190
+ dt_label = dt_instance.labels[match[0]]
191
+ gt_label = gt_instance.labels[match[1]]
192
+ iou = matcher.iou_list[match[0]]
193
+ score = dt_instance.scores[match[0]]
194
+ if score < validation_score:
195
+ continue
196
+
197
+ score *= 100
198
+ text, color = self.classify_text(
199
+ gt_label, dt_label, score, iou, validation_iou)
200
+
201
+ bounding_box = dt_instance.boxes[match[0]]
202
+ box_position = self.format_box_position(bounding_box)
203
+ self.draw_2d_bounding_box(box_position, color)
204
+
205
+ background_position, text_position = self.position_2d_text_background(
206
+ text,
207
+ (box_position[0][0], box_position[0][1]),
208
+ box_position
209
+ )
210
+ self.draw_text(
211
+ text,
212
+ text_position,
213
+ background_position=background_position,
214
+ background_color=color
215
+ )
216
+
217
+ # Visualize Ultralytics results.
218
+ else:
219
+ if len(dt_instance.boxes):
220
+ filt = dt_instance.scores >= validation_score
221
+ dt_boxes = dt_instance.boxes[filt]
222
+ dt_labels = dt_instance.labels[filt]
223
+ dt_scores = dt_instance.scores[filt]
224
+ else:
225
+ dt_boxes = []
226
+ dt_labels = []
227
+ dt_scores = []
228
+
229
+ # Draw ground truths
230
+ for box, label, score in zip(dt_boxes, dt_labels, dt_scores):
231
+ if labels is not None:
232
+ color = self.colors(labels.index(label))
233
+ else:
234
+ color = self.colors(0)
235
+
236
+ box_position = self.format_box_position(box_position=box)
237
+ self.draw_2d_bounding_box(
238
+ box_position=box_position,
239
+ color=color
240
+ )
241
+
242
+ text = f"{label} {score:.2f}"
243
+ background_position, text_position =\
244
+ self.position_2d_text_background(
245
+ text,
246
+ (box_position[0][0], box_position[1][1]),
247
+ box_position,
248
+ portion=0.10
249
+ )
250
+ self.draw_text(
251
+ text,
252
+ text_position,
253
+ background_position=background_position,
254
+ background_color=color
255
+ )
256
+
257
+ return image
258
+
259
+ def draw_2d_bounding_boxes(
260
+ self,
261
+ gt_instance: DetectionInstance,
262
+ dt_instance: DetectionInstance,
263
+ matcher: Matcher = None,
264
+ validation_iou: float = 0.50,
265
+ validation_score: float = 0.25,
266
+ method: str = "edgefirst",
267
+ labels: list = None
268
+ ) -> Image.Image:
269
+ """
270
+ This is the process for drawing all the 2D bounding boxes in an image.
271
+ This includes the ground truth and the prediction bounding boxes with
272
+ respective colors based on their classifications as true positives,
273
+ false positives, or false negatives.
274
+
275
+ Parameters
276
+ ----------
277
+ gt_instance: DetectionInstance
278
+ This is the ground truth instance containing the
279
+ bounding boxes and their labels as normalized (xyxy) format.
280
+ dt_instance: DetectionInstance
281
+ This is the prediction instance containing the bounding boxes
282
+ and their scores and labels.
283
+ matcher: Matcher
284
+ This contains the bounding box matches from EdgeFirst validation
285
+ for assigning colors to true positives, false positives, and
286
+ false negatives.
287
+ validation_iou: float
288
+ This is the validation IoU threshold which determines the point
289
+ between classifying a prediction bounding box as either a
290
+ true positive or a localization false positive.
291
+ validation_score: float
292
+ Filter to visualize the predictions with confident scores
293
+ only, score greater than this threshold set.
294
+ method: str
295
+ The type of visualization method. By default, visualization
296
+ of "edgefirst" validation results are used. Otherwise,
297
+ "ultralytics" visualizations are used.
298
+ labels: list
299
+ A list of unique string labels to designate
300
+ a specific color for the label.
301
+
302
+ Returns
303
+ -------
304
+ Image.Image
305
+ The image with 2D prediction and ground truth boxes.
306
+ """
307
+ if method == "edgefirst":
308
+ image = Image.fromarray(gt_instance.visual_image)
309
+ image = self.draw_2d_gt_boxes(
310
+ image=image,
311
+ gt_instance=gt_instance
312
+ )
313
+ image = self.draw_2d_dt_boxes(
314
+ image=image,
315
+ dt_instance=dt_instance,
316
+ gt_instance=gt_instance,
317
+ matcher=matcher,
318
+ validation_iou=validation_iou,
319
+ validation_score=validation_score,
320
+ )
321
+ else:
322
+ image = gt_instance.visual_image
323
+ gt_image = self.draw_2d_gt_boxes(
324
+ image=image.copy(),
325
+ gt_instance=gt_instance,
326
+ method="ultralytics",
327
+ labels=labels
328
+ )
329
+ dt_image = self.draw_2d_dt_boxes(
330
+ image=image.copy(),
331
+ dt_instance=dt_instance,
332
+ validation_score=validation_score,
333
+ method="ultralytics",
334
+ labels=labels
335
+ )
336
+
337
+ image = Image.new(
338
+ 'RGB',
339
+ (gt_image.width + dt_image.width, dt_image.height))
340
+ image.paste(gt_image, (0, 0))
341
+ image.paste(dt_image, (dt_image.width, 0))
342
+
343
+ draw_text = ImageDraw.Draw(image)
344
+ draw_text.text(
345
+ (0, 0),
346
+ "GROUND TRUTH",
347
+ font=self.font,
348
+ align='left',
349
+ fill=(0, 0, 0)
350
+ )
351
+ draw_text.text(
352
+ (dt_image.width, 0),
353
+ "MODEL PREDICTION",
354
+ font=self.font,
355
+ align='left',
356
+ fill=(0, 0, 0)
357
+ )
358
+ return image
359
+
360
+ def draw_rect(
361
+ self,
362
+ selected_corners: np.ndarray,
363
+ color: str,
364
+ width: int = 2
365
+ ):
366
+ """
367
+ This is primarily used for drawing 3D bounding boxes which
368
+ consists of two rectangles and four lines.
369
+
370
+ Parameters
371
+ ----------
372
+ selected_corners: np.ndarray
373
+ This contains the corners of the 3D bounding box with shape
374
+ (3,8) representing the (x,y,z) eight corners of a 3D box.
375
+ color: str
376
+ The color to use for the line.
377
+ width: int
378
+ This is the width of the line forming the rectangle.
379
+ """
380
+ prev = selected_corners[-1]
381
+ for corner in selected_corners:
382
+ self.image_draw.line(
383
+ ((int(prev[0]), int(prev[1])),
384
+ (int(corner[0]), int(corner[1]))),
385
+ fill=color,
386
+ width=width
387
+ )
388
+ prev = corner
389
+
390
+ def draw_2d_bounding_box(
391
+ self,
392
+ box_position: tuple,
393
+ color: str = "RoyalBlue",
394
+ width: int = 3
395
+ ):
396
+ """
397
+ Draws a 2D bounding box on the image.
398
+
399
+ Parameters
400
+ ----------
401
+ box_position: tuple
402
+ ((x1, y1), (x2, y2)) position of the box.
403
+ color: str
404
+ The color of the bounding box. Typically,
405
+ ground truth/false negatives are set to "RoyalBlue",
406
+ false positives are set to "OrangeRed",
407
+ true positives are set to "LimeGreen".
408
+ width: int
409
+ The width of the line to draw the bounding boxes.
410
+ """
411
+ self.image_draw.rectangle(
412
+ box_position,
413
+ outline=color,
414
+ width=width
415
+ )
416
+
417
+ def draw_text(
418
+ self,
419
+ text: str,
420
+ text_position: tuple,
421
+ color: str = "black",
422
+ align: str = "left",
423
+ background_position: tuple = None,
424
+ background_color: str = "RoyalBlue"
425
+ ):
426
+ """
427
+ Write text on the image and will also optionally
428
+ draw a 2D box overlay as the background of the text
429
+ to make it more visible.
430
+
431
+ Parameters
432
+ ----------
433
+ text: str
434
+ The text to write on the image.
435
+ text_position: tuple
436
+ This is the (x, y) position on the image to write the text.
437
+ color: str
438
+ This is the color of the text.
439
+ align: str
440
+ This is the text alignment.
441
+ background_position: tuple
442
+ This is the ((x1, y1), (x2, y2)) position to draw the
443
+ background box of the text.
444
+ background_color: str
445
+ This is the color of the background. It is recommended to align the
446
+ colors with the bounding boxes to make it clear which text
447
+ corresponds to which.
448
+ """
449
+ if background_position:
450
+ self.image_draw.rectangle(
451
+ background_position,
452
+ fill=background_color
453
+ )
454
+ self.image_draw.text(
455
+ text_position,
456
+ text,
457
+ font=self.font,
458
+ align=align,
459
+ fill=color
460
+ )
461
+
462
+ def get_text_dimensions(self, text: str) -> Tuple[int, int]:
463
+ """
464
+ Retrieve the text dimensions which varies
465
+ based on the Pillow version used.
466
+
467
+ Parameters
468
+ ----------
469
+ text: str
470
+ This is the text being drawn.
471
+
472
+ Returns
473
+ -------
474
+ width: int
475
+ The width of the text in pixels.
476
+ height: int
477
+ The height of the text in pixels.
478
+ """
479
+ if hasattr(self.font, 'getsize'): # works on older Pillow versions < 10.
480
+ text_width, text_height = self.font.getsize(text)
481
+ else:
482
+ # newer Pillow versions >= 10.
483
+ (text_width, text_height), _ = self.font.font.getsize(text)
484
+ return (text_width, text_height)
485
+
486
+ def position_2d_text_background(
487
+ self,
488
+ text: str,
489
+ text_position: tuple,
490
+ box_position: tuple,
491
+ portion: float = 0.25
492
+ ) -> Tuple[tuple, tuple]:
493
+ """
494
+ This positions the background of the text to make
495
+ it aligned with the 2D bounding box.
496
+
497
+ Parameters
498
+ ----------
499
+ text: str
500
+ The text that will be drawn on the image.
501
+ text_position: tuple
502
+ This contains the (x, y) position of the text.
503
+ box_position: tuple
504
+ This contain the ((x1, y1), (x2, y2)) position
505
+ of the 2D bounding box.
506
+ portion: float
507
+ This is the percentage of the bounding box width to
508
+ resize the font.
509
+
510
+ Returns
511
+ -------
512
+ box_position: tuple
513
+ This is the ((x1, y1), (x2, y2)) position
514
+ of the text background.
515
+ text_position: tuple
516
+ This is the (x,y) position of the text
517
+ aligned to the background.
518
+ """
519
+ text_width, text_height = self.get_text_dimensions(text)
520
+
521
+ # font_size = 10
522
+ # while (text_width < int(portion*(box_position[1][1] - box_position[0][1]))):
523
+ # self.font = ImageFont.load_default(size=font_size)
524
+ # text_width, text_height = self.get_text_dimensions(text)
525
+ # font_size += 1
526
+
527
+ box_text_x1 = box_position[0][0]
528
+ box_text_x2 = box_text_x1 + text_width
529
+
530
+ # This suggests a ground truth text is being drawn where the label is
531
+ # located in the bottom left of the bounding box.
532
+ if text_position[1] > box_position[0][1]:
533
+ # Keep the text within the bounding box.
534
+ box_text_y1 = box_position[1][1] - text_height
535
+ # The larger the text height, the large the offset to center.
536
+ text_position = (
537
+ text_position[0], text_position[1] - int(1.2 * text_height))
538
+ # A prediction text is being drawn where the labels is located
539
+ # in the top left of the bounding box.
540
+ else:
541
+ box_text_y1 = box_position[0][1]
542
+ text_position = (
543
+ text_position[0], text_position[1] - int(0.2 * text_height))
544
+
545
+ box_text_y2 = box_text_y1 + text_height
546
+ return ((box_text_x1, box_text_y1),
547
+ (box_text_x2, box_text_y2)), text_position
548
+
549
+ def classify_text(
550
+ self,
551
+ gt_label: str,
552
+ dt_label: str,
553
+ score: float,
554
+ iou: float,
555
+ validation_iou: float
556
+ ) -> Tuple[str, str]:
557
+ """
558
+ Determine the appropriate text to display and the color
559
+ to use based on the parameters provided.
560
+
561
+ Parameters
562
+ ----------
563
+ gt_label: str
564
+ This is the ground truth label.
565
+ dt_label: str
566
+ This is the prediction label.
567
+ score: float
568
+ This is the prediction score.
569
+ iou: float
570
+ This is the IoU between the ground truth and the prediction.
571
+ validation_iou: float
572
+ This IoU is the threshold of classifying predictions as either
573
+ true positives or localization false positives.
574
+
575
+ Returns
576
+ -------
577
+ text: str
578
+ This is the chosen formatted text to display.
579
+ color: str
580
+ This is the chosen color to use for the bounding box.
581
+ """
582
+ # True Positives.
583
+ if dt_label == gt_label:
584
+ text = self.messages["Match"] % (dt_label, score, iou)
585
+ color = "LimeGreen"
586
+ # Classification False Positives.
587
+ else:
588
+ text = self.messages["Clf"] % (dt_label, score, iou)
589
+ color = "OrangeRed"
590
+
591
+ # Localization False Positives.
592
+ if iou <= validation_iou:
593
+ text = self.messages["Match Loc"] % (dt_label, score, iou)
594
+ color = "OrangeRed"
595
+
596
+ # Any unmatched or sole ground truths are false negatives.
597
+ return text, color
598
+
599
+ @staticmethod
600
+ def format_box_position(
601
+ box_position, width: int = 1, height: int = 1
602
+ ) -> tuple:
603
+ """
604
+ This denormalizes the bounding box coordinates
605
+ and formats it into a tuple.
606
+
607
+ Parameters
608
+ ----------
609
+ box_position: list or np.ndarray
610
+ This is a normalized bounding box [xmin, ymin, xmax, ymax].
611
+ width: int
612
+ This is the width of the image to denormalize the box.
613
+ height: int
614
+ This is the height of the image to denormalize the box.
615
+
616
+ Returns
617
+ -------
618
+ tuple
619
+ Non normalized (pixels) ((xmin, ymin), (xmax, ymax)).
620
+ """
621
+ p1 = (box_position[0] * width, box_position[1] * height)
622
+ p2 = (box_position[2] * width, box_position[3] * height)
623
+ return (p1, p2)