@camstack/addon-pipeline 1.0.8 → 1.1.0
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 +5 -5
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +636 -659
- package/dist/detection-pipeline/index.mjs +624 -647
- package/dist/{model-download-service-C7AjBsX9-rXY-VFDk.js → model-download-service-RxAOiYvX-C8rTRJy_.js} +36 -6
- package/dist/{model-download-service-C7AjBsX9-B0ekM6dF.mjs → model-download-service-RxAOiYvX-CMAvhgO7.mjs} +36 -6
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BFy9iszl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs} +3 -3
- package/dist/stream-broker/{hostInit-zRy9SzlX.mjs → hostInit-zLZbYJcg.mjs} +3 -3
- package/dist/stream-broker/index.js +7 -7
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/inference_pool.py +65 -6
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
|
@@ -25,7 +25,7 @@ node_fs = __toESM(node_fs, 1);
|
|
|
25
25
|
let node_path = require("node:path");
|
|
26
26
|
node_path = __toESM(node_path, 1);
|
|
27
27
|
require("node:crypto");
|
|
28
|
-
//#region ../system/dist/model-download-service-
|
|
28
|
+
//#region ../system/dist/model-download-service-RxAOiYvX.mjs
|
|
29
29
|
/**
|
|
30
30
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
31
31
|
* path stays within (traversal guard). The handler serves the first candidate
|
|
@@ -173,6 +173,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
173
173
|
(0, node_fs.createReadStream)(absPath).pipe(res);
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
+
function isNonEmptyFile(filePath) {
|
|
177
|
+
return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
181
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
182
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
183
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
184
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
185
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
186
|
+
*/
|
|
187
|
+
function siblingFilesFor(formatEntry) {
|
|
188
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
189
|
+
}
|
|
190
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
191
|
+
function siblingUrl(mainUrl, sibling) {
|
|
192
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
193
|
+
}
|
|
176
194
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
177
195
|
function buildHeaders(url) {
|
|
178
196
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -282,14 +300,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
282
300
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, node_path.join(modelsDir, extra.filename));
|
|
283
301
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
284
302
|
const modelPath = node_path.join(modelsDir, filename);
|
|
303
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
285
304
|
if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory && !node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
|
|
286
305
|
recursive: true,
|
|
287
306
|
force: true
|
|
288
307
|
});
|
|
289
|
-
else return modelPath;
|
|
308
|
+
else if (siblings.some((f) => !isNonEmptyFile(node_path.join(modelsDir, f)))) {} else return modelPath;
|
|
290
309
|
node_fs.mkdirSync(modelsDir, { recursive: true });
|
|
291
310
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
292
|
-
else
|
|
311
|
+
else {
|
|
312
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
313
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path.join(modelsDir, sibling));
|
|
314
|
+
}
|
|
293
315
|
return modelPath;
|
|
294
316
|
}
|
|
295
317
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -306,17 +328,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
306
328
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
307
329
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
308
330
|
if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
|
|
309
|
-
|
|
331
|
+
if (node_fs.statSync(modelPath).size <= 0) return false;
|
|
332
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path.join(modelsDir, f)));
|
|
310
333
|
}
|
|
311
334
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
312
335
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
313
336
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
314
337
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
315
|
-
|
|
338
|
+
const formatEntry = entry.formats[format];
|
|
339
|
+
if (formatEntry?.isDirectory) node_fs.rmSync(modelPath, {
|
|
316
340
|
recursive: true,
|
|
317
341
|
force: true
|
|
318
342
|
});
|
|
319
|
-
else
|
|
343
|
+
else {
|
|
344
|
+
node_fs.unlinkSync(modelPath);
|
|
345
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
346
|
+
const sibPath = node_path.join(modelsDir, sibling);
|
|
347
|
+
if (node_fs.existsSync(sibPath)) node_fs.unlinkSync(sibPath);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
320
350
|
return true;
|
|
321
351
|
}
|
|
322
352
|
//#endregion
|
|
@@ -3,7 +3,7 @@ import { createReadStream, promises } from "node:fs";
|
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import "node:crypto";
|
|
6
|
-
//#region ../system/dist/model-download-service-
|
|
6
|
+
//#region ../system/dist/model-download-service-RxAOiYvX.mjs
|
|
7
7
|
/**
|
|
8
8
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
9
9
|
* path stays within (traversal guard). The handler serves the first candidate
|
|
@@ -151,6 +151,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
151
151
|
createReadStream(absPath).pipe(res);
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
+
function isNonEmptyFile(filePath) {
|
|
155
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
159
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
160
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
161
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
162
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
163
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
164
|
+
*/
|
|
165
|
+
function siblingFilesFor(formatEntry) {
|
|
166
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
167
|
+
}
|
|
168
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
169
|
+
function siblingUrl(mainUrl, sibling) {
|
|
170
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
171
|
+
}
|
|
154
172
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
155
173
|
function buildHeaders(url) {
|
|
156
174
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -260,14 +278,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
260
278
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, path$1.join(modelsDir, extra.filename));
|
|
261
279
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
262
280
|
const modelPath = path$1.join(modelsDir, filename);
|
|
281
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
263
282
|
if (fs.existsSync(modelPath)) if (formatEntry.isDirectory && !fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
|
|
264
283
|
recursive: true,
|
|
265
284
|
force: true
|
|
266
285
|
});
|
|
267
|
-
else return modelPath;
|
|
286
|
+
else if (siblings.some((f) => !isNonEmptyFile(path$1.join(modelsDir, f)))) {} else return modelPath;
|
|
268
287
|
fs.mkdirSync(modelsDir, { recursive: true });
|
|
269
288
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
270
|
-
else
|
|
289
|
+
else {
|
|
290
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
291
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(modelsDir, sibling));
|
|
292
|
+
}
|
|
271
293
|
return modelPath;
|
|
272
294
|
}
|
|
273
295
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -284,17 +306,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
284
306
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
285
307
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
286
308
|
if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
|
|
287
|
-
|
|
309
|
+
if (fs.statSync(modelPath).size <= 0) return false;
|
|
310
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(modelsDir, f)));
|
|
288
311
|
}
|
|
289
312
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
290
313
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
291
314
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
292
315
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
293
|
-
|
|
316
|
+
const formatEntry = entry.formats[format];
|
|
317
|
+
if (formatEntry?.isDirectory) fs.rmSync(modelPath, {
|
|
294
318
|
recursive: true,
|
|
295
319
|
force: true
|
|
296
320
|
});
|
|
297
|
-
else
|
|
321
|
+
else {
|
|
322
|
+
fs.unlinkSync(modelPath);
|
|
323
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
324
|
+
const sibPath = path$1.join(modelsDir, sibling);
|
|
325
|
+
if (fs.existsSync(sibPath)) fs.unlinkSync(sibPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
298
328
|
return true;
|
|
299
329
|
}
|
|
300
330
|
//#endregion
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_model_download_service_RxAOiYvX = require("../model-download-service-RxAOiYvX-C8rTRJy_.js");
|
|
2
2
|
const require_dist = require("../dist-BLcTVvol.js");
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
|
-
node_path =
|
|
5
|
+
node_path = require_model_download_service_RxAOiYvX.__toESM(node_path);
|
|
6
6
|
let node_child_process = require("node:child_process");
|
|
7
7
|
//#region src/recorder/segment-path.ts
|
|
8
8
|
function segmentRelPath(deviceId, profile, startMs, durMs, bytes) {
|
|
@@ -1934,7 +1934,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
1934
1934
|
store: this.segmentStore
|
|
1935
1935
|
});
|
|
1936
1936
|
try {
|
|
1937
|
-
const handler =
|
|
1937
|
+
const handler = require_model_download_service_RxAOiYvX.createFileDataPlaneHandler({ getRoots: () => this.playbackRoots() });
|
|
1938
1938
|
const served = await this.ctx.dataPlane?.serve({
|
|
1939
1939
|
prefix: PLAYBACK_PREFIX,
|
|
1940
1940
|
access: "authenticated",
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $ as selectAssignedProfileSlots, B as BaseAddon, F as storageEvictableCapability, J as hydrateSchema, K as createDurableState, N as recordingCapability, O as migrateConfigToBands, W as EventCategory, d as RecordingConfigSchema, lt as record, ut as string, z as errMsg } from "../dist-BA6DR_jV.mjs";
|
|
2
|
-
import { t as createFileDataPlaneHandler } from "../model-download-service-
|
|
2
|
+
import { t as createFileDataPlaneHandler } from "../model-download-service-RxAOiYvX-CMAvhgO7.mjs";
|
|
3
3
|
import { promises } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.0
|
|
6
|
+
version: "1.1.0",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_stream_broker_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.0
|
|
21
|
+
version: "1.1.0",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.0
|
|
36
|
+
version: "1.1.0",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.0
|
|
39
|
+
version: "1.1.0",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.0
|
|
48
|
+
version: "1.1.0",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.0
|
|
84
|
+
version: "1.1.0",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -2,20 +2,20 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const
|
|
5
|
+
const require_model_download_service_RxAOiYvX = require("../model-download-service-RxAOiYvX-C8rTRJy_.js");
|
|
6
6
|
const require_dist = require("../dist-BLcTVvol.js");
|
|
7
7
|
const require_codec_runtime = require("../codec-runtime-BOk-13PN.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
|
-
node_fs =
|
|
9
|
+
node_fs = require_model_download_service_RxAOiYvX.__toESM(node_fs);
|
|
10
10
|
let node_path = require("node:path");
|
|
11
|
-
node_path =
|
|
11
|
+
node_path = require_model_download_service_RxAOiYvX.__toESM(node_path);
|
|
12
12
|
let node_os = require("node:os");
|
|
13
|
-
node_os =
|
|
13
|
+
node_os = require_model_download_service_RxAOiYvX.__toESM(node_os);
|
|
14
14
|
let node_crypto = require("node:crypto");
|
|
15
|
-
node_crypto =
|
|
15
|
+
node_crypto = require_model_download_service_RxAOiYvX.__toESM(node_crypto);
|
|
16
16
|
let node_child_process = require("node:child_process");
|
|
17
17
|
let node_net = require("node:net");
|
|
18
|
-
node_net =
|
|
18
|
+
node_net = require_model_download_service_RxAOiYvX.__toESM(node_net);
|
|
19
19
|
let net = require("net");
|
|
20
20
|
let events = require("events");
|
|
21
21
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -26,7 +26,7 @@ let node_events = require("node:events");
|
|
|
26
26
|
/** Build a `(req, res)` handler serving the embed bundle at `root` with SPA fallback.
|
|
27
27
|
* Web MIME types (`.html`/`.js`/`.css`/…) come from the core handler's defaults. */
|
|
28
28
|
function createEmbedSpaHandler(root) {
|
|
29
|
-
const fileHandler =
|
|
29
|
+
const fileHandler = require_model_download_service_RxAOiYvX.createFileDataPlaneHandler({ getRoots: () => [root] });
|
|
30
30
|
return (req, res) => {
|
|
31
31
|
const [pathPart = "/", query] = (req.url ?? "/").split("?");
|
|
32
32
|
const lastSegment = pathPart.split("/").pop() ?? "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
2
|
import { B as BaseAddon, D as maskUrlCredentials, G as asJsonObject, H as DeviceFeature, I as streamBrokerCapability, Q as parseProfileBrokerId, R as webrtcSessionCapability, U as DeviceType, V as CAM_PROFILE_ORDER, W as EventCategory, X as makeSourceBrokerId, Y as makeProfileBrokerId, c as EncodeProfileSchema, ct as object, dt as union, f as RingBuffer, it as discriminatedUnion, lt as record, m as addonWidgetsSourceCapability, ot as literal, q as createEvent, rt as boolean, st as number, tt as _enum, ut as string, v as cameraStreamsCapability, z as errMsg } from "../dist-BA6DR_jV.mjs";
|
|
3
|
-
import { t as createFileDataPlaneHandler } from "../model-download-service-
|
|
3
|
+
import { t as createFileDataPlaneHandler } from "../model-download-service-RxAOiYvX-CMAvhgO7.mjs";
|
|
4
4
|
import { t as DecodeRuntime } from "../codec-runtime-BsqlEjPi.mjs";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CamStack Pipeline bundle — runner, detection, motion, decoders, audio + stream broker. Multi-entry npm package shipping 7 addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
package/python/inference_pool.py
CHANGED
|
@@ -9,8 +9,10 @@ Architecture mirrors Scrypted's ML plugins (coreml / openvino / onnx):
|
|
|
9
9
|
- Runtime executors:
|
|
10
10
|
CoreML → ThreadPoolExecutor(1) — ANE is single-context; one
|
|
11
11
|
Python thread is enough, and avoids GIL thrashing.
|
|
12
|
-
OpenVINO → ThreadPoolExecutor(
|
|
13
|
-
|
|
12
|
+
OpenVINO → ThreadPoolExecutor(OPTIMAL_NUMBER_OF_INFER_REQUESTS),
|
|
13
|
+
each thread driving its own InferRequest, with the model
|
|
14
|
+
compiled under the THROUGHPUT hint so the streams run
|
|
15
|
+
concurrently (a single shared request serialises cameras).
|
|
14
16
|
ONNX → ThreadPoolExecutor(N) — N independent InferenceSessions
|
|
15
17
|
where N = concurrency setting; each session pinned to
|
|
16
18
|
its own worker thread.
|
|
@@ -160,6 +162,13 @@ class ModelSlot:
|
|
|
160
162
|
|
|
161
163
|
_runtime: str = ""
|
|
162
164
|
_runtime_lib: Any = None
|
|
165
|
+
# Max OPTIMAL_NUMBER_OF_INFER_REQUESTS across loaded OpenVINO models — used
|
|
166
|
+
# to size the predict pool so the THROUGHPUT streams are actually fed.
|
|
167
|
+
_ov_optimal_reqs: int = 0
|
|
168
|
+
# Default OpenVINO predict-pool size when models load lazily (so we can't yet
|
|
169
|
+
# query the device's optimal request count). ≈ measured optimal on Intel
|
|
170
|
+
# CPU/iGPU/NPU (4-5). Threads idle-block on infer, so over-provisioning is cheap.
|
|
171
|
+
OV_DEFAULT_CONCURRENCY: int = 4
|
|
163
172
|
|
|
164
173
|
|
|
165
174
|
def _init_runtime(runtime: str) -> None:
|
|
@@ -226,14 +235,48 @@ def _load_model(slot: ModelSlot, config: dict) -> None:
|
|
|
226
235
|
elif _runtime == "openvino":
|
|
227
236
|
core = _runtime_lib
|
|
228
237
|
ov_device = config.get("device", "AUTO").upper()
|
|
229
|
-
|
|
238
|
+
if ov_device == "AUTO":
|
|
239
|
+
# Scrypted-style device priority, built from the devices OpenVINO
|
|
240
|
+
# actually enumerates (more accurate than a hardware probe): NPU >
|
|
241
|
+
# GPU > CPU. The AUTO plugin handles runtime selection + failover.
|
|
242
|
+
# On a CPU-only image this is just AUTO:CPU; once the Intel GPU/NPU
|
|
243
|
+
# runtime is present it becomes AUTO:NPU,GPU,CPU automatically.
|
|
244
|
+
order = [d for d in ("NPU", "GPU", "CPU") if d in core.available_devices]
|
|
245
|
+
if order:
|
|
246
|
+
ov_device = "AUTO:" + ",".join(order)
|
|
247
|
+
# THROUGHPUT hint lets OpenVINO spin up multiple internal execution
|
|
248
|
+
# streams. Combined with one InferRequest per predict-pool thread
|
|
249
|
+
# (below), concurrent frames from N cameras run in parallel — the
|
|
250
|
+
# old `compiled(inp)` path drove a single shared default request, so
|
|
251
|
+
# every camera serialised through one stream regardless of how many
|
|
252
|
+
# predict workers existed. Measured ~1.5–2.4x throughput on CPU/GPU/NPU.
|
|
253
|
+
ov_config = {"PERFORMANCE_HINT": "THROUGHPUT"}
|
|
254
|
+
compiled = core.compile_model(path, device_name=ov_device, config=ov_config)
|
|
230
255
|
output_layers = [compiled.output(i) for i in range(len(compiled.outputs))]
|
|
231
256
|
output_names = [o.get_any_name() for o in compiled.outputs]
|
|
232
257
|
|
|
233
|
-
|
|
258
|
+
# Record the device's optimal infer-request count so the dispatcher
|
|
259
|
+
# can size the predict pool to actually feed the streams.
|
|
260
|
+
global _ov_optimal_reqs
|
|
261
|
+
try:
|
|
262
|
+
opt = int(compiled.get_property("OPTIMAL_NUMBER_OF_INFER_REQUESTS"))
|
|
263
|
+
if opt > _ov_optimal_reqs:
|
|
264
|
+
_ov_optimal_reqs = opt
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
# One InferRequest per predict thread (thread-local) — InferRequests
|
|
269
|
+
# are NOT safe to share across threads, and a per-thread request is
|
|
270
|
+
# what lets the THROUGHPUT streams run concurrently.
|
|
271
|
+
_ov_tls = threading.local()
|
|
272
|
+
|
|
273
|
+
def predict(inp_dict: dict, _c=compiled, _layers=output_layers, _names=output_names, _tls=_ov_tls) -> dict:
|
|
274
|
+
req = getattr(_tls, "req", None)
|
|
275
|
+
if req is None:
|
|
276
|
+
req = _tls.req = _c.create_infer_request()
|
|
234
277
|
inp = list(inp_dict.values())[0]
|
|
235
|
-
result =
|
|
236
|
-
return {name: result[layer] for name, layer in zip(
|
|
278
|
+
result = req.infer(inp)
|
|
279
|
+
return {name: result[layer] for name, layer in zip(_names, _layers)}
|
|
237
280
|
|
|
238
281
|
slot.model = compiled
|
|
239
282
|
slot.predict_fn = predict
|
|
@@ -772,6 +815,22 @@ async def _run() -> None:
|
|
|
772
815
|
sys.stderr.flush()
|
|
773
816
|
models.append(slot)
|
|
774
817
|
|
|
818
|
+
# OpenVINO: size the predict pool so the THROUGHPUT execution streams are
|
|
819
|
+
# actually fed by concurrent infer-requests. The addon passes
|
|
820
|
+
# concurrency=1 (it predates per-thread infer requests) and models load
|
|
821
|
+
# lazily AFTER this point, so we can't read OPTIMAL_NUMBER_OF_INFER_REQUESTS
|
|
822
|
+
# here — default to OV_DEFAULT_CONCURRENCY (≈ the measured optimal of 4-5
|
|
823
|
+
# on Intel CPU/iGPU/NPU). If startup-loaded models report a higher optimal,
|
|
824
|
+
# use that. One InferRequest is created per predict thread on first use.
|
|
825
|
+
if runtime == "openvino":
|
|
826
|
+
target = max(_ov_optimal_reqs, OV_DEFAULT_CONCURRENCY)
|
|
827
|
+
if target > concurrency:
|
|
828
|
+
sys.stderr.write(
|
|
829
|
+
f"OpenVINO THROUGHPUT: predict concurrency {concurrency} -> {target}\n"
|
|
830
|
+
)
|
|
831
|
+
sys.stderr.flush()
|
|
832
|
+
concurrency = target
|
|
833
|
+
|
|
775
834
|
dispatcher = RuntimeDispatcher(runtime, concurrency)
|
|
776
835
|
startup_ms = round((time.perf_counter() - t_start) * 1000)
|
|
777
836
|
loaded_count = sum(1 for s in models if s.loaded)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|