@camstack/addon-pipeline 0.1.14 → 0.1.16

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.
Files changed (76) hide show
  1. package/dist/audio-analyzer/index.js +2 -4
  2. package/dist/audio-analyzer/index.js.map +1 -1
  3. package/dist/audio-analyzer/index.mjs +2 -4
  4. package/dist/audio-analyzer/index.mjs.map +1 -1
  5. package/dist/audio-codec-nodeav/index.js +1 -1
  6. package/dist/audio-codec-nodeav/index.mjs +1 -1
  7. package/dist/decoder-nodeav/index.js +552 -18
  8. package/dist/decoder-nodeav/index.js.map +1 -1
  9. package/dist/decoder-nodeav/index.mjs +553 -19
  10. package/dist/decoder-nodeav/index.mjs.map +1 -1
  11. package/dist/detection-pipeline/index.js +2 -4
  12. package/dist/detection-pipeline/index.js.map +1 -1
  13. package/dist/detection-pipeline/index.mjs +2 -4
  14. package/dist/detection-pipeline/index.mjs.map +1 -1
  15. package/dist/{index-DKh0uEve.mjs → index-CVzLrojg.mjs} +539 -97
  16. package/dist/index-CVzLrojg.mjs.map +1 -0
  17. package/dist/{index-CFPKrb2Y.js → index-p-6GfKOg.js} +539 -97
  18. package/dist/index-p-6GfKOg.js.map +1 -0
  19. package/dist/motion-wasm/index.js +2 -4
  20. package/dist/motion-wasm/index.js.map +1 -1
  21. package/dist/motion-wasm/index.mjs +2 -4
  22. package/dist/motion-wasm/index.mjs.map +1 -1
  23. package/dist/pipeline-runner/index.js +133 -54
  24. package/dist/pipeline-runner/index.js.map +1 -1
  25. package/dist/pipeline-runner/index.mjs +133 -54
  26. package/dist/pipeline-runner/index.mjs.map +1 -1
  27. package/dist/stream-broker/@mf-types.zip +0 -0
  28. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +19 -0
  29. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B4l8Nb2y.mjs +20 -0
  30. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DePVYdid.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DAssX3h0.mjs} +4 -2
  31. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CBlCGyx5.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-DFoJJhpt.mjs} +1 -1
  32. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-DZchZKbW.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-x7XMEeuJ.mjs} +1 -1
  33. package/dist/stream-broker/_stub.js +2 -2
  34. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CqeKw-Ig.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-iA_8b8fz.mjs} +6 -6
  35. package/dist/stream-broker/{client-BK73l2KT.mjs → client-CZXrddDR.mjs} +2990 -3217
  36. package/dist/stream-broker/{hostInit-DkjoXTMb.mjs → hostInit-1sVsS6_a.mjs} +12 -12
  37. package/dist/stream-broker/{index-BP0-1QYT.mjs → index-BCEx31Mh.mjs} +3808 -3100
  38. package/dist/stream-broker/{index-lmXLeXy8.mjs → index-BvV3RVTZ.mjs} +1 -1
  39. package/dist/stream-broker/{index-IUYKHbxX.mjs → index-C0BzaWmB.mjs} +1 -1
  40. package/dist/stream-broker/index-CWkKuNLr.mjs +232 -0
  41. package/dist/stream-broker/{index-ns1fRD30.mjs → index-CZNxa0ad.mjs} +1 -1
  42. package/dist/stream-broker/index-Kb4xa8FX.mjs +36403 -0
  43. package/dist/stream-broker/{index-BxHaCH3N.mjs → index-KtR7Pp0O.mjs} +1 -1
  44. package/dist/stream-broker/{index-Ss9m7Jum.mjs → index-cYW01SNH.mjs} +1 -1
  45. package/dist/stream-broker/index.js +802 -541
  46. package/dist/stream-broker/index.js.map +1 -1
  47. package/dist/stream-broker/index.mjs +802 -519
  48. package/dist/stream-broker/index.mjs.map +1 -1
  49. package/dist/stream-broker/{jsx-runtime-ZdY5pIZz.mjs → jsx-runtime-B_evVsXl.mjs} +1 -1
  50. package/dist/stream-broker/remoteEntry.js +1 -1
  51. package/package.json +23 -31
  52. package/dist/index-CFPKrb2Y.js.map +0 -1
  53. package/dist/index-DKh0uEve.mjs.map +0 -1
  54. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-CpCK52pE.mjs +0 -19
  55. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BN3K4dM8.mjs +0 -20
  56. package/dist/stream-broker/index-DKercbDS.mjs +0 -20855
  57. package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
  58. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  59. package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
  61. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  62. package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
  63. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  64. package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
  65. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  66. package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
  67. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  68. package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
  69. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  70. package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
  71. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  72. package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
  73. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  74. package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
  75. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  76. package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
