@camstack/addon-pipeline 1.0.8 → 1.1.0
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/audio-analyzer/index.js +5 -5
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +636 -659
- package/dist/detection-pipeline/index.mjs +624 -647
- package/dist/{model-download-service-C7AjBsX9-rXY-VFDk.js → model-download-service-RxAOiYvX-C8rTRJy_.js} +36 -6
- package/dist/{model-download-service-C7AjBsX9-B0ekM6dF.mjs → model-download-service-RxAOiYvX-CMAvhgO7.mjs} +36 -6
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BFy9iszl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs} +3 -3
- package/dist/stream-broker/{hostInit-zRy9SzlX.mjs → hostInit-zLZbYJcg.mjs} +3 -3
- package/dist/stream-broker/index.js +7 -7
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/inference_pool.py +65 -6
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
|
@@ -1,11 +1,255 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
2
|
import { A as nodePin, B as BaseAddon, C as detectionPipelineCapability, J as hydrateSchema, L as supportedRuntimes$1, P as runtimeDevices$1, T as hfModelUrl, W as EventCategory, Z as parseJsonUnknown, a as COCO_TO_MACRO, et as sleep, i as COCO_80_LABELS, j as pipelineExecutorCapability, p as YAMNET_TO_MACRO, q as createEvent, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as evaluateZoneRules, x as defaultDeviceFor$1, z as errMsg } from "../dist-BA6DR_jV.mjs";
|
|
3
|
-
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-
|
|
3
|
+
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-RxAOiYvX-CMAvhgO7.mjs";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import sharp from "sharp";
|
|
9
|
+
//#region src/detection-pipeline/engine-store-keys.ts
|
|
10
|
+
/**
|
|
11
|
+
* Per-node scoping for the detection-pipeline engine cascade.
|
|
12
|
+
*
|
|
13
|
+
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
14
|
+
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
15
|
+
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
16
|
+
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
17
|
+
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
18
|
+
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
19
|
+
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
20
|
+
* another node that has no NPU.
|
|
21
|
+
*
|
|
22
|
+
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
23
|
+
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
24
|
+
* before this change, or by an older build) is read as a migration fallback and
|
|
25
|
+
* re-persisted under the node-scoped key on the next write.
|
|
26
|
+
*/
|
|
27
|
+
var ENGINE_CASCADE_KEYS = [
|
|
28
|
+
"engineBackend",
|
|
29
|
+
"engineDevice",
|
|
30
|
+
"probedBestEngine"
|
|
31
|
+
];
|
|
32
|
+
function isEngineCascadeKey(key) {
|
|
33
|
+
return ENGINE_CASCADE_KEYS.includes(key);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
37
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
38
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
39
|
+
*/
|
|
40
|
+
function normalizeEngineNodeId(rawNodeId) {
|
|
41
|
+
const raw = rawNodeId ?? "hub";
|
|
42
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
43
|
+
}
|
|
44
|
+
/** The node-scoped store key for an engine cascade field. */
|
|
45
|
+
function nodeEngineKey(base, nodeId) {
|
|
46
|
+
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
50
|
+
* otherwise the legacy un-scoped value (migration), otherwise undefined.
|
|
51
|
+
*/
|
|
52
|
+
function readNodeEngineValue(store, base, nodeId) {
|
|
53
|
+
const scoped = store[nodeEngineKey(base, nodeId)];
|
|
54
|
+
return scoped !== void 0 ? scoped : store[base];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
58
|
+
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
59
|
+
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
60
|
+
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
61
|
+
* node's form).
|
|
62
|
+
*/
|
|
63
|
+
function projectNodeEngine(store, nodeId) {
|
|
64
|
+
const out = {};
|
|
65
|
+
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
66
|
+
for (const key of Object.keys(store)) {
|
|
67
|
+
const atIdx = key.indexOf("@");
|
|
68
|
+
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
69
|
+
scopedForAnyNode.add(key);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
out[key] = store[key];
|
|
73
|
+
}
|
|
74
|
+
for (const base of ENGINE_CASCADE_KEYS) {
|
|
75
|
+
const value = readNodeEngineValue(store, base, nodeId);
|
|
76
|
+
if (value !== void 0) out[base] = value;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/detection-pipeline/runtimes.ts
|
|
82
|
+
var KNOWN_PLATFORMS = [
|
|
83
|
+
"darwin",
|
|
84
|
+
"linux",
|
|
85
|
+
"win32"
|
|
86
|
+
];
|
|
87
|
+
var KNOWN_ARCHES = ["arm64", "x64"];
|
|
88
|
+
var KNOWN_GPU_TYPES = [
|
|
89
|
+
"nvidia",
|
|
90
|
+
"amd",
|
|
91
|
+
"intel",
|
|
92
|
+
"apple"
|
|
93
|
+
];
|
|
94
|
+
var KNOWN_NPU_TYPES = ["apple-ane", "intel-npu"];
|
|
95
|
+
function toKnownPlatform(p) {
|
|
96
|
+
return KNOWN_PLATFORMS.find((v) => v === p) ?? "linux";
|
|
97
|
+
}
|
|
98
|
+
function toKnownArch(a) {
|
|
99
|
+
return KNOWN_ARCHES.find((v) => v === a) ?? "x64";
|
|
100
|
+
}
|
|
101
|
+
function gpuInfoFrom(hw) {
|
|
102
|
+
if (!hw.gpu) return null;
|
|
103
|
+
const type = KNOWN_GPU_TYPES.find((v) => v === hw.gpu?.type);
|
|
104
|
+
if (!type) return null;
|
|
105
|
+
return {
|
|
106
|
+
type,
|
|
107
|
+
name: ""
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function npuInfoFrom(hw) {
|
|
111
|
+
if (!hw.npu) return null;
|
|
112
|
+
const type = KNOWN_NPU_TYPES.find((v) => v === hw.npu?.type);
|
|
113
|
+
if (!type) return null;
|
|
114
|
+
return { type };
|
|
115
|
+
}
|
|
116
|
+
function envToHardwareInfo(env) {
|
|
117
|
+
if (!env.hardware) return null;
|
|
118
|
+
return {
|
|
119
|
+
platform: toKnownPlatform(env.platform),
|
|
120
|
+
arch: toKnownArch(env.arch),
|
|
121
|
+
cpuModel: "",
|
|
122
|
+
cpuCores: 0,
|
|
123
|
+
totalRAM_MB: 0,
|
|
124
|
+
availableRAM_MB: 0,
|
|
125
|
+
gpu: gpuInfoFrom(env.hardware),
|
|
126
|
+
npu: npuInfoFrom(env.hardware)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function probedToHardwareInfo(hw) {
|
|
130
|
+
if (!hw) return null;
|
|
131
|
+
return {
|
|
132
|
+
platform: toKnownPlatform(process.platform),
|
|
133
|
+
arch: toKnownArch(process.arch),
|
|
134
|
+
cpuModel: "",
|
|
135
|
+
cpuCores: 0,
|
|
136
|
+
totalRAM_MB: 0,
|
|
137
|
+
availableRAM_MB: 0,
|
|
138
|
+
gpu: gpuInfoFrom(hw),
|
|
139
|
+
npu: npuInfoFrom(hw)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
var RUNTIME_DETAIL = {
|
|
143
|
+
onnx: {
|
|
144
|
+
label: "ONNX Runtime",
|
|
145
|
+
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
146
|
+
tuning: {
|
|
147
|
+
concurrency: 4,
|
|
148
|
+
batchMode: "list",
|
|
149
|
+
maxBatchSize: 8,
|
|
150
|
+
intraOpThreads: 0
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
openvino: {
|
|
154
|
+
label: "OpenVINO",
|
|
155
|
+
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
156
|
+
tuning: {
|
|
157
|
+
concurrency: 1,
|
|
158
|
+
batchMode: "none",
|
|
159
|
+
numStreams: 0
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
coreml: {
|
|
163
|
+
label: "CoreML",
|
|
164
|
+
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
165
|
+
tuning: {
|
|
166
|
+
concurrency: 1,
|
|
167
|
+
batchMode: "none",
|
|
168
|
+
windowMs: 8,
|
|
169
|
+
maxBatchSize: 8,
|
|
170
|
+
numWorkers: 1
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Returns the list of supported runtime IDs for the given hardware env.
|
|
176
|
+
* Delegates to `@camstack/types` `supportedRuntimes`.
|
|
177
|
+
*/
|
|
178
|
+
function supportedRuntimes(env) {
|
|
179
|
+
return supportedRuntimes$1(envToHardwareInfo(env));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the device options for a given runtime and probed hardware.
|
|
183
|
+
* Delegates to `@camstack/types` `runtimeDevices`.
|
|
184
|
+
*/
|
|
185
|
+
function runtimeDevices(id, hardware) {
|
|
186
|
+
return runtimeDevices$1(id, probedToHardwareInfo(hardware));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns the default device string for a given runtime.
|
|
190
|
+
* Delegates to `@camstack/types` `defaultDeviceFor`.
|
|
191
|
+
*/
|
|
192
|
+
function defaultDeviceFor(id) {
|
|
193
|
+
return defaultDeviceFor$1(id);
|
|
194
|
+
}
|
|
195
|
+
/** Model format for each inference runtime supported by the detection pipeline. */
|
|
196
|
+
var RUNTIME_FORMAT = {
|
|
197
|
+
onnx: "onnx",
|
|
198
|
+
openvino: "openvino",
|
|
199
|
+
coreml: "coreml"
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Returns the model format required for a given runtime.
|
|
203
|
+
* Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
|
|
204
|
+
* matching the engine-provisioner's own ModelFormat type.
|
|
205
|
+
*/
|
|
206
|
+
function modelFormatFor(id) {
|
|
207
|
+
return RUNTIME_FORMAT[id];
|
|
208
|
+
}
|
|
209
|
+
function pythonRequirementsFor(id) {
|
|
210
|
+
return RUNTIME_DETAIL[id].pythonRequirements;
|
|
211
|
+
}
|
|
212
|
+
function tuningFor(id) {
|
|
213
|
+
return RUNTIME_DETAIL[id].tuning;
|
|
214
|
+
}
|
|
215
|
+
function runtimeLabel(id) {
|
|
216
|
+
return RUNTIME_DETAIL[id].label;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
220
|
+
* Intel on Linux warrants installing openvino; coremltools covers macOS;
|
|
221
|
+
* openvino has no AMD/NVIDIA backend.
|
|
222
|
+
*/
|
|
223
|
+
function shouldInstallOpenvino(env) {
|
|
224
|
+
if (env.platform === "darwin") return false;
|
|
225
|
+
return env.gpu?.type === "intel" || env.npu?.type === "intel-npu";
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/detection-pipeline/auto-pick.ts
|
|
229
|
+
var PREFERENCE = [
|
|
230
|
+
"coreml",
|
|
231
|
+
"openvino",
|
|
232
|
+
"onnx"
|
|
233
|
+
];
|
|
234
|
+
/**
|
|
235
|
+
* Pure function — picks the best supported runtime for the given hardware env.
|
|
236
|
+
*
|
|
237
|
+
* Logic:
|
|
238
|
+
* 1. If `bestBackendHint` is in the supported set, use it.
|
|
239
|
+
* 2. Otherwise, walk PREFERENCE order and pick the first supported runtime.
|
|
240
|
+
* 3. Floor to `'onnx'` (always supported).
|
|
241
|
+
*
|
|
242
|
+
* Device is always `defaultDeviceFor(chosen)`.
|
|
243
|
+
*/
|
|
244
|
+
function pickBestRuntime(env, bestBackendHint) {
|
|
245
|
+
const supported = supportedRuntimes(env);
|
|
246
|
+
const chosen = supported.find((id) => id === bestBackendHint) ?? PREFERENCE.find((id) => supported.includes(id)) ?? "onnx";
|
|
247
|
+
return {
|
|
248
|
+
runtimeId: chosen,
|
|
249
|
+
device: defaultDeviceFor(chosen)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
9
253
|
//#region src/detection-pipeline/engine/shared-inference-pool.ts
|
|
10
254
|
var MSG_COMMAND = 0;
|
|
11
255
|
var MSG_INFER_JPEG = 1;
|
|
@@ -772,6 +1016,26 @@ var HF_REPO = "camstack/camstack-models";
|
|
|
772
1016
|
var HF_SCRYPTED = "scrypted/plugin-models";
|
|
773
1017
|
var hf = (path) => hfModelUrl(HF_REPO, path);
|
|
774
1018
|
var hfScrypted = (path) => hfModelUrl(HF_SCRYPTED, path);
|
|
1019
|
+
/**
|
|
1020
|
+
* Build an OpenVINO format entry (always python runtime).
|
|
1021
|
+
*
|
|
1022
|
+
* OpenVINO IR is a two-file bundle: a `.xml` topology + a sibling `.bin`
|
|
1023
|
+
* weights file with the same basename. We declare the `.bin` in `files` so
|
|
1024
|
+
* the (format-agnostic) downloader fetches it alongside the `.xml` — without
|
|
1025
|
+
* the weights, OpenVINO compile fails with "Empty weights data in bin file".
|
|
1026
|
+
* A plain `.onnx` run through the OpenVINO runtime (e.g. yamnet) has no
|
|
1027
|
+
* sibling, so none is added.
|
|
1028
|
+
*/
|
|
1029
|
+
var ovFormat = (url, sizeMB) => {
|
|
1030
|
+
const base = url.split("/").pop() ?? "";
|
|
1031
|
+
const files = base.endsWith(".xml") ? [base.replace(/\.xml$/, ".bin")] : void 0;
|
|
1032
|
+
return {
|
|
1033
|
+
url,
|
|
1034
|
+
sizeMB,
|
|
1035
|
+
runtimes: ["python"],
|
|
1036
|
+
...files ? { files } : {}
|
|
1037
|
+
};
|
|
1038
|
+
};
|
|
775
1039
|
var MLPACKAGE_FILES = [
|
|
776
1040
|
"Manifest.json",
|
|
777
1041
|
"Data/com.apple.CoreML/model.mlmodel",
|
|
@@ -800,11 +1064,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
800
1064
|
files: [...MLPACKAGE_FILES],
|
|
801
1065
|
runtimes: ["python"]
|
|
802
1066
|
},
|
|
803
|
-
openvino:
|
|
804
|
-
url: hf("objectDetection/yolov9/openvino/camstack-yolov9t.xml"),
|
|
805
|
-
sizeMB: 6,
|
|
806
|
-
runtimes: ["python"]
|
|
807
|
-
}
|
|
1067
|
+
openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9t.xml"), 6)
|
|
808
1068
|
}
|
|
809
1069
|
},
|
|
810
1070
|
{
|
|
@@ -829,11 +1089,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
829
1089
|
files: [...MLPACKAGE_FILES],
|
|
830
1090
|
runtimes: ["python"]
|
|
831
1091
|
},
|
|
832
|
-
openvino:
|
|
833
|
-
url: hf("objectDetection/yolov9/openvino/camstack-yolov9s.xml"),
|
|
834
|
-
sizeMB: 16,
|
|
835
|
-
runtimes: ["python"]
|
|
836
|
-
}
|
|
1092
|
+
openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9s.xml"), 16)
|
|
837
1093
|
}
|
|
838
1094
|
},
|
|
839
1095
|
{
|
|
@@ -858,11 +1114,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
858
1114
|
files: [...MLPACKAGE_FILES],
|
|
859
1115
|
runtimes: ["python"]
|
|
860
1116
|
},
|
|
861
|
-
openvino:
|
|
862
|
-
url: hf("objectDetection/yolov9/openvino/camstack-yolov9c.xml"),
|
|
863
|
-
sizeMB: 49,
|
|
864
|
-
runtimes: ["python"]
|
|
865
|
-
}
|
|
1117
|
+
openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9c.xml"), 49)
|
|
866
1118
|
}
|
|
867
1119
|
},
|
|
868
1120
|
{
|
|
@@ -887,11 +1139,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
887
1139
|
files: [...MLPACKAGE_FILES],
|
|
888
1140
|
runtimes: ["python"]
|
|
889
1141
|
},
|
|
890
|
-
openvino:
|
|
891
|
-
url: hf("objectDetection/yolo26/openvino/camstack-yolo26n.xml"),
|
|
892
|
-
sizeMB: 9,
|
|
893
|
-
runtimes: ["python"]
|
|
894
|
-
}
|
|
1142
|
+
openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26n.xml"), 9)
|
|
895
1143
|
}
|
|
896
1144
|
},
|
|
897
1145
|
{
|
|
@@ -916,11 +1164,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
916
1164
|
files: [...MLPACKAGE_FILES],
|
|
917
1165
|
runtimes: ["python"]
|
|
918
1166
|
},
|
|
919
|
-
openvino:
|
|
920
|
-
url: hf("objectDetection/yolo26/openvino/camstack-yolo26s.xml"),
|
|
921
|
-
sizeMB: 36,
|
|
922
|
-
runtimes: ["python"]
|
|
923
|
-
}
|
|
1167
|
+
openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26s.xml"), 36)
|
|
924
1168
|
}
|
|
925
1169
|
},
|
|
926
1170
|
{
|
|
@@ -945,11 +1189,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
945
1189
|
files: [...MLPACKAGE_FILES],
|
|
946
1190
|
runtimes: ["python"]
|
|
947
1191
|
},
|
|
948
|
-
openvino:
|
|
949
|
-
url: hf("objectDetection/yolo26/openvino/camstack-yolo26m.xml"),
|
|
950
|
-
sizeMB: 78,
|
|
951
|
-
runtimes: ["python"]
|
|
952
|
-
}
|
|
1192
|
+
openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26m.xml"), 78)
|
|
953
1193
|
}
|
|
954
1194
|
},
|
|
955
1195
|
{
|
|
@@ -974,11 +1214,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
974
1214
|
files: [...MLPACKAGE_FILES],
|
|
975
1215
|
runtimes: ["python"]
|
|
976
1216
|
},
|
|
977
|
-
openvino:
|
|
978
|
-
url: hf("objectDetection/yolo26/openvino/camstack-yolo26l.xml"),
|
|
979
|
-
sizeMB: 95,
|
|
980
|
-
runtimes: ["python"]
|
|
981
|
-
}
|
|
1217
|
+
openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26l.xml"), 95)
|
|
982
1218
|
}
|
|
983
1219
|
},
|
|
984
1220
|
{
|
|
@@ -1003,11 +1239,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
1003
1239
|
files: [...MLPACKAGE_FILES],
|
|
1004
1240
|
runtimes: ["python"]
|
|
1005
1241
|
},
|
|
1006
|
-
openvino:
|
|
1007
|
-
url: hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"),
|
|
1008
|
-
sizeMB: 213,
|
|
1009
|
-
runtimes: ["python"]
|
|
1010
|
-
}
|
|
1242
|
+
openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"), 213)
|
|
1011
1243
|
}
|
|
1012
1244
|
},
|
|
1013
1245
|
{
|
|
@@ -1032,11 +1264,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
1032
1264
|
files: [...MLPACKAGE_FILES],
|
|
1033
1265
|
runtimes: ["python"]
|
|
1034
1266
|
},
|
|
1035
|
-
openvino:
|
|
1036
|
-
url: hfScrypted("openvino/scrypted_yolov9t_relu/best.xml"),
|
|
1037
|
-
sizeMB: 6,
|
|
1038
|
-
runtimes: ["python"]
|
|
1039
|
-
}
|
|
1267
|
+
openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9t_relu.xml"), 6)
|
|
1040
1268
|
}
|
|
1041
1269
|
},
|
|
1042
1270
|
{
|
|
@@ -1061,11 +1289,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
1061
1289
|
files: [...MLPACKAGE_FILES],
|
|
1062
1290
|
runtimes: ["python"]
|
|
1063
1291
|
},
|
|
1064
|
-
openvino:
|
|
1065
|
-
url: hfScrypted("openvino/scrypted_yolov9s_relu/best.xml"),
|
|
1066
|
-
sizeMB: 16,
|
|
1067
|
-
runtimes: ["python"]
|
|
1068
|
-
}
|
|
1292
|
+
openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9s_relu.xml"), 16)
|
|
1069
1293
|
}
|
|
1070
1294
|
},
|
|
1071
1295
|
{
|
|
@@ -1090,11 +1314,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
1090
1314
|
files: [...MLPACKAGE_FILES],
|
|
1091
1315
|
runtimes: ["python"]
|
|
1092
1316
|
},
|
|
1093
|
-
openvino:
|
|
1094
|
-
url: hfScrypted("openvino/scrypted_yolov9c_relu/best.xml"),
|
|
1095
|
-
sizeMB: 49,
|
|
1096
|
-
runtimes: ["python"]
|
|
1097
|
-
}
|
|
1317
|
+
openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9c_relu.xml"), 49)
|
|
1098
1318
|
}
|
|
1099
1319
|
},
|
|
1100
1320
|
{
|
|
@@ -1119,11 +1339,7 @@ var OBJECT_DETECTION_MODELS = [
|
|
|
1119
1339
|
files: [...MLPACKAGE_FILES],
|
|
1120
1340
|
runtimes: ["python"]
|
|
1121
1341
|
},
|
|
1122
|
-
openvino:
|
|
1123
|
-
url: hfScrypted("openvino/scrypted_yolov9m_relu/best.xml"),
|
|
1124
|
-
sizeMB: 38,
|
|
1125
|
-
runtimes: ["python"]
|
|
1126
|
-
}
|
|
1342
|
+
openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9m_relu.xml"), 38)
|
|
1127
1343
|
}
|
|
1128
1344
|
}
|
|
1129
1345
|
];
|
|
@@ -1152,11 +1368,7 @@ var FACE_DETECTION_MODELS = [{
|
|
|
1152
1368
|
files: [...MLPACKAGE_FILES],
|
|
1153
1369
|
runtimes: ["python"]
|
|
1154
1370
|
},
|
|
1155
|
-
openvino:
|
|
1156
|
-
url: hf("faceDetection/scrfd/openvino/camstack-scrfd-2.5g.xml"),
|
|
1157
|
-
sizeMB: 1.8,
|
|
1158
|
-
runtimes: ["python"]
|
|
1159
|
-
}
|
|
1371
|
+
openvino: ovFormat(hf("faceDetection/scrfd/openvino/camstack-scrfd-2.5g.xml"), 1.8)
|
|
1160
1372
|
}
|
|
1161
1373
|
}, {
|
|
1162
1374
|
id: "scrypted-yolov9t-face",
|
|
@@ -1183,11 +1395,7 @@ var FACE_DETECTION_MODELS = [{
|
|
|
1183
1395
|
files: [...MLPACKAGE_FILES],
|
|
1184
1396
|
runtimes: ["python"]
|
|
1185
1397
|
},
|
|
1186
|
-
openvino:
|
|
1187
|
-
url: hfScrypted("openvino/scrypted_yolov9t_relu_face/best.xml"),
|
|
1188
|
-
sizeMB: 6,
|
|
1189
|
-
runtimes: ["python"]
|
|
1190
|
-
}
|
|
1398
|
+
openvino: ovFormat(hf("faceDetection/scrypted-yolov9-face/openvino/scrypted_yolov9t_relu_face.xml"), 6)
|
|
1191
1399
|
}
|
|
1192
1400
|
}];
|
|
1193
1401
|
var FACE_EMBEDDING_MODELS = [{
|
|
@@ -1217,11 +1425,7 @@ var FACE_EMBEDDING_MODELS = [{
|
|
|
1217
1425
|
files: [...MLPACKAGE_FILES],
|
|
1218
1426
|
runtimes: ["python"]
|
|
1219
1427
|
},
|
|
1220
|
-
openvino:
|
|
1221
|
-
url: hf("faceRecognition/arcface/openvino/camstack-arcface-r100.xml"),
|
|
1222
|
-
sizeMB: 65,
|
|
1223
|
-
runtimes: ["python"]
|
|
1224
|
-
}
|
|
1428
|
+
openvino: ovFormat(hf("faceRecognition/arcface/openvino/camstack-arcface-r100.xml"), 65)
|
|
1225
1429
|
}
|
|
1226
1430
|
}, {
|
|
1227
1431
|
id: "inception-resnet-v1",
|
|
@@ -1248,11 +1452,7 @@ var FACE_EMBEDDING_MODELS = [{
|
|
|
1248
1452
|
files: [...MLPACKAGE_FILES],
|
|
1249
1453
|
runtimes: ["python"]
|
|
1250
1454
|
},
|
|
1251
|
-
openvino:
|
|
1252
|
-
url: hfScrypted("openvino/inception_resnet_v1/best.xml"),
|
|
1253
|
-
sizeMB: 45,
|
|
1254
|
-
runtimes: ["python"]
|
|
1255
|
-
}
|
|
1455
|
+
openvino: ovFormat(hf("faceRecognition/inception-resnet-v1/openvino/camstack-inception-resnet-v1.xml"), 45)
|
|
1256
1456
|
}
|
|
1257
1457
|
}];
|
|
1258
1458
|
var PLATE_DETECTION_MODELS = [{
|
|
@@ -1280,11 +1480,7 @@ var PLATE_DETECTION_MODELS = [{
|
|
|
1280
1480
|
files: [...MLPACKAGE_FILES],
|
|
1281
1481
|
runtimes: ["python"]
|
|
1282
1482
|
},
|
|
1283
|
-
openvino:
|
|
1284
|
-
url: hf("plateDetection/yolov8-plate/openvino/camstack-yolov8n-plate.xml"),
|
|
1285
|
-
sizeMB: 6.1,
|
|
1286
|
-
runtimes: ["python"]
|
|
1287
|
-
}
|
|
1483
|
+
openvino: ovFormat(hf("plateDetection/yolov8-plate/openvino/camstack-yolov8n-plate.xml"), 6.1)
|
|
1288
1484
|
}
|
|
1289
1485
|
}];
|
|
1290
1486
|
var PLATE_OCR_MODELS = [{
|
|
@@ -1312,11 +1508,7 @@ var PLATE_OCR_MODELS = [{
|
|
|
1312
1508
|
files: [...MLPACKAGE_FILES],
|
|
1313
1509
|
runtimes: ["python"]
|
|
1314
1510
|
},
|
|
1315
|
-
openvino:
|
|
1316
|
-
url: hfScrypted("openvino/vgg_english_g2/best.xml"),
|
|
1317
|
-
sizeMB: 7.2,
|
|
1318
|
-
runtimes: ["python"]
|
|
1319
|
-
}
|
|
1511
|
+
openvino: ovFormat(hf("plateRecognition/vgg_english_g2/openvino/vgg_english_g2.xml"), 7.2)
|
|
1320
1512
|
}
|
|
1321
1513
|
}];
|
|
1322
1514
|
var ANIMAL_CLASSIFIER_MODELS = [{
|
|
@@ -1345,11 +1537,7 @@ var ANIMAL_CLASSIFIER_MODELS = [{
|
|
|
1345
1537
|
files: [...MLPACKAGE_FILES],
|
|
1346
1538
|
runtimes: ["python"]
|
|
1347
1539
|
},
|
|
1348
|
-
openvino:
|
|
1349
|
-
url: hf("animalClassification/animals-10/openvino/camstack-animals-10.xml"),
|
|
1350
|
-
sizeMB: 164,
|
|
1351
|
-
runtimes: ["python"]
|
|
1352
|
-
}
|
|
1540
|
+
openvino: ovFormat(hf("animalClassification/animals-10/openvino/camstack-animals-10.xml"), 164)
|
|
1353
1541
|
}
|
|
1354
1542
|
}];
|
|
1355
1543
|
var BIRD_CLASSIFIER_MODELS = [{
|
|
@@ -1378,11 +1566,7 @@ var BIRD_CLASSIFIER_MODELS = [{
|
|
|
1378
1566
|
files: [...MLPACKAGE_FILES],
|
|
1379
1567
|
runtimes: ["python"]
|
|
1380
1568
|
},
|
|
1381
|
-
openvino:
|
|
1382
|
-
url: hf("animalClassification/bird-nabirds/openvino/camstack-bird-nabirds-404.xml"),
|
|
1383
|
-
sizeMB: 47,
|
|
1384
|
-
runtimes: ["python"]
|
|
1385
|
-
}
|
|
1569
|
+
openvino: ovFormat(hf("animalClassification/bird-nabirds/openvino/camstack-bird-nabirds-404.xml"), 47)
|
|
1386
1570
|
},
|
|
1387
1571
|
extraFiles: [{
|
|
1388
1572
|
url: hf("animalClassification/bird-nabirds/onnx/camstack-bird-nabirds-404-labels.json"),
|
|
@@ -1416,11 +1600,7 @@ var VEHICLE_CLASSIFIER_MODELS = [{
|
|
|
1416
1600
|
files: [...MLPACKAGE_FILES],
|
|
1417
1601
|
runtimes: ["python"]
|
|
1418
1602
|
},
|
|
1419
|
-
openvino:
|
|
1420
|
-
url: hf("vehicleClassification/efficientnet/openvino/camstack-vehicle-type-efficientnet.xml"),
|
|
1421
|
-
sizeMB: 68,
|
|
1422
|
-
runtimes: ["python"]
|
|
1423
|
-
}
|
|
1603
|
+
openvino: ovFormat(hf("vehicleClassification/efficientnet/openvino/camstack-vehicle-type-efficientnet.xml"), 68)
|
|
1424
1604
|
},
|
|
1425
1605
|
extraFiles: [{
|
|
1426
1606
|
url: hf("vehicleClassification/efficientnet/camstack-vehicle-type-labels.json"),
|
|
@@ -1453,11 +1633,7 @@ var SEGMENTATION_REFINER_MODELS = [{
|
|
|
1453
1633
|
files: [...MLPACKAGE_FILES],
|
|
1454
1634
|
runtimes: ["python"]
|
|
1455
1635
|
},
|
|
1456
|
-
openvino:
|
|
1457
|
-
url: hf("segmentationRefiner/u2netp/openvino/camstack-u2netp.xml"),
|
|
1458
|
-
sizeMB: 2.5,
|
|
1459
|
-
runtimes: ["python"]
|
|
1460
|
-
}
|
|
1636
|
+
openvino: ovFormat(hf("segmentationRefiner/u2netp/openvino/camstack-u2netp.xml"), 2.5)
|
|
1461
1637
|
}
|
|
1462
1638
|
}];
|
|
1463
1639
|
var INSTANCE_SEGMENTATION_MODELS = [
|
|
@@ -1483,11 +1659,7 @@ var INSTANCE_SEGMENTATION_MODELS = [
|
|
|
1483
1659
|
files: [...MLPACKAGE_FILES],
|
|
1484
1660
|
runtimes: ["python"]
|
|
1485
1661
|
},
|
|
1486
|
-
openvino:
|
|
1487
|
-
url: hf("segmentation/yolo26-seg/openvino/camstack-yolo26n-seg.xml"),
|
|
1488
|
-
sizeMB: 11,
|
|
1489
|
-
runtimes: ["python"]
|
|
1490
|
-
}
|
|
1662
|
+
openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26n-seg.xml"), 11)
|
|
1491
1663
|
}
|
|
1492
1664
|
},
|
|
1493
1665
|
{
|
|
@@ -1512,11 +1684,7 @@ var INSTANCE_SEGMENTATION_MODELS = [
|
|
|
1512
1684
|
files: [...MLPACKAGE_FILES],
|
|
1513
1685
|
runtimes: ["python"]
|
|
1514
1686
|
},
|
|
1515
|
-
openvino:
|
|
1516
|
-
url: hf("segmentation/yolo26-seg/openvino/camstack-yolo26s-seg.xml"),
|
|
1517
|
-
sizeMB: 40,
|
|
1518
|
-
runtimes: ["python"]
|
|
1519
|
-
}
|
|
1687
|
+
openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26s-seg.xml"), 40)
|
|
1520
1688
|
}
|
|
1521
1689
|
},
|
|
1522
1690
|
{
|
|
@@ -1541,11 +1709,7 @@ var INSTANCE_SEGMENTATION_MODELS = [
|
|
|
1541
1709
|
files: [...MLPACKAGE_FILES],
|
|
1542
1710
|
runtimes: ["python"]
|
|
1543
1711
|
},
|
|
1544
|
-
openvino:
|
|
1545
|
-
url: hf("segmentation/yolo26-seg/openvino/camstack-yolo26m-seg.xml"),
|
|
1546
|
-
sizeMB: 90,
|
|
1547
|
-
runtimes: ["python"]
|
|
1548
|
-
}
|
|
1712
|
+
openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26m-seg.xml"), 90)
|
|
1549
1713
|
}
|
|
1550
1714
|
}
|
|
1551
1715
|
];
|
|
@@ -1561,16 +1725,12 @@ var AUDIO_CLASSIFIER_MODELS = [{
|
|
|
1561
1725
|
preprocessMode: "resize",
|
|
1562
1726
|
formats: {
|
|
1563
1727
|
onnx: {
|
|
1564
|
-
url: hf("
|
|
1728
|
+
url: hf("audioClassification/yamnet/onnx/camstack-yamnet.onnx"),
|
|
1565
1729
|
sizeMB: 3.2
|
|
1566
1730
|
},
|
|
1567
|
-
openvino:
|
|
1568
|
-
url: hf("audioClassifier/yamnet/onnx/camstack-yamnet.onnx"),
|
|
1569
|
-
sizeMB: 3.2,
|
|
1570
|
-
runtimes: ["python"]
|
|
1571
|
-
},
|
|
1731
|
+
openvino: ovFormat(hf("audioClassification/yamnet/openvino/camstack-yamnet.xml"), 3.2),
|
|
1572
1732
|
coreml: {
|
|
1573
|
-
url: hf("
|
|
1733
|
+
url: hf("audioClassification/yamnet/onnx/camstack-yamnet.onnx"),
|
|
1574
1734
|
sizeMB: 3.2,
|
|
1575
1735
|
runtimes: ["python"]
|
|
1576
1736
|
}
|
|
@@ -2158,77 +2318,108 @@ var EngineFactory = class {
|
|
|
2158
2318
|
}
|
|
2159
2319
|
};
|
|
2160
2320
|
//#endregion
|
|
2161
|
-
//#region src/detection-pipeline/engine-
|
|
2162
|
-
/**
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
2171
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
2172
|
-
* another node that has no NPU.
|
|
2173
|
-
*
|
|
2174
|
-
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
2175
|
-
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
2176
|
-
* before this change, or by an older build) is read as a migration fallback and
|
|
2177
|
-
* re-persisted under the node-scoped key on the next write.
|
|
2178
|
-
*/
|
|
2179
|
-
var ENGINE_CASCADE_KEYS = [
|
|
2180
|
-
"engineBackend",
|
|
2181
|
-
"engineDevice",
|
|
2182
|
-
"probedBestEngine"
|
|
2321
|
+
//#region src/detection-pipeline/engine-provisioner.ts
|
|
2322
|
+
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
2323
|
+
var BACKOFF_SCHEDULE_MS = [
|
|
2324
|
+
5e3,
|
|
2325
|
+
15e3,
|
|
2326
|
+
3e4,
|
|
2327
|
+
6e4,
|
|
2328
|
+
12e4,
|
|
2329
|
+
3e5
|
|
2183
2330
|
];
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2331
|
+
var IDLE_STATE = {
|
|
2332
|
+
runtimeId: null,
|
|
2333
|
+
device: null,
|
|
2334
|
+
state: "idle"
|
|
2335
|
+
};
|
|
2336
|
+
var EngineProvisioner = class {
|
|
2337
|
+
fx;
|
|
2338
|
+
current = IDLE_STATE;
|
|
2339
|
+
/** Bumped on every select/dispose — stale async results (old generation) are ignored. */
|
|
2340
|
+
generation = 0;
|
|
2341
|
+
cancelTimer = null;
|
|
2342
|
+
retryIndex = 0;
|
|
2343
|
+
constructor(fx) {
|
|
2344
|
+
this.fx = fx;
|
|
2345
|
+
}
|
|
2346
|
+
get state() {
|
|
2347
|
+
return this.current;
|
|
2348
|
+
}
|
|
2349
|
+
isReady() {
|
|
2350
|
+
return this.current.state === "ready";
|
|
2351
|
+
}
|
|
2352
|
+
select(runtimeId, device) {
|
|
2353
|
+
this.generation++;
|
|
2354
|
+
this.retryIndex = 0;
|
|
2355
|
+
this.clearTimer();
|
|
2356
|
+
this.transition({
|
|
2357
|
+
runtimeId,
|
|
2358
|
+
device,
|
|
2359
|
+
state: "installing",
|
|
2360
|
+
progress: 0
|
|
2361
|
+
});
|
|
2362
|
+
this.provision(this.generation, runtimeId, device);
|
|
2363
|
+
}
|
|
2364
|
+
dispose() {
|
|
2365
|
+
this.generation++;
|
|
2366
|
+
this.clearTimer();
|
|
2367
|
+
}
|
|
2368
|
+
clearTimer() {
|
|
2369
|
+
if (this.cancelTimer !== null) {
|
|
2370
|
+
this.cancelTimer();
|
|
2371
|
+
this.cancelTimer = null;
|
|
2223
2372
|
}
|
|
2224
|
-
out[key] = store[key];
|
|
2225
2373
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2374
|
+
transition(next) {
|
|
2375
|
+
this.current = next;
|
|
2376
|
+
this.fx.onChange(next);
|
|
2229
2377
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2378
|
+
async provision(gen, runtimeId, device) {
|
|
2379
|
+
try {
|
|
2380
|
+
await Promise.all([this.fx.installRequirements(this.fx.requirementsFor(runtimeId)), this.fx.ensureModelForFormat(this.fx.modelFormatFor(runtimeId))]);
|
|
2381
|
+
if (gen !== this.generation) return;
|
|
2382
|
+
this.transition({
|
|
2383
|
+
runtimeId,
|
|
2384
|
+
device,
|
|
2385
|
+
state: "verifying",
|
|
2386
|
+
progress: 100
|
|
2387
|
+
});
|
|
2388
|
+
await this.fx.verify(runtimeId, device);
|
|
2389
|
+
if (gen !== this.generation) return;
|
|
2390
|
+
this.retryIndex = 0;
|
|
2391
|
+
this.transition({
|
|
2392
|
+
runtimeId,
|
|
2393
|
+
device,
|
|
2394
|
+
state: "ready"
|
|
2395
|
+
});
|
|
2396
|
+
} catch (err) {
|
|
2397
|
+
if (gen !== this.generation) return;
|
|
2398
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2399
|
+
const delay = BACKOFF_SCHEDULE_MS[Math.min(this.retryIndex, BACKOFF_SCHEDULE_MS.length - 1)];
|
|
2400
|
+
this.retryIndex++;
|
|
2401
|
+
const nextRetryAt = this.fx.now() + delay;
|
|
2402
|
+
this.transition({
|
|
2403
|
+
runtimeId,
|
|
2404
|
+
device,
|
|
2405
|
+
state: "failed",
|
|
2406
|
+
error: message,
|
|
2407
|
+
nextRetryAt
|
|
2408
|
+
});
|
|
2409
|
+
this.cancelTimer = this.fx.setTimer(delay, () => {
|
|
2410
|
+
if (gen !== this.generation) return;
|
|
2411
|
+
this.cancelTimer = null;
|
|
2412
|
+
this.transition({
|
|
2413
|
+
runtimeId,
|
|
2414
|
+
device,
|
|
2415
|
+
state: "installing",
|
|
2416
|
+
progress: 0
|
|
2417
|
+
});
|
|
2418
|
+
this.provision(gen, runtimeId, device);
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2232
2423
|
//#endregion
|
|
2233
2424
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
2234
2425
|
var VALID_KINDS = new Set([
|
|
@@ -3416,463 +3607,188 @@ var PipelineExecutor = class {
|
|
|
3416
3607
|
inferenceMs: Date.now() - inferenceStart,
|
|
3417
3608
|
postprocessMs: 0,
|
|
3418
3609
|
inputType,
|
|
3419
|
-
inputWidth,
|
|
3420
|
-
inputHeight,
|
|
3421
|
-
parentDetection: parentClass,
|
|
3422
|
-
output: errorOutput,
|
|
3423
|
-
error: errorMsg
|
|
3424
|
-
});
|
|
3425
|
-
stepTimings.push({
|
|
3426
|
-
source: step.stepId,
|
|
3427
|
-
modelId: step.modelId,
|
|
3428
|
-
ms: Date.now() - preprocessStart,
|
|
3429
|
-
detectionCount: 0
|
|
3430
|
-
});
|
|
3431
|
-
throw new Error(`Inference failed for step "${step.stepId}": ${errorMsg}`, { cause: err });
|
|
3432
|
-
}
|
|
3433
|
-
const structured = engineOutput.structured ?? {};
|
|
3434
|
-
const inferenceMs = typeof structured.inferenceMs === "number" ? structured.inferenceMs : Date.now() - inferenceStart;
|
|
3435
|
-
if (typeof structured.preprocessMs === "number") poolAgg.preprocessMs += structured.preprocessMs;
|
|
3436
|
-
if (typeof structured.predictMs === "number") poolAgg.predictMs += structured.predictMs;
|
|
3437
|
-
if (typeof structured.batchSize === "number" && structured.batchSize > poolAgg.batchSize) poolAgg.batchSize = structured.batchSize;
|
|
3438
|
-
const postprocessStart = Date.now();
|
|
3439
|
-
const output = dispatchPostprocess(engineOutput, step.definition);
|
|
3440
|
-
const postprocessMs = Date.now() - postprocessStart;
|
|
3441
|
-
stepTimings.push({
|
|
3442
|
-
source: step.stepId,
|
|
3443
|
-
modelId: step.modelId,
|
|
3444
|
-
ms: preprocessMs + inferenceMs + postprocessMs,
|
|
3445
|
-
detectionCount: output.kind === "detections" ? output.detections.length : 0
|
|
3446
|
-
});
|
|
3447
|
-
if (traceBuilder.isActive) traceBuilder.addStep({
|
|
3448
|
-
stepId: step.stepId,
|
|
3449
|
-
modelId: step.modelId,
|
|
3450
|
-
slot: step.definition.slot,
|
|
3451
|
-
postprocessor: step.definition.postprocessor,
|
|
3452
|
-
preprocessMs,
|
|
3453
|
-
inferenceMs,
|
|
3454
|
-
postprocessMs,
|
|
3455
|
-
inputType,
|
|
3456
|
-
inputWidth,
|
|
3457
|
-
inputHeight,
|
|
3458
|
-
parentDetection: parentClass,
|
|
3459
|
-
output
|
|
3460
|
-
});
|
|
3461
|
-
return output;
|
|
3462
|
-
}
|
|
3463
|
-
async executeChildren(children, parentDetection, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg) {
|
|
3464
|
-
for (const child of children) {
|
|
3465
|
-
if (!this.matchesInputClasses(parentDetection.macroClass, child.inputClasses)) continue;
|
|
3466
|
-
const minParentScore = child.settings?.minParentScore ?? child.definition?.defaultMinParentScore;
|
|
3467
|
-
if (typeof minParentScore === "number" && parentDetection.score < minParentScore) continue;
|
|
3468
|
-
if (isBboxDegenerate(parentDetection.bbox)) continue;
|
|
3469
|
-
try {
|
|
3470
|
-
const childStart = Date.now();
|
|
3471
|
-
const fullFrameJpeg = await fullFrameJpegProvider();
|
|
3472
|
-
const modelEntry = child.definition.models.find((m) => m.id === child.modelId);
|
|
3473
|
-
let cropJpegBuf;
|
|
3474
|
-
let cropW;
|
|
3475
|
-
let cropH;
|
|
3476
|
-
if (modelEntry?.faceAlignment) {
|
|
3477
|
-
const minFaceSize = typeof child.settings?.["minFaceSize"] === "number" ? child.settings["minFaceSize"] : DEFAULT_MIN_FACE_SIZE_PX;
|
|
3478
|
-
if (bboxShortSide(parentDetection.bbox) < minFaceSize) continue;
|
|
3479
|
-
const lms = parentDetection.landmarks;
|
|
3480
|
-
if (lms && lms.length >= 5) {
|
|
3481
|
-
const aligned = await alignFaceCrop(fullFrameJpeg, parentDetection.bbox, lms, imageWidth, imageHeight, { outSize: modelEntry.inputSize.width });
|
|
3482
|
-
cropJpegBuf = aligned.jpeg;
|
|
3483
|
-
cropW = aligned.width;
|
|
3484
|
-
cropH = aligned.height;
|
|
3485
|
-
} else {
|
|
3486
|
-
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3487
|
-
cropJpegBuf = crop.jpeg;
|
|
3488
|
-
cropW = crop.width;
|
|
3489
|
-
cropH = crop.height;
|
|
3490
|
-
}
|
|
3491
|
-
} else {
|
|
3492
|
-
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3493
|
-
cropJpegBuf = crop.jpeg;
|
|
3494
|
-
cropW = crop.width;
|
|
3495
|
-
cropH = crop.height;
|
|
3496
|
-
}
|
|
3497
|
-
const childOutput = await this.executeStep(child, {
|
|
3498
|
-
kind: "jpeg",
|
|
3499
|
-
data: cropJpegBuf
|
|
3500
|
-
}, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
|
|
3501
|
-
const childMs = Date.now() - childStart;
|
|
3502
|
-
const detailsBefore = ctx.details.length;
|
|
3503
|
-
applyChildOutput(parentDetection, child, childOutput, childMs, ctx);
|
|
3504
|
-
if (childOutput.kind === "detections" && child.children.length > 0) {
|
|
3505
|
-
const newDetails = ctx.details.slice(detailsBefore);
|
|
3506
|
-
for (const detail of newDetails) {
|
|
3507
|
-
detail.bbox = transformBboxToImageSpace(detail.bbox, parentDetection.bbox);
|
|
3508
|
-
if (detail.landmarks) {
|
|
3509
|
-
const [px1, py1] = parentDetection.bbox;
|
|
3510
|
-
detail.landmarks = detail.landmarks.map((l) => ({
|
|
3511
|
-
x: l.x + px1,
|
|
3512
|
-
y: l.y + py1
|
|
3513
|
-
}));
|
|
3514
|
-
}
|
|
3515
|
-
await this.executeChildren(child.children, detail, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg);
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
} catch {}
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
matchesInputClasses(className, inputClasses) {
|
|
3522
|
-
if (inputClasses.length === 0) return true;
|
|
3523
|
-
return inputClasses.includes(className);
|
|
3524
|
-
}
|
|
3525
|
-
/**
|
|
3526
|
-
* Apply the object-detection step's macro filter + per-macro
|
|
3527
|
-
* minConfidence sliders introduced in the Phase 6 step rework.
|
|
3528
|
-
*
|
|
3529
|
-
* Expected `settings` shape:
|
|
3530
|
-
* - `enabledMacroClasses`: readonly string[] (e.g. ['person','vehicle','animal']).
|
|
3531
|
-
* Empty array = all allowed (legacy behaviour).
|
|
3532
|
-
* - `minConfidence<Macro>`: number (e.g. `minConfidencePerson: 0.5`)
|
|
3533
|
-
*/
|
|
3534
|
-
matchesMacroFilter(macroClass, score, settings) {
|
|
3535
|
-
if (!settings) return true;
|
|
3536
|
-
const enabled = settings["enabledMacroClasses"];
|
|
3537
|
-
if (Array.isArray(enabled) && enabled.length > 0) {
|
|
3538
|
-
if (!enabled.includes(macroClass)) return false;
|
|
3539
|
-
}
|
|
3540
|
-
const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
|
|
3541
|
-
if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return false;
|
|
3542
|
-
return true;
|
|
3543
|
-
}
|
|
3544
|
-
};
|
|
3545
|
-
function capitalize(s) {
|
|
3546
|
-
if (s.length === 0) return s;
|
|
3547
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3548
|
-
}
|
|
3549
|
-
//#endregion
|
|
3550
|
-
//#region src/detection-pipeline/pipeline/tree-builder.ts
|
|
3551
|
-
/**
|
|
3552
|
-
* Build an executable tree from user config.
|
|
3553
|
-
*
|
|
3554
|
-
* @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
|
|
3555
|
-
* @param getEngine - Function that returns an IInferenceEngine for a step ID.
|
|
3556
|
-
* Throws if step not loaded.
|
|
3557
|
-
*/
|
|
3558
|
-
function buildExecutableTree(steps, getEngine) {
|
|
3559
|
-
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine)) };
|
|
3560
|
-
}
|
|
3561
|
-
function buildNode(step, getEngine) {
|
|
3562
|
-
const definition = getStepDefinition(step.addonId);
|
|
3563
|
-
const engine = getEngine(step.addonId);
|
|
3564
|
-
const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildNode(c, getEngine));
|
|
3565
|
-
const mergedSettings = {
|
|
3566
|
-
...collectSchemaDefaults(step.addonId),
|
|
3567
|
-
...step.settings
|
|
3568
|
-
};
|
|
3569
|
-
return {
|
|
3570
|
-
stepId: step.addonId,
|
|
3571
|
-
definition,
|
|
3572
|
-
engine,
|
|
3573
|
-
modelId: step.modelId,
|
|
3574
|
-
inputClasses: definition.inputClasses ?? [],
|
|
3575
|
-
enabled: step.enabled,
|
|
3576
|
-
children,
|
|
3577
|
-
...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {}
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
/**
|
|
3581
|
-
* Walk the step's declared `getConfigSchema()` and collect every field
|
|
3582
|
-
* with a primitive `default` value into a plain record. Group fields
|
|
3583
|
-
* are flattened — a nested `{ type: 'group', fields: [...] }` is
|
|
3584
|
-
* entered recursively. Fields without a declared default are skipped.
|
|
3585
|
-
*/
|
|
3586
|
-
function collectSchemaDefaults(stepId) {
|
|
3587
|
-
const schema = getStep(stepId).getConfigSchema();
|
|
3588
|
-
const out = {};
|
|
3589
|
-
walkFieldsForDefaults(schema, out);
|
|
3590
|
-
return out;
|
|
3591
|
-
}
|
|
3592
|
-
function walkFieldsForDefaults(fields, out) {
|
|
3593
|
-
for (const field of fields) {
|
|
3594
|
-
if ("type" in field && field.type === "group" && "fields" in field) {
|
|
3595
|
-
walkFieldsForDefaults(field.fields, out);
|
|
3596
|
-
continue;
|
|
3597
|
-
}
|
|
3598
|
-
if ("key" in field && "default" in field && field.default !== void 0) out[field.key] = field.default;
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
//#endregion
|
|
3602
|
-
//#region src/detection-pipeline/runtimes.ts
|
|
3603
|
-
var KNOWN_PLATFORMS = [
|
|
3604
|
-
"darwin",
|
|
3605
|
-
"linux",
|
|
3606
|
-
"win32"
|
|
3607
|
-
];
|
|
3608
|
-
var KNOWN_ARCHES = ["arm64", "x64"];
|
|
3609
|
-
var KNOWN_GPU_TYPES = [
|
|
3610
|
-
"nvidia",
|
|
3611
|
-
"amd",
|
|
3612
|
-
"intel",
|
|
3613
|
-
"apple"
|
|
3614
|
-
];
|
|
3615
|
-
var KNOWN_NPU_TYPES = ["apple-ane", "intel-npu"];
|
|
3616
|
-
function toKnownPlatform(p) {
|
|
3617
|
-
return KNOWN_PLATFORMS.find((v) => v === p) ?? "linux";
|
|
3618
|
-
}
|
|
3619
|
-
function toKnownArch(a) {
|
|
3620
|
-
return KNOWN_ARCHES.find((v) => v === a) ?? "x64";
|
|
3621
|
-
}
|
|
3622
|
-
function gpuInfoFrom(hw) {
|
|
3623
|
-
if (!hw.gpu) return null;
|
|
3624
|
-
const type = KNOWN_GPU_TYPES.find((v) => v === hw.gpu?.type);
|
|
3625
|
-
if (!type) return null;
|
|
3626
|
-
return {
|
|
3627
|
-
type,
|
|
3628
|
-
name: ""
|
|
3629
|
-
};
|
|
3630
|
-
}
|
|
3631
|
-
function npuInfoFrom(hw) {
|
|
3632
|
-
if (!hw.npu) return null;
|
|
3633
|
-
const type = KNOWN_NPU_TYPES.find((v) => v === hw.npu?.type);
|
|
3634
|
-
if (!type) return null;
|
|
3635
|
-
return { type };
|
|
3636
|
-
}
|
|
3637
|
-
function envToHardwareInfo(env) {
|
|
3638
|
-
if (!env.hardware) return null;
|
|
3639
|
-
return {
|
|
3640
|
-
platform: toKnownPlatform(env.platform),
|
|
3641
|
-
arch: toKnownArch(env.arch),
|
|
3642
|
-
cpuModel: "",
|
|
3643
|
-
cpuCores: 0,
|
|
3644
|
-
totalRAM_MB: 0,
|
|
3645
|
-
availableRAM_MB: 0,
|
|
3646
|
-
gpu: gpuInfoFrom(env.hardware),
|
|
3647
|
-
npu: npuInfoFrom(env.hardware)
|
|
3648
|
-
};
|
|
3649
|
-
}
|
|
3650
|
-
function probedToHardwareInfo(hw) {
|
|
3651
|
-
if (!hw) return null;
|
|
3652
|
-
return {
|
|
3653
|
-
platform: toKnownPlatform(process.platform),
|
|
3654
|
-
arch: toKnownArch(process.arch),
|
|
3655
|
-
cpuModel: "",
|
|
3656
|
-
cpuCores: 0,
|
|
3657
|
-
totalRAM_MB: 0,
|
|
3658
|
-
availableRAM_MB: 0,
|
|
3659
|
-
gpu: gpuInfoFrom(hw),
|
|
3660
|
-
npu: npuInfoFrom(hw)
|
|
3661
|
-
};
|
|
3662
|
-
}
|
|
3663
|
-
var RUNTIME_DETAIL = {
|
|
3664
|
-
onnx: {
|
|
3665
|
-
label: "ONNX Runtime",
|
|
3666
|
-
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
3667
|
-
tuning: {
|
|
3668
|
-
concurrency: 4,
|
|
3669
|
-
batchMode: "list",
|
|
3670
|
-
maxBatchSize: 8,
|
|
3671
|
-
intraOpThreads: 0
|
|
3672
|
-
}
|
|
3673
|
-
},
|
|
3674
|
-
openvino: {
|
|
3675
|
-
label: "OpenVINO",
|
|
3676
|
-
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
3677
|
-
tuning: {
|
|
3678
|
-
concurrency: 1,
|
|
3679
|
-
batchMode: "none",
|
|
3680
|
-
numStreams: 0
|
|
3681
|
-
}
|
|
3682
|
-
},
|
|
3683
|
-
coreml: {
|
|
3684
|
-
label: "CoreML",
|
|
3685
|
-
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
3686
|
-
tuning: {
|
|
3687
|
-
concurrency: 1,
|
|
3688
|
-
batchMode: "none",
|
|
3689
|
-
windowMs: 8,
|
|
3690
|
-
maxBatchSize: 8,
|
|
3691
|
-
numWorkers: 1
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
};
|
|
3695
|
-
/**
|
|
3696
|
-
* Returns the list of supported runtime IDs for the given hardware env.
|
|
3697
|
-
* Delegates to `@camstack/types` `supportedRuntimes`.
|
|
3698
|
-
*/
|
|
3699
|
-
function supportedRuntimes(env) {
|
|
3700
|
-
return supportedRuntimes$1(envToHardwareInfo(env));
|
|
3701
|
-
}
|
|
3702
|
-
/**
|
|
3703
|
-
* Returns the device options for a given runtime and probed hardware.
|
|
3704
|
-
* Delegates to `@camstack/types` `runtimeDevices`.
|
|
3705
|
-
*/
|
|
3706
|
-
function runtimeDevices(id, hardware) {
|
|
3707
|
-
return runtimeDevices$1(id, probedToHardwareInfo(hardware));
|
|
3708
|
-
}
|
|
3709
|
-
/**
|
|
3710
|
-
* Returns the default device string for a given runtime.
|
|
3711
|
-
* Delegates to `@camstack/types` `defaultDeviceFor`.
|
|
3712
|
-
*/
|
|
3713
|
-
function defaultDeviceFor(id) {
|
|
3714
|
-
return defaultDeviceFor$1(id);
|
|
3715
|
-
}
|
|
3716
|
-
/** Model format for each inference runtime supported by the detection pipeline. */
|
|
3717
|
-
var RUNTIME_FORMAT = {
|
|
3718
|
-
onnx: "onnx",
|
|
3719
|
-
openvino: "openvino",
|
|
3720
|
-
coreml: "coreml"
|
|
3721
|
-
};
|
|
3722
|
-
/**
|
|
3723
|
-
* Returns the model format required for a given runtime.
|
|
3724
|
-
* Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
|
|
3725
|
-
* matching the engine-provisioner's own ModelFormat type.
|
|
3726
|
-
*/
|
|
3727
|
-
function modelFormatFor(id) {
|
|
3728
|
-
return RUNTIME_FORMAT[id];
|
|
3729
|
-
}
|
|
3730
|
-
function pythonRequirementsFor(id) {
|
|
3731
|
-
return RUNTIME_DETAIL[id].pythonRequirements;
|
|
3732
|
-
}
|
|
3733
|
-
function tuningFor(id) {
|
|
3734
|
-
return RUNTIME_DETAIL[id].tuning;
|
|
3735
|
-
}
|
|
3736
|
-
function runtimeLabel(id) {
|
|
3737
|
-
return RUNTIME_DETAIL[id].label;
|
|
3738
|
-
}
|
|
3739
|
-
/**
|
|
3740
|
-
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
3741
|
-
* Intel on Linux warrants installing openvino; coremltools covers macOS;
|
|
3742
|
-
* openvino has no AMD/NVIDIA backend.
|
|
3743
|
-
*/
|
|
3744
|
-
function shouldInstallOpenvino(env) {
|
|
3745
|
-
if (env.platform === "darwin") return false;
|
|
3746
|
-
return env.gpu?.type === "intel" || env.npu?.type === "intel-npu";
|
|
3747
|
-
}
|
|
3748
|
-
//#endregion
|
|
3749
|
-
//#region src/detection-pipeline/engine-provisioner.ts
|
|
3750
|
-
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
3751
|
-
var BACKOFF_SCHEDULE_MS = [
|
|
3752
|
-
5e3,
|
|
3753
|
-
15e3,
|
|
3754
|
-
3e4,
|
|
3755
|
-
6e4,
|
|
3756
|
-
12e4,
|
|
3757
|
-
3e5
|
|
3758
|
-
];
|
|
3759
|
-
var IDLE_STATE = {
|
|
3760
|
-
runtimeId: null,
|
|
3761
|
-
device: null,
|
|
3762
|
-
state: "idle"
|
|
3763
|
-
};
|
|
3764
|
-
var EngineProvisioner = class {
|
|
3765
|
-
fx;
|
|
3766
|
-
current = IDLE_STATE;
|
|
3767
|
-
/** Bumped on every select/dispose — stale async results (old generation) are ignored. */
|
|
3768
|
-
generation = 0;
|
|
3769
|
-
cancelTimer = null;
|
|
3770
|
-
retryIndex = 0;
|
|
3771
|
-
constructor(fx) {
|
|
3772
|
-
this.fx = fx;
|
|
3773
|
-
}
|
|
3774
|
-
get state() {
|
|
3775
|
-
return this.current;
|
|
3776
|
-
}
|
|
3777
|
-
isReady() {
|
|
3778
|
-
return this.current.state === "ready";
|
|
3779
|
-
}
|
|
3780
|
-
select(runtimeId, device) {
|
|
3781
|
-
this.generation++;
|
|
3782
|
-
this.retryIndex = 0;
|
|
3783
|
-
this.clearTimer();
|
|
3784
|
-
this.transition({
|
|
3785
|
-
runtimeId,
|
|
3786
|
-
device,
|
|
3787
|
-
state: "installing",
|
|
3788
|
-
progress: 0
|
|
3610
|
+
inputWidth,
|
|
3611
|
+
inputHeight,
|
|
3612
|
+
parentDetection: parentClass,
|
|
3613
|
+
output: errorOutput,
|
|
3614
|
+
error: errorMsg
|
|
3615
|
+
});
|
|
3616
|
+
stepTimings.push({
|
|
3617
|
+
source: step.stepId,
|
|
3618
|
+
modelId: step.modelId,
|
|
3619
|
+
ms: Date.now() - preprocessStart,
|
|
3620
|
+
detectionCount: 0
|
|
3621
|
+
});
|
|
3622
|
+
throw new Error(`Inference failed for step "${step.stepId}": ${errorMsg}`, { cause: err });
|
|
3623
|
+
}
|
|
3624
|
+
const structured = engineOutput.structured ?? {};
|
|
3625
|
+
const inferenceMs = typeof structured.inferenceMs === "number" ? structured.inferenceMs : Date.now() - inferenceStart;
|
|
3626
|
+
if (typeof structured.preprocessMs === "number") poolAgg.preprocessMs += structured.preprocessMs;
|
|
3627
|
+
if (typeof structured.predictMs === "number") poolAgg.predictMs += structured.predictMs;
|
|
3628
|
+
if (typeof structured.batchSize === "number" && structured.batchSize > poolAgg.batchSize) poolAgg.batchSize = structured.batchSize;
|
|
3629
|
+
const postprocessStart = Date.now();
|
|
3630
|
+
const output = dispatchPostprocess(engineOutput, step.definition);
|
|
3631
|
+
const postprocessMs = Date.now() - postprocessStart;
|
|
3632
|
+
stepTimings.push({
|
|
3633
|
+
source: step.stepId,
|
|
3634
|
+
modelId: step.modelId,
|
|
3635
|
+
ms: preprocessMs + inferenceMs + postprocessMs,
|
|
3636
|
+
detectionCount: output.kind === "detections" ? output.detections.length : 0
|
|
3789
3637
|
});
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3638
|
+
if (traceBuilder.isActive) traceBuilder.addStep({
|
|
3639
|
+
stepId: step.stepId,
|
|
3640
|
+
modelId: step.modelId,
|
|
3641
|
+
slot: step.definition.slot,
|
|
3642
|
+
postprocessor: step.definition.postprocessor,
|
|
3643
|
+
preprocessMs,
|
|
3644
|
+
inferenceMs,
|
|
3645
|
+
postprocessMs,
|
|
3646
|
+
inputType,
|
|
3647
|
+
inputWidth,
|
|
3648
|
+
inputHeight,
|
|
3649
|
+
parentDetection: parentClass,
|
|
3650
|
+
output
|
|
3651
|
+
});
|
|
3652
|
+
return output;
|
|
3795
3653
|
}
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
this.
|
|
3799
|
-
|
|
3654
|
+
async executeChildren(children, parentDetection, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg) {
|
|
3655
|
+
for (const child of children) {
|
|
3656
|
+
if (!this.matchesInputClasses(parentDetection.macroClass, child.inputClasses)) continue;
|
|
3657
|
+
const minParentScore = child.settings?.minParentScore ?? child.definition?.defaultMinParentScore;
|
|
3658
|
+
if (typeof minParentScore === "number" && parentDetection.score < minParentScore) continue;
|
|
3659
|
+
if (isBboxDegenerate(parentDetection.bbox)) continue;
|
|
3660
|
+
try {
|
|
3661
|
+
const childStart = Date.now();
|
|
3662
|
+
const fullFrameJpeg = await fullFrameJpegProvider();
|
|
3663
|
+
const modelEntry = child.definition.models.find((m) => m.id === child.modelId);
|
|
3664
|
+
let cropJpegBuf;
|
|
3665
|
+
let cropW;
|
|
3666
|
+
let cropH;
|
|
3667
|
+
if (modelEntry?.faceAlignment) {
|
|
3668
|
+
const minFaceSize = typeof child.settings?.["minFaceSize"] === "number" ? child.settings["minFaceSize"] : DEFAULT_MIN_FACE_SIZE_PX;
|
|
3669
|
+
if (bboxShortSide(parentDetection.bbox) < minFaceSize) continue;
|
|
3670
|
+
const lms = parentDetection.landmarks;
|
|
3671
|
+
if (lms && lms.length >= 5) {
|
|
3672
|
+
const aligned = await alignFaceCrop(fullFrameJpeg, parentDetection.bbox, lms, imageWidth, imageHeight, { outSize: modelEntry.inputSize.width });
|
|
3673
|
+
cropJpegBuf = aligned.jpeg;
|
|
3674
|
+
cropW = aligned.width;
|
|
3675
|
+
cropH = aligned.height;
|
|
3676
|
+
} else {
|
|
3677
|
+
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3678
|
+
cropJpegBuf = crop.jpeg;
|
|
3679
|
+
cropW = crop.width;
|
|
3680
|
+
cropH = crop.height;
|
|
3681
|
+
}
|
|
3682
|
+
} else {
|
|
3683
|
+
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3684
|
+
cropJpegBuf = crop.jpeg;
|
|
3685
|
+
cropW = crop.width;
|
|
3686
|
+
cropH = crop.height;
|
|
3687
|
+
}
|
|
3688
|
+
const childOutput = await this.executeStep(child, {
|
|
3689
|
+
kind: "jpeg",
|
|
3690
|
+
data: cropJpegBuf
|
|
3691
|
+
}, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
|
|
3692
|
+
const childMs = Date.now() - childStart;
|
|
3693
|
+
const detailsBefore = ctx.details.length;
|
|
3694
|
+
applyChildOutput(parentDetection, child, childOutput, childMs, ctx);
|
|
3695
|
+
if (childOutput.kind === "detections" && child.children.length > 0) {
|
|
3696
|
+
const newDetails = ctx.details.slice(detailsBefore);
|
|
3697
|
+
for (const detail of newDetails) {
|
|
3698
|
+
detail.bbox = transformBboxToImageSpace(detail.bbox, parentDetection.bbox);
|
|
3699
|
+
if (detail.landmarks) {
|
|
3700
|
+
const [px1, py1] = parentDetection.bbox;
|
|
3701
|
+
detail.landmarks = detail.landmarks.map((l) => ({
|
|
3702
|
+
x: l.x + px1,
|
|
3703
|
+
y: l.y + py1
|
|
3704
|
+
}));
|
|
3705
|
+
}
|
|
3706
|
+
await this.executeChildren(child.children, detail, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
} catch {}
|
|
3800
3710
|
}
|
|
3801
3711
|
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3712
|
+
matchesInputClasses(className, inputClasses) {
|
|
3713
|
+
if (inputClasses.length === 0) return true;
|
|
3714
|
+
return inputClasses.includes(className);
|
|
3805
3715
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
runtimeId,
|
|
3821
|
-
device,
|
|
3822
|
-
state: "ready"
|
|
3823
|
-
});
|
|
3824
|
-
} catch (err) {
|
|
3825
|
-
if (gen !== this.generation) return;
|
|
3826
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3827
|
-
const delay = BACKOFF_SCHEDULE_MS[Math.min(this.retryIndex, BACKOFF_SCHEDULE_MS.length - 1)];
|
|
3828
|
-
this.retryIndex++;
|
|
3829
|
-
const nextRetryAt = this.fx.now() + delay;
|
|
3830
|
-
this.transition({
|
|
3831
|
-
runtimeId,
|
|
3832
|
-
device,
|
|
3833
|
-
state: "failed",
|
|
3834
|
-
error: message,
|
|
3835
|
-
nextRetryAt
|
|
3836
|
-
});
|
|
3837
|
-
this.cancelTimer = this.fx.setTimer(delay, () => {
|
|
3838
|
-
if (gen !== this.generation) return;
|
|
3839
|
-
this.cancelTimer = null;
|
|
3840
|
-
this.transition({
|
|
3841
|
-
runtimeId,
|
|
3842
|
-
device,
|
|
3843
|
-
state: "installing",
|
|
3844
|
-
progress: 0
|
|
3845
|
-
});
|
|
3846
|
-
this.provision(gen, runtimeId, device);
|
|
3847
|
-
});
|
|
3716
|
+
/**
|
|
3717
|
+
* Apply the object-detection step's macro filter + per-macro
|
|
3718
|
+
* minConfidence sliders introduced in the Phase 6 step rework.
|
|
3719
|
+
*
|
|
3720
|
+
* Expected `settings` shape:
|
|
3721
|
+
* - `enabledMacroClasses`: readonly string[] (e.g. ['person','vehicle','animal']).
|
|
3722
|
+
* Empty array = all allowed (legacy behaviour).
|
|
3723
|
+
* - `minConfidence<Macro>`: number (e.g. `minConfidencePerson: 0.5`)
|
|
3724
|
+
*/
|
|
3725
|
+
matchesMacroFilter(macroClass, score, settings) {
|
|
3726
|
+
if (!settings) return true;
|
|
3727
|
+
const enabled = settings["enabledMacroClasses"];
|
|
3728
|
+
if (Array.isArray(enabled) && enabled.length > 0) {
|
|
3729
|
+
if (!enabled.includes(macroClass)) return false;
|
|
3848
3730
|
}
|
|
3731
|
+
const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
|
|
3732
|
+
if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return false;
|
|
3733
|
+
return true;
|
|
3849
3734
|
}
|
|
3850
3735
|
};
|
|
3736
|
+
function capitalize(s) {
|
|
3737
|
+
if (s.length === 0) return s;
|
|
3738
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3739
|
+
}
|
|
3851
3740
|
//#endregion
|
|
3852
|
-
//#region src/detection-pipeline/
|
|
3853
|
-
var PREFERENCE = [
|
|
3854
|
-
"coreml",
|
|
3855
|
-
"openvino",
|
|
3856
|
-
"onnx"
|
|
3857
|
-
];
|
|
3741
|
+
//#region src/detection-pipeline/pipeline/tree-builder.ts
|
|
3858
3742
|
/**
|
|
3859
|
-
*
|
|
3860
|
-
*
|
|
3861
|
-
* Logic:
|
|
3862
|
-
* 1. If `bestBackendHint` is in the supported set, use it.
|
|
3863
|
-
* 2. Otherwise, walk PREFERENCE order and pick the first supported runtime.
|
|
3864
|
-
* 3. Floor to `'onnx'` (always supported).
|
|
3743
|
+
* Build an executable tree from user config.
|
|
3865
3744
|
*
|
|
3866
|
-
*
|
|
3745
|
+
* @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
|
|
3746
|
+
* @param getEngine - Function that returns an IInferenceEngine for a step ID.
|
|
3747
|
+
* Throws if step not loaded.
|
|
3867
3748
|
*/
|
|
3868
|
-
function
|
|
3869
|
-
|
|
3870
|
-
|
|
3749
|
+
function buildExecutableTree(steps, getEngine) {
|
|
3750
|
+
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine)) };
|
|
3751
|
+
}
|
|
3752
|
+
function buildNode(step, getEngine) {
|
|
3753
|
+
const definition = getStepDefinition(step.addonId);
|
|
3754
|
+
const engine = getEngine(step.addonId);
|
|
3755
|
+
const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildNode(c, getEngine));
|
|
3756
|
+
const mergedSettings = {
|
|
3757
|
+
...collectSchemaDefaults(step.addonId),
|
|
3758
|
+
...step.settings
|
|
3759
|
+
};
|
|
3871
3760
|
return {
|
|
3872
|
-
|
|
3873
|
-
|
|
3761
|
+
stepId: step.addonId,
|
|
3762
|
+
definition,
|
|
3763
|
+
engine,
|
|
3764
|
+
modelId: step.modelId,
|
|
3765
|
+
inputClasses: definition.inputClasses ?? [],
|
|
3766
|
+
enabled: step.enabled,
|
|
3767
|
+
children,
|
|
3768
|
+
...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {}
|
|
3874
3769
|
};
|
|
3875
3770
|
}
|
|
3771
|
+
/**
|
|
3772
|
+
* Walk the step's declared `getConfigSchema()` and collect every field
|
|
3773
|
+
* with a primitive `default` value into a plain record. Group fields
|
|
3774
|
+
* are flattened — a nested `{ type: 'group', fields: [...] }` is
|
|
3775
|
+
* entered recursively. Fields without a declared default are skipped.
|
|
3776
|
+
*/
|
|
3777
|
+
function collectSchemaDefaults(stepId) {
|
|
3778
|
+
const schema = getStep(stepId).getConfigSchema();
|
|
3779
|
+
const out = {};
|
|
3780
|
+
walkFieldsForDefaults(schema, out);
|
|
3781
|
+
return out;
|
|
3782
|
+
}
|
|
3783
|
+
function walkFieldsForDefaults(fields, out) {
|
|
3784
|
+
for (const field of fields) {
|
|
3785
|
+
if ("type" in field && field.type === "group" && "fields" in field) {
|
|
3786
|
+
walkFieldsForDefaults(field.fields, out);
|
|
3787
|
+
continue;
|
|
3788
|
+
}
|
|
3789
|
+
if ("key" in field && "default" in field && field.default !== void 0) out[field.key] = field.default;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3876
3792
|
//#endregion
|
|
3877
3793
|
//#region src/detection-pipeline/provider.ts
|
|
3878
3794
|
/**
|
|
@@ -4154,6 +4070,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4154
4070
|
*/
|
|
4155
4071
|
needsAutoPick = false;
|
|
4156
4072
|
/**
|
|
4073
|
+
* Unsubscribe handle for the deferred-auto-pick `platform-probe` ready
|
|
4074
|
+
* listener (armed in `setApi` when the probe isn't ready yet). Cleared once
|
|
4075
|
+
* the engine is resolved — either by the listener firing or by the boot
|
|
4076
|
+
* safety-net `ensureBootEngineProvisioned`.
|
|
4077
|
+
*/
|
|
4078
|
+
deferredAutoPickUnsub = null;
|
|
4079
|
+
/**
|
|
4157
4080
|
* Warm cache for benchmark engine-override runs.
|
|
4158
4081
|
*
|
|
4159
4082
|
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
@@ -4186,7 +4109,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4186
4109
|
this.writeStore = (patch) => settings.writeAddonStore(patch);
|
|
4187
4110
|
this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
|
|
4188
4111
|
this.currentEngine = ONNX_FLOOR;
|
|
4189
|
-
this.log.info("Engine
|
|
4112
|
+
this.log.info("Engine pick pending (placeholder until probe / persisted selection)", { meta: {
|
|
4190
4113
|
runtime: this.currentEngine.runtime,
|
|
4191
4114
|
backend: this.currentEngine.backend,
|
|
4192
4115
|
format: this.currentEngine.format
|
|
@@ -4259,26 +4182,70 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4259
4182
|
this.needsAutoPick = false;
|
|
4260
4183
|
this.startProvisioningForCurrentEngine();
|
|
4261
4184
|
} else {
|
|
4262
|
-
this.startProvisioningForCurrentEngine();
|
|
4263
4185
|
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4264
4186
|
if (state !== "ready") return;
|
|
4265
|
-
|
|
4187
|
+
this.cancelDeferredAutoPick();
|
|
4266
4188
|
if (!this.needsAutoPick) return;
|
|
4267
4189
|
this.autoPickAndPersist().then(() => {
|
|
4268
4190
|
this.needsAutoPick = false;
|
|
4269
4191
|
this.startProvisioningForCurrentEngine();
|
|
4270
4192
|
});
|
|
4271
4193
|
});
|
|
4272
|
-
this.
|
|
4194
|
+
this.deferredAutoPickUnsub = unsubscribe;
|
|
4195
|
+
this.addonCtx.addDisposer(() => this.cancelDeferredAutoPick());
|
|
4273
4196
|
}
|
|
4274
4197
|
else this.startProvisioningForCurrentEngine();
|
|
4275
4198
|
}
|
|
4199
|
+
/** Tear down the deferred-auto-pick probe listener, if still armed. */
|
|
4200
|
+
cancelDeferredAutoPick() {
|
|
4201
|
+
if (this.deferredAutoPickUnsub) {
|
|
4202
|
+
this.deferredAutoPickUnsub();
|
|
4203
|
+
this.deferredAutoPickUnsub = null;
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Boot safety-net: deterministically provision the hardware-probed engine.
|
|
4208
|
+
*
|
|
4209
|
+
* Called from the addon's `onInitialize` right after `reprobeEngine` has
|
|
4210
|
+
* written this node's probe-driven selection (e.g. `engineBackend=openvino`)
|
|
4211
|
+
* to the store. When first-boot auto-pick was DEFERRED (probe not ready at
|
|
4212
|
+
* `setApi`), the engine would otherwise stay `idle` — selected but never
|
|
4213
|
+
* provisioned — because the `onCapabilityStateChange('platform-probe')` ready
|
|
4214
|
+
* edge can be missed when the cap is already 'ready' by the time we subscribe
|
|
4215
|
+
* (the old eager-onnx-floor masked this; removing it surfaced a node that
|
|
4216
|
+
* boots with no engine at all). This loads the now-persisted selection and
|
|
4217
|
+
* starts provisioning it, so the node reliably comes up on its real best
|
|
4218
|
+
* engine (openvino on Intel) with no onnx floor and no missed boot. No-op
|
|
4219
|
+
* when provisioning already started (persisted-engine path / listener fired)
|
|
4220
|
+
* or when nothing has been selected yet.
|
|
4221
|
+
*/
|
|
4222
|
+
async ensureBootEngineProvisioned() {
|
|
4223
|
+
if (this.getEngineProvisioning().state !== "idle") return;
|
|
4224
|
+
const stored = await this.loadEngine();
|
|
4225
|
+
if (!stored) return;
|
|
4226
|
+
this.currentEngine = stored;
|
|
4227
|
+
this.needsAutoPick = false;
|
|
4228
|
+
this.cancelDeferredAutoPick();
|
|
4229
|
+
this.log.info("Boot engine provisioning from probed selection", { meta: {
|
|
4230
|
+
runtime: stored.runtime,
|
|
4231
|
+
backend: stored.backend,
|
|
4232
|
+
device: stored.device ?? null
|
|
4233
|
+
} });
|
|
4234
|
+
this.startProvisioningForCurrentEngine();
|
|
4235
|
+
}
|
|
4276
4236
|
/**
|
|
4277
4237
|
* Auto-pick the best supported runtime at first boot (no stored engine).
|
|
4278
4238
|
* Uses the platform-probe cap's hardware + bestScore hint when available;
|
|
4279
4239
|
* falls back to platform/arch when the probe cap is not yet reachable.
|
|
4240
|
+
*
|
|
4280
4241
|
* Persists the selection as `engineBackend` + `engineDevice` so subsequent
|
|
4281
|
-
* boots load it via `loadEngine()` and skip this path
|
|
4242
|
+
* boots load it via `loadEngine()` and skip this path — but ONLY when the
|
|
4243
|
+
* probe actually answered (real `hardware` or a `bestScore` hint). If the
|
|
4244
|
+
* probe query failed (cold-start race, cap momentarily unreachable), the
|
|
4245
|
+
* pick floors to onnx purely for lack of information; persisting that would
|
|
4246
|
+
* LOCK onnx and skip auto-pick on every future boot even after the
|
|
4247
|
+
* accelerator surfaces. In that case we set the in-memory floor for liveness
|
|
4248
|
+
* but leave the store untouched so the next boot re-attempts the pick.
|
|
4282
4249
|
*/
|
|
4283
4250
|
async autoPickAndPersist() {
|
|
4284
4251
|
let hardware = null;
|
|
@@ -4300,6 +4267,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4300
4267
|
device: pick.device
|
|
4301
4268
|
};
|
|
4302
4269
|
this.currentEngine = engine;
|
|
4270
|
+
if (!(hardware !== null || bestBackendHint !== null)) {
|
|
4271
|
+
this.log.warn("Auto-pick: probe returned no hardware/hint — using onnx floor WITHOUT persisting", { meta: {
|
|
4272
|
+
backend: pick.runtimeId,
|
|
4273
|
+
device: pick.device
|
|
4274
|
+
} });
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4303
4277
|
const apNode = this.localProbeNodeId();
|
|
4304
4278
|
await this.writeStore({
|
|
4305
4279
|
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
@@ -6880,6 +6854,9 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6880
6854
|
if (!this.nodeProbedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
6881
6855
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6882
6856
|
});
|
|
6857
|
+
await this.provider.ensureBootEngineProvisioned().catch((err) => {
|
|
6858
|
+
this.ctx.logger.warn("ensureBootEngineProvisioned failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6859
|
+
});
|
|
6883
6860
|
await this.provider.warmPool();
|
|
6884
6861
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
6885
6862
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|