@camstack/addon-pipeline 1.1.53 → 1.1.55

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 (35) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +289 -1469
  4. package/dist/detection-pipeline/index.mjs +269 -1449
  5. package/dist/{dist-DWb62H5U.js → dist-DI57FC8K.js} +167 -8
  6. package/dist/{dist-BalxNwOt.mjs → dist-RWGGPwVx.mjs} +167 -8
  7. package/dist/motion-wasm/index.js +1 -1
  8. package/dist/motion-wasm/index.mjs +1 -1
  9. package/dist/pipeline-runner/index.js +772 -21
  10. package/dist/pipeline-runner/index.mjs +772 -22
  11. package/dist/recorder/index.js +1 -1
  12. package/dist/recorder/index.mjs +1 -1
  13. package/dist/{remote-source-plane-CZpzIVro.js → remote-source-plane-CHgvzzA6.js} +1 -1
  14. package/dist/{remote-source-plane-BCJW5CvF.mjs → remote-source-plane-DU0aRSPv.mjs} +1 -1
  15. package/dist/session-decode/decode-worker-child.js +538 -35
  16. package/dist/session-decode/decode-worker-child.mjs +538 -35
  17. package/dist/step-definitions-CNBFKjZe.js +1514 -0
  18. package/dist/step-definitions-CP9kVSml.mjs +1479 -0
  19. package/dist/stream-broker/_stub.js +2 -2
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DIN3CcRP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-mDCPzmT3.mjs} +3 -3
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B3gTdHEh.mjs +26 -0
  22. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C9fwKMfg.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dn_pxzP-.mjs} +1 -1
  23. package/dist/stream-broker/{hostInit-CGUEq--X.mjs → hostInit-GM_CI22k.mjs} +3 -3
  24. package/dist/stream-broker/index.js +2 -2
  25. package/dist/stream-broker/index.mjs +2 -2
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/dist/{worker-protocol-pk7qdYXt.mjs → worker-protocol-CyVJTZEO.mjs} +7 -0
  28. package/dist/{worker-protocol-BCfO8gUF.js → worker-protocol-PP4jKHHJ.js} +7 -0
  29. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CqOe8eYa.js → MaskShapeCanvas-DI4BY7W2-BChW0ntM.js} +1 -1
  30. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CZyLeUnd.js → MotionZonesSettings-NcxxQN8r-DDzqEbSe.js} +1 -1
  31. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Cn0hGZnu.js → PrivacyMaskSettings-APgPLF7p-B879GXaf.js} +1 -1
  32. package/embed-dist/assets/{index-DRl4XYjA.js → index-CNjQ5rAE.js} +10 -10
  33. package/embed-dist/index.html +1 -1
  34. package/package.json +1 -1
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-o4tu_xuc.mjs +0 -26
@@ -1,5 +1,6 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { H as createEvent, I as errMsg, L as BaseAddon, O as pipelineExecutorCapability, P as supportedRuntimes$1, S as hfModelUrl, U as hydrateSchema, V as EventCategory, X as sleep, a as COCO_TO_MACRO, b as detectionPipelineCapability, f as YAMNET_TO_MACRO, i as COCO_80_LABELS, j as runtimeDevices$1, q as parseJsonUnknown, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, v as defaultDeviceFor$1, x as evaluateZoneRules } from "../dist-BalxNwOt.mjs";
2
+ import { D as nodePin, H as createEvent, I as errMsg, L as BaseAddon, O as pipelineExecutorCapability, P as supportedRuntimes$1, U as hydrateSchema, V as EventCategory, X as sleep, b as detectionPipelineCapability, f as YAMNET_TO_MACRO, j as runtimeDevices$1, q as parseJsonUnknown, t as APPLE_SA_TO_MACRO, v as defaultDeviceFor$1, x as evaluateZoneRules } from "../dist-RWGGPwVx.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-CP9kVSml.mjs";
3
4
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
5
  import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
5
6
  import { FrameRingReaderCache } from "@camstack/shm-ring";
@@ -290,1437 +291,6 @@ function projectNodeEngine(store, nodeId) {
290
291
  return out;
291
292
  }
292
293
  //#endregion
