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,717 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+
5
+
6
+ def mean_absolute_error(
7
+ y_true: Union[list, np.ndarray],
8
+ y_pred: Union[list, np.ndarray]
9
+ ) -> float:
10
+ """
11
+ Calculates the mean absolute error defined in this source::
12
+ https://datagy.io/mae-python/
13
+
14
+ Parameters
15
+ ----------
16
+ y_true: Union[list, np.ndarray]
17
+ The true values.
18
+ y_pred: Union[list, np.ndarray]
19
+ The predicted values.
20
+
21
+ Returns
22
+ -------
23
+ float
24
+ The mean absolute error of the values comparing y_pred to y_true.
25
+ """
26
+ return np.abs(np.subtract(y_true, y_pred)).mean()
27
+
28
+
29
+ def mean_squared_error(
30
+ y_true: Union[list, np.ndarray],
31
+ y_pred: Union[list, np.ndarray]
32
+ ) -> float:
33
+ """
34
+ Calculates the mean squared error defined in this source:
35
+ https://www.geeksforgeeks.org/python-mean-squared-error/
36
+
37
+ Parameters
38
+ ----------
39
+ y_true: Union[list, np.ndarray]
40
+ The true values.
41
+ y_pred: Union[list, np.ndarray]
42
+ The predicted values.
43
+
44
+ Returns
45
+ -------
46
+ float
47
+ The mean squared error of the values
48
+ comparing y_pred to y_true.
49
+ """
50
+ return np.square(np.subtract(y_true, y_pred)).mean()
51
+
52
+
53
+ def batch_iou(box1: np.ndarray, box2: np.ndarray, eps: float = 1e-7):
54
+ """
55
+ The intersection-over-union (Jaccard index) of boxes.
56
+ The YOLOv5 implementation taken from::
57
+ https://github.com/ultralytics/yolov5/blob/master/utils/metrics.py#L266
58
+
59
+ Both sets of boxes are expected to be in (xmin, ymin, xmax, ymax) format.
60
+
61
+ Parameters
62
+ ----------
63
+ box1 : np.ndarray
64
+ The first set of bounding boxes with the shape (n, 4)
65
+ in the format [xmin, ymin, xmax, ymax].
66
+ box2 : np.ndarray
67
+ The second bounding boxes with the shape (n, 4)
68
+ in the format [xmin, ymin, xmax, ymax].
69
+ eps : float
70
+ This is used to prevent division by 0.
71
+
72
+ Returns
73
+ -------
74
+ np.ndarray
75
+ An IoU 2D matrix which calculates
76
+ the IoU between box1 (row) and
77
+ box2 (column).
78
+ """
79
+ if 0 in [len(box1), len(box2)]:
80
+ return np.zeros((len(box1), len(box2)), dtype=np.float32)
81
+
82
+ a1, a2 = np.expand_dims(
83
+ box1[:, [0, 1]], 1), np.expand_dims(box1[:, [2, 3]], 1)
84
+ b1, b2 = np.expand_dims(
85
+ box2[:, [0, 1]], 0), np.expand_dims(box2[:, [2, 3]], 0)
86
+
87
+ inter = np.minimum(a2, b2) - np.maximum(a1, b1)
88
+ inter = np.prod(np.clip(inter, a_min=0., a_max=np.max(inter)), axis=2)
89
+ return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
90
+
91
+
92
+ def mask_iou(mask1: np.ndarray, mask2: np.ndarray,
93
+ eps: float = 1e-7) -> np.ndarray:
94
+ """
95
+ The intersection-over-union (Jaccard index) of masks.
96
+ The Ultralytics implementation taken from::
97
+ https://github.com/ultralytics/ultralytics/blob/main/ultralytics/utils/metrics.py#L147
98
+
99
+ Parameters
100
+ ----------
101
+ mask1 : np.ndarray
102
+ Array of shape (N, n), N ground truth masks, flattened.
103
+ This array must have floating point values for correct IoUs.
104
+ mask2 : np.ndarray
105
+ Array of shape (M, n), M predicted masks, flattened.
106
+ This array must have floating point values for correct IoUs.
107
+ eps : float, optional
108
+ Small epsilon to prevent division by zero.
109
+
110
+ Returns
111
+ -------
112
+ np.ndarray
113
+ IoU matrix of shape (N, M) representing mask IoUs.
114
+ """
115
+ # Compute intersection
116
+ intersection = np.clip(mask1 @ mask2.T, 0, None) # shape: (N, M)
117
+
118
+ # Compute union
119
+ area1 = mask1.sum(axis=1, keepdims=True) # shape: (N, 1)
120
+ area2 = mask2.sum(axis=1, keepdims=True).T # shape: (1, M)
121
+ union = area1 + area2 - intersection
122
+
123
+ # Compute IoU
124
+ return intersection / (union + eps)
125
+
126
+
127
+ def iou_2d(
128
+ box1: Union[list, np.ndarray],
129
+ box2: Union[list, np.ndarray],
130
+ eps: float = 1e-7
131
+ ) -> float:
132
+ """
133
+ Computes the IoU between a single ground truth and prediction
134
+ bounding boxes. IoU computation method retrieved from::
135
+ https://gist.github.com/meyerjo/dd3533edc97c81258898f60d8978eddc
136
+
137
+ Parameters
138
+ ----------
139
+ box1: Union[list, np.ndarray]
140
+ This is a bounding box [xmin, ymin, xmax, ymax].
141
+ box2: Union[list, np.ndarray]
142
+ This is a bounding box [xmin, ymin, xmax, ymax].
143
+ eps: float
144
+ Avoids division by zero errors. Default to 1e-7.
145
+
146
+ Returns
147
+ -------
148
+ float
149
+ The IoU score between two bounding boxes.
150
+
151
+ Exceptions
152
+ ----------
153
+ ValueError
154
+ Raised if the provided boxes for the ground truth
155
+ and prediction does not have a length of four.
156
+ """
157
+ if len(box1) != 4 or len(box2) != 4:
158
+ raise ValueError("The provided bounding boxes does not meet "
159
+ "expected lengths [xmin, ymin, xmax, ymax]")
160
+
161
+ # Determine the (x, y)-coordinates of the intersection rectangle.
162
+ x_a = max(box1[0], box2[0])
163
+ y_a = max(box1[1], box2[1])
164
+ x_b = min(box1[2], box2[2])
165
+ y_b = min(box1[3], box2[3])
166
+
167
+ # Compute the area of intersection rectangle.
168
+ inter_area = max((x_b - x_a, 0)) * max((y_b - y_a), 0)
169
+ if inter_area == 0:
170
+ return 0.
171
+ # Compute the area of both the prediction and ground-truth rectangles.
172
+ box_a_area = abs((box1[2] - box1[0]) * (box1[3] - box1[1]))
173
+ box_b_area = abs((box2[2] - box2[0]) * (box2[3] - box2[1]))
174
+
175
+ # Compute the intersection over union by taking the intersection
176
+ # area and dividing it by the sum of prediction + ground-truth
177
+ # areas - the interesection area
178
+ iou = inter_area / float(box_a_area + box_b_area - inter_area)
179
+
180
+ assert 0. <= iou <= 1. + eps, f"The IoU {iou} is out of bounds."
181
+
182
+ # Return the intersection over union value.
183
+ return iou
184
+
185
+
186
+ def iou_3d(
187
+ corners1: Union[list, np.ndarray],
188
+ corners2: Union[list, np.ndarray]
189
+ ) -> float:
190
+ """
191
+ Computes the 3D IoU between ground truth and detection bounding boxes.
192
+ Source:: https://github.com/varunagrawal/bbox/blob/master/bbox/metrics.py#L139
193
+
194
+ Parameters
195
+ ----------
196
+ corners1: Union[list, np.ndarray]
197
+ This is an (8, 3) array of 8 corners for a single 3D bounding box where
198
+ rows represents one point and columns represents the [x,y,z] coordinates.
199
+ corners2: Union[list, np.ndarray]
200
+ This is an (8, 3) array of 8 corners for a single 3D bounding box where
201
+ rows represents one point and columns represents the [x,y,z] coordinates.
202
+
203
+ Returns
204
+ -------
205
+ float
206
+ This is the 3D IoU between ground truth and prediction bounding boxes.
207
+ """
208
+ # Check if the two boxes don't overlap.
209
+ if not polygon_collision(corners1[0:4, [0, 2]], corners2[0:4, [0, 2]]):
210
+ return 0.0
211
+
212
+ # Intersection of the x,z plane.
213
+ intersection_points = polygon_intersection(
214
+ corners1[0:4, [0, 2]], corners2[0:4, [0, 2]])
215
+ # If intersection_points is empty, means the boxes don't intersect
216
+ if len(intersection_points) == 0:
217
+ return 0.0
218
+ inter_area = polygon_area(intersection_points)
219
+
220
+ ymax = np.minimum(corners1[4, 1], corners2[4, 1])
221
+ ymin = np.maximum(corners1[0, 1], corners2[0, 1])
222
+ inter_vol = inter_area * np.maximum(0, ymax - ymin)
223
+
224
+ vol1 = box3d_vol(corners1)
225
+ vol2 = box3d_vol(corners2)
226
+ union_vol = (vol1 + vol2 - inter_vol)
227
+
228
+ iou = inter_vol / union_vol
229
+ # set nan and +/- inf to 0
230
+ if np.isinf(iou) or np.isnan(iou):
231
+ iou = 0
232
+ return iou
233
+
234
+
235
+ def minkowski_distance(
236
+ center1: Union[list, np.ndarray],
237
+ center2: Union[list, np.ndarray],
238
+ p: int = 2
239
+ ) -> float:
240
+ """
241
+ Calculates the Minkowski distance between two points.
242
+ If p is 1, then this would be the Hamming distance.
243
+ If p is 2, then this would be the Euclidean distance.
244
+ https://www.analyticsvidhya.com/blog/2020/02/4-types-of-distance-metrics-in-machine-learning/
245
+
246
+ Parameters
247
+ ----------
248
+ center1: list or np.ndarray
249
+ The 2D [x,y] or 3D [x,y,z] coordinates
250
+ for the first point.
251
+ center2: list or np.ndarray
252
+ The 2D [x,y] or 3D [x,y,z] coordinates
253
+ for the second point.
254
+ p: int
255
+ The order in the minkowski distance computation.
256
+
257
+ Returns
258
+ -------
259
+ float
260
+ The distance between two points.
261
+ """
262
+ return np.power(np.sum(np.power(np.absolute(center1 - center2), p)), 1 / p)
263
+
264
+
265
+ def cosine_similarity(
266
+ center1: Union[list, np.ndarray],
267
+ center2: Union[list, np.ndarray],
268
+ normalize: bool = False
269
+ ) -> float:
270
+ """
271
+ The cosine similarity between two vectors is the dot product
272
+ of the vectors over the product of the magnitudes of the vectors.
273
+ https://en.wikipedia.org/wiki/Cosine_similarity
274
+
275
+ Parameters
276
+ ----------
277
+ center1: list or np.ndarray
278
+ The 2D [x,y] or 3D [x,y,z] coordinates
279
+ for the first point.
280
+ center2: list or np.ndarray
281
+ The 2D [x,y] or 3D [x,y,z] coordinates
282
+ for the second point.
283
+ normalize: bool
284
+ If this is set to true, this normalizes the metric to be within
285
+ the range of 0 and 1. This is used such that the metric behaves
286
+ similar to an IoU for object detection. Otherwise, by default
287
+ it is -1 for perpendicular vectors and 1 for orthogonal.
288
+
289
+ Returns
290
+ -------
291
+ float
292
+ The distance between two points.
293
+ """
294
+ cosine = np.dot(center1, center2) / \
295
+ (np.linalg.norm(center1) * np.linalg.norm(center2))
296
+ # normalize ranges -1 to 1 into 0 to 1.
297
+ if normalize:
298
+ cosine = (cosine + 1) / 2
299
+ return cosine
300
+
301
+
302
+ def sigmoid(p: np.ndarray) -> np.ndarray:
303
+ """
304
+ The sigmoid function that maps values between 0 and 1.
305
+
306
+ Parameters
307
+ ----------
308
+ p: np.ndarray
309
+ An array of values to transform.
310
+
311
+ Returns
312
+ -------
313
+ np.ndarray
314
+ An array of values with sigmoid applied.
315
+ """
316
+ return 1 / (1 + np.exp(-p))
317
+
318
+
319
+ def softmax(x: np.ndarray) -> np.ndarray:
320
+ """
321
+ Transform values between 0 and 1.
322
+
323
+ Parameters
324
+ ----------
325
+ x: np.ndarray
326
+ An array of values to transform.
327
+
328
+ Returns
329
+ -------
330
+ np.ndarray
331
+ The array with softmax transformations.
332
+ """
333
+ # Subtract the maximum for numerical stability
334
+ e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
335
+ return e_x / np.sum(e_x, axis=-1, keepdims=True)
336
+
337
+
338
+ def localize_distance(
339
+ box1: Union[list, np.ndarray],
340
+ box2: Union[list, np.ndarray],
341
+ leniency_factor: int = 2
342
+ ) -> float:
343
+ """
344
+ Given the diagonal of the smaller bounding box, the center distance
345
+ between the bounding boxes will only be considered if the diagonal length
346
+ does not exceed the number of times as the leniency factor when compared
347
+ against the center distance calculated.
348
+
349
+ Parameters
350
+ ----------
351
+ box1: list or np.ndarray
352
+ This is a bounding box [xmin, ymin, xmax, ymax].
353
+ box2: list or np.ndarray
354
+ This is a bounding box [xmin, ymin, xmax, ymax].
355
+ leniency_factor: int
356
+ This is the maximum times the diagonal of the smaller bounding
357
+ box should fit inside the center distances.
358
+
359
+ Returns
360
+ -------
361
+ float
362
+ The restricted distance between the centers of bounding boxes. If
363
+ it does not meet the leniency criteria, it will return the maximum
364
+ distance of 1.
365
+ """
366
+ diagonal = min(minkowski_distance(box1[0:2], box1[2:4]),
367
+ minkowski_distance(box2[0:2], box2[2:4]))
368
+ center_distance = minkowski_distance(box1, box2)
369
+ if int(center_distance / diagonal) <= leniency_factor:
370
+ return center_distance
371
+ # Validation takes 1-center_distance, so returning 1. would indicate far
372
+ # apart.
373
+ return 1.
374
+
375
+
376
+ """
377
+ Useful functions to deal with 3D geometry.
378
+ Source: https://github.com/varunagrawal/bbox/blob/master/bbox/geometry.py
379
+ """
380
+
381
+
382
+ def x_rotation(angle: float) -> np.ndarray:
383
+ """
384
+ Rotation around the x-axis.
385
+ Source: https://en.wikipedia.org/wiki/Rotation_matrix
386
+
387
+ Parameters
388
+ ----------
389
+ angle: float
390
+ The angle of rotation in radians.
391
+
392
+ Returns
393
+ -------
394
+ np.ndarrau
395
+ Rotation matrix around the x-axis.
396
+ """
397
+ c = np.cos(angle)
398
+ s = np.sin(angle)
399
+ return np.array([[1, 0, 0],
400
+ [0, c, -s],
401
+ [0, s, c]])
402
+
403
+
404
+ def y_rotation(angle: float) -> np.ndarray:
405
+ """
406
+ Rotation around the y-axis.
407
+ Source: https://en.wikipedia.org/wiki/Rotation_matrix
408
+
409
+ Parameters
410
+ ----------
411
+ angle: float
412
+ The angle of rotation in radians.
413
+
414
+ Returns
415
+ -------
416
+ np.ndarray
417
+ Rotation matrix around the y-axis.
418
+ """
419
+ c = np.cos(angle)
420
+ s = np.sin(angle)
421
+ return np.array([[c, 0, s],
422
+ [0, 1, 0],
423
+ [-s, 0, c]])
424
+
425
+
426
+ def z_rotation(angle: float) -> np.ndarray:
427
+ """
428
+ Rotation around the z-axis.
429
+ Source: https://en.wikipedia.org/wiki/Rotation_matrix
430
+
431
+ Parameters
432
+ ----------
433
+ angle: float
434
+ The angle of rotation in radians.
435
+
436
+ Returns
437
+ -------
438
+ np.ndarray
439
+ Rotation matrix around the z-axis.
440
+ """
441
+ c = np.cos(angle)
442
+ s = np.sin(angle)
443
+ return np.array([[c, -s, 0],
444
+ [s, c, 0],
445
+ [0, 0, 1]])
446
+
447
+
448
+ def transform(
449
+ box_size: Union[list, np.ndarray, tuple],
450
+ heading_angle: float,
451
+ center: Union[list, np.ndarray, tuple]
452
+ ) -> np.ndarray:
453
+ """
454
+ Provides rotations and formation of the 3D box corners.
455
+
456
+ Parameters
457
+ ----------
458
+ box_size: Union[list, np.ndarray, tuple]
459
+ Can be unpacked to width, height, length respectively.
460
+ heading_angle: float
461
+ The angle in radians to rotate around the y-axis.
462
+ center: Union[list, np.ndarray, tuple]
463
+ In the order of the x-center, y-center, and z-center coordinates.
464
+
465
+ Returns
466
+ -------
467
+ np.ndarray
468
+ This is an (3, 8) array which represents the 3D bounding box corners
469
+ where rows are the [x,y,z] coordinates and columns
470
+ are the 8 corner points.
471
+ """
472
+ R = y_rotation(heading_angle)
473
+ w, h, l = box_size
474
+ x_corners = [-l / 2, l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2, -l / 2]
475
+ y_corners = [-h / 2, -h / 2, -h / 2, -h / 2, h / 2, h / 2, h / 2, h / 2]
476
+ z_corners = [-w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2, w / 2]
477
+
478
+ corners_3d = np.dot(R, np.vstack([x_corners, y_corners, z_corners]))
479
+ corners_3d[0, :] = corners_3d[0, :] + center[0]
480
+ corners_3d[1, :] = corners_3d[1, :] + center[1]
481
+ corners_3d[2, :] = corners_3d[2, :] + center[2]
482
+ return corners_3d
483
+
484
+
485
+ def box3d_vol(corners: np.ndarray) -> float:
486
+ """
487
+ Computes the volume of the 3D bounding box based on the corners provided.
488
+
489
+ Parameters
490
+ ----------
491
+ corners: np.ndarray
492
+ This is an (8, 3) array of the bounding box corners
493
+ with no assumption on axis direction.
494
+
495
+ Returns
496
+ -------
497
+ float
498
+ The volume of the bounding box.
499
+ """
500
+ a = np.sqrt(np.sum((corners[0, :] - corners[1, :])**2))
501
+ b = np.sqrt(np.sum((corners[1, :] - corners[2, :])**2))
502
+ c = np.sqrt(np.sum((corners[0, :] - corners[4, :])**2))
503
+ return a * b * c
504
+
505
+
506
+ def get_corners(sizes: list, box_angles: list, centers: list) -> list:
507
+ """
508
+ Transforms a list of sizes, angles, and centers into 3D box corners.
509
+
510
+ Parameters
511
+ ----------
512
+ sizes: list
513
+ Contains lists that can be unpacked
514
+ to width, height, length respectively.
515
+ box_angles: list
516
+ Contains the angles in radians to rotate around the y-axis.
517
+ centers: list
518
+ Contains lists in the order of the
519
+ x-center, y-center, and z-center coordinates.
520
+
521
+ Returns
522
+ -------
523
+ list
524
+ A list of (8,3) corners.
525
+ """
526
+ # corner formats should be [[[x,y,z], [x,y,z], ... x6]]
527
+ corners = list()
528
+ if 0 not in [len(sizes), len(box_angles), len(centers)]:
529
+ for size, angle, center in zip(sizes, box_angles, centers):
530
+ corners.append(transform(size, angle, center))
531
+ return corners
532
+
533
+
534
+ def get_plane(a, b, c):
535
+ """
536
+ Get plane equation from 3 points.
537
+ Returns the coefficients of `ax + by + cz + d = 0`
538
+ """
539
+ ab = b - a
540
+ ac = c - a
541
+
542
+ x = np.cross(ab, ac)
543
+ d = -np.dot(x, a)
544
+ pl = np.hstack((x, d))
545
+ return pl
546
+
547
+
548
+ def point_plane_dist(pt, plane, signed: bool = False):
549
+ """
550
+ Get the signed distance from a point `pt` to a plane `plane`.
551
+ Reference: http://mathworld.wolfram.com/Point-PlaneDistance.html
552
+
553
+ Plane is of the format [A, B, C, D], where the plane equation is Ax+By+Cz+D=0
554
+ Point is of the form [x, y, z]
555
+ `signed` flag indicates whether to return signed distance.
556
+ """
557
+ v = plane[0:3]
558
+ dist = (np.dot(v, pt) + plane[3]) / np.linalg.norm(v)
559
+
560
+ if signed:
561
+ return dist
562
+ else:
563
+ return np.abs(dist)
564
+
565
+
566
+ def edges_of(vertices):
567
+ """
568
+ Return the vectors for the edges of the polygon defined by `vertices`.
569
+
570
+ Args:
571
+ vertices: list of vertices of the polygon.
572
+ """
573
+ edges = []
574
+ N = len(vertices)
575
+
576
+ for i in range(N):
577
+ edge = vertices[(i + 1) % N] - vertices[i]
578
+ edges.append(edge)
579
+
580
+ return edges
581
+
582
+
583
+ def orthogonal(v):
584
+ """
585
+ Return a 90 degree clockwise rotation of the vector `v`.
586
+
587
+ Args:
588
+ v: 2D array representing a vector.
589
+ """
590
+ return np.array([-v[1], v[0]])
591
+
592
+
593
+ def is_separating_axis(o, p1, p2):
594
+ """
595
+ Return True and the push vector if `o` is a separating axis
596
+ of `p1` and `p2`. Otherwise, return False and None.
597
+
598
+ Args:
599
+ o: 2D array representing a vector.
600
+ p1: 2D array of points representing a polygon.
601
+ p2: 2D array of points representing a polygon.
602
+ """
603
+ min1, max1 = float('+inf'), float('-inf')
604
+ min2, max2 = float('+inf'), float('-inf')
605
+
606
+ for v in p1:
607
+ projection = np.dot(v, o)
608
+
609
+ min1 = min(min1, projection)
610
+ max1 = max(max1, projection)
611
+
612
+ for v in p2:
613
+ projection = np.dot(v, o)
614
+
615
+ min2 = min(min2, projection)
616
+ max2 = max(max2, projection)
617
+
618
+ if max1 >= min2 and max2 >= min1:
619
+ d = min(max2 - min1, max1 - min2)
620
+ # push a bit more than needed so the shapes do not overlap in future
621
+ # tests due to float precision
622
+ d_over_o_squared = d / np.dot(o, o) + 1e-10
623
+ pv = d_over_o_squared * o
624
+ return False, pv
625
+ else:
626
+ return True, None
627
+
628
+
629
+ def polygon_collision(p1, p2):
630
+ """
631
+ Return True if the shapes collide. Otherwise, return False.
632
+
633
+ p1 and p2 are np.arrays, the vertices of the polygons in the
634
+ counterclockwise direction.
635
+
636
+ Source: https://hackmd.io/s/ryFmIZrsl
637
+
638
+ Args:
639
+ p1: 2D array of points representing a polygon.
640
+ p2: 2D array of points representing a polygon.
641
+ """
642
+ edges = edges_of(p1)
643
+ edges += edges_of(p2)
644
+ orthogonals = [orthogonal(e) for e in edges]
645
+
646
+ push_vectors = []
647
+ for o in orthogonals:
648
+ separates, pv = is_separating_axis(o, p1, p2)
649
+
650
+ if separates:
651
+ # they do not collide and there is no push vector
652
+ return False
653
+ else:
654
+ push_vectors.append(pv)
655
+
656
+ return True
657
+
658
+
659
+ def polygon_area(polygon):
660
+ """
661
+ Get the area of a polygon which is represented by a 2D array of points.
662
+ Area is computed using the Shoelace Algorithm.
663
+
664
+ Args:
665
+ polygon: 2D array of points.
666
+ """
667
+ x = polygon[:, 0]
668
+ y = polygon[:, 1]
669
+ area = (np.dot(x, np.roll(y, -1)) - np.dot(np.roll(x, -1), y))
670
+ return np.abs(area) / 2
671
+
672
+
673
+ def polygon_intersection(poly1, poly2):
674
+ """
675
+ Use the Sutherland-Hodgman algorithm to
676
+ compute the intersection of 2 convex polygons.
677
+ """
678
+ def line_intersection(e1, e2, s, e):
679
+ dc = e1 - e2
680
+ dp = s - e
681
+ n1 = np.cross(e1, e2)
682
+ n2 = np.cross(s, e)
683
+ n3 = 1.0 / (np.cross(dc, dp))
684
+ return np.array([(n1 * dp[0] - n2 * dc[0]) * n3,
685
+ (n1 * dp[1] - n2 * dc[1]) * n3])
686
+
687
+ def is_inside_edge(p, e1, e2):
688
+ """Return True if e is inside edge (e1, e2)"""
689
+ return np.cross(e2 - e1, p - e1) >= 0
690
+
691
+ output_list = poly1
692
+ # e1 and e2 are the edge vertices for each edge in the clipping polygon
693
+ e1 = poly2[-1]
694
+
695
+ for e2 in poly2:
696
+ # If there is no point of intersection
697
+ if len(output_list) == 0:
698
+ break
699
+
700
+ input_list = output_list
701
+ output_list = []
702
+ s = input_list[-1]
703
+
704
+ for e in input_list:
705
+ if is_inside_edge(e, e1, e2):
706
+ # if s in not inside edge (e1, e2)
707
+ if not is_inside_edge(s, e1, e2):
708
+ # line intersects edge hence we compute intersection point
709
+ output_list.append(line_intersection(e1, e2, s, e))
710
+ output_list.append(e)
711
+ # is s inside edge (e1, e2)
712
+ elif is_inside_edge(s, e1, e2):
713
+ output_list.append(line_intersection(e1, e2, s, e))
714
+
715
+ s = e
716
+ e1 = e2
717
+ return np.array(output_list)
@@ -0,0 +1,3 @@
1
+ from edgefirst.validator.publishers.tensorboard import TensorBoardPublisher
2
+ from edgefirst.validator.publishers.console import ConsolePublisher
3
+ from edgefirst.validator.publishers.studio import StudioPublisher