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.
- deepview/modelpack/utils/argmax.py +16 -0
- edgefirst/validator/__init__.py +1 -0
- edgefirst/validator/__main__.py +375 -0
- edgefirst/validator/datasets/__init__.py +118 -0
- edgefirst/validator/datasets/cache.py +296 -0
- edgefirst/validator/datasets/core.py +250 -0
- edgefirst/validator/datasets/darknet.py +446 -0
- edgefirst/validator/datasets/database.py +1067 -0
- edgefirst/validator/datasets/instance/__init__.py +4 -0
- edgefirst/validator/datasets/instance/core.py +222 -0
- edgefirst/validator/datasets/instance/detection.py +145 -0
- edgefirst/validator/datasets/instance/multitask.py +80 -0
- edgefirst/validator/datasets/instance/segmentation.py +120 -0
- edgefirst/validator/datasets/utils/fetch.py +682 -0
- edgefirst/validator/datasets/utils/readers.py +425 -0
- edgefirst/validator/datasets/utils/transformations.py +1695 -0
- edgefirst/validator/evaluators/__init__.py +17 -0
- edgefirst/validator/evaluators/callbacks/__init__.py +3 -0
- edgefirst/validator/evaluators/callbacks/core.py +192 -0
- edgefirst/validator/evaluators/callbacks/plots.py +900 -0
- edgefirst/validator/evaluators/callbacks/studio.py +234 -0
- edgefirst/validator/evaluators/core.py +257 -0
- edgefirst/validator/evaluators/detection.py +749 -0
- edgefirst/validator/evaluators/multitask.py +270 -0
- edgefirst/validator/evaluators/parameters/__init__.py +53 -0
- edgefirst/validator/evaluators/parameters/core.py +554 -0
- edgefirst/validator/evaluators/parameters/dataset.py +239 -0
- edgefirst/validator/evaluators/parameters/model.py +338 -0
- edgefirst/validator/evaluators/parameters/validation.py +528 -0
- edgefirst/validator/evaluators/segmentation.py +729 -0
- edgefirst/validator/evaluators/utils/__init__.py +3 -0
- edgefirst/validator/evaluators/utils/classify.py +292 -0
- edgefirst/validator/evaluators/utils/match.py +262 -0
- edgefirst/validator/evaluators/utils/timer.py +132 -0
- edgefirst/validator/metrics/__init__.py +9 -0
- edgefirst/validator/metrics/data/__init__.py +7 -0
- edgefirst/validator/metrics/data/label.py +668 -0
- edgefirst/validator/metrics/data/metrics.py +759 -0
- edgefirst/validator/metrics/data/plots.py +476 -0
- edgefirst/validator/metrics/data/stats.py +507 -0
- edgefirst/validator/metrics/detection.py +595 -0
- edgefirst/validator/metrics/segmentation.py +173 -0
- edgefirst/validator/metrics/utils/math.py +717 -0
- edgefirst/validator/publishers/__init__.py +3 -0
- edgefirst/validator/publishers/console.py +147 -0
- edgefirst/validator/publishers/studio.py +128 -0
- edgefirst/validator/publishers/tensorboard.py +119 -0
- edgefirst/validator/publishers/utils/logger.py +111 -0
- edgefirst/validator/publishers/utils/table.py +403 -0
- edgefirst/validator/runners/__init__.py +8 -0
- edgefirst/validator/runners/core.py +727 -0
- edgefirst/validator/runners/deepviewrt.py +177 -0
- edgefirst/validator/runners/hailo.py +263 -0
- edgefirst/validator/runners/keras.py +150 -0
- edgefirst/validator/runners/kinara.py +265 -0
- edgefirst/validator/runners/offline.py +228 -0
- edgefirst/validator/runners/onnx.py +241 -0
- edgefirst/validator/runners/processing/decode.py +320 -0
- edgefirst/validator/runners/processing/dvapi.py +4192 -0
- edgefirst/validator/runners/processing/nms.py +637 -0
- edgefirst/validator/runners/processing/outputs.py +507 -0
- edgefirst/validator/runners/tensorrt.py +321 -0
- edgefirst/validator/runners/tflite.py +221 -0
- edgefirst/validator/validate.py +843 -0
- edgefirst/validator/visualize/__init__.py +3 -0
- edgefirst/validator/visualize/detection.py +623 -0
- edgefirst/validator/visualize/segmentation.py +281 -0
- edgefirst/validator/visualize/utils/plots.py +635 -0
- edgefirst_validator-4.2.1.dist-info/METADATA +111 -0
- edgefirst_validator-4.2.1.dist-info/RECORD +73 -0
- edgefirst_validator-4.2.1.dist-info/WHEEL +5 -0
- edgefirst_validator-4.2.1.dist-info/entry_points.txt +2 -0
- edgefirst_validator-4.2.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import ctypes
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from edgefirst.validator.publishers.utils.logger import logger
|
|
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 ONNXRunner(Runner):
|
|
18
|
+
"""
|
|
19
|
+
Loads and runs ONNX models for inference.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
model: Any
|
|
24
|
+
This is typically the path to the model (.onnx)
|
|
25
|
+
or the loaded ONNX 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
|
+
|
|
34
|
+
Raises
|
|
35
|
+
------
|
|
36
|
+
ImportError
|
|
37
|
+
Missing onnxruntime library.
|
|
38
|
+
FileNotFoundError
|
|
39
|
+
Raised if the path to the model does not exist.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
model: Any,
|
|
45
|
+
parameters: ModelParameters,
|
|
46
|
+
metadata: dict,
|
|
47
|
+
timer: TimerContext
|
|
48
|
+
):
|
|
49
|
+
super(ONNXRunner, self).__init__(model, parameters, timer=timer)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
import onnxruntime
|
|
53
|
+
except ImportError:
|
|
54
|
+
raise ImportError(
|
|
55
|
+
"onnxruntime or onnxruntime-gpu is needed to run ONNX models.")
|
|
56
|
+
|
|
57
|
+
if isinstance(model, str):
|
|
58
|
+
if not os.path.exists(model):
|
|
59
|
+
raise FileNotFoundError(
|
|
60
|
+
"The model '{}' does not exist.".format(model))
|
|
61
|
+
|
|
62
|
+
providers = self.select_providers()
|
|
63
|
+
logger(f"Selected Providers: {providers}", code="INFO")
|
|
64
|
+
self.model = onnxruntime.InferenceSession(
|
|
65
|
+
model, providers=providers)
|
|
66
|
+
|
|
67
|
+
self.graph_name = self.model.get_modelmeta().graph_name
|
|
68
|
+
self.output_names = [x.name for x in self.model.get_outputs()]
|
|
69
|
+
outputs = self.model.get_outputs()
|
|
70
|
+
self.init_decoder(metadata=metadata, outputs=outputs)
|
|
71
|
+
|
|
72
|
+
if self.parameters.warmup > 0:
|
|
73
|
+
self.warmup()
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def check_tensorrt_runtime() -> list:
|
|
77
|
+
"""
|
|
78
|
+
The following libraries are needed to run ONNX
|
|
79
|
+
with TensorrtExecutionProvider.
|
|
80
|
+
|
|
81
|
+
- "libnvinfer.so"
|
|
82
|
+
- "libnvinfer_plugin.so"
|
|
83
|
+
- "libnvonnxparser.so"
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
list
|
|
88
|
+
A list of the libraries that are missing.
|
|
89
|
+
"""
|
|
90
|
+
required_libs = ["libnvinfer.so",
|
|
91
|
+
"libnvinfer_plugin.so", "libnvonnxparser.so"]
|
|
92
|
+
missing = []
|
|
93
|
+
for lib in required_libs:
|
|
94
|
+
try:
|
|
95
|
+
ctypes.CDLL(lib)
|
|
96
|
+
except OSError:
|
|
97
|
+
missing.append(lib)
|
|
98
|
+
return missing
|
|
99
|
+
|
|
100
|
+
def select_providers(self) -> list:
|
|
101
|
+
"""
|
|
102
|
+
Specify the providers to load based on
|
|
103
|
+
the type of engine specified.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
list
|
|
108
|
+
A list of the selected providers to deploy.
|
|
109
|
+
"""
|
|
110
|
+
import onnxruntime
|
|
111
|
+
|
|
112
|
+
selected_providers = ["CPUExecutionProvider"]
|
|
113
|
+
available_providers = onnxruntime.get_available_providers()
|
|
114
|
+
if self.parameters.engine.lower() == "npu":
|
|
115
|
+
preferred_providers = ["NnapiExecutionProvider",
|
|
116
|
+
"VsiNpuExecutionProvider",
|
|
117
|
+
"TensorrtExecutionProvider",
|
|
118
|
+
"CUDAExecutionProvider",
|
|
119
|
+
"CPUExecutionProvider"]
|
|
120
|
+
selected_providers = []
|
|
121
|
+
for i, provider in enumerate(preferred_providers):
|
|
122
|
+
if provider in available_providers:
|
|
123
|
+
if provider == "TensorrtExecutionProvider":
|
|
124
|
+
missing_libraries = self.check_tensorrt_runtime()
|
|
125
|
+
if missing_libraries:
|
|
126
|
+
logger(f"The libraries {missing_libraries} are " +
|
|
127
|
+
"needed for TensorrtExecutionProvider. " +
|
|
128
|
+
f"Falling back to {preferred_providers[i+1]}.",
|
|
129
|
+
code="WARNING")
|
|
130
|
+
continue
|
|
131
|
+
selected_providers.append(provider)
|
|
132
|
+
else:
|
|
133
|
+
logger(f"{provider} is not present in the system. " +
|
|
134
|
+
f"Falling back to {preferred_providers[i+1]}.",
|
|
135
|
+
code="WARNING")
|
|
136
|
+
if selected_providers in [["TensorrtExecutionProvider",
|
|
137
|
+
"CUDAExecutionProvider",
|
|
138
|
+
"CPUExecutionProvider"],
|
|
139
|
+
["CUDAExecutionProvider",
|
|
140
|
+
"CPUExecutionProvider"]]:
|
|
141
|
+
self.parameters.engine = "gpu"
|
|
142
|
+
elif selected_providers == ["CPUExecutionProvider"]:
|
|
143
|
+
self.parameters.engine = "cpu"
|
|
144
|
+
elif self.parameters.engine.lower() == "gpu":
|
|
145
|
+
preferred_providers = ["TensorrtExecutionProvider",
|
|
146
|
+
"CUDAExecutionProvider",
|
|
147
|
+
"CPUExecutionProvider"]
|
|
148
|
+
selected_providers = []
|
|
149
|
+
for i, provider in enumerate(preferred_providers):
|
|
150
|
+
if provider in available_providers:
|
|
151
|
+
if provider == "TensorrtExecutionProvider":
|
|
152
|
+
missing_libraries = self.check_tensorrt_runtime()
|
|
153
|
+
if missing_libraries:
|
|
154
|
+
logger(f"The libraries {missing_libraries} are " +
|
|
155
|
+
"needed for TensorrtExecutionProvider. " +
|
|
156
|
+
f"Falling back to {preferred_providers[i+1]}.",
|
|
157
|
+
code="WARNING")
|
|
158
|
+
continue
|
|
159
|
+
selected_providers.append(provider)
|
|
160
|
+
else:
|
|
161
|
+
logger(f"{provider} is not present in the system. " +
|
|
162
|
+
f"Falling back to {preferred_providers[i+1]}.",
|
|
163
|
+
code="WARNING")
|
|
164
|
+
if selected_providers == ["CPUExecutionProvider"]:
|
|
165
|
+
self.parameters.engine = "cpu"
|
|
166
|
+
logger(
|
|
167
|
+
"TensorrtExecutionProvider and CUDAExecutionProvider is " +
|
|
168
|
+
"not present in the system. Falling back to CPUExecutionProvider.",
|
|
169
|
+
code="WARNING")
|
|
170
|
+
return selected_providers
|
|
171
|
+
|
|
172
|
+
def run_single_instance(self, image: np.ndarray) -> Any:
|
|
173
|
+
"""
|
|
174
|
+
Run ONNX inference on a single image and record the timings.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
image: np.ndarray
|
|
179
|
+
The input image after being preprocessed.
|
|
180
|
+
Typically this is an RGB image array.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
Any
|
|
185
|
+
This could either return detection outputs after NMS.
|
|
186
|
+
np.ndarray
|
|
187
|
+
The prediction bounding boxes.. [[box1], [box2], ...].
|
|
188
|
+
np.ndarray
|
|
189
|
+
The prediction labels.. [cl1, cl2, ...].
|
|
190
|
+
np.ndarray
|
|
191
|
+
The prediction confidence scores.. [score, score, ...]
|
|
192
|
+
normalized between 0 and 1.
|
|
193
|
+
This could also return segmentation masks.
|
|
194
|
+
np.ndarray
|
|
195
|
+
"""
|
|
196
|
+
# Inference
|
|
197
|
+
with self.timer.time("inference"):
|
|
198
|
+
outputs = self.model.run(self.output_names,
|
|
199
|
+
{self.model.get_inputs()[0].name: image})
|
|
200
|
+
|
|
201
|
+
# Postprocessing
|
|
202
|
+
return self.postprocessing(outputs)
|
|
203
|
+
|
|
204
|
+
def get_input_type(self) -> np.dtype:
|
|
205
|
+
"""
|
|
206
|
+
This returns the input type of the model for the
|
|
207
|
+
input with shape in the form
|
|
208
|
+
(batch size, channels, height, width) or
|
|
209
|
+
(batch size, height, width, channels).
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
np.dtype
|
|
214
|
+
The input type of the model.
|
|
215
|
+
"""
|
|
216
|
+
if "float16" in self.model.get_inputs()[0].type:
|
|
217
|
+
return np.float16
|
|
218
|
+
elif "float" in self.model.get_inputs()[0].type:
|
|
219
|
+
return np.float32
|
|
220
|
+
elif "uint8" in self.model.get_inputs()[0].type:
|
|
221
|
+
return np.uint8
|
|
222
|
+
elif "int8" in self.model.get_inputs()[0].type:
|
|
223
|
+
return np.int8
|
|
224
|
+
return self.model.get_inputs()[0].type
|
|
225
|
+
|
|
226
|
+
def get_input_shape(self) -> np.ndarray:
|
|
227
|
+
"""
|
|
228
|
+
This fetches the model input shape.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
np.ndarray
|
|
233
|
+
The model input shape
|
|
234
|
+
(batch size, channels, height, width) or
|
|
235
|
+
(batch size, height, width, channels).
|
|
236
|
+
"""
|
|
237
|
+
for input in self.model.get_inputs():
|
|
238
|
+
if len(input.shape) == 4:
|
|
239
|
+
if input.shape[1] == 3 or input.shape[-1] == 3:
|
|
240
|
+
return input.shape
|
|
241
|
+
return self.model.get_inputs()[0].shape
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from typing import Tuple, Union, List
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from edgefirst.validator.metrics.utils.math import sigmoid
|
|
7
|
+
from edgefirst.validator.datasets.utils.transformations import xcycwh2xyxy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decode_mpk_boxes(
|
|
11
|
+
p: np.ndarray, anchors: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
12
|
+
"""
|
|
13
|
+
Decodes ModelPack boxes into boxes and scores.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
p: np.ndarray
|
|
18
|
+
Raw model output with typical shapes such
|
|
19
|
+
as (1, 9, 15, 21) or (1, 17, 30, 21).
|
|
20
|
+
anchors: np.ndarray
|
|
21
|
+
Model anchors used for decoding the outputs
|
|
22
|
+
sometimes with shape (3, 3).
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
boxes: np.ndarray
|
|
27
|
+
The decoded bounding boxes with shape (1, N, 1, 4).
|
|
28
|
+
scores: np.ndarray
|
|
29
|
+
The decoded scores with shape (1, N, nc) where nc is
|
|
30
|
+
the number of classes.
|
|
31
|
+
"""
|
|
32
|
+
p = sigmoid(p)
|
|
33
|
+
|
|
34
|
+
na = anchors.shape[0]
|
|
35
|
+
nc = p.shape[-1] // na - 5
|
|
36
|
+
_, h, w, _ = p.shape
|
|
37
|
+
|
|
38
|
+
p = p.reshape((-1, h, w, na, nc + 5))
|
|
39
|
+
grid = np.meshgrid(np.arange(w), np.arange(h))
|
|
40
|
+
grid = np.expand_dims(np.stack(grid, axis=-1), axis=2)
|
|
41
|
+
grid = np.tile(np.expand_dims(grid, axis=0), [
|
|
42
|
+
1, 1, 1, na, 1])
|
|
43
|
+
|
|
44
|
+
# Decoding
|
|
45
|
+
xy = p[..., 0:2]
|
|
46
|
+
wh = p[..., 2:4]
|
|
47
|
+
obj = p[..., 4:5]
|
|
48
|
+
probs = p[..., 5:]
|
|
49
|
+
|
|
50
|
+
scores = obj * probs
|
|
51
|
+
|
|
52
|
+
xy = (xy * 2.0 + grid - 0.5) / (w, h)
|
|
53
|
+
wh = (wh * 2) ** 2 * anchors * 0.5
|
|
54
|
+
xyxy = np.concatenate([
|
|
55
|
+
xy - wh,
|
|
56
|
+
xy + wh
|
|
57
|
+
], axis=-1)
|
|
58
|
+
xyxy = xyxy.reshape((1, -1, 1, 4))
|
|
59
|
+
scores = scores.reshape(1, -1, nc)
|
|
60
|
+
|
|
61
|
+
return xyxy, scores
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def decode_yolo_boxes(
|
|
65
|
+
p: np.ndarray,
|
|
66
|
+
with_masks: bool,
|
|
67
|
+
nc: int
|
|
68
|
+
) -> Tuple[np.ndarray, np.ndarray, Union[np.ndarray, None]]:
|
|
69
|
+
"""
|
|
70
|
+
Takes the output from Ultralytics models and decodes
|
|
71
|
+
boxes, scores, classes, and mask coefficients (segmentation models).
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
p: np.ndarray
|
|
76
|
+
The model output tensor with shape (1, nc + 4, 8400) or
|
|
77
|
+
(1, nc + 4 + 32, 8400) for detection and segmentation respectively.
|
|
78
|
+
with_masks: bool
|
|
79
|
+
Slice the last 32 values from the output as the mask
|
|
80
|
+
proto coefficients.
|
|
81
|
+
nc: int
|
|
82
|
+
The number of labels.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
boxes: np.ndarray
|
|
87
|
+
The boxes tensor with shape (8400, 4).
|
|
88
|
+
scores: np.ndarray
|
|
89
|
+
The scores tensor with shape (8400,).
|
|
90
|
+
masks: Union[np.ndarray, None]
|
|
91
|
+
The masks tensor coefficients with shape (8400, 32).
|
|
92
|
+
Otherwise, if the model is detection, this will be None.
|
|
93
|
+
"""
|
|
94
|
+
masks = None
|
|
95
|
+
if p.shape[0] == 1:
|
|
96
|
+
p = p[0]
|
|
97
|
+
# Only transpose if shapes are [116, 8400] or [85, 25200]
|
|
98
|
+
if p.shape[0] < p.shape[1]:
|
|
99
|
+
# Transposing shape (116, 8400) -> (8400, 116).
|
|
100
|
+
p = p.transpose((1, 0))
|
|
101
|
+
boxes = xcycwh2xyxy(boxes=p[:, 0:4])
|
|
102
|
+
if with_masks:
|
|
103
|
+
det_i = p.shape[1] - 32
|
|
104
|
+
scores = p[:, 4:det_i]
|
|
105
|
+
masks = p[:, det_i:] # Additional 32 protos from segmentation models.
|
|
106
|
+
else:
|
|
107
|
+
# YOLOv5 models contains [x, y, x, y, obj_conf, cls_conf] outputs.
|
|
108
|
+
if p.shape[1] == nc + 5:
|
|
109
|
+
scores = p[:, 5:]
|
|
110
|
+
scores *= p[:, 4:5] # conf = obj_conf * cls_conf # NOSONAR
|
|
111
|
+
# YOLOv8 and YOLOv11
|
|
112
|
+
else:
|
|
113
|
+
scores = p[:, 4:]
|
|
114
|
+
return boxes, scores, masks
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def decode_yolox_boxes(
|
|
118
|
+
p: np.ndarray,
|
|
119
|
+
shape: tuple,
|
|
120
|
+
p6: bool = False
|
|
121
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
122
|
+
"""
|
|
123
|
+
Decodes YOLOx outputs into boxes and scores.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
p: np.ndarray
|
|
128
|
+
The raw YOLOx model outputs with shape (1, 8400, 85).
|
|
129
|
+
shape: tuple
|
|
130
|
+
The model input shape (height, width).
|
|
131
|
+
p6: bool
|
|
132
|
+
If True, enables support for YOLOX-P6 with stride 64 detection head.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
boxes: np.ndarray
|
|
137
|
+
The decoded boxes with shape (8400, 4).
|
|
138
|
+
scores: np.ndarray
|
|
139
|
+
The decoded scores with shape (8400, nc).
|
|
140
|
+
"""
|
|
141
|
+
h, w = shape
|
|
142
|
+
|
|
143
|
+
grids = []
|
|
144
|
+
expanded_strides = []
|
|
145
|
+
strides = [8, 16, 32] if not p6 else [8, 16, 32, 64]
|
|
146
|
+
|
|
147
|
+
hsizes = [h // stride for stride in strides]
|
|
148
|
+
wsizes = [w // stride for stride in strides]
|
|
149
|
+
for hsize, wsize, stride in zip(hsizes, wsizes, strides):
|
|
150
|
+
xv, yv = np.meshgrid(np.arange(wsize), np.arange(hsize))
|
|
151
|
+
grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
|
|
152
|
+
grids.append(grid)
|
|
153
|
+
shape = grid.shape[:2]
|
|
154
|
+
expanded_strides.append(np.full((*shape, 1), stride))
|
|
155
|
+
|
|
156
|
+
grids = np.concatenate(grids, 1)
|
|
157
|
+
expanded_strides = np.concatenate(expanded_strides, 1)
|
|
158
|
+
p[..., :2] = (p[..., :2] + grids) * expanded_strides
|
|
159
|
+
p[..., 2:4] = np.exp(p[..., 2:4]) * expanded_strides
|
|
160
|
+
predictions = p[0]
|
|
161
|
+
|
|
162
|
+
boxes = predictions[:, :4]
|
|
163
|
+
scores = predictions[:, 4:5] * predictions[:, 5:]
|
|
164
|
+
|
|
165
|
+
return boxes, scores
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def decode_yolo_masks(masks: np.ndarray, protos: np.ndarray) -> np.ndarray:
|
|
169
|
+
"""
|
|
170
|
+
Takes the output from Ultralytics segmentation models and
|
|
171
|
+
decodes instance segmentation masks from the model.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
masks: np.ndarray
|
|
176
|
+
The mask coefficients with shape (n, 32).
|
|
177
|
+
protos: np.ndarray
|
|
178
|
+
The raw output mask tensor with shape (1, h, w, 32).
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
np.ndarray
|
|
183
|
+
The instance mask per object with shape (n, h, w).
|
|
184
|
+
"""
|
|
185
|
+
# In case of shape (1, 32, h, w).
|
|
186
|
+
if protos.shape[1] == 32:
|
|
187
|
+
c, h, w = protos[0].shape
|
|
188
|
+
else:
|
|
189
|
+
h, w, c = protos[0].shape
|
|
190
|
+
protos = np.transpose(protos, (0, 3, 1, 2))
|
|
191
|
+
masks = np.matmul(masks, protos.reshape(c, -1)).reshape(-1, h, w)
|
|
192
|
+
return masks
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def decode_mpk_masks(masks: np.ndarray) -> np.ndarray:
|
|
196
|
+
"""
|
|
197
|
+
Decodes ModelPack masks into semantic segmentation.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
masks: np.ndarray
|
|
202
|
+
The raw segmentation masks from the model
|
|
203
|
+
with shape (1, h, w, nc).
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
np.ndarray
|
|
208
|
+
The decoded semantic segmentation mask
|
|
209
|
+
with shape (1, h, w).
|
|
210
|
+
"""
|
|
211
|
+
if len(masks.shape) == 3:
|
|
212
|
+
return np.array(masks, dtype=np.uint8)
|
|
213
|
+
return np.argmax(masks, axis=-1).astype(np.uint8)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def crop_masks(masks: np.ndarray, boxes: np.ndarray) -> np.ndarray:
|
|
217
|
+
"""
|
|
218
|
+
Crops each instance mask to the bounding box.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
masks: np.ndarray
|
|
223
|
+
The instance mask per object with shape (n, h, w).
|
|
224
|
+
boxes: np.ndarray
|
|
225
|
+
Normalized box coordinates in [xmin, ymin, xmax, ymax]
|
|
226
|
+
format with shape (n, 4).
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
np.ndarray
|
|
231
|
+
The instance mask per object cropped to the box dimensions
|
|
232
|
+
with shape (n, h, w).
|
|
233
|
+
"""
|
|
234
|
+
_, h, w = masks.shape
|
|
235
|
+
x1, y1, x2, y2 = np.split(
|
|
236
|
+
boxes[:, :, np.newaxis], 4, axis=1) # shape (n, 1, 1)
|
|
237
|
+
r = np.arange(w, dtype=boxes.dtype)[None, None, :] # rows shape(1,1,w)
|
|
238
|
+
c = np.arange(h, dtype=boxes.dtype)[None, :, None] # cols shape(1,h,1)
|
|
239
|
+
return masks * ((r >= x1 * w) * (r < x2 * w)
|
|
240
|
+
* (c >= y1 * h) * (c < y2 * h))
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def dequantize(x: np.ndarray, scale: float, zero_point: float) -> np.ndarray:
|
|
244
|
+
"""
|
|
245
|
+
Dequantization of the model output tensor based on the scale
|
|
246
|
+
and zero point values.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
x: np.ndarray
|
|
251
|
+
Quantized model output tensor typically with uint8 or int8 dtypes.
|
|
252
|
+
scale: float
|
|
253
|
+
Quantization scale factor.
|
|
254
|
+
zero_point: float
|
|
255
|
+
Quantization shift factor for signed tensors.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
np.ndarray
|
|
260
|
+
Dequantized tensors typically float32 dtypes.
|
|
261
|
+
"""
|
|
262
|
+
if scale > 0:
|
|
263
|
+
x = (x.astype(np.float32) - zero_point) * scale # re-scale
|
|
264
|
+
return x
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def dequantize_kinara(
|
|
268
|
+
output_dict: dict,
|
|
269
|
+
method: str = "hal"
|
|
270
|
+
) -> List[np.ndarray]:
|
|
271
|
+
"""
|
|
272
|
+
Dequantize a list of quantized outputs from Kinara models.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
output_list : dict
|
|
277
|
+
List of quantized model outputs.
|
|
278
|
+
method: str
|
|
279
|
+
The method to use for performing the dequantization.
|
|
280
|
+
By default "hal" is used. Otherwise, NumPy operations are used.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
list of np.ndarray
|
|
285
|
+
List of dequantized outputs as float arrays.
|
|
286
|
+
"""
|
|
287
|
+
num_outputs = len(output_dict)
|
|
288
|
+
dequantized_outputs = []
|
|
289
|
+
for i in range(num_outputs):
|
|
290
|
+
output: np.ndarray = output_dict[i].numpy_data
|
|
291
|
+
out_param = output_dict[i].params
|
|
292
|
+
|
|
293
|
+
if (not out_param.postprocess_param.is_struct_format
|
|
294
|
+
and not out_param.postprocess_param.is_float):
|
|
295
|
+
|
|
296
|
+
bpp = out_param.bpp
|
|
297
|
+
dtype = np.int8
|
|
298
|
+
if bpp == 1:
|
|
299
|
+
dtype = np.int8 if out_param.postprocess_param.is_signed else np.uint8
|
|
300
|
+
elif bpp == 2:
|
|
301
|
+
dtype = np.int16 if out_param.postprocess_param.is_signed else np.uint16
|
|
302
|
+
elif bpp == 4:
|
|
303
|
+
dtype = np.int32 if out_param.postprocess_param.is_signed else np.uint32
|
|
304
|
+
|
|
305
|
+
qn, offset = (out_param.postprocess_param.qn,
|
|
306
|
+
out_param.postprocess_param.offset)
|
|
307
|
+
buf = output.view(dtype=dtype)
|
|
308
|
+
|
|
309
|
+
if method == "hal":
|
|
310
|
+
dst_buffer = buf
|
|
311
|
+
else:
|
|
312
|
+
dst_buffer = (
|
|
313
|
+
(buf.astype(int) -
|
|
314
|
+
offset) *
|
|
315
|
+
qn).astype(
|
|
316
|
+
np.float32)
|
|
317
|
+
dequantized_outputs.append(dst_buffer)
|
|
318
|
+
else:
|
|
319
|
+
dequantized_outputs.append(output)
|
|
320
|
+
return dequantized_outputs
|