@camstack/addon-pipeline 1.2.107 → 1.2.115

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.
Files changed (35) hide show
  1. package/dist/{addon-utils-Vv2dCqA2.js → addon-utils-C0ht2KPO.js} +51 -15
  2. package/dist/{addon-utils-A2S9D7pu.mjs → addon-utils-CgBCF-mE.mjs} +50 -14
  3. package/dist/audio-analyzer/index.js +3 -3
  4. package/dist/audio-analyzer/index.mjs +3 -3
  5. package/dist/detection-pipeline/index.js +170 -31
  6. package/dist/detection-pipeline/index.mjs +169 -30
  7. package/dist/{dist-L-1LMGQs.js → dist-BqIbYW1A.js} +130 -17
  8. package/dist/{dist-CkXAzTZ2.mjs → dist-DGQkkWc6.mjs} +130 -17
  9. package/dist/{event-loop-stall-monitor-fHkfgmlw.mjs → event-loop-stall-monitor-D5jK5v4Z.mjs} +223 -2
  10. package/dist/{event-loop-stall-monitor-DwAho7HD.js → event-loop-stall-monitor-ylMGpz48.js} +234 -1
  11. package/dist/{lazy-sharp-DfbyQxET.js → lazy-sharp-cOsy7l_P.js} +1 -1
  12. package/dist/motion-wasm/index.js +2 -2
  13. package/dist/motion-wasm/index.mjs +1 -1
  14. package/dist/pipeline-runner/index.js +915 -107
  15. package/dist/pipeline-runner/index.mjs +914 -106
  16. package/dist/{process-memory-CgrbLo3l.js → process-memory-45juuHpN.js} +1 -1
  17. package/dist/{process-memory-DlSOzuHP.mjs → process-memory-BtT7WfbJ.mjs} +1 -1
  18. package/dist/recorder/index.js +187 -47
  19. package/dist/recorder/index.mjs +187 -47
  20. package/dist/session-decode/decode-worker-child.js +30 -10
  21. package/dist/session-decode/decode-worker-child.mjs +29 -9
  22. package/dist/stream-broker/_stub.js +2 -2
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BeL4_jgH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs} +3 -3
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs +26 -0
  25. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CmHtm5CX.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs} +1 -1
  26. package/dist/stream-broker/{hostInit-BK9nzKAG.mjs → hostInit-CG28NvlV.mjs} +3 -3
  27. package/dist/stream-broker/index.js +279 -198
  28. package/dist/stream-broker/index.mjs +279 -198
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/dist/{worker-protocol-GGnzfWu3.js → worker-protocol-CIrqGbpG.js} +6 -3
  31. package/dist/{worker-protocol-bEipQT9l.mjs → worker-protocol-DyocTIx_.mjs} +6 -3
  32. package/package.json +1 -1
  33. package/python/inference_pool.py +4 -1
  34. package/python/test_inference_pool_preprocess.py +87 -16
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DjndFc5V.mjs +0 -26
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-L-1LMGQs.js");
1
+ const require_dist = require("./dist-BqIbYW1A.js");
2
2
  require("node:crypto");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_dist.__toESM(node_path, 1);
@@ -6,7 +6,7 @@ let node_fs = require("node:fs");
6
6
  node_fs = require_dist.__toESM(node_fs, 1);
7
7
  let node_util = require("node:util");
8
8
  let node_zlib = require("node:zlib");
9
- //#region ../system/dist/file-data-plane-CuE_hBli.mjs
9
+ //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
10
10
  function isNonEmptyFile(filePath) {
11
11
  return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
12
12
  }
@@ -32,21 +32,56 @@ function buildHeaders(url) {
32
32
  if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
33
33
  return headers;
34
34
  }
