@camstack/addon-pipeline 1.2.293 → 1.2.295
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/THIRD_PARTY_MODELS.md +233 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-DCQlW9fx.mjs → default-detection-model-CAUBVbgK.mjs} +53 -19
- package/dist/{default-detection-model-DTKHLLcC.js → default-detection-model-wNEISVA9.js} +58 -18
- package/dist/detection-pipeline/index.js +286 -458
- package/dist/detection-pipeline/index.mjs +286 -458
- package/dist/{dist-Ce8s7XqV.js → dist-BsVcf5wO.js} +1321 -833
- package/dist/{dist-C0Fr4hM5.mjs → dist-CuxSNLKW.mjs} +1316 -834
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-Xm7amG2e.mjs → node-Cqb1QiXk.mjs} +1 -1
- package/dist/{node-DpOrymbW.js → node-UFk6I2f6.js} +1 -1
- package/dist/pipeline-runner/index.js +266 -72
- package/dist/pipeline-runner/index.mjs +266 -72
- package/dist/{process-memory-CBRjuhyg.js → process-memory-CJV29sPf.js} +1 -1
- package/dist/{process-memory-BN5qYGsD.mjs → process-memory-D0Nvs9rr.mjs} +1 -1
- package/dist/recorder/index.js +12 -2
- package/dist/recorder/index.mjs +12 -2
- package/dist/{segment-demux-js-BhMlS_Ow.js → segment-demux-js-DGwmf5hu.js} +1 -1
- package/dist/{segment-demux-js-Bhwpj5Wl.mjs → segment-demux-js-DIDWw1iE.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DKd7oYba.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DoyA71_q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.mjs} +1 -1
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-CCMIHjff.mjs → hostInit-BKlb3qac.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +3 -2
- package/python/postprocessors/__init__.py +2 -0
- package/python/postprocessors/ssd.py +73 -17
- package/python/postprocessors/test_ssd.py +205 -0
- package/python/postprocessors/test_yunet.py +292 -0
- package/python/postprocessors/testdata/ssdlite_mobiledet_outputs.json +1 -0
- package/python/postprocessors/yunet.py +275 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6IHzlLJ_.mjs +0 -26
|
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
8
|
-
const require_default_detection_model = require("../default-detection-model-
|
|
6
|
+
const require_dist = require("../dist-BsVcf5wO.js");
|
|
7
|
+
const require_node = require("../node-UFk6I2f6.js");
|
|
8
|
+
const require_default_detection_model = require("../default-detection-model-wNEISVA9.js");
|
|
9
9
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
10
|
-
const require_process_memory = require("../process-memory-
|
|
10
|
+
const require_process_memory = require("../process-memory-CJV29sPf.js");
|
|
11
11
|
let node_os = require("node:os");
|
|
12
12
|
node_os = require_chunk.__toESM(node_os);
|
|
13
13
|
let node_fs = require("node:fs");
|
|
@@ -1703,12 +1703,13 @@ function resolveEffectivePostprocessorForEntry(definition, entry) {
|
|
|
1703
1703
|
//#region src/detection-pipeline/registry/model-knob-applicability.ts
|
|
1704
1704
|
/** Decodes that run NMS in the pipeline (and hence consult `nmsIouThreshold`).
|
|
1705
1705
|
* Mirrors the Python postprocessors that read the knob — yolo.py,
|
|
1706
|
-
* yolo_seg.py, rfdetr.py, scrfd.py. */
|
|
1706
|
+
* yolo_seg.py, rfdetr.py, scrfd.py, yunet.py. */
|
|
1707
1707
|
var NMS_CONSUMING_POSTPROCESSORS = new Set([
|
|
1708
1708
|
"yolo",
|
|
1709
1709
|
"yolo-seg",
|
|
1710
1710
|
"rfdetr",
|
|
1711
|
-
"scrfd"
|
|
1711
|
+
"scrfd",
|
|
1712
|
+
"yunet"
|
|
1712
1713
|
]);
|
|
1713
1714
|
/**
|
|
1714
1715
|
* Does this decode consult `nmsIouThreshold`?
|
|
@@ -3165,6 +3166,52 @@ function createInferenceTimeoutGuard(deps) {
|
|
|
3165
3166
|
};
|
|
3166
3167
|
}
|
|
3167
3168
|
//#endregion
|
|
3169
|
+
//#region src/detection-pipeline/model-pin.ts
|
|
3170
|
+
function requestedModels(steps) {
|
|
3171
|
+
const out = /* @__PURE__ */ new Map();
|
|
3172
|
+
const walk = (nodes) => {
|
|
3173
|
+
for (const step of nodes) {
|
|
3174
|
+
if (!step.enabled) continue;
|
|
3175
|
+
out.set(step.addonId, step.modelId);
|
|
3176
|
+
if (step.children !== void 0) walk(step.children);
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
walk(steps);
|
|
3180
|
+
return out;
|
|
3181
|
+
}
|
|
3182
|
+
function checkReplayPin(input) {
|
|
3183
|
+
if (input.requestedDeviceKey !== void 0 && input.dispatchDeviceKey !== input.requestedDeviceKey) return {
|
|
3184
|
+
kind: "refused",
|
|
3185
|
+
reason: "device-not-servable",
|
|
3186
|
+
detail: `device pool ${input.requestedDeviceKey} cannot run this tree (the capability gate would fall back to ${input.dispatchDeviceKey ?? "the node default"})`
|
|
3187
|
+
};
|
|
3188
|
+
const asked = requestedModels(input.requested);
|
|
3189
|
+
for (const step of input.executing) {
|
|
3190
|
+
const want = asked.get(step.addonId);
|
|
3191
|
+
if (want === void 0 || want.length === 0) return {
|
|
3192
|
+
kind: "refused",
|
|
3193
|
+
reason: "model-not-servable",
|
|
3194
|
+
detail: `${step.addonId}: no model pinned by the caller (would run ${step.modelId}, ${input.format})`
|
|
3195
|
+
};
|
|
3196
|
+
if (want !== step.modelId) return {
|
|
3197
|
+
kind: "refused",
|
|
3198
|
+
reason: "model-not-servable",
|
|
3199
|
+
detail: `${step.addonId}: asked for ${want}, this pool would run ${step.modelId} (${input.format})`
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
return { kind: "pinned" };
|
|
3203
|
+
}
|
|
3204
|
+
var PRIMARY_ROOT_STEP = "object-detection";
|
|
3205
|
+
/**
|
|
3206
|
+
* The root model a frame result reports: `object-detection` when it runs (it
|
|
3207
|
+
* is the detector the tracks come from), else the first enabled video root.
|
|
3208
|
+
* `undefined` when no root runs — never a fabricated id.
|
|
3209
|
+
*/
|
|
3210
|
+
function resolveRootModelId(roots) {
|
|
3211
|
+
const enabled = roots.filter((s) => s.enabled && s.slot !== "audio-classifier");
|
|
3212
|
+
return (enabled.find((s) => s.addonId === PRIMARY_ROOT_STEP) ?? enabled[0])?.modelId;
|
|
3213
|
+
}
|
|
3214
|
+
//#endregion
|
|
3168
3215
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
3169
3216
|
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
3170
3217
|
var BACKOFF_SCHEDULE_MS = [
|
|
@@ -3435,6 +3482,7 @@ var KNOWN_POSTPROCESSORS = new Set([
|
|
|
3435
3482
|
"rfdetr",
|
|
3436
3483
|
"yolo-seg",
|
|
3437
3484
|
"scrfd",
|
|
3485
|
+
"yunet",
|
|
3438
3486
|
"arcface",
|
|
3439
3487
|
"clip",
|
|
3440
3488
|
"softmax",
|
|
@@ -3536,9 +3584,12 @@ var DropSampler = class {
|
|
|
3536
3584
|
* accumulates and a summary is released once per interval — carrying the
|
|
3537
3585
|
* number suppressed, so nothing about the RATE is lost even though the lines
|
|
3538
3586
|
* are.
|
|
3587
|
+
*
|
|
3588
|
+
* `channel` separates occasions that share a device — a replay's drops are
|
|
3589
|
+
* sampled apart from the live ones.
|
|
3539
3590
|
*/
|
|
3540
|
-
record(deviceId, macroClass) {
|
|
3541
|
-
const key = `${deviceId}:${macroClass}`;
|
|
3591
|
+
record(deviceId, macroClass, channel = "live") {
|
|
3592
|
+
const key = `${channel}:${deviceId}:${macroClass}`;
|
|
3542
3593
|
const at = this.now();
|
|
3543
3594
|
const prev = this.seen.get(key);
|
|
3544
3595
|
if (prev === void 0) {
|
|
@@ -5062,7 +5113,8 @@ function buildFrameResult(input) {
|
|
|
5062
5113
|
...embeddingModelId !== void 0 ? { embeddingModelId } : {}
|
|
5063
5114
|
} : {},
|
|
5064
5115
|
...m.faceAlignedCrop !== void 0 ? { faceAlignedCrop: m.faceAlignedCrop } : {},
|
|
5065
|
-
...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {}
|
|
5116
|
+
...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {},
|
|
5117
|
+
...m.landmarks !== void 0 ? { landmarks: m.landmarks } : {}
|
|
5066
5118
|
};
|
|
5067
5119
|
if (input.debug) {
|
|
5068
5120
|
const cleanDebug = pruneUndefined({
|
|
@@ -5323,6 +5375,11 @@ var PipelineExecutor = class {
|
|
|
5323
5375
|
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
|
|
5324
5376
|
const startMs = Date.now();
|
|
5325
5377
|
const verbosity = runOpts?.traceVerbosity ?? "off";
|
|
5378
|
+
const occasion = runOpts?.occasion ?? "live";
|
|
5379
|
+
const logTags = occasion === "replay" ? {
|
|
5380
|
+
deviceId,
|
|
5381
|
+
occasion
|
|
5382
|
+
} : { deviceId };
|
|
5326
5383
|
const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
|
|
5327
5384
|
const firstLevel = [];
|
|
5328
5385
|
const details = [];
|
|
@@ -5338,9 +5395,9 @@ var PipelineExecutor = class {
|
|
|
5338
5395
|
idGen,
|
|
5339
5396
|
details,
|
|
5340
5397
|
onClassifierClassDropped: (drop) => {
|
|
5341
|
-
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}
|
|
5398
|
+
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
|
|
5342
5399
|
if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
|
|
5343
|
-
tags:
|
|
5400
|
+
tags: logTags,
|
|
5344
5401
|
meta: {
|
|
5345
5402
|
step: drop.stepId,
|
|
5346
5403
|
model: drop.modelId,
|
|
@@ -5351,7 +5408,7 @@ var PipelineExecutor = class {
|
|
|
5351
5408
|
}
|
|
5352
5409
|
});
|
|
5353
5410
|
if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
|
|
5354
|
-
tags:
|
|
5411
|
+
tags: logTags,
|
|
5355
5412
|
meta: {
|
|
5356
5413
|
step: drop.stepId,
|
|
5357
5414
|
droppedClass: drop.droppedClass,
|
|
@@ -5384,7 +5441,7 @@ var PipelineExecutor = class {
|
|
|
5384
5441
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5385
5442
|
} catch (err) {
|
|
5386
5443
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5387
|
-
tags:
|
|
5444
|
+
tags: logTags,
|
|
5388
5445
|
meta: {
|
|
5389
5446
|
rootStepId: rootStep.stepId,
|
|
5390
5447
|
parentClass: mutable.macroClass,
|
|
@@ -5438,9 +5495,9 @@ var PipelineExecutor = class {
|
|
|
5438
5495
|
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
5439
5496
|
const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
|
|
5440
5497
|
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
|
|
5441
|
-
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
|
|
5498
|
+
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
|
|
5442
5499
|
if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
|
|
5443
|
-
tags:
|
|
5500
|
+
tags: logTags,
|
|
5444
5501
|
meta: {
|
|
5445
5502
|
macroClass: mutable.macroClass,
|
|
5446
5503
|
suppressed: sample.summaryCount,
|
|
@@ -5449,7 +5506,7 @@ var PipelineExecutor = class {
|
|
|
5449
5506
|
}
|
|
5450
5507
|
});
|
|
5451
5508
|
if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
|
|
5452
|
-
tags:
|
|
5509
|
+
tags: logTags,
|
|
5453
5510
|
meta: {
|
|
5454
5511
|
macroClass: mutable.macroClass,
|
|
5455
5512
|
score: mutable.score,
|
|
@@ -5475,7 +5532,7 @@ var PipelineExecutor = class {
|
|
|
5475
5532
|
continue;
|
|
5476
5533
|
}
|
|
5477
5534
|
if (isFullFrameSurvivor(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0)) this.opts.logger?.info("full-frame box SURVIVED the guard on score", {
|
|
5478
|
-
tags:
|
|
5535
|
+
tags: logTags,
|
|
5479
5536
|
meta: {
|
|
5480
5537
|
macroClass: mutable.macroClass,
|
|
5481
5538
|
score: mutable.score,
|
|
@@ -5489,7 +5546,7 @@ var PipelineExecutor = class {
|
|
|
5489
5546
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5490
5547
|
} catch (err) {
|
|
5491
5548
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5492
|
-
tags:
|
|
5549
|
+
tags: logTags,
|
|
5493
5550
|
meta: {
|
|
5494
5551
|
rootStepId: rootStep.stepId,
|
|
5495
5552
|
parentClass: mutable.macroClass,
|
|
@@ -6547,25 +6604,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
|
|
|
6547
6604
|
* race bound keeps a wedged transport from wedging `setApi`.
|
|
6548
6605
|
*/
|
|
6549
6606
|
var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
6550
|
-
/**
|
|
6551
|
-
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
6552
|
-
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
6553
|
-
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
6554
|
-
* instead of duplicated inline.
|
|
6555
|
-
*/
|
|
6556
|
-
/**
|
|
6557
|
-
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
6558
|
-
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
6559
|
-
* reject a persisted/global engine choice that the node's PLATFORM fundamentally
|
|
6560
|
-
* cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
|
|
6561
|
-
* from the gpu-dependent support (linux without a probed Intel iGPU still keeps
|
|
6562
|
-
* openvino as a valid platform choice; the device falls back to cpu).
|
|
6563
|
-
*/
|
|
6564
|
-
function backendPossibleOnPlatform(backend) {
|
|
6565
|
-
if (backend === "coreml") return process.platform === "darwin";
|
|
6566
|
-
if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
|
|
6567
|
-
return true;
|
|
6568
|
-
}
|
|
6569
6607
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
6570
6608
|
modelsDir;
|
|
6571
6609
|
eventBus;
|
|
@@ -6705,22 +6743,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6705
6743
|
*/
|
|
6706
6744
|
needsAutoPick = false;
|
|
6707
6745
|
/**
|
|
6708
|
-
* Warm cache for benchmark engine-override runs.
|
|
6709
|
-
*
|
|
6710
|
-
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
6711
|
-
* plus the per-model load. Benchmark tabs iterate up to 800× against
|
|
6712
|
-
* the same override (e.g. yolov9s / coreml / all) — paying that spin-up
|
|
6713
|
-
* each iteration dwarfs actual inference time.
|
|
6714
|
-
*
|
|
6715
|
-
* When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
|
|
6716
|
-
* we reuse the prior transient factory. Hitting a different override
|
|
6717
|
-
* or exceeding the TTL disposes the cache and spins a new factory.
|
|
6718
|
-
* The prior "main" factory (`priorFactory`) is still restored so
|
|
6719
|
-
* runtime dispatch (camera frames) keeps its own resident engine.
|
|
6720
|
-
*/
|
|
6721
|
-
overrideCache = null;
|
|
6722
|
-
overrideCacheTimer = null;
|
|
6723
|
-
/**
|
|
6724
6746
|
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
6725
6747
|
* with no dispatch for this long is disposed and recreated on next use — the
|
|
6726
6748
|
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
@@ -6753,7 +6775,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6753
6775
|
error: err instanceof Error ? err.message : String(err)
|
|
6754
6776
|
} })
|
|
6755
6777
|
});
|
|
6756
|
-
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
6757
6778
|
/**
|
|
6758
6779
|
* RSS bound + periodic memory telemetry for the Python inference pools
|
|
6759
6780
|
* (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
|
|
@@ -6890,15 +6911,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6890
6911
|
/**
|
|
6891
6912
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
6892
6913
|
* the embedded Python before the inference pool spawns. No-op for
|
|
6893
|
-
*
|
|
6894
|
-
*
|
|
6914
|
+
* backends with no requirements file on disk, or before the addon
|
|
6915
|
+
* context has been wired (warm-pool race window).
|
|
6895
6916
|
*
|
|
6896
6917
|
* Idempotent — `installPythonRequirements` short-circuits on a hash
|
|
6897
6918
|
* marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
|
|
6898
6919
|
* tuning respawns) skip the pip subprocess after the first install.
|
|
6899
6920
|
*/
|
|
6900
6921
|
async ensureBackendDeps(engine) {
|
|
6901
|
-
if (engine.runtime !== "python") return;
|
|
6902
6922
|
if (!this.addonCtx) return;
|
|
6903
6923
|
const pythonAddonDir = this.executorOptions.pythonAddonDir;
|
|
6904
6924
|
if (!pythonAddonDir) return;
|
|
@@ -7463,8 +7483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7463
7483
|
* result with a warning logged, same posture as `recomputeConfigIssues`.
|
|
7464
7484
|
*/
|
|
7465
7485
|
async validatePipeline(input) {
|
|
7466
|
-
|
|
7486
|
+
let format = this.currentEngine.format;
|
|
7467
7487
|
try {
|
|
7488
|
+
if (input.deviceKey !== void 0) try {
|
|
7489
|
+
format = resolveDeviceEngine(input.deviceKey).format;
|
|
7490
|
+
} catch (err) {
|
|
7491
|
+
return {
|
|
7492
|
+
ok: false,
|
|
7493
|
+
issues: [{
|
|
7494
|
+
addonId: "*",
|
|
7495
|
+
kind: "unknown-device",
|
|
7496
|
+
detail: require_dist.errMsg(err)
|
|
7497
|
+
}],
|
|
7498
|
+
substitutions: [],
|
|
7499
|
+
format
|
|
7500
|
+
};
|
|
7501
|
+
}
|
|
7468
7502
|
const steps = input.steps;
|
|
7469
7503
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
7470
7504
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
@@ -7789,7 +7823,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7789
7823
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
7790
7824
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7791
7825
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
7792
|
-
const progressToBus = !isRuntime || input.sessionId !== void 0;
|
|
7826
|
+
const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
|
|
7793
7827
|
const emit = (message, extra) => {
|
|
7794
7828
|
onProgress?.(message);
|
|
7795
7829
|
if (!progressToBus) return;
|
|
@@ -7931,7 +7965,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7931
7965
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
7932
7966
|
steps: input.steps,
|
|
7933
7967
|
deviceKey: input.deviceKey,
|
|
7934
|
-
engineOverride: input.engine,
|
|
7935
7968
|
deviceId: input.deviceId,
|
|
7936
7969
|
plane: input.plane
|
|
7937
7970
|
});
|
|
@@ -7951,322 +7984,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7951
7984
|
}
|
|
7952
7985
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
7953
7986
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
7954
|
-
|
|
7955
|
-
const
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
const cached = this.overrideCache;
|
|
7967
|
-
if (cached.initPromise) await cached.initPromise;
|
|
7968
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
7969
|
-
this.currentEngine = runEngine;
|
|
7970
|
-
this.engineFactory = cached.factory;
|
|
7971
|
-
this.executor = cached.executor;
|
|
7972
|
-
} else {
|
|
7973
|
-
if (this.overrideCache) {
|
|
7974
|
-
try {
|
|
7975
|
-
await this.overrideCache.factory.dispose();
|
|
7976
|
-
} catch {}
|
|
7977
|
-
this.overrideCache = null;
|
|
7978
|
-
}
|
|
7979
|
-
this.log.info("Benchmark: engine override", { meta: {
|
|
7980
|
-
from: `${priorEngine.runtime}/${priorEngine.backend}`,
|
|
7981
|
-
to: `${runEngine.runtime}/${runEngine.backend}`
|
|
7982
|
-
} });
|
|
7983
|
-
await this.ensureBackendDeps(runEngine);
|
|
7984
|
-
const newFactory = new EngineFactory({
|
|
7985
|
-
engine: runEngine,
|
|
7986
|
-
modelsDir: this.modelsDir,
|
|
7987
|
-
logger: this.log.child("engine-override"),
|
|
7988
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
7989
|
-
provisioning: this.executorOptions.provisioning,
|
|
7990
|
-
resolveCustomModel: this.customModelResolver
|
|
7991
|
-
});
|
|
7992
|
-
const initPromise = newFactory.initialize([]);
|
|
7993
|
-
this.overrideCache = {
|
|
7994
|
-
engine: runEngine,
|
|
7995
|
-
factory: newFactory,
|
|
7996
|
-
executor: null,
|
|
7997
|
-
lastUsedMs: Date.now(),
|
|
7998
|
-
initPromise
|
|
7999
|
-
};
|
|
8000
|
-
this.scheduleOverrideCacheEviction();
|
|
8001
|
-
try {
|
|
8002
|
-
await initPromise;
|
|
8003
|
-
this.overrideCache.initPromise = null;
|
|
8004
|
-
} catch (err) {
|
|
8005
|
-
this.overrideCache = null;
|
|
8006
|
-
if (this.overrideCacheTimer) {
|
|
8007
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8008
|
-
this.overrideCacheTimer = null;
|
|
8009
|
-
}
|
|
8010
|
-
throw err;
|
|
8011
|
-
}
|
|
8012
|
-
this.currentEngine = runEngine;
|
|
8013
|
-
this.engineFactory = newFactory;
|
|
8014
|
-
this.executor = null;
|
|
8015
|
-
}
|
|
8016
|
-
try {
|
|
8017
|
-
const deviceLabel = runEngine.device ?? "default";
|
|
8018
|
-
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
|
|
8019
|
-
step: "engine",
|
|
8020
|
-
engine: {
|
|
8021
|
-
runtime: runEngine.runtime,
|
|
8022
|
-
backend: runEngine.backend,
|
|
8023
|
-
device: deviceLabel
|
|
8024
|
-
}
|
|
8025
|
-
});
|
|
8026
|
-
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8027
|
-
const executor = this.executor ?? new PipelineExecutor({
|
|
8028
|
-
engineRuntime: runtimeStr,
|
|
8029
|
-
logger: this.log
|
|
7987
|
+
const rootModelId = resolveRootModelId(benchmarkSteps);
|
|
7988
|
+
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
7989
|
+
...r,
|
|
7990
|
+
rootModelId
|
|
7991
|
+
};
|
|
7992
|
+
if (input.replay === true) {
|
|
7993
|
+
const verdict = checkReplayPin({
|
|
7994
|
+
requested: input.steps,
|
|
7995
|
+
executing: input.plane === "frame" ? collectFramePlaneSteps(benchmarkSteps, isDetailPlaneStep) : enabledSteps,
|
|
7996
|
+
requestedDeviceKey: input.deviceKey,
|
|
7997
|
+
dispatchDeviceKey,
|
|
7998
|
+
format: dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format
|
|
8030
7999
|
});
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
} });
|
|
8040
|
-
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8041
|
-
step: s.addonId,
|
|
8042
|
-
addonId: s.addonId,
|
|
8043
|
-
modelId: s.modelId
|
|
8000
|
+
if (verdict.kind === "refused") {
|
|
8001
|
+
this.log.warn("replay refused — the node cannot run exactly what was pinned", {
|
|
8002
|
+
...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
|
|
8003
|
+
meta: {
|
|
8004
|
+
reason: verdict.reason,
|
|
8005
|
+
detail: verdict.detail,
|
|
8006
|
+
deviceKey: input.deviceKey
|
|
8007
|
+
}
|
|
8044
8008
|
});
|
|
8045
|
-
|
|
8046
|
-
emit(`All models loaded`);
|
|
8047
|
-
}
|
|
8048
|
-
emit("Running inference...");
|
|
8049
|
-
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8050
|
-
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8051
|
-
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8052
|
-
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8053
|
-
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8054
|
-
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8055
|
-
let cropZoneBbox;
|
|
8056
|
-
if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8057
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8058
|
-
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8059
|
-
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8060
|
-
}
|
|
8061
|
-
let frameViewResolver;
|
|
8062
|
-
let rootInputViewProvider;
|
|
8063
|
-
if (runtimeFrameRef) {
|
|
8064
|
-
frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
|
|
8065
|
-
rootInputViewProvider = async (root, crop) => {
|
|
8066
|
-
const view = await frameViewResolver(root, crop ? {
|
|
8067
|
-
left: crop[0],
|
|
8068
|
-
top: crop[1],
|
|
8069
|
-
width: crop[2] - crop[0],
|
|
8070
|
-
height: crop[3] - crop[1]
|
|
8071
|
-
} : void 0);
|
|
8072
|
-
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8073
|
-
return {
|
|
8074
|
-
input: {
|
|
8075
|
-
kind: "jpeg",
|
|
8076
|
-
data
|
|
8077
|
-
},
|
|
8078
|
-
jpegProvider: async () => data,
|
|
8079
|
-
width: view.width,
|
|
8080
|
-
height: view.height,
|
|
8081
|
-
geometry: view.geometry
|
|
8082
|
-
};
|
|
8083
|
-
};
|
|
8084
|
-
}
|
|
8085
|
-
let executionSucceeded = false;
|
|
8086
|
-
let execution;
|
|
8087
|
-
try {
|
|
8088
|
-
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8089
|
-
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8090
|
-
plane: input.plane
|
|
8091
|
-
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8092
|
-
executionSucceeded = true;
|
|
8093
|
-
} finally {
|
|
8094
|
-
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8095
|
-
}
|
|
8096
|
-
const { result, trace } = execution;
|
|
8097
|
-
if (isRuntime) {
|
|
8098
|
-
if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
|
|
8099
|
-
type: "device",
|
|
8100
|
-
id: trace.deviceId,
|
|
8101
|
-
nodeId: "hub"
|
|
8102
|
-
}, trace));
|
|
8103
|
-
if (effectiveDeviceId > 0) {
|
|
8104
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8105
|
-
return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
|
|
8106
|
-
}
|
|
8107
|
-
return result;
|
|
8108
|
-
}
|
|
8109
|
-
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8110
|
-
step: t.source,
|
|
8111
|
-
addonId: t.source,
|
|
8112
|
-
modelId: t.modelId ?? void 0,
|
|
8113
|
-
ms: t.ms
|
|
8114
|
-
});
|
|
8115
|
-
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8116
|
-
const wallMs = performance.now() - wallT0;
|
|
8117
|
-
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8118
|
-
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8119
|
-
return {
|
|
8120
|
-
...result,
|
|
8121
|
-
debug: {
|
|
8122
|
-
...result.debug,
|
|
8123
|
-
decodeMs: Math.round(decodeMs * 100) / 100,
|
|
8124
|
-
setupMs: Math.round(setupMs * 100) / 100,
|
|
8125
|
-
wallMs: Math.round(wallMs * 100) / 100,
|
|
8126
|
-
overheadMs: Math.round(overheadMs * 100) / 100
|
|
8127
|
-
}
|
|
8128
|
-
};
|
|
8129
|
-
} finally {
|
|
8130
|
-
if (differs && this.overrideCache) {
|
|
8131
|
-
this.overrideCache.executor = this.executor;
|
|
8132
|
-
this.overrideCache.lastUsedMs = Date.now();
|
|
8133
|
-
this.scheduleOverrideCacheEviction();
|
|
8134
|
-
}
|
|
8135
|
-
if (differs) {
|
|
8136
|
-
this.currentEngine = priorEngine;
|
|
8137
|
-
this.engineFactory = priorFactory;
|
|
8138
|
-
this.executor = priorExecutor;
|
|
8009
|
+
throw new Error(`${verdict.reason}: ${verdict.detail}`);
|
|
8139
8010
|
}
|
|
8140
8011
|
}
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
* `runPipelineBatch` (batched fast path). Without this, the batch
|
|
8152
|
-
* path silently ignored `input.engine` and ran the user's override
|
|
8153
|
-
* against the addon's saved engine — making the override matrix in
|
|
8154
|
-
* the bench UI a no-op for batchSize > 1.
|
|
8155
|
-
*/
|
|
8156
|
-
async applyEngineOverride(override) {
|
|
8157
|
-
const priorEngine = this.currentEngine;
|
|
8158
|
-
const priorFactory = this.engineFactory;
|
|
8159
|
-
const priorExecutor = this.executor;
|
|
8160
|
-
if (!override) return () => {};
|
|
8161
|
-
if (!backendPossibleOnPlatform(override.backend)) {
|
|
8162
|
-
this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
|
|
8163
|
-
override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
|
|
8164
|
-
platform: process.platform,
|
|
8165
|
-
arch: process.arch,
|
|
8166
|
-
engine: `${priorEngine.runtime}/${priorEngine.backend}`
|
|
8167
|
-
} });
|
|
8168
|
-
return () => {};
|
|
8169
|
-
}
|
|
8170
|
-
const runEngine = {
|
|
8171
|
-
runtime: override.runtime,
|
|
8172
|
-
backend: override.backend,
|
|
8173
|
-
format: override.format,
|
|
8174
|
-
...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
|
|
8175
|
-
};
|
|
8176
|
-
if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
|
|
8177
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
|
|
8178
|
-
const cached = this.overrideCache;
|
|
8179
|
-
if (cached.initPromise) await cached.initPromise;
|
|
8180
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
8181
|
-
this.currentEngine = runEngine;
|
|
8182
|
-
this.engineFactory = cached.factory;
|
|
8183
|
-
this.executor = cached.executor;
|
|
8184
|
-
} else {
|
|
8185
|
-
if (this.overrideCache) {
|
|
8186
|
-
try {
|
|
8187
|
-
await this.overrideCache.factory.dispose();
|
|
8188
|
-
} catch {}
|
|
8189
|
-
this.overrideCache = null;
|
|
8012
|
+
const appliesCameraGates = isRuntime || input.replay === true;
|
|
8013
|
+
await this.ensureEngineFactory();
|
|
8014
|
+
const runEngine = this.currentEngine;
|
|
8015
|
+
const deviceLabel = runEngine.device ?? "default";
|
|
8016
|
+
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}`, {
|
|
8017
|
+
step: "engine",
|
|
8018
|
+
engine: {
|
|
8019
|
+
runtime: runEngine.runtime,
|
|
8020
|
+
backend: runEngine.backend,
|
|
8021
|
+
device: deviceLabel
|
|
8190
8022
|
}
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8023
|
+
});
|
|
8024
|
+
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8025
|
+
const executor = this.executor ?? new PipelineExecutor({
|
|
8026
|
+
engineRuntime: runtimeStr,
|
|
8027
|
+
logger: this.log
|
|
8028
|
+
});
|
|
8029
|
+
this.executor = executor;
|
|
8030
|
+
const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
|
|
8031
|
+
const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8032
|
+
const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
|
|
8033
|
+
if (needed.length > 0) {
|
|
8034
|
+
this.log.info("Benchmark: models to load", { meta: {
|
|
8035
|
+
count: needed.length,
|
|
8036
|
+
models: needed.map((s) => `${s.addonId}/${s.modelId}`)
|
|
8194
8037
|
} });
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
logger: this.log.child("engine-override"),
|
|
8200
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
8201
|
-
provisioning: this.executorOptions.provisioning,
|
|
8202
|
-
resolveCustomModel: this.customModelResolver
|
|
8038
|
+
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8039
|
+
step: s.addonId,
|
|
8040
|
+
addonId: s.addonId,
|
|
8041
|
+
modelId: s.modelId
|
|
8203
8042
|
});
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8043
|
+
await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
|
|
8044
|
+
emit(`All models loaded`);
|
|
8045
|
+
}
|
|
8046
|
+
emit("Running inference...");
|
|
8047
|
+
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8048
|
+
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8049
|
+
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8050
|
+
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8051
|
+
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8052
|
+
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8053
|
+
let cropZoneBbox;
|
|
8054
|
+
if (appliesCameraGates && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8055
|
+
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8056
|
+
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8057
|
+
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8058
|
+
}
|
|
8059
|
+
let frameViewResolver;
|
|
8060
|
+
let rootInputViewProvider;
|
|
8061
|
+
if (runtimeFrameRef) {
|
|
8062
|
+
frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
|
|
8063
|
+
rootInputViewProvider = async (root, crop) => {
|
|
8064
|
+
const view = await frameViewResolver(root, crop ? {
|
|
8065
|
+
left: crop[0],
|
|
8066
|
+
top: crop[1],
|
|
8067
|
+
width: crop[2] - crop[0],
|
|
8068
|
+
height: crop[3] - crop[1]
|
|
8069
|
+
} : void 0);
|
|
8070
|
+
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8071
|
+
return {
|
|
8072
|
+
input: {
|
|
8073
|
+
kind: "jpeg",
|
|
8074
|
+
data
|
|
8075
|
+
},
|
|
8076
|
+
jpegProvider: async () => data,
|
|
8077
|
+
width: view.width,
|
|
8078
|
+
height: view.height,
|
|
8079
|
+
geometry: view.geometry
|
|
8080
|
+
};
|
|
8211
8081
|
};
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
this.
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8082
|
+
}
|
|
8083
|
+
let executionSucceeded = false;
|
|
8084
|
+
let execution;
|
|
8085
|
+
try {
|
|
8086
|
+
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8087
|
+
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8088
|
+
plane: input.plane,
|
|
8089
|
+
occasion: input.replay === true ? "replay" : "live"
|
|
8090
|
+
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8091
|
+
executionSucceeded = true;
|
|
8092
|
+
} finally {
|
|
8093
|
+
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8094
|
+
}
|
|
8095
|
+
const { result, trace } = execution;
|
|
8096
|
+
if (isRuntime) {
|
|
8097
|
+
if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
|
|
8098
|
+
type: "device",
|
|
8099
|
+
id: trace.deviceId,
|
|
8100
|
+
nodeId: "hub"
|
|
8101
|
+
}, trace));
|
|
8102
|
+
if (effectiveDeviceId > 0) {
|
|
8103
|
+
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8104
|
+
return stampRoot(this.gateDetectionsByZoneRules(effectiveDeviceId, result));
|
|
8223
8105
|
}
|
|
8224
|
-
|
|
8225
|
-
this.engineFactory = newFactory;
|
|
8226
|
-
this.executor = null;
|
|
8106
|
+
return stampRoot(result);
|
|
8227
8107
|
}
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8108
|
+
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8109
|
+
step: t.source,
|
|
8110
|
+
addonId: t.source,
|
|
8111
|
+
modelId: t.modelId ?? void 0,
|
|
8112
|
+
ms: t.ms
|
|
8113
|
+
});
|
|
8114
|
+
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8115
|
+
const wallMs = performance.now() - wallT0;
|
|
8116
|
+
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8117
|
+
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8118
|
+
const gated = input.replay === true && effectiveDeviceId > 0 ? await this.ensureDeviceProxy(effectiveDeviceId).then(() => this.gateDetectionsByZoneRules(effectiveDeviceId, result)) : result;
|
|
8119
|
+
return {
|
|
8120
|
+
...stampRoot(gated),
|
|
8121
|
+
debug: {
|
|
8122
|
+
...gated.debug,
|
|
8123
|
+
decodeMs: Math.round(decodeMs * 100) / 100,
|
|
8124
|
+
setupMs: Math.round(setupMs * 100) / 100,
|
|
8125
|
+
wallMs: Math.round(wallMs * 100) / 100,
|
|
8126
|
+
overheadMs: Math.round(overheadMs * 100) / 100
|
|
8233
8127
|
}
|
|
8234
|
-
this.currentEngine = priorEngine;
|
|
8235
|
-
this.engineFactory = priorFactory;
|
|
8236
|
-
this.executor = priorExecutor;
|
|
8237
8128
|
};
|
|
8238
8129
|
}
|
|
8239
8130
|
/**
|
|
8240
|
-
* Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
|
|
8241
|
-
* of idleness. Rescheduling resets the timer so an active benchmark
|
|
8242
|
-
* keeps its warm factory alive until iterations stop arriving.
|
|
8243
|
-
*/
|
|
8244
|
-
scheduleOverrideCacheEviction() {
|
|
8245
|
-
if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
|
|
8246
|
-
this.overrideCacheTimer = setTimeout(() => {
|
|
8247
|
-
this.evictOverrideCache("idle TTL");
|
|
8248
|
-
}, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
|
|
8249
|
-
}
|
|
8250
|
-
async evictOverrideCache(reason) {
|
|
8251
|
-
if (!this.overrideCache) return;
|
|
8252
|
-
const entry = this.overrideCache;
|
|
8253
|
-
this.overrideCache = null;
|
|
8254
|
-
if (this.overrideCacheTimer) {
|
|
8255
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8256
|
-
this.overrideCacheTimer = null;
|
|
8257
|
-
}
|
|
8258
|
-
try {
|
|
8259
|
-
await entry.factory.dispose();
|
|
8260
|
-
this.log.info("Override cache evicted", { meta: {
|
|
8261
|
-
reason,
|
|
8262
|
-
engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
|
|
8263
|
-
ageMs: Date.now() - entry.lastUsedMs
|
|
8264
|
-
} });
|
|
8265
|
-
} catch (err) {
|
|
8266
|
-
this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
8267
|
-
}
|
|
8268
|
-
}
|
|
8269
|
-
/**
|
|
8270
8131
|
* Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
|
|
8271
8132
|
* StepDefinitions. This is the LIVE per-camera dispatch path — it runs
|
|
8272
8133
|
* once per decoded frame (up to detectionFps × N cameras/sec) — so it
|
|
@@ -8282,13 +8143,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8282
8143
|
* `resolveStepModels` (see its doc comment); a second writer here would
|
|
8283
8144
|
* corrupt it.
|
|
8284
8145
|
*
|
|
8285
|
-
* `format` defaults to `this.currentEngine.format` (the
|
|
8286
|
-
*
|
|
8287
|
-
* per-run engine override (`input.engine`) MUST pass the override's format
|
|
8288
|
-
* explicitly — `this.currentEngine` only gets swapped to the override
|
|
8289
|
-
* LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
|
|
8290
|
-
* would resolve models against the node's persisted format instead of the
|
|
8291
|
-
* format actually being benchmarked.
|
|
8146
|
+
* `format` defaults to `this.currentEngine.format` (the node-default
|
|
8147
|
+
* pool); a `deviceKey` dispatch passes that device's format explicitly.
|
|
8292
8148
|
*/
|
|
8293
8149
|
inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
|
|
8294
8150
|
backend: this.currentEngine.backend,
|
|
@@ -8370,8 +8226,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8370
8226
|
* devices — so the whole-tree gate stays.
|
|
8371
8227
|
*
|
|
8372
8228
|
* Fallback is strictly NODE-LOCAL and ordered: the node's own selected
|
|
8373
|
-
* engine
|
|
8374
|
-
* fallback — frames never migrate to another node from here (that is the
|
|
8229
|
+
* engine is the one and only fallback — frames never migrate to another node from here (that is the
|
|
8375
8230
|
* orchestrator's tier). The fallback is logged ONCE per distinct
|
|
8376
8231
|
* (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
|
|
8377
8232
|
* dispatch is camera-scoped.
|
|
@@ -8382,12 +8237,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8382
8237
|
* configuration must stay loud, not vanish into a silent fallback chain.
|
|
8383
8238
|
*/
|
|
8384
8239
|
resolveStepsForDispatch(args) {
|
|
8385
|
-
const { deviceKey,
|
|
8386
|
-
const nodeFormat =
|
|
8387
|
-
const nodeEngine =
|
|
8388
|
-
backend: engineOverride.backend,
|
|
8389
|
-
device: engineOverride.device ?? null
|
|
8390
|
-
} : {
|
|
8240
|
+
const { deviceKey, deviceId } = args;
|
|
8241
|
+
const nodeFormat = this.currentEngine.format;
|
|
8242
|
+
const nodeEngine = {
|
|
8391
8243
|
backend: this.currentEngine.backend,
|
|
8392
8244
|
device: this.currentEngine.device ?? null
|
|
8393
8245
|
};
|
|
@@ -8632,12 +8484,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8632
8484
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
8633
8485
|
steps: input.steps,
|
|
8634
8486
|
deviceKey: input.deviceKey,
|
|
8635
|
-
engineOverride: input.engine,
|
|
8636
8487
|
deviceId: input.deviceId,
|
|
8637
8488
|
plane: void 0
|
|
8638
8489
|
});
|
|
8639
8490
|
const dispatchDeviceKey = dispatchResolution.deviceKey;
|
|
8640
|
-
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format :
|
|
8491
|
+
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
|
|
8641
8492
|
const benchmarkSteps = dispatchResolution.steps;
|
|
8642
8493
|
const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
|
|
8643
8494
|
if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
|
|
@@ -8658,53 +8509,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8658
8509
|
const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
|
|
8659
8510
|
const singleRoot = enabledSteps.length === 1;
|
|
8660
8511
|
await this.ensureEngineFactory();
|
|
8661
|
-
const
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
const perFrameMs = totalMs / rawResults.length;
|
|
8704
|
-
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8705
|
-
} finally {
|
|
8706
|
-
restoreEngine();
|
|
8707
|
-
}
|
|
8512
|
+
const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8513
|
+
if (!factory) throw new Error("runPipelineBatch: factory not initialised");
|
|
8514
|
+
if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
|
|
8515
|
+
const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
|
|
8516
|
+
this.log.info("runPipelineBatch path decision", { meta: {
|
|
8517
|
+
phase: "batch",
|
|
8518
|
+
canFastPath,
|
|
8519
|
+
singleRoot,
|
|
8520
|
+
allRaw,
|
|
8521
|
+
uniformDims,
|
|
8522
|
+
supportsBatch: factory.supportsBatch()
|
|
8523
|
+
} });
|
|
8524
|
+
if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
|
|
8525
|
+
steps: input.steps,
|
|
8526
|
+
frame,
|
|
8527
|
+
deviceId: input.deviceId,
|
|
8528
|
+
sessionId: input.sessionId,
|
|
8529
|
+
deviceKey: dispatchDeviceKey
|
|
8530
|
+
}))) };
|
|
8531
|
+
const items = input.frames.map((frame) => ({
|
|
8532
|
+
raw: Buffer.from(frame.data),
|
|
8533
|
+
width: frame.width,
|
|
8534
|
+
height: frame.height,
|
|
8535
|
+
format: frame.format
|
|
8536
|
+
}));
|
|
8537
|
+
this.log.info("runPipelineBatch fast path dispatch", { meta: {
|
|
8538
|
+
phase: "batch",
|
|
8539
|
+
stepId: rootStep.addonId,
|
|
8540
|
+
modelId: rootStep.modelId,
|
|
8541
|
+
items: items.length,
|
|
8542
|
+
totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
|
|
8543
|
+
} });
|
|
8544
|
+
const start = performance.now();
|
|
8545
|
+
const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
|
|
8546
|
+
const totalMs = performance.now() - start;
|
|
8547
|
+
this.log.info("runPipelineBatch fast path returned", { meta: {
|
|
8548
|
+
phase: "batch",
|
|
8549
|
+
resultCount: rawResults.length,
|
|
8550
|
+
ms: Math.round(totalMs)
|
|
8551
|
+
} });
|
|
8552
|
+
const perFrameMs = totalMs / rawResults.length;
|
|
8553
|
+
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8708
8554
|
}
|
|
8709
8555
|
async listReferenceImages() {
|
|
8710
8556
|
const dir = this.resolveRefImagesDir();
|
|
@@ -8809,7 +8655,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8809
8655
|
this.poolMemoryGuard = null;
|
|
8810
8656
|
this.inferenceTimeoutGuard?.stop();
|
|
8811
8657
|
this.inferenceTimeoutGuard = null;
|
|
8812
|
-
await this.evictOverrideCache("shutdown");
|
|
8813
8658
|
if (this.engineFactory) {
|
|
8814
8659
|
await this.engineFactory.dispose();
|
|
8815
8660
|
this.engineFactory = null;
|
|
@@ -8891,13 +8736,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8891
8736
|
* own runtime's row, so this is not "the node's tuning" — the per-pool truth
|
|
8892
8737
|
* is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
|
|
8893
8738
|
*/
|
|
8894
|
-
|
|
8895
|
-
|
|
8739
|
+
/**
|
|
8740
|
+
* One pool's provisioning — the device pool named by `deviceKey`, else the
|
|
8741
|
+
* node default. Resolved with the SAME override `resolveDeviceFactory` and
|
|
8742
|
+
* `ensureEngineFactory` build the pool with, so the answer describes the
|
|
8743
|
+
* pool that ran the work (D646).
|
|
8744
|
+
*/
|
|
8745
|
+
async getEffectiveTuning(input = {}) {
|
|
8746
|
+
const p = resolvePoolProvisioning(input.deviceKey !== void 0 ? resolveDeviceEngine(input.deviceKey) : this.currentEngine, this.executorOptions.provisioning);
|
|
8896
8747
|
return {
|
|
8748
|
+
deviceKey: input.deviceKey ?? null,
|
|
8749
|
+
runtime: p.runtime,
|
|
8897
8750
|
batchMode: p.batchMode,
|
|
8898
8751
|
windowMs: p.windowMs,
|
|
8899
8752
|
maxBatchSize: p.maxBatchSize,
|
|
8900
|
-
concurrency: p.concurrency
|
|
8753
|
+
concurrency: p.concurrency,
|
|
8754
|
+
numWorkers: p.numWorkers
|
|
8901
8755
|
};
|
|
8902
8756
|
}
|
|
8903
8757
|
/**
|
|
@@ -9285,9 +9139,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9285
9139
|
}
|
|
9286
9140
|
async listLoadedEngines() {
|
|
9287
9141
|
const out = [];
|
|
9288
|
-
|
|
9289
|
-
const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
|
|
9290
|
-
if (this.engineFactory && !runtimeFactoryIsOverride) {
|
|
9142
|
+
if (this.engineFactory) {
|
|
9291
9143
|
const eng = this.currentEngine;
|
|
9292
9144
|
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
|
|
9293
9145
|
const loaded = this.engineFactory.listLoaded();
|
|
@@ -9302,22 +9154,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9302
9154
|
idleTtlMs: null
|
|
9303
9155
|
});
|
|
9304
9156
|
}
|
|
9305
|
-
if (this.overrideCache) {
|
|
9306
|
-
const eng = this.overrideCache.engine;
|
|
9307
|
-
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
|
|
9308
|
-
const loaded = this.overrideCache.factory.listLoaded();
|
|
9309
|
-
const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
|
|
9310
|
-
if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
|
|
9311
|
-
engineKey,
|
|
9312
|
-
engine: eng,
|
|
9313
|
-
modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
|
|
9314
|
-
inUseByCameras: [],
|
|
9315
|
-
kind: "warm-override",
|
|
9316
|
-
poolPid: this.overrideCache.factory.getPoolPid(),
|
|
9317
|
-
idleMs,
|
|
9318
|
-
idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
|
|
9319
|
-
});
|
|
9320
|
-
}
|
|
9321
9157
|
for (const [deviceKey, factory] of this.factoriesByDevice) {
|
|
9322
9158
|
const eng = resolveDeviceEngine(deviceKey);
|
|
9323
9159
|
const loaded = factory.listLoaded();
|
|
@@ -9346,14 +9182,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9346
9182
|
return { success: true };
|
|
9347
9183
|
}
|
|
9348
9184
|
async killEngine(input) {
|
|
9349
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
|
|
9350
|
-
await this.evictOverrideCache("killEngine");
|
|
9351
|
-
this.log.info("Override engine killed", { meta: {
|
|
9352
|
-
...input.engine,
|
|
9353
|
-
force: input.force ?? false
|
|
9354
|
-
} });
|
|
9355
|
-
return { success: true };
|
|
9356
|
-
}
|
|
9357
9185
|
if (!this.engineFactory) return {
|
|
9358
9186
|
success: false,
|
|
9359
9187
|
reason: "not loaded"
|
|
@@ -9871,7 +9699,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
9871
9699
|
/**
|
|
9872
9700
|
* Embedded Python path resolved once at boot via
|
|
9873
9701
|
* `ctx.deps.ensurePython()`. Empty string means the download failed
|
|
9874
|
-
*
|
|
9702
|
+
* — the provider's
|
|
9875
9703
|
* `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
|
|
9876
9704
|
* clear error in that case.
|
|
9877
9705
|
*/
|