@@ -1,20 +1,29 @@
1
- import { o as object, s as string, _ as _enum, l as lazy, a as array, b as boolean, n as number, d as defineCustomActions, c as customAction, B as BaseAddon, E as EventCategory, p as pipelineRunnerCapability, e as errMsg, f as createEvent } from "../index-DKh0uEve.mjs";
1
+ import { e as errMsg, o as object, s as string, _ as _enum, l as lazy, a as array, b as boolean, n as number, d as defineCustomActions, c as customAction, B as BaseAddon, E as EventCategory, p as pipelineRunnerCapability, f as createEvent } from "../index-CVzLrojg.mjs";
2
+ import { FrameRingReaderCache } from "@camstack/shm-ring";
2
3
  class FrameQueue {
3
- constructor(maxSize) {
4
+ /**
5
+ * `clone` runs on every {@link enqueue} to detach a queue-owned copy from
6
+ * the (possibly borrowed) source — required by the D9 Task 7b ownership
7
+ * contract above. Motion call sites pass {@link ownFrame}; the detection
8
+ * path passes a clone that copies its inner frame's pixel buffer.
9
+ */
10
+ constructor(maxSize, clone) {
4
11
  this.maxSize = maxSize;
12
+ this.clone = clone;
5
13
  }
6
14
  latest = null;
7
15
  _droppedFrames = 0;
8
- enqueue(frame) {
16
+ clone;
17
+ enqueue(item) {
9
18
  if (this.latest !== null) {
10
19
  this._droppedFrames++;
11
20
  }
12
- this.latest = frame;
21
+ this.latest = this.clone(item);
13
22
  }
14
23
  dequeue() {
15
- const frame = this.latest ?? void 0;
24
+ const item = this.latest ?? void 0;
16
25
  this.latest = null;
17
- return frame;
26
+ return item;
18
27
  }
19
28
  get size() {
20
29
  return this.latest !== null ? 1 : 0;
@@ -26,6 +35,9 @@ class FrameQueue {
26
35
  this.latest = null;
27
36
  }
28
37
  }
38
+ function ownFrame(frame) {
39
+ return { ...frame, data: Buffer.from(frame.data) };
40
+ }
29
41
  class Semaphore {
30
42
  _concurrency;
31
43
  _available;
@@ -194,6 +206,9 @@ class PipelineTimingSampler {
194
206
  this.audioSamples.clear();
195
207
  }
196
208
  }
209
+ function ownDetectionEntry(entry) {
210
+ return { frame: ownFrame(entry.frame), handle: entry.handle };
211
+ }
197
212
  const DEFAULT_MOTION_COOLDOWN_MS = 3e4;
198
213
  function toFrameInput$1(frame) {
199
214
  return {
@@ -268,8 +283,11 @@ class PipelineRunner {
268
283
  this.detectionStreamHandler = handler;
269
284
  }
270
285
  registerCamera(deviceId, registration) {
271
- const motionQueue = new FrameQueue(this.config.maxQueueDepth);
272
- const detectionQueue = new FrameQueue(this.config.maxQueueDepth);
286
+ const motionQueue = new FrameQueue(this.config.maxQueueDepth, ownFrame);
287
+ const detectionQueue = new FrameQueue(
288
+ this.config.maxQueueDepth,
289
+ ownDetectionEntry
290
+ );
273
291
  const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
274
292
  const state = {
275
293
  registration,
@@ -324,12 +342,12 @@ class PipelineRunner {
324
342
  if (!state) return;
325
343
  state.motionQueue.enqueue(frame);
326
344
  }
327
- enqueueDetectionFrame(deviceId, frame) {
345
+ enqueueDetectionFrame(deviceId, frame, handle) {
328
346
  const state = this.cameras.get(deviceId);
329
347
  if (!state) return;
330
348
  if (state.phase !== "active") return;
331
349
  frame._enqueuedAt = Date.now();
332
- state.detectionQueue.enqueue(frame);
350
+ state.detectionQueue.enqueue({ frame, handle });
333
351
  }
334
352
  /**
335
353
  * Report a motion event for a camera. Drives the unified phase
@@ -482,9 +500,9 @@ class PipelineRunner {
482
500
  if (this.semaphore.available <= 0) return;
483
501
  const picked = this.pickNextDetectionFrame();
484
502
  if (!picked) return;
485
- const { deviceId, frame, state } = picked;
486
- const frameInput = toFrameInput$1(frame);
487
- void this.processWithSemaphore(deviceId, frame, frameInput, state, "detection");
503
+ const { deviceId, entry, state } = picked;
504
+ const frameInput = toFrameInput$1(entry.frame);
505
+ void this.processWithSemaphore(deviceId, entry, frameInput, state, "detection");
488
506
  }
489
507
  drainMotionQueues() {
490
508
  for (const [deviceId, state] of this.cameras) {
@@ -496,8 +514,9 @@ class PipelineRunner {
496
514
  }
497
515
  }
498
516
  }
499
- async processWithSemaphore(deviceId, frame, frameInput, state, streamType) {
517
+ async processWithSemaphore(deviceId, entry, frameInput, state, streamType) {
500
518
  const pickedAt = Date.now();
519
+ const { frame, handle } = entry;
501
520
  const captureTs = frame.timestamp;
502
521
  const enqueuedAt = frame._enqueuedAt ?? captureTs;
503
522
  const release = await this.semaphore.acquire();
@@ -512,7 +531,7 @@ class PipelineRunner {
512
531
  }
513
532
  state.processedCount++;
514
533
  if (result) {
515
- await this.notifyCallbacks(deviceId, frame, result, streamType);
534
+ await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
516
535
  const emittedAt = Date.now();
517
536
  this.timingSampler.addSample(deviceId, {
518
537
  captureToEnqueue: enqueuedAt - captureTs,
@@ -528,10 +547,10 @@ class PipelineRunner {
528
547
  release();
529
548
  }
530
549
  }
531
- async notifyCallbacks(deviceId, frame, result, streamType) {
550
+ async notifyCallbacks(deviceId, frame, result, streamType, handle) {
532
551
  for (const callback of this.resultCallbacks) {
533
552
  try {
534
- await callback(deviceId, frame, result, streamType);
553
+ await callback(deviceId, frame, result, streamType, handle);
535
554
  } catch {
536
555
  }
537
556
  }
@@ -539,8 +558,8 @@ class PipelineRunner {
539
558
  pickNextDetectionFrame() {
540
559
  for (const [deviceId, state] of this.cameras) {
541
560
  if (state.registration.detectionMode === "always-on" && state.detectionQueue.size > 0) {
542
- const frame = state.detectionQueue.dequeue();
543
- return { deviceId, frame, state };
561
+ const entry = state.detectionQueue.dequeue();
562
+ return { deviceId, entry, state };
544
563
  }
545
564
  }
546
565
  if (this.defaultRoundRobinKeys.length === 0) return null;
@@ -553,14 +572,75 @@ class PipelineRunner {
553
572
  if (!state) continue;
554
573
  if (state.phase === "active" && state.detectionQueue.size > 0) {
555
574
  this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length;
556
- const frame = state.detectionQueue.dequeue();
557
- if (!frame) continue;
558
- return { deviceId, frame, state };
575
+ const entry = state.detectionQueue.dequeue();
576
+ if (!entry) continue;
577
+ return { deviceId, entry, state };
559
578
  }
560
579
  }
561
580
  return null;
562
581
  }
563
582
  }
583
+ const PULL_MAX_COUNT = 4;
584
+ const MIN_POLL_INTERVAL_MS = 20;
585
+ const FALLBACK_POLL_INTERVAL_MS = 200;
586
+ async function startFrameHandlePoller(options) {
587
+ const { api, brokerId, format, maxFps, tag, onFrame, logger } = options;
588
+ let result;
589
+ try {
590
+ result = await api.streamBroker.subscribeFrames.mutate({
591
+ brokerId,
592
+ format,
593
+ maxFps,
594
+ tag
595
+ });
596
+ } catch (err) {
597
+ logger.warn("frame-handle poller: subscribeFrames failed", {
598
+ meta: { brokerId, format, tag, error: errMsg(err) }
599
+ });
600
+ return null;
601
+ }
602
+ const { subscriptionId } = result;
603
+ const readers = new FrameRingReaderCache(logger);
604
+ const pollIntervalMs = result.maxFps > 0 ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1e3 / result.maxFps)) : FALLBACK_POLL_INTERVAL_MS;
605
+ let stopped = false;
606
+ let timer;
607
+ const tick = async () => {
608
+ if (stopped) return;
609
+ try {
610
+ const handles = await api.streamBroker.pullFrameHandles.query({
611
+ subscriptionId,
612
+ maxCount: PULL_MAX_COUNT
613
+ });
614
+ for (const handle of handles) {
615
+ if (stopped) break;
616
+ const frame = readers.read(handle);
617
+ if (frame) onFrame(frame, handle);
618
+ }
619
+ } catch (err) {
620
+ logger.warn("frame-handle poller: pullFrameHandles failed", {
621
+ meta: { brokerId, subscriptionId, error: errMsg(err) }
622
+ });
623
+ }
624
+ if (!stopped) {
625
+ timer = setTimeout(() => void tick(), pollIntervalMs);
626
+ }
627
+ };
628
+ void tick();
629
+ return () => {
630
+ if (stopped) return;
631
+ stopped = true;
632
+ if (timer) {
633
+ clearTimeout(timer);
634
+ timer = void 0;
635
+ }
636
+ readers.close();
637
+ api.streamBroker.unsubscribeFrames.mutate({ subscriptionId }).catch((err) => {
638
+ logger.warn("frame-handle poller: unsubscribeFrames failed", {
639
+ meta: { brokerId, subscriptionId, error: errMsg(err) }
640
+ });
641
+ });
642
+ };
643
+ }
564
644
  const BenchEngineChoiceSchema = object({
565
645
  runtime: _enum(["node", "python"]),
566
646
  backend: string(),
@@ -721,8 +801,8 @@ class PipelineRunnerAddon extends BaseAddon {
721
801
  this.runner.onDetectionStreamChange((deviceId, action) => {
722
802
  this.handleDetectionStreamChange(deviceId, action);
723
803
  });
724
- this.runner.onResult(async (deviceId, frame, result, _streamType) => {
725
- this.emitInferenceResult(deviceId, frame, result);
804
+ this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {
805
+ this.emitInferenceResult(deviceId, frame, result, handle);
726
806
  });
727
807
  this.runner.start();
728
808
  this.ctx.logger.info(
@@ -1276,18 +1356,19 @@ class PipelineRunnerAddon extends BaseAddon {
1276
1356
  log.warn("subscribeMotionFrames: this.ctx.api not available");
1277
1357
  return null;
1278
1358
  }
1279
- const motionBrokerId = `${config.deviceId}/${config.motionStreamId}`;
1280
- const motionBroker = await api.streamBroker.getBroker.query({ brokerId: motionBrokerId });
1281
- if (!motionBroker) {
1282
- log.warn("subscribeMotionFrames: no broker found", { meta: { brokerId: motionBrokerId } });
1283
- return null;
1284
- }
1285
- return motionBroker.onDecodedFrame(
1286
- (frame) => {
1359
+ return startFrameHandlePoller({
1360
+ api,
1361
+ brokerId: `${config.deviceId}/${config.motionStreamId}`,
1362
+ format: "gray",
1363
+ maxFps: config.motionFps,
1364
+ tag: "motion",
1365
+ logger: log,
1366
+ // Motion analysis is intra-process and never propagates beyond
1367
+ // the runner; the handle is intentionally ignored here.
1368
+ onFrame: (frame, _handle) => {
1287
1369
  runner.enqueueMotionFrame(config.deviceId, frame);
1288
- },
1289
- { maxFps: config.motionFps, format: "gray", tag: "motion" }
1290
- );
1370
+ }
1371
+ });
1291
1372
  }
1292
1373
  handleDetectionStreamChange(deviceId, action) {
1293
1374
  const attachment = this.attached.get(deviceId);
@@ -1361,23 +1442,20 @@ class PipelineRunnerAddon extends BaseAddon {
1361
1442
  log.warn("subscribeDetectionFrames: this.ctx.api not available");
1362
1443
  return null;
1363
1444
  }
1364
- const detectionBrokerId = `${config.deviceId}/${config.detectionStreamId}`;
1365
- const detectionBroker = await api.streamBroker.getBroker.query({ brokerId: detectionBrokerId });
1366
- if (!detectionBroker) {
1367
- log.warn("subscribeDetectionFrames: no broker found", { meta: { brokerId: detectionBrokerId } });
1368
- return null;
1369
- }
1370
- return detectionBroker.onDecodedFrame(
1371
- (frame) => {
1372
- runner.enqueueDetectionFrame(config.deviceId, frame);
1373
- },
1374
- // `format: 'rgb'` is the Phase 4 hot-path switch: detection now
1375
- // requests raw RGB24 from the broker so the decoder skips the
1376
- // sharp JPEG encode and the Python pool skips PIL JPEG decode.
1377
- // When WebRTC also subscribes (jpeg), the broker derives JPEG
1378
- // once per frame via its conversion cache — no double work.
1379
- { maxFps: config.detectionFps, format: "rgb", tag: "detection" }
1380
- );
1445
+ return startFrameHandlePoller({
1446
+ api,
1447
+ brokerId: `${config.deviceId}/${config.detectionStreamId}`,
1448
+ format: "rgb",
1449
+ maxFps: config.detectionFps,
1450
+ tag: "detection",
1451
+ logger: log,
1452
+ // Detection threads the `FrameHandle` through the runner so the
1453
+ // emitted `PipelineInferenceResultPayload` can name the shm slot
1454
+ // post-analysis (Task 8) reads pixels back from.
1455
+ onFrame: (frame, handle) => {
1456
+ runner.enqueueDetectionFrame(config.deviceId, frame, handle);
1457
+ }
1458
+ });
1381
1459
  }
1382
1460
  // ── Internal: inference + motion callbacks ───────────────────────────
1383
1461
  async runInference(deviceId, frame) {
@@ -1494,13 +1572,14 @@ class PipelineRunnerAddon extends BaseAddon {
1494
1572
  log.error("runMotionAnalysis failed", { meta: { error: msg } });
1495
1573
  }
1496
1574
  }
1497
- emitInferenceResult(deviceId, _frame, result) {
1575
+ emitInferenceResult(deviceId, _frame, result, handle) {
1498
1576
  const ctx = this.ctx;
1499
1577
  if (!ctx?.eventBus) return;
1500
1578
  const payload = {
1501
1579
  deviceId,
1502
1580
  frame: result,
1503
- nodeId: this.nodeId
1581
+ nodeId: this.nodeId,
1582
+ frameHandle: handle
1504
1583
  };
1505
1584
  this.ctx.eventBus.emit(createEvent(
1506
1585
  EventCategory.PipelineInferenceResult,