@camstack/addon-pipeline 0.2.2 → 1.0.1
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 +141 -51
- package/dist/audio-analyzer/index.mjs +142 -51
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +56 -321
- package/dist/detection-pipeline/index.mjs +56 -321
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-OyfewkAp.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-qX99--rF.mjs} +3 -3
- package/dist/stream-broker/{hostInit-_lh9p7uA.mjs → hostInit-Bx41KdYV.mjs} +3 -3
- package/dist/stream-broker/index.js +2 -2
- package/dist/stream-broker/index.mjs +2 -2
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +2 -7
- package/python/requirements-audio.txt +5 -0
- package/python/yamnet_audio.py +113 -0
- package/dist/constants-B_b0a-6h.mjs +0 -3119
- package/dist/constants-D65v6yp6.js +0 -5963
|
@@ -1963,215 +1963,6 @@ function getDefaultModelForFormat(stepId, format) {
|
|
|
1963
1963
|
})[0].id;
|
|
1964
1964
|
}
|
|
1965
1965
|
//#endregion
|
|
1966
|
-
//#region src/detection-pipeline/engine/node-engine-manager.ts
|
|
1967
|
-
var NodeEngineManager = class {
|
|
1968
|
-
backend;
|
|
1969
|
-
resolveModelPath;
|
|
1970
|
-
createEngine;
|
|
1971
|
-
engines = /* @__PURE__ */ new Map();
|
|
1972
|
-
log;
|
|
1973
|
-
constructor(backend, resolveModelPath, createEngine, logger) {
|
|
1974
|
-
this.backend = backend;
|
|
1975
|
-
this.resolveModelPath = resolveModelPath;
|
|
1976
|
-
this.createEngine = createEngine;
|
|
1977
|
-
this.log = logger;
|
|
1978
|
-
}
|
|
1979
|
-
/**
|
|
1980
|
-
* Apply a new pipeline configuration.
|
|
1981
|
-
* Loads/unloads engines for steps that changed.
|
|
1982
|
-
*/
|
|
1983
|
-
async applyConfig(newSteps) {
|
|
1984
|
-
const enabledSteps = flattenEnabledVideoSteps(newSteps);
|
|
1985
|
-
const desiredMap = /* @__PURE__ */ new Map();
|
|
1986
|
-
for (const step of enabledSteps) desiredMap.set(step.addonId, step);
|
|
1987
|
-
for (const [stepId, loaded] of this.engines) if (!desiredMap.has(stepId)) {
|
|
1988
|
-
this.log.info("Unloading ONNX engine for step", { meta: { step: stepId } });
|
|
1989
|
-
await loaded.engine.dispose();
|
|
1990
|
-
this.engines.delete(stepId);
|
|
1991
|
-
}
|
|
1992
|
-
for (const [stepId, step] of desiredMap) {
|
|
1993
|
-
const existing = this.engines.get(stepId);
|
|
1994
|
-
if (existing && existing.modelId === step.modelId) continue;
|
|
1995
|
-
if (existing) {
|
|
1996
|
-
this.log.info("Replacing ONNX engine for step", { meta: {
|
|
1997
|
-
step: stepId,
|
|
1998
|
-
fromModelId: existing.modelId,
|
|
1999
|
-
toModelId: step.modelId
|
|
2000
|
-
} });
|
|
2001
|
-
await existing.engine.dispose();
|
|
2002
|
-
} else this.log.info("Loading ONNX engine for step", { meta: {
|
|
2003
|
-
step: stepId,
|
|
2004
|
-
modelId: step.modelId
|
|
2005
|
-
} });
|
|
2006
|
-
const modelEntry = getStepDefinition(stepId).models.find((m) => m.id === step.modelId);
|
|
2007
|
-
if (!modelEntry) throw new Error(`Model "${step.modelId}" not found in step "${stepId}" catalog`);
|
|
2008
|
-
const modelPath = this.resolveModelPath(stepId, step.modelId);
|
|
2009
|
-
const meta = {
|
|
2010
|
-
inputSize: modelEntry.inputSize,
|
|
2011
|
-
inputNormalization: modelEntry.inputNormalization ?? "zero-one",
|
|
2012
|
-
inputLayout: modelEntry.inputLayout ?? "nchw",
|
|
2013
|
-
preprocessMode: modelEntry.preprocessMode ?? "letterbox"
|
|
2014
|
-
};
|
|
2015
|
-
const engine = await this.createEngine(modelPath, this.backend, meta, this.log.child(stepId));
|
|
2016
|
-
this.engines.set(stepId, {
|
|
2017
|
-
stepId,
|
|
2018
|
-
modelId: step.modelId,
|
|
2019
|
-
engine
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Get an IInferenceEngine for a step.
|
|
2025
|
-
* @throws if the step is not loaded.
|
|
2026
|
-
*/
|
|
2027
|
-
getEngine(stepId) {
|
|
2028
|
-
const loaded = this.engines.get(stepId);
|
|
2029
|
-
if (!loaded) throw new Error(`ONNX engine for step "${stepId}" is not loaded`);
|
|
2030
|
-
return loaded.engine;
|
|
2031
|
-
}
|
|
2032
|
-
isLoaded(stepId) {
|
|
2033
|
-
return this.engines.has(stepId);
|
|
2034
|
-
}
|
|
2035
|
-
isLoadedWithModel(stepId, modelId) {
|
|
2036
|
-
const loaded = this.engines.get(stepId);
|
|
2037
|
-
return loaded !== void 0 && loaded.modelId === modelId;
|
|
2038
|
-
}
|
|
2039
|
-
async loadAdditional(steps) {
|
|
2040
|
-
const enabledSteps = flattenEnabledVideoSteps(steps);
|
|
2041
|
-
for (const step of enabledSteps) {
|
|
2042
|
-
const existing = this.engines.get(step.addonId);
|
|
2043
|
-
if (existing && existing.modelId === step.modelId) continue;
|
|
2044
|
-
if (existing) {
|
|
2045
|
-
this.log.info("Replacing ONNX engine for step", { meta: {
|
|
2046
|
-
step: step.addonId,
|
|
2047
|
-
fromModelId: existing.modelId,
|
|
2048
|
-
toModelId: step.modelId
|
|
2049
|
-
} });
|
|
2050
|
-
await existing.engine.dispose();
|
|
2051
|
-
} else this.log.info("Loading additional ONNX engine for step", { meta: {
|
|
2052
|
-
step: step.addonId,
|
|
2053
|
-
modelId: step.modelId
|
|
2054
|
-
} });
|
|
2055
|
-
const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
|
|
2056
|
-
if (!modelEntry) throw new Error(`Model "${step.modelId}" not found in step "${step.addonId}" catalog`);
|
|
2057
|
-
const modelPath = this.resolveModelPath(step.addonId, step.modelId);
|
|
2058
|
-
const meta = {
|
|
2059
|
-
inputSize: modelEntry.inputSize,
|
|
2060
|
-
inputNormalization: modelEntry.inputNormalization ?? "zero-one",
|
|
2061
|
-
inputLayout: modelEntry.inputLayout ?? "nchw",
|
|
2062
|
-
preprocessMode: modelEntry.preprocessMode ?? "letterbox"
|
|
2063
|
-
};
|
|
2064
|
-
const engine = await this.createEngine(modelPath, this.backend, meta, this.log.child(step.addonId));
|
|
2065
|
-
this.engines.set(step.addonId, {
|
|
2066
|
-
stepId: step.addonId,
|
|
2067
|
-
modelId: step.modelId,
|
|
2068
|
-
engine
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
listLoaded() {
|
|
2073
|
-
return [...this.engines.values()].map((l) => ({
|
|
2074
|
-
stepId: l.stepId,
|
|
2075
|
-
modelId: l.modelId
|
|
2076
|
-
}));
|
|
2077
|
-
}
|
|
2078
|
-
async disposeAll() {
|
|
2079
|
-
for (const [, loaded] of this.engines) await loaded.engine.dispose();
|
|
2080
|
-
this.engines.clear();
|
|
2081
|
-
}
|
|
2082
|
-
};
|
|
2083
|
-
//#endregion
|
|
2084
|
-
//#region src/detection-pipeline/engine/model-shape-validator.ts
|
|
2085
|
-
/**
|
|
2086
|
-
* Runtime input-shape validator for ONNX models.
|
|
2087
|
-
*
|
|
2088
|
-
* Reads the actual graph input dimensions from a locally-downloaded
|
|
2089
|
-
* ONNX model and compares them against the catalog declaration. Used
|
|
2090
|
-
* by the engine factory at load time so a converter regression or a
|
|
2091
|
-
* stale catalog entry surfaces as a one-line warning rather than
|
|
2092
|
-
* silent incorrect inference (preprocess uses one size, the model
|
|
2093
|
-
* expects another → garbage detections).
|
|
2094
|
-
*
|
|
2095
|
-
* Only ONNX is validated here. CoreML and OpenVINO converters in our
|
|
2096
|
-
* pipeline derive shape from the same source ONNX, so a passing ONNX
|
|
2097
|
-
* check covers all three formats. If they ever diverge, extend with a
|
|
2098
|
-
* format-specific reader (Manifest.json for .mlpackage, root <input>
|
|
2099
|
-
* tag for OpenVINO XML).
|
|
2100
|
-
*/
|
|
2101
|
-
/**
|
|
2102
|
-
* Probe an ONNX file for its first input's shape. Returns null if the
|
|
2103
|
-
* file is missing or unparseable — callers should treat null as
|
|
2104
|
-
* "validation skipped" (not a mismatch).
|
|
2105
|
-
*
|
|
2106
|
-
* Implementation: uses `onnxruntime-node` to create a session and read
|
|
2107
|
-
* `inputMetadata`. The session is released immediately after probing.
|
|
2108
|
-
*/
|
|
2109
|
-
async function readOnnxInputShape(onnxPath) {
|
|
2110
|
-
if (!node_fs.existsSync(onnxPath)) return null;
|
|
2111
|
-
try {
|
|
2112
|
-
const session = await (await import("onnxruntime-node")).InferenceSession.create(onnxPath);
|
|
2113
|
-
const firstInputName = session.inputNames[0];
|
|
2114
|
-
if (!firstInputName) return null;
|
|
2115
|
-
const numeric = (session.inputMetadata?.[firstInputName]?.dimensions ?? []).map((d) => typeof d === "number" ? d : null);
|
|
2116
|
-
let height = null;
|
|
2117
|
-
let width = null;
|
|
2118
|
-
if (numeric.length === 4) {
|
|
2119
|
-
const candidates = numeric.map((v, i) => ({
|
|
2120
|
-
v,
|
|
2121
|
-
i
|
|
2122
|
-
})).filter((e) => typeof e.v === "number" && e.v > 4).toSorted((a, b) => b.v - a.v);
|
|
2123
|
-
if (candidates.length >= 2) {
|
|
2124
|
-
const sorted = candidates.slice(0, 2).toSorted((a, b) => a.i - b.i);
|
|
2125
|
-
height = sorted[0]?.v ?? null;
|
|
2126
|
-
width = sorted[1]?.v ?? null;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
if (typeof session.release === "function") await session.release();
|
|
2130
|
-
return {
|
|
2131
|
-
height,
|
|
2132
|
-
width
|
|
2133
|
-
};
|
|
2134
|
-
} catch {
|
|
2135
|
-
return null;
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
/**
|
|
2139
|
-
* Check the catalog `expected` size against the actual ONNX input shape.
|
|
2140
|
-
* Returns a ShapeMismatch when the actual shape is known and disagrees;
|
|
2141
|
-
* returns null when the file is missing, unparseable, or matches.
|
|
2142
|
-
*/
|
|
2143
|
-
async function validateOnnxInputShape(opts) {
|
|
2144
|
-
const actual = await readOnnxInputShape(opts.modelPath);
|
|
2145
|
-
if (!actual) return null;
|
|
2146
|
-
const matchesH = actual.height === null || actual.height === opts.expected.height;
|
|
2147
|
-
const matchesW = actual.width === null || actual.width === opts.expected.width;
|
|
2148
|
-
if (matchesH && matchesW) return null;
|
|
2149
|
-
return {
|
|
2150
|
-
modelId: opts.modelId,
|
|
2151
|
-
modelPath: opts.modelPath,
|
|
2152
|
-
expected: opts.expected,
|
|
2153
|
-
actual
|
|
2154
|
-
};
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Convenience wrapper: validate, log on mismatch via the supplied
|
|
2158
|
-
* logger. Returns true when validation passed (or was skipped) so
|
|
2159
|
-
* callers can keep loading; mismatches do not throw — they warn.
|
|
2160
|
-
*/
|
|
2161
|
-
async function checkAndLogModelShape(opts, logger) {
|
|
2162
|
-
const mismatch = await validateOnnxInputShape(opts);
|
|
2163
|
-
if (!mismatch) return true;
|
|
2164
|
-
logger.warn("Model input shape mismatch — catalog declaration disagrees with model file", { meta: {
|
|
2165
|
-
modelId: mismatch.modelId,
|
|
2166
|
-
expectedWidth: mismatch.expected.width,
|
|
2167
|
-
expectedHeight: mismatch.expected.height,
|
|
2168
|
-
actualWidth: mismatch.actual.width,
|
|
2169
|
-
actualHeight: mismatch.actual.height,
|
|
2170
|
-
modelPath: mismatch.modelPath
|
|
2171
|
-
} });
|
|
2172
|
-
return false;
|
|
2173
|
-
}
|
|
2174
|
-
//#endregion
|
|
2175
1966
|
//#region src/detection-pipeline/engine/engine-factory.ts
|
|
2176
1967
|
var BACKEND_TO_POOL_RUNTIME = {
|
|
2177
1968
|
coreml: "coreml",
|
|
@@ -2188,32 +1979,29 @@ var RUNTIME_TO_FORMAT = {
|
|
|
2188
1979
|
var EngineFactory = class {
|
|
2189
1980
|
pool = null;
|
|
2190
1981
|
poolManager = null;
|
|
2191
|
-
nodeManager = null;
|
|
2192
1982
|
log;
|
|
2193
1983
|
opts;
|
|
2194
1984
|
constructor(opts) {
|
|
2195
1985
|
this.opts = opts;
|
|
2196
1986
|
this.log = opts.logger;
|
|
2197
1987
|
}
|
|
2198
|
-
/**
|
|
1988
|
+
/** Detection always uses the Python pool. */
|
|
2199
1989
|
get usesPythonPool() {
|
|
2200
|
-
return
|
|
1990
|
+
return true;
|
|
2201
1991
|
}
|
|
2202
1992
|
/**
|
|
2203
1993
|
* Initialize the engine layer and apply initial pipeline config.
|
|
2204
1994
|
*/
|
|
2205
1995
|
async initialize(steps) {
|
|
2206
|
-
|
|
2207
|
-
else await this.initNodeEngines(steps);
|
|
1996
|
+
await this.initPythonPool(steps);
|
|
2208
1997
|
}
|
|
2209
1998
|
/**
|
|
2210
1999
|
* Apply a new pipeline config (hot swap).
|
|
2211
2000
|
* Only loads/unloads models that changed.
|
|
2212
2001
|
*/
|
|
2213
2002
|
async applyConfig(steps) {
|
|
2214
|
-
if (this.poolManager)
|
|
2215
|
-
|
|
2216
|
-
else throw new Error("EngineFactory not initialized");
|
|
2003
|
+
if (!this.poolManager) throw new Error("EngineFactory not initialized");
|
|
2004
|
+
await this.poolManager.applyConfig(steps);
|
|
2217
2005
|
}
|
|
2218
2006
|
/**
|
|
2219
2007
|
* Get an IInferenceEngine for a pipeline step. Without `modelId`,
|
|
@@ -2224,49 +2012,35 @@ var EngineFactory = class {
|
|
|
2224
2012
|
* @throws if step not loaded with the requested (or active) model.
|
|
2225
2013
|
*/
|
|
2226
2014
|
getEngine(stepId, modelId) {
|
|
2227
|
-
if (this.poolManager)
|
|
2228
|
-
|
|
2229
|
-
throw new Error("EngineFactory not initialized");
|
|
2015
|
+
if (!this.poolManager) throw new Error("EngineFactory not initialized");
|
|
2016
|
+
return this.poolManager.getHandle(stepId, modelId);
|
|
2230
2017
|
}
|
|
2231
2018
|
/** Check if a step is loaded (any variant). */
|
|
2232
2019
|
isLoaded(stepId) {
|
|
2233
|
-
|
|
2234
|
-
if (this.nodeManager) return this.nodeManager.isLoaded(stepId);
|
|
2235
|
-
return false;
|
|
2020
|
+
return this.poolManager?.isLoaded(stepId) ?? false;
|
|
2236
2021
|
}
|
|
2237
2022
|
/** Check if a specific (stepId, modelId) variant is resident. */
|
|
2238
2023
|
isLoadedWithModel(stepId, modelId) {
|
|
2239
|
-
|
|
2240
|
-
if (this.nodeManager) return this.nodeManager.isLoadedWithModel(stepId, modelId);
|
|
2241
|
-
return false;
|
|
2024
|
+
return this.poolManager?.isLoadedWithModel(stepId, modelId) ?? false;
|
|
2242
2025
|
}
|
|
2243
2026
|
/**
|
|
2244
|
-
* List every loaded (stepId, modelId) variant — the pool
|
|
2245
|
-
*
|
|
2246
|
-
* the Node path surfaces just the active model per step.
|
|
2027
|
+
* List every loaded (stepId, modelId) variant — the pool surfaces each
|
|
2028
|
+
* warm slot independently (including bench overrides).
|
|
2247
2029
|
*/
|
|
2248
2030
|
listLoaded() {
|
|
2249
|
-
if (this.poolManager) return
|
|
2031
|
+
if (!this.poolManager) return [];
|
|
2032
|
+
return this.poolManager.getLoadedSteps().map((l) => ({
|
|
2250
2033
|
stepId: l.stepId,
|
|
2251
2034
|
modelId: l.modelId,
|
|
2252
2035
|
active: l.active
|
|
2253
2036
|
}));
|
|
2254
|
-
if (this.nodeManager) return this.nodeManager.listLoaded().map((l) => ({
|
|
2255
|
-
...l,
|
|
2256
|
-
active: true
|
|
2257
|
-
}));
|
|
2258
|
-
return [];
|
|
2259
2037
|
}
|
|
2260
2038
|
/** Native pid of the underlying Python pool, if any. */
|
|
2261
2039
|
getPoolPid() {
|
|
2262
2040
|
return this.pool?.getPid() ?? null;
|
|
2263
2041
|
}
|
|
2264
2042
|
/**
|
|
2265
|
-
* Whether this factory exposes the batched fast path
|
|
2266
|
-
* (`batchInferRaw`). Only the Python pool path supports it today —
|
|
2267
|
-
* Node.js ONNX engines run one inference per call with their own
|
|
2268
|
-
* thread pool inside InferenceSession, so batching at this layer
|
|
2269
|
-
* would just queue serially.
|
|
2043
|
+
* Whether this factory exposes the batched fast path (`batchInferRaw`).
|
|
2270
2044
|
*/
|
|
2271
2045
|
supportsBatch() {
|
|
2272
2046
|
return this.poolManager !== null;
|
|
@@ -2280,7 +2054,7 @@ var EngineFactory = class {
|
|
|
2280
2054
|
* in one IPC round-trip, amortising the per-call envelope overhead.
|
|
2281
2055
|
*/
|
|
2282
2056
|
async batchInferRaw(stepId, items, modelId) {
|
|
2283
|
-
if (!this.poolManager) throw new Error("EngineFactory.batchInferRaw: pool
|
|
2057
|
+
if (!this.poolManager) throw new Error("EngineFactory.batchInferRaw: pool not initialized");
|
|
2284
2058
|
const idx = this.poolManager.getPoolIndex(stepId, modelId);
|
|
2285
2059
|
if (idx === null) throw new Error(`EngineFactory.batchInferRaw: step "${stepId}"${modelId ? ` (model "${modelId}")` : ""} is not loaded`);
|
|
2286
2060
|
return this.poolManager.getPool().inferBatch(idx, items);
|
|
@@ -2302,7 +2076,6 @@ var EngineFactory = class {
|
|
|
2302
2076
|
/** Load additional models without unloading existing ones (for benchmark/test). */
|
|
2303
2077
|
async loadAdditional(steps) {
|
|
2304
2078
|
if (this.poolManager) await this.poolManager.loadAdditional(steps);
|
|
2305
|
-
else if (this.nodeManager) await this.nodeManager.loadAdditional(steps);
|
|
2306
2079
|
}
|
|
2307
2080
|
/** Shut down all engines and the pool process. */
|
|
2308
2081
|
async dispose() {
|
|
@@ -2311,14 +2084,10 @@ var EngineFactory = class {
|
|
|
2311
2084
|
this.pool = null;
|
|
2312
2085
|
this.poolManager = null;
|
|
2313
2086
|
}
|
|
2314
|
-
if (this.nodeManager) {
|
|
2315
|
-
await this.nodeManager.disposeAll();
|
|
2316
|
-
this.nodeManager = null;
|
|
2317
|
-
}
|
|
2318
2087
|
}
|
|
2319
2088
|
async initPythonPool(steps) {
|
|
2320
2089
|
const pythonPath = this.opts.pythonPath;
|
|
2321
|
-
if (!pythonPath) throw new Error("EngineFactory: pythonPath is required
|
|
2090
|
+
if (!pythonPath) throw new Error("EngineFactory: pythonPath is required — the addon must call ctx.deps.ensurePython() and pass the result. The embedded portable Python download likely failed; check the addon logs for the download error.");
|
|
2322
2091
|
const poolRuntime = BACKEND_TO_POOL_RUNTIME[this.opts.engine.backend];
|
|
2323
2092
|
if (!poolRuntime) throw new Error(`No pool runtime mapping for backend "${this.opts.engine.backend}"`);
|
|
2324
2093
|
const concurrency = this.opts.concurrency ?? {
|
|
@@ -2369,16 +2138,6 @@ var EngineFactory = class {
|
|
|
2369
2138
|
const filename = urlParts[urlParts.length - 1] ?? `${modelId}.${format}`;
|
|
2370
2139
|
const modelPath = `${this.opts.modelsDir}/${filename}`;
|
|
2371
2140
|
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
2372
|
-
const onnxEntry = modelEntry.formats.onnx;
|
|
2373
|
-
if (onnxEntry) {
|
|
2374
|
-
const onnxUrlParts = onnxEntry.url.split("/");
|
|
2375
|
-
const onnxFilename = onnxUrlParts[onnxUrlParts.length - 1] ?? `${modelId}.onnx`;
|
|
2376
|
-
checkAndLogModelShape({
|
|
2377
|
-
modelId,
|
|
2378
|
-
modelPath: `${this.opts.modelsDir}/${onnxFilename}`,
|
|
2379
|
-
expected: modelEntry.inputSize
|
|
2380
|
-
}, this.log);
|
|
2381
|
-
}
|
|
2382
2141
|
let labels = def.labels;
|
|
2383
2142
|
if (!labels && modelEntry.extraFiles) {
|
|
2384
2143
|
const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
|
|
@@ -2405,20 +2164,6 @@ var EngineFactory = class {
|
|
|
2405
2164
|
device: this.opts.engine.device ?? (poolRuntime === "coreml" ? "all" : void 0)
|
|
2406
2165
|
};
|
|
2407
2166
|
}
|
|
2408
|
-
async initNodeEngines(steps) {
|
|
2409
|
-
if (!this.opts.createNodeEngine) throw new Error("createNodeEngine function required for nodejs+onnx backend");
|
|
2410
|
-
this.nodeManager = new NodeEngineManager(this.opts.engine.backend, (stepId, modelId) => this.resolveOnnxModelPath(stepId, modelId), this.opts.createNodeEngine, this.log.child("onnx-mgr"));
|
|
2411
|
-
await this.nodeManager.applyConfig(steps);
|
|
2412
|
-
}
|
|
2413
|
-
resolveOnnxModelPath(stepId, modelId) {
|
|
2414
|
-
const modelEntry = getStepDefinition(stepId).models.find((m) => m.id === modelId);
|
|
2415
|
-
if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${stepId}" catalog`);
|
|
2416
|
-
const onnxFormat = modelEntry.formats["onnx"];
|
|
2417
|
-
if (!onnxFormat) throw new Error(`Model "${modelId}" has no ONNX format`);
|
|
2418
|
-
const urlParts = onnxFormat.url.split("/");
|
|
2419
|
-
const filename = urlParts[urlParts.length - 1] ?? `${modelId}.onnx`;
|
|
2420
|
-
return `${this.opts.modelsDir}/${filename}`;
|
|
2421
|
-
}
|
|
2422
2167
|
};
|
|
2423
2168
|
//#endregion
|
|
2424
2169
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
@@ -4129,9 +3874,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4129
3874
|
};
|
|
4130
3875
|
} catch {}
|
|
4131
3876
|
return {
|
|
4132
|
-
runtime: "
|
|
4133
|
-
backend: "
|
|
4134
|
-
format: "onnx"
|
|
3877
|
+
runtime: "python",
|
|
3878
|
+
backend: "onnx",
|
|
3879
|
+
format: "onnx",
|
|
3880
|
+
device: "cpu"
|
|
4135
3881
|
};
|
|
4136
3882
|
}
|
|
4137
3883
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
@@ -4152,18 +3898,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4152
3898
|
return this.getAvailableEnginesWithDevices().map((e) => e.engine);
|
|
4153
3899
|
}
|
|
4154
3900
|
getAvailableEnginesWithDevices() {
|
|
4155
|
-
const engines = [
|
|
4156
|
-
engine: {
|
|
4157
|
-
runtime: "node",
|
|
4158
|
-
backend: "cpu",
|
|
4159
|
-
format: "onnx"
|
|
4160
|
-
},
|
|
4161
|
-
devices: [{
|
|
4162
|
-
id: "cpu",
|
|
4163
|
-
label: "CPU"
|
|
4164
|
-
}],
|
|
4165
|
-
defaultDevice: "cpu"
|
|
4166
|
-
}];
|
|
3901
|
+
const engines = [];
|
|
4167
3902
|
if (process.platform === "darwin") engines.push({
|
|
4168
3903
|
engine: {
|
|
4169
3904
|
runtime: "python",
|
|
@@ -4216,11 +3951,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4216
3951
|
if (audioDef.models.some((m) => m.formats[format])) {
|
|
4217
3952
|
const modelId = getDefaultModelForFormat("audio-classifier", format);
|
|
4218
3953
|
const audioEngine = modelId === "apple-soundanalysis" ? {
|
|
4219
|
-
runtime: "
|
|
3954
|
+
runtime: "python",
|
|
4220
3955
|
backend: "coreml",
|
|
4221
3956
|
format: "coreml"
|
|
4222
3957
|
} : {
|
|
4223
|
-
runtime: "
|
|
3958
|
+
runtime: "python",
|
|
4224
3959
|
backend: "cpu",
|
|
4225
3960
|
format: "onnx"
|
|
4226
3961
|
};
|
|
@@ -5389,18 +5124,19 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5389
5124
|
if (typeof runtime === "string" && typeof backend === "string" && runtime && backend) {
|
|
5390
5125
|
const storedDevice = typeof store["engineDevice"] === "string" ? String(store["engineDevice"]) : "";
|
|
5391
5126
|
const detected = DetectionPipelineProvider.detectBestEngine();
|
|
5392
|
-
if (runtime === "python" && !DetectionPipelineProvider.isPythonBackendAvailable(backend)) {
|
|
5127
|
+
if (runtime === "python" && !DetectionPipelineProvider.isPythonBackendAvailable(backend, this.executorOptions.pythonPath ?? "")) {
|
|
5393
5128
|
this.log.warn("Stored engine backend unavailable on this node — falling back to detected best", { meta: {
|
|
5394
5129
|
stored: `${runtime}/${backend}`,
|
|
5395
5130
|
fallback: `${detected.runtime}/${detected.backend}`
|
|
5396
5131
|
} });
|
|
5397
5132
|
return detected;
|
|
5398
5133
|
}
|
|
5134
|
+
const migratedBackend = runtime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5399
5135
|
const device = storedDevice || detected.device;
|
|
5400
5136
|
return {
|
|
5401
|
-
runtime:
|
|
5402
|
-
backend,
|
|
5403
|
-
format: backendToFormat(
|
|
5137
|
+
runtime: "python",
|
|
5138
|
+
backend: migratedBackend,
|
|
5139
|
+
format: backendToFormat(migratedBackend),
|
|
5404
5140
|
...device ? { device } : {}
|
|
5405
5141
|
};
|
|
5406
5142
|
}
|
|
@@ -5409,13 +5145,21 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5409
5145
|
}
|
|
5410
5146
|
/**
|
|
5411
5147
|
* Synchronous availability probe for a Python inference backend. Runs a
|
|
5412
|
-
* short
|
|
5148
|
+
* short `<python> -c "import <mod>"` with a 3s timeout. Used at
|
|
5413
5149
|
* `loadEngine` time to reject a persisted backend choice the Python
|
|
5414
5150
|
* interpreter on this host can't actually import — without this the
|
|
5415
5151
|
* detection-pipeline child process exits with code 1 the moment
|
|
5416
5152
|
* `inference_pool.py` hits its `from openvino.runtime import Core`.
|
|
5153
|
+
*
|
|
5154
|
+
* MUST probe the EMBEDDED portable interpreter (`pythonPath`, resolved via
|
|
5155
|
+
* `ctx.deps.ensurePython()`) — that's where `installPythonRequirements`
|
|
5156
|
+
* puts the backend packages and what `SharedInferencePool` actually spawns.
|
|
5157
|
+
* Probing the system `python3` checks the wrong site-packages (and on a
|
|
5158
|
+
* slim container there is no system python3 at all → every backend would
|
|
5159
|
+
* be reported unavailable, silently disabling ML). Falls back to `python3`
|
|
5160
|
+
* only when no embedded interpreter is resolved.
|
|
5417
5161
|
*/
|
|
5418
|
-
static isPythonBackendAvailable(backend) {
|
|
5162
|
+
static isPythonBackendAvailable(backend, pythonPath) {
|
|
5419
5163
|
const mod = {
|
|
5420
5164
|
coreml: "coremltools",
|
|
5421
5165
|
openvino: "openvino.runtime",
|
|
@@ -5425,7 +5169,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5425
5169
|
if (!mod) return false;
|
|
5426
5170
|
try {
|
|
5427
5171
|
const { execFileSync } = require("node:child_process");
|
|
5428
|
-
execFileSync("python3", ["-c", `import ${mod}`], {
|
|
5172
|
+
execFileSync(pythonPath || "python3", ["-c", `import ${mod}`], {
|
|
5429
5173
|
timeout: 3e3,
|
|
5430
5174
|
stdio: "pipe"
|
|
5431
5175
|
});
|
|
@@ -5816,11 +5560,11 @@ function buildDefaultStepTree(format) {
|
|
|
5816
5560
|
makeStep("instance-segmentation", [], { enabled: false })
|
|
5817
5561
|
].filter((s) => s !== null));
|
|
5818
5562
|
const audioEngine = getDefaultModelForFormat("audio-classifier", format) === "apple-soundanalysis" ? {
|
|
5819
|
-
runtime: "
|
|
5563
|
+
runtime: "python",
|
|
5820
5564
|
backend: "coreml",
|
|
5821
5565
|
format: "coreml"
|
|
5822
5566
|
} : {
|
|
5823
|
-
runtime: "
|
|
5567
|
+
runtime: "python",
|
|
5824
5568
|
backend: "cpu",
|
|
5825
5569
|
format: "onnx"
|
|
5826
5570
|
};
|
|
@@ -5960,30 +5704,21 @@ function resolveAddonPythonDir() {
|
|
|
5960
5704
|
var RUNTIMES = [{
|
|
5961
5705
|
value: "python",
|
|
5962
5706
|
label: "Python (CoreML / OpenVINO / ONNX Runtime)"
|
|
5963
|
-
}, {
|
|
5964
|
-
value: "node",
|
|
5965
|
-
label: "Node.js (onnxruntime-node)"
|
|
5966
5707
|
}];
|
|
5967
|
-
var BACKENDS_BY_RUNTIME = {
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
],
|
|
5982
|
-
node: [{
|
|
5983
|
-
value: "cpu",
|
|
5984
|
-
label: "CPU (onnxruntime-node)"
|
|
5985
|
-
}]
|
|
5986
|
-
};
|
|
5708
|
+
var BACKENDS_BY_RUNTIME = { python: [
|
|
5709
|
+
{
|
|
5710
|
+
value: "coreml",
|
|
5711
|
+
label: "CoreML"
|
|
5712
|
+
},
|
|
5713
|
+
{
|
|
5714
|
+
value: "openvino",
|
|
5715
|
+
label: "OpenVINO"
|
|
5716
|
+
},
|
|
5717
|
+
{
|
|
5718
|
+
value: "onnx",
|
|
5719
|
+
label: "ONNX Runtime"
|
|
5720
|
+
}
|
|
5721
|
+
] };
|
|
5987
5722
|
var DEVICES_BY_BACKEND = {
|
|
5988
5723
|
coreml: [
|
|
5989
5724
|
{
|
|
@@ -6335,7 +6070,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6335
6070
|
...stored,
|
|
6336
6071
|
...overlay
|
|
6337
6072
|
} : stored;
|
|
6338
|
-
const runtime =
|
|
6073
|
+
const runtime = "python";
|
|
6339
6074
|
const availableBackends = await this.resolveAvailableBackends(ctx, runtime);
|
|
6340
6075
|
const runtimeBackends = availableBackends.length > 0 ? BACKENDS_BY_RUNTIME[runtime].filter((b) => availableBackends.includes(b.value)) : BACKENDS_BY_RUNTIME[runtime];
|
|
6341
6076
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|