@camstack/addon-pipeline 1.2.328 → 1.2.330

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 (41) hide show
  1. package/THIRD_PARTY_MODELS.md +2 -2
  2. package/dist/audio-analyzer/index.js +2 -2
  3. package/dist/audio-analyzer/index.mjs +2 -2
  4. package/dist/{default-detection-model-1SadEKsW.js → default-detection-model-B6gKGnfx.js} +53 -71
  5. package/dist/{default-detection-model-CMeVTcPZ.mjs → default-detection-model-CPDT_eFT.mjs} +53 -71
  6. package/dist/detection-pipeline/index.js +607 -39
  7. package/dist/detection-pipeline/index.mjs +607 -39
  8. package/dist/{dist-wJO7mXeO.js → dist-CMeICL07.js} +781 -465
  9. package/dist/{dist-Bpub2VcJ.mjs → dist-CuPEyZY2.mjs} +736 -438
  10. package/dist/motion-wasm/index.js +1 -1
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-BjCSgD4M.mjs → node-USq5jYwq.mjs} +1 -1
  13. package/dist/{node-BCrqaejt.js → node-X_0Tka7-.js} +1 -1
  14. package/dist/pipeline-runner/index.js +355 -104
  15. package/dist/pipeline-runner/index.mjs +355 -104
  16. package/dist/{process-memory-BaKQ8u2G.mjs → process-memory-07XYts8V.mjs} +1 -1
  17. package/dist/{process-memory-iUJuTpXs.js → process-memory-o4LFWbBN.js} +1 -1
  18. package/dist/recorder/index.js +7 -7
  19. package/dist/recorder/index.mjs +7 -7
  20. package/dist/{segment-demux-js-C5ez9LYG.mjs → segment-demux-js-Bq8DrH0b.mjs} +2 -2
  21. package/dist/{segment-demux-js-Opx6Hv9G.js → segment-demux-js-FqaQjG7n.js} +2 -2
  22. package/dist/stream-broker/_stub.js +2 -2
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B94LgsV0.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-EYRUYv_r.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-BnP7C_HO.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-CR0--kRv.mjs +26 -0
  26. package/dist/stream-broker/demux-worker-child.js +1 -1
  27. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  28. package/dist/stream-broker/{hostInit-xOx7AvgW.mjs → hostInit-iGdBpvGc.mjs} +3 -3
  29. package/dist/stream-broker/index.js +4 -4
  30. package/dist/stream-broker/index.mjs +4 -4
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/package.json +1 -1
  33. package/python/edgetpu_dequant.py +38 -1
  34. package/python/inference_pool.py +31 -17
  35. package/python/postprocessors/test_yolox_plate_edgetpu.py +126 -0
  36. package/python/postprocessors/test_yunet_edgetpu.py +149 -0
  37. package/python/postprocessors/testdata/yolox_plate_int8_outputs.json +1 -0
  38. package/python/postprocessors/testdata/yunet_int8_outputs.json +1 -0
  39. package/python/postprocessors/yolo.py +44 -0
  40. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BPWYf4_O.mjs +0 -26
  41. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BpXPNINd.mjs +0 -26
@@ -3,9 +3,9 @@ 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-wJO7mXeO.js");
7
- const require_node = require("../node-BCrqaejt.js");
8
- const require_default_detection_model = require("../default-detection-model-1SadEKsW.js");
6
+ const require_dist = require("../dist-CMeICL07.js");
7
+ const require_node = require("../node-X_0Tka7-.js");
8
+ const require_default_detection_model = require("../default-detection-model-B6gKGnfx.js");
9
9
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
10
10
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
11
11
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
@@ -1976,33 +1976,34 @@ function onboardRootDetections(slot, frame, gate) {
1976
1976
  };
1977
1977
  }
1978
1978
  //#endregion
1979
- //#region src/pipeline-runner/label-link.ts
1979
+ //#region src/pipeline-runner/root-supply.ts
1980
1980
  /**
1981
- * The runner's half of the detector ↔ step label link (D715).
1981
+ * The runner's half of root-model supply (D715, D730).
1982
1982
  *
1983
1983
  * The runner holds the camera's WHOLE step tree (the attach config), so it is
1984
- * the one place that can answer "who writes this effect?" for a camera: the
1985
- * root model's declaration (`fillsClassifierEffects`) against the filled
1986
- * step's own `labelLink` policy and class list (`resolveLabelLink`). Three
1987
- * consumers read that one answer:
1988
- *
1989
- * - the frame result — a linked label the executor wrote at score 0 is raised
1990
- * to the detection's score when the detector is authoritative;
1991
- * - the `detailSteps` announce — a skipped classifier is announced with
1992
- * `filledBy`, so post-analysis does not dispatch it for a subject the
1993
- * detector already named;
1994
- * - `package-detection` — when the root fills it, the package root is not
1995
- * dispatched at all and the root's own `package` boxes ARE the package
1996
- * result (the operator's package threshold moves onto the root's gate, and
1997
- * the frame says it looked).
1984
+ * the one place that can answer "does this camera use the root's output for
1985
+ * this step?": the root model's declaration (`rootSuppliesOf`) against the
1986
+ * step's own `useRootModel` flag, through the one decision `resolveRootSupply`.
1987
+ * Four consumers read that answer:
1988
+ *
1989
+ * - linked labels — effective ⇒ raised to the detection's score (the root is
1990
+ * the one writer), or DROPPED when the step's class list switched that
1991
+ * class off (the detection stays at its macro); otherwise the score-0
1992
+ * fallback the executor wrote;
1993
+ * - root child boxes — a `plate` / `face` detail the root drew is kept only
1994
+ * when its crop step's supply is effective;
1995
+ * - the `detailSteps` announce — an effective step carries `filledBy` +
1996
+ * `filledAs`, so post-analysis does not infer it;
1997
+ * - `package-detection` — effective ⇒ the package root is not dispatched and
1998
+ * the root's own `package` boxes ARE the package result.
1998
1999
  *
1999
2000
  * Pure: the caller passes the model resolver; no I/O, no clock.
2000
2001
  */
