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,425 @@
1
+ """
2
+ This module contains functions for reading various dataset files.
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import warnings
8
+
9
+ import numpy as np
10
+ from PIL import Image, ImageFile
11
+
12
+ from edgefirst.validator.publishers.utils.logger import logger
13
+
14
+ ImageFile.LOAD_TRUNCATED_IMAGES = True
15
+
16
+
17
+ def read_image(image_path: str, rotate: bool = False) -> np.ndarray:
18
+ """
19
+ Opens the image using pillow.Image and if the image is neither in the
20
+ format: [RGB, RGBA, CMYK, YCVCr] then the image will be converted to RGB.
21
+
22
+ Parameters
23
+ ----------
24
+ image_path: str
25
+ The path to the image to read.
26
+ rotate: bool
27
+ If set to True, read from the image EXIF and
28
+ apply the rotation specified.
29
+
30
+ Returns
31
+ -------
32
+ np.ndarray
33
+ The image represented as a numpy array.
34
+ """
35
+ if rotate:
36
+ from edgefirst.validator.datasets.utils.transformations import rotate_image
37
+ image = rotate_image(image_path)
38
+ else:
39
+ image = Image.open(image_path)
40
+ if image.mode in ["RGB", "RGBA", "CMYK", "YCbCr"]:
41
+ image = np.asarray(image)
42
+ else:
43
+ image.convert("RGB")
44
+ image = np.asarray(image, dtype=np.uint8)
45
+ image = np.stack((image,) * 3, axis=-1)
46
+ return image
47
+
48
+
49
+ def read_labels_file(file_path: str) -> list:
50
+ """
51
+ Opens a text file containing the string labels from
52
+ either the dataset or the model. It returns the list of string labels,
53
+ as the contents in the text file.
54
+
55
+ Parameters
56
+ ----------
57
+ file_path: str
58
+ The path to the labels.txt file.
59
+
60
+ Returns
61
+ -------
62
+ list
63
+ The list of string labels as the contents of the text
64
+ file with a string label per line.
65
+ """
66
+ with open(file_path) as file:
67
+ labels = [line.rstrip()
68
+ for line in file.readlines()
69
+ if line not in ["\n", "", "\t"]]
70
+ return labels
71
+
72
+
73
+ def read_yaml_file(file_path: str) -> dict:
74
+ """
75
+ Reads YAML files with Au-Zone specific format
76
+ for collecting dataset information.
77
+
78
+ Parameters
79
+ ----------
80
+ file_path: str
81
+ The path to the YAML file.
82
+
83
+ Returns
84
+ -------
85
+ dict
86
+ Stores the YAML file contents.
87
+
88
+ Raises
89
+ ------
90
+ FileNotFoundError
91
+ Raised if the path to the file does not exist.
92
+ """
93
+ import yaml
94
+ if not os.path.exists(file_path):
95
+ raise FileNotFoundError(f"YAML file not found: '{file_path}'")
96
+ with open(file_path) as file:
97
+ return yaml.full_load(file)
98
+
99
+
100
+ def read_toml_file(file_path: str) -> dict:
101
+ """
102
+ Reads TOML files using tomli.
103
+
104
+ Parameters
105
+ ----------
106
+ file_path: str
107
+ The path to the TOML file.
108
+
109
+ Returns
110
+ -------
111
+ dict
112
+ The contents of the TOML file.
113
+ """
114
+ import tomli
115
+ if not os.path.exists(file_path):
116
+ raise FileNotFoundError(f"TOML file not found: '{file_path}'")
117
+ with open(file_path, "rb") as f:
118
+ return tomli.load(f)
119
+
120
+
121
+ def read_npy_file(annotation_path: str) -> np.ndarray:
122
+ """
123
+ Reads NumPy files which typically contains radar data.
124
+
125
+ Parameters
126
+ ----------
127
+ annotation_path: str
128
+ The path to the NumPy file.
129
+
130
+ Returns
131
+ -------
132
+ np.ndarray
133
+ Radar cube with shape (seq, range, rx, doppler, complex).
134
+ """
135
+ try:
136
+ return np.load(annotation_path)
137
+ except (FileNotFoundError, TypeError):
138
+ return np.array([])
139
+
140
+
141
+ def read_detection_text_file(
142
+ annotation_path: str,
143
+ label_offset: int = 0,
144
+ shape: tuple = None,
145
+ normalizer=None,
146
+ transformer=None
147
+ ) -> dict:
148
+ """
149
+ Reads the text file annotation to retrieve the
150
+ ground truth bounding boxes and the labels.
151
+
152
+ Parameters
153
+ ----------
154
+ annotation_path: str
155
+ This is the path to the text file annotation.
156
+ label_offset: int
157
+ Used to offset the label indices by a specified amount.
158
+ shape: tuple
159
+ The (height, width) image dimensions for
160
+ normalizing the bounding boxes (optional).
161
+ normalizer: Function
162
+ Normalizes bounding boxes.
163
+ transformer: Function
164
+ Transforms bounding boxes to a different format.
165
+
166
+ Returns
167
+ -------
168
+ dict
169
+ This contains information such as boxes and labels.
170
+
171
+ .. code-block:: python
172
+
173
+ {
174
+ 'boxes': list of bounding boxes,
175
+ 'labels': list of labels
176
+ }
177
+ """
178
+ annotations = {
179
+ "boxes": np.array([], dtype=np.float32),
180
+ "labels": np.array([]).astype(np.int32)
181
+ }
182
+
183
+ try:
184
+ with warnings.catch_warnings():
185
+ warnings.simplefilter("ignore")
186
+ annotation = np.genfromtxt(annotation_path)
187
+ except (FileNotFoundError, TypeError):
188
+ return annotations
189
+
190
+ if len(annotation) > 0:
191
+ annotation = annotation.reshape(-1, 5)
192
+ boxes = annotation[:, 1:5]
193
+ boxes = normalizer(boxes, shape) if normalizer else boxes
194
+ boxes = transformer(boxes) if transformer else boxes
195
+ annotations["boxes"] = boxes
196
+ else:
197
+ return annotations
198
+
199
+ labels = annotation[:, 0:1].flatten().astype(np.int32) + label_offset
200
+ annotations["labels"] = labels
201
+ return annotations
202
+
203
+
204
+ def read_segmentation_text_file(
205
+ annotation_path: str,
206
+ label_offset: int = 0,
207
+ shape: tuple = None,
208
+ normalizer=None,
209
+ transformer=None,
210
+ resample: int = 1000
211
+ ) -> dict:
212
+ """
213
+ Reads a segmentation annotation file and converts it to bounding boxes.
214
+ Source: https://github.com/ultralytics/ultralytics/blob/main/ultralytics/data/utils.py#L180
215
+
216
+ Parameters
217
+ ----------
218
+ annotation_path : str
219
+ Path to the segmentation annotation text file.
220
+ Assumes annotation file is in Ultralytics YOLO segment format.
221
+ label_offset: int
222
+ Used to offset the label indices by a specified amount.
223
+ shape: tuple
224
+ The (height, width) image dimensions for
225
+ normalizing the bounding boxes (optional).
226
+ normalizer: Function
227
+ Normalizes bounding boxes.
228
+ transformer: Function
229
+ Transforms bounding boxes to a different format.
230
+ resample: int
231
+ The number of points to resample the segments.
232
+
233
+ Returns
234
+ -------
235
+ dict
236
+ A dictionary with the keys "boxes", "labels", "segments"
237
+ storing the boxes in the shape (n, 4) -> [x, y, x, y], the labels
238
+ of each box, and the segment coordinates (n, 2) -> [x, y].
239
+ """
240
+ from edgefirst.validator.datasets.utils.transformations import (segments2boxes,
241
+ resample_segments)
242
+ annotations = {
243
+ "boxes": np.array([], dtype=np.float32),
244
+ "labels": np.array([], dtype=np.int32),
245
+ "segments": np.array([], dtype=np.float32)
246
+ }
247
+
248
+ if annotation_path is None:
249
+ return annotations
250
+
251
+ segments = []
252
+
253
+ with open(annotation_path, encoding="utf-8") as f:
254
+ lb = [x.split() for x in f.read().strip().splitlines() if len(x)]
255
+ if any(len(x) > 6 for x in lb): # is segment
256
+ classes = np.array([x[0] for x in lb], dtype=np.float32)
257
+ segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2)
258
+ for x in lb] # (cls, xy1...)
259
+ lb = np.concatenate(
260
+ (classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh)
261
+ lb = np.array(lb, dtype=np.float32)
262
+ lb[..., 1:] = normalizer(
263
+ lb[..., 1:], shape) if normalizer else lb[..., 1:]
264
+ lb[..., 1:] = transformer(lb[..., 1:]) if transformer else lb[..., 1:]
265
+
266
+ # Segments are being resampled.
267
+ # https://github.com/ultralytics/ultralytics/blob/main/ultralytics/data/dataset.py#L274
268
+ # NOTE: do NOT resample oriented boxes.
269
+ if len(segments) > 0:
270
+ # make sure segments interpolate correctly if
271
+ # original length is greater than resample.
272
+ max_len = max(len(s) for s in segments)
273
+ resample = (max_len + 1) if resample < max_len else resample
274
+ # list[np.array(resample, 2)] * num_samples
275
+ segments = np.stack(resample_segments(segments, n=resample), axis=0)
276
+ # Denormalize segments.
277
+ segments[..., 0] *= shape[1]
278
+ segments[..., 1] *= shape[0]
279
+ else:
280
+ segments = np.zeros((0, resample, 2), dtype=np.float32)
281
+
282
+ annotations["boxes"] = lb[..., 1:]
283
+ annotations["labels"] = lb[..., 0] + label_offset
284
+ annotations["segments"] = segments
285
+ return annotations
286
+
287
+
288
+ def read_detection_json_file(
289
+ annotation_path: str,
290
+ label_offset: int = 0,
291
+ shape: tuple = None,
292
+ normalizer=None,
293
+ transformer=None
294
+ ) -> dict:
295
+ """
296
+ Reads from the JSON annotation to retrieve the
297
+ ground truth detection bounding boxes and labels.
298
+
299
+ Parameters
300
+ ----------
301
+ annotation_path: str
302
+ This is the path to the JSON annotation.
303
+ label_offset: int
304
+ Used to offset the label indices by a specified amount.
305
+ shape: tuple
306
+ The (height, width) image dimensions for
307
+ normalizing the bounding boxes (optional).
308
+ normalizer: Function
309
+ Normalizes bounding boxes.
310
+ transformer: Function
311
+ Transforms bounding boxes to a different format.
312
+
313
+ Returns
314
+ -------
315
+ dict
316
+ This contains information such as boxes and labels.
317
+
318
+ .. code-block:: python
319
+
320
+ {
321
+ 'boxes': list of bounding boxes,
322
+ 'labels': list of labels
323
+ }
324
+ """
325
+ annotations = {
326
+ "boxes": np.array([]),
327
+ "labels": np.array([]).astype(np.int32)
328
+ }
329
+
330
+ try:
331
+ with open(annotation_path) as file:
332
+ data: dict = json.load(file)
333
+
334
+ annotation = np.array(data.get("boxes"))
335
+ annotation = normalizer(
336
+ annotation, shape) if normalizer else annotation
337
+ boxes = transformer(
338
+ annotation[:, 0:5]) if transformer else annotation[:, 0:5]
339
+ labels = data.get("labels") + label_offset
340
+
341
+ # TypeError is due to the annotation path being None.
342
+ except (FileNotFoundError, TypeError, KeyError):
343
+ return annotations
344
+
345
+ annotations["boxes"] = boxes
346
+ annotations["labels"] = labels
347
+ return annotations
348
+
349
+
350
+ def read_segmentation_json_file(
351
+ annotation_path: str,
352
+ shape: tuple,
353
+ label_offset: int = 0,
354
+ denormalizer=None,
355
+ ) -> dict:
356
+ """
357
+ Reads from a JSON annotation file to retrieve segmentation polygons
358
+ such as multiple (x,y) coordinates around an object to be segmented.
359
+
360
+ Parameters
361
+ ----------
362
+ annotation_path: str
363
+ This is the path to the JSON annotation.
364
+ shape: tuple
365
+ This contains the image (height, width) dimensions
366
+ for denormalizing the polygon.
367
+ label_offset: int
368
+ The integer to offset the label indices which
369
+ is used for integer to string mapping for the labels.
370
+ denormalizer: Function
371
+ Denormalizes segmentation coordinates.
372
+
373
+ Returns
374
+ -------
375
+ dict
376
+ This contains segmentation information.
377
+
378
+ .. code-block:: python
379
+
380
+ {
381
+ 'segments': list of polygon segments
382
+ [[[x,y], [x,y], ...]...],
383
+ 'labels': list of labels
384
+ }
385
+ """
386
+ annotations = {
387
+ "segments": np.array([]),
388
+ "labels": np.array([]).astype(np.int32)
389
+ }
390
+
391
+ try:
392
+ with open(annotation_path) as file:
393
+ data = json.load(file)
394
+
395
+ segments, labels = list(), list()
396
+ for segment in data["segment"]:
397
+ for polygon in segment:
398
+ cls = polygon["class"]
399
+ poly = polygon["polygon"]
400
+ # label_offset should be 1 if there is a background class.
401
+ labels.append(cls + label_offset)
402
+ # a list of vertices
403
+ x_y = []
404
+ for vertex in poly:
405
+ vertex = denormalizer(vertex, shape[0], shape[1]) \
406
+ if denormalizer else vertex
407
+ x_y.append(float(vertex[0]))
408
+ x_y.append(float(vertex[1]))
409
+ segments.append(x_y)
410
+
411
+ except UnicodeDecodeError:
412
+ logger(
413
+ f"Encountered UnicodeDecodeError for '{annotation_path}'. " +
414
+ "Returning an empty ground truth schema for this image. ",
415
+ code="WARNING"
416
+ )
417
+ return annotations
418
+
419
+ # TypeError is due to the annotation path being None.
420
+ except (FileNotFoundError, TypeError, KeyError):
421
+ return annotations
422
+
423
+ annotations["segments"] = segments
424
+ annotations["labels"] = labels
425
+ return annotations