293
- //#region src/detection-pipeline/registry/model-catalogs.ts
294
- var HF_REPO = "camstack/camstack-models";
295
- var HF_SCRYPTED = "scrypted/plugin-models";
296
- var hf = (path) => hfModelUrl(HF_REPO, path);
297
- var hfScrypted = (path) => hfModelUrl(HF_SCRYPTED, path);
298
- /**
299
- * Build an OpenVINO format entry (always python runtime).
300
- *
301
- * OpenVINO IR is a two-file bundle: a `.xml` topology + a sibling `.bin`
302
- * weights file with the same basename. We declare the `.bin` in `files` so
303
- * the (format-agnostic) downloader fetches it alongside the `.xml` — without
304
- * the weights, OpenVINO compile fails with "Empty weights data in bin file".
305
- * A plain `.onnx` run through the OpenVINO runtime (e.g. yamnet) has no
306
- * sibling, so none is added.
307
- */
308
- var ovFormat = (url, sizeMB) => {
309
- const base = url.split("/").pop() ?? "";
310
- const files = base.endsWith(".xml") ? [base.replace(/\.xml$/, ".bin")] : void 0;
311
- return {
312
- url,
313
- sizeMB,
314
- runtimes: ["python"],
315
- ...files ? { files } : {}
316
- };
317
- };
318
- /**
319
- * Build a precision-variant catalog entry (OpenVINO-only) derived from a base
320
- * detection model. fp16 halves the weights (Intel iGPU/NPU sweet spot); int8 is
321
- * NNCF post-training-quantized (~4× smaller, fastest on CPU/iGPU at a small
322
- * accuracy cost). The IRs live next to the base `.xml` on HF as
323
- * `camstack-<id>-<precision>.xml`. Lets an operator scale the model to the node
324
- * (e.g. yolo26x-int8 on a 265K, yolo26n-int8 on an N100).
325
- */
326
- var ovPrecisionVariant = (baseId, ovDir, baseName, precision, sizeMB, legacy = false) => ({
327
- id: `${baseId}-${precision}`,
328
- name: `${baseName} (${precision.toUpperCase()})`,
329
- description: `${baseName} — OpenVINO ${precision.toUpperCase()} variant for Intel iGPU/NPU; scale by hardware`,
330
- inputSize: {
331
- width: 640,
332
- height: 640
333
- },
334
- labels: [],
335
- preprocessMode: "letterbox",
336
- formats: { openvino: ovFormat(hf(`${ovDir}/camstack-${baseId}-${precision}.xml`), sizeMB) },
337
- ...legacy ? { legacy: true } : {},
338
- ...precision === "int8" ? { group: {
339
- family: baseId.slice(0, -1),
340
- tier: baseId.slice(-1),
341
- precision: "int8"
342
- } } : {}
343
- });
344
- var MLPACKAGE_FILES = [
345
- "Manifest.json",
346
- "Data/com.apple.CoreML/model.mlmodel",
347
- "Data/com.apple.CoreML/weights/weight.bin"
348
- ];
349
- var YOLO26_TIER_NAME = {
350
- n: "Nano",
351
- s: "Small",
352
- m: "Medium",
353
- l: "Large"
354
- };
355
- /**
356
- * Reduced-input YOLO26 variants (2026-07-11). A lower square input (320 / 256)
357
- * is the biggest, cheapest latency lever measured across every runtime — Mac
358
- * ANE, Intel Ultra NPU, and especially the N100 — at a small-object accuracy
359
- * cost. Each resolution ships a base (fp32) entry (onnx + coreml + openvino)
360
- * grouped under `{family, tier, resolution}`, plus an OpenVINO INT8 entry (best
361
- * on Intel/N100) grouped with `precision:'int8'`. The grouped selector surfaces
362
- * `resolution` as a variant axis alongside precision.
363
- */
364
- var yolo26Reduced = (tier, res, sizes) => {
365
- const name = YOLO26_TIER_NAME[tier] ?? tier.toUpperCase();
366
- return [{
367
- id: `yolo26${tier}-${res}`,
368
- group: {
369
- family: "yolo26",
370
- tier,
371
- resolution: res
372
- },
373
- name: `YOLO26 ${name} @${res}`,
374
- description: `YOLO26 ${name}, reduced ${res}×${res} input — faster on ANE / hub NPU / N100 at a small-object accuracy cost`,
375
- inputSize: {
376
- width: res,
377
- height: res
378
- },
379
- labels: [],
380
- preprocessMode: "letterbox",
381
- formats: {
382
- onnx: {
383
- url: hf(`objectDetection/yolo26/onnx/camstack-yolo26${tier}-${res}.onnx`),
384
- sizeMB: sizes.onnx
385
- },
386
- coreml: {
387
- url: hf(`objectDetection/yolo26/coreml/camstack-yolo26${tier}-${res}.mlpackage`),
388
- sizeMB: sizes.coreml,
389
- isDirectory: true,
390
- files: [...MLPACKAGE_FILES],
391
- runtimes: ["python"]
392
- },
393
- openvino: ovFormat(hf(`objectDetection/yolo26/openvino/camstack-yolo26${tier}-${res}.xml`), sizes.ovFp32)
394
- }
395
- }, {
396
- id: `yolo26${tier}-${res}-int8`,
397
- group: {
398
- family: "yolo26",
399
- tier,
400
- precision: "int8",
401
- resolution: res
402
- },
403
- name: `YOLO26 ${name} @${res} (INT8)`,
404
- description: `YOLO26 ${name} @${res} — OpenVINO INT8 for Intel iGPU/CPU; the N100's fastest config`,
405
- inputSize: {
406
- width: res,
407
- height: res
408
- },
409
- labels: [],
410
- preprocessMode: "letterbox",
411
- formats: { openvino: ovFormat(hf(`objectDetection/yolo26/openvino/camstack-yolo26${tier}-${res}-int8.xml`), sizes.ovInt8) }
412
- }];
413
- };
414
- var OBJECT_DETECTION_MODELS = [
415
- {
416
- id: "yolov9t",
417
- legacy: true,
418
- name: "YOLOv9 Tiny",
419
- description: "YOLOv9 Tiny — ultra-lightweight next-gen detector",
420
- inputSize: {
421
- width: 640,
422
- height: 640
423
- },
424
- labels: [],
425
- preprocessMode: "letterbox",
426
- formats: {
427
- onnx: {
428
- url: hf("objectDetection/yolov9/onnx/camstack-yolov9t.onnx"),
429
- sizeMB: 8
430
- },
431
- coreml: {
432
- url: hf("objectDetection/yolov9/coreml/camstack-yolov9t.mlpackage"),
433
- sizeMB: 4,
434
- isDirectory: true,
435
- files: [...MLPACKAGE_FILES],
436
- runtimes: ["python"]
437
- },
438
- openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9t.xml"), 6)
439
- }
440
- },
441
- {
442
- id: "yolov9s",
443
- legacy: true,
444
- name: "YOLOv9 Small",
445
- description: "YOLOv9 Small — improved efficiency over YOLOv8s",
446
- inputSize: {
447
- width: 640,
448
- height: 640
449
- },
450
- labels: [],
451
- preprocessMode: "letterbox",
452
- formats: {
453
- onnx: {
454
- url: hf("objectDetection/yolov9/onnx/camstack-yolov9s.onnx"),
455
- sizeMB: 28
456
- },
457
- coreml: {
458
- url: hf("objectDetection/yolov9/coreml/camstack-yolov9s.mlpackage"),
459
- sizeMB: 14,
460
- isDirectory: true,
461
- files: [...MLPACKAGE_FILES],
462
- runtimes: ["python"]
463
- },
464
- openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9s.xml"), 16)
465
- }
466
- },
467
- {
468
- id: "yolov9c",
469
- legacy: true,
470
- name: "YOLOv9 C",
471
- description: "YOLOv9 C — high-accuracy compact model",
472
- inputSize: {
473
- width: 640,
474
- height: 640
475
- },
476
- labels: [],
477
- preprocessMode: "letterbox",
478
- formats: {
479
- onnx: {
480
- url: hf("objectDetection/yolov9/onnx/camstack-yolov9c.onnx"),
481
- sizeMB: 97
482
- },
483
- coreml: {
484
- url: hf("objectDetection/yolov9/coreml/camstack-yolov9c.mlpackage"),
485
- sizeMB: 48,
486
- isDirectory: true,
487
- files: [...MLPACKAGE_FILES],
488
- runtimes: ["python"]
489
- },
490
- openvino: ovFormat(hf("objectDetection/yolov9/openvino/camstack-yolov9c.xml"), 49)
491
- }
492
- },
493
- {
494
- id: "yolo26n",
495
- group: {
496
- family: "yolo26",
497
- tier: "n"
498
- },
499
- name: "YOLO26 Nano",
500
- description: "YOLO26 Nano — latest generation ultra-lightweight detector with attention mechanism",
501
- inputSize: {
502
- width: 640,
503
- height: 640
504
- },
505
- labels: [],
506
- preprocessMode: "letterbox",
507
- formats: {
508
- onnx: {
509
- url: hf("objectDetection/yolo26/onnx/camstack-yolo26n.onnx"),
510
- sizeMB: 10
511
- },
512
- coreml: {
513
- url: hf("objectDetection/yolo26/coreml/camstack-yolo26n.mlpackage"),
514
- sizeMB: 5,
515
- isDirectory: true,
516
- files: [...MLPACKAGE_FILES],
517
- runtimes: ["python"]
518
- },
519
- openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26n.xml"), 9)
520
- }
521
- },
522
- {
523
- id: "yolo26s",
524
- group: {
525
- family: "yolo26",
526
- tier: "s"
527
- },
528
- name: "YOLO26 Small",
529
- description: "YOLO26 Small — balanced speed/accuracy with attention-based architecture",
530
- inputSize: {
531
- width: 640,
532
- height: 640
533
- },
534
- labels: [],
535
- preprocessMode: "letterbox",
536
- formats: {
537
- onnx: {
538
- url: hf("objectDetection/yolo26/onnx/camstack-yolo26s.onnx"),
539
- sizeMB: 37
540
- },
541
- coreml: {
542
- url: hf("objectDetection/yolo26/coreml/camstack-yolo26s.mlpackage"),
543
- sizeMB: 18,
544
- isDirectory: true,
545
- files: [...MLPACKAGE_FILES],
546
- runtimes: ["python"]
547
- },
548
- openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26s.xml"), 36)
549
- }
550
- },
551
- {
552
- id: "yolo26m",
553
- group: {
554
- family: "yolo26",
555
- tier: "m"
556
- },
557
- name: "YOLO26 Medium",
558
- description: "YOLO26 Medium — high accuracy general-purpose detector",
559
- inputSize: {
560
- width: 640,
561
- height: 640
562
- },
563
- labels: [],
564
- preprocessMode: "letterbox",
565
- formats: {
566
- onnx: {
567
- url: hf("objectDetection/yolo26/onnx/camstack-yolo26m.onnx"),
568
- sizeMB: 78
569
- },
570
- coreml: {
571
- url: hf("objectDetection/yolo26/coreml/camstack-yolo26m.mlpackage"),
572
- sizeMB: 39,
573
- isDirectory: true,
574
- files: [...MLPACKAGE_FILES],
575
- runtimes: ["python"]
576
- },
577
- openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26m.xml"), 78)
578
- }
579
- },
580
- {
581
- id: "yolo26l",
582
- group: {
583
- family: "yolo26",
584
- tier: "l"
585
- },
586
- name: "YOLO26 Large",
587
- description: "YOLO26 Large — maximum accuracy for demanding deployments",
588
- inputSize: {
589
- width: 640,
590
- height: 640
591
- },
592
- labels: [],
593
- preprocessMode: "letterbox",
594
- formats: {
595
- onnx: {
596
- url: hf("objectDetection/yolo26/onnx/camstack-yolo26l.onnx"),
597
- sizeMB: 95
598
- },
599
- coreml: {
600
- url: hf("objectDetection/yolo26/coreml/camstack-yolo26l.mlpackage"),
601
- sizeMB: 48,
602
- isDirectory: true,
603
- files: [...MLPACKAGE_FILES],
604
- runtimes: ["python"]
605
- },
606
- openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26l.xml"), 95)
607
- }
608
- },
609
- {
610
- id: "yolo26x",
611
- legacy: true,
612
- name: "YOLO26 XLarge",
613
- description: "YOLO26 XLarge — highest accuracy, attention-based architecture",
614
- inputSize: {
615
- width: 640,
616
- height: 640
617
- },
618
- labels: [],
619
- preprocessMode: "letterbox",
620
- formats: {
621
- onnx: {
622
- url: hf("objectDetection/yolo26/onnx/camstack-yolo26x.onnx"),
623
- sizeMB: 213
624
- },
625
- coreml: {
626
- url: hf("objectDetection/yolo26/coreml/camstack-yolo26x.mlpackage"),
627
- sizeMB: 107,
628
- isDirectory: true,
629
- files: [...MLPACKAGE_FILES],
630
- runtimes: ["python"]
631
- },
632
- openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"), 213)
633
- }
634
- },
635
- {
636
- id: "scrypted-yolov9t-relu",
637
- legacy: true,
638
- name: "YOLOv9 Tiny ReLU",
639
- description: "YOLOv9t ReLU — optimized for int8 quantization",
640
- inputSize: {
641
- width: 320,
642
- height: 320
643
- },
644
- labels: [],
645
- preprocessMode: "letterbox",
646
- formats: {
647
- onnx: {
648
- url: hfScrypted("onnx/scrypted_yolov9t_relu/scrypted_yolov9t_relu.onnx"),
649
- sizeMB: 8.1
650
- },
651
- coreml: {
652
- url: hfScrypted("coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage"),
653
- sizeMB: 4,
654
- isDirectory: true,
655
- files: [...MLPACKAGE_FILES],
656
- runtimes: ["python"]
657
- },
658
- openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9t_relu.xml"), 6)
659
- }
660
- },
661
- {
662
- id: "scrypted-yolov9s-relu",
663
- legacy: true,
664
- name: "YOLOv9 Small ReLU",
665
- description: "YOLOv9s ReLU — improved efficiency, int8 ready",
666
- inputSize: {
667
- width: 320,
668
- height: 320
669
- },
670
- labels: [],
671
- preprocessMode: "letterbox",
672
- formats: {
673
- onnx: {
674
- url: hfScrypted("onnx/scrypted_yolov9s_relu/scrypted_yolov9s_relu.onnx"),
675
- sizeMB: 27.9
676
- },
677
- coreml: {
678
- url: hfScrypted("coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage"),
679
- sizeMB: 14,
680
- isDirectory: true,
681
- files: [...MLPACKAGE_FILES],
682
- runtimes: ["python"]
683
- },
684
- openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9s_relu.xml"), 16)
685
- }
686
- },
687
- {
688
- id: "scrypted-yolov9c-relu",
689
- legacy: true,
690
- name: "YOLOv9 C ReLU",
691
- description: "YOLOv9c ReLU — high-accuracy, int8 ready",
692
- inputSize: {
693
- width: 320,
694
- height: 320
695
- },
696
- labels: [],
697
- preprocessMode: "letterbox",
698
- formats: {
699
- onnx: {
700
- url: hfScrypted("onnx/scrypted_yolov9c_relu/scrypted_yolov9c_relu.onnx"),
701
- sizeMB: 96.9
702
- },
703
- coreml: {
704
- url: hfScrypted("coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage"),
705
- sizeMB: 48,
706
- isDirectory: true,
707
- files: [...MLPACKAGE_FILES],
708
- runtimes: ["python"]
709
- },
710
- openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9c_relu.xml"), 49)
711
- }
712
- },
713
- {
714
- id: "scrypted-yolov9m-relu",
715
- legacy: true,
716
- name: "YOLOv9 M ReLU",
717
- description: "YOLOv9m ReLU — medium, int8 ready",
718
- inputSize: {
719
- width: 320,
720
- height: 320
721
- },
722
- labels: [],
723
- preprocessMode: "letterbox",
724
- formats: {
725
- onnx: {
726
- url: hfScrypted("onnx/scrypted_yolov9m_relu/scrypted_yolov9m_relu.onnx"),
727
- sizeMB: 76.6
728
- },
729
- coreml: {
730
- url: hfScrypted("coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage"),
731
- sizeMB: 38,
732
- isDirectory: true,
733
- files: [...MLPACKAGE_FILES],
734
- runtimes: ["python"]
735
- },
736
- openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9m_relu.xml"), 38)
737
- }
738
- },
739
- ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "fp16", 5, true),
740
- ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "int8", 3, true),
741
- ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "fp16", 15, true),
742
- ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "int8", 8, true),
743
- ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "fp16", 5, true),
744
- ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "int8", 3),
745
- ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "fp16", 19, true),
746
- ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "int8", 10),
747
- ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "fp16", 41, true),
748
- ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "int8", 21),
749
- ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "fp16", 50, true),
750
- ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "int8", 25),
751
- ...yolo26Reduced("n", 320, {
752
- onnx: 10.47,
753
- coreml: 5.05,
754
- ovFp32: 10.05,
755
- ovInt8: 3.29
756
- }),
757
- ...yolo26Reduced("n", 256, {
758
- onnx: 10.47,
759
- coreml: 5.05,
760
- ovFp32: 10.05,
761
- ovInt8: 3.29
762
- }),
763
- ...yolo26Reduced("s", 320, {
764
- onnx: 38.82,
765
- coreml: 19.23,
766
- ovFp32: 38.4,
767
- ovInt8: 10.43
768
- }),
769
- ...yolo26Reduced("s", 256, {
770
- onnx: 38.82,
771
- coreml: 19.23,
772
- ovFp32: 38.4,
773
- ovInt8: 10.43
774
- }),
775
- ...yolo26Reduced("m", 320, {
776
- onnx: 83.31,
777
- coreml: 41.07,
778
- ovFp32: 82.09,
779
- ovInt8: 21.47
780
- }),
781
- ...yolo26Reduced("m", 256, {
782
- onnx: 83.31,
783
- coreml: 41.07,
784
- ovFp32: 82.09,
785
- ovInt8: 21.47
786
- }),
787
- ...yolo26Reduced("l", 320, {
788
- onnx: 101.74,
789
- coreml: 49.97,
790
- ovFp32: 99.85,
791
- ovInt8: 26.31
792
- }),
793
- ...yolo26Reduced("l", 256, {
794
- onnx: 101.74,
795
- coreml: 49.97,
796
- ovFp32: 99.85,
797
- ovInt8: 26.31
798
- }),
799
- ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "fp16", 112, true),
800
- ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "int8", 56, true)
801
- ];
802
- var FACE_DETECTION_MODELS = [{
803
- id: "scrfd-2.5g",
804
- name: "SCRFD 2.5G",
805
- description: "SCRFD 2.5G — balanced face detection model",
806
- inputSize: {
807
- width: 640,
808
- height: 640
809
- },
810
- labels: [{
811
- id: "face",
812
- name: "Face"
813
- }],
814
- preprocessMode: "letterbox",
815
- formats: {
816
- onnx: {
817
- url: hf("faceDetection/scrfd/onnx/camstack-scrfd-2.5g.onnx"),
818
- sizeMB: 3.1
819
- },
820
- coreml: {
821
- url: hf("faceDetection/scrfd/coreml/camstack-scrfd-2.5g.mlpackage"),
822
- sizeMB: 1.7,
823
- isDirectory: true,
824
- files: [...MLPACKAGE_FILES],
825
- runtimes: ["python"]
826
- },
827
- openvino: ovFormat(hf("faceDetection/scrfd/openvino/camstack-scrfd-2.5g.xml"), 1.8)
828
- }
829
- }, {
830
- id: "scrypted-yolov9t-face",
831
- name: "YOLOv9t Face ReLU",
832
- description: "YOLOv9t face detection — YOLO-based, fast, 320x320 ReLU",
833
- inputSize: {
834
- width: 320,
835
- height: 320
836
- },
837
- labels: [{
838
- id: "face",
839
- name: "Face"
840
- }],
841
- preprocessMode: "letterbox",
842
- formats: {
843
- onnx: {
844
- url: hfScrypted("onnx/scrypted_yolov9t_relu_face/scrypted_yolov9t_relu_face.onnx"),
845
- sizeMB: 8.1
846
- },
847
- coreml: {
848
- url: hfScrypted("coreml/scrypted_yolov9t_relu_face/scrypted_yolov9t_relu_face.mlpackage"),
849
- sizeMB: 4,
850
- isDirectory: true,
851
- files: [...MLPACKAGE_FILES],
852
- runtimes: ["python"]
853
- },
854
- openvino: ovFormat(hf("faceDetection/scrypted-yolov9-face/openvino/scrypted_yolov9t_relu_face.xml"), 6)
855
- }
856
- }];
857
- var FACE_EMBEDDING_MODELS = [{
858
- id: "arcface-r100",
859
- name: "ArcFace R100",
860
- description: "ArcFace ResNet-100 — high-accuracy face recognition embeddings (512-d)",
861
- inputSize: {
862
- width: 112,
863
- height: 112
864
- },
865
- inputLayout: "nhwc",
866
- labels: [{
867
- id: "embedding",
868
- name: "Face Embedding"
869
- }],
870
- preprocessMode: "resize",
871
- faceAlignment: true,
872
- formats: {
873
- onnx: {
874
- url: hf("faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx"),
875
- sizeMB: 130
876
- },
877
- coreml: {
878
- url: hf("faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage"),
879
- sizeMB: 65,
880
- isDirectory: true,
881
- files: [...MLPACKAGE_FILES],
882
- runtimes: ["python"]
883
- },
884
- openvino: ovFormat(hf("faceRecognition/arcface/openvino/camstack-arcface-r100.xml"), 65)
885
- }
886
- }, {
887
- id: "inception-resnet-v1",
888
- name: "Inception ResNet V1",
889
- description: "FaceNet-style face recognition embeddings (512-d) — hosted on plugin-models HF repo",
890
- inputSize: {
891
- width: 160,
892
- height: 160
893
- },
894
- labels: [{
895
- id: "embedding",
896
- name: "Face Embedding"
897
- }],
898
- preprocessMode: "resize",
899
- formats: {
900
- onnx: {
901
- url: hfScrypted("onnx/inception_resnet_v1/inception_resnet_v1.onnx"),
902
- sizeMB: 89.6
903
- },
904
- coreml: {
905
- url: hfScrypted("coreml/inception_resnet_v1/inception_resnet_v1.mlpackage"),
906
- sizeMB: 45,
907
- isDirectory: true,
908
- files: [...MLPACKAGE_FILES],
909
- runtimes: ["python"]
910
- },
911
- openvino: ovFormat(hf("faceRecognition/inception-resnet-v1/openvino/camstack-inception-resnet-v1.xml"), 45)
912
- }
913
- }];
914
- var PLATE_DETECTION_MODELS = [{
915
- id: "yolov8n-plate",
916
- name: "YOLOv8 Nano — License Plate",
917
- description: "YOLOv8 Nano fine-tuned for license plate detection",
918
- inputSize: {
919
- width: 640,
920
- height: 640
921
- },
922
- labels: [{
923
- id: "plate",
924
- name: "License Plate"
925
- }],
926
- preprocessMode: "letterbox",
927
- formats: {
928
- onnx: {
929
- url: hf("plateDetection/yolov8-plate/onnx/camstack-yolov8n-plate.onnx"),
930
- sizeMB: 12
931
- },
932
- coreml: {
933
- url: hf("plateDetection/yolov8-plate/coreml/camstack-yolov8n-plate.mlpackage"),
934
- sizeMB: 5.9,
935
- isDirectory: true,
936
- files: [...MLPACKAGE_FILES],
937
- runtimes: ["python"]
938
- },
939
- openvino: ovFormat(hf("plateDetection/yolov8-plate/openvino/camstack-yolov8n-plate.xml"), 6.1)
940
- }
941
- }];
942
- var PLATE_OCR_MODELS = [{
943
- id: "vgg-english-g2",
944
- name: "VGG English G2",
945
- description: "EasyOCR VGG English G2 — text recognition for license plates and scene text",
946
- inputSize: {
947
- width: 384,
948
- height: 64
949
- },
950
- labels: [{
951
- id: "text",
952
- name: "Plate Text"
953
- }],
954
- preprocessMode: "resize",
955
- formats: {
956
- onnx: {
957
- url: hfScrypted("onnx/vgg_english_g2/vgg_english_g2.onnx"),
958
- sizeMB: 14.4
959
- },
960
- coreml: {
961
- url: hfScrypted("coreml/vgg_english_g2/vgg_english_g2.mlpackage"),
962
- sizeMB: 11.2,
963
- isDirectory: true,
964
- files: [...MLPACKAGE_FILES],
965
- runtimes: ["python"]
966
- },
967
- openvino: ovFormat(hf("plateRecognition/vgg_english_g2/openvino/vgg_english_g2.xml"), 7.2)
968
- }
969
- }];
970
- var ANIMAL_CLASSIFIER_MODELS = [{
971
- id: "animals-10",
972
- name: "Animal Classifier (10)",
973
- description: "ViT-based animal type classifier — cat, cow, dog, dolphin, eagle, panda, horse, monkey, sheep, spider",
974
- inputSize: {
975
- width: 224,
976
- height: 224
977
- },
978
- inputNormalization: "imagenet",
979
- labels: [{
980
- id: "animal-type",
981
- name: "Animal Type"
982
- }],
983
- preprocessMode: "resize",
984
- formats: {
985
- onnx: {
986
- url: hf("animalClassification/animals-10/onnx/camstack-animals-10.onnx"),
987
- sizeMB: 328
988
- },
989
- coreml: {
990
- url: hf("animalClassification/animals-10/coreml/camstack-animals-10.mlpackage"),
991
- sizeMB: 164,
992
- isDirectory: true,
993
- files: [...MLPACKAGE_FILES],
994
- runtimes: ["python"]
995
- },
996
- openvino: ovFormat(hf("animalClassification/animals-10/openvino/camstack-animals-10.xml"), 164)
997
- }
998
- }];
999
- var BIRD_CLASSIFIER_MODELS = [{
1000
- id: "bird-nabirds-404",
1001
- name: "NABirds (404 species)",
1002
- description: "ResNet50 trained on NABirds — 404 North American species",
1003
- inputSize: {
1004
- width: 224,
1005
- height: 224
1006
- },
1007
- inputNormalization: "imagenet",
1008
- labels: [{
1009
- id: "species",
1010
- name: "Bird Species"
1011
- }],
1012
- preprocessMode: "resize",
1013
- formats: {
1014
- onnx: {
1015
- url: hf("animalClassification/bird-nabirds/onnx/camstack-bird-nabirds-404.onnx"),
1016
- sizeMB: 93
1017
- },
1018
- coreml: {
1019
- url: hf("animalClassification/bird-nabirds/coreml/camstack-bird-nabirds-404.mlpackage"),
1020
- sizeMB: 47,
1021
- isDirectory: true,
1022
- files: [...MLPACKAGE_FILES],
1023
- runtimes: ["python"]
1024
- },
1025
- openvino: ovFormat(hf("animalClassification/bird-nabirds/openvino/camstack-bird-nabirds-404.xml"), 47)
1026
- },
1027
- extraFiles: [{
1028
- url: hf("animalClassification/bird-nabirds/onnx/camstack-bird-nabirds-404-labels.json"),
1029
- filename: "camstack-bird-nabirds-404-labels.json",
1030
- sizeMB: .02
1031
- }]
1032
- }];
1033
- var VEHICLE_CLASSIFIER_MODELS = [{
1034
- id: "vehicle-type-efficientnet",
1035
- name: "Vehicle Type (EfficientNet)",
1036
- description: "EfficientNet-B4 vehicle make/model/year classifier — 8,949 classes from VMMRdb",
1037
- inputSize: {
1038
- width: 380,
1039
- height: 380
1040
- },
1041
- inputNormalization: "imagenet",
1042
- labels: [{
1043
- id: "vehicle-type",
1044
- name: "Vehicle Type"
1045
- }],
1046
- preprocessMode: "resize",
1047
- formats: {
1048
- onnx: {
1049
- url: hf("vehicleClassification/efficientnet/onnx/camstack-vehicle-type-efficientnet.onnx"),
1050
- sizeMB: 135
1051
- },
1052
- coreml: {
1053
- url: hf("vehicleClassification/efficientnet/coreml/camstack-vehicle-type-efficientnet.mlpackage"),
1054
- sizeMB: 10,
1055
- isDirectory: true,
1056
- files: [...MLPACKAGE_FILES],
1057
- runtimes: ["python"]
1058
- },
1059
- openvino: ovFormat(hf("vehicleClassification/efficientnet/openvino/camstack-vehicle-type-efficientnet.xml"), 68)
1060
- },
1061
- extraFiles: [{
1062
- url: hf("vehicleClassification/efficientnet/camstack-vehicle-type-labels.json"),
1063
- filename: "camstack-vehicle-type-labels.json",
1064
- sizeMB: .2
1065
- }]
1066
- }];
1067
- var SEGMENTATION_REFINER_MODELS = [{
1068
- id: "u2netp",
1069
- name: "U2-Net Portable",
1070
- description: "U2-Net-P — ultra-lightweight salient object segmentation (4.7 MB)",
1071
- inputSize: {
1072
- width: 320,
1073
- height: 320
1074
- },
1075
- labels: [{
1076
- id: "mask",
1077
- name: "Segmentation Mask"
1078
- }],
1079
- preprocessMode: "resize",
1080
- formats: {
1081
- onnx: {
1082
- url: hf("segmentationRefiner/u2netp/onnx/camstack-u2netp.onnx"),
1083
- sizeMB: 5
1084
- },
1085
- coreml: {
1086
- url: hf("segmentationRefiner/u2netp/coreml/camstack-u2netp.mlpackage"),
1087
- sizeMB: 3,
1088
- isDirectory: true,
1089
- files: [...MLPACKAGE_FILES],
1090
- runtimes: ["python"]
1091
- },
1092
- openvino: ovFormat(hf("segmentationRefiner/u2netp/openvino/camstack-u2netp.xml"), 2.5)
1093
- }
1094
- }];
1095
- var INSTANCE_SEGMENTATION_MODELS = [
1096
- {
1097
- id: "yolo26n-seg",
1098
- name: "YOLO26 Nano Seg",
1099
- description: "YOLO26 Nano Segmentation — ultra-lightweight instance segmentation with masks",
1100
- inputSize: {
1101
- width: 640,
1102
- height: 640
1103
- },
1104
- labels: [],
1105
- preprocessMode: "letterbox",
1106
- formats: {
1107
- onnx: {
1108
- url: hf("segmentation/yolo26-seg/onnx/camstack-yolo26n-seg.onnx"),
1109
- sizeMB: 11
1110
- },
1111
- coreml: {
1112
- url: hf("segmentation/yolo26-seg/coreml/camstack-yolo26n-seg.mlpackage"),
1113
- sizeMB: 6,
1114
- isDirectory: true,
1115
- files: [...MLPACKAGE_FILES],
1116
- runtimes: ["python"]
1117
- },
1118
- openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26n-seg.xml"), 11)
1119
- }
1120
- },
1121
- {
1122
- id: "yolo26s-seg",
1123
- name: "YOLO26 Small Seg",
1124
- description: "YOLO26 Small Segmentation — balanced instance segmentation",
1125
- inputSize: {
1126
- width: 640,
1127
- height: 640
1128
- },
1129
- labels: [],
1130
- preprocessMode: "letterbox",
1131
- formats: {
1132
- onnx: {
1133
- url: hf("segmentation/yolo26-seg/onnx/camstack-yolo26s-seg.onnx"),
1134
- sizeMB: 40
1135
- },
1136
- coreml: {
1137
- url: hf("segmentation/yolo26-seg/coreml/camstack-yolo26s-seg.mlpackage"),
1138
- sizeMB: 20,
1139
- isDirectory: true,
1140
- files: [...MLPACKAGE_FILES],
1141
- runtimes: ["python"]
1142
- },
1143
- openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26s-seg.xml"), 40)
1144
- }
1145
- },
1146
- {
1147
- id: "yolo26m-seg",
1148
- name: "YOLO26 Medium Seg",
1149
- description: "YOLO26 Medium Segmentation — high-accuracy instance segmentation",
1150
- inputSize: {
1151
- width: 640,
1152
- height: 640
1153
- },
1154
- labels: [],
1155
- preprocessMode: "letterbox",
1156
- formats: {
1157
- onnx: {
1158
- url: hf("segmentation/yolo26-seg/onnx/camstack-yolo26m-seg.onnx"),
1159
- sizeMB: 90
1160
- },
1161
- coreml: {
1162
- url: hf("segmentation/yolo26-seg/coreml/camstack-yolo26m-seg.mlpackage"),
1163
- sizeMB: 45,
1164
- isDirectory: true,
1165
- files: [...MLPACKAGE_FILES],
1166
- runtimes: ["python"]
1167
- },
1168
- openvino: ovFormat(hf("segmentation/yolo26-seg/openvino/camstack-yolo26m-seg.xml"), 90)
1169
- }
1170
- }
1171
- ];
1172
- var CLIP_EMBEDDING_MODELS = [
1173
- {
1174
- id: "mobileclip-s0",
1175
- name: "MobileCLIP S0",
1176
- description: "MobileCLIP S0 — Apple ultra-fast CLIP vision encoder, 512-dim, 256×256 (40 MB ONNX)",
1177
- inputSize: {
1178
- width: 256,
1179
- height: 256
1180
- },
1181
- labels: [{
1182
- id: "embedding",
1183
- name: "CLIP Embedding"
1184
- }],
1185
- preprocessMode: "resize",
1186
- inputNormalization: "none",
1187
- formats: {
1188
- onnx: {
1189
- url: hf("clip/mobileclip-s0/onnx/camstack-mobileclip-s0-vision.onnx"),
1190
- sizeMB: 40
1191
- },
1192
- openvino: ovFormat(hf("clip/mobileclip-s0/openvino/camstack-mobileclip-s0-vision.xml"), 25),
1193
- coreml: {
1194
- url: hf("clip/mobileclip-s0/coreml/camstack-mobileclip-s0-vision.mlpackage"),
1195
- sizeMB: 30,
1196
- isDirectory: true,
1197
- files: [...MLPACKAGE_FILES],
1198
- runtimes: ["python"]
1199
- }
1200
- }
1201
- },
1202
- {
1203
- id: "mobileclip-s1",
1204
- name: "MobileCLIP S1",
1205
- description: "MobileCLIP S1 — Apple balanced CLIP vision encoder, 512-dim, 256×256 (90 MB ONNX)",
1206
- inputSize: {
1207
- width: 256,
1208
- height: 256
1209
- },
1210
- labels: [{
1211
- id: "embedding",
1212
- name: "CLIP Embedding"
1213
- }],
1214
- preprocessMode: "resize",
1215
- inputNormalization: "none",
1216
- formats: {
1217
- onnx: {
1218
- url: hf("clip/mobileclip-s1/onnx/camstack-mobileclip-s1-vision.onnx"),
1219
- sizeMB: 90
1220
- },
1221
- openvino: ovFormat(hf("clip/mobileclip-s1/openvino/camstack-mobileclip-s1-vision.xml"), 55),
1222
- coreml: {
1223
- url: hf("clip/mobileclip-s1/coreml/camstack-mobileclip-s1-vision.mlpackage"),
1224
- sizeMB: 65,
1225
- isDirectory: true,
1226
- files: [...MLPACKAGE_FILES],
1227
- runtimes: ["python"]
1228
- }
1229
- }
1230
- },
1231
- {
1232
- id: "mobileclip-s2",
1233
- name: "MobileCLIP S2",
1234
- description: "MobileCLIP S2 — Apple high-accuracy CLIP vision encoder, 512-dim, 256×256 (150 MB ONNX)",
1235
- inputSize: {
1236
- width: 256,
1237
- height: 256
1238
- },
1239
- labels: [{
1240
- id: "embedding",
1241
- name: "CLIP Embedding"
1242
- }],
1243
- preprocessMode: "resize",
1244
- inputNormalization: "none",
1245
- formats: {
1246
- onnx: {
1247
- url: hf("clip/mobileclip-s2/onnx/camstack-mobileclip-s2-vision.onnx"),
1248
- sizeMB: 150
1249
- },
1250
- openvino: ovFormat(hf("clip/mobileclip-s2/openvino/camstack-mobileclip-s2-vision.xml"), 90),
1251
- coreml: {
1252
- url: hf("clip/mobileclip-s2/coreml/camstack-mobileclip-s2-vision.mlpackage"),
1253
- sizeMB: 110,
1254
- isDirectory: true,
1255
- files: [...MLPACKAGE_FILES],
1256
- runtimes: ["python"]
1257
- }
1258
- }
1259
- }
1260
- ];
1261
- var AUDIO_CLASSIFIER_MODELS = [{
1262
- id: "yamnet-onnx",
1263
- name: "YAMNet",
1264
- description: "Google YAMNet — 521-class audio event classifier (3.2 MB ONNX, runs on any platform)",
1265
- inputSize: {
1266
- width: 1,
1267
- height: 16e3
1268
- },
1269
- labels: [],
1270
- preprocessMode: "resize",
1271
- formats: {
1272
- onnx: {
1273
- url: hf("audioClassification/yamnet/onnx/camstack-yamnet.onnx"),
1274
- sizeMB: 3.2
1275
- },
1276
- openvino: ovFormat(hf("audioClassification/yamnet/openvino/camstack-yamnet.xml"), 3.2),
1277
- coreml: {
1278
- url: hf("audioClassification/yamnet/onnx/camstack-yamnet.onnx"),
1279
- sizeMB: 3.2,
1280
- runtimes: ["python"]
1281
- }
1282
- }
1283
- }, {
1284
- id: "apple-soundanalysis",
1285
- name: "Apple SoundAnalysis",
1286
- description: "macOS built-in — 303 sound categories, Neural Engine accelerated, zero download",
1287
- inputSize: {
1288
- width: 1,
1289
- height: 16e3
1290
- },
1291
- labels: [],
1292
- preprocessMode: "resize",
1293
- formats: { coreml: {
1294
- url: "",
1295
- sizeMB: 0
1296
- } }
1297
- }];
1298
- //#endregion
1299
- //#region src/detection-pipeline/registry/step-definitions.ts
1300
- var ANIMAL_10_LABELS = [
1301
- "cat",
1302
- "cow",
1303
- "dog",
1304
- "dolphin",
1305
- "eagle",
1306
- "giant panda",
1307
- "horse",
1308
- "monkey",
1309
- "sheep",
1310
- "spider"
1311
- ];
1312
- var VGG_G2_CHARSET = ["", ..."0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")];
1313
- var PipelineStepBase = class {
1314
- definition;
1315
- constructor(definition) {
1316
- this.definition = definition;
1317
- }
1318
- getConfigSchema() {
1319
- return [];
1320
- }
1321
- };
1322
- var ObjectDetectionStep = class {
1323
- definition = {
1324
- id: "object-detection",
1325
- name: "Object Detection",
1326
- slot: "detector",
1327
- postprocessor: "yolo",
1328
- extractMode: "full-frame",
1329
- inputClasses: null,
1330
- outputClasses: [
1331
- "person",
1332
- "vehicle",
1333
- "animal"
1334
- ],
1335
- models: [...OBJECT_DETECTION_MODELS],
1336
- defaultModelId: "yolo26n",
1337
- defaultConfidence: .5,
1338
- labels: COCO_80_LABELS.map((l) => l.id),
1339
- classMap: COCO_TO_MACRO
1340
- };
1341
- getConfigSchema() {
1342
- return [
1343
- {
1344
- type: "multiselect",
1345
- key: "enabledMacroClasses",
1346
- label: "Enabled Classes",
1347
- description: "Which macro classes to detect (empty = all three).",
1348
- options: [
1349
- {
1350
- value: "person",
1351
- label: "Person"
1352
- },
1353
- {
1354
- value: "vehicle",
1355
- label: "Vehicle"
1356
- },
1357
- {
1358
- value: "animal",
1359
- label: "Animal"
1360
- }
1361
- ],
1362
- default: [
1363
- "person",
1364
- "vehicle",
1365
- "animal"
1366
- ]
1367
- },
1368
- {
1369
- type: "slider",
1370
- key: "minConfidencePerson",
1371
- label: "Min Confidence — Person",
1372
- description: "Drop person predictions below this score.",
1373
- min: 0,
1374
- max: 1,
1375
- step: .05,
1376
- default: .5,
1377
- showValue: true
1378
- },
1379
- {
1380
- type: "slider",
1381
- key: "minConfidenceVehicle",
1382
- label: "Min Confidence — Vehicle",
1383
- description: "Drop vehicle predictions below this score.",
1384
- min: 0,
1385
- max: 1,
1386
- step: .05,
1387
- default: .5,
1388
- showValue: true
1389
- },
1390
- {
1391
- type: "slider",
1392
- key: "minConfidenceAnimal",
1393
- label: "Min Confidence — Animal",
1394
- description: "Drop animal predictions below this score.",
1395
- min: 0,
1396
- max: 1,
1397
- step: .05,
1398
- default: .5,
1399
- showValue: true
1400
- }
1401
- ];
1402
- }
1403
- };
1404
- var AudioClassifierStep = class {
1405
- definition = {
1406
- id: "audio-classifier",
1407
- name: "Audio Classifier",
1408
- slot: "audio-classifier",
1409
- postprocessor: "yamnet",
1410
- extractMode: "full-frame",
1411
- inputClasses: null,
1412
- outputClasses: ["audio"],
1413
- models: [...AUDIO_CLASSIFIER_MODELS],
1414
- defaultModelId: "yamnet-onnx",
1415
- defaultModelIdByFormat: { coreml: "apple-soundanalysis" },
1416
- enabledByDefault: true,
1417
- defaultConfidence: .3
1418
- };
1419
- getConfigSchema() {
1420
- return [{
1421
- type: "multiselect",
1422
- key: "enabledAudioClasses",
1423
- label: "Audio Classes",
1424
- description: "Which audio macro classes to surface (empty = all).",
1425
- options: AUDIO_MACRO_LABELS.map((l) => ({
1426
- value: l.id,
1427
- label: `${l.icon ?? ""} ${l.name}`.trim()
1428
- })),
1429
- default: [
1430
- "scream",
1431
- "crying",
1432
- "dog",
1433
- "alarm",
1434
- "doorbell",
1435
- "glass_breaking",
1436
- "gunshot",
1437
- "siren",
1438
- "footsteps"
1439
- ]
1440
- }, {
1441
- type: "slider",
1442
- key: "minConfidence",
1443
- label: "Min Confidence",
1444
- description: "Drop predictions whose score is below this threshold.",
1445
- default: .3,
1446
- min: 0,
1447
- max: 1,
1448
- step: .05,
1449
- showValue: true
1450
- }];
1451
- }
1452
- };
1453
- var AnimalClassifierStep = class {
1454
- definition = {
1455
- id: "animal-classifier",
1456
- name: "Animal Classifier",
1457
- slot: "classifier",
1458
- postprocessor: "softmax",
1459
- extractMode: "crop-roi",
1460
- inputClasses: ["animal"],
1461
- outputClasses: ["animal-type"],
1462
- models: [...ANIMAL_CLASSIFIER_MODELS],
1463
- defaultModelId: "animals-10",
1464
- enabledByDefault: false,
1465
- defaultConfidence: .2,
1466
- labels: [...ANIMAL_10_LABELS]
1467
- };
1468
- getConfigSchema() {
1469
- return [{
1470
- type: "multiselect",
1471
- key: "enabledClasses",
1472
- label: "Species",
1473
- description: "Which animal species to keep (empty = all). The classifier still scores every label but only the selected classes are returned on the detection.",
1474
- options: ANIMAL_10_LABELS.map((l) => ({
1475
- value: l,
1476
- label: l
1477
- })),
1478
- default: []
1479
- }, {
1480
- type: "slider",
1481
- key: "minConfidence",
1482
- label: "Min Confidence",
1483
- description: "Drop classifications whose score is below this threshold.",
1484
- default: this.definition.defaultConfidence,
1485
- min: 0,
1486
- max: 1,
1487
- step: .05,
1488
- showValue: true
1489
- }];
1490
- }
1491
- };
1492
- var ClassifierWithMinConfidence = class extends PipelineStepBase {
1493
- getConfigSchema() {
1494
- return [{
1495
- type: "slider",
1496
- key: "minConfidence",
1497
- label: "Min Confidence",
1498
- description: "Drop classifications whose score is below this threshold.",
1499
- default: this.definition.defaultConfidence,
1500
- min: 0,
1501
- max: 1,
1502
- step: .05,
1503
- showValue: true
1504
- }];
1505
- }
1506
- };
1507
- var STEP_FACE_DETECTION = new PipelineStepBase({
1508
- id: "face-detection",
1509
- name: "Face Detection",
1510
- slot: "cropper",
1511
- postprocessor: "scrfd",
1512
- extractMode: "crop-roi",
1513
- inputClasses: ["person"],
1514
- outputClasses: ["face"],
1515
- models: [...FACE_DETECTION_MODELS],
1516
- defaultModelId: "scrfd-2.5g",
1517
- defaultConfidence: .5,
1518
- defaultMinParentScore: .7
1519
- });
1520
- var STEP_FACE_EMBEDDING = new PipelineStepBase({
1521
- id: "face-embedding",
1522
- name: "Face Embedding",
1523
- slot: "classifier",
1524
- postprocessor: "arcface",
1525
- extractMode: "crop-roi",
1526
- inputClasses: ["face"],
1527
- outputClasses: ["identity"],
1528
- models: [...FACE_EMBEDDING_MODELS],
1529
- defaultModelId: "arcface-r100",
1530
- defaultConfidence: 0
1531
- });
1532
- var STEP_CLIP_EMBEDDING = new PipelineStepBase({
1533
- id: "clip-embedding",
1534
- name: "CLIP Embedding",
1535
- slot: "classifier",
1536
- postprocessor: "clip",
1537
- extractMode: "crop-roi",
1538
- inputClasses: [
1539
- "person",
1540
- "vehicle",
1541
- "animal"
1542
- ],
1543
- outputClasses: ["embedding"],
1544
- models: [...CLIP_EMBEDDING_MODELS],
1545
- defaultModelId: "mobileclip-s1",
1546
- enabledByDefault: true,
1547
- backfillIntoExistingOverrides: true,
1548
- defaultConfidence: 0,
1549
- defaultMinParentScore: .4,
1550
- group: "CLIP"
1551
- });
1552
- var STEP_PLATE_DETECTION = new PipelineStepBase({
1553
- id: "plate-detection",
1554
- name: "Plate Detection",
1555
- slot: "cropper",
1556
- postprocessor: "yolo",
1557
- extractMode: "crop-roi",
1558
- inputClasses: ["vehicle"],
1559
- outputClasses: ["plate"],
1560
- models: [...PLATE_DETECTION_MODELS],
1561
- defaultModelId: "yolov8n-plate",
1562
- defaultConfidence: .5,
1563
- defaultMinParentScore: .7,
1564
- labels: ["plate"]
1565
- });
1566
- var STEP_PLATE_OCR = new PipelineStepBase({
1567
- id: "plate-ocr",
1568
- name: "Plate OCR",
1569
- slot: "classifier",
1570
- postprocessor: "ctc",
1571
- extractMode: "crop-roi",
1572
- inputClasses: ["plate"],
1573
- outputClasses: ["plate-text"],
1574
- models: [...PLATE_OCR_MODELS],
1575
- defaultModelId: "vgg-english-g2",
1576
- defaultConfidence: 0,
1577
- charset: [...VGG_G2_CHARSET]
1578
- });
1579
- var STEP_BIRD_CLASSIFIER = new ClassifierWithMinConfidence({
1580
- id: "bird-classifier",
1581
- name: "Bird Classifier",
1582
- slot: "classifier",
1583
- postprocessor: "softmax",
1584
- extractMode: "crop-roi",
1585
- inputClasses: ["animal"],
1586
- outputClasses: ["species"],
1587
- models: [...BIRD_CLASSIFIER_MODELS],
1588
- defaultModelId: "bird-nabirds-404",
1589
- enabledByDefault: false,
1590
- defaultConfidence: .2
1591
- });
1592
- var STEP_VEHICLE_CLASSIFIER = new ClassifierWithMinConfidence({
1593
- id: "vehicle-classifier",
1594
- name: "Vehicle Classifier",
1595
- slot: "classifier",
1596
- postprocessor: "softmax",
1597
- extractMode: "crop-roi",
1598
- inputClasses: ["vehicle"],
1599
- outputClasses: ["vehicle-type"],
1600
- models: [...VEHICLE_CLASSIFIER_MODELS],
1601
- defaultModelId: "vehicle-type-efficientnet",
1602
- enabledByDefault: false,
1603
- defaultConfidence: .3
1604
- });
1605
- var STEP_SEGMENTATION = new PipelineStepBase({
1606
- id: "segmentation",
1607
- name: "Segmentation",
1608
- slot: "refiner",
1609
- postprocessor: "saliency",
1610
- extractMode: "crop-roi",
1611
- inputClasses: null,
1612
- outputClasses: ["mask"],
1613
- models: [...SEGMENTATION_REFINER_MODELS],
1614
- defaultModelId: "u2netp",
1615
- enabledByDefault: false,
1616
- defaultConfidence: 0,
1617
- group: "Segmentation"
1618
- });
1619
- new PipelineStepBase({
1620
- id: "instance-segmentation",
1621
- name: "Instance Segmentation",
1622
- slot: "refiner",
1623
- postprocessor: "yolo-seg",
1624
- extractMode: "full-frame",
1625
- inputClasses: null,
1626
- outputClasses: ["mask"],
1627
- models: [...INSTANCE_SEGMENTATION_MODELS],
1628
- defaultModelId: "yolo26n-seg",
1629
- enabledByDefault: false,
1630
- defaultConfidence: .25,
1631
- labels: COCO_80_LABELS.map((l) => l.id),
1632
- classMap: COCO_TO_MACRO,
1633
- group: "Segmentation"
1634
- });
1635
- var STEP_OBJECT_DETECTION_INSTANCE = new ObjectDetectionStep();
1636
- var STEP_AUDIO_CLASSIFIER_INSTANCE = new AudioClassifierStep();
1637
- var ALL_PIPELINE_STEPS = [
1638
- STEP_OBJECT_DETECTION_INSTANCE,
1639
- STEP_FACE_DETECTION,
1640
- STEP_FACE_EMBEDDING,
1641
- STEP_CLIP_EMBEDDING,
1642
- STEP_PLATE_DETECTION,
1643
- STEP_PLATE_OCR,
1644
- new AnimalClassifierStep(),
1645
- STEP_BIRD_CLASSIFIER,
1646
- STEP_VEHICLE_CLASSIFIER,
1647
- STEP_SEGMENTATION,
1648
- STEP_AUDIO_CLASSIFIER_INSTANCE
1649
- ];
1650
- /** Compat: flat array of StepDefinition for existing consumers */
1651
- var ALL_STEPS = ALL_PIPELINE_STEPS.map((s) => s.definition);
1652
- var STEP_MAP = new Map(ALL_PIPELINE_STEPS.map((s) => [s.definition.id, s]));
1653
- /**
1654
- * Look up a pipeline step by ID (class instance with getConfigSchema).
1655
- * @throws if the step ID is not registered.
1656
- */
1657
- function getStep(stepId) {
1658
- const step = STEP_MAP.get(stepId);
1659
- if (!step) throw new Error(`Unknown pipeline step: "${stepId}". Available: ${ALL_PIPELINE_STEPS.map((s) => s.definition.id).join(", ")}`);
1660
- return step;
1661
- }
1662
- /**
1663
- * Look up a step definition by ID (compat shortcut).
1664
- * @throws if the step ID is not registered.
1665
- */
1666
- function getStepDefinition(stepId) {
1667
- return getStep(stepId).definition;
1668
- }
1669
- /**
1670
- * Get the default model ID for a step given the current model format.
1671
- *
1672
- * Resolution order:
1673
- * 1. `def.defaultModelIdByFormat?.[format]` — the step's declared
1674
- * per-format preference — if that model exists in `def.models` AND
1675
- * has a build for `format`.
1676
- * 2. `def.defaultModelId` — the step's plain declared default — if it
1677
- * exists in `def.models` AND has a build for `format`.
1678
- * 3. The smallest-by-size model among those with a `format` build
1679
- * (legacy fallback, preserved for steps/formats with no declared
1680
- * preference reachable).
1681
- * 4. `def.defaultModelId` unchanged, when ZERO models have a `format`
1682
- * build — an unloadable case flagged elsewhere, not resolved here.
1683
- */
1684
- function getDefaultModelForFormat(stepId, format) {
1685
- const def = getStepDefinition(stepId);
1686
- const hasFormatBuild = (modelId) => def.models.find((m) => m.id === modelId)?.formats[format] !== void 0;
1687
- const declaredForFormat = def.defaultModelIdByFormat?.[format];
1688
- if (declaredForFormat !== void 0 && hasFormatBuild(declaredForFormat)) return declaredForFormat;
1689
- if (hasFormatBuild(def.defaultModelId)) return def.defaultModelId;
1690
- const available = def.models.filter((m) => m.formats[format] && m.legacy !== true);
1691
- if (available.length === 0) return def.defaultModelId;
1692
- return [...available].toSorted((a, b) => {
1693
- return (a.formats[format]?.sizeMB ?? Infinity) - (b.formats[format]?.sizeMB ?? Infinity);
1694
- })[0].id;
1695
- }
1696
- /**
1697
- * The single node-local authority for turning a CHOSEN model (or none) into a
1698
- * concrete model that has a build for this node's engine `format`. Fuses the
1699
- * two previously-duplicated decisions (orchestrator `pickDefaultModelId` +
1700
- * provider `substituteIncompatibleModels`):
1701
- *
1702
- * - chosen === null → the addon's default model for `format`.
1703
- * - chosen is a catalog model → keep it if it has a `format` build; else
1704
- * the addon's default model for `format`
1705
- * (a substitution — the caller surfaces it).
1706
- * - chosen is NOT in the catalog (custom/unknown) → returned unchanged; the
1707
- * operator owns a custom model's format compat.
1708
- * - unknown stepId → chosen returned unchanged (never throws).
1709
- */
1710
- function resolveModelForFormat(stepId, chosenModelId, format) {
1711
- let def;
1712
- try {
1713
- def = getStepDefinition(stepId);
1714
- } catch {
1715
- return chosenModelId ?? "";
1716
- }
1717
- if (chosenModelId === null) return getDefaultModelForFormat(stepId, format);
1718
- const entry = def.models.find((m) => m.id === chosenModelId);
1719
- if (!entry) return chosenModelId;
1720
- if (entry.formats[format]) return chosenModelId;
1721
- return getDefaultModelForFormat(stepId, format);
1722
- }
1723
- //#endregion
1724
294
  //#region src/detection-pipeline/default-detection-model.ts
