@camstack/addon-pipeline 1.2.295 → 1.2.297
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_MODELS.md +8 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-wNEISVA9.js → default-detection-model-B2n_SFXt.js} +442 -50
- package/dist/{default-detection-model-CAUBVbgK.mjs → default-detection-model-D6DypCkI.mjs} +413 -51
- package/dist/detection-pipeline/index.js +1516 -269
- package/dist/detection-pipeline/index.mjs +1515 -268
- package/dist/{dist-CuxSNLKW.mjs → dist-CxHIpsQs.mjs} +834 -398
- package/dist/{dist-BsVcf5wO.js → dist-DUqr1zyq.js} +845 -409
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-UFk6I2f6.js → node-Dx6DLQ1j.js} +1 -1
- package/dist/{node-Cqb1QiXk.mjs → node-DyeWu78a.mjs} +1 -1
- package/dist/pipeline-runner/index.js +906 -310
- package/dist/pipeline-runner/index.mjs +906 -310
- package/dist/{process-memory-D0Nvs9rr.mjs → process-memory-BVR4592X.mjs} +1 -1
- package/dist/{process-memory-CJV29sPf.js → process-memory-CMJ3NY-s.js} +1 -1
- package/dist/recorder/index.js +4 -6
- package/dist/recorder/index.mjs +4 -6
- package/dist/{segment-demux-js-DGwmf5hu.js → segment-demux-js-CmGIF_uK.js} +1 -1
- package/dist/{segment-demux-js-DIDWw1iE.mjs → segment-demux-js-Dzga-XgE.mjs} +1 -1
- package/dist/session-decode/{decode-worker-child.js → decode-worker-main.js} +481 -72
- package/dist/session-decode/{decode-worker-child.mjs → decode-worker-main.mjs} +482 -71
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-P71ze8cu.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CZkpFU-J.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-Dti8Ex88.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BKlb3qac.mjs → hostInit-CRlStbz4.mjs} +2 -2
- package/dist/stream-broker/index.js +4 -4
- package/dist/stream-broker/index.mjs +4 -4
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-B2MfQLlu.js → worker-protocol-C-G8qmye.js} +3 -1
- package/dist/{worker-protocol-C_W-P_g-.mjs → worker-protocol-D_NzPcnh.mjs} +3 -1
- package/package.json +1 -1
- package/python/inference_pool.py +422 -64
- package/python/test_inference_pool_compile_off_loop.py +414 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.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-DeD_UFdb.mjs +0 -26
package/THIRD_PARTY_MODELS.md
CHANGED
|
@@ -61,6 +61,12 @@ under [Non-commercial models](#non-commercial-models).
|
|
|
61
61
|
https://github.com/ShiqiYu/libfacedetection.train.
|
|
62
62
|
Trained on WIDER FACE, whose images are CC BY-NC-ND.
|
|
63
63
|
- **AuraFace v1** — fal.ai, Apache-2.0. https://huggingface.co/fal/AuraFace-v1
|
|
64
|
+
- **SigLIP 2** — © Google LLC, Apache-2.0 (`Apache-2.0.txt`).
|
|
65
|
+
https://huggingface.co/google/siglip2-base-patch16-224 ·
|
|
66
|
+
https://github.com/google-research/big_vision. CamStack's SigLIP 2 files are
|
|
67
|
+
modified: the vision input scaling `2x - 1` is baked into the graph, the text
|
|
68
|
+
graph slices its input to the model's 64 positions, the tokenizer lower-cases,
|
|
69
|
+
and the towers are converted to ONNX, OpenVINO IR and CoreML FP16.
|
|
64
70
|
- **EasyOCR** — JaidedAI, Apache-2.0. https://github.com/JaidedAI/EasyOCR
|
|
65
71
|
- **U²-Net** — Xuebin Qin et al., Apache-2.0. https://github.com/xuebinqin/U-2-Net
|
|
66
72
|
- **YAMNet** — © Google LLC, Apache-2.0.
|
|
@@ -202,6 +208,8 @@ commercial use**:
|
|
|
202
208
|
| --- | --- | --- | --- | --- | --- |
|
|
203
209
|
| `mobileclip-s1`, `mobileclip-s2` (image) | Apple `ml-mobileclip` | MIT / **Apple Machine Learning Research Model License** | per Apple | Model Derivative: converted to OpenVINO IR and CoreML FP16 | Mirror `clip/mobileclip-s1`, `clip/mobileclip-s2` |
|
|
204
210
|
| `mobileclip-s1-text`, `mobileclip-s2-text` | Apple `ml-mobileclip`, with the CLIP tokenizer | MIT / **Apple Machine Learning Research Model License** | per Apple | Model Derivative: quantised to INT8 ONNX and converted to OpenVINO IR | Mirror `clip/mobileclip-s1`, `clip/mobileclip-s2` |
|
|
211
|
+
| `siglip2-b16-224` (image) | Google SigLIP 2 base patch16-224 (`google/siglip2-base-patch16-224`) | Apache-2.0 / Apache-2.0 | WebLI (Google internal, not released) | input scaling `y = 2x - 1` baked ahead of the patch embedding; converted to OpenVINO IR and CoreML FP16 (an FP32 ONNX is hosted as the verification reference) (`scripts/build-siglip2-model.py`) | Mirror `clip/siglip2` |
|
|
212
|
+
| `siglip2-b16-224-text` | Google SigLIP 2 base patch16-224, with its Gemma tokenizer | Apache-2.0 / Apache-2.0 | WebLI (Google internal, not released) | input sliced to the model's 64 positions in the graph; ONNX weights FP16, OpenVINO IR and CoreML FP16; a `Lowercase` normalizer prepended to the tokenizer | Mirror `clip/siglip2` |
|
|
205
213
|
|
|
206
214
|
### Segmentation
|
|
207
215
|
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_process_memory = require("../process-memory-
|
|
6
|
+
const require_dist = require("../dist-DUqr1zyq.js");
|
|
7
|
+
const require_process_memory = require("../process-memory-CMJ3NY-s.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
10
10
|
let node_path = require("node:path");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { E as HF_BASE_URL, G as audioAnalyzerCapability, Rn as EventCategory,
|
|
3
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
2
|
+
import { E as HF_BASE_URL, G as audioAnalyzerCapability, Rn as EventCategory, Ut as resolvePoolMemoryPolicy, W as audioAnalysisCapability, _n as hydrateSchema, bt as mapAudioLabelToMacro, en as errMsg, fn as audioChunkBytesPerSample, gn as expandAudioChunkToF32le, j as PoolMemoryWatchdog, n as AUDIO_BACKEND_CHOICES, p as DEFAULT_AUDIO_ANALYZER_CONFIG, sn as BaseAddon, xn as nodePin } from "../dist-CxHIpsQs.mjs";
|
|
3
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BVR4592X.mjs";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import { downloadFile } from "@camstack/system/addon-utils";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_dist = require("./dist-
|
|
1
|
+
const require_dist = require("./dist-DUqr1zyq.js");
|
|
2
2
|
let node_crypto = require("node:crypto");
|
|
3
3
|
//#region src/detection-pipeline/pipeline/landmark-precision-gate.ts
|
|
4
4
|
/**
|
|
@@ -1184,55 +1184,84 @@ var INSTANCE_SEGMENTATION_MODELS = [
|
|
|
1184
1184
|
}
|
|
1185
1185
|
}
|
|
1186
1186
|
];
|
|
1187
|
-
var CLIP_EMBEDDING_MODELS = [
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1187
|
+
var CLIP_EMBEDDING_MODELS = [
|
|
1188
|
+
{
|
|
1189
|
+
id: "mobileclip-s1",
|
|
1190
|
+
name: "MobileCLIP S1",
|
|
1191
|
+
description: "MobileCLIP S1 — Apple balanced CLIP vision encoder, 512-dim, 256×256 (fp16 OpenVINO/CoreML)",
|
|
1192
|
+
inputSize: {
|
|
1193
|
+
width: 256,
|
|
1194
|
+
height: 256
|
|
1195
|
+
},
|
|
1196
|
+
labels: [{
|
|
1197
|
+
id: "embedding",
|
|
1198
|
+
name: "CLIP Embedding"
|
|
1199
|
+
}],
|
|
1200
|
+
preprocessMode: "resize",
|
|
1201
|
+
inputNormalization: "none",
|
|
1202
|
+
formats: {
|
|
1203
|
+
openvino: ovFormat(hf("clip/mobileclip-s1/openvino/camstack-mobileclip-s1-vision.xml"), 55),
|
|
1204
|
+
coreml: {
|
|
1205
|
+
url: hf("clip/mobileclip-s1/coreml/camstack-mobileclip-s1-vision.mlpackage"),
|
|
1206
|
+
sizeMB: 65,
|
|
1207
|
+
isDirectory: true,
|
|
1208
|
+
files: [...MLPACKAGE_FILES],
|
|
1209
|
+
runtimes: ["python"]
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1194
1212
|
},
|
|
1195
|
-
|
|
1196
|
-
id: "
|
|
1197
|
-
name: "
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1213
|
+
{
|
|
1214
|
+
id: "mobileclip-s2",
|
|
1215
|
+
name: "MobileCLIP S2",
|
|
1216
|
+
description: "MobileCLIP S2 — Apple high-accuracy CLIP vision encoder, 512-dim, 256×256 (fp16 OpenVINO/CoreML)",
|
|
1217
|
+
inputSize: {
|
|
1218
|
+
width: 256,
|
|
1219
|
+
height: 256
|
|
1220
|
+
},
|
|
1221
|
+
labels: [{
|
|
1222
|
+
id: "embedding",
|
|
1223
|
+
name: "CLIP Embedding"
|
|
1224
|
+
}],
|
|
1225
|
+
preprocessMode: "resize",
|
|
1226
|
+
inputNormalization: "none",
|
|
1227
|
+
formats: {
|
|
1228
|
+
openvino: ovFormat(hf("clip/mobileclip-s2/openvino/camstack-mobileclip-s2-vision.xml"), 90),
|
|
1229
|
+
coreml: {
|
|
1230
|
+
url: hf("clip/mobileclip-s2/coreml/camstack-mobileclip-s2-vision.mlpackage"),
|
|
1231
|
+
sizeMB: 110,
|
|
1232
|
+
isDirectory: true,
|
|
1233
|
+
files: [...MLPACKAGE_FILES],
|
|
1234
|
+
runtimes: ["python"]
|
|
1235
|
+
}
|
|
1209
1236
|
}
|
|
1210
|
-
}
|
|
1211
|
-
}, {
|
|
1212
|
-
id: "mobileclip-s2",
|
|
1213
|
-
name: "MobileCLIP S2",
|
|
1214
|
-
description: "MobileCLIP S2 — Apple high-accuracy CLIP vision encoder, 512-dim, 256×256 (fp16 OpenVINO/CoreML)",
|
|
1215
|
-
inputSize: {
|
|
1216
|
-
width: 256,
|
|
1217
|
-
height: 256
|
|
1218
1237
|
},
|
|
1219
|
-
|
|
1220
|
-
id: "
|
|
1221
|
-
name: "
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1238
|
+
{
|
|
1239
|
+
id: "siglip2-b16-224",
|
|
1240
|
+
name: "SigLIP2 B/16",
|
|
1241
|
+
description: "Google SigLIP2 base, patch 16, 224×224 — Apache-2.0 CLIP vision encoder, 768-dim (fp16 OpenVINO/CoreML)",
|
|
1242
|
+
inputSize: {
|
|
1243
|
+
width: 224,
|
|
1244
|
+
height: 224
|
|
1245
|
+
},
|
|
1246
|
+
labels: [{
|
|
1247
|
+
id: "embedding",
|
|
1248
|
+
name: "CLIP Embedding"
|
|
1249
|
+
}],
|
|
1250
|
+
preprocessMode: "resize",
|
|
1251
|
+
inputNormalization: "none",
|
|
1252
|
+
license: "Apache-2.0",
|
|
1253
|
+
formats: {
|
|
1254
|
+
openvino: ovFormat(hf("clip/siglip2/openvino/camstack-siglip2-b16-224-vision.xml"), 186),
|
|
1255
|
+
coreml: {
|
|
1256
|
+
url: hf("clip/siglip2/coreml/camstack-siglip2-b16-224-vision.mlpackage"),
|
|
1257
|
+
sizeMB: 185,
|
|
1258
|
+
isDirectory: true,
|
|
1259
|
+
files: [...MLPACKAGE_FILES],
|
|
1260
|
+
runtimes: ["python"]
|
|
1261
|
+
}
|
|
1233
1262
|
}
|
|
1234
1263
|
}
|
|
1235
|
-
|
|
1264
|
+
];
|
|
1236
1265
|
var AUDIO_CLASSIFIER_MODELS = [{
|
|
1237
1266
|
id: "yamnet-onnx",
|
|
1238
1267
|
name: "YAMNet",
|
|
@@ -1626,7 +1655,6 @@ var STEP_FACE_DETECTION = new PipelineStepBase({
|
|
|
1626
1655
|
outputClasses: ["face"],
|
|
1627
1656
|
models: [...FACE_DETECTION_MODELS],
|
|
1628
1657
|
defaultModelId: "yunet-2023mar",
|
|
1629
|
-
defaultModelIdByFormat: { tflite: "scrfd-2.5g" },
|
|
1630
1658
|
defaultConfidence: .5,
|
|
1631
1659
|
defaultMinParentScore: .7,
|
|
1632
1660
|
cadence: {
|
|
@@ -1671,7 +1699,7 @@ var STEP_FACE_EMBEDDING = new FaceEmbeddingStep({
|
|
|
1671
1699
|
outputClasses: ["identity"],
|
|
1672
1700
|
labelTier: 2,
|
|
1673
1701
|
models: [...FACE_EMBEDDING_MODELS],
|
|
1674
|
-
defaultModelId: "
|
|
1702
|
+
defaultModelId: "auraface-r100",
|
|
1675
1703
|
modelScope: "cluster",
|
|
1676
1704
|
defaultConfidence: 0,
|
|
1677
1705
|
cadence: {
|
|
@@ -2051,17 +2079,24 @@ var LocalFrameRegistry = class {
|
|
|
2051
2079
|
image,
|
|
2052
2080
|
estimatedBytes,
|
|
2053
2081
|
refs: 1,
|
|
2082
|
+
consumerRefs: 1,
|
|
2054
2083
|
releaseNativeOnClose: false
|
|
2055
2084
|
});
|
|
2056
2085
|
this.bytes += estimatedBytes;
|
|
2057
2086
|
this.evictToBounds();
|
|
2058
2087
|
return this.entries.has(ref.id) ? ref : null;
|
|
2059
2088
|
}
|
|
2060
|
-
|
|
2089
|
+
/**
|
|
2090
|
+
* Take a lease on `ref`. `holder` says why (D656): a reader that resolves a
|
|
2091
|
+
* view now is a `consumer` (the default); a lease PARKED for a read that may
|
|
2092
|
+
* come later is `retention`, and does not keep a draining worker alive.
|
|
2093
|
+
*/
|
|
2094
|
+
acquire(ref, holder = "consumer") {
|
|
2061
2095
|
if (ref.registryId === this.registryId && !this.entries.has(ref.id)) this.staleMissCount += 1;
|
|
2062
2096
|
const entry = this.getEntry(ref);
|
|
2063
2097
|
if (!entry) return null;
|
|
2064
2098
|
entry.refs += 1;
|
|
2099
|
+
if (holder === "consumer") this.addConsumer(entry);
|
|
2065
2100
|
this.touch(entry);
|
|
2066
2101
|
let released = false;
|
|
2067
2102
|
return {
|
|
@@ -2069,17 +2104,40 @@ var LocalFrameRegistry = class {
|
|
|
2069
2104
|
release: (reason) => {
|
|
2070
2105
|
if (released) return;
|
|
2071
2106
|
released = true;
|
|
2072
|
-
this.
|
|
2107
|
+
this.releaseRef(ref, reason, holder);
|
|
2073
2108
|
}
|
|
2074
2109
|
};
|
|
2075
2110
|
}
|
|
2111
|
+
/** Release the OWNER reference {@link register} took — a consumer reference. */
|
|
2076
2112
|
release(ref, reason) {
|
|
2113
|
+
this.releaseRef(ref, reason, "consumer");
|
|
2114
|
+
}
|
|
2115
|
+
releaseRef(ref, reason, holder) {
|
|
2077
2116
|
const entry = this.getEntry(ref);
|
|
2078
2117
|
if (!entry) return;
|
|
2079
2118
|
this.releaseCount += 1;
|
|
2080
2119
|
if (releasesNative(reason)) entry.releaseNativeOnClose = true;
|
|
2081
2120
|
entry.refs = Math.max(0, entry.refs - 1);
|
|
2082
|
-
if (entry.refs === 0)
|
|
2121
|
+
if (entry.refs === 0) {
|
|
2122
|
+
this.drop(entry);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
if (holder === "consumer") this.removeConsumer(entry);
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* The frame's source learns whether a consumer still holds it, on each
|
|
2129
|
+
* transition only: the last consumer letting go of a frame that stays
|
|
2130
|
+
* PARKED, and a consumer coming back to one. A drop needs no signal — the
|
|
2131
|
+
* image is closed.
|
|
2132
|
+
*/
|
|
2133
|
+
addConsumer(entry) {
|
|
2134
|
+
entry.consumerRefs += 1;
|
|
2135
|
+
if (entry.consumerRefs === 1) entry.image.setPendingConsumer?.(true);
|
|
2136
|
+
}
|
|
2137
|
+
removeConsumer(entry) {
|
|
2138
|
+
if (entry.consumerRefs === 0) return;
|
|
2139
|
+
entry.consumerRefs -= 1;
|
|
2140
|
+
if (entry.consumerRefs === 0) entry.image.setPendingConsumer?.(false);
|
|
2083
2141
|
}
|
|
2084
2142
|
clear(reason = "detach") {
|
|
2085
2143
|
for (const entry of [...this.entries.values()]) {
|
|
@@ -2178,6 +2236,310 @@ function rootObjectGate(steps) {
|
|
|
2178
2236
|
return (steps?.find((s) => s.enabled !== false))?.settings;
|
|
2179
2237
|
}
|
|
2180
2238
|
//#endregion
|
|
2239
|
+
//#region src/pipeline-runner/device-jump.ts
|
|
2240
|
+
/**
|
|
2241
|
+
* Step-tree device jump (same-node, phase 1) — the PURE resolution core.
|
|
2242
|
+
*
|
|
2243
|
+
* Given the camera's elected ("effective") inference device and the node's
|
|
2244
|
+
* enabled-device roster, decide which device ONE enrichment step should run on:
|
|
2245
|
+
*
|
|
2246
|
+
* 1. A manual `jumpDeviceKey` override wins when it names a candidate that can
|
|
2247
|
+
* run the step (bypasses the auto choice; operator intent).
|
|
2248
|
+
* 2. Otherwise, if the effective device's format can run the step's model →
|
|
2249
|
+
* NO jump (the step inherits the effective device — the common case).
|
|
2250
|
+
* 3. Otherwise AUTO-jump: among the node's OTHER enabled devices whose format
|
|
2251
|
+
* can run the step (and that are under their `maxSessions` cap), pick the
|
|
2252
|
+
* weighted-least-loaded one — mirroring the balancer's per-device core.
|
|
2253
|
+
* 4. If nothing on the node can run it → `no-candidate` (the caller emits the
|
|
2254
|
+
* loud once-per-(camera,step) warn that replaces today's silent degrade).
|
|
2255
|
+
*
|
|
2256
|
+
* Framework-free + side-effect-free so the whole decision table is unit-testable
|
|
2257
|
+
* without a live runner/catalog. The `canRun(format)` predicate is injected by
|
|
2258
|
+
* the runner (which owns the model catalog); this module never imports it.
|
|
2259
|
+
*/
|
|
2260
|
+
/** The `ModelFormat` of a `<backend>:<device>` key, or `undefined` for an unknown backend. */
|
|
2261
|
+
function formatOf(deviceKey) {
|
|
2262
|
+
const colon = deviceKey.indexOf(":");
|
|
2263
|
+
return require_dist.DEVICE_BACKEND_TO_FORMAT[colon >= 0 ? deviceKey.slice(0, colon) : deviceKey];
|
|
2264
|
+
}
|
|
2265
|
+
/** True when the step's model has a build for `deviceKey`'s format. */
|
|
2266
|
+
function canRunOn(deviceKey, canRun) {
|
|
2267
|
+
const format = formatOf(deviceKey);
|
|
2268
|
+
return format !== void 0 && canRun(format, deviceKey);
|
|
2269
|
+
}
|
|
2270
|
+
/** Effective weighted load of a device (session count ÷ weight); lower = preferred. */
|
|
2271
|
+
function weightedLoad(d) {
|
|
2272
|
+
return d.attachedCameras / (d.weight > 0 ? d.weight : 1);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Weighted-least-loaded pick with a deterministic `deviceKey` tie-break —
|
|
2276
|
+
* mirrors the node/device balancer's decision core (least `load/weight`, ties
|
|
2277
|
+
* broken stably). `devices` is assumed non-empty.
|
|
2278
|
+
*/
|
|
2279
|
+
function pickWeightedLeastLoaded(devices) {
|
|
2280
|
+
return devices.reduce((best, d) => {
|
|
2281
|
+
const dScore = weightedLoad(d);
|
|
2282
|
+
const bScore = weightedLoad(best);
|
|
2283
|
+
if (dScore < bScore) return d;
|
|
2284
|
+
if (dScore > bScore) return best;
|
|
2285
|
+
return d.deviceKey < best.deviceKey ? d : best;
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Build the `canRun` predicate for ONE enrichment dispatch. The step and its
|
|
2290
|
+
* enabled descendants ride the SAME `runPipeline` call — one pool, one format
|
|
2291
|
+
* (detail-subtree contract: "its inherited children ride the same call → same
|
|
2292
|
+
* device") — so a format can run the dispatch only when EVERY catalog-known
|
|
2293
|
+
* step in the subtree ships a build for it. Gating only the subtree TOP let a
|
|
2294
|
+
* `face-detection` with a tflite build stay on the Coral while its
|
|
2295
|
+
* `face-embedding` child (no tflite build anywhere) doomed the call.
|
|
2296
|
+
* Unknown/custom addonIds (`getStepDef` → null) have no catalog to gate on
|
|
2297
|
+
* and never block. `getStepDef` is injected — this module stays catalog-free.
|
|
2298
|
+
* Each step is judged by `stepModelRunsOnFormat` (`@camstack/types`), the one
|
|
2299
|
+
* rule every placement reads: a legacy stub build counts only when named (D657).
|
|
2300
|
+
*/
|
|
2301
|
+
function buildSubtreeCanRun(step, getStepDef) {
|
|
2302
|
+
const judged = [];
|
|
2303
|
+
const walk = (s) => {
|
|
2304
|
+
if (s.enabled === false) return;
|
|
2305
|
+
const def = getStepDef(s.addonId);
|
|
2306
|
+
if (def !== null) judged.push({
|
|
2307
|
+
def,
|
|
2308
|
+
modelId: s.modelId
|
|
2309
|
+
});
|
|
2310
|
+
for (const child of s.children ?? []) walk(child);
|
|
2311
|
+
};
|
|
2312
|
+
walk(step);
|
|
2313
|
+
return (format) => judged.every(({ def, modelId }) => require_dist.stepModelRunsOnFormat(def.models, modelId, format));
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Resolve the device one enrichment step runs on. See the module header for the
|
|
2317
|
+
* decision table. Pure — never throws, no I/O.
|
|
2318
|
+
*/
|
|
2319
|
+
function resolveStepDevice(input) {
|
|
2320
|
+
const { effectiveDeviceKey, manualJumpDeviceKey, canRun } = input;
|
|
2321
|
+
if (manualJumpDeviceKey !== void 0 && manualJumpDeviceKey !== effectiveDeviceKey) {
|
|
2322
|
+
const target = input.devices.find((d) => d.deviceKey === manualJumpDeviceKey);
|
|
2323
|
+
if (target && canRunOn(target.deviceKey, canRun)) return {
|
|
2324
|
+
kind: "jumped",
|
|
2325
|
+
deviceKey: target.deviceKey,
|
|
2326
|
+
reason: "manual"
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
if (canRunOn(effectiveDeviceKey, canRun)) return {
|
|
2330
|
+
kind: "no-jump",
|
|
2331
|
+
deviceKey: effectiveDeviceKey
|
|
2332
|
+
};
|
|
2333
|
+
const runnable = input.devices.filter((d) => d.deviceKey !== effectiveDeviceKey && (d.maxSessions === null || d.attachedCameras < d.maxSessions) && canRunOn(d.deviceKey, canRun));
|
|
2334
|
+
if (runnable.length === 0) return {
|
|
2335
|
+
kind: "no-candidate",
|
|
2336
|
+
deviceKey: effectiveDeviceKey
|
|
2337
|
+
};
|
|
2338
|
+
return {
|
|
2339
|
+
kind: "jumped",
|
|
2340
|
+
deviceKey: pickWeightedLeastLoaded(runnable).deviceKey,
|
|
2341
|
+
reason: "auto"
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Re-provision a jumped subtree for its TARGET device (D650): every step's
|
|
2346
|
+
* `modelId` / `settings` are REPLACED by the target's entry — the orchestrator
|
|
2347
|
+
* computed it as what the step would carry had the camera been elected onto
|
|
2348
|
+
* that device (its per-(node,device) base with the camera's override for it).
|
|
2349
|
+
* A step absent from the map runs with neither, i.e. the target format's
|
|
2350
|
+
* default model. The elected device's provisioning never follows a step across:
|
|
2351
|
+
* it was chosen for a format this subtree does not run on.
|
|
2352
|
+
*/
|
|
2353
|
+
function provisionSubtreeForDevice(step, provisioning) {
|
|
2354
|
+
const own = provisioning[step.addonId];
|
|
2355
|
+
const { modelId: _electedModelId, settings: _electedSettings, ...rest } = step;
|
|
2356
|
+
return {
|
|
2357
|
+
...rest,
|
|
2358
|
+
...own?.modelId !== void 0 ? { modelId: own.modelId } : {},
|
|
2359
|
+
...own?.settings !== void 0 ? { settings: own.settings } : {},
|
|
2360
|
+
...step.children !== void 0 ? { children: step.children.map((c) => provisionSubtreeForDevice(c, provisioning)) } : {}
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
/** The subtree as `deviceKey` would run it: the elected device runs it as sent; another takes its roster entry. */
|
|
2364
|
+
function provisionedFor(step, deviceKey, effectiveDeviceKey, roster) {
|
|
2365
|
+
if (deviceKey === effectiveDeviceKey) return step;
|
|
2366
|
+
const provisioning = roster.find((d) => d.deviceKey === deviceKey)?.steps;
|
|
2367
|
+
return provisioning === void 0 ? step : provisionSubtreeForDevice(step, provisioning);
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Place ONE detail subtree (D650): which same-node device runs it
|
|
2371
|
+
* ({@link resolveStepDevice}) and, when it jumps, the provisioning it runs
|
|
2372
|
+
* with there ({@link provisionSubtreeForDevice}). Pure — the caller logs.
|
|
2373
|
+
*
|
|
2374
|
+
* A roster entry without `steps` came from an orchestrator that predates D650:
|
|
2375
|
+
* the step keeps what it was sent with, as it did before.
|
|
2376
|
+
*/
|
|
2377
|
+
function placeDetailStep(input) {
|
|
2378
|
+
const { step, effectiveDeviceKey, roster } = input;
|
|
2379
|
+
if (!effectiveDeviceKey) return {
|
|
2380
|
+
deviceKey: void 0,
|
|
2381
|
+
step,
|
|
2382
|
+
decision: null,
|
|
2383
|
+
provisioned: false
|
|
2384
|
+
};
|
|
2385
|
+
if (!roster || roster.length < 2) return {
|
|
2386
|
+
deviceKey: effectiveDeviceKey,
|
|
2387
|
+
step,
|
|
2388
|
+
decision: null,
|
|
2389
|
+
provisioned: false
|
|
2390
|
+
};
|
|
2391
|
+
const decision = resolveStepDevice({
|
|
2392
|
+
effectiveDeviceKey,
|
|
2393
|
+
devices: roster.map((d) => ({
|
|
2394
|
+
deviceKey: d.deviceKey,
|
|
2395
|
+
weight: d.weight,
|
|
2396
|
+
maxSessions: d.maxSessions,
|
|
2397
|
+
attachedCameras: input.attachedByKey.get(d.deviceKey) ?? 0
|
|
2398
|
+
})),
|
|
2399
|
+
...step.jumpDeviceKey !== void 0 ? { manualJumpDeviceKey: step.jumpDeviceKey } : {},
|
|
2400
|
+
canRun: (format, deviceKey) => buildSubtreeCanRun(provisionedFor(step, deviceKey, effectiveDeviceKey, roster), input.getStepDef)(format)
|
|
2401
|
+
});
|
|
2402
|
+
if (decision.kind !== "jumped") return {
|
|
2403
|
+
deviceKey: decision.deviceKey,
|
|
2404
|
+
step,
|
|
2405
|
+
decision,
|
|
2406
|
+
provisioned: false
|
|
2407
|
+
};
|
|
2408
|
+
const provisioning = roster.find((d) => d.deviceKey === decision.deviceKey)?.steps;
|
|
2409
|
+
return {
|
|
2410
|
+
deviceKey: decision.deviceKey,
|
|
2411
|
+
step: provisioning === void 0 ? step : provisionSubtreeForDevice(step, provisioning),
|
|
2412
|
+
decision,
|
|
2413
|
+
provisioned: provisioning !== void 0
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* The steps whose models a camera's elected pool loads (D657).
|
|
2418
|
+
*
|
|
2419
|
+
* The frame-plane dispatch executes only root-plane steps; a detail subtree
|
|
2420
|
+
* rides it solely so its model pre-warms the elected pool. That is right only
|
|
2421
|
+
* when the subtree RUNS there. The decision is {@link placeDetailStep}'s — the
|
|
2422
|
+
* one the detail plane takes for the same subtree — so the pool that loads a
|
|
2423
|
+
* model and the pool that runs it cannot disagree:
|
|
2424
|
+
* - no decision (no elected device, or a roster of one) or `no-jump`: kept;
|
|
2425
|
+
* - `jumped` (auto or manual): left out — the detail dispatch loads it on the
|
|
2426
|
+
* TARGET pool, with the target's provisioning;
|
|
2427
|
+
* - `no-candidate`: left out — nothing on the node can load it, so a
|
|
2428
|
+
* pre-warm could only fail (the detail plane still says so, loudly).
|
|
2429
|
+
* Roots are the elected device's job and always stay. Root-plane children
|
|
2430
|
+
* stay and are walked; a detail subtree is judged whole at its top, because
|
|
2431
|
+
* its children ride its call. Pure — the caller logs.
|
|
2432
|
+
*/
|
|
2433
|
+
function stepsForElectedPool(steps, isDetailStep, place) {
|
|
2434
|
+
const delegated = [];
|
|
2435
|
+
const walk = (nodes, topLevel) => {
|
|
2436
|
+
const kept = [];
|
|
2437
|
+
for (const step of nodes) {
|
|
2438
|
+
if (!topLevel && step.enabled !== false && isDetailStep(step.addonId)) {
|
|
2439
|
+
const decision = place(step).decision;
|
|
2440
|
+
if (decision === null || decision.kind === "no-jump") kept.push(step);
|
|
2441
|
+
else delegated.push(step.addonId);
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
kept.push(step.children !== void 0 && step.children.length > 0 ? {
|
|
2445
|
+
...step,
|
|
2446
|
+
children: walk(step.children, false)
|
|
2447
|
+
} : step);
|
|
2448
|
+
}
|
|
2449
|
+
return kept;
|
|
2450
|
+
};
|
|
2451
|
+
return {
|
|
2452
|
+
steps: walk(steps, true),
|
|
2453
|
+
delegated
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
//#endregion
|
|
2457
|
+
//#region src/detection-pipeline/registry/custom-models.ts
|
|
2458
|
+
/**
|
|
2459
|
+
* Group a flat list of custom-model descriptors (as returned by the
|
|
2460
|
+
* `custom-model-registry` collection cap) into a `stepId → entries` map for
|
|
2461
|
+
* the picker / resolution union. Pure; order within a step preserved.
|
|
2462
|
+
*/
|
|
2463
|
+
function groupCustomModelsByStep(descriptors) {
|
|
2464
|
+
const byStep = /* @__PURE__ */ new Map();
|
|
2465
|
+
for (const d of descriptors) {
|
|
2466
|
+
const arr = byStep.get(d.stepId) ?? [];
|
|
2467
|
+
arr.push(d.entry);
|
|
2468
|
+
byStep.set(d.stepId, arr);
|
|
2469
|
+
}
|
|
2470
|
+
return byStep;
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Union a step's static catalog models with operator-registered custom
|
|
2474
|
+
* models. On an `id` collision the static catalog entry wins — a custom
|
|
2475
|
+
* model can never shadow a built-in one.
|
|
2476
|
+
*
|
|
2477
|
+
* Pure + side-effect-free so it can be unit-tested in isolation and called
|
|
2478
|
+
* from the (free) `buildSchemaSlots` builder without any addon context.
|
|
2479
|
+
*/
|
|
2480
|
+
function mergeCustomModels(staticModels, customModels) {
|
|
2481
|
+
const seen = new Set(staticModels.map((m) => m.id));
|
|
2482
|
+
const merged = [...staticModels];
|
|
2483
|
+
for (const m of customModels) {
|
|
2484
|
+
if (seen.has(m.id)) continue;
|
|
2485
|
+
seen.add(m.id);
|
|
2486
|
+
merged.push({
|
|
2487
|
+
...m,
|
|
2488
|
+
provider: require_dist.inferModelProvider(m)
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
return merged;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* A step's definition with its custom models merged in (static wins on an id
|
|
2495
|
+
* collision), or `null` for an unknown addonId. Pure.
|
|
2496
|
+
*/
|
|
2497
|
+
function mergedStepDefinition(stepId, customByStep) {
|
|
2498
|
+
let def;
|
|
2499
|
+
try {
|
|
2500
|
+
def = getStepDefinition(stepId);
|
|
2501
|
+
} catch {
|
|
2502
|
+
return null;
|
|
2503
|
+
}
|
|
2504
|
+
const custom = customByStep.get(stepId);
|
|
2505
|
+
if (custom === void 0 || custom.length === 0) return def;
|
|
2506
|
+
return {
|
|
2507
|
+
...def,
|
|
2508
|
+
models: mergeCustomModels(def.models, custom)
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
/** TTL-cached read of the custom-model registry, with a synchronous merged view. */
|
|
2512
|
+
var CustomModelCatalog = class {
|
|
2513
|
+
logger;
|
|
2514
|
+
now;
|
|
2515
|
+
byStep = /* @__PURE__ */ new Map();
|
|
2516
|
+
readAtMs = Number.NEGATIVE_INFINITY;
|
|
2517
|
+
constructor(logger, now = Date.now) {
|
|
2518
|
+
this.logger = logger;
|
|
2519
|
+
this.now = now;
|
|
2520
|
+
}
|
|
2521
|
+
/** Re-read the registry when the snapshot is older than the TTL; answer the snapshot. */
|
|
2522
|
+
async refresh(api) {
|
|
2523
|
+
const now = this.now();
|
|
2524
|
+
if (now - this.readAtMs < 5e3) return this.byStep;
|
|
2525
|
+
this.readAtMs = now;
|
|
2526
|
+
if (api === void 0) return this.byStep;
|
|
2527
|
+
try {
|
|
2528
|
+
const providers = await api.addons.listCapabilityProviders.query({ capName: "custom-model-registry" });
|
|
2529
|
+
this.byStep = providers.some((p) => p.isActive) ? groupCustomModelsByStep(await api.customModelRegistry.listModels.query()) : /* @__PURE__ */ new Map();
|
|
2530
|
+
} catch (err) {
|
|
2531
|
+
this.logger.warn("custom-model-registry query failed — keeping the last custom-model snapshot", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
2532
|
+
}
|
|
2533
|
+
return this.byStep;
|
|
2534
|
+
}
|
|
2535
|
+
/** The snapshot, synchronously. */
|
|
2536
|
+
current() {
|
|
2537
|
+
return this.byStep;
|
|
2538
|
+
}
|
|
2539
|
+
/** A step's merged definition from the snapshot, synchronously. */
|
|
2540
|
+
stepDefinition = (stepId) => mergedStepDefinition(stepId, this.byStep);
|
|
2541
|
+
};
|
|
2542
|
+
//#endregion
|
|
2181
2543
|
//#region src/detection-pipeline/default-detection-model.ts
|
|
2182
2544
|
/** The object-detection step id — the only slot this resolver applies to. */
|
|
2183
2545
|
var OBJECT_DETECTION_STEP_ID = "object-detection";
|
|
@@ -2272,6 +2634,12 @@ Object.defineProperty(exports, "ALL_STEPS", {
|
|
|
2272
2634
|
return ALL_STEPS;
|
|
2273
2635
|
}
|
|
2274
2636
|
});
|
|
2637
|
+
Object.defineProperty(exports, "CustomModelCatalog", {
|
|
2638
|
+
enumerable: true,
|
|
2639
|
+
get: function() {
|
|
2640
|
+
return CustomModelCatalog;
|
|
2641
|
+
}
|
|
2642
|
+
});
|
|
2275
2643
|
Object.defineProperty(exports, "OBJECT_DETECTION_STEP_ID", {
|
|
2276
2644
|
enumerable: true,
|
|
2277
2645
|
get: function() {
|
|
@@ -2284,6 +2652,12 @@ Object.defineProperty(exports, "RETIRED_MODEL_IDS", {
|
|
|
2284
2652
|
return RETIRED_MODEL_IDS;
|
|
2285
2653
|
}
|
|
2286
2654
|
});
|
|
2655
|
+
Object.defineProperty(exports, "buildSubtreeCanRun", {
|
|
2656
|
+
enumerable: true,
|
|
2657
|
+
get: function() {
|
|
2658
|
+
return buildSubtreeCanRun;
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2287
2661
|
Object.defineProperty(exports, "getDefaultModelForFormat", {
|
|
2288
2662
|
enumerable: true,
|
|
2289
2663
|
get: function() {
|
|
@@ -2320,6 +2694,18 @@ Object.defineProperty(exports, "macroGateVerdict", {
|
|
|
2320
2694
|
return macroGateVerdict;
|
|
2321
2695
|
}
|
|
2322
2696
|
});
|
|
2697
|
+
Object.defineProperty(exports, "mergeCustomModels", {
|
|
2698
|
+
enumerable: true,
|
|
2699
|
+
get: function() {
|
|
2700
|
+
return mergeCustomModels;
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
Object.defineProperty(exports, "placeDetailStep", {
|
|
2704
|
+
enumerable: true,
|
|
2705
|
+
get: function() {
|
|
2706
|
+
return placeDetailStep;
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2323
2709
|
Object.defineProperty(exports, "resolveDefaultDetectionModel", {
|
|
2324
2710
|
enumerable: true,
|
|
2325
2711
|
get: function() {
|
|
@@ -2344,3 +2730,9 @@ Object.defineProperty(exports, "rootObjectGate", {
|
|
|
2344
2730
|
return rootObjectGate;
|
|
2345
2731
|
}
|
|
2346
2732
|
});
|
|
2733
|
+
Object.defineProperty(exports, "stepsForElectedPool", {
|
|
2734
|
+
enumerable: true,
|
|
2735
|
+
get: function() {
|
|
2736
|
+
return stepsForElectedPool;
|
|
2737
|
+
}
|
|
2738
|
+
});
|