@camstack/addon-pipeline 1.2.609 → 1.2.610
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/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DtSn_bza.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BypETcj_.mjs} +3 -3
- package/dist/stream-broker/{hostInit-DTCC4mJ-.mjs → hostInit-DvGKmNcA.mjs} +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/edgetpu_device.py +32 -1
- package/python/inference_pool.py +4 -4
- package/python/postprocessors/test_yolov9_edgetpu_concat.py +145 -0
- package/python/postprocessors/yolo_split_dfl.py +58 -9
- package/python/requirements-edgetpu.txt +13 -10
- package/python/test_edgetpu_device.py +30 -1
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.142",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_stream_broker_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.392",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.309",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.392",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.142",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.309",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BypETcj_.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.610",
|
|
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",
|
package/python/edgetpu_device.py
CHANGED
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import ctypes
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
|
-
from typing import Callable, Optional
|
|
13
|
+
from typing import Callable, Mapping, Optional
|
|
14
14
|
|
|
15
15
|
_DEVICE = re.compile(r"^(usb|pci)(:\d+)?$")
|
|
16
16
|
|
|
@@ -32,6 +32,37 @@ def edgetpu_delegate_options(device: Optional[str]) -> Optional[dict[str, str]]:
|
|
|
32
32
|
return {"device": dev}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
# Where the hub image's installer has always put the delegate.
|
|
36
|
+
LEGACY_EDGETPU_LIB = "/data/deps/edgetpu/lib/libedgetpu.so.1"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def edgetpu_library_path(
|
|
40
|
+
env: "Mapping[str, str]",
|
|
41
|
+
platform: str,
|
|
42
|
+
exists: Optional[Callable[[str], bool]] = None,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""The libedgetpu file this pool loads.
|
|
45
|
+
|
|
46
|
+
The same lookup as the TS probe (``chooseEdgetpuDelegate`` in
|
|
47
|
+
``@camstack/types`` ``deps/edgetpu-delegate.ts``): ``CAMSTACK_EDGETPU_LIB``
|
|
48
|
+
when set (followed even if missing — ``load_delegate`` then names it); the
|
|
49
|
+
copy the deps downloader staged under ``<CAMSTACK_DATA>/deps/edgetpu/lib/``
|
|
50
|
+
(``libedgetpu.1.dylib`` on macOS, ``libedgetpu.so.1`` elsewhere); else the
|
|
51
|
+
hub's historical path, which on the hub image is the same file.
|
|
52
|
+
"""
|
|
53
|
+
check = exists or os.path.exists
|
|
54
|
+
override = (env.get("CAMSTACK_EDGETPU_LIB") or "").strip()
|
|
55
|
+
if override:
|
|
56
|
+
return override
|
|
57
|
+
data_root = env.get("CAMSTACK_DATA") or env.get("CAMSTACK_DATA_DIR") or ""
|
|
58
|
+
if data_root:
|
|
59
|
+
name = "libedgetpu.1.dylib" if platform == "darwin" else "libedgetpu.so.1"
|
|
60
|
+
staged = os.path.join(data_root, "deps", "edgetpu", "lib", name)
|
|
61
|
+
if check(staged):
|
|
62
|
+
return staged
|
|
63
|
+
return LEGACY_EDGETPU_LIB
|
|
64
|
+
|
|
65
|
+
|
|
35
66
|
# The libedgetpu "std" delegate links libusb even when it drives a PCIe Coral.
|
|
36
67
|
# The installer (edgetpu-install.sh) drops this copy beside the delegate for an
|
|
37
68
|
# image that lacks the system package.
|
package/python/inference_pool.py
CHANGED
|
@@ -830,12 +830,12 @@ def _init_runtime(runtime: str, device: str = "") -> None:
|
|
|
830
830
|
# The delegate is created ONCE here and shared across all model slots (the
|
|
831
831
|
# USB device is a single hardware context). See docs: the delegate's TF
|
|
832
832
|
# version MUST match the interpreter — feranick libedgetpu tf2.17.1 pairs
|
|
833
|
-
# with ai-edge-litert 2.1.6.
|
|
833
|
+
# with ai-edge-litert 2.1.6. Staged by the deps downloader beside the
|
|
834
|
+
# edgetpu requirements (`requirements-edgetpu.txt`), or edgetpu-install.sh.
|
|
834
835
|
import threading
|
|
835
836
|
from ai_edge_litert.interpreter import Interpreter, load_delegate
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
)
|
|
837
|
+
from edgetpu_device import edgetpu_library_path
|
|
838
|
+
lib_path = edgetpu_library_path(os.environ, sys.platform)
|
|
839
839
|
# Multiple Corals (USB and/or PCIe) coexist and run concurrently, one
|
|
840
840
|
# pool each. libedgetpu addresses a unit by device string; the TS side
|
|
841
841
|
# sends the explicit ordinal ("usb:0", "usb:1", "pci:0", "pci:1") as the
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""YOLOv9 Edge TPU build with ALL strides concatenated (the community Frigate export).
|
|
2
|
+
|
|
3
|
+
The EdgeTPU-compiled YOLOv9 detectors the Frigate community publishes
|
|
4
|
+
(`dbro/frigate-detector-edgetpu-yolo9`, releases v1.0 and v1.5) were read with
|
|
5
|
+
a flatbuffer dump of the files themselves (2026-10-22):
|
|
6
|
+
|
|
7
|
+
yolov9-s-relu6-best_320_int8_edgetpu.tflite (v1.0)
|
|
8
|
+
in : serving_default_images:0 [1, 320, 320, 3] uint8 scale 1/255 zp 0
|
|
9
|
+
out: PartitionedCall:1 [1, 2100, 17] int8 scale 0.027195 zp 19 (class LOGITS)
|
|
10
|
+
PartitionedCall:0 [1, 2100, 64] int8 scale 0.079690 zp -45 (DFL l,t,r,b × 16)
|
|
11
|
+
yolov9-s-relu6-tpumax_512_int8_edgetpu.tflite (v1.5)
|
|
12
|
+
out: [1, 5376, 64] int8, [1, 5376, 17] int8, and a THIRD [1, 5376, 1] int8
|
|
13
|
+
|
|
14
|
+
N is the three strides CONCATENATED, 8 then 16 then 32, each row-major
|
|
15
|
+
(2100 = 40² + 20² + 10²; 5376 = 64² + 32² + 16²) — not one tensor pair per
|
|
16
|
+
stride like the Scrypted export `test_yolo_split_dfl.py` pins. The anchor of
|
|
17
|
+
cell (gx, gy) is ((gx + 0.5) · s, (gy + 0.5) · s); a box is anchor ∓ ltrb · s.
|
|
18
|
+
|
|
19
|
+
Every fixture below is QUANTISED with the real file's scale/zero-point and
|
|
20
|
+
dequantised through `edgetpu_dequant` exactly as the pool's edgetpu predict
|
|
21
|
+
closure does; the expected boxes are computed by hand in the comments.
|
|
22
|
+
|
|
23
|
+
Pure lists — runs on CI's bare interpreter (no numpy).
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import math
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import unittest
|
|
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 # noqa: E402
|
|
36
|
+
from yolo_split_dfl import postprocess_yolo_split_dfl # noqa: E402
|
|
37
|
+
|
|
38
|
+
# The real quantisation of yolov9-s-relu6-best_320_int8_edgetpu.tflite.
|
|
39
|
+
CLS_SCALE, CLS_ZP = 0.027195103466510773, 19
|
|
40
|
+
BOX_SCALE, BOX_ZP = 0.07969039678573608, -45
|
|
41
|
+
|
|
42
|
+
COCO17 = [
|
|
43
|
+
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
|
|
44
|
+
"boat", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
Q_HOT, Q_COLD = 127, -128 # int8 extremes: DFL bin logits 13.7 / -6.6 after dequant
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _dfl(l: int, t: int, r: int, b: int) -> list[int]:
|
|
51
|
+
out: list[int] = []
|
|
52
|
+
for bin_index in (l, t, r, b):
|
|
53
|
+
out += [Q_HOT if i == bin_index else Q_COLD for i in range(16)]
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _sigmoid(q: int) -> float:
|
|
58
|
+
return 1.0 / (1.0 + math.exp(-((q - CLS_ZP) * CLS_SCALE)))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _quantised_outputs(cells: dict[int, tuple[tuple[int, int, int, int], int, int]], n: int) -> tuple[list, list]:
|
|
62
|
+
"""cells: candidate index → (ltrb bins, class id, quantised class logit)."""
|
|
63
|
+
box = [[_dfl(0, 0, 0, 0) for _ in range(n)]]
|
|
64
|
+
cls = [[[Q_COLD] * len(COCO17) for _ in range(n)]]
|
|
65
|
+
for idx, (ltrb, class_id, q) in cells.items():
|
|
66
|
+
box[0][idx] = _dfl(*ltrb)
|
|
67
|
+
cls[0][idx][class_id] = q
|
|
68
|
+
return box, cls
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dequant_cls(cls: list) -> list:
|
|
72
|
+
return dequant_edgetpu_output(cls, dtype_name="int8", scale=CLS_SCALE, zero_point=CLS_ZP)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _dequant_box(box: list) -> list:
|
|
76
|
+
return dequant_edgetpu_output(box, dtype_name="int8", scale=BOX_SCALE, zero_point=BOX_ZP)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
CONFIG = {
|
|
80
|
+
"labels": COCO17,
|
|
81
|
+
"confidence": 0.5,
|
|
82
|
+
"preprocessMode": "letterbox",
|
|
83
|
+
"inputSize": 320,
|
|
84
|
+
"inputWidth": 320,
|
|
85
|
+
"inputHeight": 320,
|
|
86
|
+
"nmsIouThreshold": 0.45,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 640×480 frame letterboxed to 320: scale 0.5, content 320×240, pad (0, 40).
|
|
90
|
+
ORIG_W, ORIG_H, SCALE, PAD = 640, 480, 0.5, (0, 40)
|
|
91
|
+
|
|
92
|
+
# Stride 16 (grid 20, offset 40² = 1600), cell (gx 5, gy 7) → idx 1600 + 7·20 + 5 = 1745.
|
|
93
|
+
# anchor (5.5·16, 7.5·16) = (88, 120); ltrb (2, 3, 4, 1) → (56, 72)-(152, 136) input px
|
|
94
|
+
# → original ((56-0)/0.5, (72-40)/0.5, (152-0)/0.5, (136-40)/0.5) = (112, 64, 304, 192). car.
|
|
95
|
+
CAR_IDX, CAR_Q = 1745, 99
|
|
96
|
+
# Its neighbour (gx 6) → idx 1746, anchor (104, 120); ltrb (3, 3, 3, 1) → the SAME box,
|
|
97
|
+
# a weaker score: NMS must keep only the car above.
|
|
98
|
+
DUP_IDX, DUP_Q = 1746, 60
|
|
99
|
+
# Stride 32 (grid 10, offset 1600 + 400 = 2000), cell (gx 3, gy 2) → idx 2000 + 2·10 + 3 = 2023.
|
|
100
|
+
# anchor (3.5·32, 2.5·32) = (112, 80); ltrb (1, 1, 2, 2) → (80, 48)-(176, 144) input px
|
|
101
|
+
# → original (160, 16, 352, 208). person.
|
|
102
|
+
PERSON_IDX, PERSON_Q = 2023, 127
|
|
103
|
+
|
|
104
|
+
CELLS = {
|
|
105
|
+
CAR_IDX: ((2, 3, 4, 1), 2, CAR_Q),
|
|
106
|
+
DUP_IDX: ((3, 3, 3, 1), 2, DUP_Q),
|
|
107
|
+
PERSON_IDX: ((1, 1, 2, 2), 0, PERSON_Q),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class YoloV9EdgeTpuConcatenatedStrides(unittest.TestCase):
|
|
112
|
+
def _decode(self, predictions: dict) -> list[dict]:
|
|
113
|
+
out = postprocess_yolo_split_dfl(predictions, CONFIG, ORIG_W, ORIG_H, SCALE, PAD)
|
|
114
|
+
self.assertEqual(out["kind"], "detections")
|
|
115
|
+
return sorted(out["detections"], key=lambda d: d["class"])
|
|
116
|
+
|
|
117
|
+
def _assert_expected(self, dets: list[dict]) -> None:
|
|
118
|
+
self.assertEqual([d["class"] for d in dets], ["car", "person"])
|
|
119
|
+
car, person = dets
|
|
120
|
+
self.assertEqual(car["bbox"], [112.0, 64.0, 304.0, 192.0])
|
|
121
|
+
self.assertEqual(person["bbox"], [160.0, 16.0, 352.0, 208.0])
|
|
122
|
+
self.assertEqual(car["score"], round(_sigmoid(CAR_Q), 4))
|
|
123
|
+
self.assertEqual(person["score"], round(_sigmoid(PERSON_Q), 4))
|
|
124
|
+
|
|
125
|
+
def test_v1_0_two_tensors_scores_first(self) -> None:
|
|
126
|
+
box, cls = _quantised_outputs(CELLS, 2100)
|
|
127
|
+
# Positional keys in the file's output order: scores, then boxes.
|
|
128
|
+
self._assert_expected(self._decode({"0": _dequant_cls(cls), "1": _dequant_box(box)}))
|
|
129
|
+
|
|
130
|
+
def test_v1_5_third_single_channel_tensor_is_not_the_class_tensor(self) -> None:
|
|
131
|
+
box, cls = _quantised_outputs(CELLS, 2100)
|
|
132
|
+
# The v1.5 files add a [1, N, 1] output AFTER the class tensor. Read as the
|
|
133
|
+
# class tensor it would score every candidate on ONE cold channel.
|
|
134
|
+
aux = [[[Q_COLD] for _ in range(2100)]]
|
|
135
|
+
preds = {"0": _dequant_box(box), "1": _dequant_cls(cls), "2": _dequant_cls(aux)}
|
|
136
|
+
self._assert_expected(self._decode(preds))
|
|
137
|
+
|
|
138
|
+
def test_a_concatenated_n_that_matches_no_input_size_is_refused(self) -> None:
|
|
139
|
+
box, cls = _quantised_outputs({}, 2101)
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
self._decode({"0": _dequant_cls(cls), "1": _dequant_box(box)})
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
unittest.main()
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
"""YOLO split-head DFL postprocessor (
|
|
1
|
+
"""YOLO split-head DFL postprocessor (YOLOv9 TFLite / EdgeTPU).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
ONNX ``[1, 7, 2100]`` packed layout:
|
|
3
|
+
Two split layouts reach it, neither the ONNX ``[1, 7, 2100]`` packed one:
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
* Scrypted's TFLite builds emit SIX tensors (3 strides × box+cls):
|
|
6
|
+
|
|
7
|
+
box: [1, H·W, 4·reg_max] with ``reg_max=16`` (DFL distances l,t,r,b)
|
|
8
|
+
cls: [1, H·W, C] logits over person/vehicle/animal (C=3)
|
|
9
|
+
|
|
10
|
+
* The community Frigate EdgeTPU export (``dbro/frigate-detector-edgetpu-yolo9``,
|
|
11
|
+
read from the published files) emits ONE pair with the strides
|
|
12
|
+
CONCATENATED, 8 then 16 then 32, each row-major:
|
|
13
|
+
|
|
14
|
+
box: [1, N, 64] cls: [1, N, C] N = Σ (H/s)·(W/s) — 2100 at 320², 5376 at 512²
|
|
15
|
+
|
|
16
|
+
and its v1.5 files add a third ``[1, N, 1]`` tensor that is NOT the class
|
|
17
|
+
tensor: the widest non-box tensor of a given ``N`` is.
|
|
8
18
|
|
|
9
19
|
Pairs are matched by shared spatial length ``N`` (1600/400/100 for 320²), not
|
|
10
20
|
by positional key order — TFLite output order has drifted between exports.
|
|
@@ -110,7 +120,10 @@ def _pair_box_cls(predictions: dict) -> list[tuple[Any, Any]]:
|
|
|
110
120
|
n, width = shape
|
|
111
121
|
if width == 4 * REG_MAX:
|
|
112
122
|
boxes[n] = tensor
|
|
113
|
-
elif width > 0 and
|
|
123
|
+
elif width > 0 and (n not in classes or width > _shape(classes[n])[1]):
|
|
124
|
+
# The WIDEST non-box tensor of this N is the class tensor: a
|
|
125
|
+
# one-channel sibling (the v1.5 Frigate export's [1, N, 1]) never
|
|
126
|
+
# displaces it, whatever the output order.
|
|
114
127
|
classes[n] = tensor
|
|
115
128
|
pairs: list[tuple[Any, Any]] = []
|
|
116
129
|
for n, box in boxes.items():
|
|
@@ -128,6 +141,37 @@ def _pair_box_cls(predictions: dict) -> list[tuple[Any, Any]]:
|
|
|
128
141
|
return pairs
|
|
129
142
|
|
|
130
143
|
|
|
144
|
+
CONCAT_STRIDES: tuple[int, ...] = (8, 16, 32)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _concat_stride_cells(n: int, in_w: int, in_h: int) -> "list[tuple[int, int, int]] | None":
|
|
148
|
+
"""``[(offset, stride, grid_w), …]`` when ``N`` is every stride of the input
|
|
149
|
+
concatenated (8, 16, 32 in that order), else ``None``."""
|
|
150
|
+
cells: list[tuple[int, int, int]] = []
|
|
151
|
+
offset = 0
|
|
152
|
+
for stride in CONCAT_STRIDES:
|
|
153
|
+
if in_w % stride or in_h % stride:
|
|
154
|
+
return None
|
|
155
|
+
cells.append((offset, stride, in_w // stride))
|
|
156
|
+
offset += (in_w // stride) * (in_h // stride)
|
|
157
|
+
return cells if offset == n else None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _segments(
|
|
161
|
+
box: Any, cls: Any, in_w: int, in_h: int, input_size: int
|
|
162
|
+
) -> list[tuple[Any, Any, int, int]]:
|
|
163
|
+
"""``(box rows, cls rows, stride, grid_w)`` per stride of one box/cls pair."""
|
|
164
|
+
n = _shape(box)[0]
|
|
165
|
+
concat = _concat_stride_cells(n, in_w, in_h)
|
|
166
|
+
if concat is None:
|
|
167
|
+
return [(box, cls, _infer_stride(n, input_size), int(round(math.sqrt(n))))]
|
|
168
|
+
ends = [offset for offset, _, _ in concat[1:]] + [n]
|
|
169
|
+
return [
|
|
170
|
+
(box[offset:end], cls[offset:end], stride, grid_w)
|
|
171
|
+
for (offset, stride, grid_w), end in zip(concat, ends)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
131
175
|
def _infer_stride(n: int, input_size: int) -> int:
|
|
132
176
|
"""N = (input_size / stride)² → stride = input_size / sqrt(N)."""
|
|
133
177
|
side = int(round(math.sqrt(n)))
|
|
@@ -191,12 +235,17 @@ def postprocess_yolo_split_dfl(
|
|
|
191
235
|
labels = config.get("labels") or ["person", "vehicle", "animal"]
|
|
192
236
|
nms_iou = float(config.get("nmsIouThreshold", 0.45))
|
|
193
237
|
input_size = int(config.get("inputSize") or config.get("inputWidth") or 320)
|
|
238
|
+
in_w = int(config.get("inputWidth") or input_size)
|
|
239
|
+
in_h = int(config.get("inputHeight") or input_size)
|
|
194
240
|
|
|
241
|
+
segments = [
|
|
242
|
+
seg
|
|
243
|
+
for box_pair, cls_pair in _pair_box_cls(predictions)
|
|
244
|
+
for seg in _segments(box_pair, cls_pair, in_w, in_h, input_size)
|
|
245
|
+
]
|
|
195
246
|
candidates: list[dict] = []
|
|
196
|
-
for box_tensor, cls_tensor in
|
|
247
|
+
for box_tensor, cls_tensor, stride, grid in segments:
|
|
197
248
|
n = _shape(box_tensor)[0]
|
|
198
|
-
stride = _infer_stride(n, input_size)
|
|
199
|
-
grid = int(round(math.sqrt(n)))
|
|
200
249
|
num_classes = _shape(cls_tensor)[1]
|
|
201
250
|
|
|
202
251
|
for i in range(n):
|
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
# and is numpy-2 native.
|
|
10
10
|
ai-edge-litert==2.1.6
|
|
11
11
|
|
|
12
|
-
# libedgetpu
|
|
13
|
-
# install it
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
12
|
+
# libedgetpu (the Coral delegate) is a native shared library — pip cannot
|
|
13
|
+
# install it — and its TensorFlow version MUST match the interpreter (stock
|
|
14
|
+
# Coral libedgetpu 16.0/grouper is built against old TFLite and FAILS at invoke:
|
|
15
|
+
# "EdgeTpuDelegateForCustomOp failed to invoke"). The line below asks the deps
|
|
16
|
+
# downloader to stage feranick's libedgetpu rebuilt against TF 2.17.1 right
|
|
17
|
+
# after this file installs (`@camstack/types` `deps/edgetpu-delegate.ts`:
|
|
18
|
+
# pinned, SHA-256 verified; on Linux libusb-1.0.so.0 beside it, because the std
|
|
19
|
+
# delegate links libusb even for a PCIe Coral and the pool preloads that copy).
|
|
20
|
+
# It lands in <CAMSTACK_DATA>/deps/edgetpu/lib/ — /data/deps/edgetpu/lib on the
|
|
21
|
+
# hub image, where `edgetpu-install.sh` (beside this file) has always put it.
|
|
22
|
+
# Linux x64/arm64 and Apple Silicon only; Windows has no TF 2.17.1 build.
|
|
23
|
+
# CAMSTACK_EDGETPU_LIB overrides the path the pool loads.
|
|
24
|
+
# camstack-native-dep: edgetpu-delegate
|
|
22
25
|
#
|
|
23
26
|
# Host prerequisite, USB: /dev/bus/usb passed through plus a
|
|
24
27
|
# `--device-cgroup-rule 'c 189:* rmw'` rule (the Coral re-enumerates its USB
|
|
@@ -14,7 +14,7 @@ import tempfile
|
|
|
14
14
|
import unittest
|
|
15
15
|
from unittest import mock
|
|
16
16
|
|
|
17
|
-
from edgetpu_device import edgetpu_delegate_options, preload_libusb
|
|
17
|
+
from edgetpu_device import edgetpu_delegate_options, edgetpu_library_path, preload_libusb
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class EdgetpuDelegateOptions(unittest.TestCase):
|
|
@@ -70,5 +70,34 @@ class PreloadLibusb(unittest.TestCase):
|
|
|
70
70
|
cdll.assert_called_once_with(path, mode=ctypes.RTLD_GLOBAL)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
class EdgetpuLibraryPath(unittest.TestCase):
|
|
74
|
+
"""The delegate the pool loads — the same lookup as the TS probe
|
|
75
|
+
(`chooseEdgetpuDelegate` in `@camstack/types`): override, then the copy
|
|
76
|
+
staged under the node data root, then the hub's historical path."""
|
|
77
|
+
|
|
78
|
+
LEGACY = "/data/deps/edgetpu/lib/libedgetpu.so.1"
|
|
79
|
+
|
|
80
|
+
def test_a_desktop_loads_the_dylib_staged_under_its_data_root(self) -> None:
|
|
81
|
+
staged = "/Users/me/CamStack/data/deps/edgetpu/lib/libedgetpu.1.dylib"
|
|
82
|
+
path = edgetpu_library_path(
|
|
83
|
+
{"CAMSTACK_DATA": "/Users/me/CamStack/data"}, "darwin", lambda p: p == staged
|
|
84
|
+
)
|
|
85
|
+
self.assertEqual(path, staged)
|
|
86
|
+
|
|
87
|
+
def test_a_linux_agent_with_another_data_root_loads_its_staged_copy(self) -> None:
|
|
88
|
+
staged = "/srv/cs/deps/edgetpu/lib/libedgetpu.so.1"
|
|
89
|
+
env = {"CAMSTACK_DATA_DIR": "/srv/cs"}
|
|
90
|
+
self.assertEqual(edgetpu_library_path(env, "linux", lambda p: p in (staged, self.LEGACY)), staged)
|
|
91
|
+
|
|
92
|
+
def test_the_override_wins_even_when_missing(self) -> None:
|
|
93
|
+
# The operator named a file: loading another build instead would hide it.
|
|
94
|
+
env = {"CAMSTACK_EDGETPU_LIB": "/opt/x.so", "CAMSTACK_DATA": "/d"}
|
|
95
|
+
self.assertEqual(edgetpu_library_path(env, "linux", lambda p: False), "/opt/x.so")
|
|
96
|
+
|
|
97
|
+
def test_nothing_staged_falls_back_to_the_historical_hub_path(self) -> None:
|
|
98
|
+
self.assertEqual(edgetpu_library_path({}, "linux", lambda p: False), self.LEGACY)
|
|
99
|
+
self.assertEqual(edgetpu_library_path({"CAMSTACK_DATA": "/d"}, "linux", lambda p: False), self.LEGACY)
|
|
100
|
+
|
|
101
|
+
|
|
73
102
|
if __name__ == "__main__":
|
|
74
103
|
unittest.main()
|