@camstack/addon-pipeline 1.2.69 → 1.2.71
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 +32 -30
- package/dist/detection-pipeline/index.mjs +32 -30
- package/dist/{dist-tJF2BzWi.js → dist-CtOVWTCi.js} +586 -41
- package/dist/{dist-DGLqaonv.mjs → dist-D1Xk5Tji.mjs} +586 -41
- package/dist/{event-loop-stall-monitor-D9d2e49f.js → event-loop-stall-monitor-BJs7Yws7.js} +1 -1
- package/dist/{event-loop-stall-monitor-C6_VDupx.mjs → event-loop-stall-monitor-Cmbt664E.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +100 -30
- package/dist/pipeline-runner/index.mjs +100 -30
- package/dist/recorder/index.js +734 -104
- package/dist/recorder/index.mjs +734 -104
- package/dist/session-decode/decode-worker-child.js +483 -52
- package/dist/session-decode/decode-worker-child.mjs +483 -52
- package/dist/stream-broker/_stub.js +660 -556
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DCyQ7RKH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BWf4H4Nl.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cu_kQpIy.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-CExO0piw.mjs +26 -0
- package/dist/stream-broker/{hostInit-DxqtIjAe.mjs → hostInit-DD5Z1U6p.mjs} +3 -3
- package/dist/stream-broker/index.js +501 -32
- package/dist/stream-broker/index.mjs +501 -32
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-D6tXz9Or.js → worker-protocol-DEV7g32b.js} +26 -19
- package/dist/{worker-protocol-DuftUFEI.mjs → worker-protocol-Dm6LQ9Ls.mjs} +26 -19
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ircnzt0s.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-DU7u9EJt.mjs +0 -26
package/dist/{event-loop-stall-monitor-C6_VDupx.mjs → event-loop-stall-monitor-Cmbt664E.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-
|
|
1
|
+
import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-D1Xk5Tji.mjs";
|
|
2
2
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
3
3
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
4
4
|
var HF_REPO = "camstack/camstack-models";
|
|
@@ -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-CtOVWTCi.js");
|
|
6
6
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-
|
|
1
|
+
import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-D1Xk5Tji.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
8
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
6
|
+
const require_dist = require("../dist-CtOVWTCi.js");
|
|
7
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BJs7Yws7.js");
|
|
8
|
+
const require_worker_protocol = require("../worker-protocol-DEV7g32b.js");
|
|
9
9
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
10
10
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
11
11
|
const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
|
|
@@ -17,28 +17,6 @@ let node_perf_hooks = require("node:perf_hooks");
|
|
|
17
17
|
let node_os = require("node:os");
|
|
18
18
|
node_os = require_chunk.__toESM(node_os);
|
|
19
19
|
//#region src/session-decode/fork-decode-worker.ts
|
|
20
|
-
/**
|
|
21
|
-
* Real `fork` implementation for the session-decode coordinator — Epic C P1,
|
|
22
|
-
* Task 4.
|
|
23
|
-
*
|
|
24
|
-
* Spawns the decode-worker child (`decode-worker-child.ts`, Task 2) as a
|
|
25
|
-
* dedicated `child_process.fork` and adapts the returned `ChildProcess` to the
|
|
26
|
-
* coordinator's minimal {@link ForkedChildLike} contract WITHOUT any cast —
|
|
27
|
-
* an explicit forwarding wrapper, so a wider Node `ChildProcess` signature
|
|
28
|
-
* never has to structurally satisfy the narrower interface.
|
|
29
|
-
*
|
|
30
|
-
* `serialization: 'advanced'` is MANDATORY: Node's default 'json' IPC
|
|
31
|
-
* base64-serialises the `buffer` reply's `Uint8Array`, breaking the "raw
|
|
32
|
-
* bytes, not base64" contract the coordinator + `worker-protocol.ts`'s
|
|
33
|
-
* `value.bytes instanceof Uint8Array` guard both depend on. Advanced (V8
|
|
34
|
-
* structured-clone) preserves the typed array on the wire.
|
|
35
|
-
*
|
|
36
|
-
* The child entry is resolved relative to THIS module's built location. Both
|
|
37
|
-
* this module and `pipeline-runner/index.ts` bundle into the SAME entry
|
|
38
|
-
* (`dist/pipeline-runner/index.js`, `manualChunks: () => null`), so
|
|
39
|
-
* `import.meta.url` here is that file's URL and the sibling child lands at
|
|
40
|
-
* `dist/session-decode/decode-worker-child.js` (its own vite build entry).
|
|
41
|
-
*/
|
|
42
20
|
/** Absolute path to the built decode-worker child entry (a vite build entry). */
|
|
43
21
|
var CHILD_ENTRY_PATH = (0, node_url.fileURLToPath)(new URL("../session-decode/decode-worker-child.js", require("url").pathToFileURL(__filename).href));
|
|
44
22
|
/**
|
|
@@ -696,8 +674,24 @@ var SessionDecodeSession = class {
|
|
|
696
674
|
});
|
|
697
675
|
}
|
|
698
676
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
677
|
+
* The runner has this frame's detections: cut one native subject tile per box
|
|
678
|
+
* and END the hold on `frameId`. Fire and forget (no reply) so the encode can
|
|
679
|
+
* never delay the runner; a dropped message costs only the tiles.
|
|
680
|
+
*
|
|
681
|
+
* An EMPTY box list is still sent — it is how a frame that detected nothing
|
|
682
|
+
* releases its hold, and that is the common case.
|
|
683
|
+
*/
|
|
684
|
+
cutTiles(frameId, bboxes) {
|
|
685
|
+
if (this.exited) return;
|
|
686
|
+
this.trySend({
|
|
687
|
+
kind: "cutTiles",
|
|
688
|
+
frameId,
|
|
689
|
+
bboxes
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Prompt-release the worker's RAM native-frame hold for `frameId`. Fire and
|
|
694
|
+
* forget (no reply): the hold is also bounded by a frame count, so a dropped
|
|
701
695
|
* release only defers reclaim. No-op once the worker has exited.
|
|
702
696
|
*/
|
|
703
697
|
releaseLease(frameId) {
|
|
@@ -977,6 +971,7 @@ function buildFrameImage(session, reply) {
|
|
|
977
971
|
return session.requestBuffer(reply.frameId, opts);
|
|
978
972
|
},
|
|
979
973
|
nativeCrop: (bbox, maxWidth) => session.requestNativeCrop(reply.frameId, bbox, maxWidth),
|
|
974
|
+
cutTiles: (bboxes) => session.cutTiles(reply.frameId, bboxes),
|
|
980
975
|
releaseNativeLease: () => session.releaseLease(reply.frameId),
|
|
981
976
|
markInferred: () => session.markInferred(reply.frameId),
|
|
982
977
|
close: () => {
|
|
@@ -1117,6 +1112,7 @@ function startSessionDecodePump(deps) {
|
|
|
1117
1112
|
const image = frame.image;
|
|
1118
1113
|
const cropFn = image.nativeCrop;
|
|
1119
1114
|
const nativeCrop = cropFn ? Object.assign((bbox, maxWidth) => cropFn(bbox, maxWidth), {
|
|
1115
|
+
cutTiles: (bboxes) => image.cutTiles?.(bboxes),
|
|
1120
1116
|
release: () => image.releaseNativeLease?.(),
|
|
1121
1117
|
markInferred: () => image.markInferred?.()
|
|
1122
1118
|
}) : void 0;
|
|
@@ -2525,7 +2521,7 @@ var NATIVE_LEASE_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
|
2525
2521
|
* old value.
|
|
2526
2522
|
*/
|
|
2527
2523
|
function sameOverride(a, b) {
|
|
2528
|
-
return a.
|
|
2524
|
+
return a.holdFrames === b.holdFrames && a.budgetMb === b.budgetMb && a.activityMs === b.activityMs && a.admission === b.admission && a.tileBudgetMb === b.tileBudgetMb;
|
|
2529
2525
|
}
|
|
2530
2526
|
/**
|
|
2531
2527
|
* TTL-cached read of the cluster native-lease knobs.
|
|
@@ -3190,6 +3186,7 @@ var PipelineRunner = class {
|
|
|
3190
3186
|
motionQueue,
|
|
3191
3187
|
detectionQueue,
|
|
3192
3188
|
inferenceTimes: [],
|
|
3189
|
+
shedFrames: 0,
|
|
3193
3190
|
processedCount: 0,
|
|
3194
3191
|
startTime: Date.now(),
|
|
3195
3192
|
phase: initialPhase,
|
|
@@ -3482,7 +3479,7 @@ var PipelineRunner = class {
|
|
|
3482
3479
|
actualFps,
|
|
3483
3480
|
queueDepth: state.motionQueue.size + state.detectionQueue.size,
|
|
3484
3481
|
avgInferenceTimeMs: avgInference,
|
|
3485
|
-
droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,
|
|
3482
|
+
droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames + state.shedFrames,
|
|
3486
3483
|
phase: state.phase
|
|
3487
3484
|
};
|
|
3488
3485
|
}
|
|
@@ -3589,6 +3586,7 @@ var PipelineRunner = class {
|
|
|
3589
3586
|
if (state.inferenceTimes.length > 100) state.inferenceTimes.shift();
|
|
3590
3587
|
state.processedCount++;
|
|
3591
3588
|
if (result) {
|
|
3589
|
+
if (result.shed === true) state.shedFrames++;
|
|
3592
3590
|
await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
|
|
3593
3591
|
const emittedAt = Date.now();
|
|
3594
3592
|
const capturedAt = frame.capturedAt;
|
|
@@ -3800,6 +3798,42 @@ async function runStatelessStep(deps, input) {
|
|
|
3800
3798
|
details
|
|
3801
3799
|
};
|
|
3802
3800
|
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Normalised boxes for the subjects worth a tile, highest confidence first,
|
|
3803
|
+
* capped.
|
|
3804
|
+
*
|
|
3805
|
+
* Returns an empty array for a frame with no usable detections — and the caller
|
|
3806
|
+
* still sends it: an empty `cutTiles` is how a frame that detected nothing ends
|
|
3807
|
+
* its hold, which is the majority of frames and the whole reason this design
|
|
3808
|
+
* costs less than the one it replaces.
|
|
3809
|
+
*
|
|
3810
|
+
* A degenerate or non-finite box is DROPPED rather than clamped: a zero-area
|
|
3811
|
+
* container serves nothing, and a NaN one would poison the worker's geometry.
|
|
3812
|
+
*/
|
|
3813
|
+
function planTileCuts(detections, frame, maxSubjects = 4) {
|
|
3814
|
+
if (frame.width <= 0 || frame.height <= 0) return [];
|
|
3815
|
+
const ordered = [...detections.filter((d) => isUsable(d.bbox))].sort((a, b) => b.score - a.score);
|
|
3816
|
+
const out = [];
|
|
3817
|
+
for (const detection of ordered.slice(0, Math.max(0, maxSubjects))) {
|
|
3818
|
+
const { x, y, width, height } = detection.bbox;
|
|
3819
|
+
out.push({
|
|
3820
|
+
x: x / frame.width,
|
|
3821
|
+
y: y / frame.height,
|
|
3822
|
+
w: width / frame.width,
|
|
3823
|
+
h: height / frame.height
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
return out;
|
|
3827
|
+
}
|
|
3828
|
+
function isUsable(bbox) {
|
|
3829
|
+
if (![
|
|
3830
|
+
bbox.x,
|
|
3831
|
+
bbox.y,
|
|
3832
|
+
bbox.width,
|
|
3833
|
+
bbox.height
|
|
3834
|
+
].every((v) => Number.isFinite(v))) return false;
|
|
3835
|
+
return bbox.width > 0 && bbox.height > 0;
|
|
3836
|
+
}
|
|
3803
3837
|
//#endregion
|
|
3804
3838
|
//#region src/pipeline-runner/index.ts
|
|
3805
3839
|
var DEFAULT_CONFIG = {
|
|
@@ -4288,6 +4322,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4288
4322
|
this.handleDetectionStreamChange(deviceId, action);
|
|
4289
4323
|
});
|
|
4290
4324
|
this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {
|
|
4325
|
+
this.cutSubjectTiles(deviceId, result, handle);
|
|
4291
4326
|
this.emitInferenceResult(deviceId, frame, result, handle);
|
|
4292
4327
|
const detectionCount = result.detections?.length ?? 0;
|
|
4293
4328
|
this.runner?.noteDetectionResult(deviceId, detectionCount);
|
|
@@ -5109,6 +5144,40 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5109
5144
|
}, resolved.tier, input.encodeJpeg);
|
|
5110
5145
|
}
|
|
5111
5146
|
/**
|
|
5147
|
+
* Turn a finished frame into subject TILES and end its hold — the runner half
|
|
5148
|
+
* of R2 (`docs/design/2026-08-13-native-lease-two-tier-redesign.md`).
|
|
5149
|
+
*
|
|
5150
|
+
* Called from the result callback, which is the last instant at which the
|
|
5151
|
+
* frame's native pixels exist and its detections are known at the same time.
|
|
5152
|
+
* Every path through here ends the hold, including the one where there is
|
|
5153
|
+
* nothing to cut: a frame that detected nothing is the majority of frames and
|
|
5154
|
+
* releasing it promptly is most of the RAM this redesign gives back.
|
|
5155
|
+
*
|
|
5156
|
+
* Deliberately synchronous and unawaited. The worker replies to nothing, so
|
|
5157
|
+
* there is nothing to wait for, and a detection result must never be delayed
|
|
5158
|
+
* by an encode.
|
|
5159
|
+
*/
|
|
5160
|
+
cutSubjectTiles(deviceId, result, handle) {
|
|
5161
|
+
if (!handle) return;
|
|
5162
|
+
const found = this.nativeCropRegistry.get(handle);
|
|
5163
|
+
if (!found) return;
|
|
5164
|
+
const boxes = planTileCuts(result.detections ?? [], {
|
|
5165
|
+
width: result.width,
|
|
5166
|
+
height: result.height
|
|
5167
|
+
});
|
|
5168
|
+
try {
|
|
5169
|
+
found.request.cutTiles(boxes);
|
|
5170
|
+
} catch (err) {
|
|
5171
|
+
this.ctx.logger.debug("subject tile cut could not be dispatched", {
|
|
5172
|
+
tags: { deviceId },
|
|
5173
|
+
meta: {
|
|
5174
|
+
subjects: boxes.length,
|
|
5175
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5176
|
+
}
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
/**
|
|
5112
5181
|
* The native-lease half of {@link getNativeCrop}, extracted so the DETAIL path
|
|
5113
5182
|
* can keep the miss REASON the cap's `null` return has no room for. Both
|
|
5114
5183
|
* callers share this one decision — a second copy of the tier arithmetic is
|
|
@@ -6102,11 +6171,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6102
6171
|
let image;
|
|
6103
6172
|
if (!this.executorCoLocated()) {
|
|
6104
6173
|
if (frame.format === "rgb") try {
|
|
6105
|
-
|
|
6174
|
+
const encoded = await (0, sharp.default)(frame.data, { raw: {
|
|
6106
6175
|
width: frame.width,
|
|
6107
6176
|
height: frame.height,
|
|
6108
6177
|
channels: 3
|
|
6109
6178
|
} }).jpeg({ quality: DETECTION_HOP_JPEG_QUALITY }).toBuffer();
|
|
6179
|
+
image = new Uint8Array(encoded);
|
|
6110
6180
|
} catch (err) {
|
|
6111
6181
|
log.debug("runInference: frame JPEG encode failed — falling back to raw pixels", { meta: { error: require_dist.errMsg(err) } });
|
|
6112
6182
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-
|
|
2
|
-
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-
|
|
3
|
-
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-
|
|
1
|
+
import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-D1Xk5Tji.mjs";
|
|
2
|
+
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-Cmbt664E.mjs";
|
|
3
|
+
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-Dm6LQ9Ls.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
|
|
6
6
|
import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
|
|
@@ -10,28 +10,6 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
11
11
|
import * as os from "node:os";
|
|
12
12
|
//#region src/session-decode/fork-decode-worker.ts
|
|
13
|
-
/**
|
|
14
|
-
* Real `fork` implementation for the session-decode coordinator — Epic C P1,
|
|
15
|
-
* Task 4.
|
|
16
|
-
*
|
|
17
|
-
* Spawns the decode-worker child (`decode-worker-child.ts`, Task 2) as a
|
|
18
|
-
* dedicated `child_process.fork` and adapts the returned `ChildProcess` to the
|
|
19
|
-
* coordinator's minimal {@link ForkedChildLike} contract WITHOUT any cast —
|
|
20
|
-
* an explicit forwarding wrapper, so a wider Node `ChildProcess` signature
|
|
21
|
-
* never has to structurally satisfy the narrower interface.
|
|
22
|
-
*
|
|
23
|
-
* `serialization: 'advanced'` is MANDATORY: Node's default 'json' IPC
|
|
24
|
-
* base64-serialises the `buffer` reply's `Uint8Array`, breaking the "raw
|
|
25
|
-
* bytes, not base64" contract the coordinator + `worker-protocol.ts`'s
|
|
26
|
-
* `value.bytes instanceof Uint8Array` guard both depend on. Advanced (V8
|
|
27
|
-
* structured-clone) preserves the typed array on the wire.
|
|
28
|
-
*
|
|
29
|
-
* The child entry is resolved relative to THIS module's built location. Both
|
|
30
|
-
* this module and `pipeline-runner/index.ts` bundle into the SAME entry
|
|
31
|
-
* (`dist/pipeline-runner/index.js`, `manualChunks: () => null`), so
|
|
32
|
-
* `import.meta.url` here is that file's URL and the sibling child lands at
|
|
33
|
-
* `dist/session-decode/decode-worker-child.js` (its own vite build entry).
|
|
34
|
-
*/
|
|
35
13
|
/** Absolute path to the built decode-worker child entry (a vite build entry). */
|
|
36
14
|
var CHILD_ENTRY_PATH = fileURLToPath(new URL("../session-decode/decode-worker-child.js", import.meta.url));
|
|
37
15
|
/**
|
|
@@ -689,8 +667,24 @@ var SessionDecodeSession = class {
|
|
|
689
667
|
});
|
|
690
668
|
}
|
|
691
669
|
/**
|
|
692
|
-
*
|
|
693
|
-
*
|
|
670
|
+
* The runner has this frame's detections: cut one native subject tile per box
|
|
671
|
+
* and END the hold on `frameId`. Fire and forget (no reply) so the encode can
|
|
672
|
+
* never delay the runner; a dropped message costs only the tiles.
|
|
673
|
+
*
|
|
674
|
+
* An EMPTY box list is still sent — it is how a frame that detected nothing
|
|
675
|
+
* releases its hold, and that is the common case.
|
|
676
|
+
*/
|
|
677
|
+
cutTiles(frameId, bboxes) {
|
|
678
|
+
if (this.exited) return;
|
|
679
|
+
this.trySend({
|
|
680
|
+
kind: "cutTiles",
|
|
681
|
+
frameId,
|
|
682
|
+
bboxes
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Prompt-release the worker's RAM native-frame hold for `frameId`. Fire and
|
|
687
|
+
* forget (no reply): the hold is also bounded by a frame count, so a dropped
|
|
694
688
|
* release only defers reclaim. No-op once the worker has exited.
|
|
695
689
|
*/
|
|
696
690
|
releaseLease(frameId) {
|
|
@@ -970,6 +964,7 @@ function buildFrameImage(session, reply) {
|
|
|
970
964
|
return session.requestBuffer(reply.frameId, opts);
|
|
971
965
|
},
|
|
972
966
|
nativeCrop: (bbox, maxWidth) => session.requestNativeCrop(reply.frameId, bbox, maxWidth),
|
|
967
|
+
cutTiles: (bboxes) => session.cutTiles(reply.frameId, bboxes),
|
|
973
968
|
releaseNativeLease: () => session.releaseLease(reply.frameId),
|
|
974
969
|
markInferred: () => session.markInferred(reply.frameId),
|
|
975
970
|
close: () => {
|
|
@@ -1110,6 +1105,7 @@ function startSessionDecodePump(deps) {
|
|
|
1110
1105
|
const image = frame.image;
|
|
1111
1106
|
const cropFn = image.nativeCrop;
|
|
1112
1107
|
const nativeCrop = cropFn ? Object.assign((bbox, maxWidth) => cropFn(bbox, maxWidth), {
|
|
1108
|
+
cutTiles: (bboxes) => image.cutTiles?.(bboxes),
|
|
1113
1109
|
release: () => image.releaseNativeLease?.(),
|
|
1114
1110
|
markInferred: () => image.markInferred?.()
|
|
1115
1111
|
}) : void 0;
|
|
@@ -2517,7 +2513,7 @@ var NATIVE_LEASE_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
|
2517
2513
|
* old value.
|
|
2518
2514
|
*/
|
|
2519
2515
|
function sameOverride(a, b) {
|
|
2520
|
-
return a.
|
|
2516
|
+
return a.holdFrames === b.holdFrames && a.budgetMb === b.budgetMb && a.activityMs === b.activityMs && a.admission === b.admission && a.tileBudgetMb === b.tileBudgetMb;
|
|
2521
2517
|
}
|
|
2522
2518
|
/**
|
|
2523
2519
|
* TTL-cached read of the cluster native-lease knobs.
|
|
@@ -3182,6 +3178,7 @@ var PipelineRunner = class {
|
|
|
3182
3178
|
motionQueue,
|
|
3183
3179
|
detectionQueue,
|
|
3184
3180
|
inferenceTimes: [],
|
|
3181
|
+
shedFrames: 0,
|
|
3185
3182
|
processedCount: 0,
|
|
3186
3183
|
startTime: Date.now(),
|
|
3187
3184
|
phase: initialPhase,
|
|
@@ -3474,7 +3471,7 @@ var PipelineRunner = class {
|
|
|
3474
3471
|
actualFps,
|
|
3475
3472
|
queueDepth: state.motionQueue.size + state.detectionQueue.size,
|
|
3476
3473
|
avgInferenceTimeMs: avgInference,
|
|
3477
|
-
droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,
|
|
3474
|
+
droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames + state.shedFrames,
|
|
3478
3475
|
phase: state.phase
|
|
3479
3476
|
};
|
|
3480
3477
|
}
|
|
@@ -3581,6 +3578,7 @@ var PipelineRunner = class {
|
|
|
3581
3578
|
if (state.inferenceTimes.length > 100) state.inferenceTimes.shift();
|
|
3582
3579
|
state.processedCount++;
|
|
3583
3580
|
if (result) {
|
|
3581
|
+
if (result.shed === true) state.shedFrames++;
|
|
3584
3582
|
await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
|
|
3585
3583
|
const emittedAt = Date.now();
|
|
3586
3584
|
const capturedAt = frame.capturedAt;
|
|
@@ -3792,6 +3790,42 @@ async function runStatelessStep(deps, input) {
|
|
|
3792
3790
|
details
|
|
3793
3791
|
};
|
|
3794
3792
|
}
|
|
3793
|
+
/**
|
|
3794
|
+
* Normalised boxes for the subjects worth a tile, highest confidence first,
|
|
3795
|
+
* capped.
|
|
3796
|
+
*
|
|
3797
|
+
* Returns an empty array for a frame with no usable detections — and the caller
|
|
3798
|
+
* still sends it: an empty `cutTiles` is how a frame that detected nothing ends
|
|
3799
|
+
* its hold, which is the majority of frames and the whole reason this design
|
|
3800
|
+
* costs less than the one it replaces.
|
|
3801
|
+
*
|
|
3802
|
+
* A degenerate or non-finite box is DROPPED rather than clamped: a zero-area
|
|
3803
|
+
* container serves nothing, and a NaN one would poison the worker's geometry.
|
|
3804
|
+
*/
|
|
3805
|
+
function planTileCuts(detections, frame, maxSubjects = 4) {
|
|
3806
|
+
if (frame.width <= 0 || frame.height <= 0) return [];
|
|
3807
|
+
const ordered = [...detections.filter((d) => isUsable(d.bbox))].sort((a, b) => b.score - a.score);
|
|
3808
|
+
const out = [];
|
|
3809
|
+
for (const detection of ordered.slice(0, Math.max(0, maxSubjects))) {
|
|
3810
|
+
const { x, y, width, height } = detection.bbox;
|
|
3811
|
+
out.push({
|
|
3812
|
+
x: x / frame.width,
|
|
3813
|
+
y: y / frame.height,
|
|
3814
|
+
w: width / frame.width,
|
|
3815
|
+
h: height / frame.height
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
return out;
|
|
3819
|
+
}
|
|
3820
|
+
function isUsable(bbox) {
|
|
3821
|
+
if (![
|
|
3822
|
+
bbox.x,
|
|
3823
|
+
bbox.y,
|
|
3824
|
+
bbox.width,
|
|
3825
|
+
bbox.height
|
|
3826
|
+
].every((v) => Number.isFinite(v))) return false;
|
|
3827
|
+
return bbox.width > 0 && bbox.height > 0;
|
|
3828
|
+
}
|
|
3795
3829
|
//#endregion
|
|
3796
3830
|
//#region src/pipeline-runner/index.ts
|
|
3797
3831
|
var DEFAULT_CONFIG = {
|
|
@@ -4280,6 +4314,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4280
4314
|
this.handleDetectionStreamChange(deviceId, action);
|
|
4281
4315
|
});
|
|
4282
4316
|
this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {
|
|
4317
|
+
this.cutSubjectTiles(deviceId, result, handle);
|
|
4283
4318
|
this.emitInferenceResult(deviceId, frame, result, handle);
|
|
4284
4319
|
const detectionCount = result.detections?.length ?? 0;
|
|
4285
4320
|
this.runner?.noteDetectionResult(deviceId, detectionCount);
|
|
@@ -5101,6 +5136,40 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
5101
5136
|
}, resolved.tier, input.encodeJpeg);
|
|
5102
5137
|
}
|
|
5103
5138
|
/**
|
|
5139
|
+
* Turn a finished frame into subject TILES and end its hold — the runner half
|
|
5140
|
+
* of R2 (`docs/design/2026-08-13-native-lease-two-tier-redesign.md`).
|
|
5141
|
+
*
|
|
5142
|
+
* Called from the result callback, which is the last instant at which the
|
|
5143
|
+
* frame's native pixels exist and its detections are known at the same time.
|
|
5144
|
+
* Every path through here ends the hold, including the one where there is
|
|
5145
|
+
* nothing to cut: a frame that detected nothing is the majority of frames and
|
|
5146
|
+
* releasing it promptly is most of the RAM this redesign gives back.
|
|
5147
|
+
*
|
|
5148
|
+
* Deliberately synchronous and unawaited. The worker replies to nothing, so
|
|
5149
|
+
* there is nothing to wait for, and a detection result must never be delayed
|
|
5150
|
+
* by an encode.
|
|
5151
|
+
*/
|
|
5152
|
+
cutSubjectTiles(deviceId, result, handle) {
|
|
5153
|
+
if (!handle) return;
|
|
5154
|
+
const found = this.nativeCropRegistry.get(handle);
|
|
5155
|
+
if (!found) return;
|
|
5156
|
+
const boxes = planTileCuts(result.detections ?? [], {
|
|
5157
|
+
width: result.width,
|
|
5158
|
+
height: result.height
|
|
5159
|
+
});
|
|
5160
|
+
try {
|
|
5161
|
+
found.request.cutTiles(boxes);
|
|
5162
|
+
} catch (err) {
|
|
5163
|
+
this.ctx.logger.debug("subject tile cut could not be dispatched", {
|
|
5164
|
+
tags: { deviceId },
|
|
5165
|
+
meta: {
|
|
5166
|
+
subjects: boxes.length,
|
|
5167
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5168
|
+
}
|
|
5169
|
+
});
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
/**
|
|
5104
5173
|
* The native-lease half of {@link getNativeCrop}, extracted so the DETAIL path
|
|
5105
5174
|
* can keep the miss REASON the cap's `null` return has no room for. Both
|
|
5106
5175
|
* callers share this one decision — a second copy of the tier arithmetic is
|
|
@@ -6094,11 +6163,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6094
6163
|
let image;
|
|
6095
6164
|
if (!this.executorCoLocated()) {
|
|
6096
6165
|
if (frame.format === "rgb") try {
|
|
6097
|
-
|
|
6166
|
+
const encoded = await sharp(frame.data, { raw: {
|
|
6098
6167
|
width: frame.width,
|
|
6099
6168
|
height: frame.height,
|
|
6100
6169
|
channels: 3
|
|
6101
6170
|
} }).jpeg({ quality: DETECTION_HOP_JPEG_QUALITY }).toBuffer();
|
|
6171
|
+
image = new Uint8Array(encoded);
|
|
6102
6172
|
} catch (err) {
|
|
6103
6173
|
log.debug("runInference: frame JPEG encode failed — falling back to raw pixels", { meta: { error: errMsg(err) } });
|
|
6104
6174
|
}
|