35
- /**
36
- * Download a single file from a URL to a destination path.
37
- * Uses native fetch() (Node 22+) which handles redirects natively.
38
- * Streams to disk with optional progress callback.
39
- * Returns the destination path. Skips download if file already exists.
40
- */
41
- async function downloadFile(url, destPath, onProgress) {
35
+ var DEFAULT_MAX_REDIRECTS = 5;
36
+ function normalizeDownloadOptions(third) {
37
+ if (typeof third === "function") return { onProgress: third };
38
+ return third ?? {};
39
+ }
40
+ function isRedirectStatus(status) {
41
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
42
+ }
43
+ function resolveRedirectUrl(current, location) {
44
+ return new URL(location, current);
45
+ }
46
+ async function downloadFile(url, destPath, onProgressOrOptions) {
42
47
  if (node_fs.existsSync(destPath)) return destPath;
48
+ const opts = normalizeDownloadOptions(onProgressOrOptions);
49
+ const fetchImpl = opts.fetchImpl ?? fetch;
50
+ const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
43
51
  node_fs.mkdirSync(node_path.dirname(destPath), { recursive: true });
44
52
  const tmpPath = destPath + ".downloading";
45
53
  try {
46
- const response = await fetch(url, {
47
- redirect: "follow",
48
- headers: buildHeaders(url)
49
- });
54
+ let current = url;
55
+ const seen = /* @__PURE__ */ new Set();
56
+ let response;
57
+ const manual = opts.redirectPolicy !== void 0;
58
+ for (let hop = 0; hop <= maxRedirects; hop++) {
59
+ const parsed = new URL(current);
60
+ if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
61
+ if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
62
+ seen.add(parsed.href);
63
+ const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
64
+ const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
65
+ try {
66
+ response = await fetchImpl(current, {
67
+ redirect: manual ? "manual" : "follow",
68
+ headers: buildHeaders(current),
69
+ ...controller ? { signal: controller.signal } : {}
70
+ });
71
+ } finally {
72
+ if (timer) clearTimeout(timer);
73
+ }
74
+ if (manual && isRedirectStatus(response.status)) {
75
+ const location = response.headers.get("location");
76
+ if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
77
+ if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
78
+ current = resolveRedirectUrl(current, location).href;
79
+ continue;
80
+ }
81
+ break;
82
+ }
83
+ if (!response) throw new Error(`No response downloading ${url}`);
84
+ if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
50
85
  if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
51
86
  if (!response.body) throw new Error(`No response body from ${url}`);
52
87
  const total = parseInt(response.headers.get("content-length") ?? "0", 10);
@@ -57,9 +92,10 @@ async function downloadFile(url, destPath, onProgress) {
57
92
  for (;;) {
58
93
  const { done, value } = await reader.read();
59
94
  if (done || !value) break;
60
- fileStream.write(value);
61
95
  downloaded += value.length;
62
- onProgress?.(downloaded, total);
96
+ if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
97
+ fileStream.write(value);
98
+ opts.onProgress?.(downloaded, total);
63
99
  }
64
100
  } finally {
65
101
  fileStream.end();
@@ -5,7 +5,7 @@ import * as fs from "node:fs";
5
5
  import { createReadStream, promises } from "node:fs";
6
6
  import { promisify } from "node:util";
7
7
  import { brotliCompress, constants, gzip } from "node:zlib";
8
- //#region ../system/dist/file-data-plane-CuE_hBli.mjs
8
+ //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
9
9
  function isNonEmptyFile(filePath) {
10
10
  return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
11
11
  }
@@ -31,21 +31,56 @@ function buildHeaders(url) {
31
31
  if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
32
32
  return headers;
33
33
  }
34
- /**
35
- * Download a single file from a URL to a destination path.
36
- * Uses native fetch() (Node 22+) which handles redirects natively.
37
- * Streams to disk with optional progress callback.
38
- * Returns the destination path. Skips download if file already exists.
39
- */
40
- async function downloadFile(url, destPath, onProgress) {
34
+ var DEFAULT_MAX_REDIRECTS = 5;
35
+ function normalizeDownloadOptions(third) {
36
+ if (typeof third === "function") return { onProgress: third };
37
+ return third ?? {};
38
+ }
39
+ function isRedirectStatus(status) {
40
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
41
+ }
42
+ function resolveRedirectUrl(current, location) {
43
+ return new URL(location, current);
44
+ }
45
+ async function downloadFile(url, destPath, onProgressOrOptions) {
41
46
  if (fs.existsSync(destPath)) return destPath;
47
+ const opts = normalizeDownloadOptions(onProgressOrOptions);
48
+ const fetchImpl = opts.fetchImpl ?? fetch;
49
+ const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
42
50
  fs.mkdirSync(path$1.dirname(destPath), { recursive: true });
43
51
  const tmpPath = destPath + ".downloading";
44
52
  try {
45
- const response = await fetch(url, {
46
- redirect: "follow",
47
- headers: buildHeaders(url)
48
- });
53
+ let current = url;
54
+ const seen = /* @__PURE__ */ new Set();
55
+ let response;
56
+ const manual = opts.redirectPolicy !== void 0;
57
+ for (let hop = 0; hop <= maxRedirects; hop++) {
58
+ const parsed = new URL(current);
59
+ if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
60
+ if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
61
+ seen.add(parsed.href);
62
+ const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
63
+ const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
64
+ try {
65
+ response = await fetchImpl(current, {
66
+ redirect: manual ? "manual" : "follow",
67
+ headers: buildHeaders(current),
68
+ ...controller ? { signal: controller.signal } : {}
69
+ });
70
+ } finally {
71
+ if (timer) clearTimeout(timer);
72
+ }
73
+ if (manual && isRedirectStatus(response.status)) {
74
+ const location = response.headers.get("location");
75
+ if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
76
+ if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
77
+ current = resolveRedirectUrl(current, location).href;
78
+ continue;
79
+ }
80
+ break;
81
+ }
82
+ if (!response) throw new Error(`No response downloading ${url}`);
83
+ if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
49
84
  if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
50
85
  if (!response.body) throw new Error(`No response body from ${url}`);
51
86
  const total = parseInt(response.headers.get("content-length") ?? "0", 10);
@@ -56,9 +91,10 @@ async function downloadFile(url, destPath, onProgress) {
56
91
  for (;;) {
57
92
  const { done, value } = await reader.read();
58
93
  if (done || !value) break;
59
- fileStream.write(value);
60
94
  downloaded += value.length;
61
- onProgress?.(downloaded, total);
95
+ if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
96
+ fileStream.write(value);
97
+ opts.onProgress?.(downloaded, total);
62
98
  }
63
99
  } finally {
64
100
  fileStream.end();
@@ -2,9 +2,9 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-L-1LMGQs.js");
6
- const require_process_memory = require("../process-memory-CgrbLo3l.js");
7
- const require_addon_utils = require("../addon-utils-Vv2dCqA2.js");
5
+ const require_dist = require("../dist-BqIbYW1A.js");
6
+ const require_process_memory = require("../process-memory-45juuHpN.js");
7
+ const require_addon_utils = require("../addon-utils-C0ht2KPO.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_dist.__toESM(node_path);
10
10
  let node_fs = require("node:fs");
@@ -1,7 +1,7 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { A as audioAnalyzerCapability, Et as hydrateSchema, _ as HF_BASE_URL, ct as resolvePoolMemoryPolicy, ht as errMsg, k as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, q as mapAudioLabelToMacro, s as DEFAULT_AUDIO_ANALYZER_CONFIG, x as PoolMemoryWatchdog, xt as BaseAddon } from "../dist-CkXAzTZ2.mjs";
3
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-DlSOzuHP.mjs";
4
- import { i as downloadFile } from "../addon-utils-A2S9D7pu.mjs";
2
+ import { A as audioAnalyzerCapability, Et as hydrateSchema, _ as HF_BASE_URL, ct as resolvePoolMemoryPolicy, ht as errMsg, k as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, q as mapAudioLabelToMacro, s as DEFAULT_AUDIO_ANALYZER_CONFIG, x as PoolMemoryWatchdog, xt as BaseAddon } from "../dist-DGQkkWc6.mjs";
3
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BtT7WfbJ.mjs";
4
+ import { i as downloadFile } from "../addon-utils-CgBCF-mE.mjs";
5
5
  import * as path$1 from "node:path";
6
6
  import * as fs from "node:fs";
7
7
  //#region src/audio-analyzer/audio-pipeline.ts
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-L-1LMGQs.js");
6
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DwAho7HD.js");
7
- const require_lazy_sharp = require("../lazy-sharp-DfbyQxET.js");
8
- const require_process_memory = require("../process-memory-CgrbLo3l.js");
9
- const require_addon_utils = require("../addon-utils-Vv2dCqA2.js");
5
+ const require_dist = require("../dist-BqIbYW1A.js");
6
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-ylMGpz48.js");
7
+ const require_lazy_sharp = require("../lazy-sharp-cOsy7l_P.js");
8
+ const require_process_memory = require("../process-memory-45juuHpN.js");
9
+ const require_addon_utils = require("../addon-utils-C0ht2KPO.js");
10
10
  let sharp = require("sharp");
11
11
  sharp = require_dist.__toESM(sharp);
12
12
  let node_child_process = require("node:child_process");
@@ -264,7 +264,13 @@ var OBJECT_DETECTION_STEP_ID = "object-detection";
264
264
  * `null` → the caller substitutes the step's own `defaultModelId` (`yolo26n`).
265
265
  * INTEL NPU + iGPU default to OUR YOLOv9 (int8 @320): yolo26 does NOT compile on
266
266
  * the Intel NPU (arch incompat → 0 frames), while yolov9 runs on it (~298 fps
267
- * measured). Apple ANE stays on yolo26 until the CoreML yolov9 build lands.
267
+ * measured). Apple ANE uses the CoreML yolov9 @320 build.
268
+ *
269
+ * Do **not** promote these ids to `yolov9m-640` / `*-640`. The controlled
270
+ * evaluation recovered 0/3 misses at the current 0.50 threshold and failed the
271
+ * false-positive and capacity gates — see
272
+ * `docs/benchmarks/pipeline-frame-model-eval.md`. 640 remains an explicit
273
+ * operator pick, not a hardware-aware default.
268
274
  */
269
275
  var MODEL_BY_CLASS = {
270
276
  "apple-ane": "yolov9m-320",
@@ -1374,7 +1380,7 @@ function macroOfMinConfidenceKey(key) {
1374
1380
  */
1375
1381
  function reachableMacros(def, entry) {
1376
1382
  if (entry.labels.length === 0) return void 0;
1377
- const map = def.classMap;
1383
+ const map = entry.classMap ?? def.classMap;
1378
1384
  if (!map) return new Set(entry.labels.map((l) => l.id));
1379
1385
  if (map.preserveOriginal) return void 0;
1380
1386
  const macros = /* @__PURE__ */ new Set();
@@ -1394,7 +1400,7 @@ function reachableMacros(def, entry) {
1394
1400
  */
1395
1401
  function collectClassMapDropWarning(def, entry) {
1396
1402
  if (entry.labels.length === 0) return null;
1397
- const map = def.classMap;
1403
+ const map = entry.classMap ?? def.classMap;
1398
1404
  if (!map || map.preserveOriginal) return null;
1399
1405
  const unmapped = entry.labels.filter((l) => map.mapping[l.id] === void 0).map((l) => l.id);
1400
1406
  if (unmapped.length === 0) return null;
@@ -4170,7 +4176,7 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
4170
4176
  switch (output.kind) {
4171
4177
  case "detections":
4172
4178
  if (childStep.definition.slot === "refiner") {
4173
- const classMap = childStep.definition.classMap?.mapping;
4179
+ const classMap = childStep.effectiveClassMap?.mapping;
4174
4180
  const parentMacro = parent.macroClass;
4175
4181
  const best = output.detections.filter((d) => {
4176
4182
  if (d.mask === void 0) return false;
@@ -4510,6 +4516,29 @@ function reprojectCropZoneDetections(output, cropOrigin) {
4510
4516
  }))
4511
4517
  };
4512
4518
  }
4519
+ function reprojectViewDetections(output, geometry) {
4520
+ const mapPoint = (x, y) => ({
4521
+ x: geometry.sourceCrop.left + x / geometry.scale.x,
4522
+ y: geometry.sourceCrop.top + y / geometry.scale.y
4523
+ });
4524
+ return {
4525
+ kind: "detections",
4526
+ detections: output.detections.map((det) => {
4527
+ const origin = mapPoint(det.bbox[0], det.bbox[1]);
4528
+ const end = mapPoint(det.bbox[2], det.bbox[3]);
4529
+ return {
4530
+ ...det,
4531
+ bbox: [
4532
+ origin.x,
4533
+ origin.y,
4534
+ end.x,
4535
+ end.y
4536
+ ],
4537
+ ...det.landmarks ? { landmarks: det.landmarks.map((landmark) => mapPoint(landmark.x, landmark.y)) } : {}
4538
+ };
4539
+ })
4540
+ };
4541
+ }
4513
4542
  var PipelineExecutor = class {
4514
4543
  opts;
4515
4544
  /** Bounds the full-frame-guard drop log: first example per (device,
@@ -4571,7 +4600,7 @@ var PipelineExecutor = class {
4571
4600
  *
4572
4601
  * @returns FrameResult + optional trace
4573
4602
  */
4574
- async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox) {
4603
+ async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
4575
4604
  const startMs = Date.now();
4576
4605
  const verbosity = runOpts?.traceVerbosity ?? "off";
4577
4606
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
@@ -4619,18 +4648,20 @@ var PipelineExecutor = class {
4619
4648
  };
4620
4649
  for (const rootStep of tree.roots) {
4621
4650
  if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
4622
- const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
4651
+ const rootCrop = rootStep.definition.extractMode === "crop-zone" ? cropZoneBbox : void 0;
4652
+ const rootView = rootInputViewProvider ? await rootInputViewProvider(rootStep, rootCrop) : null;
4653
+ const cropPrep = !rootView && rootCrop !== void 0 ? await this.prepareCropZoneInput(rootCrop, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
4623
4654
  const rootStart = Date.now();
4624
- const rawRootOutput = await this.executeStep(rootStep, cropPrep?.input ?? withInferenceDeviceId(rootInput, deviceId), cropPrep?.width ?? imageWidth, cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg, rootShed);
4655
+ const rawRootOutput = await this.executeStep(rootStep, rootView?.input ?? cropPrep?.input ?? withInferenceDeviceId(rootInput, deviceId), rootView?.width ?? cropPrep?.width ?? imageWidth, rootView?.height ?? cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg, rootShed);
4625
4656
  const rootMs = Date.now() - rootStart;
4626
4657
  if (rawRootOutput === null) continue;
4627
- const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
4658
+ const rootOutput = rootView && rawRootOutput.kind === "detections" ? reprojectViewDetections(rawRootOutput, rootView.geometry) : cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
4628
4659
  if (rootOutput.kind !== "detections") {
4629
4660
  if (isEnrichmentOutput(rootOutput)) {
4630
4661
  const mutable = this.synthesizeRootDetection(rootOutput, rootStep, idGen, rootMs, imageWidth, imageHeight);
4631
4662
  applyChildOutput(mutable, rootStep, rootOutput, rootMs, ctx);
4632
4663
  try {
4633
- await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
4664
+ await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
4634
4665
  } catch (err) {
4635
4666
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
4636
4667
  tags: { deviceId },
@@ -4648,12 +4679,12 @@ var PipelineExecutor = class {
4648
4679
  }
4649
4680
  for (const det of rootOutput.detections) {
4650
4681
  const mutable = toMutableRootDetection(det, rootStep, idGen, rootMs);
4651
- if (rootStep.definition.classMap) {
4652
- const mapped = rootStep.definition.classMap.mapping[det.class];
4682
+ if (rootStep.effectiveClassMap) {
4683
+ const mapped = rootStep.effectiveClassMap.mapping[det.class];
4653
4684
  if (mapped) {
4654
4685
  mutable.originalClass = det.class;
4655
4686
  mutable.macroClass = mapped;
4656
- } else if (!rootStep.definition.classMap.preserveOriginal) {
4687
+ } else if (!rootStep.effectiveClassMap.preserveOriginal) {
4657
4688
  (discarded ??= []).push({
4658
4689
  bbox: [
4659
4690
  mutable.bbox[0],
@@ -4735,7 +4766,7 @@ var PipelineExecutor = class {
4735
4766
  }
4736
4767
  });
4737
4768
  try {
4738
- await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
4769
+ await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
4739
4770
  } catch (err) {
4740
4771
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
4741
4772
  tags: { deviceId },
@@ -5155,6 +5186,59 @@ function capitalize(s) {
5155
5186
  return s.charAt(0).toUpperCase() + s.slice(1);
5156
5187
  }
5157
5188
  //#endregion
5189
+ //#region src/session-decode/frame-view-payload.ts
5190
+ var FRAME_VIEW_PAYLOAD_POLICY = "jpeg";
5191
+ //#endregion
5192
+ //#region src/detection-pipeline/root-frame-view.ts
5193
+ /** Resolve the pre-padding content requested from the decode worker for one root. */
5194
+ function resolveRootFrameViewSpec(model, source, crop, format = FRAME_VIEW_PAYLOAD_POLICY) {
5195
+ const sourceWidth = crop?.width ?? source.width;
5196
+ const sourceHeight = crop?.height ?? source.height;
5197
+ if (model.preprocessMode !== "letterbox") return {
5198
+ ...crop ? { crop } : {},
5199
+ content: {
5200
+ width: model.inputSize.width,
5201
+ height: model.inputSize.height
5202
+ },
5203
+ fit: "stretch",
5204
+ format
5205
+ };
5206
+ const scale = Math.min(model.inputSize.width / sourceWidth, model.inputSize.height / sourceHeight);
5207
+ return {
5208
+ ...crop ? { crop } : {},
5209
+ content: {
5210
+ width: Math.max(1, Math.round(sourceWidth * scale)),
5211
+ height: Math.max(1, Math.round(sourceHeight * scale))
5212
+ },
5213
+ fit: "contain",
5214
+ format
5215
+ };
5216
+ }
5217
+ //#endregion
5218
+ //#region src/detection-pipeline/frame-ref-view-provider.ts
5219
+ var LocalFrameRefMissError = class extends Error {
5220
+ reason;
5221
+ constructor(reason) {
5222
+ super(`local frameRef miss: ${reason}`);
5223
+ this.reason = reason;
5224
+ this.name = "LocalFrameRefMissError";
5225
+ }
5226
+ };
5227
+ /** Bind one registry lease for all heterogeneous roots of a pipeline invocation. */
5228
+ function createRootFrameViewResolver(registry, ref) {
5229
+ const lease = registry.acquire(ref);
5230
+ if (!lease) throw new LocalFrameRefMissError(ref.registryId === registry.registryId ? "stale-ref" : "foreign-registry");
5231
+ const resolve = async (root, crop) => {
5232
+ const model = root.definition.models.find((candidate) => candidate.id === root.modelId);
5233
+ if (!model) throw new Error(`frame view model not found: ${root.modelId}`);
5234
+ const spec = resolveRootFrameViewSpec(model, ref, crop);
5235
+ const result = await lease.resolve(spec);
5236
+ if (result.kind === "miss") throw new LocalFrameRefMissError(result.reason);
5237
+ return result;
5238
+ };
5239
+ return Object.assign(resolve, { release: (reason) => lease.release(reason) });
5240
+ }
5241
+ //#endregion
5158
5242
  //#region src/detection-pipeline/pipeline/native-crop-compose.ts
5159
5243
  /** Clamp `v` into `[lo, hi]`. */
5160
5244
  function clamp(v, lo, hi) {
@@ -5194,23 +5278,32 @@ function composeCropRoiToFrameNorm(roi, cropFrameSpace, frameWidth, frameHeight)
5194
5278
  //#endregion
5195
5279
  //#region src/detection-pipeline/pipeline/tree-builder.ts
5196
5280
  /**
5281
+ * Effective class map for `(definition, modelId)` without writing back to the
5282
+ * shared StepDefinition. Custom models missing from `definition.models` are
5283
+ * resolved through the optional registry callback.
5284
+ */
5285
+ function resolveEffectiveClassMap(definition, modelId, resolveModel) {
5286
+ return (definition.models.find((model) => model.id === modelId) ?? resolveModel?.(definition.id, modelId))?.classMap ?? definition.classMap;
5287
+ }
5288
+ /**
5197
5289
  * Build an executable tree from user config.
5198
5290
  *
5199
5291
  * @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
5200
5292
  * @param getEngine - Function that returns an IInferenceEngine for a step ID.
5201
5293
  * Throws if step not loaded.
5202
5294
  */
5203
- function buildExecutableTree(steps, getEngine) {
5204
- return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine)) };
5295
+ function buildExecutableTree(steps, getEngine, resolveModel) {
5296
+ return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine, resolveModel)) };
5205
5297
  }
5206
- function buildNode(step, getEngine) {
5298
+ function buildNode(step, getEngine, resolveModel) {
5207
5299
  const definition = require_event_loop_stall_monitor.getStepDefinition(step.addonId);
5208
5300
  const engine = getEngine(step.addonId);
5209
- const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildNode(c, getEngine));
5301
+ const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildNode(c, getEngine, resolveModel));
5210
5302
  const mergedSettings = {
5211
5303
  ...collectSchemaDefaults(step.addonId),
5212
5304
  ...step.settings
5213
5305
  };
5306
+ const effectiveClassMap = resolveEffectiveClassMap(definition, step.modelId, resolveModel);
5214
5307
  return {
5215
5308
  stepId: step.addonId,
5216
5309
  definition,
@@ -5219,7 +5312,8 @@ function buildNode(step, getEngine) {
5219
5312
  inputClasses: definition.inputClasses ?? [],
5220
5313
  enabled: step.enabled,
5221
5314
  children,
5222
- ...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {}
5315
+ ...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {},
5316
+ ...effectiveClassMap !== void 0 ? { effectiveClassMap } : {}
5223
5317
  };
5224
5318
  }
5225
5319
  /**
@@ -7098,17 +7192,30 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7098
7192
  let jpegProvider;
7099
7193
  const sources = [
7100
7194
  input.frame ? "frame" : null,
7195
+ input.frameRef ? "frameRef" : null,
7101
7196
  input.frameHandle ? "frameHandle" : null,
7102
7197
  input.image ? "image" : null,
7103
7198
  input.imageBase64 ? "imageBase64" : null,
7104
7199
  input.referenceImage ? "referenceImage" : null
7105
7200
  ].filter((s) => s !== null);
7106
- if (sources.length === 0) throw new Error("runPipeline requires exactly one of: frame, frameHandle, image, imageBase64, referenceImage");
7201
+ if (sources.length === 0) throw new Error("runPipeline requires exactly one of: frame, frameRef, frameHandle, image, imageBase64, referenceImage");
7107
7202
  if (sources.length > 1) throw new Error(`runPipeline received conflicting image sources: ${sources.join(", ")}`);
7108
7203
  if (input.frameHandle) return this.emptyFrameHandleResult(input);
7109
7204
  const runtimeFrame = input.frame ?? void 0;
7205
+ const runtimeFrameRef = input.frameRef;
7110
7206
  const decodeT0 = performance.now();
7111
- if (runtimeFrame) {
7207
+ if (runtimeFrameRef) {
7208
+ imageWidth = runtimeFrameRef.width;
7209
+ imageHeight = runtimeFrameRef.height;
7210
+ rootInput = {
7211
+ kind: "jpeg",
7212
+ data: Buffer.alloc(0)
7213
+ };
7214
+ jpegProvider = async () => {
7215
+ throw new Error("frameRef root view was requested before model resolution");
7216
+ };
7217
+ emit(`FrameRef: ${imageWidth}×${imageHeight} (${runtimeFrameRef.format})`);
7218
+ } else if (runtimeFrame) {
7112
7219
  const frame = runtimeFrame;
7113
7220
  imageWidth = frame.width;
7114
7221
  imageHeight = frame.height;
@@ -7308,9 +7415,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7308
7415
  emit(`All models loaded`);
7309
7416
  }
7310
7417
  emit("Running inference...");
7311
- const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId));
7418
+ const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
7312
7419
  setupMs = performance.now() - wallT0 - decodeMs;
7313
- const isRuntime = Boolean(runtimeFrame);
7420
+ const isRuntime = Boolean(runtimeFrame || runtimeFrameRef);
7314
7421
  const effectiveDeviceId = input.deviceId ?? 0;
7315
7422
  const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
7316
7423
  const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
@@ -7321,10 +7428,42 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7321
7428
  const proxy = this.deviceProxies.get(effectiveDeviceId);
7322
7429
  if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
7323
7430
  }
7324
- const { result, trace } = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
7325
- traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
7326
- plane: input.plane
7327
- }, nativeCropProvider, cropZoneBbox);
7431
+ let frameViewResolver;
7432
+ let rootInputViewProvider;
7433
+ if (runtimeFrameRef) {
7434
+ frameViewResolver = createRootFrameViewResolver(require_event_loop_stall_monitor.localFrameRegistry, runtimeFrameRef);
7435
+ rootInputViewProvider = async (root, crop) => {
7436
+ const view = await frameViewResolver(root, crop ? {
7437
+ left: crop[0],
7438
+ top: crop[1],
7439
+ width: crop[2] - crop[0],
7440
+ height: crop[3] - crop[1]
7441
+ } : void 0);
7442
+ const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
7443
+ return {
7444
+ input: {
7445
+ kind: "jpeg",
7446
+ data
7447
+ },
7448
+ jpegProvider: async () => data,
7449
+ width: view.width,
7450
+ height: view.height,
7451
+ geometry: view.geometry
7452
+ };
7453
+ };
7454
+ }
7455
+ let executionSucceeded = false;
7456
+ let execution;
7457
+ try {
7458
+ execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
7459
+ traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
7460
+ plane: input.plane
7461
+ }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
7462
+ executionSucceeded = true;
7463
+ } finally {
7464
+ frameViewResolver?.release(executionSucceeded ? "success" : "error");
7465
+ }
7466
+ const { result, trace } = execution;
7328
7467
  if (isRuntime) {
7329
7468
  if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
7330
7469
  type: "device",