1725
295
  /** The object-detection step id — the only slot this resolver applies to. */
1726
296
  var OBJECT_DETECTION_STEP_ID = "object-detection";
@@ -3715,6 +2285,110 @@ function bboxShortSide(bbox) {
3715
2285
  return Math.min(bbox[2] - bbox[0], bbox[3] - bbox[1]);
3716
2286
  }
3717
2287
  /**
2288
+ * Padded region (clamped to the frame) around a face bbox — the area a native
2289
+ * crop must cover so the warp has room around a tilted / edge face. Mirrors the
2290
+ * padding {@link alignFaceCrop} applies internally (`padRatio` default 0.4), so
2291
+ * the native and fallback paths sample the same neighbourhood.
2292
+ */
2293
+ function paddedFaceRegion(faceBbox, imageWidth, imageHeight, padRatio = .4) {
2294
+ const bw = faceBbox[2] - faceBbox[0];
2295
+ const bh = faceBbox[3] - faceBbox[1];
2296
+ const padX = bw * padRatio;
2297
+ const padY = bh * padRatio;
2298
+ return {
2299
+ left: Math.max(0, Math.floor(faceBbox[0] - padX)),
2300
+ top: Math.max(0, Math.floor(faceBbox[1] - padY)),
2301
+ right: Math.min(imageWidth, Math.ceil(faceBbox[2] + padX)),
2302
+ bottom: Math.min(imageHeight, Math.ceil(faceBbox[3] + padY))
2303
+ };
2304
+ }
2305
+ /**
2306
+ * Warp a raw RGB crop of `region` (native-resolution pixels of exactly that
2307
+ * detection-space region) so `landmarksImageSpace` land on the ArcFace
2308
+ * template. Landmarks are mapped from image space into the crop's local pixel
2309
+ * space by the per-axis region→crop scale (handles `maxWidth` downscale of the
2310
+ * native crop). Channels default to RGB (3) — the format the decode worker's
2311
+ * native surface produces.
2312
+ */
2313
+ async function alignFaceCropFromRgb(rgb, region, landmarksImageSpace, opts = {}) {
2314
+ const outSize = opts.outSize ?? 112;
2315
+ const quality = opts.quality ?? 95;
2316
+ const channels = opts.channels ?? 3;
2317
+ const regionW = region.right - region.left;
2318
+ const regionH = region.bottom - region.top;
2319
+ if (regionW < 1 || regionH < 1) throw new Error(`alignFaceCropFromRgb: degenerate region ${regionW}x${regionH}`);
2320
+ if (rgb.width < 2 || rgb.height < 2) throw new Error(`alignFaceCropFromRgb: degenerate crop ${rgb.width}x${rgb.height}`);
2321
+ const scaleX = rgb.width / regionW;
2322
+ const scaleY = rgb.height / regionH;
2323
+ const localLandmarks = landmarksImageSpace.map((l) => ({
2324
+ x: (l.x - region.left) * scaleX,
2325
+ y: (l.y - region.top) * scaleY
2326
+ }));
2327
+ const aligned = warpToTemplate({
2328
+ data: rgb.bytes,
2329
+ width: rgb.width,
2330
+ height: rgb.height,
2331
+ channels
2332
+ }, localLandmarks, opts.template ?? ARCFACE_TEMPLATE_112, outSize);
2333
+ return {
2334
+ jpeg: await sharp(Buffer.from(aligned.data), { raw: {
2335
+ width: aligned.width,
2336
+ height: aligned.height,
2337
+ channels: aligned.channels
2338
+ } }).jpeg({ quality }).toBuffer(),
2339
+ width: aligned.width,
2340
+ height: aligned.height
2341
+ };
2342
+ }
2343
+ /**
2344
+ * Produce a landmark-aligned face crop, preferring a NATIVE-resolution crop
2345
+ * (design B — the quality win) and FALLING BACK to today's downscaled
2346
+ * full-frame alignment on any native miss/error (never worse than before).
2347
+ *
2348
+ * The native path requests the padded face region from `nativeCropProvider`
2349
+ * (a best-effort local cap round-trip bound to this frame), warps the native
2350
+ * pixels; the fallback path re-uses {@link alignFaceCrop} on the downscaled
2351
+ * JPEG. ArcFace input is a fixed `outSize`×`outSize` either way — only the crop
2352
+ * SOURCE changes, so model cost is unchanged.
2353
+ */
2354
+ async function buildAlignedFaceCrop(args) {
2355
+ if (args.nativeCropProvider) try {
2356
+ const region = paddedFaceRegion(args.faceBbox, args.imageWidth, args.imageHeight);
2357
+ const roi = {
2358
+ x: region.left / args.imageWidth,
2359
+ y: region.top / args.imageHeight,
2360
+ w: (region.right - region.left) / args.imageWidth,
2361
+ h: (region.bottom - region.top) / args.imageHeight
2362
+ };
2363
+ const native = await args.nativeCropProvider(roi, args.nativeMaxWidth);
2364
+ if (native && native.width >= 2 && native.height >= 2 && native.bytes.length >= native.width * native.height * 3) return {
2365
+ ...await alignFaceCropFromRgb(native, region, args.landmarksImageSpace, { outSize: args.outSize }),
2366
+ source: "native",
2367
+ nativeFaceShortSidePx: nativeFaceShortSide(args.faceBbox, region, native)
2368
+ };
2369
+ } catch {}
2370
+ return {
2371
+ ...await alignFaceCrop(args.fullFrameJpeg, args.faceBbox, args.landmarksImageSpace, args.imageWidth, args.imageHeight, { outSize: args.outSize }),
2372
+ source: "fallback"
2373
+ };
2374
+ }
2375
+ /**
2376
+ * Short side (px) of `faceBbox` (in detection space) re-measured in the NATIVE
2377
+ * crop's resolution. The native crop covers `region` (detection-space padded
2378
+ * region) rendered at `native.width × native.height` native pixels, so the
2379
+ * per-axis scale `native / region` maps the detection face size to its native
2380
+ * size. This is the size an embedder actually sees, so the gate uses it.
2381
+ */
2382
+ function nativeFaceShortSide(faceBbox, region, native) {
2383
+ const regionW = Math.max(1, region.right - region.left);
2384
+ const regionH = Math.max(1, region.bottom - region.top);
2385
+ const scaleX = native.width / regionW;
2386
+ const scaleY = native.height / regionH;
2387
+ const faceW = (faceBbox[2] - faceBbox[0]) * scaleX;
2388
+ const faceH = (faceBbox[3] - faceBbox[1]) * scaleY;
2389
+ return Math.min(faceW, faceH);
2390
+ }
2391
+ /**
3718
2392
  * Produce an aligned 112x112 JPEG of a face from the full-frame JPEG and the
3719
2393
  * face's 5 landmarks (in absolute image-space coordinates). Decodes a padded
3720
2394
  * region around the face bbox to raw RGB, warps to the ArcFace template, and
@@ -3942,7 +2616,8 @@ function buildFrameResult(input) {
3942
2616
  ...embedding !== void 0 ? {
3943
2617
  embedding,
3944
2618
  ...embeddingModelId !== void 0 ? { embeddingModelId } : {}
3945
- } : {}
2619
+ } : {},
2620
+ ...m.faceAlignedCrop !== void 0 ? { faceAlignedCrop: m.faceAlignedCrop } : {}
3946
2621
  };
3947
2622
  if (input.debug) {
3948
2623
  const cleanDebug = pruneUndefined({
@@ -4023,7 +2698,8 @@ function toMutableRootDetection(det, rootStep, idGen, rootStepLatencyMs) {
4023
2698
  mask: det.mask,
4024
2699
  maskWidth: det.maskWidth,
4025
2700
  maskHeight: det.maskHeight
4026
- } : {}
2701
+ } : {},
2702
+ ...det.landmarks !== void 0 ? { landmarks: det.landmarks } : {}
4027
2703
  };
4028
2704
  }
4029
2705
  //#endregion
@@ -4141,6 +2817,33 @@ function formatTopResult(output) {
4141
2817
  * resolution — a deeper change tracked separately.
4142
2818
  */
