@camstack/addon-pipeline 1.2.294 → 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-0dPKRKUD.mjs → default-detection-model-CAUBVbgK.mjs} +107 -54
- package/dist/{default-detection-model-D24AJOTn.js → default-detection-model-wNEISVA9.js} +107 -54
- package/dist/detection-pipeline/index.js +284 -457
- package/dist/detection-pipeline/index.mjs +284 -457
- package/dist/{dist-CJR259Xf.js → dist-BsVcf5wO.js} +1310 -832
- package/dist/{dist-RXbmRAwP.mjs → dist-CuxSNLKW.mjs} +1305 -833
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-atmRSHPk.mjs → node-Cqb1QiXk.mjs} +1 -1
- package/dist/{node-DWg9zbY1.js → node-UFk6I2f6.js} +1 -1
- package/dist/pipeline-runner/index.js +189 -70
- package/dist/pipeline-runner/index.mjs +189 -70
- package/dist/{process-memory-BJUXvTjd.js → process-memory-CJV29sPf.js} +1 -1
- package/dist/{process-memory-BgFOHFnx.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-C_fPJub3.js → segment-demux-js-DGwmf5hu.js} +1 -1
- package/dist/{segment-demux-js-G7wFpHzn.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-6IyM-BIn.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-BBYHWS3M.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) {
|
|
@@ -5324,6 +5375,11 @@ var PipelineExecutor = class {
|
|
|
5324
5375
|
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
|
|
5325
5376
|
const startMs = Date.now();
|
|
5326
5377
|
const verbosity = runOpts?.traceVerbosity ?? "off";
|
|
5378
|
+
const occasion = runOpts?.occasion ?? "live";
|
|
5379
|
+
const logTags = occasion === "replay" ? {
|
|
5380
|
+
deviceId,
|
|
5381
|
+
occasion
|
|
5382
|
+
} : { deviceId };
|
|
5327
5383
|
const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
|
|
5328
5384
|
const firstLevel = [];
|
|
5329
5385
|
const details = [];
|
|
@@ -5339,9 +5395,9 @@ var PipelineExecutor = class {
|
|
|
5339
5395
|
idGen,
|
|
5340
5396
|
details,
|
|
5341
5397
|
onClassifierClassDropped: (drop) => {
|
|
5342
|
-
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}
|
|
5398
|
+
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
|
|
5343
5399
|
if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
|
|
5344
|
-
tags:
|
|
5400
|
+
tags: logTags,
|
|
5345
5401
|
meta: {
|
|
5346
5402
|
step: drop.stepId,
|
|
5347
5403
|
model: drop.modelId,
|
|
@@ -5352,7 +5408,7 @@ var PipelineExecutor = class {
|
|
|
5352
5408
|
}
|
|
5353
5409
|
});
|
|
5354
5410
|
if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
|
|
5355
|
-
tags:
|
|
5411
|
+
tags: logTags,
|
|
5356
5412
|
meta: {
|
|
5357
5413
|
step: drop.stepId,
|
|
5358
5414
|
droppedClass: drop.droppedClass,
|
|
@@ -5385,7 +5441,7 @@ var PipelineExecutor = class {
|
|
|
5385
5441
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5386
5442
|
} catch (err) {
|
|
5387
5443
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5388
|
-
tags:
|
|
5444
|
+
tags: logTags,
|
|
5389
5445
|
meta: {
|
|
5390
5446
|
rootStepId: rootStep.stepId,
|
|
5391
5447
|
parentClass: mutable.macroClass,
|
|
@@ -5439,9 +5495,9 @@ var PipelineExecutor = class {
|
|
|
5439
5495
|
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
5440
5496
|
const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
|
|
5441
5497
|
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
|
|
5442
|
-
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
|
|
5498
|
+
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
|
|
5443
5499
|
if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
|
|
5444
|
-
tags:
|
|
5500
|
+
tags: logTags,
|
|
5445
5501
|
meta: {
|
|
5446
5502
|
macroClass: mutable.macroClass,
|
|
5447
5503
|
suppressed: sample.summaryCount,
|
|
@@ -5450,7 +5506,7 @@ var PipelineExecutor = class {
|
|
|
5450
5506
|
}
|
|
5451
5507
|
});
|
|
5452
5508
|
if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
|
|
5453
|
-
tags:
|
|
5509
|
+
tags: logTags,
|
|
5454
5510
|
meta: {
|
|
5455
5511
|
macroClass: mutable.macroClass,
|
|
5456
5512
|
score: mutable.score,
|
|
@@ -5476,7 +5532,7 @@ var PipelineExecutor = class {
|
|
|
5476
5532
|
continue;
|
|
5477
5533
|
}
|
|
5478
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", {
|
|
5479
|
-
tags:
|
|
5535
|
+
tags: logTags,
|
|
5480
5536
|
meta: {
|
|
5481
5537
|
macroClass: mutable.macroClass,
|
|
5482
5538
|
score: mutable.score,
|
|
@@ -5490,7 +5546,7 @@ var PipelineExecutor = class {
|
|
|
5490
5546
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5491
5547
|
} catch (err) {
|
|
5492
5548
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5493
|
-
tags:
|
|
5549
|
+
tags: logTags,
|
|
5494
5550
|
meta: {
|
|
5495
5551
|
rootStepId: rootStep.stepId,
|
|
5496
5552
|
parentClass: mutable.macroClass,
|
|
@@ -6548,25 +6604,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
|
|
|
6548
6604
|
* race bound keeps a wedged transport from wedging `setApi`.
|
|
6549
6605
|
*/
|
|
6550
6606
|
var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
6551
|
-
/**
|
|
6552
|
-
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
6553
|
-
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
6554
|
-
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
6555
|
-
* instead of duplicated inline.
|
|
6556
|
-
*/
|
|
6557
|
-
/**
|
|
6558
|
-
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
6559
|
-
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
6560
|
-
* reject a persisted/global engine choice that the node's PLATFORM fundamentally
|
|
6561
|
-
* cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
|
|
6562
|
-
* from the gpu-dependent support (linux without a probed Intel iGPU still keeps
|
|
6563
|
-
* openvino as a valid platform choice; the device falls back to cpu).
|
|
6564
|
-
*/
|
|
6565
|
-
function backendPossibleOnPlatform(backend) {
|
|
6566
|
-
if (backend === "coreml") return process.platform === "darwin";
|
|
6567
|
-
if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
|
|
6568
|
-
return true;
|
|
6569
|
-
}
|
|
6570
6607
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
6571
6608
|
modelsDir;
|
|
6572
6609
|
eventBus;
|
|
@@ -6706,22 +6743,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6706
6743
|
*/
|
|
6707
6744
|
needsAutoPick = false;
|
|
6708
6745
|
/**
|
|
6709
|
-
* Warm cache for benchmark engine-override runs.
|
|
6710
|
-
*
|
|
6711
|
-
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
6712
|
-
* plus the per-model load. Benchmark tabs iterate up to 800× against
|
|
6713
|
-
* the same override (e.g. yolov9s / coreml / all) — paying that spin-up
|
|
6714
|
-
* each iteration dwarfs actual inference time.
|
|
6715
|
-
*
|
|
6716
|
-
* When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
|
|
6717
|
-
* we reuse the prior transient factory. Hitting a different override
|
|
6718
|
-
* or exceeding the TTL disposes the cache and spins a new factory.
|
|
6719
|
-
* The prior "main" factory (`priorFactory`) is still restored so
|
|
6720
|
-
* runtime dispatch (camera frames) keeps its own resident engine.
|
|
6721
|
-
*/
|
|
6722
|
-
overrideCache = null;
|
|
6723
|
-
overrideCacheTimer = null;
|
|
6724
|
-
/**
|
|
6725
6746
|
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
6726
6747
|
* with no dispatch for this long is disposed and recreated on next use — the
|
|
6727
6748
|
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
@@ -6754,7 +6775,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6754
6775
|
error: err instanceof Error ? err.message : String(err)
|
|
6755
6776
|
} })
|
|
6756
6777
|
});
|
|
6757
|
-
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
6758
6778
|
/**
|
|
6759
6779
|
* RSS bound + periodic memory telemetry for the Python inference pools
|
|
6760
6780
|
* (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
|
|
@@ -6891,15 +6911,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6891
6911
|
/**
|
|
6892
6912
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
6893
6913
|
* the embedded Python before the inference pool spawns. No-op for
|
|
6894
|
-
*
|
|
6895
|
-
*
|
|
6914
|
+
* backends with no requirements file on disk, or before the addon
|
|
6915
|
+
* context has been wired (warm-pool race window).
|
|
6896
6916
|
*
|
|
6897
6917
|
* Idempotent — `installPythonRequirements` short-circuits on a hash
|
|
6898
6918
|
* marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
|
|
6899
6919
|
* tuning respawns) skip the pip subprocess after the first install.
|
|
6900
6920
|
*/
|
|
6901
6921
|
async ensureBackendDeps(engine) {
|
|
6902
|
-
if (engine.runtime !== "python") return;
|
|
6903
6922
|
if (!this.addonCtx) return;
|
|
6904
6923
|
const pythonAddonDir = this.executorOptions.pythonAddonDir;
|
|
6905
6924
|
if (!pythonAddonDir) return;
|
|
@@ -7464,8 +7483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7464
7483
|
* result with a warning logged, same posture as `recomputeConfigIssues`.
|
|
7465
7484
|
*/
|
|
7466
7485
|
async validatePipeline(input) {
|
|
7467
|
-
|
|
7486
|
+
let format = this.currentEngine.format;
|
|
7468
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
|
+
}
|
|
7469
7502
|
const steps = input.steps;
|
|
7470
7503
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
7471
7504
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
@@ -7790,7 +7823,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7790
7823
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
7791
7824
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7792
7825
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
7793
|
-
const progressToBus = !isRuntime || input.sessionId !== void 0;
|
|
7826
|
+
const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
|
|
7794
7827
|
const emit = (message, extra) => {
|
|
7795
7828
|
onProgress?.(message);
|
|
7796
7829
|
if (!progressToBus) return;
|
|
@@ -7932,7 +7965,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7932
7965
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
7933
7966
|
steps: input.steps,
|
|
7934
7967
|
deviceKey: input.deviceKey,
|
|
7935
|
-
engineOverride: input.engine,
|
|
7936
7968
|
deviceId: input.deviceId,
|
|
7937
7969
|
plane: input.plane
|
|
7938
7970
|
});
|
|
@@ -7952,322 +7984,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7952
7984
|
}
|
|
7953
7985
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
7954
7986
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
7955
|
-
|
|
7956
|
-
const
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
const cached = this.overrideCache;
|
|
7968
|
-
if (cached.initPromise) await cached.initPromise;
|
|
7969
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
7970
|
-
this.currentEngine = runEngine;
|
|
7971
|
-
this.engineFactory = cached.factory;
|
|
7972
|
-
this.executor = cached.executor;
|
|
7973
|
-
} else {
|
|
7974
|
-
if (this.overrideCache) {
|
|
7975
|
-
try {
|
|
7976
|
-
await this.overrideCache.factory.dispose();
|
|
7977
|
-
} catch {}
|
|
7978
|
-
this.overrideCache = null;
|
|
7979
|
-
}
|
|
7980
|
-
this.log.info("Benchmark: engine override", { meta: {
|
|
7981
|
-
from: `${priorEngine.runtime}/${priorEngine.backend}`,
|
|
7982
|
-
to: `${runEngine.runtime}/${runEngine.backend}`
|
|
7983
|
-
} });
|
|
7984
|
-
await this.ensureBackendDeps(runEngine);
|
|
7985
|
-
const newFactory = new EngineFactory({
|
|
7986
|
-
engine: runEngine,
|
|
7987
|
-
modelsDir: this.modelsDir,
|
|
7988
|
-
logger: this.log.child("engine-override"),
|
|
7989
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
7990
|
-
provisioning: this.executorOptions.provisioning,
|
|
7991
|
-
resolveCustomModel: this.customModelResolver
|
|
7992
|
-
});
|
|
7993
|
-
const initPromise = newFactory.initialize([]);
|
|
7994
|
-
this.overrideCache = {
|
|
7995
|
-
engine: runEngine,
|
|
7996
|
-
factory: newFactory,
|
|
7997
|
-
executor: null,
|
|
7998
|
-
lastUsedMs: Date.now(),
|
|
7999
|
-
initPromise
|
|
8000
|
-
};
|
|
8001
|
-
this.scheduleOverrideCacheEviction();
|
|
8002
|
-
try {
|
|
8003
|
-
await initPromise;
|
|
8004
|
-
this.overrideCache.initPromise = null;
|
|
8005
|
-
} catch (err) {
|
|
8006
|
-
this.overrideCache = null;
|
|
8007
|
-
if (this.overrideCacheTimer) {
|
|
8008
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8009
|
-
this.overrideCacheTimer = null;
|
|
8010
|
-
}
|
|
8011
|
-
throw err;
|
|
8012
|
-
}
|
|
8013
|
-
this.currentEngine = runEngine;
|
|
8014
|
-
this.engineFactory = newFactory;
|
|
8015
|
-
this.executor = null;
|
|
8016
|
-
}
|
|
8017
|
-
try {
|
|
8018
|
-
const deviceLabel = runEngine.device ?? "default";
|
|
8019
|
-
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
|
|
8020
|
-
step: "engine",
|
|
8021
|
-
engine: {
|
|
8022
|
-
runtime: runEngine.runtime,
|
|
8023
|
-
backend: runEngine.backend,
|
|
8024
|
-
device: deviceLabel
|
|
8025
|
-
}
|
|
8026
|
-
});
|
|
8027
|
-
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8028
|
-
const executor = this.executor ?? new PipelineExecutor({
|
|
8029
|
-
engineRuntime: runtimeStr,
|
|
8030
|
-
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
|
|
8031
7999
|
});
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
} });
|
|
8041
|
-
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8042
|
-
step: s.addonId,
|
|
8043
|
-
addonId: s.addonId,
|
|
8044
|
-
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
|
+
}
|
|
8045
8008
|
});
|
|
8046
|
-
|
|
8047
|
-
emit(`All models loaded`);
|
|
8048
|
-
}
|
|
8049
|
-
emit("Running inference...");
|
|
8050
|
-
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8051
|
-
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8052
|
-
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8053
|
-
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8054
|
-
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8055
|
-
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8056
|
-
let cropZoneBbox;
|
|
8057
|
-
if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8058
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8059
|
-
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8060
|
-
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8061
|
-
}
|
|
8062
|
-
let frameViewResolver;
|
|
8063
|
-
let rootInputViewProvider;
|
|
8064
|
-
if (runtimeFrameRef) {
|
|
8065
|
-
frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
|
|
8066
|
-
rootInputViewProvider = async (root, crop) => {
|
|
8067
|
-
const view = await frameViewResolver(root, crop ? {
|
|
8068
|
-
left: crop[0],
|
|
8069
|
-
top: crop[1],
|
|
8070
|
-
width: crop[2] - crop[0],
|
|
8071
|
-
height: crop[3] - crop[1]
|
|
8072
|
-
} : void 0);
|
|
8073
|
-
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8074
|
-
return {
|
|
8075
|
-
input: {
|
|
8076
|
-
kind: "jpeg",
|
|
8077
|
-
data
|
|
8078
|
-
},
|
|
8079
|
-
jpegProvider: async () => data,
|
|
8080
|
-
width: view.width,
|
|
8081
|
-
height: view.height,
|
|
8082
|
-
geometry: view.geometry
|
|
8083
|
-
};
|
|
8084
|
-
};
|
|
8085
|
-
}
|
|
8086
|
-
let executionSucceeded = false;
|
|
8087
|
-
let execution;
|
|
8088
|
-
try {
|
|
8089
|
-
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8090
|
-
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8091
|
-
plane: input.plane
|
|
8092
|
-
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8093
|
-
executionSucceeded = true;
|
|
8094
|
-
} finally {
|
|
8095
|
-
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8096
|
-
}
|
|
8097
|
-
const { result, trace } = execution;
|
|
8098
|
-
if (isRuntime) {
|
|
8099
|
-
if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
|
|
8100
|
-
type: "device",
|
|
8101
|
-
id: trace.deviceId,
|
|
8102
|
-
nodeId: "hub"
|
|
8103
|
-
}, trace));
|
|
8104
|
-
if (effectiveDeviceId > 0) {
|
|
8105
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8106
|
-
return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
|
|
8107
|
-
}
|
|
8108
|
-
return result;
|
|
8109
|
-
}
|
|
8110
|
-
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8111
|
-
step: t.source,
|
|
8112
|
-
addonId: t.source,
|
|
8113
|
-
modelId: t.modelId ?? void 0,
|
|
8114
|
-
ms: t.ms
|
|
8115
|
-
});
|
|
8116
|
-
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8117
|
-
const wallMs = performance.now() - wallT0;
|
|
8118
|
-
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8119
|
-
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8120
|
-
return {
|
|
8121
|
-
...result,
|
|
8122
|
-
debug: {
|
|
8123
|
-
...result.debug,
|
|
8124
|
-
decodeMs: Math.round(decodeMs * 100) / 100,
|
|
8125
|
-
setupMs: Math.round(setupMs * 100) / 100,
|
|
8126
|
-
wallMs: Math.round(wallMs * 100) / 100,
|
|
8127
|
-
overheadMs: Math.round(overheadMs * 100) / 100
|
|
8128
|
-
}
|
|
8129
|
-
};
|
|
8130
|
-
} finally {
|
|
8131
|
-
if (differs && this.overrideCache) {
|
|
8132
|
-
this.overrideCache.executor = this.executor;
|
|
8133
|
-
this.overrideCache.lastUsedMs = Date.now();
|
|
8134
|
-
this.scheduleOverrideCacheEviction();
|
|
8135
|
-
}
|
|
8136
|
-
if (differs) {
|
|
8137
|
-
this.currentEngine = priorEngine;
|
|
8138
|
-
this.engineFactory = priorFactory;
|
|
8139
|
-
this.executor = priorExecutor;
|
|
8009
|
+
throw new Error(`${verdict.reason}: ${verdict.detail}`);
|
|
8140
8010
|
}
|
|
8141
8011
|
}
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
* `runPipelineBatch` (batched fast path). Without this, the batch
|
|
8153
|
-
* path silently ignored `input.engine` and ran the user's override
|
|
8154
|
-
* against the addon's saved engine — making the override matrix in
|
|
8155
|
-
* the bench UI a no-op for batchSize > 1.
|
|
8156
|
-
*/
|
|
8157
|
-
async applyEngineOverride(override) {
|
|
8158
|
-
const priorEngine = this.currentEngine;
|
|
8159
|
-
const priorFactory = this.engineFactory;
|
|
8160
|
-
const priorExecutor = this.executor;
|
|
8161
|
-
if (!override) return () => {};
|
|
8162
|
-
if (!backendPossibleOnPlatform(override.backend)) {
|
|
8163
|
-
this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
|
|
8164
|
-
override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
|
|
8165
|
-
platform: process.platform,
|
|
8166
|
-
arch: process.arch,
|
|
8167
|
-
engine: `${priorEngine.runtime}/${priorEngine.backend}`
|
|
8168
|
-
} });
|
|
8169
|
-
return () => {};
|
|
8170
|
-
}
|
|
8171
|
-
const runEngine = {
|
|
8172
|
-
runtime: override.runtime,
|
|
8173
|
-
backend: override.backend,
|
|
8174
|
-
format: override.format,
|
|
8175
|
-
...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
|
|
8176
|
-
};
|
|
8177
|
-
if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
|
|
8178
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
|
|
8179
|
-
const cached = this.overrideCache;
|
|
8180
|
-
if (cached.initPromise) await cached.initPromise;
|
|
8181
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
8182
|
-
this.currentEngine = runEngine;
|
|
8183
|
-
this.engineFactory = cached.factory;
|
|
8184
|
-
this.executor = cached.executor;
|
|
8185
|
-
} else {
|
|
8186
|
-
if (this.overrideCache) {
|
|
8187
|
-
try {
|
|
8188
|
-
await this.overrideCache.factory.dispose();
|
|
8189
|
-
} catch {}
|
|
8190
|
-
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
|
|
8191
8022
|
}
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
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}`)
|
|
8195
8037
|
} });
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
logger: this.log.child("engine-override"),
|
|
8201
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
8202
|
-
provisioning: this.executorOptions.provisioning,
|
|
8203
|
-
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
|
|
8204
8042
|
});
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
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
|
+
};
|
|
8212
8081
|
};
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
this.
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
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));
|
|
8224
8105
|
}
|
|
8225
|
-
|
|
8226
|
-
this.engineFactory = newFactory;
|
|
8227
|
-
this.executor = null;
|
|
8106
|
+
return stampRoot(result);
|
|
8228
8107
|
}
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
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
|
|
8234
8127
|
}
|
|
8235
|
-
this.currentEngine = priorEngine;
|
|
8236
|
-
this.engineFactory = priorFactory;
|
|
8237
|
-
this.executor = priorExecutor;
|
|
8238
8128
|
};
|
|
8239
8129
|
}
|
|
8240
8130
|
/**
|
|
8241
|
-
* Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
|
|
8242
|
-
* of idleness. Rescheduling resets the timer so an active benchmark
|
|
8243
|
-
* keeps its warm factory alive until iterations stop arriving.
|
|
8244
|
-
*/
|
|
8245
|
-
scheduleOverrideCacheEviction() {
|
|
8246
|
-
if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
|
|
8247
|
-
this.overrideCacheTimer = setTimeout(() => {
|
|
8248
|
-
this.evictOverrideCache("idle TTL");
|
|
8249
|
-
}, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
|
|
8250
|
-
}
|
|
8251
|
-
async evictOverrideCache(reason) {
|
|
8252
|
-
if (!this.overrideCache) return;
|
|
8253
|
-
const entry = this.overrideCache;
|
|
8254
|
-
this.overrideCache = null;
|
|
8255
|
-
if (this.overrideCacheTimer) {
|
|
8256
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8257
|
-
this.overrideCacheTimer = null;
|
|
8258
|
-
}
|
|
8259
|
-
try {
|
|
8260
|
-
await entry.factory.dispose();
|
|
8261
|
-
this.log.info("Override cache evicted", { meta: {
|
|
8262
|
-
reason,
|
|
8263
|
-
engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
|
|
8264
|
-
ageMs: Date.now() - entry.lastUsedMs
|
|
8265
|
-
} });
|
|
8266
|
-
} catch (err) {
|
|
8267
|
-
this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
8268
|
-
}
|
|
8269
|
-
}
|
|
8270
|
-
/**
|
|
8271
8131
|
* Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
|
|
8272
8132
|
* StepDefinitions. This is the LIVE per-camera dispatch path — it runs
|
|
8273
8133
|
* once per decoded frame (up to detectionFps × N cameras/sec) — so it
|
|
@@ -8283,13 +8143,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8283
8143
|
* `resolveStepModels` (see its doc comment); a second writer here would
|
|
8284
8144
|
* corrupt it.
|
|
8285
8145
|
*
|
|
8286
|
-
* `format` defaults to `this.currentEngine.format` (the
|
|
8287
|
-
*
|
|
8288
|
-
* per-run engine override (`input.engine`) MUST pass the override's format
|
|
8289
|
-
* explicitly — `this.currentEngine` only gets swapped to the override
|
|
8290
|
-
* LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
|
|
8291
|
-
* would resolve models against the node's persisted format instead of the
|
|
8292
|
-
* 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.
|
|
8293
8148
|
*/
|
|
8294
8149
|
inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
|
|
8295
8150
|
backend: this.currentEngine.backend,
|
|
@@ -8371,8 +8226,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8371
8226
|
* devices — so the whole-tree gate stays.
|
|
8372
8227
|
*
|
|
8373
8228
|
* Fallback is strictly NODE-LOCAL and ordered: the node's own selected
|
|
8374
|
-
* engine
|
|
8375
|
-
* 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
|
|
8376
8230
|
* orchestrator's tier). The fallback is logged ONCE per distinct
|
|
8377
8231
|
* (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
|
|
8378
8232
|
* dispatch is camera-scoped.
|
|
@@ -8383,12 +8237,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8383
8237
|
* configuration must stay loud, not vanish into a silent fallback chain.
|
|
8384
8238
|
*/
|
|
8385
8239
|
resolveStepsForDispatch(args) {
|
|
8386
|
-
const { deviceKey,
|
|
8387
|
-
const nodeFormat =
|
|
8388
|
-
const nodeEngine =
|
|
8389
|
-
backend: engineOverride.backend,
|
|
8390
|
-
device: engineOverride.device ?? null
|
|
8391
|
-
} : {
|
|
8240
|
+
const { deviceKey, deviceId } = args;
|
|
8241
|
+
const nodeFormat = this.currentEngine.format;
|
|
8242
|
+
const nodeEngine = {
|
|
8392
8243
|
backend: this.currentEngine.backend,
|
|
8393
8244
|
device: this.currentEngine.device ?? null
|
|
8394
8245
|
};
|
|
@@ -8633,12 +8484,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8633
8484
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
8634
8485
|
steps: input.steps,
|
|
8635
8486
|
deviceKey: input.deviceKey,
|
|
8636
|
-
engineOverride: input.engine,
|
|
8637
8487
|
deviceId: input.deviceId,
|
|
8638
8488
|
plane: void 0
|
|
8639
8489
|
});
|
|
8640
8490
|
const dispatchDeviceKey = dispatchResolution.deviceKey;
|
|
8641
|
-
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format :
|
|
8491
|
+
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
|
|
8642
8492
|
const benchmarkSteps = dispatchResolution.steps;
|
|
8643
8493
|
const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
|
|
8644
8494
|
if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
|
|
@@ -8659,53 +8509,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8659
8509
|
const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
|
|
8660
8510
|
const singleRoot = enabledSteps.length === 1;
|
|
8661
8511
|
await this.ensureEngineFactory();
|
|
8662
|
-
const
|
|
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
|
-
|
|
8704
|
-
const perFrameMs = totalMs / rawResults.length;
|
|
8705
|
-
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8706
|
-
} finally {
|
|
8707
|
-
restoreEngine();
|
|
8708
|
-
}
|
|
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)) };
|
|
8709
8554
|
}
|
|
8710
8555
|
async listReferenceImages() {
|
|
8711
8556
|
const dir = this.resolveRefImagesDir();
|
|
@@ -8810,7 +8655,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8810
8655
|
this.poolMemoryGuard = null;
|
|
8811
8656
|
this.inferenceTimeoutGuard?.stop();
|
|
8812
8657
|
this.inferenceTimeoutGuard = null;
|
|
8813
|
-
await this.evictOverrideCache("shutdown");
|
|
8814
8658
|
if (this.engineFactory) {
|
|
8815
8659
|
await this.engineFactory.dispose();
|
|
8816
8660
|
this.engineFactory = null;
|
|
@@ -8892,13 +8736,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8892
8736
|
* own runtime's row, so this is not "the node's tuning" — the per-pool truth
|
|
8893
8737
|
* is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
|
|
8894
8738
|
*/
|
|
8895
|
-
|
|
8896
|
-
|
|
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);
|
|
8897
8747
|
return {
|
|
8748
|
+
deviceKey: input.deviceKey ?? null,
|
|
8749
|
+
runtime: p.runtime,
|
|
8898
8750
|
batchMode: p.batchMode,
|
|
8899
8751
|
windowMs: p.windowMs,
|
|
8900
8752
|
maxBatchSize: p.maxBatchSize,
|
|
8901
|
-
concurrency: p.concurrency
|
|
8753
|
+
concurrency: p.concurrency,
|
|
8754
|
+
numWorkers: p.numWorkers
|
|
8902
8755
|
};
|
|
8903
8756
|
}
|
|
8904
8757
|
/**
|
|
@@ -9286,9 +9139,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9286
9139
|
}
|
|
9287
9140
|
async listLoadedEngines() {
|
|
9288
9141
|
const out = [];
|
|
9289
|
-
|
|
9290
|
-
const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
|
|
9291
|
-
if (this.engineFactory && !runtimeFactoryIsOverride) {
|
|
9142
|
+
if (this.engineFactory) {
|
|
9292
9143
|
const eng = this.currentEngine;
|
|
9293
9144
|
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
|
|
9294
9145
|
const loaded = this.engineFactory.listLoaded();
|
|
@@ -9303,22 +9154,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9303
9154
|
idleTtlMs: null
|
|
9304
9155
|
});
|
|
9305
9156
|
}
|
|
9306
|
-
if (this.overrideCache) {
|
|
9307
|
-
const eng = this.overrideCache.engine;
|
|
9308
|
-
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
|
|
9309
|
-
const loaded = this.overrideCache.factory.listLoaded();
|
|
9310
|
-
const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
|
|
9311
|
-
if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
|
|
9312
|
-
engineKey,
|
|
9313
|
-
engine: eng,
|
|
9314
|
-
modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
|
|
9315
|
-
inUseByCameras: [],
|
|
9316
|
-
kind: "warm-override",
|
|
9317
|
-
poolPid: this.overrideCache.factory.getPoolPid(),
|
|
9318
|
-
idleMs,
|
|
9319
|
-
idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
|
|
9320
|
-
});
|
|
9321
|
-
}
|
|
9322
9157
|
for (const [deviceKey, factory] of this.factoriesByDevice) {
|
|
9323
9158
|
const eng = resolveDeviceEngine(deviceKey);
|
|
9324
9159
|
const loaded = factory.listLoaded();
|
|
@@ -9347,14 +9182,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9347
9182
|
return { success: true };
|
|
9348
9183
|
}
|
|
9349
9184
|
async killEngine(input) {
|
|
9350
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
|
|
9351
|
-
await this.evictOverrideCache("killEngine");
|
|
9352
|
-
this.log.info("Override engine killed", { meta: {
|
|
9353
|
-
...input.engine,
|
|
9354
|
-
force: input.force ?? false
|
|
9355
|
-
} });
|
|
9356
|
-
return { success: true };
|
|
9357
|
-
}
|
|
9358
9185
|
if (!this.engineFactory) return {
|
|
9359
9186
|
success: false,
|
|
9360
9187
|
reason: "not loaded"
|
|
@@ -9872,7 +9699,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
9872
9699
|
/**
|
|
9873
9700
|
* Embedded Python path resolved once at boot via
|
|
9874
9701
|
* `ctx.deps.ensurePython()`. Empty string means the download failed
|
|
9875
|
-
*
|
|
9702
|
+
* — the provider's
|
|
9876
9703
|
* `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
|
|
9877
9704
|
* clear error in that case.
|
|
9878
9705
|
*/
|