@camstack/addon-pipeline 1.1.65 → 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 +33 -11
- package/dist/detection-pipeline/index.mjs +33 -11
- package/dist/{dist-D6tHM9GA.mjs → dist-BpWot5-w.mjs} +13 -0
- package/dist/{dist-WW3gH8hF.js → dist-D5o9fGxq.js} +13 -0
- 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-CXZpwwT0.js → step-definitions-81eYyJbW.js} +127 -8
- package/dist/{step-definitions-Dbsm64_I.mjs → step-definitions-D6R9X0V7.mjs} +127 -8
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CbU_HuOC.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-QaDEOY0Z.mjs → hostInit-CeBNudbY.mjs} +2 -2
- package/dist/stream-broker/index.js +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C7H0kVAH.js → MaskShapeCanvas-DI4BY7W2-CbBbcdZb.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CHfEFlEF.js → MotionZonesSettings-NcxxQN8r-aA6zvms_.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Bc-trVxX.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/softmax.py +17 -5
- package/python/postprocessors/test_softmax.py +52 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CxZvj8X1.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-BzNJyeNE.mjs +0 -26
- package/embed-dist/assets/index-Bm9LaQPH.css +0 -2
- package/embed-dist/assets/index-eZGm_itM.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");
|
|
@@ -952,6 +952,8 @@ function serializeModelConfig(config) {
|
|
|
952
952
|
confidence: config.confidence
|
|
953
953
|
};
|
|
954
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;
|
|
955
957
|
if (config.inputWidth !== void 0) result["inputWidth"] = config.inputWidth;
|
|
956
958
|
if (config.inputHeight !== void 0) result["inputHeight"] = config.inputHeight;
|
|
957
959
|
if (config.inputChannels !== void 0) result["inputChannels"] = config.inputChannels;
|
|
@@ -1284,6 +1286,30 @@ var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
|
|
|
1284
1286
|
function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
|
|
1285
1287
|
return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
|
|
1286
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
|
+
}
|
|
1287
1313
|
var EngineFactory = class {
|
|
1288
1314
|
pool = null;
|
|
1289
1315
|
poolManager = null;
|
|
@@ -1451,14 +1477,7 @@ var EngineFactory = class {
|
|
|
1451
1477
|
const inputHeight = modelEntry.inputSize.height;
|
|
1452
1478
|
const inputSize = Math.max(inputWidth, inputHeight);
|
|
1453
1479
|
const inputChannels = modelEntry.inputChannels ?? 3;
|
|
1454
|
-
|
|
1455
|
-
if (!labels && modelEntry.extraFiles) {
|
|
1456
|
-
const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
|
|
1457
|
-
if (labelsFile) {
|
|
1458
|
-
const labelsPath = node_path.join(this.opts.modelsDir, labelsFile.filename);
|
|
1459
|
-
if (node_fs.existsSync(labelsPath)) labels = JSON.parse(node_fs.readFileSync(labelsPath, "utf8"));
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1480
|
+
const labels = readModelLabels(this.opts.modelsDir, modelEntry) ?? def.labels;
|
|
1462
1481
|
return {
|
|
1463
1482
|
path: modelPath,
|
|
1464
1483
|
inputSize,
|
|
@@ -1466,6 +1485,8 @@ var EngineFactory = class {
|
|
|
1466
1485
|
inputHeight,
|
|
1467
1486
|
inputChannels,
|
|
1468
1487
|
preprocessMode: modelEntry.preprocessMode ?? "letterbox",
|
|
1488
|
+
inputNormalization: modelEntry.inputNormalization,
|
|
1489
|
+
outputProbabilities: modelEntry.outputProbabilities,
|
|
1469
1490
|
postprocessor: modelEntry.postprocessor ?? def.postprocessor,
|
|
1470
1491
|
confidence: resolvePoolConfidenceFloor(stepId, def.defaultConfidence),
|
|
1471
1492
|
nmsIouThreshold: this.opts.nmsIouThreshold ?? .45,
|
|
@@ -2584,7 +2605,8 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
|
|
|
2584
2605
|
case "classifications": {
|
|
2585
2606
|
const rawEnabled = childStep.settings?.["enabledClasses"];
|
|
2586
2607
|
const enabledSet = Array.isArray(rawEnabled) && rawEnabled.length > 0 ? new Set(rawEnabled.filter((v) => typeof v === "string")) : null;
|
|
2587
|
-
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());
|
|
2588
2610
|
const minConfidence = typeof childStep.settings?.["minConfidence"] === "number" ? childStep.settings["minConfidence"] : 0;
|
|
2589
2611
|
const winner = output.classifications[0];
|
|
2590
2612
|
if (winner && passesClass(winner) && winner.score >= minConfidence) parent.ownLabels.push({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { $ as sleep, A as pipelineExecutorCapability, B as errMsg, C as evaluateZoneRules, F as runtimeDevices$1, G as createEvent, K as hydrateSchema, R as supportedRuntimes$1, S as detectionPipelineCapability, V as BaseAddon, X as parseJsonUnknown, b as defaultDeviceFor$1, dt as EventCategory, k as nodePin, m as YAMNET_TO_MACRO, t as APPLE_SA_TO_MACRO } from "../dist-
|
|
3
|
-
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-
|
|
2
|
+
import { $ as sleep, A as pipelineExecutorCapability, B as errMsg, C as evaluateZoneRules, F as runtimeDevices$1, G as createEvent, K as hydrateSchema, R as supportedRuntimes$1, S as detectionPipelineCapability, V as BaseAddon, X as parseJsonUnknown, b as defaultDeviceFor$1, dt as EventCategory, k as nodePin, lt as string, m as YAMNET_TO_MACRO, st as object, t as APPLE_SA_TO_MACRO, tt as array, ut as union } from "../dist-BpWot5-w.mjs";
|
|
3
|
+
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-D6R9X0V7.mjs";
|
|
4
4
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
5
5
|
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -944,6 +944,8 @@ function serializeModelConfig(config) {
|
|
|
944
944
|
confidence: config.confidence
|
|
945
945
|
};
|
|
946
946
|
if (config.nmsIouThreshold !== void 0) result["nmsIouThreshold"] = config.nmsIouThreshold;
|
|
947
|
+
if (config.inputNormalization !== void 0) result["inputNormalization"] = config.inputNormalization;
|
|
948
|
+
if (config.outputProbabilities !== void 0) result["outputProbabilities"] = config.outputProbabilities;
|
|
947
949
|
if (config.inputWidth !== void 0) result["inputWidth"] = config.inputWidth;
|
|
948
950
|
if (config.inputHeight !== void 0) result["inputHeight"] = config.inputHeight;
|
|
949
951
|
if (config.inputChannels !== void 0) result["inputChannels"] = config.inputChannels;
|
|
@@ -1276,6 +1278,30 @@ var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
|
|
|
1276
1278
|
function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
|
|
1277
1279
|
return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
|
|
1278
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Load a model's runtime label list from its `-labels.json` extraFile, if any.
|
|
1283
|
+
*
|
|
1284
|
+
* Accepts BOTH on-disk shapes:
|
|
1285
|
+
* - a flat `string[]` (legacy label files), and
|
|
1286
|
+
* - a `{ "labels": string[], ... }` metadata object (the newer format the
|
|
1287
|
+
* animal / bird / vehicle classifiers ship, which also carries preprocess /
|
|
1288
|
+
* license metadata alongside the label array).
|
|
1289
|
+
*
|
|
1290
|
+
* Returns `undefined` when the model declares no `-labels.json`, the file is
|
|
1291
|
+
* absent, or its contents are not a recognised label shape — the caller then
|
|
1292
|
+
* falls back to the step's `def.labels`.
|
|
1293
|
+
*/
|
|
1294
|
+
var LabelsFileSchema = union([array(string()).readonly(), object({ labels: array(string()).readonly() })]);
|
|
1295
|
+
function readModelLabels(modelsDir, modelEntry) {
|
|
1296
|
+
const labelsFile = modelEntry.extraFiles?.find((f) => f.filename.endsWith("-labels.json"));
|
|
1297
|
+
if (!labelsFile) return void 0;
|
|
1298
|
+
const labelsPath = path$1.join(modelsDir, labelsFile.filename);
|
|
1299
|
+
if (!fs.existsSync(labelsPath)) return void 0;
|
|
1300
|
+
const parsed = LabelsFileSchema.safeParse(JSON.parse(fs.readFileSync(labelsPath, "utf8")));
|
|
1301
|
+
if (!parsed.success) return void 0;
|
|
1302
|
+
const data = parsed.data;
|
|
1303
|
+
return "labels" in data ? data.labels : data;
|
|
1304
|
+
}
|
|
1279
1305
|
var EngineFactory = class {
|
|
1280
1306
|
pool = null;
|
|
1281
1307
|
poolManager = null;
|
|
@@ -1443,14 +1469,7 @@ var EngineFactory = class {
|
|
|
1443
1469
|
const inputHeight = modelEntry.inputSize.height;
|
|
1444
1470
|
const inputSize = Math.max(inputWidth, inputHeight);
|
|
1445
1471
|
const inputChannels = modelEntry.inputChannels ?? 3;
|
|
1446
|
-
|
|
1447
|
-
if (!labels && modelEntry.extraFiles) {
|
|
1448
|
-
const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
|
|
1449
|
-
if (labelsFile) {
|
|
1450
|
-
const labelsPath = path$1.join(this.opts.modelsDir, labelsFile.filename);
|
|
1451
|
-
if (fs.existsSync(labelsPath)) labels = JSON.parse(fs.readFileSync(labelsPath, "utf8"));
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1472
|
+
const labels = readModelLabels(this.opts.modelsDir, modelEntry) ?? def.labels;
|
|
1454
1473
|
return {
|
|
1455
1474
|
path: modelPath,
|
|
1456
1475
|
inputSize,
|
|
@@ -1458,6 +1477,8 @@ var EngineFactory = class {
|
|
|
1458
1477
|
inputHeight,
|
|
1459
1478
|
inputChannels,
|
|
1460
1479
|
preprocessMode: modelEntry.preprocessMode ?? "letterbox",
|
|
1480
|
+
inputNormalization: modelEntry.inputNormalization,
|
|
1481
|
+
outputProbabilities: modelEntry.outputProbabilities,
|
|
1461
1482
|
postprocessor: modelEntry.postprocessor ?? def.postprocessor,
|
|
1462
1483
|
confidence: resolvePoolConfidenceFloor(stepId, def.defaultConfidence),
|
|
1463
1484
|
nmsIouThreshold: this.opts.nmsIouThreshold ?? .45,
|
|
@@ -2576,7 +2597,8 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
|
|
|
2576
2597
|
case "classifications": {
|
|
2577
2598
|
const rawEnabled = childStep.settings?.["enabledClasses"];
|
|
2578
2599
|
const enabledSet = Array.isArray(rawEnabled) && rawEnabled.length > 0 ? new Set(rawEnabled.filter((v) => typeof v === "string")) : null;
|
|
2579
|
-
const
|
|
2600
|
+
const rejectSet = childStep.definition.rejectClasses && childStep.definition.rejectClasses.length > 0 ? new Set(childStep.definition.rejectClasses.map((c) => c.toLowerCase())) : /* @__PURE__ */ new Set();
|
|
2601
|
+
const passesClass = (c) => (enabledSet === null || enabledSet.has(c.class)) && !rejectSet.has(c.class.toLowerCase());
|
|
2580
2602
|
const minConfidence = typeof childStep.settings?.["minConfidence"] === "number" ? childStep.settings["minConfidence"] : 0;
|
|
2581
2603
|
const winner = output.classifications[0];
|
|
2582
2604
|
if (winner && passesClass(winner) && winner.score >= minConfidence) parent.ownLabels.push({
|
|
@@ -7207,6 +7207,17 @@ var ModelCatalogEntrySchema = object({
|
|
|
7207
7207
|
"imagenet",
|
|
7208
7208
|
"none"
|
|
7209
7209
|
]).optional(),
|
|
7210
|
+
/**
|
|
7211
|
+
* The model already applies softmax IN-GRAPH — its raw output is a
|
|
7212
|
+
* probability distribution, not logits. When set, the `softmax`
|
|
7213
|
+
* postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
|
|
7214
|
+
* probability vector collapses it toward uniform (top-1 score craters far
|
|
7215
|
+
* below its true value, making every confidence gate meaningless). Absent ⇒
|
|
7216
|
+
* the output is raw logits and the postprocessor applies softmax (the normal
|
|
7217
|
+
* case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
|
|
7218
|
+
* TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
|
|
7219
|
+
*/
|
|
7220
|
+
outputProbabilities: boolean().optional(),
|
|
7210
7221
|
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
7211
7222
|
/**
|
|
7212
7223
|
* Per-MODEL postprocessor override. Absent ⇒ the step's own
|
|
@@ -14087,6 +14098,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
14087
14098
|
kind: "mutation",
|
|
14088
14099
|
auth: "admin"
|
|
14089
14100
|
});
|
|
14101
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
|
|
14102
|
+
new Set(Object.values(DeviceType));
|
|
14090
14103
|
/**
|
|
14091
14104
|
* Evaluate `rules` against `items`. Returns the items partitioned
|
|
14092
14105
|
* into `passed` (kept by the gate) and `excluded` (dropped). When
|
|
@@ -7207,6 +7207,17 @@ var ModelCatalogEntrySchema = object({
|
|
|
7207
7207
|
"imagenet",
|
|
7208
7208
|
"none"
|
|
7209
7209
|
]).optional(),
|
|
7210
|
+
/**
|
|
7211
|
+
* The model already applies softmax IN-GRAPH — its raw output is a
|
|
7212
|
+
* probability distribution, not logits. When set, the `softmax`
|
|
7213
|
+
* postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
|
|
7214
|
+
* probability vector collapses it toward uniform (top-1 score craters far
|
|
7215
|
+
* below its true value, making every confidence gate meaningless). Absent ⇒
|
|
7216
|
+
* the output is raw logits and the postprocessor applies softmax (the normal
|
|
7217
|
+
* case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
|
|
7218
|
+
* TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
|
|
7219
|
+
*/
|
|
7220
|
+
outputProbabilities: boolean().optional(),
|
|
7210
7221
|
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
7211
7222
|
/**
|
|
7212
7223
|
* Per-MODEL postprocessor override. Absent ⇒ the step's own
|
|
@@ -14087,6 +14098,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
14087
14098
|
kind: "mutation",
|
|
14088
14099
|
auth: "admin"
|
|
14089
14100
|
});
|
|
14101
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
|
|
14102
|
+
new Set(Object.values(DeviceType));
|
|
14090
14103
|
/**
|
|
14091
14104
|
* Evaluate `rules` against `items`. Returns the items partitioned
|
|
14092
14105
|
* into `passed` (kept by the gate) and `excluded` (dropped). When
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-D5o9fGxq.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as evaluateZoneRules, K as hydrateSchema, O as motionDetectionCapability, V as BaseAddon, W as DeviceType } from "../dist-
|
|
1
|
+
import { C as evaluateZoneRules, K as hydrateSchema, O as motionDetectionCapability, V as BaseAddon, W as DeviceType } from "../dist-BpWot5-w.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -3,11 +3,11 @@ 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_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_sheet_geometry = require("../sheet-geometry-BR-ip0CP.js");
|
|
9
9
|
const require_worker_protocol = require("../worker-protocol-PP4jKHHJ.js");
|
|
10
|
-
const require_step_definitions = require("../step-definitions-
|
|
10
|
+
const require_step_definitions = require("../step-definitions-81eYyJbW.js");
|
|
11
11
|
let node_child_process = require("node:child_process");
|
|
12
12
|
let node_url = require("node:url");
|
|
13
13
|
let sharp = require("sharp");
|
|
@@ -1248,23 +1248,39 @@ function startGuardedSessionDecode(deps) {
|
|
|
1248
1248
|
/** {@link RETAINED_FRAME_BUDGET_MB} in bytes. */
|
|
1249
1249
|
var RETAINED_FRAME_BUDGET_BYTES = (() => {
|
|
1250
1250
|
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_MB"]);
|
|
1251
|
-
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) :
|
|
1251
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 96;
|
|
1252
1252
|
})() * 1024 * 1024;
|
|
1253
|
+
/**
|
|
1254
|
+
* How many of each device's MOST RECENT frames are protected from a busy
|
|
1255
|
+
* neighbour's budget eviction. Sized to cover the late two-plane / cross-process
|
|
1256
|
+
* enrichment horizon (~150-500 ms): 6 frames ≈ 0.6 s @10 fps of guaranteed
|
|
1257
|
+
* survival for every active camera. Read ONCE from
|
|
1258
|
+
* `CAMSTACK_SESSION_RETAIN_FLOOR`; `0` disables the floor (pure global FIFO — the
|
|
1259
|
+
* pre-2026-07-19 behaviour). The hard byte ceiling always overrides the floor,
|
|
1260
|
+
* so this can never push retention past {@link RETAINED_FRAME_BUDGET_BYTES}.
|
|
1261
|
+
*/
|
|
1262
|
+
var RETAINED_FRAME_PER_DEVICE_FLOOR = (() => {
|
|
1263
|
+
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_FLOOR"]);
|
|
1264
|
+
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 6;
|
|
1265
|
+
})();
|
|
1253
1266
|
/** Opaque RAM-key prefix stamped into a retained frame's `FrameHandle.shmId`. */
|
|
1254
1267
|
var RETAINED_FRAME_KEY_PREFIX = "ram:";
|
|
1255
1268
|
/** A retention frame is uniquely identified by its key + slot + committed seq. */
|
|
1256
1269
|
function keyOf(handle) {
|
|
1257
1270
|
return `${handle.shmId}#${handle.slot}#${handle.seq}`;
|
|
1258
1271
|
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Node-level RAM store of retained rgb detection frames, keyed by
|
|
1261
|
-
* `FrameHandle` identity. Latest-wins with a byte budget.
|
|
1262
|
-
*/
|
|
1263
1272
|
var RetainedFrameStore = class RetainedFrameStore {
|
|
1264
1273
|
nodeId;
|
|
1265
1274
|
budgetBytes;
|
|
1266
|
-
|
|
1275
|
+
perDeviceFloor;
|
|
1276
|
+
/** Insertion-ordered (Map) → globally oldest is `keys().next()`. */
|
|
1267
1277
|
entries = /* @__PURE__ */ new Map();
|
|
1278
|
+
/**
|
|
1279
|
+
* Per-device insertion-ordered keys (oldest→newest). Mirrors {@link entries}
|
|
1280
|
+
* so the floor can be enforced in O(1) per eviction candidate without scanning
|
|
1281
|
+
* the global map for a device's frame count.
|
|
1282
|
+
*/
|
|
1283
|
+
perDeviceKeys = /* @__PURE__ */ new Map();
|
|
1268
1284
|
bytes = 0;
|
|
1269
1285
|
seq = 0;
|
|
1270
1286
|
/** Ring width used only to spread the `slot` field; the map key is authoritative. */
|
|
@@ -1272,6 +1288,7 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1272
1288
|
constructor(options) {
|
|
1273
1289
|
this.nodeId = options.nodeId;
|
|
1274
1290
|
this.budgetBytes = options.budgetBytes ?? RETAINED_FRAME_BUDGET_BYTES;
|
|
1291
|
+
this.perDeviceFloor = options.perDeviceFloor ?? RETAINED_FRAME_PER_DEVICE_FLOOR;
|
|
1275
1292
|
}
|
|
1276
1293
|
/** Number of retained frames (tests / metrics). */
|
|
1277
1294
|
get size() {
|
|
@@ -1304,14 +1321,21 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1304
1321
|
nodeId: this.nodeId,
|
|
1305
1322
|
slotCount: RetainedFrameStore.SLOT_SPREAD
|
|
1306
1323
|
};
|
|
1307
|
-
|
|
1324
|
+
const key = keyOf(handle);
|
|
1325
|
+
this.entries.set(key, {
|
|
1326
|
+
frame,
|
|
1327
|
+
deviceId
|
|
1328
|
+
});
|
|
1329
|
+
const deviceKeys = this.perDeviceKeys.get(deviceId);
|
|
1330
|
+
if (deviceKeys) deviceKeys.push(key);
|
|
1331
|
+
else this.perDeviceKeys.set(deviceId, [key]);
|
|
1308
1332
|
this.bytes += byteLength;
|
|
1309
1333
|
this.evictToBudget();
|
|
1310
1334
|
return handle;
|
|
1311
1335
|
}
|
|
1312
1336
|
/** The retained frame for `handle`, or `null` if never retained / already evicted. */
|
|
1313
1337
|
getFrame(handle) {
|
|
1314
|
-
return this.entries.get(keyOf(handle)) ?? null;
|
|
1338
|
+
return this.entries.get(keyOf(handle))?.frame ?? null;
|
|
1315
1339
|
}
|
|
1316
1340
|
/**
|
|
1317
1341
|
* Crop (and optionally downscale) the retained rgb frame for `handle` to the
|
|
@@ -1322,26 +1346,56 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1322
1346
|
* decode worker's surface (`getNativeCrop`), not here.
|
|
1323
1347
|
*/
|
|
1324
1348
|
crop(handle, bbox, maxWidth) {
|
|
1325
|
-
const
|
|
1326
|
-
if (!
|
|
1327
|
-
return cropRgb(frame, bbox, maxWidth);
|
|
1349
|
+
const entry = this.entries.get(keyOf(handle));
|
|
1350
|
+
if (!entry || entry.frame.format !== "rgb") return null;
|
|
1351
|
+
return cropRgb(entry.frame, bbox, maxWidth);
|
|
1328
1352
|
}
|
|
1329
1353
|
/** Drop every retained frame (node/addon shutdown). */
|
|
1330
1354
|
clear() {
|
|
1331
1355
|
this.entries.clear();
|
|
1356
|
+
this.perDeviceKeys.clear();
|
|
1332
1357
|
this.bytes = 0;
|
|
1333
1358
|
}
|
|
1334
|
-
/**
|
|
1359
|
+
/**
|
|
1360
|
+
* Evict frames until the retained bytes fit the budget (keep ≥1).
|
|
1361
|
+
*
|
|
1362
|
+
* Pass 1 (fairness): walk globally oldest→newest and drop only frames whose
|
|
1363
|
+
* device still holds MORE than {@link perDeviceFloor} frames — so a busy
|
|
1364
|
+
* camera's excess is reclaimed before any camera's protected recent window.
|
|
1365
|
+
* Pass 2 (OOM guard): if still over budget (every remaining frame is
|
|
1366
|
+
* floor-protected yet the ceiling is exhausted), drop globally oldest-first
|
|
1367
|
+
* regardless of the floor. The ceiling ALWAYS wins over fairness.
|
|
1368
|
+
*/
|
|
1335
1369
|
evictToBudget() {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
this.entries.
|
|
1341
|
-
if (
|
|
1370
|
+
if (this.bytes <= this.budgetBytes) return;
|
|
1371
|
+
const oldestFirst = [...this.entries.keys()];
|
|
1372
|
+
for (const key of oldestFirst) {
|
|
1373
|
+
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1374
|
+
const entry = this.entries.get(key);
|
|
1375
|
+
if (!entry) continue;
|
|
1376
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
1377
|
+
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key);
|
|
1378
|
+
}
|
|
1379
|
+
if (this.bytes <= this.budgetBytes) return;
|
|
1380
|
+
for (const key of oldestFirst) {
|
|
1381
|
+
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1382
|
+
this.dropKey(key);
|
|
1342
1383
|
}
|
|
1343
1384
|
if (this.bytes < 0) this.bytes = 0;
|
|
1344
1385
|
}
|
|
1386
|
+
/** Remove one frame by key, keeping the byte tally + per-device index exact. */
|
|
1387
|
+
dropKey(key) {
|
|
1388
|
+
const entry = this.entries.get(key);
|
|
1389
|
+
if (!entry) return;
|
|
1390
|
+
this.entries.delete(key);
|
|
1391
|
+
this.bytes -= entry.frame.data.byteLength;
|
|
1392
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
1393
|
+
if (deviceKeys) {
|
|
1394
|
+
const idx = deviceKeys.indexOf(key);
|
|
1395
|
+
if (idx >= 0) deviceKeys.splice(idx, 1);
|
|
1396
|
+
if (deviceKeys.length === 0) this.perDeviceKeys.delete(entry.deviceId);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1345
1399
|
};
|
|
1346
1400
|
/**
|
|
1347
1401
|
* Nearest-neighbour crop + optional downscale of a packed RGB24 frame to a
|
|
@@ -1931,6 +1985,18 @@ function startSessionDecodePump(deps) {
|
|
|
1931
1985
|
iterator.return().catch(() => {});
|
|
1932
1986
|
};
|
|
1933
1987
|
}
|
|
1988
|
+
//#endregion
|
|
1989
|
+
//#region src/session-decode/native-crop-registry.ts
|
|
1990
|
+
/**
|
|
1991
|
+
* Default max entries retained across all active decode sessions on a node. Read
|
|
1992
|
+
* ONCE from `CAMSTACK_NATIVE_CROP_REGISTRY_CAP`; a non-finite / non-positive
|
|
1993
|
+
* value falls back to 256 (was a fixed 64 — too small for a busy multi-camera
|
|
1994
|
+
* node, see class doc).
|
|
1995
|
+
*/
|
|
1996
|
+
var DEFAULT_NATIVE_CROP_REGISTRY_CAP = (() => {
|
|
1997
|
+
const raw = Number(process.env["CAMSTACK_NATIVE_CROP_REGISTRY_CAP"]);
|
|
1998
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 256;
|
|
1999
|
+
})();
|
|
1934
2000
|
/** Invoke a requester's best-effort `release()`, swallowing any transport error. */
|
|
1935
2001
|
function safeRelease(request) {
|
|
1936
2002
|
try {
|
|
@@ -1940,7 +2006,7 @@ function safeRelease(request) {
|
|
|
1940
2006
|
var NativeCropRegistry = class {
|
|
1941
2007
|
capacity;
|
|
1942
2008
|
entries = /* @__PURE__ */ new Map();
|
|
1943
|
-
constructor(capacity =
|
|
2009
|
+
constructor(capacity = DEFAULT_NATIVE_CROP_REGISTRY_CAP) {
|
|
1944
2010
|
this.capacity = capacity;
|
|
1945
2011
|
}
|
|
1946
2012
|
/** Number of registered handles (tests / metrics). */
|
|
@@ -3106,7 +3172,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3106
3172
|
*/
|
|
3107
3173
|
async getNativeCrop(input) {
|
|
3108
3174
|
const b = input.bbox;
|
|
3109
|
-
if (!(b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999)) {
|
|
3175
|
+
if (!(b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999 && input.maxWidth === void 0)) {
|
|
3110
3176
|
const requester = this.nativeCropRegistry.get(input.handle);
|
|
3111
3177
|
if (requester) {
|
|
3112
3178
|
const result = await requester(input.bbox, input.maxWidth);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { B as errMsg, G as createEvent, V as BaseAddon, dt as EventCategory, et as _enum, it as lazy, j as pipelineRunnerCapability, k as nodePin, lt as string, nt as boolean, ot as number, st as object, tt as array, x as defineCustomActions, y as customAction } from "../dist-
|
|
1
|
+
import { B as errMsg, G as createEvent, V as BaseAddon, dt as EventCategory, et as _enum, it as lazy, j as pipelineRunnerCapability, k as nodePin, lt as string, nt as boolean, ot as number, st as object, tt as array, x as defineCustomActions, y as customAction } from "../dist-BpWot5-w.mjs";
|
|
2
2
|
import { n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-BeHi78PZ.mjs";
|
|
3
3
|
import { r as resolveHubHostname } from "../sheet-geometry-DcpuGTCm.mjs";
|
|
4
4
|
import { t as isWorkerReply } from "../worker-protocol-CyVJTZEO.mjs";
|
|
5
|
-
import { a as getStepDefinition } from "../step-definitions-
|
|
5
|
+
import { a as getStepDefinition } from "../step-definitions-D6R9X0V7.mjs";
|
|
6
6
|
import { fork } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import sharp from "sharp";
|
|
@@ -1242,23 +1242,39 @@ function startGuardedSessionDecode(deps) {
|
|
|
1242
1242
|
/** {@link RETAINED_FRAME_BUDGET_MB} in bytes. */
|
|
1243
1243
|
var RETAINED_FRAME_BUDGET_BYTES = (() => {
|
|
1244
1244
|
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_MB"]);
|
|
1245
|
-
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) :
|
|
1245
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 96;
|
|
1246
1246
|
})() * 1024 * 1024;
|
|
1247
|
+
/**
|
|
1248
|
+
* How many of each device's MOST RECENT frames are protected from a busy
|
|
1249
|
+
* neighbour's budget eviction. Sized to cover the late two-plane / cross-process
|
|
1250
|
+
* enrichment horizon (~150-500 ms): 6 frames ≈ 0.6 s @10 fps of guaranteed
|
|
1251
|
+
* survival for every active camera. Read ONCE from
|
|
1252
|
+
* `CAMSTACK_SESSION_RETAIN_FLOOR`; `0` disables the floor (pure global FIFO — the
|
|
1253
|
+
* pre-2026-07-19 behaviour). The hard byte ceiling always overrides the floor,
|
|
1254
|
+
* so this can never push retention past {@link RETAINED_FRAME_BUDGET_BYTES}.
|
|
1255
|
+
*/
|
|
1256
|
+
var RETAINED_FRAME_PER_DEVICE_FLOOR = (() => {
|
|
1257
|
+
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_FLOOR"]);
|
|
1258
|
+
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 6;
|
|
1259
|
+
})();
|
|
1247
1260
|
/** Opaque RAM-key prefix stamped into a retained frame's `FrameHandle.shmId`. */
|
|
1248
1261
|
var RETAINED_FRAME_KEY_PREFIX = "ram:";
|
|
1249
1262
|
/** A retention frame is uniquely identified by its key + slot + committed seq. */
|
|
1250
1263
|
function keyOf(handle) {
|
|
1251
1264
|
return `${handle.shmId}#${handle.slot}#${handle.seq}`;
|
|
1252
1265
|
}
|
|
1253
|
-
/**
|
|
1254
|
-
* Node-level RAM store of retained rgb detection frames, keyed by
|
|
1255
|
-
* `FrameHandle` identity. Latest-wins with a byte budget.
|
|
1256
|
-
*/
|
|
1257
1266
|
var RetainedFrameStore = class RetainedFrameStore {
|
|
1258
1267
|
nodeId;
|
|
1259
1268
|
budgetBytes;
|
|
1260
|
-
|
|
1269
|
+
perDeviceFloor;
|
|
1270
|
+
/** Insertion-ordered (Map) → globally oldest is `keys().next()`. */
|
|
1261
1271
|
entries = /* @__PURE__ */ new Map();
|
|
1272
|
+
/**
|
|
1273
|
+
* Per-device insertion-ordered keys (oldest→newest). Mirrors {@link entries}
|
|
1274
|
+
* so the floor can be enforced in O(1) per eviction candidate without scanning
|
|
1275
|
+
* the global map for a device's frame count.
|
|
1276
|
+
*/
|
|
1277
|
+
perDeviceKeys = /* @__PURE__ */ new Map();
|
|
1262
1278
|
bytes = 0;
|
|
1263
1279
|
seq = 0;
|
|
1264
1280
|
/** Ring width used only to spread the `slot` field; the map key is authoritative. */
|
|
@@ -1266,6 +1282,7 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1266
1282
|
constructor(options) {
|
|
1267
1283
|
this.nodeId = options.nodeId;
|
|
1268
1284
|
this.budgetBytes = options.budgetBytes ?? RETAINED_FRAME_BUDGET_BYTES;
|
|
1285
|
+
this.perDeviceFloor = options.perDeviceFloor ?? RETAINED_FRAME_PER_DEVICE_FLOOR;
|
|
1269
1286
|
}
|
|
1270
1287
|
/** Number of retained frames (tests / metrics). */
|
|
1271
1288
|
get size() {
|
|
@@ -1298,14 +1315,21 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1298
1315
|
nodeId: this.nodeId,
|
|
1299
1316
|
slotCount: RetainedFrameStore.SLOT_SPREAD
|
|
1300
1317
|
};
|
|
1301
|
-
|
|
1318
|
+
const key = keyOf(handle);
|
|
1319
|
+
this.entries.set(key, {
|
|
1320
|
+
frame,
|
|
1321
|
+
deviceId
|
|
1322
|
+
});
|
|
1323
|
+
const deviceKeys = this.perDeviceKeys.get(deviceId);
|
|
1324
|
+
if (deviceKeys) deviceKeys.push(key);
|
|
1325
|
+
else this.perDeviceKeys.set(deviceId, [key]);
|
|
1302
1326
|
this.bytes += byteLength;
|
|
1303
1327
|
this.evictToBudget();
|
|
1304
1328
|
return handle;
|
|
1305
1329
|
}
|
|
1306
1330
|
/** The retained frame for `handle`, or `null` if never retained / already evicted. */
|
|
1307
1331
|
getFrame(handle) {
|
|
1308
|
-
return this.entries.get(keyOf(handle)) ?? null;
|
|
1332
|
+
return this.entries.get(keyOf(handle))?.frame ?? null;
|
|
1309
1333
|
}
|
|
1310
1334
|
/**
|
|
1311
1335
|
* Crop (and optionally downscale) the retained rgb frame for `handle` to the
|
|
@@ -1316,26 +1340,56 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1316
1340
|
* decode worker's surface (`getNativeCrop`), not here.
|
|
1317
1341
|
*/
|
|
1318
1342
|
crop(handle, bbox, maxWidth) {
|
|
1319
|
-
const
|
|
1320
|
-
if (!
|
|
1321
|
-
return cropRgb(frame, bbox, maxWidth);
|
|
1343
|
+
const entry = this.entries.get(keyOf(handle));
|
|
1344
|
+
if (!entry || entry.frame.format !== "rgb") return null;
|
|
1345
|
+
return cropRgb(entry.frame, bbox, maxWidth);
|
|
1322
1346
|
}
|
|
1323
1347
|
/** Drop every retained frame (node/addon shutdown). */
|
|
1324
1348
|
clear() {
|
|
1325
1349
|
this.entries.clear();
|
|
1350
|
+
this.perDeviceKeys.clear();
|
|
1326
1351
|
this.bytes = 0;
|
|
1327
1352
|
}
|
|
1328
|
-
/**
|
|
1353
|
+
/**
|
|
1354
|
+
* Evict frames until the retained bytes fit the budget (keep ≥1).
|
|
1355
|
+
*
|
|
1356
|
+
* Pass 1 (fairness): walk globally oldest→newest and drop only frames whose
|
|
1357
|
+
* device still holds MORE than {@link perDeviceFloor} frames — so a busy
|
|
1358
|
+
* camera's excess is reclaimed before any camera's protected recent window.
|
|
1359
|
+
* Pass 2 (OOM guard): if still over budget (every remaining frame is
|
|
1360
|
+
* floor-protected yet the ceiling is exhausted), drop globally oldest-first
|
|
1361
|
+
* regardless of the floor. The ceiling ALWAYS wins over fairness.
|
|
1362
|
+
*/
|
|
1329
1363
|
evictToBudget() {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
this.entries.
|
|
1335
|
-
if (
|
|
1364
|
+
if (this.bytes <= this.budgetBytes) return;
|
|
1365
|
+
const oldestFirst = [...this.entries.keys()];
|
|
1366
|
+
for (const key of oldestFirst) {
|
|
1367
|
+
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1368
|
+
const entry = this.entries.get(key);
|
|
1369
|
+
if (!entry) continue;
|
|
1370
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
1371
|
+
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key);
|
|
1372
|
+
}
|
|
1373
|
+
if (this.bytes <= this.budgetBytes) return;
|
|
1374
|
+
for (const key of oldestFirst) {
|
|
1375
|
+
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1376
|
+
this.dropKey(key);
|
|
1336
1377
|
}
|
|
1337
1378
|
if (this.bytes < 0) this.bytes = 0;
|
|
1338
1379
|
}
|
|
1380
|
+
/** Remove one frame by key, keeping the byte tally + per-device index exact. */
|
|
1381
|
+
dropKey(key) {
|
|
1382
|
+
const entry = this.entries.get(key);
|
|
1383
|
+
if (!entry) return;
|
|
1384
|
+
this.entries.delete(key);
|
|
1385
|
+
this.bytes -= entry.frame.data.byteLength;
|
|
1386
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
1387
|
+
if (deviceKeys) {
|
|
1388
|
+
const idx = deviceKeys.indexOf(key);
|
|
1389
|
+
if (idx >= 0) deviceKeys.splice(idx, 1);
|
|
1390
|
+
if (deviceKeys.length === 0) this.perDeviceKeys.delete(entry.deviceId);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1339
1393
|
};
|
|
1340
1394
|
/**
|
|
1341
1395
|
* Nearest-neighbour crop + optional downscale of a packed RGB24 frame to a
|
|
@@ -1925,6 +1979,18 @@ function startSessionDecodePump(deps) {
|
|
|
1925
1979
|
iterator.return().catch(() => {});
|
|
1926
1980
|
};
|
|
1927
1981
|
}
|
|
1982
|
+
//#endregion
|
|
1983
|
+
//#region src/session-decode/native-crop-registry.ts
|
|
1984
|
+
/**
|
|
1985
|
+
* Default max entries retained across all active decode sessions on a node. Read
|
|
1986
|
+
* ONCE from `CAMSTACK_NATIVE_CROP_REGISTRY_CAP`; a non-finite / non-positive
|
|
1987
|
+
* value falls back to 256 (was a fixed 64 — too small for a busy multi-camera
|
|
1988
|
+
* node, see class doc).
|
|
1989
|
+
*/
|
|
1990
|
+
var DEFAULT_NATIVE_CROP_REGISTRY_CAP = (() => {
|
|
1991
|
+
const raw = Number(process.env["CAMSTACK_NATIVE_CROP_REGISTRY_CAP"]);
|
|
1992
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 256;
|
|
1993
|
+
})();
|
|
1928
1994
|
/** Invoke a requester's best-effort `release()`, swallowing any transport error. */
|
|
1929
1995
|
function safeRelease(request) {
|
|
1930
1996
|
try {
|
|
@@ -1934,7 +2000,7 @@ function safeRelease(request) {
|
|
|
1934
2000
|
var NativeCropRegistry = class {
|
|
1935
2001
|
capacity;
|
|
1936
2002
|
entries = /* @__PURE__ */ new Map();
|
|
1937
|
-
constructor(capacity =
|
|
2003
|
+
constructor(capacity = DEFAULT_NATIVE_CROP_REGISTRY_CAP) {
|
|
1938
2004
|
this.capacity = capacity;
|
|
1939
2005
|
}
|
|
1940
2006
|
/** Number of registered handles (tests / metrics). */
|
|
@@ -3100,7 +3166,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3100
3166
|
*/
|
|
3101
3167
|
async getNativeCrop(input) {
|
|
3102
3168
|
const b = input.bbox;
|
|
3103
|
-
if (!(b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999)) {
|
|
3169
|
+
if (!(b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999 && input.maxWidth === void 0)) {
|
|
3104
3170
|
const requester = this.nativeCropRegistry.get(input.handle);
|
|
3105
3171
|
if (requester) {
|
|
3106
3172
|
const result = await requester(input.bbox, input.maxWidth);
|