4143
2819
  var DEFAULT_MIN_FACE_SIZE_PX = 28;
2820
+ /**
2821
+ * Cheap pre-native floor (px, detection space): below this a face is treated as
2822
+ * genuinely degenerate — SCRFD landmarks are unreliable and even a large native
2823
+ * upscale can't rescue it — so it is skipped WITHOUT paying a native-crop
2824
+ * round-trip. Above the floor, the real {@link DEFAULT_MIN_FACE_SIZE_PX} gate is
2825
+ * evaluated on the NATIVE face size (see the face branch of `executeChildren`).
2826
+ */
2827
+ var MIN_FACE_DETECTION_FLOOR_PX = 6;
2828
+ /**
2829
+ * Cap (px) on the width of the NATIVE face crop fetched for embedding (design
2830
+ * B). The arcface input is a fixed 112² warp, so a wider native crop buys no
2831
+ * embedding quality past a modest margin but does inflate the per-face transport
2832
+ * of the best-effort native-crop round-trip. 320px keeps a distant face well
2833
+ * above the downscaled detection resolution while bounding the crop payload.
2834
+ */
2835
+ var NATIVE_FACE_CROP_MAX_WIDTH = 320;
2836
+ /**
2837
+ * StepOutput kinds that synthesize a full-frame detection when they run at
2838
+ * the tree ROOT (see `synthesizeRootDetection`) — classifier/embedding steps
2839
+ * promoted to root by `pipelineRunner.runDetailSubtree` (task 6b), e.g.
2840
+ * `clip-embedding`, `animal-classifier`. Detector (`'detections'`) roots take
2841
+ * the normal path; `'text'`/`'mask'` roots are out of scope and still
2842
+ * `continue` with no output, matching pre-existing behaviour.
2843
+ */
2844
+ function isEnrichmentOutput(output) {
2845
+ return output.kind === "classifications" || output.kind === "embedding";
2846
+ }
4144
2847
  var PipelineExecutor = class {
4145
2848
  opts;
4146
2849
  constructor(opts) {
@@ -4158,6 +2861,23 @@ var PipelineExecutor = class {
4158
2861
  logger.warn(message, { meta });
4159
2862
  }
4160
2863
  /**
2864
+ * Surface WHY a face embedding was skipped — a detected face simply too small
2865
+ * (distance / night) is the common silent cause of "faces stopped collecting".
2866
+ * Reports the detection-space short side AND, when the skip was decided on the
2867
+ * native crop, the NATIVE face size so an operator sees the size that actually
2868
+ * gated. Throttled so it isn't per-frame spam.
2869
+ */
2870
+ warnFaceTooSmall(child, detShortSidePx, minFaceSizePx, nativeFaceShortSidePx) {
2871
+ this.throttledWarn(`face-small:${child.stepId}`, 3e4, "face detected but too small to embed — skipping", {
2872
+ step: child.stepId,
2873
+ model: child.modelId,
2874
+ faceShortSidePx: Math.round(detShortSidePx),
2875
+ minFaceSizePx,
2876
+ gatedOn: nativeFaceShortSidePx === void 0 ? "detection" : "native",
2877
+ ...nativeFaceShortSidePx === void 0 ? {} : { nativeFaceShortSidePx: Math.round(nativeFaceShortSidePx) }
2878
+ });
2879
+ }
2880
+ /**
4161
2881
  * Execute the full pipeline tree against a frame.
4162
2882
  *
4163
2883
  * `rootInput` carries the frame in its source representation
@@ -4170,7 +2890,7 @@ var PipelineExecutor = class {
4170
2890
  *
4171
2891
  * @returns FrameResult + optional trace
4172
2892
  */
4173
- async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts) {
2893
+ async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeFaceCropProvider) {
4174
2894
  const startMs = Date.now();
4175
2895
  const verbosity = runOpts?.traceVerbosity ?? "off";
4176
2896
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
@@ -4193,7 +2913,27 @@ var PipelineExecutor = class {
4193
2913
  const rootStart = Date.now();
4194
2914
  const rootOutput = await this.executeStep(rootStep, rootInput, imageWidth, imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg);
4195
2915
  const rootMs = Date.now() - rootStart;
4196
- if (rootOutput.kind !== "detections") continue;
2916
+ if (rootOutput.kind !== "detections") {
2917
+ if (isEnrichmentOutput(rootOutput)) {
2918
+ const mutable = this.synthesizeRootDetection(rootOutput, rootStep, idGen, rootMs, imageWidth, imageHeight);
2919
+ applyChildOutput(mutable, rootStep, rootOutput, rootMs, ctx);
2920
+ try {
2921
+ await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, nativeFaceCropProvider);
2922
+ } catch (err) {
2923
+ this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
2924
+ tags: { deviceId },
2925
+ meta: {
2926
+ rootStepId: rootStep.stepId,
2927
+ parentClass: mutable.macroClass,
2928
+ parentScore: mutable.score,
2929
+ error: errMsg(err)
2930
+ }
2931
+ });
2932
+ }
2933
+ firstLevel.push(mutable);
2934
+ }
2935
+ continue;
2936
+ }
4197
2937
  for (const det of rootOutput.detections) {
4198
2938
  const mutable = toMutableRootDetection(det, rootStep, idGen, rootMs);
4199
2939
  if (rootStep.definition.classMap) {
@@ -4208,7 +2948,7 @@ var PipelineExecutor = class {
4208
2948
  continue;
4209
2949
  }
4210
2950
  try {
4211
- await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg);
2951
+ await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, nativeFaceCropProvider);
4212
2952
  } catch (err) {
4213
2953
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
4214
2954
  tags: { deviceId },
@@ -4244,6 +2984,29 @@ var PipelineExecutor = class {
4244
2984
  trace: traceBuilder.build(firstLevel.length + details.length)
4245
2985
  };
4246
2986
  }
