@camstack/addon-pipeline 1.1.64 → 1.1.66
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/dist/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +188 -26
- package/dist/detection-pipeline/index.mjs +188 -26
- package/dist/{dist-BzVZ2I4P.mjs → dist-BpWot5-w.mjs} +13 -55
- package/dist/{dist-B2jTt7Lq.js → dist-D5o9fGxq.js} +13 -55
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +88 -22
- package/dist/pipeline-runner/index.mjs +88 -22
- package/dist/recorder/index.js +141 -1
- package/dist/recorder/index.mjs +141 -1
- package/dist/session-decode/decode-worker-child.js +9 -3
- package/dist/session-decode/decode-worker-child.mjs +9 -3
- package/dist/{step-definitions-DXkY5Zor.js → step-definitions-81eYyJbW.js} +138 -8
- package/dist/{step-definitions-B07M9qhj.mjs → step-definitions-D6R9X0V7.mjs} +138 -8
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-dTMki_1D.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CIIIVPzB.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CPbLcza9.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-DlALQ_wp.mjs +26 -0
- package/dist/stream-broker/{hostInit-BY2oQAEX.mjs → hostInit-CeBNudbY.mjs} +2 -2
- package/dist/stream-broker/index.js +51 -3
- package/dist/stream-broker/index.mjs +51 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C03fx7Sp.js → MaskShapeCanvas-DI4BY7W2-CbBbcdZb.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BTxzn1rQ.js → MotionZonesSettings-NcxxQN8r-aA6zvms_.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-GKO8O7rC.js → PrivacyMaskSettings-APgPLF7p-CcGB7iAE.js} +1 -1
- package/embed-dist/assets/index-BRG_Fqb5.js +114 -0
- package/embed-dist/assets/index-D2pF9Z3W.css +2 -0
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +30 -0
- package/python/postprocessors/scrfd.py +4 -1
- package/python/postprocessors/softmax.py +17 -5
- package/python/postprocessors/test_softmax.py +52 -0
- package/python/postprocessors/test_yolo_nms.py +45 -0
- package/python/postprocessors/yolo.py +4 -1
- package/python/postprocessors/yolo_seg.py +4 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-bAh96WNb.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CV2WbUaO.mjs +0 -26
- package/embed-dist/assets/index-Bm9LaQPH.css +0 -2
- package/embed-dist/assets/index-QdssILAA.js +0 -81
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-D5o9fGxq.js");
|
|
7
7
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
8
8
|
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-Dv-oFwjN.js");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as errMsg, K as hydrateSchema, T as mapAudioLabelToMacro, V as BaseAddon, _ as audioAnalyzerCapability, g as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, u as HF_BASE_URL } from "../dist-
|
|
2
|
+
import { B as errMsg, K as hydrateSchema, T as mapAudioLabelToMacro, V as BaseAddon, _ as audioAnalyzerCapability, g as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, u as HF_BASE_URL } from "../dist-BpWot5-w.mjs";
|
|
3
3
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
4
4
|
import { i as downloadFile } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
5
5
|
import * as fs from "node:fs";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_step_definitions = require("../step-definitions-
|
|
6
|
+
const require_dist = require("../dist-D5o9fGxq.js");
|
|
7
|
+
const require_step_definitions = require("../step-definitions-81eYyJbW.js");
|
|
8
8
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
9
9
|
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-Dv-oFwjN.js");
|
|
10
10
|
let node_child_process = require("node:child_process");
|
|
@@ -316,7 +316,7 @@ function projectNodeEngine(store, nodeId) {
|
|
|
316
316
|
//#endregion
|
|
317
317
|
//#region src/detection-pipeline/default-detection-model.ts
|
|
318
318
|
/** The object-detection step id — the only slot this resolver applies to. */
|
|
319
|
-
var OBJECT_DETECTION_STEP_ID = "object-detection";
|
|
319
|
+
var OBJECT_DETECTION_STEP_ID$1 = "object-detection";
|
|
320
320
|
/**
|
|
321
321
|
* Balanced default object-detection model per accelerator class (measured on
|
|
322
322
|
* COCO val2017). `'cpu'` maps to `null` → the caller substitutes the step's
|
|
@@ -376,7 +376,7 @@ function classifyAccelerator(backend, device) {
|
|
|
376
376
|
function resolveDefaultDetectionModel(backend, device, format, getStepDef = require_step_definitions.getStepDefinition) {
|
|
377
377
|
let def;
|
|
378
378
|
try {
|
|
379
|
-
def = getStepDef(OBJECT_DETECTION_STEP_ID);
|
|
379
|
+
def = getStepDef(OBJECT_DETECTION_STEP_ID$1);
|
|
380
380
|
} catch {
|
|
381
381
|
return "yolo26n";
|
|
382
382
|
}
|
|
@@ -951,6 +951,9 @@ function serializeModelConfig(config) {
|
|
|
951
951
|
postprocessor: config.postprocessor,
|
|
952
952
|
confidence: config.confidence
|
|
953
953
|
};
|
|
954
|
+
if (config.nmsIouThreshold !== void 0) result["nmsIouThreshold"] = config.nmsIouThreshold;
|
|
955
|
+
if (config.inputNormalization !== void 0) result["inputNormalization"] = config.inputNormalization;
|
|
956
|
+
if (config.outputProbabilities !== void 0) result["outputProbabilities"] = config.outputProbabilities;
|
|
954
957
|
if (config.inputWidth !== void 0) result["inputWidth"] = config.inputWidth;
|
|
955
958
|
if (config.inputHeight !== void 0) result["inputHeight"] = config.inputHeight;
|
|
956
959
|
if (config.inputChannels !== void 0) result["inputChannels"] = config.inputChannels;
|
|
@@ -1258,6 +1261,55 @@ var RUNTIME_TO_FORMAT = {
|
|
|
1258
1261
|
onnxruntime: "onnx",
|
|
1259
1262
|
edgetpu: "tflite"
|
|
1260
1263
|
};
|
|
1264
|
+
/** The step whose detections are re-filtered downstream by the executor's
|
|
1265
|
+
* per-macro `minConfidence*` sliders (`matchesMacroFilter`). Its pool-side
|
|
1266
|
+
* floor must stay BELOW those sliders or it silently pre-empts them. */
|
|
1267
|
+
var OBJECT_DETECTION_STEP_ID = "object-detection";
|
|
1268
|
+
/**
|
|
1269
|
+
* Pool-side confidence FLOOR for the object-detection step (#4 foot-gun fix).
|
|
1270
|
+
*
|
|
1271
|
+
* The pool used to receive `def.defaultConfidence` (0.5) as its floor and drop
|
|
1272
|
+
* every box < 0.5 BEFORE the executor's per-macro `minConfidence*` sliders run —
|
|
1273
|
+
* so a slider set below 0.5 was a silent no-op. Lower the pool floor to a small
|
|
1274
|
+
* constant so the operator-visible per-macro sliders are the authoritative
|
|
1275
|
+
* filter across their full 0–1 range. The per-macro slider DEFAULTS stay at 0.5
|
|
1276
|
+
* (see `step-definitions.ts` getConfigSchema), so default UI behaviour is
|
|
1277
|
+
* unchanged; only sub-0.5 sliders start having an effect. Non-object-detection
|
|
1278
|
+
* steps keep their own `def.defaultConfidence` floor (no per-macro re-filter). */
|
|
1279
|
+
var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
|
|
1280
|
+
/**
|
|
1281
|
+
* Resolve the pool-side confidence FLOOR for a step (#4). The object-detection
|
|
1282
|
+
* step gets the small {@link OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR} so its
|
|
1283
|
+
* per-macro `minConfidence*` sliders (applied downstream by the executor) are
|
|
1284
|
+
* the authoritative filter; every other step keeps its own `defaultConfidence`.
|
|
1285
|
+
*/
|
|
1286
|
+
function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
|
|
1287
|
+
return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Load a model's runtime label list from its `-labels.json` extraFile, if any.
|
|
1291
|
+
*
|
|
1292
|
+
* Accepts BOTH on-disk shapes:
|
|
1293
|
+
* - a flat `string[]` (legacy label files), and
|
|
1294
|
+
* - a `{ "labels": string[], ... }` metadata object (the newer format the
|
|
1295
|
+
* animal / bird / vehicle classifiers ship, which also carries preprocess /
|
|
1296
|
+
* license metadata alongside the label array).
|
|
1297
|
+
*
|
|
1298
|
+
* Returns `undefined` when the model declares no `-labels.json`, the file is
|
|
1299
|
+
* absent, or its contents are not a recognised label shape — the caller then
|
|
1300
|
+
* falls back to the step's `def.labels`.
|
|
1301
|
+
*/
|
|
1302
|
+
var LabelsFileSchema = require_dist.union([require_dist.array(require_dist.string()).readonly(), require_dist.object({ labels: require_dist.array(require_dist.string()).readonly() })]);
|
|
1303
|
+
function readModelLabels(modelsDir, modelEntry) {
|
|
1304
|
+
const labelsFile = modelEntry.extraFiles?.find((f) => f.filename.endsWith("-labels.json"));
|
|
1305
|
+
if (!labelsFile) return void 0;
|
|
1306
|
+
const labelsPath = node_path.join(modelsDir, labelsFile.filename);
|
|
1307
|
+
if (!node_fs.existsSync(labelsPath)) return void 0;
|
|
1308
|
+
const parsed = LabelsFileSchema.safeParse(JSON.parse(node_fs.readFileSync(labelsPath, "utf8")));
|
|
1309
|
+
if (!parsed.success) return void 0;
|
|
1310
|
+
const data = parsed.data;
|
|
1311
|
+
return "labels" in data ? data.labels : data;
|
|
1312
|
+
}
|
|
1261
1313
|
var EngineFactory = class {
|
|
1262
1314
|
pool = null;
|
|
1263
1315
|
poolManager = null;
|
|
@@ -1425,14 +1477,7 @@ var EngineFactory = class {
|
|
|
1425
1477
|
const inputHeight = modelEntry.inputSize.height;
|
|
1426
1478
|
const inputSize = Math.max(inputWidth, inputHeight);
|
|
1427
1479
|
const inputChannels = modelEntry.inputChannels ?? 3;
|
|
1428
|
-
|
|
1429
|
-
if (!labels && modelEntry.extraFiles) {
|
|
1430
|
-
const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
|
|
1431
|
-
if (labelsFile) {
|
|
1432
|
-
const labelsPath = node_path.join(this.opts.modelsDir, labelsFile.filename);
|
|
1433
|
-
if (node_fs.existsSync(labelsPath)) labels = JSON.parse(node_fs.readFileSync(labelsPath, "utf8"));
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1480
|
+
const labels = readModelLabels(this.opts.modelsDir, modelEntry) ?? def.labels;
|
|
1436
1481
|
return {
|
|
1437
1482
|
path: modelPath,
|
|
1438
1483
|
inputSize,
|
|
@@ -1440,8 +1485,11 @@ var EngineFactory = class {
|
|
|
1440
1485
|
inputHeight,
|
|
1441
1486
|
inputChannels,
|
|
1442
1487
|
preprocessMode: modelEntry.preprocessMode ?? "letterbox",
|
|
1488
|
+
inputNormalization: modelEntry.inputNormalization,
|
|
1489
|
+
outputProbabilities: modelEntry.outputProbabilities,
|
|
1443
1490
|
postprocessor: modelEntry.postprocessor ?? def.postprocessor,
|
|
1444
|
-
confidence: def.defaultConfidence,
|
|
1491
|
+
confidence: resolvePoolConfidenceFloor(stepId, def.defaultConfidence),
|
|
1492
|
+
nmsIouThreshold: this.opts.nmsIouThreshold ?? .45,
|
|
1445
1493
|
labels,
|
|
1446
1494
|
charset: def.charset,
|
|
1447
1495
|
numClasses: labels?.length,
|
|
@@ -1560,6 +1608,9 @@ var EngineProvisioner = class {
|
|
|
1560
1608
|
};
|
|
1561
1609
|
//#endregion
|
|
1562
1610
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
1611
|
+
/** Detector NMS IoU fallback — the historical hardcoded literal, used when the
|
|
1612
|
+
* step carries no `nmsIouThreshold` setting (unset == today). */
|
|
1613
|
+
var DEFAULT_NMS_IOU = .45;
|
|
1563
1614
|
var VALID_KINDS = new Set([
|
|
1564
1615
|
"detections",
|
|
1565
1616
|
"classifications",
|
|
@@ -1587,7 +1638,7 @@ function tryStructured(output) {
|
|
|
1587
1638
|
if (!isStepOutput(output.structured)) throw new Error(`Python pool returned structured output with unexpected kind: ${JSON.stringify(output.structured["kind"])}`);
|
|
1588
1639
|
return output.structured;
|
|
1589
1640
|
}
|
|
1590
|
-
function postprocessYolo(output, stepDef) {
|
|
1641
|
+
function postprocessYolo(output, stepDef, nmsIouThreshold) {
|
|
1591
1642
|
const structured = tryStructured(output);
|
|
1592
1643
|
if (structured) return structured;
|
|
1593
1644
|
const tensor = output.tensor;
|
|
@@ -1636,7 +1687,7 @@ function postprocessYolo(output, stepDef) {
|
|
|
1636
1687
|
}
|
|
1637
1688
|
return {
|
|
1638
1689
|
kind: "detections",
|
|
1639
|
-
detections: simpleNms(dets,
|
|
1690
|
+
detections: simpleNms(dets, nmsIouThreshold)
|
|
1640
1691
|
};
|
|
1641
1692
|
}
|
|
1642
1693
|
function postprocessSsd(output, _stepDef) {
|
|
@@ -1644,7 +1695,7 @@ function postprocessSsd(output, _stepDef) {
|
|
|
1644
1695
|
if (structured) return structured;
|
|
1645
1696
|
throw new Error("SSD postprocessing runs in the Python inference pool (ssd.py); no TypeScript raw-tensor path.");
|
|
1646
1697
|
}
|
|
1647
|
-
function postprocessScrfd(output, stepDef) {
|
|
1698
|
+
function postprocessScrfd(output, stepDef, nmsIouThreshold) {
|
|
1648
1699
|
const structured = tryStructured(output);
|
|
1649
1700
|
if (structured) return structured;
|
|
1650
1701
|
if (!output.tensors) throw new Error("SCRFD postprocessor: no tensors in engine output");
|
|
@@ -1722,7 +1773,7 @@ function postprocessScrfd(output, stepDef) {
|
|
|
1722
1773
|
score: c.score,
|
|
1723
1774
|
bbox: c.bbox,
|
|
1724
1775
|
...c.landmarks ? { landmarks: c.landmarks } : {}
|
|
1725
|
-
})),
|
|
1776
|
+
})), nmsIouThreshold)
|
|
1726
1777
|
};
|
|
1727
1778
|
}
|
|
1728
1779
|
/** Match ONNX output tensors to SCRFD strides by expected anchor count. */
|
|
@@ -1927,7 +1978,7 @@ function postprocessSaliency(output, _stepDef) {
|
|
|
1927
1978
|
maskHeight: side
|
|
1928
1979
|
};
|
|
1929
1980
|
}
|
|
1930
|
-
function postprocessYoloSeg(output, stepDef) {
|
|
1981
|
+
function postprocessYoloSeg(output, stepDef, nmsIouThreshold) {
|
|
1931
1982
|
const structured = tryStructured(output);
|
|
1932
1983
|
if (structured) return structured;
|
|
1933
1984
|
const tensors = output.tensors;
|
|
@@ -2052,7 +2103,7 @@ function postprocessYoloSeg(output, stepDef) {
|
|
|
2052
2103
|
class: d.class,
|
|
2053
2104
|
score: d.score,
|
|
2054
2105
|
bbox: d.bbox
|
|
2055
|
-
})),
|
|
2106
|
+
})), nmsIouThreshold);
|
|
2056
2107
|
const keptSet = new Set(keptBoxes.map((d) => `${d.bbox[0]}_${d.bbox[1]}_${d.bbox[2]}_${d.bbox[3]}_${d.score}`));
|
|
2057
2108
|
return {
|
|
2058
2109
|
kind: "detections",
|
|
@@ -2085,10 +2136,11 @@ var DISPATCH = {
|
|
|
2085
2136
|
* Both paths pass through normalizeOutput() to ensure consistent format
|
|
2086
2137
|
* (e.g., classification top-K, alternates).
|
|
2087
2138
|
*/
|
|
2088
|
-
function dispatchPostprocess(output, stepDef) {
|
|
2139
|
+
function dispatchPostprocess(output, stepDef, settings) {
|
|
2089
2140
|
const fn = DISPATCH[stepDef.postprocessor];
|
|
2090
2141
|
if (!fn) throw new Error(`Unknown postprocessor type: "${stepDef.postprocessor}"`);
|
|
2091
|
-
|
|
2142
|
+
const raw = settings?.["nmsIouThreshold"];
|
|
2143
|
+
return normalizeOutput(fn(output, stepDef, typeof raw === "number" && raw > 0 ? raw : DEFAULT_NMS_IOU));
|
|
2092
2144
|
}
|
|
2093
2145
|
/**
|
|
2094
2146
|
* Normalize any StepOutput to a consistent format.
|
|
@@ -2553,7 +2605,8 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
|
|
|
2553
2605
|
case "classifications": {
|
|
2554
2606
|
const rawEnabled = childStep.settings?.["enabledClasses"];
|
|
2555
2607
|
const enabledSet = Array.isArray(rawEnabled) && rawEnabled.length > 0 ? new Set(rawEnabled.filter((v) => typeof v === "string")) : null;
|
|
2556
|
-
const
|
|
2608
|
+
const rejectSet = childStep.definition.rejectClasses && childStep.definition.rejectClasses.length > 0 ? new Set(childStep.definition.rejectClasses.map((c) => c.toLowerCase())) : /* @__PURE__ */ new Set();
|
|
2609
|
+
const passesClass = (c) => (enabledSet === null || enabledSet.has(c.class)) && !rejectSet.has(c.class.toLowerCase());
|
|
2557
2610
|
const minConfidence = typeof childStep.settings?.["minConfidence"] === "number" ? childStep.settings["minConfidence"] : 0;
|
|
2558
2611
|
const winner = output.classifications[0];
|
|
2559
2612
|
if (winner && passesClass(winner) && winner.score >= minConfidence) parent.ownLabels.push({
|
|
@@ -2889,6 +2942,26 @@ var NATIVE_FACE_CROP_MAX_WIDTH = 320;
|
|
|
2889
2942
|
function isEnrichmentOutput(output) {
|
|
2890
2943
|
return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
|
|
2891
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Re-project a `crop-zone` root's detections from crop-relative coordinates
|
|
2947
|
+
* back to full-frame image space by the crop's origin — the root analogue of
|
|
2948
|
+
* the crop-roi child re-projection (`transformBboxToImageSpace`). Landmarks (if
|
|
2949
|
+
* any) are shifted by the same origin. Immutable: returns a fresh output.
|
|
2950
|
+
*/
|
|
2951
|
+
function reprojectCropZoneDetections(output, cropOrigin) {
|
|
2952
|
+
const [ox, oy] = cropOrigin;
|
|
2953
|
+
return {
|
|
2954
|
+
kind: "detections",
|
|
2955
|
+
detections: output.detections.map((det) => ({
|
|
2956
|
+
...det,
|
|
2957
|
+
bbox: transformBboxToImageSpace(det.bbox, cropOrigin),
|
|
2958
|
+
...det.landmarks ? { landmarks: det.landmarks.map((l) => ({
|
|
2959
|
+
x: l.x + ox,
|
|
2960
|
+
y: l.y + oy
|
|
2961
|
+
})) } : {}
|
|
2962
|
+
}))
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2892
2965
|
var PipelineExecutor = class {
|
|
2893
2966
|
opts;
|
|
2894
2967
|
constructor(opts) {
|
|
@@ -2942,7 +3015,7 @@ var PipelineExecutor = class {
|
|
|
2942
3015
|
*
|
|
2943
3016
|
* @returns FrameResult + optional trace
|
|
2944
3017
|
*/
|
|
2945
|
-
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeFaceCropProvider) {
|
|
3018
|
+
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeFaceCropProvider, cropZoneBbox) {
|
|
2946
3019
|
const startMs = Date.now();
|
|
2947
3020
|
const verbosity = runOpts?.traceVerbosity ?? "off";
|
|
2948
3021
|
const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
|
|
@@ -2962,9 +3035,11 @@ var PipelineExecutor = class {
|
|
|
2962
3035
|
};
|
|
2963
3036
|
for (const rootStep of tree.roots) {
|
|
2964
3037
|
if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
|
|
3038
|
+
const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
|
|
2965
3039
|
const rootStart = Date.now();
|
|
2966
|
-
const
|
|
3040
|
+
const rawRootOutput = await this.executeStep(rootStep, cropPrep?.input ?? rootInput, cropPrep?.width ?? imageWidth, cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg);
|
|
2967
3041
|
const rootMs = Date.now() - rootStart;
|
|
3042
|
+
const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
|
|
2968
3043
|
if (rootOutput.kind !== "detections") {
|
|
2969
3044
|
if (isEnrichmentOutput(rootOutput)) {
|
|
2970
3045
|
const mutable = this.synthesizeRootDetection(rootOutput, rootStep, idGen, rootMs, imageWidth, imageHeight);
|
|
@@ -3059,6 +3134,39 @@ var PipelineExecutor = class {
|
|
|
3059
3134
|
]
|
|
3060
3135
|
}, rootStep, idGen, rootStepLatencyMs);
|
|
3061
3136
|
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Crop the full frame to a `crop-zone` root's absolute-pixel zone bbox and
|
|
3139
|
+
* return the cropped JPEG input + the exact origin used, so detections can be
|
|
3140
|
+
* re-projected. Returns null (⇒ full-frame fallback) when the bbox is
|
|
3141
|
+
* degenerate or the crop/encode fails — a package frame is never dropped for
|
|
3142
|
+
* a crop failure. The origin is computed to match `cropJpeg`'s clamp+round
|
|
3143
|
+
* exactly so the re-projection is pixel-accurate.
|
|
3144
|
+
*/
|
|
3145
|
+
async prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) {
|
|
3146
|
+
if (isBboxDegenerate(cropZoneBbox)) return null;
|
|
3147
|
+
try {
|
|
3148
|
+
const crop = await cropJpeg(await fullFrameJpegProvider(), cropZoneBbox, imageWidth, imageHeight);
|
|
3149
|
+
const ox = Math.max(0, Math.round(cropZoneBbox[0]));
|
|
3150
|
+
const oy = Math.max(0, Math.round(cropZoneBbox[1]));
|
|
3151
|
+
return {
|
|
3152
|
+
input: {
|
|
3153
|
+
kind: "jpeg",
|
|
3154
|
+
data: crop.jpeg
|
|
3155
|
+
},
|
|
3156
|
+
width: crop.width,
|
|
3157
|
+
height: crop.height,
|
|
3158
|
+
origin: [
|
|
3159
|
+
ox,
|
|
3160
|
+
oy,
|
|
3161
|
+
ox + crop.width,
|
|
3162
|
+
oy + crop.height
|
|
3163
|
+
]
|
|
3164
|
+
};
|
|
3165
|
+
} catch (err) {
|
|
3166
|
+
this.throttledWarn(`crop-zone-prep:${deviceId}`, 3e4, "crop-zone frame preparation failed — running package-detection full-frame", { error: require_dist.errMsg(err) }, deviceId);
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3062
3170
|
async executeStep(step, input, inputWidth, inputHeight, inputType, _parentBbox, parentClass, traceBuilder, stepTimings, poolAgg) {
|
|
3063
3171
|
const preprocessStart = Date.now();
|
|
3064
3172
|
const preprocessMs = Date.now() - preprocessStart;
|
|
@@ -3101,7 +3209,7 @@ var PipelineExecutor = class {
|
|
|
3101
3209
|
if (typeof structured.predictMs === "number") poolAgg.predictMs += structured.predictMs;
|
|
3102
3210
|
if (typeof structured.batchSize === "number" && structured.batchSize > poolAgg.batchSize) poolAgg.batchSize = structured.batchSize;
|
|
3103
3211
|
const postprocessStart = Date.now();
|
|
3104
|
-
const output = dispatchPostprocess(engineOutput, step.definition);
|
|
3212
|
+
const output = dispatchPostprocess(engineOutput, step.definition, step.settings);
|
|
3105
3213
|
const postprocessMs = Date.now() - postprocessStart;
|
|
3106
3214
|
stepTimings.push({
|
|
3107
3215
|
source: step.stepId,
|
|
@@ -3248,6 +3356,54 @@ function capitalize(s) {
|
|
|
3248
3356
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3249
3357
|
}
|
|
3250
3358
|
//#endregion
|
|
3359
|
+
//#region src/detection-pipeline/pipeline/crop-zone.ts
|
|
3360
|
+
/**
|
|
3361
|
+
* Resolve the absolute-pixel crop bbox for a `crop-zone` root detector: the
|
|
3362
|
+
* bounding box of the UNION of every polygon referenced by the device's
|
|
3363
|
+
* ENABLED `package`-stage zone rules. Zone polygons are normalized (0..1); the
|
|
3364
|
+
* union is clamped to [0,1] then scaled to the frame's pixel dimensions.
|
|
3365
|
+
*
|
|
3366
|
+
* Returns `null` when there is nothing usable to crop to — no enabled rules, no
|
|
3367
|
+
* referenced geometry, or a degenerate result — in which case the caller falls
|
|
3368
|
+
* back to full-frame inference (behaviour identical to before the crop-zone
|
|
3369
|
+
* plumbing).
|
|
3370
|
+
*/
|
|
3371
|
+
function resolvePackageCropBbox(zones, packageRules, frameWidth, frameHeight) {
|
|
3372
|
+
if (frameWidth <= 0 || frameHeight <= 0) return null;
|
|
3373
|
+
const enabledRules = packageRules.filter((r) => r.enabled !== false);
|
|
3374
|
+
if (enabledRules.length === 0) return null;
|
|
3375
|
+
const zoneIds = /* @__PURE__ */ new Set();
|
|
3376
|
+
for (const rule of enabledRules) for (const id of rule.zoneIds) zoneIds.add(id);
|
|
3377
|
+
if (zoneIds.size === 0) return null;
|
|
3378
|
+
const zoneById = new Map(zones.map((z) => [z.id, z]));
|
|
3379
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
3380
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
3381
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
3382
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
3383
|
+
let sawPoint = false;
|
|
3384
|
+
for (const id of zoneIds) {
|
|
3385
|
+
const zone = zoneById.get(id);
|
|
3386
|
+
if (!zone || zone.polygon.length === 0) continue;
|
|
3387
|
+
for (const point of zone.polygon) {
|
|
3388
|
+
sawPoint = true;
|
|
3389
|
+
if (point.x < minX) minX = point.x;
|
|
3390
|
+
if (point.y < minY) minY = point.y;
|
|
3391
|
+
if (point.x > maxX) maxX = point.x;
|
|
3392
|
+
if (point.y > maxY) maxY = point.y;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
if (!sawPoint) return null;
|
|
3396
|
+
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
3397
|
+
const bbox = [
|
|
3398
|
+
clamp01(minX) * frameWidth,
|
|
3399
|
+
clamp01(minY) * frameHeight,
|
|
3400
|
+
clamp01(maxX) * frameWidth,
|
|
3401
|
+
clamp01(maxY) * frameHeight
|
|
3402
|
+
];
|
|
3403
|
+
if (isBboxDegenerate(bbox)) return null;
|
|
3404
|
+
return bbox;
|
|
3405
|
+
}
|
|
3406
|
+
//#endregion
|
|
3251
3407
|
//#region src/detection-pipeline/pipeline/tree-builder.ts
|
|
3252
3408
|
/**
|
|
3253
3409
|
* Build an executable tree from user config.
|
|
@@ -4985,10 +5141,16 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4985
5141
|
const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
|
|
4986
5142
|
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
4987
5143
|
const nativeFaceCropProvider = this.buildNativeFaceCropProvider(input.frameHandle);
|
|
5144
|
+
let cropZoneBbox;
|
|
5145
|
+
if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
5146
|
+
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
5147
|
+
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
5148
|
+
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
5149
|
+
}
|
|
4988
5150
|
const { result, trace } = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
4989
5151
|
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
4990
5152
|
plane: input.plane
|
|
4991
|
-
}, nativeFaceCropProvider);
|
|
5153
|
+
}, nativeFaceCropProvider, cropZoneBbox);
|
|
4992
5154
|
if (isRuntime) {
|
|
4993
5155
|
if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
|
|
4994
5156
|
type: "device",
|