@camstack/addon-pipeline 1.0.0 → 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.
@@ -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
- /** Whether this factory uses a Python pool (vs Node.js ONNX). */
1988
+ /** Detection always uses the Python pool. */
2199
1989
  get usesPythonPool() {
2200
- return this.opts.engine.runtime === "python";
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
- if (this.usesPythonPool) await this.initPythonPool(steps);
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) await this.poolManager.applyConfig(steps);
2215
- else if (this.nodeManager) await this.nodeManager.applyConfig(steps);
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) return this.poolManager.getHandle(stepId, modelId);
2228
- if (this.nodeManager) return this.nodeManager.getEngine(stepId);
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
- if (this.poolManager) return this.poolManager.isLoaded(stepId);
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
- if (this.poolManager) return this.poolManager.isLoadedWithModel(stepId, modelId);
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 path
2245
- * surfaces each warm slot independently (including bench overrides);
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 this.poolManager.getLoadedSteps().map((l) => ({
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 path not available (Node ONNX factory)");
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 for runtime=\"python\" — 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.");
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: "node",
4133
- backend: "cpu",
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: "node",
3954
+ runtime: "python",
4220
3955
  backend: "coreml",
4221
3956
  format: "coreml"
4222
3957
  } : {
4223
- runtime: "node",
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: runtime === "node" ? "node" : "python",
5402
- backend,
5403
- format: backendToFormat(backend),
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 `python3 -c "import <mod>"` with a 3s timeout. Used at
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: "node",
5563
+ runtime: "python",
5820
5564
  backend: "coreml",
5821
5565
  format: "coreml"
5822
5566
  } : {
5823
- runtime: "node",
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
- python: [
5969
- {
5970
- value: "coreml",
5971
- label: "CoreML"
5972
- },
5973
- {
5974
- value: "openvino",
5975
- label: "OpenVINO"
5976
- },
5977
- {
5978
- value: "onnx",
5979
- label: "ONNX Runtime"
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 = merged.engineRuntime === "node" ? "node" : "python";
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 : "";