2987
+ /**
2988
+ * Build a full-frame `MutableObjectDetection` for a ROOT step whose engine
2989
+ * output is a classification/embedding rather than detections — there is
2990
+ * no detector to bound a smaller box, so the synthesized detection covers
2991
+ * the entire input frame (`bbox = [0, 0, imageWidth, imageHeight]`).
2992
+ * `macroClass` is the step's own output semantic (`outputClasses[0]`,
2993
+ * falling back to `inputClasses[0]` when `outputClasses` is empty, then
2994
+ * the step id as a last resort so the field is never empty). `score` is
2995
+ * the output's own confidence when it has one (the classifier's top-1
2996
+ * score) — an embedding carries no confidence, so it defaults to 1.
2997
+ */
2998
+ synthesizeRootDetection(output, rootStep, idGen, rootStepLatencyMs, imageWidth, imageHeight) {
2999
+ return toMutableRootDetection({
3000
+ class: rootStep.definition.outputClasses[0] ?? rootStep.definition.inputClasses?.[0] ?? rootStep.stepId,
3001
+ score: output.kind === "classifications" ? output.classifications[0]?.score ?? 1 : 1,
3002
+ bbox: [
3003
+ 0,
3004
+ 0,
3005
+ imageWidth,
3006
+ imageHeight
3007
+ ]
3008
+ }, rootStep, idGen, rootStepLatencyMs);
3009
+ }
4247
3010
  async executeStep(step, input, inputWidth, inputHeight, inputType, _parentBbox, parentClass, traceBuilder, stepTimings, poolAgg) {
4248
3011
  const preprocessStart = Date.now();
4249
3012
  const preprocessMs = Date.now() - preprocessStart;
@@ -4310,8 +3073,9 @@ var PipelineExecutor = class {
4310
3073
  });