2001
- /** The root step the fills are read from — the always-on camera root. */
2002
- var LABEL_LINK_ROOT_STEP_ID = "object-detection";
2002
+ /** The root step the supplies are read from — the always-on camera root. */
2003
+ var ROOT_SUPPLY_ROOT_STEP_ID = "object-detection";
2003
2004
  var PACKAGE_STEP_ID = "package-detection";
2004
2005
  var PACKAGE_THRESHOLD_KEY = "minConfidencePackage";
2005
- var EMPTY_LABEL_LINK_PLAN = { decisions: /* @__PURE__ */ new Map() };
2006
+ var EMPTY_ROOT_SUPPLY_PLAN = { states: /* @__PURE__ */ new Map() };
2006
2007
  function findStep(steps, addonId) {
2007
2008
  for (const step of steps) {
2008
2009
  if (step.addonId === addonId) return step;
@@ -2011,70 +2012,108 @@ function findStep(steps, addonId) {
2011
2012
  }
2012
2013
  }
2013
2014
  /**
2014
- * Plan a camera's label links from its configured tree. A root with no pinned
2015
- * model runs the static default — a COCO model, which fills nothing.
2015
+ * Plan a camera's root supplies from its configured tree. A root with no
2016
+ * pinned model runs the step's default — a COCO model, which supplies the
2017
+ * animal and vehicle classifiers like any other root.
2016
2018
  */
2017
- function planLabelLinks(steps, resolveModel) {
2018
- if (steps === void 0) return EMPTY_LABEL_LINK_PLAN;
2019
+ function planRootSupplies(steps, resolveModel) {
2020
+ if (steps === void 0) return EMPTY_ROOT_SUPPLY_PLAN;
2019
2021
  const root = steps.find((s) => s.addonId === "object-detection" && s.enabled !== false);
2020
- if (root?.modelId === void 0) return EMPTY_LABEL_LINK_PLAN;
2021
- const fills = resolveModel("object-detection", root.modelId)?.fillsClassifierEffects ?? [];
2022
- if (fills.length === 0) return {
2023
- rootModelId: root.modelId,
2024
- decisions: /* @__PURE__ */ new Map()
2025
- };
2026
- const decisions = /* @__PURE__ */ new Map();
2027
- for (const fill of fills) {
2028
- const step = findStep(steps, fill.stepId);
2029
- decisions.set(fill.stepId, require_dist.resolveLabelLink(fill, step === void 0 ? void 0 : {
2030
- enabled: step.enabled !== false,
2031
- ...step.settings !== void 0 ? { settings: step.settings } : {}
2032
- }));
2022
+ if (root === void 0) return EMPTY_ROOT_SUPPLY_PLAN;
2023
+ const definition = require_default_detection_model.getStepDefinition(ROOT_SUPPLY_ROOT_STEP_ID);
2024
+ const rootModelId = root.modelId ?? definition.defaultModelId;
2025
+ const supplies = require_dist.rootSuppliesOf(resolveModel(ROOT_SUPPLY_ROOT_STEP_ID, rootModelId), {
2026
+ labels: definition.labels ?? [],
2027
+ ...definition.classMap !== void 0 ? { classMap: definition.classMap } : {}
2028
+ });
2029
+ const states = /* @__PURE__ */ new Map();
2030
+ for (const stepId of require_dist.ROOT_SUPPLIABLE_STEP_IDS) {
2031
+ const settings = findStep(steps, stepId)?.settings;
2032
+ states.set(stepId, {
2033
+ ...require_dist.resolveRootSupply(stepId, settings, supplies),
2034
+ ...settings !== void 0 ? { settings } : {}
2035
+ });
2033
2036
  }
2034
2037
  return {
2035
- rootModelId: root.modelId,
2036
- decisions
2038
+ rootModelId,
2039
+ states
2037
2040
  };
2038
2041
  }
2039
- /** Steps this plan says must not be dispatched, with the model that fills them. */
2040
- function filledStepIds(plan) {
2042
+ /** The steps whose supply is effective, with the model and the kind. */
2043
+ function suppliedSteps(plan) {
2041
2044
  const out = /* @__PURE__ */ new Map();
2042
2045
  if (plan.rootModelId === void 0) return out;
2043
- for (const [stepId, decision] of plan.decisions) if (decision.skip) out.set(stepId, plan.rootModelId);
2046
+ for (const [stepId, state] of plan.states) if (state.effective && state.supply !== void 0) out.set(stepId, {
2047
+ modelId: plan.rootModelId,
2048
+ kind: state.supply.kind
2049
+ });
2044
2050
  return out;
2045
2051
  }
2046
- function raiseLinkedLabels(detection, plan) {
2052
+ /** The steps whose supply just BECAME effective between two plans. */
2053
+ function newlySuppliedSteps(previous, next) {
2054
+ const out = [];
2055
+ for (const [stepId, state] of next.states) if (state.effective && previous?.states.get(stepId)?.effective !== true) out.push(stepId);
2056
+ return out;
2057
+ }
2058
+ function authoritativeLabels(detection, plan) {
2047
2059
  let changed = false;
2048
- const labels = detection.labels.map((label) => {
2060
+ const labels = [];
2061
+ for (const label of detection.labels) {
2049
2062
  const stepId = require_dist.linkedStepOf(label.stepId);
2050
- if (stepId === void 0 || plan.decisions.get(stepId)?.authoritative !== true) return label;
2051
- if (label.score === detection.score) return label;
2063
+ const state = stepId !== void 0 ? plan.states.get(stepId) : void 0;
2064
+ if (state === void 0 || !state.effective) {
2065
+ labels.push(label);
2066
+ continue;
2067
+ }
2068
+ if (!require_dist.allowedSuppliedEffect(label.label, state.settings)) {
2069
+ changed = true;
2070
+ continue;
2071
+ }
2072
+ if (label.score === detection.score) {
2073
+ labels.push(label);
2074
+ continue;
2075
+ }
2052
2076
  changed = true;
2053
- return {
2077
+ labels.push({
2054
2078
  ...label,
2055
2079
  score: detection.score
2056
- };
2057
- });
2080
+ });
2081
+ }
2058
2082
  return changed ? labels : void 0;
2059
2083
  }
2084
+ /** Whether a root child box survives: only under an effective crop supply. */
2085
+ function keepsDetection(detection, plan) {
2086
+ const supplied = detection.rootSupplied;
2087
+ if (supplied === void 0 || detection.kind !== "detail") return true;
2088
+ const stepId = require_dist.ROOT_SUPPLIABLE_STEP_IDS.find((id) => id === supplied.stepId);
2089
+ return stepId !== void 0 && plan.states.get(stepId)?.effective === true;
2090
+ }
2060
2091
  /**
2061
- * Raise each linked label to its detection's score where the detector is the
2062
- * authoritative writer of that effect. Every other linked label stays the
2063
- * score-0 fallback the executor wrote. Never mutates; returns the input when
2064
- * nothing changes.
2092
+ * Apply the plan to a frame: linked labels of an effective step become
2093
+ * authoritative (or are dropped by its class list), and root child boxes of a
2094
+ * crop step that is not effective are removed. Never mutates; returns the
2095
+ * input when nothing changes.
2065
2096
  */
2066
- function applyLabelLinkAuthority(result, plan) {
2067
- if (plan.decisions.size === 0) return result;
2097
+ function applyRootSupplyAuthority(result, plan) {
2098
+ if (plan.states.size === 0) return result;
2068
2099
  let changed = false;
2069
- const detections = result.detections.map((d) => {
2070
- const labels = raiseLinkedLabels(d, plan);
2071
- if (labels === void 0) return d;
2100
+ const detections = [];
2101
+ for (const d of result.detections) {
2102
+ if (!keepsDetection(d, plan)) {
2103
+ changed = true;
2104
+ continue;
2105
+ }
2106
+ const labels = authoritativeLabels(d, plan);
2107
+ if (labels === void 0) {
2108
+ detections.push(d);
2109
+ continue;
2110
+ }
2072
2111
  changed = true;
2073
- return {
2112
+ detections.push({
2074
2113
  ...d,
2075
2114
  labels
2076
- };
2077
- });
2115
+ });
2116
+ }
2078
2117
  return changed ? {
2079
2118
  ...result,
2080
2119
  detections
@@ -2082,7 +2121,7 @@ function applyLabelLinkAuthority(result, plan) {
2082
2121
  }
2083
2122
  /** The root step's schema default for its class allow-list. */
2084
2123
  function rootDefaultMacroClasses() {
2085
- const field = require_default_detection_model.getStep(LABEL_LINK_ROOT_STEP_ID).getConfigSchema().find((f) => "key" in f && f.key === "enabledMacroClasses");
2124
+ const field = require_default_detection_model.getStep(ROOT_SUPPLY_ROOT_STEP_ID).getConfigSchema().find((f) => "key" in f && f.key === "enabledMacroClasses");
2086
2125
  const value = field !== void 0 && "default" in field ? field.default : void 0;
2087
2126
  return Array.isArray(value) ? value : [];
2088
2127
  }
@@ -2103,18 +2142,19 @@ function withPackageAdmitted(root, packageStep) {
2103
2142
  };
2104
2143
  }
2105
2144
  /**
2106
- * When the root fills `package-detection` and that step's policy lets it be
2107
- * skipped, drop the package root from the dispatch and let the camera root
2108
- * report packages: its gate admits `package` at the package step's threshold.
2109
- * Otherwise the tree is returned untouched.
2145
+ * When the root's `package` supply is effective for this camera, drop the
2146
+ * package root from the dispatch and let the camera root report packages: its
2147
+ * gate admits `package` at the package step's threshold. Otherwise (the root
2148
+ * declares no `package`, or the flag is off) the tree is returned untouched
2149
+ * and the package step runs its own model.
2110
2150
  */
2111
2151
  function applyPackageFill(steps, plan) {
2112
- if (plan.decisions.get(PACKAGE_STEP_ID)?.skip !== true) return {
2152
+ if (plan.states.get(PACKAGE_STEP_ID)?.effective !== true) return {
2113
2153
  steps,
2114
2154
  filledByRoot: false
2115
2155
  };
2116
2156
  const packageStep = steps.find((s) => s.addonId === PACKAGE_STEP_ID);
2117
- if (packageStep === void 0) return {
2157
+ if (packageStep === void 0 || packageStep.enabled === false) return {
2118
2158
  steps,
2119
2159
  filledByRoot: false
2120
2160
  };
@@ -3296,6 +3336,150 @@ var FaceOutsideParentLog = class {
3296
3336
  }
3297
3337
  };
3298
3338
  //#endregion
3339
+ //#region src/pipeline-runner/root-face-refine.ts
3340
+ /**
3341
+ * Root face refinement (D731) — how a ROOT model's `face` box becomes a face
3342
+ * the recogniser can use, without ever embedding an unaligned crop.
3343
+ *
3344
+ * A root face box has no landmarks. It is used as a LOCATION only:
3345
+ *
3346
+ * 1. one face-scoped square around it (`deriveRootFaceRefineRect`, box ×1.6)
3347
+ * is cut from the frame's NATIVE surface — the native lease or nothing;
3348
+ * 2. the square is shrunk to {@link ROOT_FACE_REFINE_INPUT_PX} and laid in the
3349
+ * top-left of a black canvas the size of the face detector's input, so the
3350
+ * detector runs at an effective 160 px input on its own 640 graph — the
3351
+ * network is convolutional, the black margin contains nothing, and no 160 px
3352
+ * build of the model is needed. The canvas maps back to the frame through
3353
+ * a "virtual" frame rectangle 640/160 times the square, so every existing
3354
+ * crop→frame mapping (results, native child re-cuts) stays exact;
3355
+ * 3. the detector's best face in the square is kept — its landmarks come from
3356
+ * the refinement input, where the face is large, which is the space the
3357
+ * landmark-precision gate then measures it in;
3358
+ * 4. that face is handed back to the SAME step as its output
3359
+ * (`suppliedRoot`), so the embedder child aligns it through the existing
3360
+ * native-crop alignment, exactly like a detected face.
3361
+ *
3362
+ * No face in the square ⇒ nothing is embedded, and the drop is logged.
3363
+ */
3364
+ /** The face detector's input side for the step as it will run. */
3365
+ function detectorInputPx(step) {
3366
+ const def = require_default_detection_model.getStepDefinition(step.addonId);
3367
+ const modelId = step.modelId ?? def.defaultModelId;
3368
+ return def.models.find((m) => m.id === modelId)?.inputSize.width ?? 160;
3369
+ }
3370
+ /** Lay the refinement square, shrunk to the refine input, on a black canvas. */
3371
+ async function buildRefineCanvas(squareJpeg, canvasPx) {
3372
+ const sharp = await require_lazy_sharp.getSharp();
3373
+ const small = await sharp(squareJpeg).resize(160, 160, { fit: "fill" }).toBuffer();
3374
+ const pad = Math.max(0, canvasPx - 160);
3375
+ return sharp(small).extend({
3376
+ top: 0,
3377
+ left: 0,
3378
+ bottom: pad,
3379
+ right: pad,
3380
+ background: {
3381
+ r: 0,
3382
+ g: 0,
3383
+ b: 0
3384
+ }
3385
+ }).jpeg({ quality: 92 }).toBuffer();
3386
+ }
3387
+ /** The best face of a refinement pass, or `undefined` when it found none. */
3388
+ function bestFace(detections) {
3389
+ return detections.filter((d) => d.macroClass === "face").reduce((best, d) => best === void 0 || d.score > best.score ? d : best, void 0);
3390
+ }
3391
+ /** The canvas-space face as the step's own output for the second pass. */
3392
+ function refinedAsSupplied(face, stepId, modelId) {
3393
+ return {
3394
+ stepId,
3395
+ modelId,
3396
+ detections: [{
3397
+ bbox: [
3398
+ face.bbox.x,
3399
+ face.bbox.y,
3400
+ face.bbox.x + face.bbox.width,
3401
+ face.bbox.y + face.bbox.height
3402
+ ],
3403
+ score: face.score,
3404
+ ...face.landmarks !== void 0 ? { landmarks: face.landmarks.map((p) => ({
3405
+ x: p.x,
3406
+ y: p.y
3407
+ })) } : {}
3408
+ }]
3409
+ };
3410
+ }
3411
+ /**
3412
+ * Refine the root's face box for one step and run the requested chain on the
3413
+ * face it finds. `null` ⇒ no pixels (the native lease is the only source);
3414
+ * an empty list ⇒ the square held no face. Every drop is logged.
3415
+ */
3416
+ async function refineRootFace(deps, input, supplied, step, requested, deviceKey) {
3417
+ const handle = input.frameHandle;
3418
+ const tags = { deviceId: input.deviceId };
3419
+ if (handle === void 0) {
3420
+ deps.logger.warn("root face refinement needs the native frame — no frame handle, dropped", {
3421
+ tags,
3422
+ meta: { stepId: step.addonId }
3423
+ });
3424
+ return null;
3425
+ }
3426
+ const box = supplied.boxes.reduce((best, b) => b.score > best.score ? b : best);
3427
+ const square = require_dist.deriveRootFaceRefineRect(box.bbox, handle.width, handle.height);
3428
+ const native = await deps.getNativeCrop(input.deviceId, handle, normalizeRect(square, handle.width, handle.height));
3429
+ if (native === null || isNativeCropMiss(native)) {
3430
+ deps.logger.warn("root face refinement: the native square missed — dropped", {
3431
+ tags,
3432
+ meta: {
3433
+ stepId: step.addonId,
3434
+ nativeMissReason: native === null ? null : native.miss
3435
+ }
3436
+ });
3437
+ return null;
3438
+ }
3439
+ const canvasPx = detectorInputPx(step);
3440
+ const scale = canvasPx / 160;
3441
+ const virtualFrameSpace = {
3442
+ x: native.frameSpace.x,
3443
+ y: native.frameSpace.y,
3444
+ w: native.frameSpace.w * scale,
3445
+ h: native.frameSpace.h * scale
3446
+ };
3447
+ const crop = {
3448
+ jpeg: await buildRefineCanvas(native.jpeg, canvasPx),
3449
+ width: canvasPx,
3450
+ height: canvasPx,
3451
+ frameSpace: virtualFrameSpace,
3452
+ source: "native"
3453
+ };
3454
+ const nativeCropRef = {
3455
+ handle,
3456
+ cropFrameSpace: virtualFrameSpace
3457
+ };
3458
+ const detectorOnly = {
3459
+ ...step,
3460
+ children: []
3461
+ };
3462
+ const found = await deps.runPipeline([detectorOnly], crop.jpeg, input.deviceId, deviceKey, nativeCropRef);
3463
+ const face = found === null ? void 0 : bestFace(found.detections);
3464
+ if (face === void 0) {
3465
+ deps.logger.warn("root face refinement found no face in the square — dropped, never embedded unaligned", {
3466
+ tags,
3467
+ meta: {
3468
+ stepId: step.addonId,
3469
+ rootModelId: supplied.modelId,
3470
+ rootScore: box.score
3471
+ }
3472
+ });
3473
+ return [];
3474
+ }
3475
+ const chain = requested === void 0 ? step : pruneChildStepsToRequested(step, requested);
3476
+ if (chain.children === void 0 || chain.children.length === 0) return [toDetailResult(face, step.addonId, crop)];
3477
+ const modelId = step.modelId ?? require_default_detection_model.getStepDefinition(step.addonId).defaultModelId;
3478
+ const embedded = await deps.runPipeline([chain], crop.jpeg, input.deviceId, deviceKey, nativeCropRef, refinedAsSupplied(face, step.addonId, modelId));
3479
+ if (embedded === null) return [toDetailResult(face, step.addonId, crop)];
3480
+ return embedded.detections.map((d) => toDetailResult(d, step.addonId, crop));
3481
+ }
3482
+ //#endregion
3299
3483
  //#region src/pipeline-runner/detail-subtree.ts
3300
3484
  /** Did the subtree run refuse because the camera is not attached (park lapsed)? */
3301
3485
  function isDetailNotAttachedRefusal(outcome) {
@@ -3388,6 +3572,43 @@ function mapBboxToFrameSpace(bbox, crop) {
3388
3572
  h: bbox.height * scaleY
3389
3573
  };
3390
3574
  }
3575
+ /**
3576
+ * Map the root's FRAME-space boxes (D730) into the crop's pixels — the inverse
3577
+ * of {@link mapBboxToFrameSpace} — clamped to the crop. A box with nothing left
3578
+ * inside the crop is dropped. `null` when no box survives.
3579
+ */
3580
+ function suppliedRootForCrop(supplied, crop) {
3581
+ const scaleX = crop.width / crop.frameSpace.w;
3582
+ const scaleY = crop.height / crop.frameSpace.h;
3583
+ const detections = [];
3584
+ for (const box of supplied.boxes) {
3585
+ const x1 = Math.max(0, (box.bbox.x - crop.frameSpace.x) * scaleX);
3586
+ const y1 = Math.max(0, (box.bbox.y - crop.frameSpace.y) * scaleY);
3587
+ const x2 = Math.min(crop.width, (box.bbox.x + box.bbox.w - crop.frameSpace.x) * scaleX);
3588
+ const y2 = Math.min(crop.height, (box.bbox.y + box.bbox.h - crop.frameSpace.y) * scaleY);
3589
+ if (x2 <= x1 || y2 <= y1) continue;
3590
+ detections.push({
3591
+ bbox: [
3592
+ x1,
3593
+ y1,
3594
+ x2,
3595
+ y2
3596
+ ],
3597
+ score: box.score
3598
+ });
3599
+ }
3600
+ const [first, ...rest] = detections;
3601
+ if (first === void 0) return null;
3602
+ return {
3603
+ stepId: supplied.stepId,
3604
+ modelId: supplied.modelId,
3605
+ detections: [first, ...rest]
3606
+ };
3607
+ }
3608
+ /** Whether the root's box for this step is a location to refine in (a face). */
3609
+ function refinesSuppliedStep(stepId) {
3610
+ return (stepId === "plate-detection" || stepId === "face-detection") && require_dist.CROP_SUPPLY_SOURCES[stepId].use === "refine";
3611
+ }
3391
3612
  /** Base64-encode a normalized float32 embedding vector. */
3392
3613
  function encodeEmbeddingBase64(embedding) {
3393
3614
  return Buffer.from(Float32Array.from(embedding).buffer).toString("base64");
@@ -3591,6 +3812,18 @@ async function runDetailSubtree(deps, input) {
3591
3812
  }
3592
3813
  const requested = input.steps;
3593
3814
  const steps = requested === void 0 ? matched : matched.map((step) => pruneChildStepsToRequested(step, requested));
3815
+ const supplied = input.supplied;
3816
+ if (supplied !== void 0 && refinesSuppliedStep(supplied.stepId)) {
3817
+ const details = [];
3818
+ for (const step of matched) {
3819
+ if (step.addonId !== supplied.stepId) continue;
3820
+ const placement = deps.placeStep?.(input.deviceId, step);
3821
+ const refined = await refineRootFace(deps, input, supplied, placement?.step ?? step, requested, placement?.deviceKey);
3822
+ if (refined === null) return null;
3823
+ details.push(...refined);
3824
+ }
3825
+ return { details };
3826
+ }
3594
3827
  const { crop, nativeMiss } = await resolveCrop(deps, input, deps.resolveCropConvention(input.deviceId));
3595
3828
  if (!crop) {
3596
3829
  deps.logger.warn("runDetailSubtree: no crop source resolved — the dispatch produced nothing", {
@@ -3619,7 +3852,18 @@ async function runDetailSubtree(deps, input) {
3619
3852
  let unjudgedFaces = 0;
3620
3853
  for (const step of steps) {
3621
3854
  const placement = deps.placeStep?.(input.deviceId, step);
3622
- const result = await deps.runPipeline([placement?.step ?? step], crop.jpeg, input.deviceId, placement?.deviceKey, nativeCropRef);
3855
+ const suppliedRoot = input.supplied !== void 0 && input.supplied.stepId === step.addonId ? suppliedRootForCrop(input.supplied, crop) : void 0;
3856
+ if (suppliedRoot === null) {
3857
+ deps.logger.warn("runDetailSubtree: the root-supplied boxes fall outside the crop — nothing to read", {
3858
+ tags: { deviceId: input.deviceId },
3859
+ meta: {
3860
+ stepId: step.addonId,
3861
+ parentClass: input.parent.className
3862
+ }
3863
+ });
3864
+ continue;
3865
+ }
3866
+ const result = await deps.runPipeline([placement?.step ?? step], crop.jpeg, input.deviceId, placement?.deviceKey, nativeCropRef, ...suppliedRoot !== void 0 ? [suppliedRoot] : []);
3623
3867
  if (!result) continue;
3624
3868
  for (const detection of result.detections) {
3625
3869
  const detail = toDetailResult(detection, step.addonId, crop);
@@ -7342,15 +7586,15 @@ function isDetailPlaneStepId(stepId) {
7342
7586
  * Extracted as a pure function so the unit suite can exercise the
7343
7587
  * derivation without instantiating the full addon.
7344
7588
  */
7345
- function deriveDetailSteps(steps, plan = EMPTY_LABEL_LINK_PLAN) {
7589
+ function deriveDetailSteps(steps, plan = EMPTY_ROOT_SUPPLY_PLAN) {
7346
7590
  const stepIds = /* @__PURE__ */ new Set();
7347
7591
  collectEnabledStepIds(steps, stepIds);
7348
- const filled = filledStepIds(plan);
7592
+ const supplied = suppliedSteps(plan);
7349
7593
  const announces = [];
7350
7594
  for (const stepId of stepIds) {
7351
7595
  const def = tryGetStepDefinition(stepId);
7352
7596
  if (!def || def.inputClasses === null) continue;
7353
- const filledBy = filled.get(def.id);
7597
+ const by = supplied.get(def.id);
7354
7598
  announces.push({
7355
7599
  stepId: def.id,
7356
7600
  inputClasses: def.inputClasses,
@@ -7358,7 +7602,10 @@ function deriveDetailSteps(steps, plan = EMPTY_LABEL_LINK_PLAN) {
7358
7602
  trigger: "once",
7359
7603
  maxPerTrack: 3
7360
7604
  },
7361
- ...filledBy !== void 0 ? { filledBy } : {}
7605
+ ...by !== void 0 ? {
7606
+ filledBy: by.modelId,
7607
+ filledAs: by.kind
7608
+ } : {}
7362
7609
  });
7363
7610
  }
7364
7611
  return announces;
@@ -8490,7 +8737,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8490
8737
  if (!ctx.api) throw new Error("runCapacityRamp: ctx.api unavailable");
8491
8738
  if (this.capacityRuns.get(input.sessionId)?.status.state === "running") throw new Error(`runCapacityRamp: session '${input.sessionId}' is already running`);
8492
8739
  for (const [id, entry] of this.capacityRuns) if (entry.status.state === "running") throw new Error(`runCapacityRamp: ramp '${id}' is already running on this node — one at a time, the measurement owns the node`);
8493
- if (input.nodeId !== void 0 && input.nodeId !== this.nodeId) throw new Error(`runCapacityRamp: driver is node-local — this runner is '${this.nodeId}', refusing nodeId '${input.nodeId}'. Frames must not cross a node boundary per call (D9).`);
8740
+ if (input.nodeId !== void 0 && input.nodeId !== this.nodeId) throw new Error(`runCapacityRamp: driver is node-local — this runner is '${this.nodeId}', refusing nodeId '${input.nodeId}'. Frames must not cross a node boundary per call.`);
8494
8741
  const nonIdle = (this.runner?.getAllCameraMetrics() ?? []).filter((m) => m.phase !== "idle");
8495
8742
  const contaminated = nonIdle.length > 0;
8496
8743
  if (contaminated && input.allowActiveCameras !== true) throw new Error(`runCapacityRamp: node not quiet — ${nonIdle.length} camera(s) not idle (deviceIds ${nonIdle.map((m) => m.deviceId).join(", ")}). Pause detection first (scripts/capacity/pause-restore.mjs) or pass allowActiveCameras: true to run a knowingly contaminated measurement.`);
@@ -9540,7 +9787,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
9540
9787
  const outcome = await runDetailSubtree({
9541
9788
  resolveChildSteps: (deviceId, parentClassName, filter) => this.resolveDetailChildSteps(deviceId, parentClassName, filter),
9542
9789
  getNativeCrop: (deviceId, handle, paddedBbox) => this.getNativeCropForDetail(deviceId, handle, paddedBbox),
9543
- runPipeline: (steps, imageJpeg, deviceId, deviceKey, nativeCropRef) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey, nativeCropRef),
9790
+ runPipeline: (steps, imageJpeg, deviceId, deviceKey, nativeCropRef, suppliedRoot) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey, nativeCropRef, suppliedRoot),
9544
9791
  placeStep: (deviceId, step) => this.placeDetailStepForCamera(deviceId, step),
9545
9792
  resolveCropConvention: () => convention,
9546
9793
  logger: this.detailSubtreeLogger(),
@@ -9675,39 +9922,42 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
9675
9922
  */
9676
9923
  customModels = new require_default_detection_model.CustomModelCatalog({ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras) });
9677
9924
  /**
9678
- * Per camera, the label-link plan (D715) and the announce derived from it,
9925
+ * Per camera, the root-supply plan (D730) and the announce derived from it,
9679
9926
  * keyed on the two inputs it reads: the attach tree and the custom-model
9680
9927
  * snapshot (a Frigate+ root is a custom model). Recomputed when either
9681
9928
  * changes identity — at most once per catalog refresh.
9682
9929
  */
9683
- labelLinkCache = /* @__PURE__ */ new Map();
9684
- /** The camera's label-link plan; logs once per change of what is filled. */
9685
- labelLinkPlanFor(deviceId, steps) {
9930
+ rootSupplyCache = /* @__PURE__ */ new Map();
9931
+ /**
9932
+ * The camera's root-supply plan. One INFO per (camera, step) when a step
9933
+ * BECOMES supplied by the root — a COCO root now stops dispatching the
9934
+ * animal and vehicle classifiers by default, and that must be readable per
9935
+ * camera rather than inferred from a missing dispatch (D391).
9936
+ */
9937
+ rootSupplyPlanFor(deviceId, steps) {
9686
9938
  const byStep = this.customModels.current();
9687
- const cached = this.labelLinkCache.get(deviceId);
9939
+ const cached = this.rootSupplyCache.get(deviceId);
9688
9940
  if (cached !== void 0 && cached.steps === steps && cached.byStep === byStep) return cached.plan;
9689
- const plan = planLabelLinks(steps, (stepId, modelId) => this.customModels.stepDefinition(stepId)?.models.find((m) => m.id === modelId));
9941
+ const plan = planRootSupplies(steps, (stepId, modelId) => this.customModels.stepDefinition(stepId)?.models.find((m) => m.id === modelId));
9690
9942
  const announces = deriveDetailSteps(steps, plan);
9691
- this.labelLinkCache.set(deviceId, {
9943
+ this.rootSupplyCache.set(deviceId, {
9692
9944
  steps,
9693
9945
  byStep,
9694
9946
  plan,
9695
9947
  announces
9696
9948
  });
9697
- const describe = (p) => p === void 0 ? "" : [...p.decisions].map(([id, d]) => `${id}:${d.skip ? "skip" : d.authoritative ? "sole" : "fallback"}`).join(",");
9698
- if (describe(cached?.plan) !== describe(plan) && plan.decisions.size > 0) this.ctxIfReady?.logger.info("label link: the root model fills pipeline steps", {
9699
- tags: { deviceId },
9700
- meta: {
9701
- rootModelId: plan.rootModelId ?? null,
9702
- steps: [...plan.decisions].map(([stepId, d]) => ({
9949
+ for (const stepId of newlySuppliedSteps(cached?.plan, plan)) {
9950
+ const state = plan.states.get(stepId);
9951
+ this.ctxIfReady?.logger.info("root model supplies a pipeline step — the step is not dispatched", {
9952
+ tags: { deviceId },
9953
+ meta: {
9703
9954
  stepId,
9704
- policy: d.policy,
9705
- authoritative: d.authoritative,
9706
- skip: d.skip,
9707
- openGaps: d.openGaps
9708
- }))
9709
- }
9710
- });
9955
+ rootModelId: plan.rootModelId ?? null,
9956
+ kind: state?.supply?.kind ?? null,
9957
+ uncovered: state?.supply?.kind === "label" ? state.supply.uncovered : []
9958
+ }
9959
+ });
9960
+ }
9711
9961
  return plan;
9712
9962
  }
9713
9963
  cropConvention = new CropConventionSource({ logger: {
@@ -9975,7 +10225,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
9975
10225
  };
9976
10226
  }
9977
10227
  /** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
9978
- async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey, nativeCropRef) {
10228
+ async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey, nativeCropRef, suppliedRoot) {
9979
10229
  const api = this.ctxIfReady?.api;
9980
10230
  if (!api) return null;
9981
10231
  const detailDeviceKey = stepDeviceKey ?? this.attached.get(deviceId)?.config?.deviceKey ?? this.freshParkedDetail(deviceId)?.deviceKey;
@@ -9986,7 +10236,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
9986
10236
  deviceId,
9987
10237
  ...detailDeviceKey ? { deviceKey: detailDeviceKey } : {},
9988
10238
  plane: "full",
9989
- ...nativeCropRef ? { nativeCropRef } : {}
10239
+ ...nativeCropRef ? { nativeCropRef } : {},
10240
+ ...suppliedRoot ? { suppliedRoot } : {}
9990
10241
  });
9991
10242
  } catch (err) {
9992
10243
  this.ctxIfReady?.logger.warn("runDetailSubtree: runPipeline failed", {
@@ -10105,7 +10356,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
10105
10356
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
10106
10357
  this.packageGovernor.forget(deviceId);
10107
10358
  this.packageGateSkips.delete(deviceId);
10108
- this.labelLinkCache.delete(deviceId);
10359
+ this.rootSupplyCache.delete(deviceId);
10109
10360
  this.packageZoneUnionByDevice.delete(deviceId);
10110
10361
  this.packageZoneUnionAt.delete(deviceId);
10111
10362
  this.lastMotionNearPackageZone.delete(deviceId);
@@ -10821,8 +11072,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
10821
11072
  return null;
10822
11073
  }
10823
11074
  if (steps.length === 0) return null;
10824
- const labelLinkPlan = this.labelLinkPlanFor(deviceId, steps);
10825
- const packageFill = applyPackageFill(steps, labelLinkPlan);
11075
+ const rootSupplyPlan = this.rootSupplyPlanFor(deviceId, steps);
11076
+ const packageFill = applyPackageFill(steps, rootSupplyPlan);
10826
11077
  let dispatchSteps = packageFill.steps;
10827
11078
  if (dispatchSteps.some((s) => s.addonId === "package-detection" && s.enabled !== false)) {
10828
11079
  this.refreshPackageZoneUnion(deviceId);
@@ -10911,7 +11162,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
10911
11162
  return null;
10912
11163
  }
10913
11164
  if (result === null) return null;
10914
- const looked = withPackageLook(applyLabelLinkAuthority(result, labelLinkPlan), dispatchSteps, packageFill.filledByRoot);
11165
+ const looked = withPackageLook(applyRootSupplyAuthority(result, rootSupplyPlan), dispatchSteps, packageFill.filledByRoot);
10915
11166
  return this.recoverSmallSubjects(deviceId, frame, looked, dispatchSteps, camConfig?.deviceKey);
10916
11167
  }
10917
11168
  /**
@@ -11608,7 +11859,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
11608
11859
  emitInferenceResult(deviceId, frame, result, handle, source, occasion) {
11609
11860
  if (!this.ctx?.eventBus) return;
11610
11861
  const capturedAt = frame.capturedAt;
11611
- const detailSteps = this.labelLinkCache.get(deviceId)?.announces ?? this.attached.get(deviceId)?.detailSteps;
11862
+ const detailSteps = this.rootSupplyCache.get(deviceId)?.announces ?? this.attached.get(deviceId)?.detailSteps;
11612
11863
  const payload = {
11613
11864
  deviceId,
11614
11865
  frame: result,