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,321 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from typing import TYPE_CHECKING, Any, Tuple
6
+
7
+ import numpy as np
8
+
9
+ from edgefirst.validator.runners.core import Runner
10
+
11
+ if TYPE_CHECKING:
12
+ from edgefirst.validator.evaluators import ModelParameters, TimerContext
13
+
14
+
15
+ class TensorRTRunner(Runner):
16
+ """
17
+ Loads and runs TensorRT Engines (.engine, .trt). These models
18
+ are intended to be deployed on a device with a dedicated GPU.
19
+ This implementation was taken from the following sources:
20
+ https://github.com/NVIDIA/TensorRT/blob/main/samples/python/efficientdet/infer.py
21
+ https://github.com/ultralytics/ultralytics/blob/main/ultralytics/nn/autobackend.py#L326
22
+
23
+ Parameters
24
+ ----------
25
+ model: Any
26
+ This is typically the path to the model or the loaded TensorRT model.
27
+ parameters: ModelParameters
28
+ These are the model parameters set from the command line.
29
+ metadata: dict
30
+ The model metadata which contains information for decoding
31
+ the model outputs.
32
+ timer: TimerContext
33
+ A timer object for handling validation timings for the model.
34
+
35
+ Raises
36
+ ------
37
+ ImportError
38
+ Missing tensorrt library.
39
+ FileNotFoundError
40
+ Raised if the path to the model does not exist.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ model: Any,
46
+ parameters: ModelParameters,
47
+ metadata: dict,
48
+ timer: TimerContext
49
+ ):
50
+ super(TensorRTRunner, self).__init__(model, parameters, timer=timer)
51
+
52
+ try:
53
+ import tensorrt as trt
54
+ except ImportError:
55
+ raise ImportError(
56
+ "tensorrt is needed to run TensorRT models.")
57
+ try:
58
+ import pycuda.driver as cuda # type: ignore
59
+ except ImportError:
60
+ raise ImportError(
61
+ "pycuda is needed to perform memory allocations for TensorRT.")
62
+
63
+ # Use autoprimaryctx if available (pycuda >= 2021.1) to
64
+ # prevent issues with other modules that rely on the primary
65
+ # device context.
66
+ try:
67
+ import pycuda.autoprimaryctx # type: ignore
68
+ except ModuleNotFoundError:
69
+ try:
70
+ import pycuda.autoinit # type: ignore
71
+ except ImportError:
72
+ raise ImportError(
73
+ "supported NVIDIA GPU device is needed.")
74
+
75
+ # TensorRT are intended to run on the GPU.
76
+ self.parameters.engine = "gpu"
77
+ self.logger = trt.Logger(trt.Logger.INFO)
78
+ trt.init_libnvinfer_plugins(self.logger, namespace="")
79
+
80
+ # Read file
81
+ if isinstance(model, str):
82
+ with open(model, "rb") as f, trt.Runtime(self.logger) as runtime:
83
+ assert runtime
84
+ try:
85
+ meta_len = int.from_bytes(
86
+ f.read(4), byteorder="little") # read metadata length
87
+ metadata = json.loads(
88
+ f.read(meta_len).decode("utf-8")) # read metadata
89
+ dla = metadata.get("dla", None)
90
+ if dla is not None:
91
+ runtime.DLA_core = int(dla)
92
+ except UnicodeDecodeError:
93
+ # engine file may lack embedded Ultralytics metadata
94
+ f.seek(0)
95
+ self.model = runtime.deserialize_cuda_engine(
96
+ f.read()) # read engine
97
+ assert self.model
98
+
99
+ self.context = self.model.create_execution_context()
100
+ assert self.context
101
+
102
+ self.output_names = []
103
+ self.output = []
104
+ self.input = []
105
+ self.allocations = []
106
+ self.scales = [] # Float values for dequantizing INT8 outputs.
107
+
108
+ num = range(self.model.num_bindings if hasattr(self.model, "num_bindings")
109
+ else self.model.num_io_tensors)
110
+
111
+ for i in num:
112
+ name = self.model.get_tensor_name(i)
113
+ dtype = np.dtype(trt.nptype(self.model.get_tensor_dtype(name)))
114
+ shape = tuple(self.context.get_tensor_shape(name))
115
+ is_input = self.model.get_tensor_mode(
116
+ name) == trt.TensorIOMode.INPUT
117
+ if is_input:
118
+ if -1 in tuple(self.model.get_tensor_shape(name)):
119
+ self.context.set_input_shape(
120
+ name, tuple(self.model.get_tensor_profile_shape(name, 0)[1]))
121
+ else:
122
+ self.output_names.append(name)
123
+
124
+ dyn_range = None
125
+ if hasattr(self.model, "get_tensor_dynamic_range"):
126
+ dyn_range = self.model.get_tensor_dynamic_range(i)
127
+ elif hasattr(self.model, "get_binding_dynamic_range"):
128
+ dyn_range = self.model.get_binding_dynamic_range(i)
129
+
130
+ if dyn_range and dyn_range > 0:
131
+ scale = float(dyn_range) / 127.0
132
+ self.scales.append(scale)
133
+
134
+ shape = tuple(self.context.get_tensor_shape(name))
135
+ size = dtype.itemsize
136
+ for s in shape:
137
+ size *= s
138
+ allocation = cuda.mem_alloc(size)
139
+ host_allocation = None if is_input else np.zeros(shape, dtype)
140
+
141
+ binding = {
142
+ "index": i,
143
+ "name": name,
144
+ "dtype": dtype,
145
+ "shape": list(shape),
146
+ "allocation": allocation,
147
+ "host_allocation": host_allocation,
148
+ }
149
+
150
+ self.allocations.append(allocation)
151
+ if is_input:
152
+ self.input.append(binding)
153
+ else:
154
+ self.output.append(binding)
155
+
156
+ assert len(self.input) > 0
157
+ assert len(self.output) > 0
158
+ assert len(self.allocations) > 0
159
+
160
+ outputs = [np.zeros(out["shape"], np.dtype(out["dtype"]))
161
+ for out in self.output]
162
+ self.init_decoder(metadata=metadata, outputs=outputs)
163
+
164
+ if self.parameters.warmup > 0:
165
+ self.warmup()
166
+
167
+ def infer(self, image: np.ndarray) -> list:
168
+ """
169
+ Executes inference on a batch of images.
170
+
171
+ Parameters
172
+ ----------
173
+ image: np.ndarray
174
+ The image input after being preprocessed.
175
+ Typically this is an RGB image array with the same
176
+ input shape as the model.
177
+
178
+ Returns
179
+ -------
180
+ list
181
+ Raw model outputs stored inside a list.
182
+
183
+ Raises
184
+ ------
185
+ ImportError
186
+ Raised if the pycuda library is not installed.
187
+ """
188
+ try:
189
+ import pycuda.driver as cuda # type: ignore
190
+ except ImportError:
191
+ raise ImportError(
192
+ "pycuda driver is needed for TensorRT inference.")
193
+
194
+ start = time.perf_counter()
195
+ # Copy I/O and Execute.
196
+ image = np.ascontiguousarray(image)
197
+ cuda.memcpy_htod(self.input[0]['allocation'], image)
198
+ elapsed = time.perf_counter() - start
199
+ # Setting the tensor as part of the preprocess time.
200
+ self.timer.add_time("input", elapsed * 1e3) # Convert to ms.
201
+
202
+ with self.timer.time("inference"):
203
+ self.context.execute_v2(self.allocations)
204
+
205
+ def run_single_instance(self, image: np.ndarray) -> Any:
206
+ """
207
+ Run TensorRT inference on a single image and record the timings.
208
+ Memory copying to and from the GPU device be performed here.
209
+
210
+ Parameters
211
+ ----------
212
+ image: np.ndarray
213
+ The input image after being preprocessed.
214
+ Typically this is an RGB image array.
215
+
216
+ Returns
217
+ -------
218
+ Any
219
+ This could either return detection outputs after NMS.
220
+ np.ndarray
221
+ The prediction bounding boxes.. [[box1], [box2], ...].
222
+ np.ndarray
223
+ The prediction labels.. [cl1, cl2, ...].
224
+ np.ndarray
225
+ The prediction confidence scores.. [score, score, ...]
226
+ normalized between 0 and 1.
227
+ This could also return segmentation masks.
228
+ np.ndarray
229
+ """
230
+ try:
231
+ import pycuda.driver as cuda # type: ignore
232
+ except ImportError:
233
+ raise ImportError(
234
+ "pycuda driver is needed for TensorRT inference.")
235
+
236
+ # Inference
237
+ self.infer(image)
238
+
239
+ start = time.perf_counter()
240
+ for o in range(len(self.output)):
241
+ cuda.memcpy_dtoh(
242
+ self.output[o]['host_allocation'],
243
+ self.output[o]['allocation'])
244
+
245
+ # Dequantize INT8 outputs if engine provides dynamic ranges.
246
+ if len(self.scales):
247
+ outputs = []
248
+ for i, out in enumerate(self.output):
249
+ o = out['host_allocation']
250
+ # only handle integer types here
251
+ if o.dtype in [np.int8, np.uint8]:
252
+ o = o.astype(np.float32) * self.scales[i]
253
+ outputs.append(o)
254
+ else:
255
+ outputs = [o['host_allocation'] for o in self.output]
256
+ elapsed = time.perf_counter() - start
257
+
258
+ # Postprocessing
259
+ outputs = self.postprocessing(outputs)
260
+
261
+ # Fetching the tensor as part of the postprocess time.
262
+ self.timer.add_time("output", elapsed * 1e3) # Convert to ms.
263
+
264
+ return outputs
265
+
266
+ def input_spec(self) -> Tuple[tuple, np.dtype]:
267
+ """
268
+ Grabs the specs for the input tensor
269
+ of the network. Useful to prepare memory allocations.
270
+
271
+ Returns
272
+ -------
273
+ shape: tuple
274
+ The shape of the input tensor.
275
+ dtype: np.dtype
276
+ The input datatype.
277
+ """
278
+ return self.input[0]['shape'], self.input[0]['dtype']
279
+
280
+ def output_spec(self) -> list:
281
+ """
282
+ Grabs the specs for the output tensors of the network.
283
+ Useful to prepare memory allocations.
284
+
285
+ Returns
286
+ -------
287
+ list
288
+ A list with two items per element, the shape and (numpy)
289
+ datatype of each output tensor.
290
+ """
291
+ specs = list()
292
+ for o in self.output:
293
+ specs.append((o['shape'], o['dtype']))
294
+ return specs
295
+
296
+ def get_input_type(self) -> np.dtype:
297
+ """
298
+ This returns the input type of the model for the
299
+ input with shape in the form
300
+ (batch size, channels, height, width) or
301
+ (batch size, height, width, channels).
302
+
303
+ Returns
304
+ -------
305
+ np.dtype
306
+ The input type of the model.
307
+ """
308
+ return self.input[0]['dtype']
309
+
310
+ def get_input_shape(self) -> list:
311
+ """
312
+ This fetches the model input shape.
313
+
314
+ Returns
315
+ -------
316
+ list
317
+ The model input shape
318
+ [batch size, channels, height, width] or
319
+ [batch size, height, width, channels].
320
+ """
321
+ return self.input[0]['shape']
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import time
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import numpy as np
8
+
9
+ from edgefirst.validator.publishers.utils.logger import logger
10
+ from edgefirst.validator.runners.core import Runner
11
+
12
+ if TYPE_CHECKING:
13
+ from edgefirst.validator.evaluators import ModelParameters, TimerContext
14
+
15
+
16
+ class TFliteRunner(Runner):
17
+ """
18
+ Loads and runs TensorFlow Lite models for inference.
19
+
20
+ Parameters
21
+ ----------
22
+ model: Any
23
+ The is typically the path to the model or the loaded TFLite model.
24
+ parameters: ModelParameters
25
+ These are the model parameters set from the command line.
26
+ metadata: dict
27
+ The model metadata which contains information for decoding
28
+ the model outputs.
29
+ timer: TimerContext
30
+ A timer object for handling validation timings for the model.
31
+
32
+ Raises
33
+ ------
34
+ ImportError
35
+ Raised if tflite_runtime and TensorFlow is not intalled.
36
+ FileNotFoundError
37
+ Raised if the path to the model does not exist.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ model: Any,
43
+ parameters: ModelParameters,
44
+ metadata: dict,
45
+ timer: TimerContext
46
+ ):
47
+ super(TFliteRunner, self).__init__(model, parameters, timer=timer)
48
+
49
+ try:
50
+ from tflite_runtime.interpreter import Interpreter # type: ignore
51
+ except ImportError:
52
+ logger("tflite_runtime is not installed. Falling back to TensorFlow.",
53
+ code="WARNING")
54
+ try:
55
+ import tensorflow as tf # type: ignore
56
+ Interpreter = tf.lite.Interpreter
57
+ except ImportError:
58
+ raise ImportError(
59
+ "TensorFlow or tflite_runtime is needed to run TFLite models.")
60
+
61
+ if isinstance(model, str):
62
+ if not os.path.exists(model):
63
+ raise FileNotFoundError(
64
+ "The model '{}' does not exist.".format(model))
65
+
66
+ ext_delegate = self.select_delegates()
67
+ logger(f"Engine: {self.parameters.engine}", code="INFO")
68
+
69
+ if ext_delegate:
70
+ self.model = Interpreter(
71
+ model_path=model,
72
+ experimental_delegates=[ext_delegate]
73
+ )
74
+ else:
75
+ self.model = Interpreter(model_path=model)
76
+
77
+ self.model.allocate_tensors()
78
+
79
+ self.input_details = self.model.get_input_details()
80
+ self.output_details = self.model.get_output_details()
81
+ self.parameters.common.input_quantization = self.input_details[0][
82
+ "quantization"]
83
+ self.init_decoder(metadata=metadata, outputs=self.output_details)
84
+
85
+ # The model NumPy view object.
86
+ self.parameters.common.input_tensor = self.model.tensor(
87
+ self.input_details[0]["index"])
88
+
89
+ if self.parameters.warmup > 0:
90
+ self.warmup()
91
+
92
+ def select_delegates(self) -> Any:
93
+ """
94
+ Specify the delegates to load based on
95
+ the type of engine specified.
96
+
97
+ Returns
98
+ -------
99
+ Any
100
+ This is either the loaded delegate object or None
101
+ if it doesn't exist.
102
+ """
103
+ try: # https://coral.ai/docs/edgetpu/tflite-python/#update-existing-tf-lite-code-for-the-edge-tpu
104
+ from tflite_runtime.interpreter import load_delegate # type: ignore
105
+ except ImportError:
106
+ try:
107
+ import tensorflow as tf # type: ignore
108
+ load_delegate = tf.lite.experimental.load_delegate
109
+ except ImportError:
110
+ raise ImportError(
111
+ "TensorFlow or tflite_runtime is needed to run TFLite models.")
112
+
113
+ ext_delegate = None
114
+ if (os.path.exists(self.parameters.engine) and
115
+ self.parameters.engine.endswith(".so")):
116
+ ext_delegate = load_delegate(self.parameters.engine, {})
117
+ elif self.parameters.engine.lower() == "npu":
118
+ if os.path.exists("/usr/lib/libvx_delegate.so"):
119
+ self.parameters.engine = "/usr/lib/libvx_delegate.so"
120
+ ext_delegate = load_delegate(self.parameters.engine, {})
121
+ logger("Using '/usr/lib/libvx_delegate.so' for NPU inference.",
122
+ code="INFO")
123
+ elif os.path.exists("/usr/lib/libneutron_delegate.so"):
124
+ self.parameters.engine = "/usr/lib/libneutron_delegate.so"
125
+ ext_delegate = load_delegate(self.parameters.engine, {})
126
+ logger("Using '/usr/lib/libneutron_delegate.so' for NPU inference.",
127
+ code="INFO")
128
+ else:
129
+ logger(
130
+ "Specified NPU, but cannot find '/usr/lib/lib<>_delegate.so'. " +
131
+ "Specify the path to libvx_delegate.so or libneutron_delegate.so " +
132
+ "in your system. Falling back to use the CPU instead.", code="WARNING")
133
+ self.parameters.engine = "cpu"
134
+ elif self.parameters.engine.lower() == "gpu":
135
+ logger(
136
+ "Inference with the GPU is currently not supported for TFLite. " +
137
+ "Falling back to use the CPU instead.", code="WARNING")
138
+ self.parameters.engine = "cpu"
139
+ return ext_delegate
140
+
141
+ def run_single_instance(self, image: np.ndarray = None) -> Any:
142
+ """
143
+ Run TFLite inference on a single image and record the timings.
144
+
145
+ Parameters
146
+ ----------
147
+ image: np.ndarray
148
+ The input image after being preprocessed.
149
+ Typically this is an RGB image array. This is by default None.
150
+ Currently setting the image tensor using the logic below.
151
+
152
+ .. code-block:: python
153
+
154
+ input_tensor = self.model.tensor(self.input_details[0]["index"])
155
+ np.copyto(input_tensor(), image)
156
+
157
+ Returns
158
+ -------
159
+ Any
160
+ This could either return detection outputs after NMS.
161
+ np.ndarray
162
+ The prediction bounding boxes.. [[box1], [box2], ...].
163
+ np.ndarray
164
+ The prediction labels.. [cl1, cl2, ...].
165
+ np.ndarray
166
+ The prediction confidence scores.. [score, score, ...]
167
+ normalized between 0 and 1.
168
+ This could also return segmentation masks.
169
+ np.ndarray
170
+ """
171
+
172
+ # Inference
173
+ with self.timer.time("inference"):
174
+ self.model.invoke()
175
+
176
+ start = time.perf_counter()
177
+ outputs = [self.model.get_tensor(output["index"])
178
+ for output in self.output_details]
179
+ elapsed = time.perf_counter() - start
180
+
181
+ # Postprocessing
182
+ outputs = self.postprocessing(outputs)
183
+
184
+ # Fetching the tensor as part of the postprocess time.
185
+ self.timer.add_time("output", elapsed * 1e3) # Convert to ms.
186
+
187
+ return outputs
188
+
189
+ def get_input_type(self) -> np.dtype:
190
+ """
191
+ This returns the input type of the model with shape
192
+ (batch size, channels, height, width) or
193
+ (batch size, height, width, channels).
194
+
195
+ Returns
196
+ -------
197
+ np.dtype
198
+ The input type of the model.
199
+ """
200
+ return self.input_details[0]["dtype"]
201
+
202
+ def get_input_shape(self) -> np.ndarray:
203
+ """
204
+ Grabs the model input shape.
205
+
206
+ Returns
207
+ -------
208
+ np.ndarray
209
+ The model input shape (batch size, channels, height, width) or
210
+ (batch size, height, width, channels).
211
+ """
212
+ for input in self.input_details:
213
+ shape = input["shape"]
214
+ # Detection shapes are in the form (batch size, height, width,
215
+ # channels=3).
216
+ if len(shape) == 4:
217
+ if shape[1] == 3 or shape[-1] == [3]:
218
+ return shape
219
+ # If it does not conform with expected format, return the first
220
+ # element.
221
+ return self.input_details[0]["shape"]