4311
3074
  return output;
4312
3075
  }
4313
- async executeChildren(children, parentDetection, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg) {
3076
+ async executeChildren(children, parentDetection, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, plane, nativeFaceCropProvider) {
4314
3077
  for (const child of children) {
3078
+ if (plane === "frame" && child.definition.inputClasses !== null) continue;
4315
3079
  if (!this.matchesInputClasses(parentDetection.macroClass, child.inputClasses)) continue;
4316
3080
  const minParentScore = child.settings?.minParentScore ?? child.definition?.defaultMinParentScore;
4317
3081
  if (typeof minParentScore === "number" && parentDetection.score < minParentScore) continue;
@@ -4325,23 +3089,43 @@ var PipelineExecutor = class {
4325
3089
  let cropH;
4326
3090
  if (modelEntry?.faceAlignment) {
4327
3091
  const minFaceSize = typeof child.settings?.["minFaceSize"] === "number" ? child.settings["minFaceSize"] : DEFAULT_MIN_FACE_SIZE_PX;
4328
- const faceShortSide = bboxShortSide(parentDetection.bbox);
4329
- if (faceShortSide < minFaceSize) {
4330
- this.throttledWarn(`face-small:${child.stepId}`, 3e4, "face detected but too small to embed — skipping", {
4331
- step: child.stepId,
4332
- model: child.modelId,
4333
- faceShortSidePx: Math.round(faceShortSide),
4334
- minFaceSizePx: minFaceSize
4335
- });
3092
+ const detShortSide = bboxShortSide(parentDetection.bbox);
3093
+ if (detShortSide < MIN_FACE_DETECTION_FLOOR_PX) {
3094
+ this.warnFaceTooSmall(child, detShortSide, minFaceSize, void 0);
4336
3095
  continue;
4337
3096
  }
4338
3097
  const lms = parentDetection.landmarks;
4339
3098
  if (lms && lms.length >= 5) {
4340
- const aligned = await alignFaceCrop(fullFrameJpeg, parentDetection.bbox, lms, imageWidth, imageHeight, { outSize: modelEntry.inputSize.width });
3099
+ if (!nativeFaceCropProvider && detShortSide < minFaceSize) {
3100
+ this.warnFaceTooSmall(child, detShortSide, minFaceSize, void 0);
3101
+ continue;
3102
+ }
3103
+ const aligned = await buildAlignedFaceCrop({
3104
+ fullFrameJpeg,
3105
+ faceBbox: parentDetection.bbox,
3106
+ landmarksImageSpace: lms,
3107
+ imageWidth,
3108
+ imageHeight,
3109
+ outSize: modelEntry.inputSize.width,
3110
+ ...nativeFaceCropProvider ? {
3111
+ nativeCropProvider: nativeFaceCropProvider,
3112
+ nativeMaxWidth: NATIVE_FACE_CROP_MAX_WIDTH
3113
+ } : {}
3114
+ });
3115
+ const gateShortSide = aligned.nativeFaceShortSidePx ?? detShortSide;
3116
+ if (gateShortSide < minFaceSize) {
3117
+ this.warnFaceTooSmall(child, detShortSide, minFaceSize, aligned.source === "native" ? gateShortSide : void 0);
3118
+ continue;
3119
+ }
4341
3120
  cropJpegBuf = aligned.jpeg;
4342
3121
  cropW = aligned.width;
4343
3122
  cropH = aligned.height;
3123
+ parentDetection.faceAlignedCrop = cropJpegBuf.toString("base64");
4344
3124
  } else {
3125
+ if (detShortSide < minFaceSize) {
3126
+ this.warnFaceTooSmall(child, detShortSide, minFaceSize, void 0);
3127
+ continue;
3128
+ }
4345
3129
  const crop = await cropJpeg(fullFrameJpeg, parentDetection.bbox, imageWidth, imageHeight);
4346
3130
  cropJpegBuf = crop.jpeg;
4347
3131
  cropW = crop.width;
@@ -4371,7 +3155,7 @@ var PipelineExecutor = class {
4371
3155
  y: l.y + py1
4372
3156
  }));
4373
3157
  }
4374
- await this.executeChildren(child.children, detail, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg);
3158
+ await this.executeChildren(child.children, detail, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, plane, nativeFaceCropProvider);
4375
3159
  }
4376
3160
  }
4377
3161
  } catch (err) {
@@ -5863,6 +4647,38 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5863
4647
  debug: { totalInferenceMs: 0 }
5864
4648
  };
5865
4649
  }
