@camstack/addon-pipeline 0.1.14 → 0.1.15

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-CWHjxwIc.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-B86vUcFC.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
@@ -22,23 +22,32 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
- const index = require("../index-CFPKrb2Y.js");
25
+ const index = require("../index-p-6GfKOg.js");
26
+ const shmRing = require("@camstack/shm-ring");
26
27
  class FrameQueue {
27
- constructor(maxSize) {
28
+ /**
29
+ * `clone` runs on every {@link enqueue} to detach a queue-owned copy from
30
+ * the (possibly borrowed) source — required by the D9 Task 7b ownership
31
+ * contract above. Motion call sites pass {@link ownFrame}; the detection
32
+ * path passes a clone that copies its inner frame's pixel buffer.
33
+ */
34
+ constructor(maxSize, clone) {
28
35
  this.maxSize = maxSize;
36
+ this.clone = clone;
29
37
  }
30
38
  latest = null;
31
39
  _droppedFrames = 0;
32
- enqueue(frame) {
40
+ clone;
41
+ enqueue(item) {
33
42
  if (this.latest !== null) {
34
43
  this._droppedFrames++;
35
44
  }
36
- this.latest = frame;
45
+ this.latest = this.clone(item);
37
46
  }
38
47
  dequeue() {
39
- const frame = this.latest ?? void 0;
48
+ const item = this.latest ?? void 0;
40
49
  this.latest = null;
41
- return frame;
50
+ return item;
42
51
  }
43
52
  get size() {
44
53
  return this.latest !== null ? 1 : 0;
@@ -50,6 +59,9 @@ class FrameQueue {
50
59
  this.latest = null;
51
60
  }
52
61
  }
62
+ function ownFrame(frame) {
63
+ return { ...frame, data: Buffer.from(frame.data) };
64
+ }
53
65
  class Semaphore {
54
66
  _concurrency;
55
67
  _available;
@@ -218,6 +230,9 @@ class PipelineTimingSampler {
218
230
  this.audioSamples.clear();
219
231
  }
220
232
  }
233
+ function ownDetectionEntry(entry) {
234
+ return { frame: ownFrame(entry.frame), handle: entry.handle };
235
+ }
221
236
  const DEFAULT_MOTION_COOLDOWN_MS = 3e4;
222
237
  function toFrameInput$1(frame) {
223
238
  return {
@@ -292,8 +307,11 @@ class PipelineRunner {
292
307
  this.detectionStreamHandler = handler;
293
308
  }
294
309
  registerCamera(deviceId, registration) {
295
- const motionQueue = new FrameQueue(this.config.maxQueueDepth);
296
- const detectionQueue = new FrameQueue(this.config.maxQueueDepth);
310
+ const motionQueue = new FrameQueue(this.config.maxQueueDepth, ownFrame);
311
+ const detectionQueue = new FrameQueue(
312
+ this.config.maxQueueDepth,
313
+ ownDetectionEntry
314
+ );
297
315
  const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
298
316
  const state = {
299
317
  registration,
@@ -348,12 +366,12 @@ class PipelineRunner {
348
366
  if (!state) return;
349
367
  state.motionQueue.enqueue(frame);
350
368
  }
351
- enqueueDetectionFrame(deviceId, frame) {
369
+ enqueueDetectionFrame(deviceId, frame, handle) {
352
370
  const state = this.cameras.get(deviceId);
353
371
  if (!state) return;
354
372
  if (state.phase !== "active") return;
355
373
  frame._enqueuedAt = Date.now();
356
- state.detectionQueue.enqueue(frame);
374
+ state.detectionQueue.enqueue({ frame, handle });
357
375
  }
358
376
  /**
359
377
  * Report a motion event for a camera. Drives the unified phase
@@ -506,9 +524,9 @@ class PipelineRunner {
506
524
  if (this.semaphore.available <= 0) return;
507
525
  const picked = this.pickNextDetectionFrame();
508
526
  if (!picked) return;
509
- const { deviceId, frame, state } = picked;
510
- const frameInput = toFrameInput$1(frame);
511
- void this.processWithSemaphore(deviceId, frame, frameInput, state, "detection");
527
+ const { deviceId, entry, state } = picked;
528
+ const frameInput = toFrameInput$1(entry.frame);
529
+ void this.processWithSemaphore(deviceId, entry, frameInput, state, "detection");
512
530
  }
513
531
  drainMotionQueues() {
514
532
  for (const [deviceId, state] of this.cameras) {
@@ -520,8 +538,9 @@ class PipelineRunner {
520
538
  }
521
539
  }
522
540
  }
523
- async processWithSemaphore(deviceId, frame, frameInput, state, streamType) {
541
+ async processWithSemaphore(deviceId, entry, frameInput, state, streamType) {
524
542
  const pickedAt = Date.now();
543
+ const { frame, handle } = entry;
525
544
  const captureTs = frame.timestamp;
526
545
  const enqueuedAt = frame._enqueuedAt ?? captureTs;
527
546
  const release = await this.semaphore.acquire();
@@ -536,7 +555,7 @@ class PipelineRunner {
536
555
  }
537
556
  state.processedCount++;
538
557
  if (result) {
539
- await this.notifyCallbacks(deviceId, frame, result, streamType);
558
+ await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
540
559
  const emittedAt = Date.now();
541
560
  this.timingSampler.addSample(deviceId, {
542
561
  captureToEnqueue: enqueuedAt - captureTs,
@@ -552,10 +571,10 @@ class PipelineRunner {
552
571
  release();
553
572
  }
554
573
  }
555
- async notifyCallbacks(deviceId, frame, result, streamType) {
574
+ async notifyCallbacks(deviceId, frame, result, streamType, handle) {
556
575
  for (const callback of this.resultCallbacks) {
557
576
  try {
558
- await callback(deviceId, frame, result, streamType);
577
+ await callback(deviceId, frame, result, streamType, handle);
559
578
  } catch {
560
579
  }
561
580
  }
@@ -563,8 +582,8 @@ class PipelineRunner {
563
582
  pickNextDetectionFrame() {
564
583
  for (const [deviceId, state] of this.cameras) {
565
584
  if (state.registration.detectionMode === "always-on" && state.detectionQueue.size > 0) {
566
- const frame = state.detectionQueue.dequeue();
567
- return { deviceId, frame, state };
585
+ const entry = state.detectionQueue.dequeue();
586
+ return { deviceId, entry, state };
568
587
  }
569
588
  }
570
589
  if (this.defaultRoundRobinKeys.length === 0) return null;
@@ -577,14 +596,75 @@ class PipelineRunner {
577
596
  if (!state) continue;
578
597
  if (state.phase === "active" && state.detectionQueue.size > 0) {
579
598
  this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length;
580
- const frame = state.detectionQueue.dequeue();
581
- if (!frame) continue;
582
- return { deviceId, frame, state };
599
+ const entry = state.detectionQueue.dequeue();
600
+ if (!entry) continue;
601
+ return { deviceId, entry, state };
583
602
  }
584
603
  }
585
604
  return null;
586
605
  }
587
606
  }
607
+ const PULL_MAX_COUNT = 4;
608
+ const MIN_POLL_INTERVAL_MS = 20;
609
+ const FALLBACK_POLL_INTERVAL_MS = 200;
610
+ async function startFrameHandlePoller(options) {
611
+ const { api, brokerId, format, maxFps, tag, onFrame, logger } = options;
612
+ let result;
613
+ try {
614
+ result = await api.streamBroker.subscribeFrames.mutate({
615
+ brokerId,
616
+ format,
617
+ maxFps,
618
+ tag
619
+ });
620
+ } catch (err) {
621
+ logger.warn("frame-handle poller: subscribeFrames failed", {
622
+ meta: { brokerId, format, tag, error: index.errMsg(err) }
623
+ });
624
+ return null;
625
+ }
626
+ const { subscriptionId } = result;
627
+ const readers = new shmRing.FrameRingReaderCache(logger);
628
+ const pollIntervalMs = result.maxFps > 0 ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1e3 / result.maxFps)) : FALLBACK_POLL_INTERVAL_MS;
629
+ let stopped = false;
630
+ let timer;
631
+ const tick = async () => {
632
+ if (stopped) return;
633
+ try {
634
+ const handles = await api.streamBroker.pullFrameHandles.query({
635
+ subscriptionId,
636
+ maxCount: PULL_MAX_COUNT
637
+ });
638
+ for (const handle of handles) {
639
+ if (stopped) break;
640
+ const frame = readers.read(handle);
641
+ if (frame) onFrame(frame, handle);
642
+ }
643
+ } catch (err) {
644
+ logger.warn("frame-handle poller: pullFrameHandles failed", {
645
+ meta: { brokerId, subscriptionId, error: index.errMsg(err) }
646
+ });
647
+ }
648
+ if (!stopped) {
649
+ timer = setTimeout(() => void tick(), pollIntervalMs);
650
+ }
651
+ };
652
+ void tick();
653
+ return () => {
654
+ if (stopped) return;
655
+ stopped = true;
656
+ if (timer) {
657
+ clearTimeout(timer);
658
+ timer = void 0;
659
+ }
660
+ readers.close();
661
+ api.streamBroker.unsubscribeFrames.mutate({ subscriptionId }).catch((err) => {
662
+ logger.warn("frame-handle poller: unsubscribeFrames failed", {
663
+ meta: { brokerId, subscriptionId, error: index.errMsg(err) }
664
+ });
665
+ });
666
+ };
667
+ }
588
668
  const BenchEngineChoiceSchema = index.object({
589
669
  runtime: index._enum(["node", "python"]),
590
670
  backend: index.string(),
@@ -745,8 +825,8 @@ class PipelineRunnerAddon extends index.BaseAddon {
745
825
  this.runner.onDetectionStreamChange((deviceId, action) => {
746
826
  this.handleDetectionStreamChange(deviceId, action);
747
827
  });
748
- this.runner.onResult(async (deviceId, frame, result, _streamType) => {
749
- this.emitInferenceResult(deviceId, frame, result);
828
+ this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {
829
+ this.emitInferenceResult(deviceId, frame, result, handle);
750
830
  });
751
831
  this.runner.start();
752
832
  this.ctx.logger.info(
@@ -1300,18 +1380,19 @@ class PipelineRunnerAddon extends index.BaseAddon {
1300
1380
  log.warn("subscribeMotionFrames: this.ctx.api not available");
1301
1381
  return null;
1302
1382
  }
1303
- const motionBrokerId = `${config.deviceId}/${config.motionStreamId}`;
1304
- const motionBroker = await api.streamBroker.getBroker.query({ brokerId: motionBrokerId });
1305
- if (!motionBroker) {
1306
- log.warn("subscribeMotionFrames: no broker found", { meta: { brokerId: motionBrokerId } });
1307
- return null;
1308
- }
1309
- return motionBroker.onDecodedFrame(
1310
- (frame) => {
1383
+ return startFrameHandlePoller({
1384
+ api,
1385
+ brokerId: `${config.deviceId}/${config.motionStreamId}`,
1386
+ format: "gray",
1387
+ maxFps: config.motionFps,
1388
+ tag: "motion",
1389
+ logger: log,
1390
+ // Motion analysis is intra-process and never propagates beyond
1391
+ // the runner; the handle is intentionally ignored here.
1392
+ onFrame: (frame, _handle) => {
1311
1393
  runner.enqueueMotionFrame(config.deviceId, frame);
1312
- },
1313
- { maxFps: config.motionFps, format: "gray", tag: "motion" }
1314
- );
1394
+ }
1395
+ });
1315
1396
  }
1316
1397
  handleDetectionStreamChange(deviceId, action) {
1317
1398
  const attachment = this.attached.get(deviceId);
@@ -1385,23 +1466,20 @@ class PipelineRunnerAddon extends index.BaseAddon {
1385
1466
  log.warn("subscribeDetectionFrames: this.ctx.api not available");
1386
1467
  return null;
1387
1468
  }
1388
- const detectionBrokerId = `${config.deviceId}/${config.detectionStreamId}`;
1389
- const detectionBroker = await api.streamBroker.getBroker.query({ brokerId: detectionBrokerId });
1390
- if (!detectionBroker) {
1391
- log.warn("subscribeDetectionFrames: no broker found", { meta: { brokerId: detectionBrokerId } });
1392
- return null;
1393
- }
1394
- return detectionBroker.onDecodedFrame(
1395
- (frame) => {
1396
- runner.enqueueDetectionFrame(config.deviceId, frame);
1397
- },
1398
- // `format: 'rgb'` is the Phase 4 hot-path switch: detection now
1399
- // requests raw RGB24 from the broker so the decoder skips the
1400
- // sharp JPEG encode and the Python pool skips PIL JPEG decode.
1401
- // When WebRTC also subscribes (jpeg), the broker derives JPEG
1402
- // once per frame via its conversion cache — no double work.
1403
- { maxFps: config.detectionFps, format: "rgb", tag: "detection" }
1404
- );
1469
+ return startFrameHandlePoller({
1470
+ api,
1471
+ brokerId: `${config.deviceId}/${config.detectionStreamId}`,
1472
+ format: "rgb",
1473
+ maxFps: config.detectionFps,
1474
+ tag: "detection",
1475
+ logger: log,
1476
+ // Detection threads the `FrameHandle` through the runner so the
1477
+ // emitted `PipelineInferenceResultPayload` can name the shm slot
1478
+ // post-analysis (Task 8) reads pixels back from.
1479
+ onFrame: (frame, handle) => {
1480
+ runner.enqueueDetectionFrame(config.deviceId, frame, handle);
1481
+ }
1482
+ });
1405
1483
  }
1406
1484
  // ── Internal: inference + motion callbacks ───────────────────────────
1407
1485
  async runInference(deviceId, frame) {
@@ -1518,13 +1596,14 @@ class PipelineRunnerAddon extends index.BaseAddon {
1518
1596
  log.error("runMotionAnalysis failed", { meta: { error: msg } });
1519
1597
  }
1520
1598
  }
1521
- emitInferenceResult(deviceId, _frame, result) {
1599
+ emitInferenceResult(deviceId, _frame, result, handle) {
1522
1600
  const ctx = this.ctx;
1523
1601
  if (!ctx?.eventBus) return;
1524
1602
  const payload = {
1525
1603
  deviceId,
1526
1604
  frame: result,
1527
- nodeId: this.nodeId
1605
+ nodeId: this.nodeId,
1606
+ frameHandle: handle
1528
1607
  };
1529
1608
  this.ctx.eventBus.emit(index.createEvent(
1530
1609
  index.EventCategory.PipelineInferenceResult,