@camstack/addon-pipeline 1.2.101 → 1.2.102

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 (32) hide show
  1. package/dist/{addon-utils-eUS6n_Zj.js → addon-utils-CLc6yHCN.js} +1 -1
  2. package/dist/audio-analyzer/index.js +3 -3
  3. package/dist/audio-analyzer/index.mjs +2 -2
  4. package/dist/detection-pipeline/index.js +163 -42
  5. package/dist/detection-pipeline/index.mjs +161 -40
  6. package/dist/{dist-dm3t4BOt.js → dist-C11WuNUP.js} +87 -51
  7. package/dist/{dist-gXdWP96z.mjs → dist-Ccmt3fGJ.mjs} +82 -52
  8. package/dist/{event-loop-stall-monitor-Lki2_mYY.js → event-loop-stall-monitor-Cq_NeC4o.js} +175 -132
  9. package/dist/{event-loop-stall-monitor-TnQ21mu0.mjs → event-loop-stall-monitor-OJrOMeuu.mjs} +170 -133
  10. package/dist/{lazy-sharp-oYppDIZR.js → lazy-sharp-RxUs6on_.js} +1 -1
  11. package/dist/motion-wasm/index.js +2 -2
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +5 -4
  14. package/dist/pipeline-runner/index.mjs +4 -3
  15. package/dist/{process-memory-CKWh609B.mjs → process-memory-D0zDmXLI.mjs} +1 -1
  16. package/dist/{process-memory-vkFVOdkV.js → process-memory-DOjQ3MgC.js} +1 -1
  17. package/dist/recorder/index.js +2 -2
  18. package/dist/recorder/index.mjs +1 -1
  19. package/dist/session-decode/decode-worker-child.js +2 -2
  20. package/dist/session-decode/decode-worker-child.mjs +1 -1
  21. package/dist/stream-broker/_stub.js +1 -1
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C4fRkYcj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BdgcF1lL.mjs} +2 -2
  23. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-zT-cYUdF.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DY31bUCj.mjs} +1 -1
  24. package/dist/stream-broker/{hostInit-CS6xpCdb.mjs → hostInit-Da9wVA2r.mjs} +2 -2
  25. package/dist/stream-broker/index.js +1 -1
  26. package/dist/stream-broker/index.mjs +1 -1
  27. package/dist/stream-broker/remoteEntry.js +1 -1
  28. package/dist/{worker-protocol-BrlrMxXu.js → worker-protocol-BePduZVV.js} +1 -1
  29. package/dist/{worker-protocol-DJWdPLKF.mjs → worker-protocol-DDpliBIW.mjs} +1 -1
  30. package/package.json +1 -1
  31. package/python/postprocessors/ctc.py +56 -3
  32. package/python/postprocessors/test_ctc.py +37 -0