4650
+ /**
4651
+ * Build the design-B native-face-crop provider for a runtime frame. Present
4652
+ * ONLY when the frame arrived as a retention `frameHandle` (the key the owning
4653
+ * pipeline-runner registered the frame's native surface under) and the sibling
4654
+ * `pipeline-runner` cap is reachable via `ctx.api`. Each call is a best-effort
4655
+ * round-trip pinned to the frame's OWNING node (`nodePin(handle.nodeId)` — the
4656
+ * node that decoded it, i.e. THIS node for a local frame, never a cross-node
4657
+ * hop) that returns the native ROI or `null` on a miss; the executor then
4658
+ * falls back to the downscaled crop, so behaviour is never worse than before.
4659
+ */
4660
+ buildNativeFaceCropProvider(handle) {
4661
+ if (!handle) return void 0;
4662
+ const api = this.addonCtx?.api;
4663
+ if (!api) return void 0;
4664
+ return async (roi, maxWidth) => {
4665
+ try {
4666
+ const native = await api.pipelineRunner.getNativeCrop.query({
4667
+ handle,
4668
+ bbox: roi,
4669
+ ...maxWidth !== void 0 ? { maxWidth } : {}
4670
+ }, nodePin(handle.nodeId));
4671
+ if (!native || native.width <= 0 || native.height <= 0) return null;
4672
+ return {
4673
+ bytes: native.bytes,
4674
+ width: native.width,
4675
+ height: native.height
4676
+ };
4677
+ } catch {
4678
+ return null;
4679
+ }
4680
+ };
4681
+ }
5866
4682
  async runPipeline(input, onProgress) {
5867
4683
  const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
5868
4684
  const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
@@ -6119,7 +4935,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6119
4935
  const effectiveDeviceId = input.deviceId ?? 0;
6120
4936
  const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
6121
4937
  const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
6122
- const { result, trace } = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, { traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full" });
4938
+ const nativeFaceCropProvider = this.buildNativeFaceCropProvider(input.frameHandle);
4939
+ const { result, trace } = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
4940
+ traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
4941
+ plane: input.plane
4942
+ }, nativeFaceCropProvider);
6123
4943
  if (isRuntime) {
6124
4944
  if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
6125
4945
  type: "device",