@camstack/addon-pipeline 1.0.7 → 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 +7 -8
- package/dist/audio-analyzer/index.mjs +3 -4
- package/dist/audio-codec-nodeav/index.js +2 -2
- package/dist/audio-codec-nodeav/index.mjs +2 -2
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +862 -696
- package/dist/detection-pipeline/index.mjs +850 -684
- package/dist/{dist-CjrjeaDd.mjs → dist-BA6DR_jV.mjs} +128 -5
- package/dist/{dist-G45MVm6i.js → dist-BLcTVvol.js} +151 -4
- 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/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/recorder/index.js +6 -6
- package/dist/recorder/index.mjs +4 -4
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Tbqpu0v3.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +26 -0
- package/dist/stream-broker/{hostInit-tIev5Gd9.mjs → hostInit-zLZbYJcg.mjs} +3 -3
- package/dist/stream-broker/index.js +26 -26
- package/dist/stream-broker/index.mjs +20 -20
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0kKwNX_.js → MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js} +1 -1
- package/embed-dist/assets/MotionZonesSettings-NcxxQN8r-CQzEnQoq.js +1 -0
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C2SRtNe6.js → PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js} +1 -1
- package/embed-dist/assets/{index-B2LRyXWh.js → index-CSuLwWK-.js} +3 -3
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/inference_pool.py +65 -6
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DCsgcqTa.mjs +0 -26
- package/embed-dist/assets/MotionZonesSettings-C1EEbk2V-CYtJc892.js +0 -1
|
@@ -1,11 +1,255 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as
|
|
3
|
-
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-
|
|
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-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,71 +2318,174 @@ var EngineFactory = class {
|
|
|
2158
2318
|
}
|
|
2159
2319
|
};
|
|
2160
2320
|
//#endregion
|
|
2161
|
-
//#region src/detection-pipeline/
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
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
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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
|
|
2330
|
+
];
|
|
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;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
transition(next) {
|
|
2375
|
+
this.current = next;
|
|
2376
|
+
this.fx.onChange(next);
|
|
2377
|
+
}
|
|
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
|
+
};
|
|
2423
|
+
//#endregion
|
|
2424
|
+
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
2425
|
+
var VALID_KINDS = new Set([
|
|
2426
|
+
"detections",
|
|
2427
|
+
"classifications",
|
|
2428
|
+
"embedding",
|
|
2429
|
+
"text",
|
|
2430
|
+
"mask"
|
|
2431
|
+
]);
|
|
2432
|
+
/**
|
|
2433
|
+
* Type guard: validates that a structured payload from the Python pool is a
|
|
2434
|
+
* well-formed StepOutput discriminated union.
|
|
2435
|
+
*
|
|
2436
|
+
* The Python inference_pool.py always sets `output.structured` with a `kind`
|
|
2437
|
+
* field. This guard narrows `Record<string, unknown>` to `StepOutput` without
|
|
2438
|
+
* resorting to double-cast.
|
|
2439
|
+
*/
|
|
2440
|
+
function isStepOutput(value) {
|
|
2441
|
+
return typeof value === "object" && value !== null && typeof value["kind"] === "string" && VALID_KINDS.has(value["kind"]);
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Return the structured payload as `StepOutput`, or null if it fails validation.
|
|
2445
|
+
* Callers must handle null by proceeding to raw-tensor postprocessing.
|
|
2446
|
+
*/
|
|
2447
|
+
function tryStructured(output) {
|
|
2448
|
+
if (!output.structured) return null;
|
|
2449
|
+
if (!isStepOutput(output.structured)) throw new Error(`Python pool returned structured output with unexpected kind: ${JSON.stringify(output.structured["kind"])}`);
|
|
2450
|
+
return output.structured;
|
|
2451
|
+
}
|
|
2452
|
+
function postprocessYolo(output, stepDef) {
|
|
2453
|
+
const structured = tryStructured(output);
|
|
2454
|
+
if (structured) return structured;
|
|
2455
|
+
const tensor = output.tensor;
|
|
2456
|
+
if (!tensor) throw new Error("YOLO postprocessor: no tensor in engine output");
|
|
2457
|
+
const labels = stepDef.labels ?? [];
|
|
2458
|
+
const numClasses = labels.length || 80;
|
|
2459
|
+
const numBoxes = tensor.length / (4 + numClasses);
|
|
2460
|
+
const letterbox = output.letterbox;
|
|
2461
|
+
const dets = [];
|
|
2462
|
+
for (let i = 0; i < numBoxes; i++) {
|
|
2463
|
+
const cx = tensor[i];
|
|
2464
|
+
const cy = tensor[1 * numBoxes + i];
|
|
2465
|
+
const w = tensor[2 * numBoxes + i];
|
|
2466
|
+
const h = tensor[3 * numBoxes + i];
|
|
2467
|
+
let bestScore = -Infinity;
|
|
2468
|
+
let bestClass = 0;
|
|
2469
|
+
for (let j = 0; j < numClasses; j++) {
|
|
2470
|
+
const score = tensor[(4 + j) * numBoxes + i];
|
|
2471
|
+
if (score > bestScore) {
|
|
2472
|
+
bestScore = score;
|
|
2473
|
+
bestClass = j;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (bestScore <= 0) continue;
|
|
2477
|
+
let x1 = cx - w / 2;
|
|
2478
|
+
let y1 = cy - h / 2;
|
|
2479
|
+
let x2 = cx + w / 2;
|
|
2480
|
+
let y2 = cy + h / 2;
|
|
2481
|
+
if (letterbox) {
|
|
2482
|
+
x1 = (x1 - letterbox.padX) / letterbox.scale;
|
|
2483
|
+
y1 = (y1 - letterbox.padY) / letterbox.scale;
|
|
2484
|
+
x2 = (x2 - letterbox.padX) / letterbox.scale;
|
|
2485
|
+
y2 = (y2 - letterbox.padY) / letterbox.scale;
|
|
2486
|
+
}
|
|
2487
|
+
const label = labels[bestClass] ?? String(bestClass);
|
|
2488
|
+
dets.push({
|
|
2226
2489
|
class: label,
|
|
2227
2490
|
score: bestScore,
|
|
2228
2491
|
bbox: [
|
|
@@ -2353,7 +2616,7 @@ function postprocessArcface(output, _stepDef) {
|
|
|
2353
2616
|
let sumSq = 0;
|
|
2354
2617
|
for (let i = 0; i < tensor.length; i++) sumSq += tensor[i] * tensor[i];
|
|
2355
2618
|
const norm = Math.sqrt(sumSq);
|
|
2356
|
-
const normalized =
|
|
2619
|
+
const normalized = Array.from({ length: tensor.length });
|
|
2357
2620
|
for (let i = 0; i < tensor.length; i++) normalized[i] = norm === 0 ? 0 : tensor[i] / norm;
|
|
2358
2621
|
return {
|
|
2359
2622
|
kind: "embedding",
|
|
@@ -3389,403 +3652,143 @@ var PipelineExecutor = class {
|
|
|
3389
3652
|
return output;
|
|
3390
3653
|
}
|
|
3391
3654
|
async executeChildren(children, parentDetection, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg) {
|
|
3392
|
-
for (const child of children) {
|
|
3393
|
-
if (!this.matchesInputClasses(parentDetection.macroClass, child.inputClasses)) continue;
|
|
3394
|
-
const minParentScore = child.settings?.minParentScore ?? child.definition?.defaultMinParentScore;
|
|
3395
|
-
if (typeof minParentScore === "number" && parentDetection.score < minParentScore) continue;
|
|
3396
|
-
if (isBboxDegenerate(parentDetection.bbox)) continue;
|
|
3397
|
-
try {
|
|
3398
|
-
const childStart = Date.now();
|
|
3399
|
-
const fullFrameJpeg = await fullFrameJpegProvider();
|
|
3400
|
-
const modelEntry = child.definition.models.find((m) => m.id === child.modelId);
|
|
3401
|
-
let cropJpegBuf;
|
|
3402
|
-
let cropW;
|
|
3403
|
-
let cropH;
|
|
3404
|
-
if (modelEntry?.faceAlignment) {
|
|
3405
|
-
const minFaceSize = typeof child.settings?.["minFaceSize"] === "number" ? child.settings["minFaceSize"] : DEFAULT_MIN_FACE_SIZE_PX;
|
|
3406
|
-
if (bboxShortSide(parentDetection.bbox) < minFaceSize) continue;
|
|
3407
|
-
const lms = parentDetection.landmarks;
|
|
3408
|
-
if (lms && lms.length >= 5) {
|
|
3409
|
-
const aligned = await alignFaceCrop(fullFrameJpeg, parentDetection.bbox, lms, imageWidth, imageHeight, { outSize: modelEntry.inputSize.width });
|
|
3410
|
-
cropJpegBuf = aligned.jpeg;
|
|
3411
|
-
cropW = aligned.width;
|
|
3412
|
-
cropH = aligned.height;
|
|
3413
|
-
} else {
|
|
3414
|
-
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3415
|
-
cropJpegBuf = crop.jpeg;
|
|
3416
|
-
cropW = crop.width;
|
|
3417
|
-
cropH = crop.height;
|
|
3418
|
-
}
|
|
3419
|
-
} else {
|
|
3420
|
-
const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
|
|
3421
|
-
cropJpegBuf = crop.jpeg;
|
|
3422
|
-
cropW = crop.width;
|
|
3423
|
-
cropH = crop.height;
|
|
3424
|
-
}
|
|
3425
|
-
const childOutput = await this.executeStep(child, {
|
|
3426
|
-
kind: "jpeg",
|
|
3427
|
-
data: cropJpegBuf
|
|
3428
|
-
}, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
|
|
3429
|
-
const childMs = Date.now() - childStart;
|
|
3430
|
-
const detailsBefore = ctx.details.length;
|
|
3431
|
-
applyChildOutput(parentDetection, child, childOutput, childMs, ctx);
|
|
3432
|
-
if (childOutput.kind === "detections" && child.children.length > 0) {
|
|
3433
|
-
const newDetails = ctx.details.slice(detailsBefore);
|
|
3434
|
-
for (const detail of newDetails) {
|
|
3435
|
-
detail.bbox = transformBboxToImageSpace(detail.bbox, parentDetection.bbox);
|
|
3436
|
-
if (detail.landmarks) {
|
|
3437
|
-
const [px1, py1] = parentDetection.bbox;
|
|
3438
|
-
detail.landmarks = detail.landmarks.map((l) => ({
|
|
3439
|
-
x: l.x + px1,
|
|
3440
|
-
y: l.y + py1
|
|
3441
|
-
}));
|
|
3442
|
-
}
|
|
3443
|
-
await this.executeChildren(child.children, detail, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg);
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
} catch {}
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
matchesInputClasses(className, inputClasses) {
|
|
3450
|
-
if (inputClasses.length === 0) return true;
|
|
3451
|
-
return inputClasses.includes(className);
|
|
3452
|
-
}
|
|
3453
|
-
/**
|
|
3454
|
-
* Apply the object-detection step's macro filter + per-macro
|
|
3455
|
-
* minConfidence sliders introduced in the Phase 6 step rework.
|
|
3456
|
-
*
|
|
3457
|
-
* Expected `settings` shape:
|
|
3458
|
-
* - `enabledMacroClasses`: readonly string[] (e.g. ['person','vehicle','animal']).
|
|
3459
|
-
* Empty array = all allowed (legacy behaviour).
|
|
3460
|
-
* - `minConfidence<Macro>`: number (e.g. `minConfidencePerson: 0.5`)
|
|
3461
|
-
*/
|
|
3462
|
-
matchesMacroFilter(macroClass, score, settings) {
|
|
3463
|
-
if (!settings) return true;
|
|
3464
|
-
const enabled = settings["enabledMacroClasses"];
|
|
3465
|
-
if (Array.isArray(enabled) && enabled.length > 0) {
|
|
3466
|
-
if (!enabled.includes(macroClass)) return false;
|
|
3467
|
-
}
|
|
3468
|
-
const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
|
|
3469
|
-
if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return false;
|
|
3470
|
-
return true;
|
|
3471
|
-
}
|
|
3472
|
-
};
|
|
3473
|
-
function capitalize(s) {
|
|
3474
|
-
if (s.length === 0) return s;
|
|
3475
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3476
|
-
}
|
|
3477
|
-
//#endregion
|
|
3478
|
-
//#region src/detection-pipeline/pipeline/tree-builder.ts
|
|
3479
|
-
/**
|
|
3480
|
-
* Build an executable tree from user config.
|
|
3481
|
-
*
|
|
3482
|
-
* @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
|
|
3483
|
-
* @param getEngine - Function that returns an IInferenceEngine for a step ID.
|
|
3484
|
-
* Throws if step not loaded.
|
|
3485
|
-
*/
|
|
3486
|
-
function buildExecutableTree(steps, getEngine) {
|
|
3487
|
-
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine)) };
|
|
3488
|
-
}
|
|
3489
|
-
function buildNode(step, getEngine) {
|
|
3490
|
-
const definition = getStepDefinition(step.addonId);
|
|
3491
|
-
const engine = getEngine(step.addonId);
|
|
3492
|
-
const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildNode(c, getEngine));
|
|
3493
|
-
const mergedSettings = {
|
|
3494
|
-
...collectSchemaDefaults(step.addonId),
|
|
3495
|
-
...step.settings
|
|
3496
|
-
};
|
|
3497
|
-
return {
|
|
3498
|
-
stepId: step.addonId,
|
|
3499
|
-
definition,
|
|
3500
|
-
engine,
|
|
3501
|
-
modelId: step.modelId,
|
|
3502
|
-
inputClasses: definition.inputClasses ?? [],
|
|
3503
|
-
enabled: step.enabled,
|
|
3504
|
-
children,
|
|
3505
|
-
...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {}
|
|
3506
|
-
};
|
|
3507
|
-
}
|
|
3508
|
-
/**
|
|
3509
|
-
* Walk the step's declared `getConfigSchema()` and collect every field
|
|
3510
|
-
* with a primitive `default` value into a plain record. Group fields
|
|
3511
|
-
* are flattened — a nested `{ type: 'group', fields: [...] }` is
|
|
3512
|
-
* entered recursively. Fields without a declared default are skipped.
|
|
3513
|
-
*/
|
|
3514
|
-
function collectSchemaDefaults(stepId) {
|
|
3515
|
-
const schema = getStep(stepId).getConfigSchema();
|
|
3516
|
-
const out = {};
|
|
3517
|
-
walkFieldsForDefaults(schema, out);
|
|
3518
|
-
return out;
|
|
3519
|
-
}
|
|
3520
|
-
function walkFieldsForDefaults(fields, out) {
|
|
3521
|
-
for (const field of fields) {
|
|
3522
|
-
if ("type" in field && field.type === "group" && "fields" in field) {
|
|
3523
|
-
walkFieldsForDefaults(field.fields, out);
|
|
3524
|
-
continue;
|
|
3525
|
-
}
|
|
3526
|
-
if ("key" in field && "default" in field && field.default !== void 0) out[field.key] = field.default;
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
//#endregion
|
|
3530
|
-
//#region src/detection-pipeline/runtimes.ts
|
|
3531
|
-
var AUTO = {
|
|
3532
|
-
value: "auto",
|
|
3533
|
-
label: "Auto"
|
|
3534
|
-
};
|
|
3535
|
-
var CPU = {
|
|
3536
|
-
value: "cpu",
|
|
3537
|
-
label: "CPU"
|
|
3538
|
-
};
|
|
3539
|
-
var RUNTIMES = [
|
|
3540
|
-
{
|
|
3541
|
-
id: "onnx",
|
|
3542
|
-
label: "ONNX Runtime",
|
|
3543
|
-
supports: () => true,
|
|
3544
|
-
devices: (hw) => {
|
|
3545
|
-
if (!hw) return [CPU];
|
|
3546
|
-
const out = [CPU];
|
|
3547
|
-
if (hw.gpu?.type === "nvidia") out.push({
|
|
3548
|
-
value: "cuda",
|
|
3549
|
-
label: "CUDA"
|
|
3550
|
-
});
|
|
3551
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3552
|
-
value: "coreml",
|
|
3553
|
-
label: "CoreML EP"
|
|
3554
|
-
});
|
|
3555
|
-
return out;
|
|
3556
|
-
},
|
|
3557
|
-
defaultDevice: "cpu",
|
|
3558
|
-
modelFormat: "onnx",
|
|
3559
|
-
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
3560
|
-
tuning: {
|
|
3561
|
-
concurrency: 4,
|
|
3562
|
-
batchMode: "list",
|
|
3563
|
-
maxBatchSize: 8,
|
|
3564
|
-
intraOpThreads: 0
|
|
3565
|
-
}
|
|
3566
|
-
},
|
|
3567
|
-
{
|
|
3568
|
-
id: "openvino",
|
|
3569
|
-
label: "OpenVINO",
|
|
3570
|
-
supports: (env) => env.arch === "x64" && env.platform !== "darwin" && env.hardware?.gpu?.type === "intel",
|
|
3571
|
-
devices: (hw) => {
|
|
3572
|
-
if (!hw) return [AUTO];
|
|
3573
|
-
const out = [AUTO, CPU];
|
|
3574
|
-
if (hw.gpu?.type === "intel") out.push({
|
|
3575
|
-
value: "gpu",
|
|
3576
|
-
label: "GPU"
|
|
3577
|
-
});
|
|
3578
|
-
if (hw.npu?.type === "intel-npu") out.push({
|
|
3579
|
-
value: "npu",
|
|
3580
|
-
label: "NPU"
|
|
3581
|
-
});
|
|
3582
|
-
return out;
|
|
3583
|
-
},
|
|
3584
|
-
defaultDevice: "auto",
|
|
3585
|
-
modelFormat: "openvino",
|
|
3586
|
-
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
3587
|
-
tuning: {
|
|
3588
|
-
concurrency: 1,
|
|
3589
|
-
batchMode: "none",
|
|
3590
|
-
numStreams: 0
|
|
3591
|
-
}
|
|
3592
|
-
},
|
|
3593
|
-
{
|
|
3594
|
-
id: "coreml",
|
|
3595
|
-
label: "CoreML",
|
|
3596
|
-
supports: (env) => env.platform === "darwin",
|
|
3597
|
-
devices: (hw) => {
|
|
3598
|
-
const all = {
|
|
3599
|
-
value: "all",
|
|
3600
|
-
label: "All (ANE + GPU + CPU)"
|
|
3601
|
-
};
|
|
3602
|
-
if (!hw) return [all];
|
|
3603
|
-
const out = [all];
|
|
3604
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3605
|
-
value: "ane",
|
|
3606
|
-
label: "Apple Neural Engine"
|
|
3607
|
-
});
|
|
3608
|
-
out.push({
|
|
3609
|
-
value: "gpu",
|
|
3610
|
-
label: "GPU"
|
|
3611
|
-
}, CPU);
|
|
3612
|
-
return out;
|
|
3613
|
-
},
|
|
3614
|
-
defaultDevice: "all",
|
|
3615
|
-
modelFormat: "coreml",
|
|
3616
|
-
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
3617
|
-
tuning: {
|
|
3618
|
-
concurrency: 1,
|
|
3619
|
-
batchMode: "none",
|
|
3620
|
-
windowMs: 8,
|
|
3621
|
-
maxBatchSize: 8,
|
|
3622
|
-
numWorkers: 1
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
];
|
|
3626
|
-
function def(id) {
|
|
3627
|
-
const d = RUNTIMES.find((r) => r.id === id);
|
|
3628
|
-
if (!d) throw new Error(`Unknown runtime: ${id}`);
|
|
3629
|
-
return d;
|
|
3630
|
-
}
|
|
3631
|
-
function supportedRuntimes(env) {
|
|
3632
|
-
return RUNTIMES.filter((r) => r.supports(env)).map((r) => r.id);
|
|
3633
|
-
}
|
|
3634
|
-
function runtimeDevices(id, hardware) {
|
|
3635
|
-
return def(id).devices(hardware);
|
|
3636
|
-
}
|
|
3637
|
-
function defaultDeviceFor(id) {
|
|
3638
|
-
return def(id).defaultDevice;
|
|
3639
|
-
}
|
|
3640
|
-
function pythonRequirementsFor(id) {
|
|
3641
|
-
return def(id).pythonRequirements;
|
|
3642
|
-
}
|
|
3643
|
-
function modelFormatFor(id) {
|
|
3644
|
-
return def(id).modelFormat;
|
|
3645
|
-
}
|
|
3646
|
-
function tuningFor(id) {
|
|
3647
|
-
return def(id).tuning;
|
|
3648
|
-
}
|
|
3649
|
-
function runtimeLabel(id) {
|
|
3650
|
-
return def(id).label;
|
|
3651
|
-
}
|
|
3652
|
-
/**
|
|
3653
|
-
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
3654
|
-
* Intel on Linux warrants installing openvino; coremltools covers macOS;
|
|
3655
|
-
* openvino has no AMD/NVIDIA backend.
|
|
3656
|
-
*/
|
|
3657
|
-
function shouldInstallOpenvino(env) {
|
|
3658
|
-
if (env.platform === "darwin") return false;
|
|
3659
|
-
return env.gpu?.type === "intel" || env.npu?.type === "intel-npu";
|
|
3660
|
-
}
|
|
3661
|
-
//#endregion
|
|
3662
|
-
//#region src/detection-pipeline/engine-provisioner.ts
|
|
3663
|
-
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
3664
|
-
var BACKOFF_SCHEDULE_MS = [
|
|
3665
|
-
5e3,
|
|
3666
|
-
15e3,
|
|
3667
|
-
3e4,
|
|
3668
|
-
6e4,
|
|
3669
|
-
12e4,
|
|
3670
|
-
3e5
|
|
3671
|
-
];
|
|
3672
|
-
var IDLE_STATE = {
|
|
3673
|
-
runtimeId: null,
|
|
3674
|
-
device: null,
|
|
3675
|
-
state: "idle"
|
|
3676
|
-
};
|
|
3677
|
-
var EngineProvisioner = class {
|
|
3678
|
-
fx;
|
|
3679
|
-
current = IDLE_STATE;
|
|
3680
|
-
/** Bumped on every select/dispose — stale async results (old generation) are ignored. */
|
|
3681
|
-
generation = 0;
|
|
3682
|
-
cancelTimer = null;
|
|
3683
|
-
retryIndex = 0;
|
|
3684
|
-
constructor(fx) {
|
|
3685
|
-
this.fx = fx;
|
|
3686
|
-
}
|
|
3687
|
-
get state() {
|
|
3688
|
-
return this.current;
|
|
3689
|
-
}
|
|
3690
|
-
isReady() {
|
|
3691
|
-
return this.current.state === "ready";
|
|
3692
|
-
}
|
|
3693
|
-
select(runtimeId, device) {
|
|
3694
|
-
this.generation++;
|
|
3695
|
-
this.retryIndex = 0;
|
|
3696
|
-
this.clearTimer();
|
|
3697
|
-
this.transition({
|
|
3698
|
-
runtimeId,
|
|
3699
|
-
device,
|
|
3700
|
-
state: "installing",
|
|
3701
|
-
progress: 0
|
|
3702
|
-
});
|
|
3703
|
-
this.provision(this.generation, runtimeId, device);
|
|
3704
|
-
}
|
|
3705
|
-
dispose() {
|
|
3706
|
-
this.generation++;
|
|
3707
|
-
this.clearTimer();
|
|
3708
|
-
}
|
|
3709
|
-
clearTimer() {
|
|
3710
|
-
if (this.cancelTimer !== null) {
|
|
3711
|
-
this.cancelTimer();
|
|
3712
|
-
this.cancelTimer = null;
|
|
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 {}
|
|
3713
3710
|
}
|
|
3714
3711
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3712
|
+
matchesInputClasses(className, inputClasses) {
|
|
3713
|
+
if (inputClasses.length === 0) return true;
|
|
3714
|
+
return inputClasses.includes(className);
|
|
3718
3715
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
runtimeId,
|
|
3734
|
-
device,
|
|
3735
|
-
state: "ready"
|
|
3736
|
-
});
|
|
3737
|
-
} catch (err) {
|
|
3738
|
-
if (gen !== this.generation) return;
|
|
3739
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3740
|
-
const delay = BACKOFF_SCHEDULE_MS[Math.min(this.retryIndex, BACKOFF_SCHEDULE_MS.length - 1)];
|
|
3741
|
-
this.retryIndex++;
|
|
3742
|
-
const nextRetryAt = this.fx.now() + delay;
|
|
3743
|
-
this.transition({
|
|
3744
|
-
runtimeId,
|
|
3745
|
-
device,
|
|
3746
|
-
state: "failed",
|
|
3747
|
-
error: message,
|
|
3748
|
-
nextRetryAt
|
|
3749
|
-
});
|
|
3750
|
-
this.cancelTimer = this.fx.setTimer(delay, () => {
|
|
3751
|
-
if (gen !== this.generation) return;
|
|
3752
|
-
this.cancelTimer = null;
|
|
3753
|
-
this.transition({
|
|
3754
|
-
runtimeId,
|
|
3755
|
-
device,
|
|
3756
|
-
state: "installing",
|
|
3757
|
-
progress: 0
|
|
3758
|
-
});
|
|
3759
|
-
this.provision(gen, runtimeId, device);
|
|
3760
|
-
});
|
|
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;
|
|
3761
3730
|
}
|
|
3731
|
+
const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
|
|
3732
|
+
if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return false;
|
|
3733
|
+
return true;
|
|
3762
3734
|
}
|
|
3763
3735
|
};
|
|
3736
|
+
function capitalize(s) {
|
|
3737
|
+
if (s.length === 0) return s;
|
|
3738
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3739
|
+
}
|
|
3764
3740
|
//#endregion
|
|
3765
|
-
//#region src/detection-pipeline/
|
|
3766
|
-
var PREFERENCE = [
|
|
3767
|
-
"coreml",
|
|
3768
|
-
"openvino",
|
|
3769
|
-
"onnx"
|
|
3770
|
-
];
|
|
3741
|
+
//#region src/detection-pipeline/pipeline/tree-builder.ts
|
|
3771
3742
|
/**
|
|
3772
|
-
*
|
|
3773
|
-
*
|
|
3774
|
-
* Logic:
|
|
3775
|
-
* 1. If `bestBackendHint` is in the supported set, use it.
|
|
3776
|
-
* 2. Otherwise, walk PREFERENCE order and pick the first supported runtime.
|
|
3777
|
-
* 3. Floor to `'onnx'` (always supported).
|
|
3743
|
+
* Build an executable tree from user config.
|
|
3778
3744
|
*
|
|
3779
|
-
*
|
|
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.
|
|
3780
3748
|
*/
|
|
3781
|
-
function
|
|
3782
|
-
|
|
3783
|
-
|
|
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
|
+
};
|
|
3784
3760
|
return {
|
|
3785
|
-
|
|
3786
|
-
|
|
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 } : {}
|
|
3787
3769
|
};
|
|
3788
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
|
+
}
|
|
3789
3792
|
//#endregion
|
|
3790
3793
|
//#region src/detection-pipeline/provider.ts
|
|
3791
3794
|
/**
|
|
@@ -3988,6 +3991,27 @@ function toProbedHardware(hw) {
|
|
|
3988
3991
|
gpu: hw.gpu ? { type: hw.gpu.type } : null
|
|
3989
3992
|
};
|
|
3990
3993
|
}
|
|
3994
|
+
var ONNX_FLOOR = {
|
|
3995
|
+
runtime: "python",
|
|
3996
|
+
backend: "onnx",
|
|
3997
|
+
format: "onnx",
|
|
3998
|
+
device: "cpu"
|
|
3999
|
+
};
|
|
4000
|
+
/**
|
|
4001
|
+
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
4002
|
+
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
4003
|
+
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
4004
|
+
* instead of duplicated inline.
|
|
4005
|
+
*/
|
|
4006
|
+
function onnxFloorPick() {
|
|
4007
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
4008
|
+
return {
|
|
4009
|
+
runtime: "python",
|
|
4010
|
+
backend: pick.runtimeId,
|
|
4011
|
+
format: modelFormatFor(pick.runtimeId),
|
|
4012
|
+
device: pick.device
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
3991
4015
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
3992
4016
|
modelsDir;
|
|
3993
4017
|
eventBus;
|
|
@@ -4046,6 +4070,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4046
4070
|
*/
|
|
4047
4071
|
needsAutoPick = false;
|
|
4048
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
|
+
/**
|
|
4049
4080
|
* Warm cache for benchmark engine-override runs.
|
|
4050
4081
|
*
|
|
4051
4082
|
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
@@ -4077,8 +4108,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4077
4108
|
this.readStore = () => settings.readAddonStore();
|
|
4078
4109
|
this.writeStore = (patch) => settings.writeAddonStore(patch);
|
|
4079
4110
|
this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
|
|
4080
|
-
this.currentEngine =
|
|
4081
|
-
this.log.info("Engine
|
|
4111
|
+
this.currentEngine = ONNX_FLOOR;
|
|
4112
|
+
this.log.info("Engine pick pending (placeholder until probe / persisted selection)", { meta: {
|
|
4082
4113
|
runtime: this.currentEngine.runtime,
|
|
4083
4114
|
backend: this.currentEngine.backend,
|
|
4084
4115
|
format: this.currentEngine.format
|
|
@@ -4143,69 +4174,78 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4143
4174
|
isReady() {
|
|
4144
4175
|
return this.ready;
|
|
4145
4176
|
}
|
|
4146
|
-
/** Detect the best inference engine for this platform. */
|
|
4147
|
-
static detectBestEngine() {
|
|
4148
|
-
const platform = process.platform;
|
|
4149
|
-
const arch = process.arch;
|
|
4150
|
-
if (platform === "darwin" && arch === "arm64") return {
|
|
4151
|
-
runtime: "python",
|
|
4152
|
-
backend: "coreml",
|
|
4153
|
-
format: "coreml",
|
|
4154
|
-
device: "all"
|
|
4155
|
-
};
|
|
4156
|
-
if (platform === "darwin") return {
|
|
4157
|
-
runtime: "python",
|
|
4158
|
-
backend: "coreml",
|
|
4159
|
-
format: "coreml",
|
|
4160
|
-
device: "gpu"
|
|
4161
|
-
};
|
|
4162
|
-
try {
|
|
4163
|
-
const { execFileSync } = __require("node:child_process");
|
|
4164
|
-
execFileSync("nvidia-smi", ["--query-gpu=name", "--format=csv,noheader"], {
|
|
4165
|
-
timeout: 3e3,
|
|
4166
|
-
stdio: "pipe"
|
|
4167
|
-
});
|
|
4168
|
-
return {
|
|
4169
|
-
runtime: "python",
|
|
4170
|
-
backend: "onnx",
|
|
4171
|
-
format: "onnx",
|
|
4172
|
-
device: "cuda"
|
|
4173
|
-
};
|
|
4174
|
-
} catch {}
|
|
4175
|
-
try {
|
|
4176
|
-
const fsmod = __require("node:fs");
|
|
4177
|
-
const isIntel = __require("node:os").cpus()[0]?.model?.includes("Intel") ?? false;
|
|
4178
|
-
const hasIgpu = fsmod.existsSync("/dev/dri/renderD128");
|
|
4179
|
-
const hasNpu = fsmod.existsSync("/dev/accel/accel0") || fsmod.existsSync("/dev/accel");
|
|
4180
|
-
if (isIntel && (hasIgpu || hasNpu)) return {
|
|
4181
|
-
runtime: "python",
|
|
4182
|
-
backend: "openvino",
|
|
4183
|
-
format: "openvino",
|
|
4184
|
-
device: "auto"
|
|
4185
|
-
};
|
|
4186
|
-
} catch {}
|
|
4187
|
-
return {
|
|
4188
|
-
runtime: "python",
|
|
4189
|
-
backend: "onnx",
|
|
4190
|
-
format: "onnx",
|
|
4191
|
-
device: "cpu"
|
|
4192
|
-
};
|
|
4193
|
-
}
|
|
4194
4177
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
4195
4178
|
async setApi(addonCtx) {
|
|
4196
4179
|
this.addonCtx = addonCtx;
|
|
4197
|
-
if (this.needsAutoPick) {
|
|
4180
|
+
if (this.needsAutoPick) if (this.addonCtx.useCapability("platform-probe").isReady) {
|
|
4198
4181
|
await this.autoPickAndPersist();
|
|
4199
4182
|
this.needsAutoPick = false;
|
|
4183
|
+
this.startProvisioningForCurrentEngine();
|
|
4184
|
+
} else {
|
|
4185
|
+
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4186
|
+
if (state !== "ready") return;
|
|
4187
|
+
this.cancelDeferredAutoPick();
|
|
4188
|
+
if (!this.needsAutoPick) return;
|
|
4189
|
+
this.autoPickAndPersist().then(() => {
|
|
4190
|
+
this.needsAutoPick = false;
|
|
4191
|
+
this.startProvisioningForCurrentEngine();
|
|
4192
|
+
});
|
|
4193
|
+
});
|
|
4194
|
+
this.deferredAutoPickUnsub = unsubscribe;
|
|
4195
|
+
this.addonCtx.addDisposer(() => this.cancelDeferredAutoPick());
|
|
4200
4196
|
}
|
|
4197
|
+
else this.startProvisioningForCurrentEngine();
|
|
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
|
+
} });
|
|
4201
4234
|
this.startProvisioningForCurrentEngine();
|
|
4202
4235
|
}
|
|
4203
4236
|
/**
|
|
4204
4237
|
* Auto-pick the best supported runtime at first boot (no stored engine).
|
|
4205
4238
|
* Uses the platform-probe cap's hardware + bestScore hint when available;
|
|
4206
4239
|
* falls back to platform/arch when the probe cap is not yet reachable.
|
|
4240
|
+
*
|
|
4207
4241
|
* Persists the selection as `engineBackend` + `engineDevice` so subsequent
|
|
4208
|
-
* 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.
|
|
4209
4249
|
*/
|
|
4210
4250
|
async autoPickAndPersist() {
|
|
4211
4251
|
let hardware = null;
|
|
@@ -4213,7 +4253,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4213
4253
|
try {
|
|
4214
4254
|
const api = this.addonCtx?.api;
|
|
4215
4255
|
if (api) {
|
|
4216
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
4256
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
4217
4257
|
hardware = caps?.hardware ?? null;
|
|
4218
4258
|
const bs = caps?.bestScore;
|
|
4219
4259
|
if (bs && bs.runtime === "python") bestBackendHint = bs.backend;
|
|
@@ -4227,9 +4267,17 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4227
4267
|
device: pick.device
|
|
4228
4268
|
};
|
|
4229
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
|
+
}
|
|
4277
|
+
const apNode = this.localProbeNodeId();
|
|
4230
4278
|
await this.writeStore({
|
|
4231
|
-
engineBackend: pick.runtimeId,
|
|
4232
|
-
engineDevice: pick.device
|
|
4279
|
+
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4280
|
+
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4233
4281
|
});
|
|
4234
4282
|
this.log.info("Auto-picked engine at first boot", { meta: {
|
|
4235
4283
|
backend: pick.runtimeId,
|
|
@@ -4402,11 +4450,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4402
4450
|
* callers fall back to the registry's safe minimum. The engine OFFER derives
|
|
4403
4451
|
* from hardware ONLY — install state (probe `scores`) never gates it.
|
|
4404
4452
|
*/
|
|
4453
|
+
/**
|
|
4454
|
+
* The local Moleculer node id (child-suffix stripped). MUST be passed to every
|
|
4455
|
+
* `platformProbe.getCapabilities` query: the cap is a singleton and a query
|
|
4456
|
+
* with no nodeId resolves to the HUB's probe — so on a remote agent the engine
|
|
4457
|
+
* decision would use the HUB's hardware (e.g. an Intel NPU the agent doesn't
|
|
4458
|
+
* have) and pin a device the node can't run, breaking provisioning.
|
|
4459
|
+
*/
|
|
4460
|
+
localProbeNodeId() {
|
|
4461
|
+
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
4462
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
4463
|
+
}
|
|
4405
4464
|
async fetchProbeGatingData() {
|
|
4406
4465
|
try {
|
|
4407
4466
|
const api = this.addonCtx?.api;
|
|
4408
4467
|
if (!api) return { hardware: null };
|
|
4409
|
-
return { hardware: (await api.platformProbe.getCapabilities.query())?.hardware ?? null };
|
|
4468
|
+
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4410
4469
|
} catch {
|
|
4411
4470
|
return { hardware: null };
|
|
4412
4471
|
}
|
|
@@ -5471,7 +5530,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5471
5530
|
this.engineFactory = null;
|
|
5472
5531
|
this.executor = null;
|
|
5473
5532
|
}
|
|
5474
|
-
for (const id of
|
|
5533
|
+
for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
|
|
5475
5534
|
}
|
|
5476
5535
|
/**
|
|
5477
5536
|
* Resolve and cache a {@link DeviceProxy} for the given camera. Pins
|
|
@@ -5655,25 +5714,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5655
5714
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
5656
5715
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
5657
5716
|
* Returns null when neither source has anything; the caller keeps
|
|
5658
|
-
*
|
|
5717
|
+
* the onnx floor set at construction until autoPickAndPersist() runs.
|
|
5659
5718
|
*/
|
|
5660
5719
|
async loadEngine() {
|
|
5661
5720
|
const store = await this.readStore();
|
|
5662
5721
|
const storedRuntime = store["engineRuntime"];
|
|
5663
|
-
const
|
|
5722
|
+
const node = this.localProbeNodeId();
|
|
5723
|
+
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5664
5724
|
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
5665
5725
|
const backend = storedBackend;
|
|
5666
|
-
const
|
|
5667
|
-
const
|
|
5726
|
+
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5727
|
+
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5728
|
+
const floor = onnxFloorPick();
|
|
5668
5729
|
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5669
5730
|
if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
|
|
5670
|
-
this.log.warn("Stored engine backend unavailable on this node — falling back to
|
|
5731
|
+
this.log.warn("Stored engine backend unavailable on this node — falling back to onnx floor", { meta: {
|
|
5671
5732
|
stored: migratedBackend,
|
|
5672
|
-
fallback: `${
|
|
5733
|
+
fallback: `${floor.backend}`
|
|
5673
5734
|
} });
|
|
5674
|
-
return
|
|
5735
|
+
return floor;
|
|
5675
5736
|
}
|
|
5676
|
-
const device = storedDevice ||
|
|
5737
|
+
const device = storedDevice || floor.device;
|
|
5677
5738
|
return {
|
|
5678
5739
|
runtime: "python",
|
|
5679
5740
|
backend: migratedBackend,
|
|
@@ -5845,18 +5906,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5845
5906
|
const api = this.addonCtx?.api;
|
|
5846
5907
|
let best;
|
|
5847
5908
|
if (api) try {
|
|
5848
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
5909
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
5849
5910
|
const bs = caps?.bestScore;
|
|
5850
5911
|
if (bs && bs.runtime === "python") {
|
|
5851
5912
|
const probeBackend = bs.backend;
|
|
5852
5913
|
const probeDevice = (() => {
|
|
5853
5914
|
const hw = caps.hardware;
|
|
5854
|
-
if (probeBackend === "
|
|
5855
|
-
if (probeBackend === "
|
|
5856
|
-
if (hw?.npu?.type === "intel-npu") return "npu";
|
|
5857
|
-
if (hw?.gpu?.type === "intel") return "gpu";
|
|
5858
|
-
return "auto";
|
|
5859
|
-
}
|
|
5915
|
+
if (probeBackend === "openvino") return defaultDeviceFor("openvino");
|
|
5916
|
+
if (probeBackend === "coreml") return defaultDeviceFor("coreml");
|
|
5860
5917
|
if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
|
|
5861
5918
|
return "cpu";
|
|
5862
5919
|
})();
|
|
@@ -5866,16 +5923,17 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5866
5923
|
format: backendToFormat(probeBackend),
|
|
5867
5924
|
device: probeDevice
|
|
5868
5925
|
};
|
|
5869
|
-
} else best =
|
|
5926
|
+
} else best = onnxFloorPick();
|
|
5870
5927
|
} catch {
|
|
5871
|
-
best =
|
|
5928
|
+
best = onnxFloorPick();
|
|
5872
5929
|
}
|
|
5873
|
-
else best =
|
|
5930
|
+
else best = onnxFloorPick();
|
|
5874
5931
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
5932
|
+
const rpNode = this.localProbeNodeId();
|
|
5875
5933
|
await this.writeStore({
|
|
5876
|
-
probedBestEngine: probedLabel,
|
|
5877
|
-
engineBackend: best.backend,
|
|
5878
|
-
engineDevice: best.device ?? "cpu"
|
|
5934
|
+
[nodeEngineKey("probedBestEngine", rpNode)]: probedLabel,
|
|
5935
|
+
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
5936
|
+
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
5879
5937
|
});
|
|
5880
5938
|
this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
|
|
5881
5939
|
backend: best.backend,
|
|
@@ -6298,7 +6356,8 @@ var DEFAULT_CONFIG = {
|
|
|
6298
6356
|
engineRuntime: "python",
|
|
6299
6357
|
engineBackend: "onnx",
|
|
6300
6358
|
engineDevice: "cpu",
|
|
6301
|
-
probedBestEngine: ""
|
|
6359
|
+
probedBestEngine: "",
|
|
6360
|
+
activeEngine: ""
|
|
6302
6361
|
};
|
|
6303
6362
|
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6304
6363
|
function backendToFormat(backend) {
|
|
@@ -6341,6 +6400,15 @@ var POOL_BOUND_KEYS = [
|
|
|
6341
6400
|
];
|
|
6342
6401
|
var DetectionPipelineAddon = class extends BaseAddon {
|
|
6343
6402
|
provider = null;
|
|
6403
|
+
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6404
|
+
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
6405
|
+
* node because the hub addon probes other nodes when serving their config. */
|
|
6406
|
+
lastGoodHardwareByNode = /* @__PURE__ */ new Map();
|
|
6407
|
+
/** This node's effective engine selection, cached from the node-scoped store
|
|
6408
|
+
* so the synchronous `resolveBackendTuning` and the reprobe gate don't read
|
|
6409
|
+
* the cluster-shared bare keys. Refreshed at init + on every config change. */
|
|
6410
|
+
nodeEngineBackend = DEFAULT_CONFIG.engineBackend;
|
|
6411
|
+
nodeProbedBestEngine = "";
|
|
6344
6412
|
engineMetricsTimer = null;
|
|
6345
6413
|
/** Snapshot-equality cache for engine-metrics emit. Most ticks
|
|
6346
6414
|
* the engine inventory is unchanged (no model load/unload), so
|
|
@@ -6395,6 +6463,14 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6395
6463
|
tooltip: "Re-probe engine"
|
|
6396
6464
|
}]
|
|
6397
6465
|
}),
|
|
6466
|
+
this.field({
|
|
6467
|
+
type: "text",
|
|
6468
|
+
key: "activeEngine",
|
|
6469
|
+
label: "Active engine",
|
|
6470
|
+
description: "The runtime/device the inference pool ACTUALLY loaded on this node, with its provisioning state (format: provider/device (state)). If this differs from the selected provider below — e.g. \"onnx/cpu (ready)\" while OpenVINO is selected — the chosen accelerator could not load on this host and the engine fell back to the ONNX-CPU baseline.",
|
|
6471
|
+
readonlyField: true,
|
|
6472
|
+
default: ""
|
|
6473
|
+
}),
|
|
6398
6474
|
this.field({
|
|
6399
6475
|
type: "select",
|
|
6400
6476
|
key: "engineBackend",
|
|
@@ -6531,14 +6607,18 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6531
6607
|
* probe is unreachable). Stored backend / device snap back to the registry
|
|
6532
6608
|
* floor / default when they fall outside the offered set.
|
|
6533
6609
|
*/
|
|
6534
|
-
async getGlobalSettings(overlay) {
|
|
6535
|
-
const
|
|
6536
|
-
const
|
|
6610
|
+
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6611
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6612
|
+
const rawStored = await this.resolveUiSettingsStore();
|
|
6613
|
+
if (rawStored === null) return null;
|
|
6614
|
+
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6615
|
+
const storedBackendRaw = stored["engineBackend"];
|
|
6616
|
+
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6537
6617
|
const merged = overlay ? {
|
|
6538
6618
|
...stored,
|
|
6539
6619
|
...overlay
|
|
6540
6620
|
} : stored;
|
|
6541
|
-
const env = await this.probeHardwareEnv();
|
|
6621
|
+
const env = await this.probeHardwareEnv(targetNode);
|
|
6542
6622
|
const hardware = env.hardware;
|
|
6543
6623
|
const offered = supportedRuntimes(env);
|
|
6544
6624
|
const runtimeBackends = offered.map((id) => ({
|
|
@@ -6546,14 +6626,22 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6546
6626
|
label: runtimeLabel(id)
|
|
6547
6627
|
}));
|
|
6548
6628
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6549
|
-
const backend =
|
|
6629
|
+
const backend = (() => {
|
|
6630
|
+
const rid = toRuntimeId(storedBackend);
|
|
6631
|
+
if (rid === "onnx") return "onnx";
|
|
6632
|
+
if (offered.includes(rid)) return rid;
|
|
6633
|
+
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6634
|
+
})();
|
|
6550
6635
|
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6551
6636
|
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6552
6637
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6638
|
+
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6639
|
+
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6553
6640
|
const raw = {
|
|
6554
6641
|
...merged,
|
|
6555
6642
|
engineBackend: backend,
|
|
6556
|
-
engineDevice: device
|
|
6643
|
+
engineDevice: device,
|
|
6644
|
+
activeEngine
|
|
6557
6645
|
};
|
|
6558
6646
|
const schema = this.globalSettingsSchema();
|
|
6559
6647
|
if (!schema) return { sections: [] };
|
|
@@ -6584,15 +6672,15 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6584
6672
|
if (field.type === "slider" && "key" in field) {
|
|
6585
6673
|
const tuned = tuning[field.key];
|
|
6586
6674
|
const sliderField = field;
|
|
6587
|
-
let
|
|
6675
|
+
let patchedField = typeof tuned === "number" ? {
|
|
6588
6676
|
...sliderField,
|
|
6589
6677
|
default: tuned
|
|
6590
6678
|
} : sliderField;
|
|
6591
|
-
if (sliderField.key === "concurrency" && backend === "coreml")
|
|
6592
|
-
...
|
|
6679
|
+
if (sliderField.key === "concurrency" && backend === "coreml") patchedField = {
|
|
6680
|
+
...patchedField,
|
|
6593
6681
|
max: 4
|
|
6594
6682
|
};
|
|
6595
|
-
return
|
|
6683
|
+
return patchedField;
|
|
6596
6684
|
}
|
|
6597
6685
|
return field;
|
|
6598
6686
|
})
|
|
@@ -6609,23 +6697,41 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6609
6697
|
* Protected seam — overridable by tests (canned hardware) and reused by the
|
|
6610
6698
|
* Phase 2 auto-pick path. NEVER reads install state.
|
|
6611
6699
|
*/
|
|
6612
|
-
|
|
6613
|
-
|
|
6700
|
+
/**
|
|
6701
|
+
* Resolve the persisted UI settings store, or `null` when it can't be read
|
|
6702
|
+
* right now (ctx/settings not wired — mid-restart). A readable-but-empty
|
|
6703
|
+
* store (genuine first boot) returns `{}`, never null. `getGlobalSettings`
|
|
6704
|
+
* uses the null signal to avoid serving fabricated empty-store defaults
|
|
6705
|
+
* during the restart window. Seam kept protected so tests can drive the
|
|
6706
|
+
* unreadable vs empty vs populated cases without faking a full AddonContext.
|
|
6707
|
+
*/
|
|
6708
|
+
async resolveUiSettingsStore() {
|
|
6709
|
+
const settings = this.ctxIfReady?.settings;
|
|
6710
|
+
if (!settings) return null;
|
|
6711
|
+
return await settings.readAddonStore() ?? {};
|
|
6712
|
+
}
|
|
6713
|
+
async probeHardwareEnv(nodeId) {
|
|
6714
|
+
const node = nodeId ?? this.localNodeId();
|
|
6715
|
+
const hardware = await this.resolveProbeHardware(node);
|
|
6716
|
+
if (hardware) this.lastGoodHardwareByNode.set(node, hardware);
|
|
6717
|
+
const effective = hardware ?? this.lastGoodHardwareByNode.get(node) ?? null;
|
|
6614
6718
|
return {
|
|
6615
6719
|
platform: process.platform,
|
|
6616
6720
|
arch: process.arch,
|
|
6617
|
-
hardware
|
|
6721
|
+
hardware: effective
|
|
6618
6722
|
};
|
|
6619
6723
|
}
|
|
6620
6724
|
/**
|
|
6621
|
-
* Fetch the probed hardware from the platform-probe cap
|
|
6622
|
-
* the cap is not reachable (caller falls back to
|
|
6725
|
+
* Fetch the probed hardware from the platform-probe cap for `nodeId` (default
|
|
6726
|
+
* = self). Returns null when the cap is not reachable (caller falls back to
|
|
6727
|
+
* the registry's safe minimum).
|
|
6623
6728
|
*/
|
|
6624
|
-
async resolveProbeHardware() {
|
|
6729
|
+
async resolveProbeHardware(nodeId) {
|
|
6625
6730
|
try {
|
|
6626
6731
|
const api = this.ctxIfReady?.api;
|
|
6627
6732
|
if (!api) return null;
|
|
6628
|
-
const
|
|
6733
|
+
const node = nodeId ?? this.localNodeId();
|
|
6734
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(void 0, nodePin(node)))?.hardware;
|
|
6629
6735
|
if (!hw) return null;
|
|
6630
6736
|
return {
|
|
6631
6737
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -6636,6 +6742,61 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6636
6742
|
}
|
|
6637
6743
|
}
|
|
6638
6744
|
/**
|
|
6745
|
+
* Bare node id used to scope the engine cascade in the shared store. MUST
|
|
6746
|
+
* match the provider's `localProbeNodeId()` (kernel.localNodeId, default
|
|
6747
|
+
* 'hub') so the UI write path (this) and the provider read/write path
|
|
6748
|
+
* (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
|
|
6749
|
+
* `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
|
|
6750
|
+
* when kernel.localNodeId was absent, so UI saves landed on a key the
|
|
6751
|
+
* provider never read — silently losing the per-node selection.
|
|
6752
|
+
*/
|
|
6753
|
+
localNodeId() {
|
|
6754
|
+
return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");
|
|
6755
|
+
}
|
|
6756
|
+
/**
|
|
6757
|
+
* Refresh this node's cached engine selection from the node-scoped store.
|
|
6758
|
+
* `resolveBackendTuning` is synchronous and the reprobe gate runs before the
|
|
6759
|
+
* provider exists, so both read these cached fields instead of the
|
|
6760
|
+
* cluster-shared bare keys (which belong to no single node). Best-effort: a
|
|
6761
|
+
* transiently-unreadable store leaves the last cached values in place.
|
|
6762
|
+
*/
|
|
6763
|
+
async refreshNodeEngineFromStore() {
|
|
6764
|
+
const store = await this.resolveUiSettingsStore();
|
|
6765
|
+
if (store === null) return;
|
|
6766
|
+
const node = this.localNodeId();
|
|
6767
|
+
const backend = readNodeEngineValue(store, "engineBackend", node);
|
|
6768
|
+
if (typeof backend === "string" && backend !== "") this.nodeEngineBackend = backend;
|
|
6769
|
+
const probed = readNodeEngineValue(store, "probedBestEngine", node);
|
|
6770
|
+
this.nodeProbedBestEngine = typeof probed === "string" ? probed : "";
|
|
6771
|
+
}
|
|
6772
|
+
/**
|
|
6773
|
+
* Persist a settings patch, mirroring the engine cascade fields to the TARGET
|
|
6774
|
+
* node's scoped keys so each node keeps an INDEPENDENT engine selection in the
|
|
6775
|
+
* cluster-central store. The hub addon serves writes for every node, so it
|
|
6776
|
+
* scopes by the requested `nodeId` (default self).
|
|
6777
|
+
*
|
|
6778
|
+
* When the target IS this node, `super.updateGlobalSettings` drives the normal
|
|
6779
|
+
* apply path (`resolveConfig` / `onConfigChanged` / `requiresRestart` restart),
|
|
6780
|
+
* and the bare engine keys it writes are shadowed by the node-scoped keys on
|
|
6781
|
+
* read. When the target is a SIBLING node, we persist the scoped engine keys +
|
|
6782
|
+
* the non-engine bare keys but DON'T run this node's restart/reprovision — the
|
|
6783
|
+
* owning node applies its own engine selection on its next (re)start.
|
|
6784
|
+
*/
|
|
6785
|
+
async updateGlobalSettings(patch, nodeId) {
|
|
6786
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6787
|
+
const patchRecord = patch;
|
|
6788
|
+
const scopedEngine = {};
|
|
6789
|
+
for (const key of ENGINE_CASCADE_KEYS) if (key in patchRecord) scopedEngine[nodeEngineKey(key, targetNode)] = patchRecord[key];
|
|
6790
|
+
if (Object.keys(scopedEngine).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scopedEngine);
|
|
6791
|
+
if (targetNode === this.localNodeId()) {
|
|
6792
|
+
await super.updateGlobalSettings(patch, nodeId);
|
|
6793
|
+
return;
|
|
6794
|
+
}
|
|
6795
|
+
const sharedPatch = {};
|
|
6796
|
+
for (const [k, v] of Object.entries(patchRecord)) if (!ENGINE_CASCADE_KEYS.includes(k)) sharedPatch[k] = v;
|
|
6797
|
+
if (Object.keys(sharedPatch).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(sharedPatch);
|
|
6798
|
+
}
|
|
6799
|
+
/**
|
|
6639
6800
|
* Resolve the effective pool tuning for the configured backend.
|
|
6640
6801
|
*
|
|
6641
6802
|
* Reads the registry's `tuningFor(backend)` and ignores any persisted
|
|
@@ -6649,7 +6810,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6649
6810
|
* a reason to disagree.
|
|
6650
6811
|
*/
|
|
6651
6812
|
resolveBackendTuning() {
|
|
6652
|
-
const t = tuningFor(toRuntimeId(this.
|
|
6813
|
+
const t = tuningFor(toRuntimeId(this.nodeEngineBackend ?? DEFAULT_CONFIG.engineBackend));
|
|
6653
6814
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
6654
6815
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
6655
6816
|
return {
|
|
@@ -6668,6 +6829,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6668
6829
|
relativePath: ""
|
|
6669
6830
|
}).catch(() => "camstack-data/models");
|
|
6670
6831
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
6832
|
+
await this.refreshNodeEngineFromStore();
|
|
6671
6833
|
this.pythonAddonDir = resolveAddonPythonDir();
|
|
6672
6834
|
const py = await ensurePythonReady(this.ctx.deps, this.ctx.logger);
|
|
6673
6835
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
@@ -6689,9 +6851,12 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6689
6851
|
});
|
|
6690
6852
|
await this.provider.init();
|
|
6691
6853
|
await this.provider.setApi(this.ctx);
|
|
6692
|
-
if (!this.
|
|
6854
|
+
if (!this.nodeProbedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
6693
6855
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6694
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
|
+
});
|
|
6695
6860
|
await this.provider.warmPool();
|
|
6696
6861
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
6697
6862
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -6741,8 +6906,8 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6741
6906
|
/**
|
|
6742
6907
|
* Proactively install the OpenVINO Python package when Intel hardware
|
|
6743
6908
|
* (iGPU or NPU) is detected. Called once from `onInitialize`, before the
|
|
6744
|
-
* provider is constructed, so the module is
|
|
6745
|
-
*
|
|
6909
|
+
* provider is constructed, so the module is RUNNABLE when the engine is
|
|
6910
|
+
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
6746
6911
|
*
|
|
6747
6912
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
6748
6913
|
* the onnx-cpu baseline. The hardware query itself is also best-effort —
|
|
@@ -6811,6 +6976,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6811
6976
|
* lifecycle (engineFactory rebuild on next runPipeline).
|
|
6812
6977
|
*/
|
|
6813
6978
|
async onConfigChanged() {
|
|
6979
|
+
await this.refreshNodeEngineFromStore();
|
|
6814
6980
|
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
6815
6981
|
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6816
6982
|
});
|