@camstack/addon-pipeline 1.2.328 → 1.2.330
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.
- package/THIRD_PARTY_MODELS.md +2 -2
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-1SadEKsW.js → default-detection-model-B6gKGnfx.js} +53 -71
- package/dist/{default-detection-model-CMeVTcPZ.mjs → default-detection-model-CPDT_eFT.mjs} +53 -71
- package/dist/detection-pipeline/index.js +607 -39
- package/dist/detection-pipeline/index.mjs +607 -39
- package/dist/{dist-wJO7mXeO.js → dist-CMeICL07.js} +781 -465
- package/dist/{dist-Bpub2VcJ.mjs → dist-CuPEyZY2.mjs} +736 -438
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-BjCSgD4M.mjs → node-USq5jYwq.mjs} +1 -1
- package/dist/{node-BCrqaejt.js → node-X_0Tka7-.js} +1 -1
- package/dist/pipeline-runner/index.js +355 -104
- package/dist/pipeline-runner/index.mjs +355 -104
- package/dist/{process-memory-BaKQ8u2G.mjs → process-memory-07XYts8V.mjs} +1 -1
- package/dist/{process-memory-iUJuTpXs.js → process-memory-o4LFWbBN.js} +1 -1
- package/dist/recorder/index.js +7 -7
- package/dist/recorder/index.mjs +7 -7
- package/dist/{segment-demux-js-C5ez9LYG.mjs → segment-demux-js-Bq8DrH0b.mjs} +2 -2
- package/dist/{segment-demux-js-Opx6Hv9G.js → segment-demux-js-FqaQjG7n.js} +2 -2
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B94LgsV0.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-EYRUYv_r.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BnP7C_HO.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CR0--kRv.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-xOx7AvgW.mjs → hostInit-iGdBpvGc.mjs} +3 -3
- package/dist/stream-broker/index.js +4 -4
- package/dist/stream-broker/index.mjs +4 -4
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/edgetpu_dequant.py +38 -1
- package/python/inference_pool.py +31 -17
- package/python/postprocessors/test_yolox_plate_edgetpu.py +126 -0
- package/python/postprocessors/test_yunet_edgetpu.py +149 -0
- package/python/postprocessors/testdata/yolox_plate_int8_outputs.json +1 -0
- package/python/postprocessors/testdata/yunet_int8_outputs.json +1 -0
- package/python/postprocessors/yolo.py +44 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BPWYf4_O.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BpXPNINd.mjs +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.330",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -28,7 +28,16 @@ def dequant_edgetpu_output(
|
|
|
28
28
|
that exposes ``tolist``. Float dtypes are returned unchanged. A zero or
|
|
29
29
|
missing scale becomes a plain float cast — we never invent a scale.
|
|
30
30
|
"""
|
|
31
|
-
|
|
31
|
+
is_array = hasattr(value, "tolist") and not isinstance(value, (list, tuple))
|
|
32
|
+
if is_array and _is_integer_dtype(dtype_name) and scale is not None and float(scale) > 0.0:
|
|
33
|
+
# A numpy / TFLite tensor is dequantised VECTORISED, through the
|
|
34
|
+
# array's own methods (no numpy import here). The list walk below
|
|
35
|
+
# costs ~0.3 us per value: YuNet at 640 emits 134 400 of them per
|
|
36
|
+
# frame, tens of milliseconds the predict closure would spend in
|
|
37
|
+
# Python. The result is a float32 array; every postprocessor reads
|
|
38
|
+
# its inputs through `np.asarray`/`np.array`.
|
|
39
|
+
return (value.astype("float32") - float(int(zero_point))) * float(scale)
|
|
40
|
+
if is_array:
|
|
32
41
|
# numpy / TFLite tensor — decide from dtype_name, then walk lists.
|
|
33
42
|
value = value.tolist()
|
|
34
43
|
|
|
@@ -53,3 +62,31 @@ def _scale_tree(value: Any, scale: float, zero_point: int) -> Any:
|
|
|
53
62
|
if isinstance(value, (list, tuple)):
|
|
54
63
|
return [_scale_tree(v, scale, zero_point) for v in value]
|
|
55
64
|
return (float(value) - zero_point) * scale
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Postprocessors that find their tensors by NAME. The edgetpu predict closure
|
|
68
|
+
# keys their outputs by the TFLite tensor name, exactly as the onnx, openvino
|
|
69
|
+
# and coreml runtimes key every model's; every other postprocessor keeps the
|
|
70
|
+
# positional "0".."n-1" keys (`ssd.py` reads "0".."3" of
|
|
71
|
+
# TFLite_Detection_PostProcess, whose names carry ":1", ":2", ":3").
|
|
72
|
+
# `yunet` matches its twelve outputs by name only (D645): `cls_*` and `obj_*`
|
|
73
|
+
# have identical shapes, so a position cannot say which is which. `yolo` finds
|
|
74
|
+
# the raw YOLOX head of the `yolox-tiny-plate` Coral build by name
|
|
75
|
+
# (`reg_8` … `score_32`); its other paths pick a tensor by shape, never by key.
|
|
76
|
+
NAME_KEYED_POSTPROCESSORS = frozenset({"yunet", "yolo"})
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def edgetpu_output_keys(names: "list[str]", postprocessor: "str | None") -> "list[str]":
|
|
80
|
+
"""The key under which each output of an Edge TPU model reaches its postprocessor.
|
|
81
|
+
|
|
82
|
+
Named only for a postprocessor in ``NAME_KEYED_POSTPROCESSORS``, and only
|
|
83
|
+
when every name is non-empty and distinct; otherwise positional — and a
|
|
84
|
+
name-keyed postprocessor then raises, naming the keys it got, instead of
|
|
85
|
+
decoding tensors it cannot tell apart.
|
|
86
|
+
"""
|
|
87
|
+
positional = [str(i) for i in range(len(names))]
|
|
88
|
+
if postprocessor not in NAME_KEYED_POSTPROCESSORS:
|
|
89
|
+
return positional
|
|
90
|
+
if any(not n for n in names) or len(set(names)) != len(names):
|
|
91
|
+
return positional
|
|
92
|
+
return list(names)
|
package/python/inference_pool.py
CHANGED
|
@@ -1319,9 +1319,19 @@ def _load_model(slot: ModelSlot, config: dict) -> None:
|
|
|
1319
1319
|
slot.config["_edgetpu_in_dtype"] = in_dtype
|
|
1320
1320
|
slot.config["inputWidth"] = in_w
|
|
1321
1321
|
slot.config["inputHeight"] = in_h
|
|
1322
|
-
|
|
1322
|
+
# The FILE's input size is the truth for a compiled Edge TPU graph: a
|
|
1323
|
+
# Coral build may be compiled at a smaller input than the catalog
|
|
1324
|
+
# entry's other formats (activations share the 8 MB on-chip memory
|
|
1325
|
+
# with the parameter cache, D710), and a decoder sized from the
|
|
1326
|
+
# catalog would put every prior in the wrong cell.
|
|
1327
|
+
slot.config["inputSize"] = max(in_w, in_h)
|
|
1323
1328
|
in_index = int(in_det["index"])
|
|
1324
1329
|
out_index = [int(o["index"]) for o in out_dets]
|
|
1330
|
+
from edgetpu_dequant import edgetpu_output_keys
|
|
1331
|
+
|
|
1332
|
+
out_keys = edgetpu_output_keys(
|
|
1333
|
+
[str(o.get("name") or "") for o in out_dets], slot.config.get("postprocessor")
|
|
1334
|
+
)
|
|
1325
1335
|
sys.stderr.write(
|
|
1326
1336
|
f"EdgeTPU: loaded {os.path.basename(path)} in={in_w}x{in_h} "
|
|
1327
1337
|
f"dtype={in_dtype} outputs={len(out_dets)}\n"
|
|
@@ -1331,6 +1341,7 @@ def _load_model(slot: ModelSlot, config: dict) -> None:
|
|
|
1331
1341
|
def predict(
|
|
1332
1342
|
inp_dict: dict,
|
|
1333
1343
|
_interp=interp, _lock=lock, _in=in_index, _outs=out_index, _details=out_dets,
|
|
1344
|
+
_keys=out_keys,
|
|
1334
1345
|
) -> dict:
|
|
1335
1346
|
from edgetpu_dequant import dequant_edgetpu_output
|
|
1336
1347
|
|
|
@@ -1338,22 +1349,25 @@ def _load_model(slot: ModelSlot, config: dict) -> None:
|
|
|
1338
1349
|
with _lock:
|
|
1339
1350
|
_interp.set_tensor(_in, tensor)
|
|
1340
1351
|
_interp.invoke()
|
|
1341
|
-
#
|
|
1342
|
-
#
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1352
|
+
# `get_tensor` returns a copy, so the Coral is released before
|
|
1353
|
+
# the dequantisation below runs.
|
|
1354
|
+
raws = [_interp.get_tensor(idx) for idx in _outs]
|
|
1355
|
+
# Positional keys — ssd reads "0".."3"; yolo-split-dfl pairs by
|
|
1356
|
+
# shape — except for a postprocessor that matches by name
|
|
1357
|
+
# (`edgetpu_output_keys`). Integer outputs are dequantised here so
|
|
1358
|
+
# every postprocessor sees float32 (SSD Detection_PostProcess is
|
|
1359
|
+
# already float and passes through).
|
|
1360
|
+
result = {}
|
|
1361
|
+
for i, raw in enumerate(raws):
|
|
1362
|
+
detail = _details[i]
|
|
1363
|
+
q = detail.get("quantization") or (0.0, 0)
|
|
1364
|
+
scale = float(q[0]) if q else 0.0
|
|
1365
|
+
zp = int(q[1]) if q else 0
|
|
1366
|
+
dtype_name = str(np.dtype(detail["dtype"]).name)
|
|
1367
|
+
result[_keys[i]] = dequant_edgetpu_output(
|
|
1368
|
+
raw, dtype_name=dtype_name, scale=scale, zero_point=zp
|
|
1369
|
+
)
|
|
1370
|
+
return result
|
|
1357
1371
|
|
|
1358
1372
|
slot.model = interp
|
|
1359
1373
|
slot.predict_fn = predict
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Pins the YOLOX-tiny plate Coral build on REAL output tensors (D710 enforcement).
|
|
2
|
+
|
|
3
|
+
`yolox-tiny-plate` ships a `tflite` format compiled for the Edge TPU
|
|
4
|
+
(`camstack-yolox-tiny-plate_edgetpu.tflite`). Unlike its other builds — which
|
|
5
|
+
bake YOLOX's grid decode into the graph and emit the YOLOv8 layout — the Coral
|
|
6
|
+
graph stops at the raw head (an `Exp` and a coordinate Concat would share one
|
|
7
|
+
int8 scale between pixels and probabilities): per stride, `reg_s` (dx, dy,
|
|
8
|
+
log w, log h) and `score_s` (obj*cls), NHWC, int8. Three things must hold for
|
|
9
|
+
the step to emit the box the ONNX build emits:
|
|
10
|
+
|
|
11
|
+
- the edgetpu predict closure keys the `yolo` postprocessor's outputs by
|
|
12
|
+
tensor NAME (`edgetpu_output_keys`) — the head is found by its names;
|
|
13
|
+
- every output is dequantised (`edgetpu_dequant`);
|
|
14
|
+
- `yolo.py`'s `yolox_head_to_v8` decodes the head (row-major cells, centre =
|
|
15
|
+
(offset + cell) * stride, size = exp(log) * stride) into the layout the rest
|
|
16
|
+
of the postprocessor already reads.
|
|
17
|
+
|
|
18
|
+
The fixture (`testdata/yolox_plate_int8_outputs.json`) holds the raw outputs of
|
|
19
|
+
the int8 graph the Edge TPU file is compiled from, on the public
|
|
20
|
+
`reference-images/1-car-plate.jpg` letterboxed to 640, and the published ONNX
|
|
21
|
+
build's detections on the same frame. Produced by
|
|
22
|
+
`scripts/build-yolox-plate-edgetpu.py`.
|
|
23
|
+
|
|
24
|
+
`unittest.TestCase`: the key and dequant cases run on CI's bare interpreter;
|
|
25
|
+
the decode cases need numpy and are skipped, by name, without it.
|
|
26
|
+
"""
|
|
27
|
+
import base64
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
import unittest
|
|
32
|
+
import zlib
|
|
33
|
+
|
|
34
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
sys.path.insert(0, os.path.dirname(_HERE)) # edgetpu_dequant lives one level up
|
|
36
|
+
|
|
37
|
+
from edgetpu_dequant import dequant_edgetpu_output, edgetpu_output_keys # noqa: E402
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import numpy as _numpy_probe
|
|
41
|
+
except ImportError:
|
|
42
|
+
_numpy_probe = None
|
|
43
|
+
_HAS_REAL_NUMPY = _numpy_probe is not None and hasattr(_numpy_probe, "ndarray")
|
|
44
|
+
|
|
45
|
+
with open(os.path.join(_HERE, "testdata", "yolox_plate_int8_outputs.json"), encoding="utf-8") as _fh:
|
|
46
|
+
_FIXTURE = json.load(_fh)
|
|
47
|
+
|
|
48
|
+
_OUTPUTS = _FIXTURE["outputs"]
|
|
49
|
+
_FRAME = _FIXTURE["frame"]
|
|
50
|
+
_STEP_GATE = 0.5 # plate-detection.defaultConfidence
|
|
51
|
+
_HEAD = ["reg_8", "score_8", "reg_16", "score_16", "reg_32", "score_32"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _raw(name):
|
|
55
|
+
data = zlib.decompress(base64.b64decode(_OUTPUTS[name]["zlibB64"]))
|
|
56
|
+
return [b - 256 if b > 127 else b for b in data]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _iou(a, b):
|
|
60
|
+
ix = max(0.0, min(a[2], b[2]) - max(a[0], b[0]))
|
|
61
|
+
iy = max(0.0, min(a[3], b[3]) - max(a[1], b[1]))
|
|
62
|
+
inter = ix * iy
|
|
63
|
+
union = (a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter
|
|
64
|
+
return inter / union if union > 0 else 0.0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class YoloxPlateEdgetpuContract(unittest.TestCase):
|
|
68
|
+
def test_fixture_is_the_shipped_contract(self):
|
|
69
|
+
self.assertEqual(sorted(_OUTPUTS), sorted(_HEAD))
|
|
70
|
+
self.assertEqual(_OUTPUTS["reg_8"]["shape"], [1, 80, 80, 4])
|
|
71
|
+
self.assertEqual(_OUTPUTS["score_32"]["shape"], [1, 20, 20, 1])
|
|
72
|
+
for name, o in _OUTPUTS.items():
|
|
73
|
+
self.assertEqual(o["dtype"], "int8", name)
|
|
74
|
+
self.assertEqual(len(_FIXTURE["onnxDetections"]), 1)
|
|
75
|
+
|
|
76
|
+
def test_the_yolo_postprocessor_gets_named_outputs_on_the_coral(self):
|
|
77
|
+
listed = list(_OUTPUTS)
|
|
78
|
+
self.assertEqual(edgetpu_output_keys(listed, "yolo"), listed)
|
|
79
|
+
|
|
80
|
+
def test_dequantised_scores_are_probabilities(self):
|
|
81
|
+
for name in ("score_8", "score_16", "score_32"):
|
|
82
|
+
o = _OUTPUTS[name]
|
|
83
|
+
probs = dequant_edgetpu_output(_raw(name), dtype_name="int8", scale=o["scale"], zero_point=o["zeroPoint"])
|
|
84
|
+
self.assertTrue(all(-1e-6 <= p <= 1.0 + 1e-6 for p in probs), name)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@unittest.skipUnless(_HAS_REAL_NUMPY, "yolo.py needs numpy")
|
|
88
|
+
class YoloxPlateEdgetpuDecode(unittest.TestCase):
|
|
89
|
+
def _decode(self, keys):
|
|
90
|
+
import numpy as np
|
|
91
|
+
from yolo import postprocess_yolo
|
|
92
|
+
|
|
93
|
+
preds = {}
|
|
94
|
+
for key, name in zip(keys, _OUTPUTS):
|
|
95
|
+
o = _OUTPUTS[name]
|
|
96
|
+
raw = np.array(_raw(name), dtype=np.int8).reshape(o["shape"])
|
|
97
|
+
preds[key] = dequant_edgetpu_output(raw, dtype_name="int8", scale=o["scale"], zero_point=o["zeroPoint"])
|
|
98
|
+
config = {"confidence": _STEP_GATE, "labels": ["plate"], "numClasses": 1, "nmsIouThreshold": 0.45,
|
|
99
|
+
"inputSize": 640}
|
|
100
|
+
return postprocess_yolo(preds, config, _FRAME["w"], _FRAME["h"], _FRAME["scale"],
|
|
101
|
+
tuple(_FRAME["pad"]))["detections"]
|
|
102
|
+
|
|
103
|
+
def test_the_int8_head_decodes_to_the_onnx_plate(self):
|
|
104
|
+
got = self._decode(edgetpu_output_keys(list(_OUTPUTS), "yolo"))
|
|
105
|
+
ref = _FIXTURE["onnxDetections"]
|
|
106
|
+
self.assertEqual(len(got), len(ref))
|
|
107
|
+
self.assertEqual(got[0]["class"], "plate")
|
|
108
|
+
self.assertGreaterEqual(_iou(got[0]["bbox"], ref[0]["bbox"]), 0.9)
|
|
109
|
+
self.assertAlmostEqual(got[0]["score"], ref[0]["score"], delta=0.05)
|
|
110
|
+
|
|
111
|
+
def test_a_head_sized_for_another_input_is_refused(self):
|
|
112
|
+
import numpy as np
|
|
113
|
+
from yolo import yolox_head_to_v8
|
|
114
|
+
|
|
115
|
+
preds = {name: np.zeros(_OUTPUTS[name]["shape"], dtype=np.float32) for name in _OUTPUTS}
|
|
116
|
+
with self.assertRaises(ValueError):
|
|
117
|
+
yolox_head_to_v8(preds, 416)
|
|
118
|
+
|
|
119
|
+
def test_positional_keys_never_reach_the_head_decode(self):
|
|
120
|
+
from yolo import yolox_head_to_v8
|
|
121
|
+
|
|
122
|
+
self.assertIsNone(yolox_head_to_v8({str(i): None for i in range(6)}, 640))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
unittest.main()
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Pins the YuNet Coral build on REAL output tensors (D710 enforcement).
|
|
2
|
+
|
|
3
|
+
`yunet-2023mar` ships a `tflite` format compiled for the Edge TPU
|
|
4
|
+
(`camstack-yunet-2023mar_edgetpu.tflite`). Its twelve outputs are int8, each
|
|
5
|
+
with its own scale and zero point, and `cls_*`/`obj_*` have identical shapes.
|
|
6
|
+
Three things must hold for the face step to emit what the ONNX build emits:
|
|
7
|
+
|
|
8
|
+
- the edgetpu predict closure keys this postprocessor's outputs by tensor NAME
|
|
9
|
+
(`edgetpu_output_keys`) — keyed "0".."11" the decoder cannot tell `cls_8`
|
|
10
|
+
from `obj_8` and refuses the frame;
|
|
11
|
+
- every output is dequantised (`edgetpu_dequant`) — read raw, a "probability"
|
|
12
|
+
is an integer in [-128, 127];
|
|
13
|
+
- the `yunet` decoder, at the model's own gate (0.85, D662), then finds the
|
|
14
|
+
ONNX build's faces, boxes AND landmarks (face-align depends on them).
|
|
15
|
+
|
|
16
|
+
The fixture (`testdata/yunet_int8_outputs.json`) holds the raw outputs of the
|
|
17
|
+
int8 graph the Edge TPU file is compiled from, on the public
|
|
18
|
+
`reference-images/3-persons.jpg` letterboxed to 640, and the ONNX build's
|
|
19
|
+
detections on the same frame. Produced by `scripts/build-yunet-edgetpu.py`.
|
|
20
|
+
|
|
21
|
+
`unittest.TestCase`: the key and dequant cases run on CI's bare interpreter;
|
|
22
|
+
the decode cases need numpy (`yunet.py` imports it) and are skipped, by name,
|
|
23
|
+
without it.
|
|
24
|
+
"""
|
|
25
|
+
import base64
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
import unittest
|
|
30
|
+
import zlib
|
|
31
|
+
|
|
32
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
33
|
+
sys.path.insert(0, os.path.dirname(_HERE)) # edgetpu_dequant lives one level up
|
|
34
|
+
|
|
35
|
+
from edgetpu_dequant import dequant_edgetpu_output, edgetpu_output_keys # noqa: E402
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import numpy as _numpy_probe
|
|
39
|
+
except ImportError:
|
|
40
|
+
_numpy_probe = None
|
|
41
|
+
_HAS_REAL_NUMPY = _numpy_probe is not None and hasattr(_numpy_probe, "ndarray")
|
|
42
|
+
|
|
43
|
+
with open(os.path.join(_HERE, "testdata", "yunet_int8_outputs.json"), encoding="utf-8") as _fh:
|
|
44
|
+
_FIXTURE = json.load(_fh)
|
|
45
|
+
|
|
46
|
+
_OUTPUTS = _FIXTURE["outputs"]
|
|
47
|
+
_FRAME = _FIXTURE["frame"]
|
|
48
|
+
_STEP_GATE = 0.85 # yunet-2023mar.defaultConfidence (D662)
|
|
49
|
+
_NAMES = [f"{k}_{s}" for k in ("cls", "obj", "bbox", "kps") for s in (8, 16, 32)]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _raw(name):
|
|
53
|
+
"""The int8 tensor as a flat list of ints — no numpy needed."""
|
|
54
|
+
data = zlib.decompress(base64.b64decode(_OUTPUTS[name]["zlibB64"]))
|
|
55
|
+
return [b - 256 if b > 127 else b for b in data]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _dequant_list(name):
|
|
59
|
+
o = _OUTPUTS[name]
|
|
60
|
+
return dequant_edgetpu_output(_raw(name), dtype_name=o["dtype"], scale=o["scale"], zero_point=o["zeroPoint"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _iou(a, b):
|
|
64
|
+
ix = max(0.0, min(a[2], b[2]) - max(a[0], b[0]))
|
|
65
|
+
iy = max(0.0, min(a[3], b[3]) - max(a[1], b[1]))
|
|
66
|
+
inter = ix * iy
|
|
67
|
+
union = (a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter
|
|
68
|
+
return inter / union if union > 0 else 0.0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class YunetEdgetpuContract(unittest.TestCase):
|
|
72
|
+
def test_fixture_is_the_shipped_contract(self):
|
|
73
|
+
self.assertEqual(sorted(_OUTPUTS), sorted(_NAMES))
|
|
74
|
+
for name, o in _OUTPUTS.items():
|
|
75
|
+
self.assertEqual(o["dtype"], "int8", name)
|
|
76
|
+
self.assertEqual(_OUTPUTS["cls_8"]["shape"], [1, 6400, 1])
|
|
77
|
+
self.assertEqual(_OUTPUTS["kps_32"]["shape"], [1, 400, 10])
|
|
78
|
+
self.assertGreaterEqual(len(_FIXTURE["onnxDetections"]), 3)
|
|
79
|
+
|
|
80
|
+
def test_yunet_outputs_are_keyed_by_name(self):
|
|
81
|
+
# The order the TFLite file lists them in is not the ONNX order.
|
|
82
|
+
listed = list(_OUTPUTS)
|
|
83
|
+
self.assertEqual(edgetpu_output_keys(listed, "yunet"), listed)
|
|
84
|
+
|
|
85
|
+
def test_ssd_keeps_its_positional_keys(self):
|
|
86
|
+
# ssd.py reads "0".."3"; TFLite_Detection_PostProcess names carry ":1".
|
|
87
|
+
names = ["TFLite_Detection_PostProcess", "TFLite_Detection_PostProcess:1",
|
|
88
|
+
"TFLite_Detection_PostProcess:2", "TFLite_Detection_PostProcess:3"]
|
|
89
|
+
self.assertEqual(edgetpu_output_keys(names, "ssd"), ["0", "1", "2", "3"])
|
|
90
|
+
|
|
91
|
+
def test_unnamed_or_duplicate_outputs_stay_positional(self):
|
|
92
|
+
self.assertEqual(edgetpu_output_keys(["cls_8", ""], "yunet"), ["0", "1"])
|
|
93
|
+
self.assertEqual(edgetpu_output_keys(["cls_8", "cls_8"], "yunet"), ["0", "1"])
|
|
94
|
+
|
|
95
|
+
def test_dequantised_probabilities_are_probabilities(self):
|
|
96
|
+
for name in ("cls_8", "obj_8", "cls_32", "obj_32"):
|
|
97
|
+
raw = _raw(name)
|
|
98
|
+
self.assertLess(min(raw), 0, name) # raw int8 is not a probability
|
|
99
|
+
probs = _dequant_list(name)
|
|
100
|
+
self.assertTrue(all(-1e-6 <= p <= 1.0 + 1e-6 for p in probs), name)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@unittest.skipUnless(_HAS_REAL_NUMPY, "yunet.py needs numpy")
|
|
104
|
+
class YunetEdgetpuDecode(unittest.TestCase):
|
|
105
|
+
def _decode(self, keys):
|
|
106
|
+
import numpy as np
|
|
107
|
+
from yunet import postprocess_yunet
|
|
108
|
+
|
|
109
|
+
preds = {}
|
|
110
|
+
for key, name in zip(keys, _OUTPUTS):
|
|
111
|
+
o = _OUTPUTS[name]
|
|
112
|
+
raw = np.array(_raw(name), dtype=np.int8).reshape(o["shape"])
|
|
113
|
+
preds[key] = dequant_edgetpu_output(raw, dtype_name="int8", scale=o["scale"], zero_point=o["zeroPoint"])
|
|
114
|
+
config = {"confidence": _STEP_GATE, "inputSize": 640, "nmsIouThreshold": 0.45}
|
|
115
|
+
return postprocess_yunet(preds, config, _FRAME["w"], _FRAME["h"], _FRAME["scale"],
|
|
116
|
+
tuple(_FRAME["pad"]))["detections"]
|
|
117
|
+
|
|
118
|
+
def test_the_int8_build_finds_the_onnx_faces(self):
|
|
119
|
+
got = self._decode(edgetpu_output_keys(list(_OUTPUTS), "yunet"))
|
|
120
|
+
ref = _FIXTURE["onnxDetections"]
|
|
121
|
+
self.assertEqual(len(got), len(ref))
|
|
122
|
+
for r in ref:
|
|
123
|
+
best = max(got, key=lambda g: _iou(g["bbox"], r["bbox"]))
|
|
124
|
+
self.assertGreaterEqual(_iou(best["bbox"], r["bbox"]), 0.9)
|
|
125
|
+
self.assertAlmostEqual(best["score"], r["score"], delta=0.03)
|
|
126
|
+
diag = ((r["bbox"][2] - r["bbox"][0]) ** 2 + (r["bbox"][3] - r["bbox"][1]) ** 2) ** 0.5
|
|
127
|
+
errors = [((p["x"] - q["x"]) ** 2 + (p["y"] - q["y"]) ** 2) ** 0.5
|
|
128
|
+
for p, q in zip(best["landmarks"], r["landmarks"])]
|
|
129
|
+
# face-align reads these. The build's parity pass measured a mean
|
|
130
|
+
# point error of 1.1% of the face diagonal (p95 2.8%) on 110 faces.
|
|
131
|
+
self.assertLess(sum(errors) / len(errors), 0.03 * diag)
|
|
132
|
+
self.assertLess(max(errors), 0.05 * diag)
|
|
133
|
+
|
|
134
|
+
def test_positional_keys_are_refused_not_misdecoded(self):
|
|
135
|
+
with self.assertRaises(ValueError):
|
|
136
|
+
self._decode([str(i) for i in range(len(_OUTPUTS))])
|
|
137
|
+
|
|
138
|
+
def test_a_vectorised_dequant_matches_the_list_walk(self):
|
|
139
|
+
import numpy as np
|
|
140
|
+
|
|
141
|
+
o = _OUTPUTS["bbox_8"]
|
|
142
|
+
arr = dequant_edgetpu_output(np.array(_raw("bbox_8"), dtype=np.int8), dtype_name="int8",
|
|
143
|
+
scale=o["scale"], zero_point=o["zeroPoint"])
|
|
144
|
+
self.assertEqual(str(arr.dtype), "float32")
|
|
145
|
+
self.assertTrue(np.allclose(arr, np.array(_dequant_list("bbox_8"), dtype=np.float32), atol=1e-6))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
unittest.main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"frame": {"w": 1280, "h": 853, "scale": 0.5, "pad": [0, 107]}, "outputs": {"reg_32": {"shape": [1, 20, 20, 4], "dtype": "int8", "scale": 0.031176157295703888, "zeroPoint": 30, "zlibB64": "eNoV1Nlv24YBx3HZsiRTIkWdpCRKJMX7piiREmXdsmw1ki85sVMfSVfHreOtyZrt79keVgzDsIetGLJsRbOh6Lp1XTAUTQO0b20xIGlnp06d1LasY+7D5/X79MNPUhY5Re0JSmZNULMbsprbUtT8NVUuXNdk+5om2duaaG+pkr2pivaGIhZfVnh7TWYLl0XGWhJoo8MyWoum5AbFMhVGpBtphZmjdL5DGcoqmzU2pJy5rWQvmkbxmqoXtxSttClfkLTyhqiWrwpS+QovzqxwvN2l2dwsyehFjFGsuMiaKYaQYyIhRCUmE1blGqpnOoRhXXRLa6JRW1cyjcuK3rwsZxo9SW/0BLWxLMj1BV6otllupkbSZi5Bakw4LSRhnmIRAo2FcMQPkgnUx5E8LPNFRM9eIrKlHp9tXFEzs5czWmtV11urmjbXU5XWkiK2ujJXnxPo4gxNGnIiyaNBLO0HKRILxUPhaDTogREEBpNYMsgxRlw35hmzuqZn57csrb1tSy9tF6TO1g/yQvdli+1cyVGtRZ2szEpE1qAwBkXiGACROBpMBRKpRCiWimM0RlI6IalNySytWXbrlRlz/kZFbe9WpPbrVeHSXo3v7tWYxd0KufCjEn5ps0g2Vwtpu6ljopBGCSSWwlPxdIQXKExVGaGqS8aiadgbRbt6o2Y39hqZxs2mXNufFWo/nuMqt+boyu15on5rPtHcn4/N7s6R9etNptIr4bmqnuQUDk8rFIdkNJksFTPqatUyX6kXCrv1or3XsAo361p+vyHmf9LkzNstLv/mPG3e6aSsO4uodXsplt9fTFu7HdbebFL5hTIhlzMUbbNKwjZ0ulU19Y1mXtuZLeg36ra+U7a0nRlDea0syzdrvHRrVlDfbDPKzxZS0p2lOPvGcoLc7abxrQZLLtsU0zTwtCXjZIEyktWcybxUt+T1hilvVS31asHOXM1axnpW1zctWd6pyNx+U2V/2haZn3fS+J2lFLrXwaBeGXPmJdwpphPuFB6Hkqk4KiZyqXouR8yVLHq5UKCWMybf5vJmm5QvtkplVmRC3LA47tWSkr5ZEfE3GlRyr4nBCxbqxJPhs1Nv6OTxBHzyXyc8OPT4QQhUwqZyQdNjJVFL2pTFFZJmtoQJ1lwMNRfYEL+SJYgVg0GWFDawpOH+poK6Ulho9LUTPv3gBOr/5Qj4/p0j7+BBf9r7YoL2YGnC4ccIIBQmfWhIRJiIntZQ2mjEwmID8ZMtNoKYWMJPhTEgGUHckTA8fjYNnX94Fjj9wxF8/utvps/fegIM7z3zQIfD2GgYjr74Egz3v5gITjydiLl8IB3hA+lkMYoi9UTAzycgj9MTnjoYBh1PRn7Hk7F3+PkIOr9/HDj7zQE0+MVjaPDLr73ju8cAdOgIjR4DwRfvjeCzd0/A849OfOMnjrDP5UYCsBcFpn3Rqf854PGHx/7RvUPv6K9HwPhf34ODD06h/p+eBfq//SboeOsAGv3q0Dt+5xQAv52Ehl85fSfv9uHzt5+Do7vfgaP3n/snHw6izs+GIdeD49Dk/afBibcPoPHvDrzOP3/nHf/tFBzeP4aG9771j+8+BR2/PwJHf3zuG/9j6PM9cwOTLzzuwSdDwPHPvnfy76eA86OhZ+rRwDX12cDtfnTucf/nDPD8u+/xPDj3TH8ynHZ/OgRcD4eA89HIM/Fx3+34+AwYPxyAzi+nA/AEHAxgCa/XE53yOWAXOA644KmoJwzGp6OhBICE4kAUQnwIEAERTxCMTYehmDfijwFhX8QV8gUn/d7QVMAXcoX9YQCLYUmSZSsKLVQ0UqpLpDovkUZXocxVjS6u6+mZNQ0v9tRUoSsThbZIFFoCaTd5olDjCKvC4GaZIc0ySxozHC7aInWxtgzXFXShJ+jyVVpTt8mM9iqZUV8jVW3v4mteJ3l9B+eM60kxt5EQc+txMXclJpqrqJBbQYTc0g9QLtPFWKVNMGyD/D+01aR5"}, "score_8": {"shape": [1, 80, 80, 1], "dtype": "int8", "scale": 0.0028875411953777075, "zeroPoint": -128, "zlibB64": "eNrt0CEBAAAMArDqKHLT4epii7AEAAAA+KoKAABOBrlRgLs="}, "score_32": {"shape": [1, 20, 20, 1], "dtype": "int8", "scale": 0.0024907481856644154, "zeroPoint": -128, "zlibB64": "eNpraBgFgwkAAK60yAE="}, "score_16": {"shape": [1, 40, 40, 1], "dtype": "int8", "scale": 0.003375293919816613, "zeroPoint": -128, "zlibB64": "eNpraBgFo2AUDAOQU8JElLqSshKi1KkWB4wG6ijADwAR3B7W"}, "reg_8": {"shape": [1, 80, 80, 4], "dtype": "int8", "scale": 0.02890142798423767, "zeroPoint": 11, "zlibB64": "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"}, "reg_16": {"shape": [1, 40, 40, 4], "dtype": "int8", "scale": 0.04153960198163986, "zeroPoint": 35, "zlibB64": "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"}}, "int8Detections": [{"score": 0.8303, "bbox": [301.8, 569.5, 492.8, 634.4]}], "onnxDetections": [{"score": 0.8295, "bbox": [300.4, 570.4, 494.4, 633.2]}], "producedBy": "scripts/build-yolox-plate-edgetpu.py: raw outputs of the int8 graph the Edge TPU file is compiled from, on reference-images/1-car-plate.jpg letterboxed to 640; onnxDetections = the published ONNX at the step gate"}
|