@@ -15319,128 +15319,103 @@ var COCO_TO_MACRO = {
15319
15319
  var AUDIO_MACRO_LABELS = [
15320
15320
  {
15321
15321
  id: "speech",
15322
- name: "Speech",
15323
- icon: "🗣️"
15322
+ name: "Speech"
15324
15323
  },
15325
15324
  {
15326
15325
  id: "scream",
15327
- name: "Scream / Shout",
15328
- icon: "😱"
15326
+ name: "Scream / Shout"
15329
15327
  },
15330
15328
  {
15331
15329
  id: "crying",
15332
- name: "Crying / Baby",
15333
- icon: "😢"
15330
+ name: "Crying / Baby"
15334
15331
  },
15335
15332
  {
15336
15333
  id: "laughter",
15337
- name: "Laughter",
15338
- icon: "😂"
15334
+ name: "Laughter"
15339
15335
  },
15340
15336
  {
15341
15337
  id: "music",
15342
- name: "Music",
15343
- icon: "🎵"
15338
+ name: "Music"
15344
15339
  },
15345
15340
  {
15346
15341
  id: "dog",
15347
- name: "Dog",
15348
- icon: "🐕"
15342
+ name: "Dog"
15349
15343
  },
15350
15344
  {
15351
15345
  id: "cat",
15352
- name: "Cat",
15353
- icon: "🐈"
15346
+ name: "Cat"
15354
15347
  },
15355
15348
  {
15356
15349
  id: "bird",
15357
- name: "Bird",
15358
- icon: "🐦"
15350
+ name: "Bird"
15359
15351
  },
15360
15352
  {
15361
15353
  id: "animal",
15362
- name: "Animal (other)",
15363
- icon: "🐾"
15354
+ name: "Animal (other)"
15364
15355
  },
15365
15356
  {
15366
15357
  id: "alarm",
15367
- name: "Alarm / Siren",
15368
- icon: "🚨"
15358
+ name: "Alarm / Siren"
15369
15359
  },
15370
15360
  {
15371
15361
  id: "doorbell",
15372
- name: "Doorbell / Knock",
15373
- icon: "🔔"
15362
+ name: "Doorbell / Knock"
15374
15363
  },
15375
15364
  {
15376
15365
  id: "glass_breaking",
15377
- name: "Glass Breaking",
15378
- icon: "💥"
15366
+ name: "Glass Breaking"
15379
15367
  },
15380
15368
  {
15381
15369
  id: "gunshot",
15382
- name: "Gunshot / Explosion",
15383
- icon: "💣"
15370
+ name: "Gunshot / Explosion"
15384
15371
  },
15385
15372
  {
15386
15373
  id: "vehicle",
15387
- name: "Vehicle",
15388
- icon: "🚗"
15374
+ name: "Vehicle"
15389
15375
  },
15390
15376
  {
15391
15377
  id: "siren",
15392
- name: "Emergency Siren",
15393
- icon: "🚑"
15378
+ name: "Emergency Siren"
15394
15379
  },
15395
15380
  {
15396
15381
  id: "fire",
15397
- name: "Fire / Smoke",
15398
- icon: "🔥"
15382
+ name: "Fire / Smoke"
15399
15383
  },
15400
15384
  {
15401
15385
  id: "water",
15402
- name: "Water",
15403
- icon: "💧"
15386
+ name: "Water"
15404
15387
  },
15405
15388
  {
15406
15389
  id: "wind",
15407
- name: "Wind / Weather",
15408
- icon: "🌬️"
15390
+ name: "Wind / Weather"
15409
15391
  },
15410
15392
  {
15411
15393
  id: "door",
15412
- name: "Door",
15413
- icon: "🚪"
15394
+ name: "Door"
15414
15395
  },
15415
15396
  {
15416
15397
  id: "footsteps",
15417
- name: "Footsteps",
15418
- icon: "👣"
15398
+ name: "Footsteps"
15419
15399
  },
15420
15400
  {
15421
15401
  id: "crowd",
15422
- name: "Crowd / Chatter",
15423
- icon: "👥"
15402
+ name: "Crowd / Chatter"
15424
15403
  },
15425
15404
  {
15426
15405
  id: "telephone",
15427
- name: "Telephone",
15428
- icon: "📞"
15406
+ name: "Telephone"
15429
15407
  },
15430
15408
  {
15431
15409
  id: "engine",
15432
- name: "Engine / Motor",
15433
- icon: "⚙️"
15410
+ name: "Engine / Motor"
15434
15411
  },
15435
15412
  {
15436
15413
  id: "tools",
15437
- name: "Tools / Construction",
15438
- icon: "🔨"
15414
+ name: "Tools / Construction"
15439
15415
  },
15440
15416
  {
15441
15417
  id: "silence",
15442
- name: "Silence",
15443
- icon: "🤫"
15418
+ name: "Silence"
15444
15419
  }
15445
15420
  ];
15446
15421
  var YAMNET_TO_MACRO = {
@@ -19704,6 +19679,12 @@ var DetailResultSchema = object({
19704
19679
  /** FRAME-space bbox (already mapped back from crop space). */
19705
19680
  bbox: NativeCropBboxSchema.optional(),
19706
19681
  embedding: string().optional(),
19682
+ /**
19683
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
19684
+ * so an older runner that does not send it degrades to "unmeasurable", which
19685
+ * the consuming gate treats as accept — never as reject.
19686
+ */
19687
+ embeddingMagnitude: number().optional(),
19707
19688
  label: string().optional(),
19708
19689
  /**
19709
19690
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -35818,12 +35799,61 @@ Object.freeze({
35818
35799
  "network-access": "ingress",
35819
35800
  "smtp-provider": "email"
35820
35801
  });
35802
+ /**
35803
+ * audio-condition — the authoring logic behind the sustained-sound condition
35804
+ * (`NcConditions.audio`), pure and UI-framework-free.
35805
+ *
35806
+ * WHY IT LIVES HERE
35807
+ * ─────────────────
35808
+ * Same reason as `condition-taxonomy.ts` next door: two editors author this
35809
+ * condition (the admin UI and the viewer), the vocabulary it offers is already
35810
+ * in this package (`AUDIO_MACRO_LABELS`), and a second copy of "what a cleared
35811
+ * threshold writes" is a mirror free to drift. The viewer keeps its own copy
35812
+ * because it is a separate app with its own toolchain; every surface that CAN
35813
+ * import `@camstack/types` uses this one.
35814
+ *
35815
+ * Four behaviours here are not cosmetic:
35816
+ *
35817
+ * 1. **dBFS is negative-going.** `0` is full scale; digital silence reads
35818
+ * {@link NC_AUDIO_DBFS_FLOOR}. A control that let an operator author `60`
35819
+ * meaning "loud" would write a threshold no sample can reach, and the rule
35820
+ * would look broken rather than mis-configured. Every write is clamped.
35821
+ * 2. **Neither filter given = the engine REFUSES.** With no level floor and no
35822
+ * labels every sample is trivially a hit, so the window would fire on
35823
+ * silence. {@link audioIsFailClosed} is how an editor says that BEFORE the
35824
+ * save instead of shipping a rule that never notifies and never says why.
35825
+ * 3. **Both label spellings are the same label.** The hub's taxonomy serves
35826
+ * `audio-dog`; the macro catalog and an operator both say `dog`, and the
35827
+ * matcher normalizes the prefix away on both sides. Selection therefore
35828
+ * compares NORMALIZED ids — a rule authored in the admin UI must not read
35829
+ * as "nothing selected" on the phone.
35830
+ * 4. **A stored label the build does not know stays visible.** It is offered
35831
+ * as an explicitly-unknown, removable choice, never silently dropped:
35832
+ * rewriting the rule the operator is looking at is worse than showing a
35833
+ * stale id.
35834
+ */
35835
+ /** The `audio-` namespace the NC taxonomy uses for these same macro ids. */
35836
+ var AUDIO_KIND_PREFIX = "audio-";
35837
+ /**
35838
+ * The inverse: a macro id in the taxonomy's namespace (`dog` → `audio-dog`),
35839
+ * which is the `iconId` every UI resolves to a glyph (`EVENT_KIND_ICONS` in
35840
+ * `@camstack/ui-library`, `event-kind-icons.ts` in the viewer). Idempotent, so
35841
+ * a value that already carries the namespace is passed through unchanged.
35842
+ *
35843
+ * This lives here — beside `normalizeAudioLabel` and next to the vocabulary
35844
+ * itself — rather than in a UI package, because the *id* is data and the *glyph*
35845
+ * is presentation: the server puts the id on a config option, and whichever
35846
+ * renderer picks it up (lucide-react on the web, lucide-react-native in the
35847
+ * viewer) supplies its own component for it.
35848
+ */
35849
+ function audioKindId(macroId) {
35850
+ return macroId.startsWith(AUDIO_KIND_PREFIX) ? macroId : `${AUDIO_KIND_PREFIX}${macroId}`;
35851
+ }
35821
35852
  /** Schema defaults — an untouched sub-field must author exactly these. */
35822
35853
  var NC_AUDIO_DEFAULTS = {
35823
35854
  hitPercent: 60,
35824
35855
  samplingSeconds: 10
35825
35856
  };
35826
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
35827
35857
  new Set(["devices", "classes"]);
35828
35858
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
35829
35859
  /**
@@ -37110,4 +37140,4 @@ function enumerateInferenceDevices(hw) {
37110
37140
  return out;
37111
37141
  }
37112
37142
  //#endregion
37113
- export { recordingExportCapability as $, cameraStreamsCapability as A, boolean as At, egressTransportFromRequest as B, RecordingConfigSchema as C, nodePin as Ct, audioAnalysisCapability as D, sleep as Dt, addonWidgetsSourceCapability as E, selectAssignedProfileSlots as Et, deriveBatteryPresence as F, object as Ft, maskUrlCredentials as G, evaluateZoneRules as H, deriveDetailCropRect as I, record as It, pickDetailCropConvention as J, motionDetectionCapability as K, deriveRecordingMode as L, string as Lt, customAction as M, lazy as Mt, defaultDeviceFor as N, literal as Nt, audioAnalyzerCapability as O, _enum as Ot, defineCustomActions as P, number as Pt, recordingCapability as Q, detectionPipelineCapability as R, union as Rt, RECORDING_EXPORT_MAX_READ_BYTES as S, makeSourceBrokerId as St, YAMNET_TO_MACRO as T, parseProfileBrokerId as Tt, hfModelUrl as U, enumerateInferenceDevices as V, mapAudioLabelToMacro as W, pipelineExecutorCapability as X, pickNativeLeaseOverride as Y, pipelineRunnerCapability as Z, NativeLeaseSettingsSchema as _, DeviceType as _t, COCO_80_LABELS as a, streamBrokerCapability as at, RATE_CONTROL_RELAXED as b, isEvent as bt, DEFAULT_DETAIL_CROP_CONVENTION as c, errMsg as ct, DEVICE_BACKEND_TO_FORMAT as d, buildFfmpegArgs as dt, resolveEgressDecodeHwAccel as et, EVENT_PAD_MS as f, invocationFromEncodeProfile as ft, NativeLeaseAdmissionSchema as g, DeviceFeature as gt, HF_BASE_URL as h, CAM_PROFILE_ORDER as ht, BatteryStatusSchema as i, storageEvictableCapability as it, createHwAccelCache as j, discriminatedUnion as jt, batteryCapability as k, array as kt, DEFAULT_EVENTS_BAND_BUFFER_SEC as l, AUDIO_PRESETS as lt, ExportRecordSchema as m, BaseAddon as mt, AUDIO_BACKEND_CHOICES as n, resolveRecordingProfiles as nt, COCO_TO_MACRO as o, supportedRuntimes as ot, EncodeProfileSchema as p, isSoftwareDecode as pt, parseProcStatus as q, AUDIO_MACRO_LABELS as r, runtimeDevices as rt, DEFAULT_AUDIO_ANALYZER_CONFIG as s, webrtcSessionCapability as st, APPLE_SA_TO_MACRO as t, resolvePoolMemoryPolicy as tt, DEFAULT_NATIVE_LEASE_SETTINGS as u, Fmp4BoxSplitter as ut, OpsLogEntrySchema as v, createEvent as vt, RingBuffer as w, parseJsonUnknown as wt, RATE_CONTROL_TIGHT as x, makeProfileBrokerId as xt, PoolMemoryWatchdog as y, hydrateSchema as yt, egressTranscodeSharingKey as z, EventCategory as zt };
37143
+ export { recordingCapability as $, batteryCapability as A, array as At, egressTranscodeSharingKey as B, EventCategory as Bt, RecordingConfigSchema as C, makeSourceBrokerId as Ct, audioAnalysisCapability as D, selectAssignedProfileSlots as Dt, addonWidgetsSourceCapability as E, parseProfileBrokerId as Et, defineCustomActions as F, number as Ft, mapAudioLabelToMacro as G, enumerateInferenceDevices as H, deriveBatteryPresence as I, object as It, parseProcStatus as J, maskUrlCredentials as K, deriveDetailCropRect as L, record as Lt, createHwAccelCache as M, discriminatedUnion as Mt, customAction as N, lazy as Nt, audioAnalyzerCapability as O, sleep as Ot, defaultDeviceFor as P, literal as Pt, pipelineRunnerCapability as Q, deriveRecordingMode as R, string as Rt, RECORDING_EXPORT_MAX_READ_BYTES as S, makeProfileBrokerId as St, YAMNET_TO_MACRO as T, parseJsonUnknown as Tt, evaluateZoneRules as U, egressTransportFromRequest as V, hfModelUrl as W, pickNativeLeaseOverride as X, pickDetailCropConvention as Y, pipelineExecutorCapability as Z, NativeLeaseSettingsSchema as _, DeviceFeature as _t, COCO_80_LABELS as a, storageEvictableCapability as at, RATE_CONTROL_RELAXED as b, hydrateSchema as bt, DEFAULT_DETAIL_CROP_CONVENTION as c, webrtcSessionCapability as ct, DEVICE_BACKEND_TO_FORMAT as d, Fmp4BoxSplitter as dt, recordingExportCapability as et, EVENT_PAD_MS as f, buildFfmpegArgs as ft, NativeLeaseAdmissionSchema as g, CAM_PROFILE_ORDER as gt, HF_BASE_URL as h, BaseAddon as ht, BatteryStatusSchema as i, runtimeDevices as it, cameraStreamsCapability as j, boolean as jt, audioKindId as k, _enum as kt, DEFAULT_EVENTS_BAND_BUFFER_SEC as l, errMsg as lt, ExportRecordSchema as m, isSoftwareDecode as mt, AUDIO_BACKEND_CHOICES as n, resolvePoolMemoryPolicy as nt, COCO_TO_MACRO as o, streamBrokerCapability as ot, EncodeProfileSchema as p, invocationFromEncodeProfile as pt, motionDetectionCapability as q, AUDIO_MACRO_LABELS as r, resolveRecordingProfiles as rt, DEFAULT_AUDIO_ANALYZER_CONFIG as s, supportedRuntimes as st, APPLE_SA_TO_MACRO as t, resolveEgressDecodeHwAccel as tt, DEFAULT_NATIVE_LEASE_SETTINGS as u, AUDIO_PRESETS as ut, OpsLogEntrySchema as v, DeviceType as vt, RingBuffer as w, nodePin as wt, RATE_CONTROL_TIGHT as x, isEvent as xt, PoolMemoryWatchdog as y, createEvent as yt, detectionPipelineCapability as z, union as zt };
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-dm3t4BOt.js");
1
+ const require_dist = require("./dist-C11WuNUP.js");
2
2
  let node_perf_hooks = require("node:perf_hooks");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -48,7 +48,8 @@ var ovPrecisionVariant = (baseId, ovDir, baseName, precision, sizeMB, legacy = f
48
48
  ...precision === "int8" ? { group: {
49
49
  family: baseId.slice(0, -1),
50
50
  tier: baseId.slice(-1),
51
- precision: "int8"
51
+ precision: "int8",
52
+ resolution: 640
52
53
  } } : {}
53
54
  });
54
55
  var MLPACKAGE_FILES = [
@@ -191,7 +192,8 @@ var OBJECT_DETECTION_MODELS = [
191
192
  id: "yolo26n",
192
193
  group: {
193
194
  family: "yolo26",
194
- tier: "n"
195
+ tier: "n",
196
+ resolution: 640
195
197
  },
196
198
  name: "YOLO26 Nano",
197
199
  description: "YOLO26 Nano — latest generation ultra-lightweight detector with attention mechanism",
@@ -220,7 +222,8 @@ var OBJECT_DETECTION_MODELS = [
220
222
  id: "yolo26s",
221
223
  group: {
222
224
  family: "yolo26",
223
- tier: "s"
225
+ tier: "s",
226
+ resolution: 640
224
227
  },
225
228
  name: "YOLO26 Small",
226
229
  description: "YOLO26 Small — balanced speed/accuracy with attention-based architecture",
@@ -249,7 +252,8 @@ var OBJECT_DETECTION_MODELS = [
249
252
  id: "yolo26m",
250
253
  group: {
251
254
  family: "yolo26",
252
- tier: "m"
255
+ tier: "m",
256
+ resolution: 640
253
257
  },
254
258
  name: "YOLO26 Medium",
255
259
  description: "YOLO26 Medium — high accuracy general-purpose detector",
@@ -278,7 +282,8 @@ var OBJECT_DETECTION_MODELS = [
278
282
  id: "yolo26l",
279
283
  group: {
280
284
  family: "yolo26",
281
- tier: "l"
285
+ tier: "l",
286
+ resolution: 640
282
287
  },
283
288
  name: "YOLO26 Large",
284
289
  description: "YOLO26 Large — maximum accuracy for demanding deployments",
@@ -474,7 +479,7 @@ var FACE_DETECTION_MODELS = [
474
479
  {
475
480
  id: "scrfd-2.5g",
476
481
  name: "SCRFD 2.5G",
477
- description: "SCRFD 2.5G — balanced face detection model",
482
+ description: "SCRFD 2.5G bnkps — balanced face detection with 5 keypoints (WIDER FACE 93.80/92.02/77.13). InsightFace pretrained weights.",
478
483
  inputSize: {
479
484
  width: 640,
480
485
  height: 640
@@ -484,6 +489,7 @@ var FACE_DETECTION_MODELS = [
484
489
  name: "Face"
485
490
  }],
486
491
  preprocessMode: "letterbox",
492
+ license: "InsightFace-NonCommercial-Research",
487
493
  formats: {
488
494
  onnx: {
489
495
  url: hf("faceDetection/scrfd/onnx/camstack-scrfd-2.5g.onnx"),
@@ -512,6 +518,7 @@ var FACE_DETECTION_MODELS = [
512
518
  name: "Face"
513
519
  }],
514
520
  preprocessMode: "letterbox",
521
+ license: "UNKNOWN",
515
522
  formats: {
516
523
  onnx: {
517
524
  url: hfScrypted("onnx/scrypted_yolov9t_relu_face/scrypted_yolov9t_relu_face.onnx"),
@@ -550,63 +557,98 @@ var FACE_DETECTION_MODELS = [
550
557
  } }
551
558
  }
552
559
  ];
553
- var FACE_EMBEDDING_MODELS = [{
554
- id: "arcface-r100",
555
- name: "ArcFace R100",
556
- description: "ArcFace ResNet-100 — high-accuracy face recognition embeddings (512-d)",
557
- inputSize: {
558
- width: 112,
559
- height: 112
560
- },
561
- inputLayout: "nhwc",
562
- labels: [{
563
- id: "embedding",
564
- name: "Face Embedding"
565
- }],
566
- preprocessMode: "resize",
567
- faceAlignment: true,
568
- formats: {
569
- onnx: {
570
- url: hf("faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx"),
571
- sizeMB: 130
572
- },
573
- coreml: {
574
- url: hf("faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage"),
575
- sizeMB: 65,
576
- isDirectory: true,
577
- files: [...MLPACKAGE_FILES],
578
- runtimes: ["python"]
560
+ var FACE_EMBEDDING_MODELS = [
561
+ {
562
+ id: "arcface-r100",
563
+ name: "ArcFace (ResNet34)",
564
+ description: "ArcFace ResNet34 — face recognition embeddings (512-d). NOTE: despite the `arcface-r100` id this is a ResNet34, not a ResNet-100 (verified 2026-08-19).",
565
+ inputSize: {
566
+ width: 112,
567
+ height: 112
579
568
  },
580
- openvino: ovFormat(hf("faceRecognition/arcface/openvino/camstack-arcface-r100.xml"), 65)
581
- }
582
- }, {
583
- id: "inception-resnet-v1",
584
- name: "Inception ResNet V1",
585
- description: "FaceNet-style face recognition embeddings (512-d) — hosted on plugin-models HF repo",
586
- inputSize: {
587
- width: 160,
588
- height: 160
569
+ inputLayout: "nhwc",
570
+ labels: [{
571
+ id: "embedding",
572
+ name: "Face Embedding"
573
+ }],
574
+ preprocessMode: "resize",
575
+ faceAlignment: true,
576
+ license: "UNKNOWN",
577
+ formats: {
578
+ onnx: {
579
+ url: hf("faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx"),
580
+ sizeMB: 130
581
+ },
582
+ coreml: {
583
+ url: hf("faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage"),
584
+ sizeMB: 65,
585
+ isDirectory: true,
586
+ files: [...MLPACKAGE_FILES],
587
+ runtimes: ["python"]
588
+ },
589
+ openvino: ovFormat(hf("faceRecognition/arcface/openvino/camstack-arcface-r100.xml"), 65)
590
+ }
589
591
  },
590
- labels: [{
591
- id: "embedding",
592
- name: "Face Embedding"
593
- }],
594
- preprocessMode: "resize",
595
- formats: {
596
- onnx: {
597
- url: hfScrypted("onnx/inception_resnet_v1/inception_resnet_v1.onnx"),
598
- sizeMB: 89.6
592
+ {
593
+ id: "auraface-r100",
594
+ name: "AuraFace R100",
595
+ description: "AuraFace v1 (fal.ai) — iResNet-100 ArcFace face recognition embeddings (512-d), Apache-2.0. Benchmarks published by fal: LFW 99.65, CFP-FP 95.19, AgeDB 96.10, CALFW 94.70, CPLFW 90.93. Genuinely a ResNet-100 (65 M params) — roughly twice the depth of the `arcface-r100` entry, which is a ResNet34.",
596
+ inputSize: {
597
+ width: 112,
598
+ height: 112
599
599
  },
600
- coreml: {
601
- url: hfScrypted("coreml/inception_resnet_v1/inception_resnet_v1.mlpackage"),
602
- sizeMB: 45,
603
- isDirectory: true,
604
- files: [...MLPACKAGE_FILES],
605
- runtimes: ["python"]
600
+ labels: [{
601
+ id: "embedding",
602
+ name: "Face Embedding"
603
+ }],
604
+ preprocessMode: "resize",
605
+ faceAlignment: true,
606
+ license: "Apache-2.0",
607
+ formats: {
608
+ onnx: {
609
+ url: hf("faceRecognition/auraface/onnx/camstack-auraface-r100.onnx"),
610
+ sizeMB: 261
611
+ },
612
+ coreml: {
613
+ url: hf("faceRecognition/auraface/coreml/camstack-auraface-r100.mlpackage"),
614
+ sizeMB: 131,
615
+ isDirectory: true,
616
+ files: [...MLPACKAGE_FILES],
617
+ runtimes: ["python"]
618
+ },
619
+ openvino: ovFormat(hf("faceRecognition/auraface/openvino/camstack-auraface-r100.xml"), 131)
620
+ }
621
+ },
622
+ {
623
+ id: "inception-resnet-v1",
624
+ name: "Inception ResNet V1",
625
+ description: "FaceNet-style face recognition embeddings (512-d) — hosted on plugin-models HF repo",
626
+ inputSize: {
627
+ width: 160,
628
+ height: 160
606
629
  },
607
- openvino: ovFormat(hf("faceRecognition/inception-resnet-v1/openvino/camstack-inception-resnet-v1.xml"), 45)
630
+ labels: [{
631
+ id: "embedding",
632
+ name: "Face Embedding"
633
+ }],
634
+ preprocessMode: "resize",
635
+ license: "UNKNOWN",
636
+ formats: {
637
+ onnx: {
638
+ url: hfScrypted("onnx/inception_resnet_v1/inception_resnet_v1.onnx"),
639
+ sizeMB: 89.6
640
+ },
641
+ coreml: {
642
+ url: hfScrypted("coreml/inception_resnet_v1/inception_resnet_v1.mlpackage"),
643
+ sizeMB: 45,
644
+ isDirectory: true,
645
+ files: [...MLPACKAGE_FILES],
646
+ runtimes: ["python"]
647
+ },
648
+ openvino: ovFormat(hf("faceRecognition/inception-resnet-v1/openvino/camstack-inception-resnet-v1.xml"), 45)
649
+ }
608
650
  }
609
- }];
651
+ ];
610
652
  var PLATE_DETECTION_MODELS = [{
611
653
  id: "yolov8n-plate",
612
654
  name: "YOLOv8 Nano — License Plate",
@@ -1192,6 +1234,22 @@ var AUDIO_CLASSIFIER_MODELS = [{
1192
1234
  } }
1193
1235
  }];
1194
1236
  //#endregion
1237
+ //#region src/detection-pipeline/pipeline/landmark-precision-gate.ts
1238
+ /**
1239
+ * `null` = admit. A verdict = refuse, carrying both numbers so the caller can
1240
+ * name them in a log. Pure.
1241
+ */
1242
+ function landmarkPrecisionVerdict(input) {
1243
+ const { detectionShortSidePx, floorPx } = input;
1244
+ if (!(floorPx > 0)) return null;
1245
+ if (!Number.isFinite(detectionShortSidePx)) return null;
1246
+ if (detectionShortSidePx >= floorPx) return null;
1247
+ return {
1248
+ detectionShortSidePx,
1249
+ floorPx
1250
+ };
1251
+ }
1252
+ //#endregion
1195
1253
  //#region src/detection-pipeline/registry/step-definitions.ts
1196
1254
  var ANIMAL_10_LABELS = [
1197
1255
  "cat",
@@ -1272,7 +1330,7 @@ var ObjectDetectionStep = class {
1272
1330
  type: "multiselect",
1273
1331
  key: "enabledMacroClasses",
1274
1332
  label: "Enabled Classes",
1275
- description: "Which macro classes to detect (empty = all three).",
1333
+ description: "Which macro classes to detect (empty = every class listed here).",
1276
1334
  options: [
1277
1335
  {
1278
1336
  value: "person",
@@ -1285,10 +1343,6 @@ var ObjectDetectionStep = class {
1285
1343
  {
1286
1344
  value: "animal",
1287
1345
  label: "Animal"
1288
- },
1289
- {
1290
- value: "package",
1291
- label: "Package"
1292
1346
  }
1293
1347
  ],
1294
1348
  default: [
@@ -1341,21 +1395,6 @@ var ObjectDetectionStep = class {
1341
1395
  default: .5,
1342
1396
  showValue: true
1343
1397
  },
1344
- {
1345
- type: "slider",
1346
- key: "minConfidencePackage",
1347
- label: "Min Confidence — Package",
1348
- description: "Drop package predictions below this score.",
1349
- min: 0,
1350
- max: 1,
1351
- step: .05,
1352
- default: .5,
1353
- showValue: true,
1354
- showWhen: {
1355
- field: "enabledMacroClasses",
1356
- includes: "package"
1357
- }
1358
- },
1359
1398
  {
1360
1399
  type: "slider",
1361
1400
  key: "nmsIouThreshold",
@@ -1393,7 +1432,8 @@ var AudioClassifierStep = class {
1393
1432
  description: "Which audio macro classes to surface (empty = all).",
1394
1433
  options: require_dist.AUDIO_MACRO_LABELS.map((l) => ({
1395
1434
  value: l.id,
1396
- label: `${l.icon ?? ""} ${l.name}`.trim()
1435
+ label: l.name,
1436
+ iconId: require_dist.audioKindId(l.id)
1397
1437
  })),
1398
1438
  default: [
1399
1439
  "scream",
@@ -1520,66 +1560,36 @@ var ClassifierWithMinConfidence = class extends PipelineStepBase {
1520
1560
  var PLATE_OCR_DEFAULT_REGION = "DE";
1521
1561
  var PLATE_OCR_DEFAULT_MIN_TEXT_LENGTH = 3;
1522
1562
  var PLATE_OCR_DEFAULT_MIN_TEXT_CONFIDENCE = 0;
1523
- var PLATE_OCR_REGION_OPTIONS = [{
1524
- value: "off",
1525
- label: "Off (no format correction)"
1526
- }, {
1527
- value: "DE",
1528
- label: "Germany"
1529
- }];
1530
1563
  /**
1531
1564
  * Plate OCR step — exposes a `rectify` toggle (default ON) that deskews the
1532
1565
  * plate crop (rotation rectification) before the CTC recognizer reads it.
1533
1566
  * Oblique cameras (steeply angled plates) skew the text baseline and collapse
1534
1567
  * CTC beyond ~10-15°; a frontal camera loses nothing at 0° because the executor
1535
- * skips the warp when the estimated angle is ~0. Per-camera configurable.
1568
+ * skips the warp when the estimated angle is ~0. Per-camera configurable, and
1569
+ * the ONLY per-camera knob this step has — see below.
1536
1570
  *
1537
- * Also exposes the format-aware rescoring controls (`plateRegion` grammar +
1538
- * plausibility-gate thresholds) forwarded to the Python CTC postprocessor.
1571
+ * `plateRegion` / `minTextLength` / `minTextConfidence` are NOT offered here.
1572
+ * They are consumed inside the Python CTC postprocessor, whose config is bound
1573
+ * when the model is loaded into the shared inference pool — node-level by
1574
+ * construction, exactly like `nmsIouThreshold` and the pool confidence floor.
1575
+ * They used to be rendered as per-camera sliders that wrote a settings row
1576
+ * nobody read: `engine-factory.buildPoolModelConfig` takes the STATIC value off
1577
+ * the step definition below, so the operator moved a slider and the runtime
1578
+ * kept the default (D62 — a per-camera switch that stores nothing is worse than
1579
+ * no switch). Making them genuinely per-camera means a per-REQUEST config
1580
+ * override through SharedInferencePool → inference_pool.py → ModelSlot.config,
1581
+ * i.e. a new wire protocol, not a step-schema entry.
1539
1582
  */
1540
1583
  var PlateOcrStep = class extends PipelineStepBase {
1541
1584
  getConfigSchema() {
1542
- return [
1543
- {
1544
- type: "boolean",
1545
- key: "rectify",
1546
- label: "Deskew Plate Crop",
1547
- description: "Rotate the plate crop to level the text baseline before OCR. Recommended for oblique/angled cameras; a frontal camera is unaffected (no rotation applied at ~0°).",
1548
- default: true,
1549
- style: "switch"
1550
- },
1551
- {
1552
- type: "select",
1553
- key: "plateRegion",
1554
- label: "Plate Format",
1555
- description: "Validate each read against a country plate grammar and repair OCR confusables (0↔O, 1↔I, 8↔B, …) to the highest-scoring conforming read. Never drops a plausible read — only fixes or annotates it. \"Off\" disables correction.",
1556
- options: PLATE_OCR_REGION_OPTIONS,
1557
- default: PLATE_OCR_DEFAULT_REGION,
1558
- immediate: true
1559
- },
1560
- {
1561
- type: "slider",
1562
- key: "minTextLength",
1563
- label: "Min Plate Length",
1564
- description: "Suppress reads shorter than this many characters as low-resolution noise (never stored as a plate).",
1565
- min: 1,
1566
- max: 8,
1567
- step: 1,
1568
- default: PLATE_OCR_DEFAULT_MIN_TEXT_LENGTH,
1569
- showValue: true
1570
- },
1571
- {
1572
- type: "slider",
1573
- key: "minTextConfidence",
1574
- label: "Min OCR Confidence",
1575
- description: "Suppress reads whose mean character confidence is below this floor. 0 disables the confidence gate (length is the primary discriminator).",
1576
- min: 0,
1577
- max: 1,
1578
- step: .05,
1579
- default: PLATE_OCR_DEFAULT_MIN_TEXT_CONFIDENCE,
1580
- showValue: true
1581
- }
1582
- ];
1585
+ return [{
1586
+ type: "boolean",
1587
+ key: "rectify",
1588
+ label: "Deskew Plate Crop",
1589
+ description: "Rotate the plate crop to level the text baseline before OCR. Recommended for oblique/angled cameras; a frontal camera is unaffected (no rotation applied at ~0°).",
1590
+ default: true,
1591
+ style: "switch"
1592
+ }];
1583
1593
  }
1584
1594
  };
1585
1595
  var STEP_FACE_DETECTION = new PipelineStepBase({
@@ -1599,7 +1609,34 @@ var STEP_FACE_DETECTION = new PipelineStepBase({
1599
1609
  minIntervalMs: 1e3
1600
1610
  }
1601
1611
  });
1602
- var STEP_FACE_EMBEDDING = new PipelineStepBase({
1612
+ /**
1613
+ * `face-embedding` — carries the LANDMARK-PRECISION floor as its only operator
1614
+ * knob.
1615
+ *
1616
+ * It has to live here rather than beside the other face thresholds in
1617
+ * post-analysis's `face-settings.ts`: the gate is enforced in the runner's
1618
+ * executor, on the detection-frame measurement, BEFORE the native-size
1619
+ * substitution — and `child.settings` (hydrated from this schema by
1620
+ * `tree-builder`) is the only channel that reaches that code. A copy in
1621
+ * `face-settings.ts` would be a second knob that writes a store the gate never
1622
+ * reads, which this repo has shipped twice already.
1623
+ */
1624
+ var FaceEmbeddingStep = class extends PipelineStepBase {
1625
+ getConfigSchema() {
1626
+ return [{
1627
+ type: "slider",
1628
+ key: "minLandmarkFaceSize",
1629
+ label: "Min face size for recognition (detection px)",
1630
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME — not at native resolution. The 5 landmarks that align the crop are regressed at detection scale, so below this floor the aligned crop is not reliably a face, and a native-resolution cutout cannot fix it. Measured on 60 live crops: at 24px, 61% come out correctly aligned versus 20% with no floor, at the cost of about 70% of the volume. Lower it if you are losing too many faces; 0 disables the gate. A degenerate crop is worse than no crop — it scores high similarity against everyone and poisons the gallery.",
1631
+ min: 0,
1632
+ max: 64,
1633
+ step: 2,
1634
+ showValue: true,
1635
+ default: 24
1636
+ }];
1637
+ }
1638
+ };
1639
+ var STEP_FACE_EMBEDDING = new FaceEmbeddingStep({
1603
1640
  id: "face-embedding",
1604
1641
  name: "Face Embedding",
1605
1642
  slot: "classifier",
@@ -2037,6 +2074,12 @@ Object.defineProperty(exports, "getStepDefinition", {
2037
2074
  return getStepDefinition;
2038
2075
  }
2039
2076
  });
2077
+ Object.defineProperty(exports, "landmarkPrecisionVerdict", {
2078
+ enumerable: true,
2079
+ get: function() {
2080
+ return landmarkPrecisionVerdict;
2081
+ }
2082
+ });
2040
2083
  Object.defineProperty(exports, "resolveModelForFormat", {
2041
2084
  enumerable: true,
2042
2085
  get: function() {