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,265 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import time
5
+ import threading
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ import numpy as np
9
+
10
+ from edgefirst.validator.runners.processing.decode import dequantize_kinara
11
+ from edgefirst.validator.runners.core import Runner
12
+
13
+ if TYPE_CHECKING:
14
+ from edgefirst.validator.evaluators import ModelParameters, TimerContext
15
+
16
+
17
+ class KinaraRunner(Runner):
18
+ """
19
+ Loads and runs Kinara models for inference.
20
+
21
+ Parameters
22
+ ----------
23
+ model: Any
24
+ This is typically the path to the model (.dvm)
25
+ or the loaded Kinara model.
26
+ parameters: ModelParameters
27
+ These are the model parameters set from the command line.
28
+ metadata: dict
29
+ The model metadata which contains information for decoding
30
+ the model outputs.
31
+ timer: TimerContext
32
+ A timer object for handling validation timings for the model.
33
+ socket_path : str, optional
34
+ Path to the UNIX socket for DVSession.
35
+
36
+ Raises
37
+ ------
38
+ ImportError
39
+ Missing Kinara library.
40
+ FileNotFoundError
41
+ Raised if the path to the model does not exist.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ model: Any,
47
+ parameters: ModelParameters,
48
+ metadata: dict,
49
+ timer: TimerContext,
50
+ socket_path: str = "/var/run/ara2.sock"
51
+ ):
52
+ super(KinaraRunner, self).__init__(model, parameters, timer)
53
+
54
+ self.socket_path = socket_path
55
+ self.conn = None
56
+ self.endpoints = None
57
+ self.loaded_model = None
58
+ self._lock = threading.Lock()
59
+ self.start()
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
+ self.load_model(model)
66
+
67
+ self.input_param = self.model.input_param[0]
68
+ self.preprocess_param = self.input_param.preprocess_param
69
+ self.parameters.common.input_quantization = (
70
+ self.preprocess_param.qn, self.preprocess_param.offset)
71
+
72
+ outputs = []
73
+ for output_param in self.model.output_param:
74
+ bpp = output_param.bpp
75
+ dtype = np.int8
76
+ if bpp == 1:
77
+ dtype = (np.int8 if output_param.postprocess_param.is_signed
78
+ else np.uint8)
79
+ elif bpp == 2:
80
+ dtype = (np.int16 if output_param.postprocess_param.is_signed
81
+ else np.uint16)
82
+ elif bpp == 4:
83
+ dtype = (np.int32 if output_param.postprocess_param.is_signed
84
+ else np.uint32)
85
+
86
+ outputs.append(
87
+ {
88
+ "shape": np.array([1, output_param.nch,
89
+ output_param.height]),
90
+ "quantization": [output_param.postprocess_param.qn,
91
+ output_param.postprocess_param.offset],
92
+ "channels_first": False,
93
+ "dtype": dtype
94
+ }
95
+ )
96
+
97
+ self.init_decoder(metadata=metadata, outputs=outputs)
98
+ if self.parameters.warmup > 0:
99
+ self.warmup()
100
+
101
+ def start(self):
102
+ """
103
+ Start DVSession and fetch available endpoints.
104
+
105
+ Raises
106
+ ------
107
+ RuntimeError
108
+ If session creation or endpoint retrieval fails.
109
+ """
110
+ from edgefirst.validator.runners.processing.dvapi import DVSession
111
+
112
+ ret, self.conn = DVSession.create_via_unix_socket(self.socket_path)
113
+ if ret != 0:
114
+ raise RuntimeError("Failed to create DVSession")
115
+ ret, self.endpoints = self.conn.get_endpoint_list()
116
+ if ret != 0:
117
+ raise RuntimeError("Failed to get endpoints")
118
+
119
+ def load_model(self, model_path: str):
120
+ """
121
+ Load model from file into DVM endpoint.
122
+
123
+ Parameters
124
+ ----------
125
+ model_path : str
126
+ Path to the model file to load.
127
+
128
+ Raises
129
+ ------
130
+ RuntimeError
131
+ If model loading fails.
132
+ """
133
+ with self._lock:
134
+ ret, self.model = self.conn.load_model_from_file(
135
+ endpoint=self.endpoints[0], model_path=model_path)
136
+ if ret != 0:
137
+ raise RuntimeError("Failed to load model")
138
+
139
+ def infer(self, input_tensor, timeout: int = 50000):
140
+ """
141
+ Run synchronous inference on input tensor.
142
+
143
+ Parameters
144
+ ----------
145
+ image : DVTensor
146
+ Input tensor for inference.
147
+ timeout : int, optional
148
+ Timeout in milliseconds for inference.
149
+
150
+ Returns
151
+ -------
152
+ List[DVTensor]
153
+ Inference response outputs from the model.
154
+
155
+ Raises
156
+ ------
157
+ RuntimeError
158
+ If inference execution fails.
159
+ """
160
+ # thread-safe single-call wrapper
161
+ with self._lock:
162
+ ret, response = self.model.infer_sync(
163
+ [input_tensor], timeout=timeout, endpoint=self.endpoints[0])
164
+ if ret != 0:
165
+ raise RuntimeError("Inference failed")
166
+ return response
167
+
168
+ def run_single_instance(self, image: np.ndarray) -> Any:
169
+ """
170
+ Run Kinara inference on a single image and record the timings.
171
+
172
+ Parameters
173
+ ----------
174
+ image: np.ndarray
175
+ The input image after being preprocessed.
176
+ Typically this is an RGB image array.
177
+
178
+ Returns
179
+ -------
180
+ Any
181
+ This could either return detection outputs after NMS.
182
+ np.ndarray
183
+ The prediction bounding boxes.. [[box1], [box2], ...].
184
+ np.ndarray
185
+ The prediction labels.. [cl1, cl2, ...].
186
+ np.ndarray
187
+ The prediction confidence scores.. [score, score, ...]
188
+ normalized between 0 and 1.
189
+ This could also return segmentation masks.
190
+ np.ndarray
191
+ """
192
+ from edgefirst.validator.runners.processing.dvapi import DVTensor
193
+
194
+ start = time.perf_counter()
195
+ input_tensor = DVTensor(image.flatten(), self.input_param)
196
+ elapsed = time.perf_counter() - start
197
+ # Converting the tensor as part of the preprocess time.
198
+ self.timer.add_time("input", elapsed * 1e3) # Convert to ms.
199
+
200
+ # Inference
201
+ with self.timer.time("inference"):
202
+ response = self.infer(input_tensor)
203
+
204
+ start = time.perf_counter()
205
+ output = dequantize_kinara(
206
+ response.get_output_tensors(),
207
+ method=self.parameters.nms)
208
+
209
+ outputs = []
210
+ for meta in self.outputs.metadata["outputs"]:
211
+ outputs.append(
212
+ output[meta["index"]].reshape(meta["shape"])
213
+ )
214
+ elapsed = time.perf_counter() - start
215
+
216
+ # Postprocessing
217
+ outputs = self.postprocessing(outputs)
218
+
219
+ # Fetching the tensor as part of the postprocess time.
220
+ self.timer.add_time("output", elapsed * 1e3) # Convert to ms.
221
+
222
+ return outputs
223
+
224
+ def stop(self):
225
+ """
226
+ Close DVSession connection gracefully.
227
+ Attempts to close the session and suppresses any exceptions.
228
+ """
229
+ try:
230
+ self.conn.close()
231
+ except Exception:
232
+ try:
233
+ self.conn.__exit__(None, None, None)
234
+ except Exception:
235
+ pass
236
+
237
+ def get_input_type(self):
238
+ """
239
+ This returns the input type of the model. Kinara models
240
+ are always quantized in either INT8 or UINT8 datatypes.
241
+
242
+ Returns
243
+ -------
244
+ np.dtype
245
+ The input type of the model.
246
+ """
247
+ if self.preprocess_param.is_signed:
248
+ return np.int8
249
+ return np.uint8
250
+
251
+ def get_input_shape(self):
252
+ """
253
+ This fetches the model input shape. Kinara models are
254
+ always channels first.
255
+
256
+ Returns
257
+ -------
258
+ np.ndarray
259
+ The model input shape (batch size, channels, height, width).
260
+ """
261
+ input_shape = (self.input_param.batch_size,
262
+ self.input_param.nch,
263
+ self.input_param.height,
264
+ self.input_param.width)
265
+ return input_shape
@@ -0,0 +1,228 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import json
5
+ import warnings
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ import numpy as np
9
+
10
+ from edgefirst.validator.datasets.utils.transformations import (xcycwh2xyxy,
11
+ xywh2xyxy)
12
+ from edgefirst.validator.datasets.utils.fetch import get_shape
13
+ from edgefirst.validator.runners.core import Runner
14
+
15
+ if TYPE_CHECKING:
16
+ from edgefirst.validator.evaluators import ModelParameters, TimerContext
17
+
18
+
19
+ class OfflineRunner(Runner):
20
+ """
21
+ This class reads model detection annotations stored in text files
22
+ that are in YOLO format. For more information on the YOLO format visit:
23
+ https://support.deepviewml.com/hc/en-us/articles/10869801702029
24
+
25
+ *Note: These text files should also include the model prediction scores
26
+ which adds to the YOLO format: [cls score xc yc width height]*
27
+
28
+ Use Case: PT models are ran using https://github.com/ultralytics/yolov5
29
+ repository. These model predictions will be stored in TXT files that
30
+ are in YOLO format. This class will read the text files to be validated.
31
+
32
+ Parameters
33
+ ----------
34
+ annotation_source: str
35
+ This is the path to the model prediction annotations
36
+ stored in text files with YOLO format annotations.
37
+ [cls score xc yc width height].
38
+ parameters: ModelParameters
39
+ These are the model parameters set from the command line.
40
+ annotation_extension: str
41
+ This represents the extension of the files that store
42
+ the prediction annotations. Only text files is supported
43
+ at the moment.
44
+ timer: TimerContext
45
+ A timer object for handling validation timings for the model.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ annotation_source: str,
51
+ parameters: ModelParameters,
52
+ timer: TimerContext,
53
+ annotation_extension='txt'
54
+ ):
55
+ super(OfflineRunner, self).__init__(
56
+ annotation_source, parameters, timer=timer)
57
+
58
+ self.annotation_extension = annotation_extension
59
+ if self.parameters.box_format not in ['xcycwh', 'xywh', 'xyxy']:
60
+ raise ValueError(
61
+ f"Unknown annotation format provided {self.parameters.box_format}.")
62
+
63
+ self.transformer = None
64
+ if self.parameters.box_format == 'xcycwh':
65
+ self.transformer = xcycwh2xyxy
66
+ elif self.parameters.box_format == 'xywh':
67
+ self.transformer = xywh2xyxy
68
+ else:
69
+ self.transformer = None
70
+
71
+ self.parameters.common.with_boxes = True
72
+ self.parameters.common.with_masks = False
73
+
74
+ self.__timings = {
75
+ 'min_read_time': 0,
76
+ 'max_read_time': 0,
77
+ 'min_load_time': 0,
78
+ 'max_load_time': 0,
79
+ 'min_backbone_time': 0,
80
+ 'max_backbone_time': 0,
81
+ 'min_decode_time': 0,
82
+ 'max_decode_time': 0,
83
+ 'min_box_time': 0,
84
+ 'max_box_time': 0,
85
+ 'avg_read_time': 0,
86
+ 'avg_load_time': 0,
87
+ 'avg_backbone_time': 0,
88
+ 'avg_decode_time': 0,
89
+ 'avg_box_time': 0,
90
+ }
91
+ timings_path = os.path.join(annotation_source, "timings.json")
92
+ if os.path.exists(timings_path):
93
+ timings = {}
94
+ with open(timings_path) as file:
95
+ timings: dict = json.load(file)
96
+ self.parameters.engine = timings.get("engine", "cpu")
97
+ self.parameters.max_detections = timings.get("max_boxes", None)
98
+ self.parameters.iou_threshold = timings.get("iou", None)
99
+ self.parameters.score_threshold = timings.get("threshold", None)
100
+ self.parameters.common.shape = timings.get(
101
+ "shape", (1, 640, 640, 3))
102
+ self.parameters.common.dtype = timings.get("dtype", "float32")
103
+ self.parameters.warmup = None
104
+ self.__timings = timings.get("timings", {})
105
+ else:
106
+ # OFfline validation is not concerned with these parameters.
107
+ self.parameters.engine = "cpu"
108
+ self.parameters.nms = None
109
+ self.parameters.max_detections = None
110
+ self.parameters.iou_threshold = None
111
+ self.parameters.score_threshold = None
112
+ self.parameters.warmup = None
113
+ self.parameters.common.shape = (1, 640, 640, 3)
114
+ self.parameters.common.dtype = "float32"
115
+ timer.to_dict = self.get_timings
116
+
117
+ height, width = get_shape(self.parameters.common.shape)
118
+ try:
119
+ import edgefirst_python # type: ignore
120
+ self.parameters.common.input_dst = edgefirst_python.TensorImage(
121
+ width, height, edgefirst_python.FourCC.RGBA
122
+ )
123
+ except ImportError:
124
+ if self.parameters.common.backend == "hal":
125
+ raise ImportError(
126
+ "EdgeFirst HAL is needed to create a TensorImage destination."
127
+ )
128
+
129
+ def run_single_instance(self, image: str) -> Any:
130
+ """
131
+ This method reads one prediction annotation file based on the
132
+ image name and returns the bounding boxes and labels.
133
+
134
+ Parameters
135
+ ----------
136
+ image: str
137
+ The path to the image. This is used to match the
138
+ annotation to be read.
139
+
140
+ Returns
141
+ -------
142
+ Any:
143
+ This could either return detection outputs after NMS.
144
+ np.ndarray
145
+ The prediction bounding boxes.. [[box1], [box2], ...].
146
+ np.ndarray
147
+ The prediction labels.. [cl1, cl2, ...].
148
+ np.ndarray
149
+ The prediction confidence scores.. [score, score, ...]
150
+ normalized between 0 and 1.
151
+ This could also return segmentation masks.
152
+ np.ndarray
153
+ """
154
+ annotation_path = os.path.join(self.model, "{}.{}".format(
155
+ os.path.splitext(os.path.basename(image))[0],
156
+ self.annotation_extension
157
+ ))
158
+
159
+ try:
160
+ with warnings.catch_warnings():
161
+ warnings.simplefilter("ignore")
162
+ annotation = np.genfromtxt(annotation_path)
163
+ except FileNotFoundError:
164
+ return np.array([], dtype=np.float32), \
165
+ np.array([], dtype=np.int32), np.array([], dtype=np.float32)
166
+
167
+ if len(annotation):
168
+ annotation = annotation.reshape(-1, 6)
169
+ boxes = annotation[:, 2:6]
170
+ boxes = self.transformer(boxes) if self.transformer else boxes
171
+ else:
172
+ return np.array([], dtype=np.float32), \
173
+ np.array([], dtype=np.int32), np.array([], dtype=np.float32)
174
+
175
+ scores = annotation[:, 1:2].flatten().astype(np.float32)
176
+ labels = annotation[:, 0:1].flatten().astype(
177
+ np.int32) + self.parameters.label_offset
178
+
179
+ return boxes, labels, scores
180
+
181
+ def get_timings(self) -> dict:
182
+ """
183
+ Returns a summary of all the timings:
184
+ (mean, avg, max) of (load, inference, box).
185
+
186
+ Returns
187
+ -------
188
+ dict
189
+ The timings in milliseconds.
190
+
191
+ .. code-block:: python
192
+
193
+ {
194
+ 'min_read_time': minimum time to read the input,
195
+ 'max_read_time': maximum time to read the input,
196
+ 'min_load_time': minimum time to preprocess the input,
197
+ 'max_load_time': maximum time to preprocess the input,
198
+ 'min_backbone_time': minimum time to run the model,
199
+ 'max_backbone_time': maximum time to run the model,
200
+ 'min_decode_time': minimum time to decode the outputs,
201
+ 'max_decode_time': maximum time to decode the outputs,
202
+ 'min_box_time': minimum time to process the outputs,
203
+ 'max_box_time': maximum time to process the outputs,
204
+ 'avg_read_time': average time to read the input,
205
+ 'avg_load_time': average time to preprocess the input,
206
+ 'avg_backbone_time': average time to run the model,
207
+ 'avg_decode_time': average time to decode the outputs,
208
+ 'avg_box_time': average time to process the outputs,
209
+ }
210
+ """
211
+ # Convert timings to ms.
212
+ return {
213
+ 'min_read_time': self.__timings.get("min_read_time", 0) * 1e3,
214
+ 'max_read_time': self.__timings.get("max_read_time", 0) * 1e3,
215
+ 'min_load_time': self.__timings.get("min_load_time", 0) * 1e3,
216
+ 'max_load_time': self.__timings.get("max_load_time", 0) * 1e3,
217
+ 'min_backbone_time': self.__timings.get("min_backbone_time", 0) * 1e3,
218
+ 'max_backbone_time': self.__timings.get("max_backbone_time", 0) * 1e3,
219
+ 'min_decode_time': self.__timings.get("min_decode_time", 0) * 1e3,
220
+ 'max_decode_time': self.__timings.get("max_decode_time", 0) * 1e3,
221
+ 'min_box_time': self.__timings.get("min_box_time", 0) * 1e3,
222
+ 'max_box_time': self.__timings.get("max_box_time", 0) * 1e3,
223
+ 'avg_read_time': self.__timings.get("avg_read_time", 0) * 1e3,
224
+ 'avg_load_time': self.__timings.get("avg_load_time", 0) * 1e3,
225
+ 'avg_backbone_time': self.__timings.get("avg_backbone_time", 0) * 1e3,
226
+ 'avg_decode_time': self.__timings.get("avg_decode_time", 0) * 1e3,
227
+ 'avg_box_time': self.__timings.get("avg_box_time", 0) * 1e3,
228
+ }