@camstack/addon-vision 0.1.0

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 (109) hide show
  1. package/dist/addons/animal-classifier/index.d.mts +25 -0
  2. package/dist/addons/animal-classifier/index.d.ts +25 -0
  3. package/dist/addons/animal-classifier/index.js +652 -0
  4. package/dist/addons/animal-classifier/index.js.map +1 -0
  5. package/dist/addons/animal-classifier/index.mjs +10 -0
  6. package/dist/addons/animal-classifier/index.mjs.map +1 -0
  7. package/dist/addons/audio-classification/index.d.mts +31 -0
  8. package/dist/addons/audio-classification/index.d.ts +31 -0
  9. package/dist/addons/audio-classification/index.js +572 -0
  10. package/dist/addons/audio-classification/index.js.map +1 -0
  11. package/dist/addons/audio-classification/index.mjs +8 -0
  12. package/dist/addons/audio-classification/index.mjs.map +1 -0
  13. package/dist/addons/bird-global-classifier/index.d.mts +26 -0
  14. package/dist/addons/bird-global-classifier/index.d.ts +26 -0
  15. package/dist/addons/bird-global-classifier/index.js +658 -0
  16. package/dist/addons/bird-global-classifier/index.js.map +1 -0
  17. package/dist/addons/bird-global-classifier/index.mjs +10 -0
  18. package/dist/addons/bird-global-classifier/index.mjs.map +1 -0
  19. package/dist/addons/bird-nabirds-classifier/index.d.mts +28 -0
  20. package/dist/addons/bird-nabirds-classifier/index.d.ts +28 -0
  21. package/dist/addons/bird-nabirds-classifier/index.js +700 -0
  22. package/dist/addons/bird-nabirds-classifier/index.js.map +1 -0
  23. package/dist/addons/bird-nabirds-classifier/index.mjs +10 -0
  24. package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -0
  25. package/dist/addons/camera-native-detection/index.d.mts +32 -0
  26. package/dist/addons/camera-native-detection/index.d.ts +32 -0
  27. package/dist/addons/camera-native-detection/index.js +99 -0
  28. package/dist/addons/camera-native-detection/index.js.map +1 -0
  29. package/dist/addons/camera-native-detection/index.mjs +7 -0
  30. package/dist/addons/camera-native-detection/index.mjs.map +1 -0
  31. package/dist/addons/face-detection/index.d.mts +24 -0
  32. package/dist/addons/face-detection/index.d.ts +24 -0
  33. package/dist/addons/face-detection/index.js +720 -0
  34. package/dist/addons/face-detection/index.js.map +1 -0
  35. package/dist/addons/face-detection/index.mjs +10 -0
  36. package/dist/addons/face-detection/index.mjs.map +1 -0
  37. package/dist/addons/face-recognition/index.d.mts +24 -0
  38. package/dist/addons/face-recognition/index.d.ts +24 -0
  39. package/dist/addons/face-recognition/index.js +603 -0
  40. package/dist/addons/face-recognition/index.js.map +1 -0
  41. package/dist/addons/face-recognition/index.mjs +9 -0
  42. package/dist/addons/face-recognition/index.mjs.map +1 -0
  43. package/dist/addons/motion-detection/index.d.mts +26 -0
  44. package/dist/addons/motion-detection/index.d.ts +26 -0
  45. package/dist/addons/motion-detection/index.js +273 -0
  46. package/dist/addons/motion-detection/index.js.map +1 -0
  47. package/dist/addons/motion-detection/index.mjs +8 -0
  48. package/dist/addons/motion-detection/index.mjs.map +1 -0
  49. package/dist/addons/object-detection/index.d.mts +26 -0
  50. package/dist/addons/object-detection/index.d.ts +26 -0
  51. package/dist/addons/object-detection/index.js +1214 -0
  52. package/dist/addons/object-detection/index.js.map +1 -0
  53. package/dist/addons/object-detection/index.mjs +10 -0
  54. package/dist/addons/object-detection/index.mjs.map +1 -0
  55. package/dist/addons/plate-detection/index.d.mts +25 -0
  56. package/dist/addons/plate-detection/index.d.ts +25 -0
  57. package/dist/addons/plate-detection/index.js +646 -0
  58. package/dist/addons/plate-detection/index.js.map +1 -0
  59. package/dist/addons/plate-detection/index.mjs +10 -0
  60. package/dist/addons/plate-detection/index.mjs.map +1 -0
  61. package/dist/addons/plate-recognition/index.d.mts +25 -0
  62. package/dist/addons/plate-recognition/index.d.ts +25 -0
  63. package/dist/addons/plate-recognition/index.js +648 -0
  64. package/dist/addons/plate-recognition/index.js.map +1 -0
  65. package/dist/addons/plate-recognition/index.mjs +9 -0
  66. package/dist/addons/plate-recognition/index.mjs.map +1 -0
  67. package/dist/chunk-3MQFUDRU.mjs +260 -0
  68. package/dist/chunk-3MQFUDRU.mjs.map +1 -0
  69. package/dist/chunk-5AIQSN32.mjs +227 -0
  70. package/dist/chunk-5AIQSN32.mjs.map +1 -0
  71. package/dist/chunk-5JJZGKL7.mjs +186 -0
  72. package/dist/chunk-5JJZGKL7.mjs.map +1 -0
  73. package/dist/chunk-6OR5TE7A.mjs +101 -0
  74. package/dist/chunk-6OR5TE7A.mjs.map +1 -0
  75. package/dist/chunk-AYBFB7ID.mjs +763 -0
  76. package/dist/chunk-AYBFB7ID.mjs.map +1 -0
  77. package/dist/chunk-B3R66MPF.mjs +219 -0
  78. package/dist/chunk-B3R66MPF.mjs.map +1 -0
  79. package/dist/chunk-DTOAB2CE.mjs +79 -0
  80. package/dist/chunk-DTOAB2CE.mjs.map +1 -0
  81. package/dist/chunk-ISOIDU4U.mjs +54 -0
  82. package/dist/chunk-ISOIDU4U.mjs.map +1 -0
  83. package/dist/chunk-J4WRYHHY.mjs +212 -0
  84. package/dist/chunk-J4WRYHHY.mjs.map +1 -0
  85. package/dist/chunk-KUO2BVFY.mjs +90 -0
  86. package/dist/chunk-KUO2BVFY.mjs.map +1 -0
  87. package/dist/chunk-LPI42WL6.mjs +324 -0
  88. package/dist/chunk-LPI42WL6.mjs.map +1 -0
  89. package/dist/chunk-MEVASN3P.mjs +305 -0
  90. package/dist/chunk-MEVASN3P.mjs.map +1 -0
  91. package/dist/chunk-PDSHDDPV.mjs +255 -0
  92. package/dist/chunk-PDSHDDPV.mjs.map +1 -0
  93. package/dist/chunk-Q3SQOYG6.mjs +218 -0
  94. package/dist/chunk-Q3SQOYG6.mjs.map +1 -0
  95. package/dist/chunk-QIMDG34B.mjs +229 -0
  96. package/dist/chunk-QIMDG34B.mjs.map +1 -0
  97. package/dist/index.d.mts +171 -0
  98. package/dist/index.d.ts +171 -0
  99. package/dist/index.js +3463 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/index.mjs +111 -0
  102. package/dist/index.mjs.map +1 -0
  103. package/package.json +49 -0
  104. package/python/__pycache__/coreml_inference.cpython-313.pyc +0 -0
  105. package/python/__pycache__/openvino_inference.cpython-313.pyc +0 -0
  106. package/python/__pycache__/pytorch_inference.cpython-313.pyc +0 -0
  107. package/python/coreml_inference.py +319 -0
  108. package/python/openvino_inference.py +247 -0
  109. package/python/pytorch_inference.py +255 -0
package/dist/index.js ADDED
@@ -0,0 +1,3463 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ ANIMAL_TYPE_MODELS: () => ANIMAL_TYPE_MODELS,
34
+ AUDIO_CLASSIFICATION_MODELS: () => AUDIO_CLASSIFICATION_MODELS,
35
+ AnimalClassifierAddon: () => AnimalClassifierAddon,
36
+ AudioClassificationAddon: () => AudioClassificationAddon,
37
+ BIRD_NABIRDS_MODELS: () => BIRD_NABIRDS_MODELS,
38
+ BIRD_SPECIES_MODELS: () => BIRD_SPECIES_MODELS,
39
+ BirdGlobalClassifierAddon: () => BirdGlobalClassifierAddon,
40
+ BirdNABirdsClassifierAddon: () => BirdNABirdsClassifierAddon,
41
+ CameraNativeDetectionAddon: () => CameraNativeDetectionAddon,
42
+ FACE_DETECTION_MODELS: () => FACE_DETECTION_MODELS,
43
+ FACE_RECOGNITION_MODELS: () => FACE_RECOGNITION_MODELS,
44
+ FaceDetectionAddon: () => FaceDetectionAddon,
45
+ FaceRecognitionAddon: () => FaceRecognitionAddon,
46
+ MotionDetectionAddon: () => MotionDetectionAddon,
47
+ NodeInferenceEngine: () => NodeInferenceEngine,
48
+ OBJECT_DETECTION_MODELS: () => OBJECT_DETECTION_MODELS,
49
+ ObjectDetectionAddon: () => ObjectDetectionAddon,
50
+ PLATE_DETECTION_MODELS: () => PLATE_DETECTION_MODELS,
51
+ PLATE_RECOGNITION_MODELS: () => PLATE_RECOGNITION_MODELS,
52
+ PlateDetectionAddon: () => PlateDetectionAddon,
53
+ PlateRecognitionAddon: () => PlateRecognitionAddon,
54
+ PythonInferenceEngine: () => PythonInferenceEngine,
55
+ SEGMENTATION_MODELS: () => SEGMENTATION_MODELS,
56
+ cosineSimilarity: () => cosineSimilarity,
57
+ cropRegion: () => cropRegion,
58
+ ctcDecode: () => ctcDecode,
59
+ detectMotion: () => detectMotion,
60
+ iou: () => iou,
61
+ jpegToRgb: () => jpegToRgb,
62
+ l2Normalize: () => l2Normalize,
63
+ letterbox: () => letterbox,
64
+ nms: () => nms,
65
+ probeOnnxBackends: () => probeOnnxBackends,
66
+ resizeAndNormalize: () => resizeAndNormalize,
67
+ resolveEngine: () => resolveEngine,
68
+ rgbToGrayscale: () => rgbToGrayscale,
69
+ scrfdPostprocess: () => scrfdPostprocess,
70
+ yamnetPostprocess: () => yamnetPostprocess,
71
+ yoloPostprocess: () => yoloPostprocess
72
+ });
73
+ module.exports = __toCommonJS(src_exports);
74
+
75
+ // src/shared/image-utils.ts
76
+ var import_sharp = __toESM(require("sharp"));
77
+ async function jpegToRgb(jpeg) {
78
+ const { data, info } = await (0, import_sharp.default)(jpeg).removeAlpha().raw().toBuffer({ resolveWithObject: true });
79
+ return { data, width: info.width, height: info.height };
80
+ }
81
+ async function cropRegion(jpeg, roi) {
82
+ return (0, import_sharp.default)(jpeg).extract({
83
+ left: Math.round(roi.x),
84
+ top: Math.round(roi.y),
85
+ width: Math.round(roi.w),
86
+ height: Math.round(roi.h)
87
+ }).jpeg().toBuffer();
88
+ }
89
+ async function letterbox(jpeg, targetSize) {
90
+ const meta = await (0, import_sharp.default)(jpeg).metadata();
91
+ const originalWidth = meta.width ?? 0;
92
+ const originalHeight = meta.height ?? 0;
93
+ const scale = Math.min(targetSize / originalWidth, targetSize / originalHeight);
94
+ const scaledWidth = Math.round(originalWidth * scale);
95
+ const scaledHeight = Math.round(originalHeight * scale);
96
+ const padX = Math.floor((targetSize - scaledWidth) / 2);
97
+ const padY = Math.floor((targetSize - scaledHeight) / 2);
98
+ const { data } = await (0, import_sharp.default)(jpeg).resize(scaledWidth, scaledHeight).extend({
99
+ top: padY,
100
+ bottom: targetSize - scaledHeight - padY,
101
+ left: padX,
102
+ right: targetSize - scaledWidth - padX,
103
+ background: { r: 114, g: 114, b: 114 }
104
+ }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
105
+ const numPixels = targetSize * targetSize;
106
+ const float32 = new Float32Array(3 * numPixels);
107
+ for (let i = 0; i < numPixels; i++) {
108
+ const srcBase = i * 3;
109
+ float32[0 * numPixels + i] = data[srcBase] / 255;
110
+ float32[1 * numPixels + i] = data[srcBase + 1] / 255;
111
+ float32[2 * numPixels + i] = data[srcBase + 2] / 255;
112
+ }
113
+ return { data: float32, scale, padX, padY, originalWidth, originalHeight };
114
+ }
115
+ async function resizeAndNormalize(jpeg, targetWidth, targetHeight, normalization, layout) {
116
+ const { data } = await (0, import_sharp.default)(jpeg).resize(targetWidth, targetHeight).removeAlpha().raw().toBuffer({ resolveWithObject: true });
117
+ const numPixels = targetWidth * targetHeight;
118
+ const float32 = new Float32Array(3 * numPixels);
119
+ const mean = [0.485, 0.456, 0.406];
120
+ const std = [0.229, 0.224, 0.225];
121
+ if (layout === "nchw") {
122
+ for (let i = 0; i < numPixels; i++) {
123
+ const srcBase = i * 3;
124
+ for (let c = 0; c < 3; c++) {
125
+ const raw = data[srcBase + c] / 255;
126
+ let val;
127
+ if (normalization === "zero-one") {
128
+ val = raw;
129
+ } else if (normalization === "imagenet") {
130
+ val = (raw - mean[c]) / std[c];
131
+ } else {
132
+ val = data[srcBase + c];
133
+ }
134
+ float32[c * numPixels + i] = val;
135
+ }
136
+ }
137
+ } else {
138
+ for (let i = 0; i < numPixels; i++) {
139
+ const srcBase = i * 3;
140
+ for (let c = 0; c < 3; c++) {
141
+ const raw = data[srcBase + c] / 255;
142
+ let val;
143
+ if (normalization === "zero-one") {
144
+ val = raw;
145
+ } else if (normalization === "imagenet") {
146
+ val = (raw - mean[c]) / std[c];
147
+ } else {
148
+ val = data[srcBase + c];
149
+ }
150
+ float32[i * 3 + c] = val;
151
+ }
152
+ }
153
+ }
154
+ return float32;
155
+ }
156
+ function rgbToGrayscale(rgb, width, height) {
157
+ const numPixels = width * height;
158
+ const gray = new Uint8Array(numPixels);
159
+ for (let i = 0; i < numPixels; i++) {
160
+ const r = rgb[i * 3];
161
+ const g = rgb[i * 3 + 1];
162
+ const b = rgb[i * 3 + 2];
163
+ gray[i] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
164
+ }
165
+ return gray;
166
+ }
167
+
168
+ // src/shared/postprocess/yolo.ts
169
+ function iou(a, b) {
170
+ const ax1 = a.x;
171
+ const ay1 = a.y;
172
+ const ax2 = a.x + a.w;
173
+ const ay2 = a.y + a.h;
174
+ const bx1 = b.x;
175
+ const by1 = b.y;
176
+ const bx2 = b.x + b.w;
177
+ const by2 = b.y + b.h;
178
+ const interX1 = Math.max(ax1, bx1);
179
+ const interY1 = Math.max(ay1, by1);
180
+ const interX2 = Math.min(ax2, bx2);
181
+ const interY2 = Math.min(ay2, by2);
182
+ const interW = Math.max(0, interX2 - interX1);
183
+ const interH = Math.max(0, interY2 - interY1);
184
+ const interArea = interW * interH;
185
+ if (interArea === 0) return 0;
186
+ const areaA = a.w * a.h;
187
+ const areaB = b.w * b.h;
188
+ const unionArea = areaA + areaB - interArea;
189
+ return unionArea === 0 ? 0 : interArea / unionArea;
190
+ }
191
+ function nms(boxes, iouThreshold) {
192
+ const indices = boxes.map((_, i) => i).sort((a, b) => boxes[b].score - boxes[a].score);
193
+ const kept = [];
194
+ const suppressed = /* @__PURE__ */ new Set();
195
+ for (const idx of indices) {
196
+ if (suppressed.has(idx)) continue;
197
+ kept.push(idx);
198
+ for (const other of indices) {
199
+ if (other === idx || suppressed.has(other)) continue;
200
+ if (iou(boxes[idx].bbox, boxes[other].bbox) > iouThreshold) {
201
+ suppressed.add(other);
202
+ }
203
+ }
204
+ }
205
+ return kept;
206
+ }
207
+ function yoloPostprocess(output, numClasses, numBoxes, options) {
208
+ const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options;
209
+ const candidates = [];
210
+ for (let i = 0; i < numBoxes; i++) {
211
+ const cx = output[0 * numBoxes + i];
212
+ const cy = output[1 * numBoxes + i];
213
+ const w = output[2 * numBoxes + i];
214
+ const h = output[3 * numBoxes + i];
215
+ let bestScore = -Infinity;
216
+ let bestClass = 0;
217
+ for (let j = 0; j < numClasses; j++) {
218
+ const score = output[(4 + j) * numBoxes + i];
219
+ if (score > bestScore) {
220
+ bestScore = score;
221
+ bestClass = j;
222
+ }
223
+ }
224
+ if (bestScore < confidence) continue;
225
+ const bbox = {
226
+ x: cx - w / 2,
227
+ y: cy - h / 2,
228
+ w,
229
+ h
230
+ };
231
+ candidates.push({ bbox, score: bestScore, classIdx: bestClass });
232
+ }
233
+ if (candidates.length === 0) return [];
234
+ const keptIndices = nms(candidates, iouThreshold);
235
+ return keptIndices.map((idx) => {
236
+ const { bbox, score, classIdx } = candidates[idx];
237
+ const label = labels[classIdx] ?? String(classIdx);
238
+ const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale));
239
+ const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale));
240
+ const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale));
241
+ const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale));
242
+ const finalBbox = { x, y, w: x2 - x, h: y2 - y };
243
+ return {
244
+ class: label,
245
+ originalClass: label,
246
+ score,
247
+ bbox: finalBbox
248
+ };
249
+ });
250
+ }
251
+
252
+ // src/shared/postprocess/scrfd.ts
253
+ var STRIDES = [8, 16, 32];
254
+ var NUM_ANCHORS_PER_STRIDE = 2;
255
+ function generateAnchors(stride, inputSize) {
256
+ const featureSize = Math.ceil(inputSize / stride);
257
+ const anchors = [];
258
+ for (let y = 0; y < featureSize; y++) {
259
+ for (let x = 0; x < featureSize; x++) {
260
+ for (let k = 0; k < NUM_ANCHORS_PER_STRIDE; k++) {
261
+ anchors.push({
262
+ cx: (x + 0.5) * stride,
263
+ cy: (y + 0.5) * stride
264
+ });
265
+ }
266
+ }
267
+ }
268
+ return anchors;
269
+ }
270
+ function scrfdPostprocess(outputs, confidence, inputSize, originalWidth, originalHeight) {
271
+ const scaleX = originalWidth / inputSize;
272
+ const scaleY = originalHeight / inputSize;
273
+ const candidates = [];
274
+ for (const stride of STRIDES) {
275
+ const scoreKey = Object.keys(outputs).find((k) => k.includes(`score_${stride}`) || k.includes(`_${stride}_score`));
276
+ const bboxKey = Object.keys(outputs).find((k) => k.includes(`bbox_${stride}`) || k.includes(`_${stride}_bbox`));
277
+ const kpsKey = Object.keys(outputs).find((k) => k.includes(`kps_${stride}`) || k.includes(`_${stride}_kps`));
278
+ if (!scoreKey || !bboxKey) continue;
279
+ const scores = outputs[scoreKey];
280
+ const bboxes = outputs[bboxKey];
281
+ const kps = kpsKey ? outputs[kpsKey] : void 0;
282
+ const anchors = generateAnchors(stride, inputSize);
283
+ const n = anchors.length;
284
+ for (let i = 0; i < n; i++) {
285
+ const score = scores[i];
286
+ if (score < confidence) continue;
287
+ const anchor = anchors[i];
288
+ const x1 = anchor.cx - bboxes[i * 4] * stride;
289
+ const y1 = anchor.cy - bboxes[i * 4 + 1] * stride;
290
+ const x2 = anchor.cx + bboxes[i * 4 + 2] * stride;
291
+ const y2 = anchor.cy + bboxes[i * 4 + 3] * stride;
292
+ const bbox = {
293
+ x: x1 * scaleX,
294
+ y: y1 * scaleY,
295
+ w: (x2 - x1) * scaleX,
296
+ h: (y2 - y1) * scaleY
297
+ };
298
+ let landmarks;
299
+ if (kps) {
300
+ const pts = [];
301
+ for (let p = 0; p < 5; p++) {
302
+ pts.push({
303
+ x: (anchor.cx + kps[i * 10 + p * 2] * stride) * scaleX,
304
+ y: (anchor.cy + kps[i * 10 + p * 2 + 1] * stride) * scaleY
305
+ });
306
+ }
307
+ landmarks = pts;
308
+ }
309
+ candidates.push({ bbox, score, landmarks });
310
+ }
311
+ }
312
+ if (candidates.length === 0) return [];
313
+ const keptIndices = nms(candidates, 0.45);
314
+ return keptIndices.map((idx) => {
315
+ const { bbox, score, landmarks } = candidates[idx];
316
+ return {
317
+ class: "face",
318
+ originalClass: "face",
319
+ score,
320
+ bbox,
321
+ ...landmarks ? { landmarks } : {}
322
+ };
323
+ });
324
+ }
325
+
326
+ // src/shared/postprocess/arcface.ts
327
+ function l2Normalize(vec) {
328
+ let sumSq = 0;
329
+ for (let i = 0; i < vec.length; i++) {
330
+ sumSq += vec[i] * vec[i];
331
+ }
332
+ const norm = Math.sqrt(sumSq);
333
+ if (norm === 0) return new Float32Array(vec.length);
334
+ const out = new Float32Array(vec.length);
335
+ for (let i = 0; i < vec.length; i++) {
336
+ out[i] = vec[i] / norm;
337
+ }
338
+ return out;
339
+ }
340
+ function cosineSimilarity(a, b) {
341
+ if (a.length !== b.length) throw new Error("Embedding length mismatch");
342
+ let dot = 0;
343
+ for (let i = 0; i < a.length; i++) {
344
+ dot += a[i] * b[i];
345
+ }
346
+ return dot;
347
+ }
348
+
349
+ // src/shared/postprocess/paddleocr.ts
350
+ function ctcDecode(output, seqLen, numChars, charset) {
351
+ let totalLogScore = 0;
352
+ const rawIndices = [];
353
+ for (let t = 0; t < seqLen; t++) {
354
+ const offset = t * numChars;
355
+ let bestIdx = 0;
356
+ let bestVal = output[offset];
357
+ for (let c = 1; c < numChars; c++) {
358
+ const val = output[offset + c];
359
+ if (val > bestVal) {
360
+ bestVal = val;
361
+ bestIdx = c;
362
+ }
363
+ }
364
+ rawIndices.push(bestIdx);
365
+ totalLogScore += bestVal;
366
+ }
367
+ const collapsed = [];
368
+ for (let i = 0; i < rawIndices.length; i++) {
369
+ const cur = rawIndices[i];
370
+ if (i === 0 || cur !== rawIndices[i - 1]) {
371
+ collapsed.push(cur);
372
+ }
373
+ }
374
+ const filtered = collapsed.filter((idx) => idx !== 0);
375
+ const text = filtered.map((idx) => charset[idx] ?? "").join("");
376
+ const confidence = seqLen > 0 ? totalLogScore / seqLen : 0;
377
+ return { text, confidence };
378
+ }
379
+
380
+ // src/shared/postprocess/yamnet.ts
381
+ function yamnetPostprocess(output, numFrames, numClasses, classNames, minScore) {
382
+ const avgScores = new Float32Array(numClasses);
383
+ for (let f = 0; f < numFrames; f++) {
384
+ for (let c = 0; c < numClasses; c++) {
385
+ const prev = avgScores[c] ?? 0;
386
+ avgScores[c] = prev + (output[f * numClasses + c] ?? 0);
387
+ }
388
+ }
389
+ if (numFrames > 0) {
390
+ for (let c = 0; c < numClasses; c++) {
391
+ const val = avgScores[c] ?? 0;
392
+ avgScores[c] = val / numFrames;
393
+ }
394
+ }
395
+ const results = [];
396
+ for (let c = 0; c < numClasses; c++) {
397
+ const score = avgScores[c];
398
+ if (score >= minScore) {
399
+ results.push({
400
+ className: classNames[c] ?? String(c),
401
+ score
402
+ });
403
+ }
404
+ }
405
+ return results.sort((a, b) => b.score - a.score);
406
+ }
407
+
408
+ // src/shared/node-engine.ts
409
+ var path = __toESM(require("path"));
410
+ var BACKEND_TO_PROVIDER = {
411
+ cpu: "cpu",
412
+ coreml: "coreml",
413
+ cuda: "cuda",
414
+ tensorrt: "tensorrt",
415
+ dml: "dml"
416
+ };
417
+ var BACKEND_TO_DEVICE = {
418
+ cpu: "cpu",
419
+ coreml: "gpu-mps",
420
+ cuda: "gpu-cuda",
421
+ tensorrt: "tensorrt"
422
+ };
423
+ var NodeInferenceEngine = class {
424
+ constructor(modelPath, backend) {
425
+ this.modelPath = modelPath;
426
+ this.backend = backend;
427
+ this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
428
+ }
429
+ runtime = "onnx";
430
+ device;
431
+ session = null;
432
+ async initialize() {
433
+ const ort = await import("onnxruntime-node");
434
+ const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
435
+ const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
436
+ const sessionOptions = {
437
+ executionProviders: [provider]
438
+ };
439
+ this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
440
+ }
441
+ async run(input, inputShape) {
442
+ if (!this.session) {
443
+ throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
444
+ }
445
+ const ort = await import("onnxruntime-node");
446
+ const sess = this.session;
447
+ const inputName = sess.inputNames[0];
448
+ const tensor = new ort.Tensor("float32", input, [...inputShape]);
449
+ const feeds = { [inputName]: tensor };
450
+ const results = await sess.run(feeds);
451
+ const outputName = sess.outputNames[0];
452
+ const outputTensor = results[outputName];
453
+ return outputTensor.data;
454
+ }
455
+ async runMultiOutput(input, inputShape) {
456
+ if (!this.session) {
457
+ throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
458
+ }
459
+ const ort = await import("onnxruntime-node");
460
+ const sess = this.session;
461
+ const inputName = sess.inputNames[0];
462
+ const tensor = new ort.Tensor("float32", input, [...inputShape]);
463
+ const feeds = { [inputName]: tensor };
464
+ const results = await sess.run(feeds);
465
+ const out = {};
466
+ for (const name of sess.outputNames) {
467
+ out[name] = results[name].data;
468
+ }
469
+ return out;
470
+ }
471
+ async dispose() {
472
+ this.session = null;
473
+ }
474
+ };
475
+
476
+ // src/shared/python-engine.ts
477
+ var import_node_child_process = require("child_process");
478
+ var PythonInferenceEngine = class {
479
+ constructor(pythonPath, scriptPath, runtime, modelPath, extraArgs = []) {
480
+ this.pythonPath = pythonPath;
481
+ this.scriptPath = scriptPath;
482
+ this.modelPath = modelPath;
483
+ this.extraArgs = extraArgs;
484
+ this.runtime = runtime;
485
+ const runtimeDeviceMap = {
486
+ onnx: "cpu",
487
+ coreml: "gpu-mps",
488
+ pytorch: "cpu",
489
+ openvino: "cpu",
490
+ tflite: "cpu"
491
+ };
492
+ this.device = runtimeDeviceMap[runtime];
493
+ }
494
+ runtime;
495
+ device;
496
+ process = null;
497
+ receiveBuffer = Buffer.alloc(0);
498
+ pendingResolve = null;
499
+ pendingReject = null;
500
+ async initialize() {
501
+ const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
502
+ this.process = (0, import_node_child_process.spawn)(this.pythonPath, args, {
503
+ stdio: ["pipe", "pipe", "pipe"]
504
+ });
505
+ if (!this.process.stdout || !this.process.stdin) {
506
+ throw new Error("PythonInferenceEngine: failed to create process pipes");
507
+ }
508
+ this.process.stderr?.on("data", (chunk) => {
509
+ process.stderr.write(`[python-engine] ${chunk.toString()}`);
510
+ });
511
+ this.process.on("error", (err) => {
512
+ this.pendingReject?.(err);
513
+ this.pendingReject = null;
514
+ this.pendingResolve = null;
515
+ });
516
+ this.process.on("exit", (code) => {
517
+ if (code !== 0) {
518
+ const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
519
+ this.pendingReject?.(err);
520
+ this.pendingReject = null;
521
+ this.pendingResolve = null;
522
+ }
523
+ });
524
+ this.process.stdout.on("data", (chunk) => {
525
+ this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
526
+ this._tryReceive();
527
+ });
528
+ await new Promise((resolve2, reject) => {
529
+ const timeout = setTimeout(() => resolve2(), 2e3);
530
+ this.process?.on("error", (err) => {
531
+ clearTimeout(timeout);
532
+ reject(err);
533
+ });
534
+ this.process?.on("exit", (code) => {
535
+ clearTimeout(timeout);
536
+ if (code !== 0) {
537
+ reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
538
+ }
539
+ });
540
+ });
541
+ }
542
+ _tryReceive() {
543
+ if (this.receiveBuffer.length < 4) return;
544
+ const length = this.receiveBuffer.readUInt32LE(0);
545
+ if (this.receiveBuffer.length < 4 + length) return;
546
+ const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
547
+ this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
548
+ const resolve2 = this.pendingResolve;
549
+ const reject = this.pendingReject;
550
+ this.pendingResolve = null;
551
+ this.pendingReject = null;
552
+ if (!resolve2) return;
553
+ try {
554
+ const parsed = JSON.parse(jsonBytes.toString("utf8"));
555
+ resolve2(parsed);
556
+ } catch (err) {
557
+ reject?.(err instanceof Error ? err : new Error(String(err)));
558
+ }
559
+ }
560
+ /** Send JPEG buffer, receive JSON detection results */
561
+ async runJpeg(jpeg) {
562
+ if (!this.process?.stdin) {
563
+ throw new Error("PythonInferenceEngine: process not initialized");
564
+ }
565
+ return new Promise((resolve2, reject) => {
566
+ this.pendingResolve = resolve2;
567
+ this.pendingReject = reject;
568
+ const lengthBuf = Buffer.allocUnsafe(4);
569
+ lengthBuf.writeUInt32LE(jpeg.length, 0);
570
+ this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
571
+ });
572
+ }
573
+ /** IInferenceEngine.run — wraps runJpeg for compatibility */
574
+ async run(_input, _inputShape) {
575
+ throw new Error(
576
+ "PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input"
577
+ );
578
+ }
579
+ /** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
580
+ async runMultiOutput(_input, _inputShape) {
581
+ throw new Error(
582
+ "PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input"
583
+ );
584
+ }
585
+ async dispose() {
586
+ if (this.process) {
587
+ this.process.stdin?.end();
588
+ this.process.kill("SIGTERM");
589
+ this.process = null;
590
+ }
591
+ }
592
+ };
593
+
594
+ // src/shared/engine-resolver.ts
595
+ var fs = __toESM(require("fs"));
596
+ var path2 = __toESM(require("path"));
597
+ var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
598
+ var BACKEND_TO_FORMAT = {
599
+ cpu: "onnx",
600
+ coreml: "coreml",
601
+ cuda: "onnx",
602
+ tensorrt: "onnx"
603
+ };
604
+ var RUNTIME_TO_FORMAT = {
605
+ onnx: "onnx",
606
+ coreml: "coreml",
607
+ openvino: "openvino",
608
+ tflite: "tflite",
609
+ pytorch: "pt"
610
+ };
611
+ function modelFilePath(modelsDir, modelEntry, format) {
612
+ const formatEntry = modelEntry.formats[format];
613
+ if (!formatEntry) {
614
+ throw new Error(`Model ${modelEntry.id} has no ${format} format`);
615
+ }
616
+ const urlParts = formatEntry.url.split("/");
617
+ const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
618
+ return path2.join(modelsDir, filename);
619
+ }
620
+ function modelExists(filePath) {
621
+ try {
622
+ return fs.existsSync(filePath);
623
+ } catch {
624
+ return false;
625
+ }
626
+ }
627
+ async function resolveEngine(options) {
628
+ const { runtime, backend, modelEntry, modelsDir, downloadModel } = options;
629
+ let selectedFormat;
630
+ let selectedBackend;
631
+ if (runtime === "auto") {
632
+ const available = await probeOnnxBackends();
633
+ let chosen = null;
634
+ for (const b of AUTO_BACKEND_PRIORITY) {
635
+ if (!available.includes(b)) continue;
636
+ const fmt = BACKEND_TO_FORMAT[b];
637
+ if (!fmt) continue;
638
+ if (!modelEntry.formats[fmt]) continue;
639
+ chosen = { backend: b, format: fmt };
640
+ break;
641
+ }
642
+ if (!chosen) {
643
+ throw new Error(
644
+ `resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`
645
+ );
646
+ }
647
+ selectedFormat = chosen.format;
648
+ selectedBackend = chosen.backend;
649
+ } else {
650
+ const fmt = RUNTIME_TO_FORMAT[runtime];
651
+ if (!fmt) {
652
+ throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
653
+ }
654
+ if (!modelEntry.formats[fmt]) {
655
+ throw new Error(
656
+ `resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`
657
+ );
658
+ }
659
+ selectedFormat = fmt;
660
+ selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
661
+ }
662
+ let modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
663
+ if (!modelExists(modelPath)) {
664
+ if (downloadModel) {
665
+ const formatEntry = modelEntry.formats[selectedFormat];
666
+ modelPath = await downloadModel(formatEntry.url, modelsDir);
667
+ } else {
668
+ throw new Error(
669
+ `resolveEngine: model file not found at ${modelPath} and no downloadModel function provided`
670
+ );
671
+ }
672
+ }
673
+ if (selectedFormat === "onnx" || selectedFormat === "coreml") {
674
+ const engine = new NodeInferenceEngine(modelPath, selectedBackend);
675
+ await engine.initialize();
676
+ return { engine, format: selectedFormat, modelPath };
677
+ }
678
+ const { pythonPath } = options;
679
+ const PYTHON_SCRIPT_MAP = {
680
+ coreml: "coreml_inference.py",
681
+ pytorch: "pytorch_inference.py",
682
+ openvino: "openvino_inference.py"
683
+ };
684
+ const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
685
+ const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
686
+ if (scriptName && pythonPath) {
687
+ const scriptPath = path2.join(__dirname, "../../python", scriptName);
688
+ const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
689
+ const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
690
+ `--input-size=${inputSize}`,
691
+ `--confidence=0.25`
692
+ ]);
693
+ await engine.initialize();
694
+ return { engine, format: selectedFormat, modelPath };
695
+ }
696
+ const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
697
+ if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
698
+ const engine = new NodeInferenceEngine(fallbackPath, "cpu");
699
+ await engine.initialize();
700
+ return { engine, format: "onnx", modelPath: fallbackPath };
701
+ }
702
+ throw new Error(
703
+ `resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`
704
+ );
705
+ }
706
+ async function probeOnnxBackends() {
707
+ const available = ["cpu"];
708
+ try {
709
+ const ort = await import("onnxruntime-node");
710
+ const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
711
+ for (const p of providers) {
712
+ const normalized = p.toLowerCase().replace("executionprovider", "");
713
+ if (normalized === "coreml") available.push("coreml");
714
+ else if (normalized === "cuda") available.push("cuda");
715
+ else if (normalized === "tensorrt") available.push("tensorrt");
716
+ }
717
+ } catch {
718
+ }
719
+ if (process.platform === "darwin" && !available.includes("coreml")) {
720
+ available.push("coreml");
721
+ }
722
+ return [...new Set(available)];
723
+ }
724
+
725
+ // src/addons/motion-detection/frame-diff.ts
726
+ function detectMotion(current, previous, width, height, threshold, minArea) {
727
+ const numPixels = width * height;
728
+ const mask = new Uint8Array(numPixels);
729
+ for (let i = 0; i < numPixels; i++) {
730
+ mask[i] = Math.abs((current[i] ?? 0) - (previous[i] ?? 0)) > threshold ? 1 : 0;
731
+ }
732
+ const labels = new Int32Array(numPixels).fill(0);
733
+ const parent = new Int32Array(numPixels + 1).fill(0);
734
+ let nextLabel = 1;
735
+ function findRoot(x) {
736
+ while (parent[x] !== x) {
737
+ parent[x] = parent[parent[x]];
738
+ x = parent[x];
739
+ }
740
+ return x;
741
+ }
742
+ function union(a, b) {
743
+ const ra = findRoot(a);
744
+ const rb = findRoot(b);
745
+ if (ra !== rb) parent[rb] = ra;
746
+ return ra;
747
+ }
748
+ for (let i = 0; i <= numPixels; i++) {
749
+ parent[i] = i;
750
+ }
751
+ for (let y = 0; y < height; y++) {
752
+ for (let x = 0; x < width; x++) {
753
+ const idx = y * width + x;
754
+ if (!mask[idx]) continue;
755
+ const above = y > 0 ? labels[(y - 1) * width + x] ?? 0 : 0;
756
+ const left = x > 0 ? labels[y * width + (x - 1)] ?? 0 : 0;
757
+ if (above === 0 && left === 0) {
758
+ labels[idx] = nextLabel;
759
+ parent[nextLabel] = nextLabel;
760
+ nextLabel++;
761
+ } else if (above !== 0 && left === 0) {
762
+ labels[idx] = above;
763
+ } else if (above === 0 && left !== 0) {
764
+ labels[idx] = left;
765
+ } else {
766
+ labels[idx] = union(above, left);
767
+ }
768
+ }
769
+ }
770
+ for (let i = 0; i < numPixels; i++) {
771
+ if (labels[i]) {
772
+ labels[i] = findRoot(labels[i]);
773
+ }
774
+ }
775
+ const bboxMap = /* @__PURE__ */ new Map();
776
+ for (let y = 0; y < height; y++) {
777
+ for (let x = 0; x < width; x++) {
778
+ const idx = y * width + x;
779
+ const label = labels[idx];
780
+ if (!label) continue;
781
+ const diff = Math.abs((current[idx] ?? 0) - (previous[idx] ?? 0));
782
+ const existing = bboxMap.get(label);
783
+ if (existing) {
784
+ existing.minX = Math.min(existing.minX, x);
785
+ existing.minY = Math.min(existing.minY, y);
786
+ existing.maxX = Math.max(existing.maxX, x);
787
+ existing.maxY = Math.max(existing.maxY, y);
788
+ existing.count++;
789
+ existing.intensitySum += diff;
790
+ } else {
791
+ bboxMap.set(label, {
792
+ minX: x,
793
+ minY: y,
794
+ maxX: x,
795
+ maxY: y,
796
+ count: 1,
797
+ intensitySum: diff
798
+ });
799
+ }
800
+ }
801
+ }
802
+ const regions = [];
803
+ for (const [, info] of bboxMap) {
804
+ if (info.count < minArea) continue;
805
+ regions.push({
806
+ bbox: {
807
+ x: info.minX,
808
+ y: info.minY,
809
+ w: info.maxX - info.minX + 1,
810
+ h: info.maxY - info.minY + 1
811
+ },
812
+ pixelCount: info.count,
813
+ intensity: info.intensitySum / info.count
814
+ });
815
+ }
816
+ return regions;
817
+ }
818
+
819
+ // src/catalogs/object-detection-models.ts
820
+ var import_types = require("@camstack/types");
821
+ var HF_REPO = "camstack/camstack-models";
822
+ var OBJECT_DETECTION_MODELS = [
823
+ {
824
+ id: "yolov8n",
825
+ name: "YOLOv8 Nano",
826
+ description: "YOLOv8 Nano \u2014 fastest, smallest object detection model",
827
+ inputSize: { width: 640, height: 640 },
828
+ labels: import_types.COCO_80_LABELS,
829
+ formats: {
830
+ onnx: {
831
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/onnx/camstack-yolov8n.onnx"),
832
+ sizeMB: 12
833
+ },
834
+ coreml: {
835
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/coreml/camstack-yolov8n.mlpackage"),
836
+ sizeMB: 6
837
+ },
838
+ openvino: {
839
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/openvino/camstack-yolov8n.xml"),
840
+ sizeMB: 7
841
+ },
842
+ tflite: {
843
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/tflite/camstack-yolov8n_float32.tflite"),
844
+ sizeMB: 12
845
+ }
846
+ }
847
+ },
848
+ {
849
+ id: "yolov8s",
850
+ name: "YOLOv8 Small",
851
+ description: "YOLOv8 Small \u2014 balanced speed and accuracy",
852
+ inputSize: { width: 640, height: 640 },
853
+ labels: import_types.COCO_80_LABELS,
854
+ formats: {
855
+ onnx: {
856
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/onnx/camstack-yolov8s.onnx"),
857
+ sizeMB: 43
858
+ },
859
+ coreml: {
860
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/coreml/camstack-yolov8s.mlpackage"),
861
+ sizeMB: 21
862
+ },
863
+ openvino: {
864
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/openvino/camstack-yolov8s.xml"),
865
+ sizeMB: 22
866
+ },
867
+ tflite: {
868
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/tflite/camstack-yolov8s_float32.tflite"),
869
+ sizeMB: 43
870
+ }
871
+ }
872
+ },
873
+ {
874
+ id: "yolov8m",
875
+ name: "YOLOv8 Medium",
876
+ description: "YOLOv8 Medium \u2014 higher accuracy, moderate size",
877
+ inputSize: { width: 640, height: 640 },
878
+ labels: import_types.COCO_80_LABELS,
879
+ formats: {
880
+ onnx: {
881
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/onnx/camstack-yolov8m.onnx"),
882
+ sizeMB: 99
883
+ },
884
+ coreml: {
885
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/coreml/camstack-yolov8m.mlpackage"),
886
+ sizeMB: 49
887
+ },
888
+ openvino: {
889
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/openvino/camstack-yolov8m.xml"),
890
+ sizeMB: 50
891
+ },
892
+ tflite: {
893
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov8/tflite/camstack-yolov8m_float32.tflite"),
894
+ sizeMB: 99
895
+ }
896
+ }
897
+ },
898
+ {
899
+ id: "yolov9t",
900
+ name: "YOLOv9 Tiny",
901
+ description: "YOLOv9 Tiny \u2014 ultra-lightweight next-gen detector",
902
+ inputSize: { width: 640, height: 640 },
903
+ labels: import_types.COCO_80_LABELS,
904
+ formats: {
905
+ onnx: {
906
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/onnx/camstack-yolov9t.onnx"),
907
+ sizeMB: 8
908
+ },
909
+ coreml: {
910
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/coreml/camstack-yolov9t.mlpackage"),
911
+ sizeMB: 4
912
+ },
913
+ openvino: {
914
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/openvino/camstack-yolov9t.xml"),
915
+ sizeMB: 6
916
+ },
917
+ tflite: {
918
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/tflite/camstack-yolov9t_float32.tflite"),
919
+ sizeMB: 8
920
+ }
921
+ }
922
+ },
923
+ {
924
+ id: "yolov9s",
925
+ name: "YOLOv9 Small",
926
+ description: "YOLOv9 Small \u2014 improved efficiency over YOLOv8s",
927
+ inputSize: { width: 640, height: 640 },
928
+ labels: import_types.COCO_80_LABELS,
929
+ formats: {
930
+ onnx: {
931
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/onnx/camstack-yolov9s.onnx"),
932
+ sizeMB: 28
933
+ },
934
+ coreml: {
935
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/coreml/camstack-yolov9s.mlpackage"),
936
+ sizeMB: 14
937
+ },
938
+ openvino: {
939
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/openvino/camstack-yolov9s.xml"),
940
+ sizeMB: 16
941
+ },
942
+ tflite: {
943
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/tflite/camstack-yolov9s_float32.tflite"),
944
+ sizeMB: 28
945
+ }
946
+ }
947
+ },
948
+ {
949
+ id: "yolov9c",
950
+ name: "YOLOv9 C",
951
+ description: "YOLOv9 C \u2014 high-accuracy compact model",
952
+ inputSize: { width: 640, height: 640 },
953
+ labels: import_types.COCO_80_LABELS,
954
+ formats: {
955
+ onnx: {
956
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/onnx/camstack-yolov9c.onnx"),
957
+ sizeMB: 97
958
+ },
959
+ coreml: {
960
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/coreml/camstack-yolov9c.mlpackage"),
961
+ sizeMB: 48
962
+ },
963
+ openvino: {
964
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/openvino/camstack-yolov9c.xml"),
965
+ sizeMB: 49
966
+ },
967
+ tflite: {
968
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolov9/tflite/camstack-yolov9c_float32.tflite"),
969
+ sizeMB: 97
970
+ }
971
+ }
972
+ },
973
+ // YOLO11 — no CoreML (coremltools incompatible)
974
+ {
975
+ id: "yolo11n",
976
+ name: "YOLO11 Nano",
977
+ description: "YOLO11 Nano \u2014 fastest, smallest YOLO11 detection model",
978
+ inputSize: { width: 640, height: 640 },
979
+ labels: import_types.COCO_80_LABELS,
980
+ formats: {
981
+ onnx: {
982
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/onnx/camstack-yolo11n.onnx"),
983
+ sizeMB: 10
984
+ },
985
+ openvino: {
986
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/openvino/camstack-yolo11n.xml"),
987
+ sizeMB: 5.4
988
+ },
989
+ tflite: {
990
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/tflite/camstack-yolo11n_float32.tflite"),
991
+ sizeMB: 10
992
+ }
993
+ }
994
+ },
995
+ {
996
+ id: "yolo11s",
997
+ name: "YOLO11 Small",
998
+ description: "YOLO11 Small \u2014 balanced speed and accuracy",
999
+ inputSize: { width: 640, height: 640 },
1000
+ labels: import_types.COCO_80_LABELS,
1001
+ formats: {
1002
+ onnx: {
1003
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/onnx/camstack-yolo11s.onnx"),
1004
+ sizeMB: 36
1005
+ },
1006
+ openvino: {
1007
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/openvino/camstack-yolo11s.xml"),
1008
+ sizeMB: 18
1009
+ },
1010
+ tflite: {
1011
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/tflite/camstack-yolo11s_float32.tflite"),
1012
+ sizeMB: 36
1013
+ }
1014
+ }
1015
+ },
1016
+ {
1017
+ id: "yolo11m",
1018
+ name: "YOLO11 Medium",
1019
+ description: "YOLO11 Medium \u2014 higher accuracy, moderate size",
1020
+ inputSize: { width: 640, height: 640 },
1021
+ labels: import_types.COCO_80_LABELS,
1022
+ formats: {
1023
+ onnx: {
1024
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/onnx/camstack-yolo11m.onnx"),
1025
+ sizeMB: 77
1026
+ },
1027
+ openvino: {
1028
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/openvino/camstack-yolo11m.xml"),
1029
+ sizeMB: 39
1030
+ },
1031
+ tflite: {
1032
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/tflite/camstack-yolo11m_float32.tflite"),
1033
+ sizeMB: 77
1034
+ }
1035
+ }
1036
+ },
1037
+ {
1038
+ id: "yolo11l",
1039
+ name: "YOLO11 Large",
1040
+ description: "YOLO11 Large \u2014 high-accuracy large model",
1041
+ inputSize: { width: 640, height: 640 },
1042
+ labels: import_types.COCO_80_LABELS,
1043
+ formats: {
1044
+ onnx: {
1045
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/onnx/camstack-yolo11l.onnx"),
1046
+ sizeMB: 97
1047
+ },
1048
+ openvino: {
1049
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/openvino/camstack-yolo11l.xml"),
1050
+ sizeMB: 49
1051
+ },
1052
+ tflite: {
1053
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/tflite/camstack-yolo11l_float32.tflite"),
1054
+ sizeMB: 97
1055
+ }
1056
+ }
1057
+ },
1058
+ {
1059
+ id: "yolo11x",
1060
+ name: "YOLO11 Extra-Large",
1061
+ description: "YOLO11 Extra-Large \u2014 maximum accuracy",
1062
+ inputSize: { width: 640, height: 640 },
1063
+ labels: import_types.COCO_80_LABELS,
1064
+ formats: {
1065
+ onnx: {
1066
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/onnx/camstack-yolo11x.onnx"),
1067
+ sizeMB: 218
1068
+ },
1069
+ openvino: {
1070
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/openvino/camstack-yolo11x.xml"),
1071
+ sizeMB: 109
1072
+ },
1073
+ tflite: {
1074
+ url: (0, import_types.hfModelUrl)(HF_REPO, "objectDetection/yolo11/tflite/camstack-yolo11x_float32.tflite"),
1075
+ sizeMB: 218
1076
+ }
1077
+ }
1078
+ }
1079
+ ];
1080
+
1081
+ // src/catalogs/face-detection-models.ts
1082
+ var import_types2 = require("@camstack/types");
1083
+ var HF_REPO2 = "camstack/camstack-models";
1084
+ var FACE_LABELS = [
1085
+ { id: "face", name: "Face" }
1086
+ ];
1087
+ var FACE_DETECTION_MODELS = [
1088
+ {
1089
+ id: "scrfd-500m",
1090
+ name: "SCRFD 500M",
1091
+ description: "SCRFD 500M \u2014 ultra-lightweight face detector",
1092
+ inputSize: { width: 640, height: 640 },
1093
+ labels: FACE_LABELS,
1094
+ formats: {
1095
+ onnx: {
1096
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/onnx/camstack-scrfd-500m.onnx"),
1097
+ sizeMB: 2.2
1098
+ },
1099
+ coreml: {
1100
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/coreml/camstack-scrfd-500m.mlpackage"),
1101
+ sizeMB: 1.2
1102
+ },
1103
+ openvino: {
1104
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/openvino/camstack-scrfd-500m.xml"),
1105
+ sizeMB: 1.3
1106
+ }
1107
+ }
1108
+ },
1109
+ {
1110
+ id: "scrfd-2.5g",
1111
+ name: "SCRFD 2.5G",
1112
+ description: "SCRFD 2.5G \u2014 balanced face detection model",
1113
+ inputSize: { width: 640, height: 640 },
1114
+ labels: FACE_LABELS,
1115
+ formats: {
1116
+ onnx: {
1117
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/onnx/camstack-scrfd-2.5g.onnx"),
1118
+ sizeMB: 3.1
1119
+ },
1120
+ coreml: {
1121
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/coreml/camstack-scrfd-2.5g.mlpackage"),
1122
+ sizeMB: 1.7
1123
+ },
1124
+ openvino: {
1125
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/openvino/camstack-scrfd-2.5g.xml"),
1126
+ sizeMB: 1.8
1127
+ }
1128
+ }
1129
+ },
1130
+ {
1131
+ id: "scrfd-10g",
1132
+ name: "SCRFD 10G",
1133
+ description: "SCRFD 10G \u2014 high-accuracy face detector",
1134
+ inputSize: { width: 640, height: 640 },
1135
+ labels: FACE_LABELS,
1136
+ formats: {
1137
+ onnx: {
1138
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/onnx/camstack-scrfd-10g.onnx"),
1139
+ sizeMB: 16
1140
+ },
1141
+ coreml: {
1142
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/coreml/camstack-scrfd-10g.mlpackage"),
1143
+ sizeMB: 8.2
1144
+ },
1145
+ openvino: {
1146
+ url: (0, import_types2.hfModelUrl)(HF_REPO2, "faceDetection/scrfd/openvino/camstack-scrfd-10g.xml"),
1147
+ sizeMB: 8.3
1148
+ }
1149
+ }
1150
+ }
1151
+ ];
1152
+
1153
+ // src/catalogs/face-recognition-models.ts
1154
+ var import_types3 = require("@camstack/types");
1155
+ var HF_REPO3 = "camstack/camstack-models";
1156
+ var FACE_EMBEDDING_LABELS = [
1157
+ { id: "embedding", name: "Face Embedding" }
1158
+ ];
1159
+ var FACE_RECOGNITION_MODELS = [
1160
+ {
1161
+ id: "arcface-r100",
1162
+ name: "ArcFace R100",
1163
+ description: "ArcFace ResNet-100 \u2014 high-accuracy face recognition embeddings",
1164
+ inputSize: { width: 112, height: 112 },
1165
+ inputLayout: "nhwc",
1166
+ labels: FACE_EMBEDDING_LABELS,
1167
+ formats: {
1168
+ onnx: {
1169
+ url: (0, import_types3.hfModelUrl)(HF_REPO3, "faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx"),
1170
+ sizeMB: 130
1171
+ },
1172
+ coreml: {
1173
+ url: (0, import_types3.hfModelUrl)(HF_REPO3, "faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage"),
1174
+ sizeMB: 65
1175
+ },
1176
+ openvino: {
1177
+ url: (0, import_types3.hfModelUrl)(HF_REPO3, "faceRecognition/arcface/openvino/camstack-arcface-r100.xml"),
1178
+ sizeMB: 65
1179
+ }
1180
+ }
1181
+ }
1182
+ ];
1183
+
1184
+ // src/catalogs/plate-detection-models.ts
1185
+ var import_types4 = require("@camstack/types");
1186
+ var HF_REPO4 = "camstack/camstack-models";
1187
+ var PLATE_LABELS = [
1188
+ { id: "plate", name: "License Plate" }
1189
+ ];
1190
+ var PLATE_DETECTION_MODELS = [
1191
+ {
1192
+ id: "yolov8n-plate",
1193
+ name: "YOLOv8 Nano \u2014 License Plate",
1194
+ description: "YOLOv8 Nano fine-tuned for license plate detection",
1195
+ inputSize: { width: 640, height: 640 },
1196
+ labels: PLATE_LABELS,
1197
+ formats: {
1198
+ onnx: {
1199
+ url: (0, import_types4.hfModelUrl)(HF_REPO4, "plateDetection/yolov8-plate/onnx/camstack-yolov8n-plate.onnx"),
1200
+ sizeMB: 12
1201
+ },
1202
+ coreml: {
1203
+ url: (0, import_types4.hfModelUrl)(HF_REPO4, "plateDetection/yolov8-plate/coreml/camstack-yolov8n-plate.mlpackage"),
1204
+ sizeMB: 5.9
1205
+ },
1206
+ openvino: {
1207
+ url: (0, import_types4.hfModelUrl)(HF_REPO4, "plateDetection/yolov8-plate/openvino/camstack-yolov8n-plate.xml"),
1208
+ sizeMB: 6.1
1209
+ },
1210
+ tflite: {
1211
+ url: (0, import_types4.hfModelUrl)(HF_REPO4, "plateDetection/yolov8-plate/tflite/camstack-yolov8n-plate_float32.tflite"),
1212
+ sizeMB: 12
1213
+ }
1214
+ }
1215
+ }
1216
+ ];
1217
+
1218
+ // src/catalogs/plate-recognition-models.ts
1219
+ var import_types5 = require("@camstack/types");
1220
+ var HF_REPO5 = "camstack/camstack-models";
1221
+ var PLATE_TEXT_LABELS = [
1222
+ { id: "text", name: "Plate Text" }
1223
+ ];
1224
+ var PLATE_RECOGNITION_MODELS = [
1225
+ {
1226
+ id: "paddleocr-latin",
1227
+ name: "PaddleOCR Latin",
1228
+ description: "PaddleOCR recognition model for Latin-script license plates",
1229
+ inputSize: { width: 320, height: 48 },
1230
+ labels: PLATE_TEXT_LABELS,
1231
+ formats: {
1232
+ onnx: {
1233
+ url: (0, import_types5.hfModelUrl)(HF_REPO5, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-rec.onnx"),
1234
+ sizeMB: 7.5
1235
+ },
1236
+ openvino: {
1237
+ url: (0, import_types5.hfModelUrl)(HF_REPO5, "plateRecognition/paddleocr/openvino/camstack-paddleocr-latin.xml"),
1238
+ sizeMB: 4
1239
+ }
1240
+ }
1241
+ },
1242
+ {
1243
+ id: "paddleocr-en",
1244
+ name: "PaddleOCR English",
1245
+ description: "PaddleOCR recognition model optimized for English license plates",
1246
+ inputSize: { width: 320, height: 48 },
1247
+ labels: PLATE_TEXT_LABELS,
1248
+ formats: {
1249
+ onnx: {
1250
+ url: (0, import_types5.hfModelUrl)(HF_REPO5, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-rec.onnx"),
1251
+ sizeMB: 7.5
1252
+ },
1253
+ openvino: {
1254
+ url: (0, import_types5.hfModelUrl)(HF_REPO5, "plateRecognition/paddleocr/openvino/camstack-paddleocr-en.xml"),
1255
+ sizeMB: 4
1256
+ }
1257
+ }
1258
+ }
1259
+ ];
1260
+
1261
+ // src/catalogs/audio-classification-models.ts
1262
+ var import_types6 = require("@camstack/types");
1263
+ var HF_REPO6 = "camstack/camstack-models";
1264
+ var AUDIO_LABELS = [
1265
+ { id: "audio", name: "Audio Event" }
1266
+ ];
1267
+ var AUDIO_CLASSIFICATION_MODELS = [
1268
+ {
1269
+ id: "yamnet",
1270
+ name: "YAMNet",
1271
+ description: "YAMNet \u2014 audio event classification from raw waveform",
1272
+ inputSize: { width: 1, height: 16e3 },
1273
+ labels: AUDIO_LABELS,
1274
+ formats: {
1275
+ onnx: {
1276
+ url: (0, import_types6.hfModelUrl)(HF_REPO6, "audioClassification/yamnet/onnx/camstack-yamnet.onnx"),
1277
+ sizeMB: 15
1278
+ },
1279
+ openvino: {
1280
+ url: (0, import_types6.hfModelUrl)(HF_REPO6, "audioClassification/yamnet/openvino/camstack-yamnet.xml"),
1281
+ sizeMB: 8
1282
+ }
1283
+ }
1284
+ }
1285
+ ];
1286
+
1287
+ // src/catalogs/segmentation-models.ts
1288
+ var import_types7 = require("@camstack/types");
1289
+ var HF_REPO7 = "camstack/camstack-models";
1290
+ var SEGMENTATION_MODELS = [
1291
+ // YOLO11-seg — no CoreML (coremltools incompatible)
1292
+ {
1293
+ id: "yolo11n-seg",
1294
+ name: "YOLO11 Nano Segmentation",
1295
+ description: "YOLO11 Nano \u2014 fastest, smallest YOLO11 instance segmentation model",
1296
+ inputSize: { width: 640, height: 640 },
1297
+ labels: import_types7.COCO_80_LABELS,
1298
+ formats: {
1299
+ onnx: {
1300
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/onnx/camstack-yolo11n-seg.onnx"),
1301
+ sizeMB: 11
1302
+ },
1303
+ openvino: {
1304
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/openvino/camstack-yolo11n-seg.xml"),
1305
+ sizeMB: 6
1306
+ }
1307
+ }
1308
+ },
1309
+ {
1310
+ id: "yolo11s-seg",
1311
+ name: "YOLO11 Small Segmentation",
1312
+ description: "YOLO11 Small \u2014 balanced speed and accuracy for instance segmentation",
1313
+ inputSize: { width: 640, height: 640 },
1314
+ labels: import_types7.COCO_80_LABELS,
1315
+ formats: {
1316
+ onnx: {
1317
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/onnx/camstack-yolo11s-seg.onnx"),
1318
+ sizeMB: 39
1319
+ },
1320
+ openvino: {
1321
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/openvino/camstack-yolo11s-seg.xml"),
1322
+ sizeMB: 20
1323
+ }
1324
+ }
1325
+ },
1326
+ {
1327
+ id: "yolo11m-seg",
1328
+ name: "YOLO11 Medium Segmentation",
1329
+ description: "YOLO11 Medium \u2014 higher accuracy instance segmentation",
1330
+ inputSize: { width: 640, height: 640 },
1331
+ labels: import_types7.COCO_80_LABELS,
1332
+ formats: {
1333
+ onnx: {
1334
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/onnx/camstack-yolo11m-seg.onnx"),
1335
+ sizeMB: 86
1336
+ },
1337
+ openvino: {
1338
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolo11-seg/openvino/camstack-yolo11m-seg.xml"),
1339
+ sizeMB: 43
1340
+ }
1341
+ }
1342
+ },
1343
+ // YOLOv8-seg — CoreML available
1344
+ {
1345
+ id: "yolov8n-seg",
1346
+ name: "YOLOv8 Nano Segmentation",
1347
+ description: "YOLOv8 Nano \u2014 fastest, smallest YOLOv8 instance segmentation model",
1348
+ inputSize: { width: 640, height: 640 },
1349
+ labels: import_types7.COCO_80_LABELS,
1350
+ formats: {
1351
+ onnx: {
1352
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/onnx/camstack-yolov8n-seg.onnx"),
1353
+ sizeMB: 13
1354
+ },
1355
+ coreml: {
1356
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/coreml/camstack-yolov8n-seg.mlpackage"),
1357
+ sizeMB: 7
1358
+ },
1359
+ openvino: {
1360
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/openvino/camstack-yolov8n-seg.xml"),
1361
+ sizeMB: 7
1362
+ }
1363
+ }
1364
+ },
1365
+ {
1366
+ id: "yolov8s-seg",
1367
+ name: "YOLOv8 Small Segmentation",
1368
+ description: "YOLOv8 Small \u2014 balanced speed and accuracy for instance segmentation",
1369
+ inputSize: { width: 640, height: 640 },
1370
+ labels: import_types7.COCO_80_LABELS,
1371
+ formats: {
1372
+ onnx: {
1373
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/onnx/camstack-yolov8s-seg.onnx"),
1374
+ sizeMB: 45
1375
+ },
1376
+ coreml: {
1377
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/coreml/camstack-yolov8s-seg.mlpackage"),
1378
+ sizeMB: 23
1379
+ },
1380
+ openvino: {
1381
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/openvino/camstack-yolov8s-seg.xml"),
1382
+ sizeMB: 23
1383
+ }
1384
+ }
1385
+ },
1386
+ {
1387
+ id: "yolov8m-seg",
1388
+ name: "YOLOv8 Medium Segmentation",
1389
+ description: "YOLOv8 Medium \u2014 higher accuracy instance segmentation",
1390
+ inputSize: { width: 640, height: 640 },
1391
+ labels: import_types7.COCO_80_LABELS,
1392
+ formats: {
1393
+ onnx: {
1394
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/onnx/camstack-yolov8m-seg.onnx"),
1395
+ sizeMB: 104
1396
+ },
1397
+ coreml: {
1398
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/coreml/camstack-yolov8m-seg.mlpackage"),
1399
+ sizeMB: 52
1400
+ },
1401
+ openvino: {
1402
+ url: (0, import_types7.hfModelUrl)(HF_REPO7, "segmentation/yolov8-seg/openvino/camstack-yolov8m-seg.xml"),
1403
+ sizeMB: 53
1404
+ }
1405
+ }
1406
+ }
1407
+ ];
1408
+
1409
+ // src/catalogs/animal-classification-models.ts
1410
+ var import_types8 = require("@camstack/types");
1411
+ var HF_REPO8 = "camstack/camstack-models";
1412
+ var hf = (path6) => (0, import_types8.hfModelUrl)(HF_REPO8, path6);
1413
+ var BIRD_LABEL = { id: "species", name: "Bird Species" };
1414
+ var ANIMAL_TYPE_LABEL = { id: "animal-type", name: "Animal Type" };
1415
+ var BIRD_SPECIES_MODELS = [
1416
+ {
1417
+ id: "bird-species-525",
1418
+ name: "Bird Species (525)",
1419
+ description: "EfficientNet bird species classifier \u2014 525 species, MIT license",
1420
+ inputSize: { width: 224, height: 224 },
1421
+ inputNormalization: "imagenet",
1422
+ labels: [BIRD_LABEL],
1423
+ formats: {
1424
+ onnx: { url: hf("animalClassification/bird-species/onnx/camstack-bird-species-525.onnx"), sizeMB: 32 }
1425
+ }
1426
+ }
1427
+ ];
1428
+ var BIRD_NABIRDS_MODELS = [
1429
+ {
1430
+ id: "bird-nabirds-404",
1431
+ name: "NABirds (404 species)",
1432
+ description: "ResNet50 trained on NABirds \u2014 404 North American species with ONNX, CoreML, OpenVINO",
1433
+ inputSize: { width: 224, height: 224 },
1434
+ inputNormalization: "imagenet",
1435
+ labels: [{ id: "species", name: "Bird Species" }],
1436
+ formats: {
1437
+ onnx: { url: hf("animalClassification/bird-nabirds/onnx/camstack-bird-nabirds-404.onnx"), sizeMB: 93 },
1438
+ coreml: { url: hf("animalClassification/bird-nabirds/coreml/camstack-bird-nabirds-404.mlpackage"), sizeMB: 47 },
1439
+ openvino: { url: hf("animalClassification/bird-nabirds/openvino/camstack-bird-nabirds-404.xml"), sizeMB: 47 }
1440
+ }
1441
+ }
1442
+ ];
1443
+ var ANIMAL_TYPE_MODELS = [
1444
+ {
1445
+ id: "animals-10",
1446
+ name: "Animal Classifier (10)",
1447
+ description: "ViT-based animal type classifier \u2014 cat, cow, dog, dolphin, eagle, panda, horse, monkey, sheep, spider",
1448
+ inputSize: { width: 224, height: 224 },
1449
+ inputNormalization: "imagenet",
1450
+ labels: [ANIMAL_TYPE_LABEL],
1451
+ formats: {
1452
+ onnx: { url: hf("animalClassification/animals-10/onnx/camstack-animals-10.onnx"), sizeMB: 328 }
1453
+ }
1454
+ }
1455
+ ];
1456
+
1457
+ // src/addons/object-detection/index.ts
1458
+ var import_types9 = require("@camstack/types");
1459
+
1460
+ // src/shared/postprocess/yolo-seg.ts
1461
+ function sigmoid(x) {
1462
+ return 1 / (1 + Math.exp(-x));
1463
+ }
1464
+ function computeRawMask(coeffs, protos, numMaskCoeffs, maskH, maskW) {
1465
+ const maskSize = maskH * maskW;
1466
+ const rawMask = new Float32Array(maskSize);
1467
+ for (let px = 0; px < maskSize; px++) {
1468
+ let val = 0;
1469
+ for (let k = 0; k < numMaskCoeffs; k++) {
1470
+ val += (coeffs[k] ?? 0) * (protos[k * maskSize + px] ?? 0);
1471
+ }
1472
+ rawMask[px] = sigmoid(val);
1473
+ }
1474
+ return rawMask;
1475
+ }
1476
+ function cropAndThresholdMask(rawMask, maskH, maskW, bbox, maskThreshold, maskScale) {
1477
+ const cropX1 = Math.max(0, Math.floor(bbox.x * maskScale));
1478
+ const cropY1 = Math.max(0, Math.floor(bbox.y * maskScale));
1479
+ const cropX2 = Math.min(maskW, Math.ceil((bbox.x + bbox.w) * maskScale));
1480
+ const cropY2 = Math.min(maskH, Math.ceil((bbox.y + bbox.h) * maskScale));
1481
+ const cropW = Math.max(1, cropX2 - cropX1);
1482
+ const cropH = Math.max(1, cropY2 - cropY1);
1483
+ const data = new Uint8Array(cropW * cropH);
1484
+ for (let row = 0; row < cropH; row++) {
1485
+ const srcRow = cropY1 + row;
1486
+ for (let col = 0; col < cropW; col++) {
1487
+ const srcCol = cropX1 + col;
1488
+ const srcIdx = srcRow * maskW + srcCol;
1489
+ data[row * cropW + col] = (rawMask[srcIdx] ?? 0) > maskThreshold ? 255 : 0;
1490
+ }
1491
+ }
1492
+ return { data, width: cropW, height: cropH };
1493
+ }
1494
+ function yoloSegPostprocess(segOutput, options) {
1495
+ const {
1496
+ detectionOutput,
1497
+ protoOutput,
1498
+ numClasses,
1499
+ numBoxes,
1500
+ numMaskCoeffs,
1501
+ maskHeight,
1502
+ maskWidth
1503
+ } = segOutput;
1504
+ const {
1505
+ confidence,
1506
+ iouThreshold,
1507
+ labels,
1508
+ scale,
1509
+ padX,
1510
+ padY,
1511
+ originalWidth,
1512
+ originalHeight,
1513
+ maskThreshold = 0.5
1514
+ } = options;
1515
+ const yoloInputSize = 640;
1516
+ const maskScale = maskHeight / yoloInputSize;
1517
+ const candidates = [];
1518
+ for (let i = 0; i < numBoxes; i++) {
1519
+ const cx = detectionOutput[0 * numBoxes + i] ?? 0;
1520
+ const cy = detectionOutput[1 * numBoxes + i] ?? 0;
1521
+ const w = detectionOutput[2 * numBoxes + i] ?? 0;
1522
+ const h = detectionOutput[3 * numBoxes + i] ?? 0;
1523
+ let bestScore = -Infinity;
1524
+ let bestClass = 0;
1525
+ for (let j = 0; j < numClasses; j++) {
1526
+ const score = detectionOutput[(4 + j) * numBoxes + i] ?? 0;
1527
+ if (score > bestScore) {
1528
+ bestScore = score;
1529
+ bestClass = j;
1530
+ }
1531
+ }
1532
+ if (bestScore < confidence) continue;
1533
+ const bbox = {
1534
+ x: cx - w / 2,
1535
+ y: cy - h / 2,
1536
+ w,
1537
+ h
1538
+ };
1539
+ const coeffs = new Float32Array(numMaskCoeffs);
1540
+ for (let k = 0; k < numMaskCoeffs; k++) {
1541
+ coeffs[k] = detectionOutput[(4 + numClasses + k) * numBoxes + i] ?? 0;
1542
+ }
1543
+ candidates.push({ bbox, score: bestScore, classIdx: bestClass, coeffs });
1544
+ }
1545
+ if (candidates.length === 0) return [];
1546
+ const keptIndices = nms(candidates, iouThreshold);
1547
+ return keptIndices.map((idx) => {
1548
+ const { bbox, score, classIdx, coeffs } = candidates[idx];
1549
+ const label = labels[classIdx] ?? String(classIdx);
1550
+ const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale));
1551
+ const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale));
1552
+ const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale));
1553
+ const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale));
1554
+ const finalBbox = { x, y, w: x2 - x, h: y2 - y };
1555
+ const rawMask = computeRawMask(coeffs, protoOutput, numMaskCoeffs, maskHeight, maskWidth);
1556
+ const { data: maskData, width: mW, height: mH } = cropAndThresholdMask(
1557
+ rawMask,
1558
+ maskHeight,
1559
+ maskWidth,
1560
+ bbox,
1561
+ maskThreshold,
1562
+ maskScale
1563
+ );
1564
+ return {
1565
+ class: label,
1566
+ originalClass: label,
1567
+ score,
1568
+ bbox: finalBbox,
1569
+ mask: maskData,
1570
+ maskWidth: mW,
1571
+ maskHeight: mH
1572
+ };
1573
+ });
1574
+ }
1575
+
1576
+ // src/addons/object-detection/index.ts
1577
+ function isSegModel(modelId) {
1578
+ return modelId.includes("-seg");
1579
+ }
1580
+ var ALL_DETECTION_MODELS = [
1581
+ ...OBJECT_DETECTION_MODELS,
1582
+ ...SEGMENTATION_MODELS
1583
+ ];
1584
+ function applyClassMap(detections, classMap) {
1585
+ return detections.filter((d) => classMap.mapping[d.class] !== void 0).map((d) => ({
1586
+ ...d,
1587
+ originalClass: d.class,
1588
+ class: classMap.mapping[d.class]
1589
+ }));
1590
+ }
1591
+ var ObjectDetectionAddon = class {
1592
+ id = "object-detection";
1593
+ slot = "detector";
1594
+ inputClasses = null;
1595
+ outputClasses = ["person", "vehicle", "animal"];
1596
+ slotPriority = 0;
1597
+ manifest = {
1598
+ id: "object-detection",
1599
+ name: "Object Detection",
1600
+ version: "0.1.0",
1601
+ description: "YOLO-based object detection \u2014 detects persons, vehicles, and animals",
1602
+ packageName: "@camstack/addon-vision",
1603
+ slot: "detector",
1604
+ inputClasses: void 0,
1605
+ outputClasses: ["person", "vehicle", "animal"],
1606
+ supportsCustomModels: true,
1607
+ mayRequirePython: false,
1608
+ defaultConfig: {
1609
+ modelId: "yolov8n",
1610
+ runtime: "auto",
1611
+ backend: "cpu",
1612
+ confidence: 0.5,
1613
+ iouThreshold: 0.45,
1614
+ classMapMode: "macro"
1615
+ }
1616
+ };
1617
+ engine;
1618
+ modelEntry;
1619
+ confidence = 0.5;
1620
+ iouThreshold = 0.45;
1621
+ classMapMode = "macro";
1622
+ async initialize(ctx) {
1623
+ const cfg = ctx.addonConfig;
1624
+ const modelId = cfg["modelId"] ?? "yolov8n";
1625
+ const runtime = cfg["runtime"] ?? "auto";
1626
+ const backend = cfg["backend"] ?? "cpu";
1627
+ this.confidence = cfg["confidence"] ?? 0.5;
1628
+ this.iouThreshold = cfg["iouThreshold"] ?? 0.45;
1629
+ this.classMapMode = cfg["classMapMode"] ?? "macro";
1630
+ const entry = ALL_DETECTION_MODELS.find((m) => m.id === modelId);
1631
+ if (!entry) {
1632
+ throw new Error(`ObjectDetectionAddon: unknown modelId "${modelId}"`);
1633
+ }
1634
+ this.modelEntry = entry;
1635
+ const resolved = await resolveEngine({
1636
+ runtime,
1637
+ backend,
1638
+ modelEntry: entry,
1639
+ modelsDir: ctx.locationPaths.models
1640
+ });
1641
+ this.engine = resolved.engine;
1642
+ }
1643
+ async detect(frame) {
1644
+ const start = Date.now();
1645
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
1646
+ const targetSize = Math.max(inputW, inputH);
1647
+ const lb = await letterbox(frame.data, targetSize);
1648
+ const numClasses = this.modelEntry.labels.length;
1649
+ const labels = this.modelEntry.labels.map((l) => l.id);
1650
+ const postprocessOpts = {
1651
+ confidence: this.confidence,
1652
+ iouThreshold: this.iouThreshold,
1653
+ labels,
1654
+ scale: lb.scale,
1655
+ padX: lb.padX,
1656
+ padY: lb.padY,
1657
+ originalWidth: lb.originalWidth,
1658
+ originalHeight: lb.originalHeight
1659
+ };
1660
+ let rawDetections;
1661
+ if (isSegModel(this.modelEntry.id)) {
1662
+ const outputs = await this.engine.runMultiOutput(lb.data, [1, 3, targetSize, targetSize]);
1663
+ const outputNames = Object.keys(outputs);
1664
+ if (outputNames.length < 2) {
1665
+ throw new Error(
1666
+ `ObjectDetectionAddon: seg model "${this.modelEntry.id}" returned ${outputNames.length} output(s); expected 2`
1667
+ );
1668
+ }
1669
+ const detectionOutput = outputs[outputNames[0]];
1670
+ const protoOutput = outputs[outputNames[1]];
1671
+ const numMaskCoeffs = 32;
1672
+ const numBoxes = detectionOutput.length / (4 + numClasses + numMaskCoeffs);
1673
+ const maskHeight = 160;
1674
+ const maskWidth = 160;
1675
+ rawDetections = yoloSegPostprocess(
1676
+ {
1677
+ detectionOutput,
1678
+ protoOutput,
1679
+ numClasses,
1680
+ numBoxes,
1681
+ numMaskCoeffs,
1682
+ maskHeight,
1683
+ maskWidth
1684
+ },
1685
+ postprocessOpts
1686
+ );
1687
+ } else {
1688
+ const output = await this.engine.run(lb.data, [1, 3, targetSize, targetSize]);
1689
+ const numBoxes = output.length / (4 + numClasses);
1690
+ rawDetections = yoloPostprocess(output, numClasses, numBoxes, postprocessOpts);
1691
+ }
1692
+ const detections = this.classMapMode === "all" ? rawDetections : applyClassMap(rawDetections, import_types9.COCO_TO_MACRO);
1693
+ return {
1694
+ detections,
1695
+ inferenceMs: Date.now() - start,
1696
+ modelId: this.modelEntry.id
1697
+ };
1698
+ }
1699
+ async shutdown() {
1700
+ await this.engine?.dispose();
1701
+ }
1702
+ getConfigSchema() {
1703
+ return {
1704
+ sections: [
1705
+ {
1706
+ id: "model",
1707
+ title: "Model",
1708
+ columns: 2,
1709
+ fields: [
1710
+ {
1711
+ key: "modelId",
1712
+ label: "Model",
1713
+ type: "model-selector",
1714
+ catalog: [...ALL_DETECTION_MODELS],
1715
+ allowCustom: true,
1716
+ allowConversion: true,
1717
+ acceptFormats: ["onnx", "coreml", "openvino", "tflite"],
1718
+ requiredMetadata: ["inputSize", "labels", "outputFormat"],
1719
+ outputFormatHint: "yolo"
1720
+ }
1721
+ ]
1722
+ },
1723
+ {
1724
+ id: "runtime",
1725
+ title: "Runtime",
1726
+ columns: 2,
1727
+ fields: [
1728
+ {
1729
+ key: "runtime",
1730
+ label: "Runtime",
1731
+ type: "select",
1732
+ options: [
1733
+ { value: "auto", label: "Auto (recommended)" },
1734
+ { value: "onnx", label: "ONNX Runtime" },
1735
+ { value: "coreml", label: "CoreML (Apple)" },
1736
+ { value: "openvino", label: "OpenVINO (Intel)" }
1737
+ ]
1738
+ },
1739
+ {
1740
+ key: "backend",
1741
+ label: "Backend",
1742
+ type: "select",
1743
+ dependsOn: { runtime: "onnx" },
1744
+ options: [
1745
+ { value: "cpu", label: "CPU" },
1746
+ { value: "coreml", label: "CoreML" },
1747
+ { value: "cuda", label: "CUDA (NVIDIA)" },
1748
+ { value: "tensorrt", label: "TensorRT (NVIDIA)" }
1749
+ ]
1750
+ }
1751
+ ]
1752
+ },
1753
+ {
1754
+ id: "thresholds",
1755
+ title: "Detection Thresholds",
1756
+ columns: 2,
1757
+ fields: [
1758
+ {
1759
+ key: "confidence",
1760
+ label: "Confidence Threshold",
1761
+ type: "slider",
1762
+ min: 0.1,
1763
+ max: 1,
1764
+ step: 0.05,
1765
+ default: 0.5
1766
+ },
1767
+ {
1768
+ key: "iouThreshold",
1769
+ label: "IoU Threshold (NMS)",
1770
+ type: "slider",
1771
+ min: 0.1,
1772
+ max: 1,
1773
+ step: 0.05,
1774
+ default: 0.45
1775
+ }
1776
+ ]
1777
+ },
1778
+ {
1779
+ id: "classmap",
1780
+ title: "Class Mapping",
1781
+ columns: 1,
1782
+ fields: [
1783
+ {
1784
+ key: "classMapMode",
1785
+ label: "Output classes",
1786
+ type: "select",
1787
+ options: [
1788
+ { value: "macro", label: "Macro (person / vehicle / animal)" },
1789
+ { value: "all", label: "All COCO classes (80)" }
1790
+ ]
1791
+ }
1792
+ ]
1793
+ }
1794
+ ]
1795
+ };
1796
+ }
1797
+ getClassMap() {
1798
+ return import_types9.COCO_TO_MACRO;
1799
+ }
1800
+ getModelCatalog() {
1801
+ return [...ALL_DETECTION_MODELS];
1802
+ }
1803
+ getAvailableModels() {
1804
+ return [];
1805
+ }
1806
+ getActiveLabels() {
1807
+ return this.classMapMode === "all" ? import_types9.COCO_80_LABELS : import_types9.MACRO_LABELS;
1808
+ }
1809
+ async probe() {
1810
+ return {
1811
+ available: true,
1812
+ runtime: this.engine?.runtime ?? "onnx",
1813
+ device: this.engine?.device ?? "cpu",
1814
+ capabilities: ["fp32"]
1815
+ };
1816
+ }
1817
+ };
1818
+
1819
+ // src/addons/motion-detection/index.ts
1820
+ var MOTION_LABEL = { id: "motion", name: "Motion" };
1821
+ var MOTION_LABELS = [MOTION_LABEL];
1822
+ var EMPTY_CLASS_MAP = { mapping: {}, preserveOriginal: true };
1823
+ var MotionDetectionAddon = class {
1824
+ id = "motion-detection";
1825
+ slot = "detector";
1826
+ inputClasses = null;
1827
+ outputClasses = ["motion"];
1828
+ slotPriority = 10;
1829
+ // runs first — feeds other detectors
1830
+ manifest = {
1831
+ id: "motion-detection",
1832
+ name: "Motion Detection",
1833
+ version: "0.1.0",
1834
+ description: "Frame-differencing motion detector \u2014 no inference engine required",
1835
+ packageName: "@camstack/addon-vision",
1836
+ slot: "detector",
1837
+ inputClasses: void 0,
1838
+ outputClasses: ["motion"],
1839
+ supportsCustomModels: false,
1840
+ mayRequirePython: false,
1841
+ defaultConfig: {
1842
+ threshold: 25,
1843
+ minArea: 500
1844
+ }
1845
+ };
1846
+ previousGray = null;
1847
+ previousWidth = 0;
1848
+ previousHeight = 0;
1849
+ threshold = 25;
1850
+ minArea = 500;
1851
+ async initialize(ctx) {
1852
+ const cfg = ctx.addonConfig;
1853
+ this.threshold = cfg["threshold"] ?? 25;
1854
+ this.minArea = cfg["minArea"] ?? 500;
1855
+ }
1856
+ async detect(frame) {
1857
+ const start = Date.now();
1858
+ const { data, width, height } = await jpegToRgb(frame.data);
1859
+ const currentGray = rgbToGrayscale(data, width, height);
1860
+ if (!this.previousGray || this.previousWidth !== width || this.previousHeight !== height) {
1861
+ this.previousGray = currentGray;
1862
+ this.previousWidth = width;
1863
+ this.previousHeight = height;
1864
+ return { detections: [], inferenceMs: Date.now() - start, modelId: "frame-diff" };
1865
+ }
1866
+ const regions = detectMotion(
1867
+ currentGray,
1868
+ this.previousGray,
1869
+ width,
1870
+ height,
1871
+ this.threshold,
1872
+ this.minArea
1873
+ );
1874
+ this.previousGray = currentGray;
1875
+ const detections = regions.map((r) => ({
1876
+ class: "motion",
1877
+ originalClass: "motion",
1878
+ score: Math.min(1, r.intensity / 255),
1879
+ bbox: r.bbox
1880
+ }));
1881
+ return {
1882
+ detections,
1883
+ inferenceMs: Date.now() - start,
1884
+ modelId: "frame-diff"
1885
+ };
1886
+ }
1887
+ async shutdown() {
1888
+ this.previousGray = null;
1889
+ }
1890
+ getConfigSchema() {
1891
+ return {
1892
+ sections: [
1893
+ {
1894
+ id: "motion",
1895
+ title: "Motion Detection",
1896
+ columns: 2,
1897
+ fields: [
1898
+ {
1899
+ key: "threshold",
1900
+ label: "Pixel Difference Threshold",
1901
+ description: "Minimum per-pixel intensity change to count as motion (0-255)",
1902
+ type: "slider",
1903
+ min: 5,
1904
+ max: 100,
1905
+ step: 5,
1906
+ default: 25
1907
+ },
1908
+ {
1909
+ key: "minArea",
1910
+ label: "Minimum Region Area (px)",
1911
+ description: "Minimum number of changed pixels to report a motion region",
1912
+ type: "number",
1913
+ min: 50,
1914
+ max: 1e4
1915
+ }
1916
+ ]
1917
+ }
1918
+ ]
1919
+ };
1920
+ }
1921
+ getClassMap() {
1922
+ return EMPTY_CLASS_MAP;
1923
+ }
1924
+ getModelCatalog() {
1925
+ return [];
1926
+ }
1927
+ getAvailableModels() {
1928
+ return [];
1929
+ }
1930
+ getActiveLabels() {
1931
+ return MOTION_LABELS;
1932
+ }
1933
+ async probe() {
1934
+ return {
1935
+ available: true,
1936
+ runtime: "onnx",
1937
+ // no inference; satisfies the type (any runtime works)
1938
+ device: "cpu",
1939
+ capabilities: ["fp32"]
1940
+ };
1941
+ }
1942
+ };
1943
+
1944
+ // src/addons/face-detection/index.ts
1945
+ var FACE_LABEL = { id: "face", name: "Face" };
1946
+ var FACE_LABELS2 = [FACE_LABEL];
1947
+ var FACE_CLASS_MAP = { mapping: {}, preserveOriginal: true };
1948
+ var FaceDetectionAddon = class {
1949
+ id = "face-detection";
1950
+ slot = "cropper";
1951
+ inputClasses = ["person"];
1952
+ outputClasses = ["face"];
1953
+ slotPriority = 0;
1954
+ manifest = {
1955
+ id: "face-detection",
1956
+ name: "Face Detection",
1957
+ version: "0.1.0",
1958
+ description: "SCRFD-based face detector \u2014 crops face regions from person detections",
1959
+ packageName: "@camstack/addon-vision",
1960
+ slot: "cropper",
1961
+ inputClasses: ["person"],
1962
+ outputClasses: ["face"],
1963
+ supportsCustomModels: false,
1964
+ mayRequirePython: false,
1965
+ defaultConfig: {
1966
+ modelId: "scrfd-500m",
1967
+ runtime: "auto",
1968
+ backend: "cpu",
1969
+ confidence: 0.5
1970
+ }
1971
+ };
1972
+ engine;
1973
+ modelEntry;
1974
+ confidence = 0.5;
1975
+ async initialize(ctx) {
1976
+ const cfg = ctx.addonConfig;
1977
+ const modelId = cfg["modelId"] ?? "scrfd-500m";
1978
+ const runtime = cfg["runtime"] ?? "auto";
1979
+ const backend = cfg["backend"] ?? "cpu";
1980
+ this.confidence = cfg["confidence"] ?? 0.5;
1981
+ const entry = FACE_DETECTION_MODELS.find((m) => m.id === modelId);
1982
+ if (!entry) {
1983
+ throw new Error(`FaceDetectionAddon: unknown modelId "${modelId}"`);
1984
+ }
1985
+ this.modelEntry = entry;
1986
+ const resolved = await resolveEngine({
1987
+ runtime,
1988
+ backend,
1989
+ modelEntry: entry,
1990
+ modelsDir: ctx.locationPaths.models
1991
+ });
1992
+ this.engine = resolved.engine;
1993
+ }
1994
+ async crop(input) {
1995
+ const start = Date.now();
1996
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
1997
+ const targetSize = Math.max(inputW, inputH);
1998
+ const personCrop = await cropRegion(input.frame.data, input.roi);
1999
+ const lb = await letterbox(personCrop, targetSize);
2000
+ const engineWithMulti = this.engine;
2001
+ let outputs;
2002
+ if (typeof engineWithMulti.runMultiOutput === "function") {
2003
+ outputs = await engineWithMulti.runMultiOutput(lb.data, [1, 3, targetSize, targetSize]);
2004
+ } else {
2005
+ const single = await this.engine.run(lb.data, [1, 3, targetSize, targetSize]);
2006
+ outputs = { output0: single };
2007
+ }
2008
+ const crops = scrfdPostprocess(
2009
+ outputs,
2010
+ this.confidence,
2011
+ targetSize,
2012
+ lb.originalWidth,
2013
+ lb.originalHeight
2014
+ );
2015
+ return {
2016
+ crops,
2017
+ inferenceMs: Date.now() - start,
2018
+ modelId: this.modelEntry.id
2019
+ };
2020
+ }
2021
+ async shutdown() {
2022
+ await this.engine?.dispose();
2023
+ }
2024
+ getConfigSchema() {
2025
+ return {
2026
+ sections: [
2027
+ {
2028
+ id: "model",
2029
+ title: "Model",
2030
+ columns: 1,
2031
+ fields: [
2032
+ {
2033
+ key: "modelId",
2034
+ label: "Model",
2035
+ type: "model-selector",
2036
+ catalog: [...FACE_DETECTION_MODELS],
2037
+ allowCustom: false,
2038
+ allowConversion: false,
2039
+ acceptFormats: ["onnx", "coreml", "openvino"],
2040
+ requiredMetadata: ["inputSize", "labels", "outputFormat"],
2041
+ outputFormatHint: "ssd"
2042
+ }
2043
+ ]
2044
+ },
2045
+ {
2046
+ id: "thresholds",
2047
+ title: "Detection Thresholds",
2048
+ columns: 1,
2049
+ fields: [
2050
+ {
2051
+ key: "confidence",
2052
+ label: "Confidence Threshold",
2053
+ type: "slider",
2054
+ min: 0.1,
2055
+ max: 1,
2056
+ step: 0.05,
2057
+ default: 0.5
2058
+ }
2059
+ ]
2060
+ }
2061
+ ]
2062
+ };
2063
+ }
2064
+ getClassMap() {
2065
+ return FACE_CLASS_MAP;
2066
+ }
2067
+ getModelCatalog() {
2068
+ return [...FACE_DETECTION_MODELS];
2069
+ }
2070
+ getAvailableModels() {
2071
+ return [];
2072
+ }
2073
+ getActiveLabels() {
2074
+ return FACE_LABELS2;
2075
+ }
2076
+ async probe() {
2077
+ return {
2078
+ available: true,
2079
+ runtime: this.engine?.runtime ?? "onnx",
2080
+ device: this.engine?.device ?? "cpu",
2081
+ capabilities: ["fp32"]
2082
+ };
2083
+ }
2084
+ };
2085
+
2086
+ // src/addons/face-recognition/index.ts
2087
+ var IDENTITY_LABEL = { id: "identity", name: "Identity" };
2088
+ var IDENTITY_LABELS = [IDENTITY_LABEL];
2089
+ var FACE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2090
+ var REQUIRED_STEPS = [
2091
+ { slot: "cropper", outputClasses: ["face"], description: "Requires a face detector" }
2092
+ ];
2093
+ var FaceRecognitionAddon = class {
2094
+ id = "face-recognition";
2095
+ slot = "classifier";
2096
+ inputClasses = ["face"];
2097
+ outputClasses = ["identity:*"];
2098
+ slotPriority = 0;
2099
+ requiredSteps = REQUIRED_STEPS;
2100
+ manifest = {
2101
+ id: "face-recognition",
2102
+ name: "Face Recognition",
2103
+ version: "0.1.0",
2104
+ description: "ArcFace-based face recognition \u2014 produces 512-d identity embeddings",
2105
+ packageName: "@camstack/addon-vision",
2106
+ slot: "classifier",
2107
+ inputClasses: ["face"],
2108
+ outputClasses: ["identity:*"],
2109
+ requiredSteps: REQUIRED_STEPS,
2110
+ supportsCustomModels: false,
2111
+ mayRequirePython: false,
2112
+ defaultConfig: {
2113
+ modelId: "arcface-r100",
2114
+ runtime: "auto",
2115
+ backend: "cpu"
2116
+ }
2117
+ };
2118
+ engine;
2119
+ modelEntry;
2120
+ async initialize(ctx) {
2121
+ const cfg = ctx.addonConfig;
2122
+ const modelId = cfg["modelId"] ?? "arcface-r100";
2123
+ const runtime = cfg["runtime"] ?? "auto";
2124
+ const backend = cfg["backend"] ?? "cpu";
2125
+ const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId);
2126
+ if (!entry) {
2127
+ throw new Error(`FaceRecognitionAddon: unknown modelId "${modelId}"`);
2128
+ }
2129
+ this.modelEntry = entry;
2130
+ const resolved = await resolveEngine({
2131
+ runtime,
2132
+ backend,
2133
+ modelEntry: entry,
2134
+ modelsDir: ctx.locationPaths.models
2135
+ });
2136
+ this.engine = resolved.engine;
2137
+ }
2138
+ async classify(input) {
2139
+ const start = Date.now();
2140
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
2141
+ const faceCrop = await cropRegion(input.frame.data, input.roi);
2142
+ const layout = this.modelEntry.inputLayout ?? "nhwc";
2143
+ const normalization = this.modelEntry.inputNormalization ?? "zero-one";
2144
+ const normalized = await resizeAndNormalize(faceCrop, inputW, inputH, normalization, layout);
2145
+ const rawEmbedding = await this.engine.run(normalized, [1, inputH, inputW, 3]);
2146
+ const embedding = l2Normalize(rawEmbedding);
2147
+ return {
2148
+ classifications: [
2149
+ {
2150
+ class: "identity",
2151
+ score: 1,
2152
+ embedding
2153
+ }
2154
+ ],
2155
+ inferenceMs: Date.now() - start,
2156
+ modelId: this.modelEntry.id
2157
+ };
2158
+ }
2159
+ async shutdown() {
2160
+ await this.engine?.dispose();
2161
+ }
2162
+ getConfigSchema() {
2163
+ return {
2164
+ sections: [
2165
+ {
2166
+ id: "model",
2167
+ title: "Model",
2168
+ columns: 1,
2169
+ fields: [
2170
+ {
2171
+ key: "modelId",
2172
+ label: "Model",
2173
+ type: "model-selector",
2174
+ catalog: [...FACE_RECOGNITION_MODELS],
2175
+ allowCustom: false,
2176
+ allowConversion: false,
2177
+ acceptFormats: ["onnx", "coreml", "openvino"],
2178
+ requiredMetadata: ["inputSize", "labels", "outputFormat"],
2179
+ outputFormatHint: "embedding"
2180
+ }
2181
+ ]
2182
+ },
2183
+ {
2184
+ id: "runtime",
2185
+ title: "Runtime",
2186
+ columns: 2,
2187
+ fields: [
2188
+ {
2189
+ key: "runtime",
2190
+ label: "Runtime",
2191
+ type: "select",
2192
+ options: [
2193
+ { value: "auto", label: "Auto (recommended)" },
2194
+ { value: "onnx", label: "ONNX Runtime" },
2195
+ { value: "coreml", label: "CoreML (Apple)" }
2196
+ ]
2197
+ },
2198
+ {
2199
+ key: "backend",
2200
+ label: "Backend",
2201
+ type: "select",
2202
+ dependsOn: { runtime: "onnx" },
2203
+ options: [
2204
+ { value: "cpu", label: "CPU" },
2205
+ { value: "coreml", label: "CoreML" },
2206
+ { value: "cuda", label: "CUDA (NVIDIA)" }
2207
+ ]
2208
+ }
2209
+ ]
2210
+ }
2211
+ ]
2212
+ };
2213
+ }
2214
+ getClassMap() {
2215
+ return FACE_REC_CLASS_MAP;
2216
+ }
2217
+ getModelCatalog() {
2218
+ return [...FACE_RECOGNITION_MODELS];
2219
+ }
2220
+ getAvailableModels() {
2221
+ return [];
2222
+ }
2223
+ getActiveLabels() {
2224
+ return IDENTITY_LABELS;
2225
+ }
2226
+ async probe() {
2227
+ return {
2228
+ available: true,
2229
+ runtime: this.engine?.runtime ?? "onnx",
2230
+ device: this.engine?.device ?? "cpu",
2231
+ capabilities: ["fp32"]
2232
+ };
2233
+ }
2234
+ };
2235
+
2236
+ // src/addons/plate-detection/index.ts
2237
+ var PLATE_LABEL = { id: "plate", name: "License Plate" };
2238
+ var PLATE_LABELS2 = [PLATE_LABEL];
2239
+ var PLATE_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2240
+ var PlateDetectionAddon = class {
2241
+ id = "plate-detection";
2242
+ slot = "cropper";
2243
+ inputClasses = ["vehicle"];
2244
+ outputClasses = ["plate"];
2245
+ slotPriority = 0;
2246
+ manifest = {
2247
+ id: "plate-detection",
2248
+ name: "License Plate Detection",
2249
+ version: "0.1.0",
2250
+ description: "YOLO-based license plate detector \u2014 crops plate regions from vehicle detections",
2251
+ packageName: "@camstack/addon-vision",
2252
+ slot: "cropper",
2253
+ inputClasses: ["vehicle"],
2254
+ outputClasses: ["plate"],
2255
+ supportsCustomModels: false,
2256
+ mayRequirePython: false,
2257
+ defaultConfig: {
2258
+ modelId: "yolov8n-plate",
2259
+ runtime: "auto",
2260
+ backend: "cpu",
2261
+ confidence: 0.5,
2262
+ iouThreshold: 0.45
2263
+ }
2264
+ };
2265
+ engine;
2266
+ modelEntry;
2267
+ confidence = 0.5;
2268
+ iouThreshold = 0.45;
2269
+ async initialize(ctx) {
2270
+ const cfg = ctx.addonConfig;
2271
+ const modelId = cfg["modelId"] ?? "yolov8n-plate";
2272
+ const runtime = cfg["runtime"] ?? "auto";
2273
+ const backend = cfg["backend"] ?? "cpu";
2274
+ this.confidence = cfg["confidence"] ?? 0.5;
2275
+ this.iouThreshold = cfg["iouThreshold"] ?? 0.45;
2276
+ const entry = PLATE_DETECTION_MODELS.find((m) => m.id === modelId);
2277
+ if (!entry) {
2278
+ throw new Error(`PlateDetectionAddon: unknown modelId "${modelId}"`);
2279
+ }
2280
+ this.modelEntry = entry;
2281
+ const resolved = await resolveEngine({
2282
+ runtime,
2283
+ backend,
2284
+ modelEntry: entry,
2285
+ modelsDir: ctx.locationPaths.models
2286
+ });
2287
+ this.engine = resolved.engine;
2288
+ }
2289
+ async crop(input) {
2290
+ const start = Date.now();
2291
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
2292
+ const targetSize = Math.max(inputW, inputH);
2293
+ const vehicleCrop = await cropRegion(input.frame.data, input.roi);
2294
+ const lb = await letterbox(vehicleCrop, targetSize);
2295
+ const output = await this.engine.run(lb.data, [1, 3, targetSize, targetSize]);
2296
+ const numClasses = this.modelEntry.labels.length;
2297
+ const numBoxes = output.length / (4 + numClasses);
2298
+ const labels = this.modelEntry.labels.map((l) => l.id);
2299
+ const plates = yoloPostprocess(output, numClasses, numBoxes, {
2300
+ confidence: this.confidence,
2301
+ iouThreshold: this.iouThreshold,
2302
+ labels,
2303
+ scale: lb.scale,
2304
+ padX: lb.padX,
2305
+ padY: lb.padY,
2306
+ originalWidth: lb.originalWidth,
2307
+ originalHeight: lb.originalHeight
2308
+ });
2309
+ const crops = plates.map((p) => ({ ...p, class: "plate", originalClass: p.originalClass }));
2310
+ return {
2311
+ crops,
2312
+ inferenceMs: Date.now() - start,
2313
+ modelId: this.modelEntry.id
2314
+ };
2315
+ }
2316
+ async shutdown() {
2317
+ await this.engine?.dispose();
2318
+ }
2319
+ getConfigSchema() {
2320
+ return {
2321
+ sections: [
2322
+ {
2323
+ id: "thresholds",
2324
+ title: "Detection Thresholds",
2325
+ columns: 2,
2326
+ fields: [
2327
+ {
2328
+ key: "confidence",
2329
+ label: "Confidence Threshold",
2330
+ type: "slider",
2331
+ min: 0.1,
2332
+ max: 1,
2333
+ step: 0.05,
2334
+ default: 0.5
2335
+ },
2336
+ {
2337
+ key: "iouThreshold",
2338
+ label: "IoU Threshold (NMS)",
2339
+ type: "slider",
2340
+ min: 0.1,
2341
+ max: 1,
2342
+ step: 0.05,
2343
+ default: 0.45
2344
+ }
2345
+ ]
2346
+ }
2347
+ ]
2348
+ };
2349
+ }
2350
+ getClassMap() {
2351
+ return PLATE_CLASS_MAP;
2352
+ }
2353
+ getModelCatalog() {
2354
+ return [...PLATE_DETECTION_MODELS];
2355
+ }
2356
+ getAvailableModels() {
2357
+ return [];
2358
+ }
2359
+ getActiveLabels() {
2360
+ return PLATE_LABELS2;
2361
+ }
2362
+ async probe() {
2363
+ return {
2364
+ available: true,
2365
+ runtime: this.engine?.runtime ?? "onnx",
2366
+ device: this.engine?.device ?? "cpu",
2367
+ capabilities: ["fp32"]
2368
+ };
2369
+ }
2370
+ };
2371
+
2372
+ // src/addons/plate-recognition/index.ts
2373
+ var fs2 = __toESM(require("fs"));
2374
+ var path3 = __toESM(require("path"));
2375
+ var PLATE_TEXT_LABEL = { id: "plate-text", name: "Plate Text" };
2376
+ var PLATE_TEXT_LABELS2 = [PLATE_TEXT_LABEL];
2377
+ var PLATE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2378
+ function loadCharset(modelsDir, modelId) {
2379
+ const dictNames = [
2380
+ `camstack-${modelId}-dict.txt`,
2381
+ `camstack-paddleocr-latin-dict.txt`,
2382
+ `camstack-paddleocr-en-dict.txt`
2383
+ ];
2384
+ for (const name of dictNames) {
2385
+ const dictPath = path3.join(modelsDir, name);
2386
+ if (fs2.existsSync(dictPath)) {
2387
+ const lines = fs2.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
2388
+ return ["", ...lines, " "];
2389
+ }
2390
+ }
2391
+ throw new Error(`PlateRecognitionAddon: dict.txt not found in ${modelsDir}`);
2392
+ }
2393
+ var CHARSET = [];
2394
+ var REQUIRED_STEPS2 = [
2395
+ { slot: "cropper", outputClasses: ["plate"], description: "Requires a plate detector" }
2396
+ ];
2397
+ var PlateRecognitionAddon = class {
2398
+ id = "plate-recognition";
2399
+ slot = "classifier";
2400
+ inputClasses = ["plate"];
2401
+ outputClasses = ["plate-text:*"];
2402
+ slotPriority = 0;
2403
+ requiredSteps = REQUIRED_STEPS2;
2404
+ manifest = {
2405
+ id: "plate-recognition",
2406
+ name: "License Plate Recognition (OCR)",
2407
+ version: "0.1.0",
2408
+ description: "PaddleOCR-based license plate text recognition",
2409
+ packageName: "@camstack/addon-vision",
2410
+ slot: "classifier",
2411
+ inputClasses: ["plate"],
2412
+ outputClasses: ["plate-text:*"],
2413
+ requiredSteps: REQUIRED_STEPS2,
2414
+ supportsCustomModels: false,
2415
+ mayRequirePython: false,
2416
+ defaultConfig: {
2417
+ modelId: "paddleocr-latin",
2418
+ runtime: "auto",
2419
+ backend: "cpu",
2420
+ minConfidence: 0.5
2421
+ }
2422
+ };
2423
+ engine;
2424
+ modelEntry;
2425
+ minConfidence = 0.5;
2426
+ async initialize(ctx) {
2427
+ const cfg = ctx.addonConfig;
2428
+ const modelId = cfg["modelId"] ?? "paddleocr-latin";
2429
+ const runtime = cfg["runtime"] ?? "auto";
2430
+ const backend = cfg["backend"] ?? "cpu";
2431
+ this.minConfidence = cfg["minConfidence"] ?? 0.5;
2432
+ const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
2433
+ if (!entry) {
2434
+ throw new Error(`PlateRecognitionAddon: unknown modelId "${modelId}"`);
2435
+ }
2436
+ this.modelEntry = entry;
2437
+ CHARSET = loadCharset(ctx.locationPaths.models, modelId);
2438
+ const resolved = await resolveEngine({
2439
+ runtime,
2440
+ backend,
2441
+ modelEntry: entry,
2442
+ modelsDir: ctx.locationPaths.models
2443
+ });
2444
+ this.engine = resolved.engine;
2445
+ }
2446
+ async classify(input) {
2447
+ const start = Date.now();
2448
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
2449
+ const plateCrop = await cropRegion(input.frame.data, input.roi);
2450
+ const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, "zero-one", "nchw");
2451
+ const output = await this.engine.run(normalized, [1, 3, inputH, inputW]);
2452
+ const numChars = CHARSET.length;
2453
+ const seqLen = output.length / numChars;
2454
+ const { text, confidence } = ctcDecode(output, seqLen, numChars, CHARSET);
2455
+ if (confidence < this.minConfidence || text.trim().length === 0) {
2456
+ return {
2457
+ classifications: [],
2458
+ inferenceMs: Date.now() - start,
2459
+ modelId: this.modelEntry.id
2460
+ };
2461
+ }
2462
+ return {
2463
+ classifications: [
2464
+ {
2465
+ class: "plate-text",
2466
+ score: confidence,
2467
+ text
2468
+ }
2469
+ ],
2470
+ inferenceMs: Date.now() - start,
2471
+ modelId: this.modelEntry.id
2472
+ };
2473
+ }
2474
+ async shutdown() {
2475
+ await this.engine?.dispose();
2476
+ }
2477
+ getConfigSchema() {
2478
+ return {
2479
+ sections: [
2480
+ {
2481
+ id: "model",
2482
+ title: "Model",
2483
+ columns: 1,
2484
+ fields: [
2485
+ {
2486
+ key: "modelId",
2487
+ label: "Model",
2488
+ type: "model-selector",
2489
+ catalog: [...PLATE_RECOGNITION_MODELS],
2490
+ allowCustom: false,
2491
+ allowConversion: false,
2492
+ acceptFormats: ["onnx", "openvino"],
2493
+ requiredMetadata: ["inputSize", "labels", "outputFormat"],
2494
+ outputFormatHint: "ocr"
2495
+ }
2496
+ ]
2497
+ },
2498
+ {
2499
+ id: "thresholds",
2500
+ title: "Recognition Settings",
2501
+ columns: 1,
2502
+ fields: [
2503
+ {
2504
+ key: "minConfidence",
2505
+ label: "Minimum Confidence",
2506
+ type: "slider",
2507
+ min: 0.1,
2508
+ max: 1,
2509
+ step: 0.05,
2510
+ default: 0.5
2511
+ }
2512
+ ]
2513
+ }
2514
+ ]
2515
+ };
2516
+ }
2517
+ getClassMap() {
2518
+ return PLATE_REC_CLASS_MAP;
2519
+ }
2520
+ getModelCatalog() {
2521
+ return [...PLATE_RECOGNITION_MODELS];
2522
+ }
2523
+ getAvailableModels() {
2524
+ return [];
2525
+ }
2526
+ getActiveLabels() {
2527
+ return PLATE_TEXT_LABELS2;
2528
+ }
2529
+ async probe() {
2530
+ return {
2531
+ available: true,
2532
+ runtime: this.engine?.runtime ?? "onnx",
2533
+ device: this.engine?.device ?? "cpu",
2534
+ capabilities: ["fp32"]
2535
+ };
2536
+ }
2537
+ };
2538
+
2539
+ // src/addons/audio-classification/index.ts
2540
+ var YAMNET_NUM_CLASSES = 521;
2541
+ var AUDIO_EVENT_LABEL = { id: "audio-event", name: "Audio Event" };
2542
+ var AUDIO_LABELS2 = [AUDIO_EVENT_LABEL];
2543
+ var AUDIO_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2544
+ var AudioClassificationAddon = class {
2545
+ id = "audio-classification";
2546
+ slot = "classifier";
2547
+ inputClasses = null;
2548
+ outputClasses = ["audio-event:*"];
2549
+ slotPriority = 0;
2550
+ manifest = {
2551
+ id: "audio-classification",
2552
+ name: "Audio Classification",
2553
+ version: "0.1.0",
2554
+ description: "YAMNet-based audio event classification from audio waveform",
2555
+ packageName: "@camstack/addon-vision",
2556
+ slot: "classifier",
2557
+ inputClasses: void 0,
2558
+ outputClasses: ["audio-event:*"],
2559
+ supportsCustomModels: false,
2560
+ mayRequirePython: false,
2561
+ defaultConfig: {
2562
+ modelId: "yamnet",
2563
+ runtime: "auto",
2564
+ backend: "cpu",
2565
+ minScore: 0.3
2566
+ }
2567
+ };
2568
+ engine;
2569
+ modelEntry;
2570
+ minScore = 0.3;
2571
+ async initialize(ctx) {
2572
+ const cfg = ctx.addonConfig;
2573
+ const modelId = cfg["modelId"] ?? "yamnet";
2574
+ const runtime = cfg["runtime"] ?? "auto";
2575
+ const backend = cfg["backend"] ?? "cpu";
2576
+ this.minScore = cfg["minScore"] ?? 0.3;
2577
+ const entry = AUDIO_CLASSIFICATION_MODELS.find((m) => m.id === modelId);
2578
+ if (!entry) {
2579
+ throw new Error(`AudioClassificationAddon: unknown modelId "${modelId}"`);
2580
+ }
2581
+ this.modelEntry = entry;
2582
+ const resolved = await resolveEngine({
2583
+ runtime,
2584
+ backend,
2585
+ modelEntry: entry,
2586
+ modelsDir: ctx.locationPaths.models
2587
+ });
2588
+ this.engine = resolved.engine;
2589
+ }
2590
+ /**
2591
+ * classify() receives a CropInput but internally treats input.frame.data as raw audio context.
2592
+ * For audio, the actual audio chunk data should be stored in frame.data as a Float32Array
2593
+ * serialized into a Buffer (little-endian float32 samples at 16 kHz).
2594
+ *
2595
+ * The CropInput.roi is not used for audio — it is ignored.
2596
+ */
2597
+ async classify(input) {
2598
+ const start = Date.now();
2599
+ const buf = input.frame.data;
2600
+ const numSamples = Math.floor(buf.length / 4);
2601
+ const audioData = new Float32Array(numSamples);
2602
+ for (let i = 0; i < numSamples; i++) {
2603
+ audioData[i] = buf.readFloatLE(i * 4);
2604
+ }
2605
+ const output = await this.engine.run(audioData, [numSamples]);
2606
+ const numFrames = output.length / YAMNET_NUM_CLASSES;
2607
+ const classNames = this.modelEntry.labels.map((l) => l.id);
2608
+ while (classNames.length < YAMNET_NUM_CLASSES) {
2609
+ classNames.push(`class_${classNames.length}`);
2610
+ }
2611
+ const results = yamnetPostprocess(
2612
+ output,
2613
+ Math.round(numFrames),
2614
+ YAMNET_NUM_CLASSES,
2615
+ classNames,
2616
+ this.minScore
2617
+ );
2618
+ const classifications = results.map((r) => ({
2619
+ class: `audio-event:${r.className}`,
2620
+ score: r.score
2621
+ }));
2622
+ return {
2623
+ classifications,
2624
+ inferenceMs: Date.now() - start,
2625
+ modelId: this.modelEntry.id
2626
+ };
2627
+ }
2628
+ async shutdown() {
2629
+ await this.engine?.dispose();
2630
+ }
2631
+ getConfigSchema() {
2632
+ return {
2633
+ sections: [
2634
+ {
2635
+ id: "runtime",
2636
+ title: "Runtime",
2637
+ columns: 2,
2638
+ fields: [
2639
+ {
2640
+ key: "runtime",
2641
+ label: "Runtime",
2642
+ type: "select",
2643
+ options: [
2644
+ { value: "auto", label: "Auto (recommended)" },
2645
+ { value: "onnx", label: "ONNX Runtime" },
2646
+ { value: "openvino", label: "OpenVINO (Intel)" }
2647
+ ]
2648
+ },
2649
+ {
2650
+ key: "backend",
2651
+ label: "Backend",
2652
+ type: "select",
2653
+ dependsOn: { runtime: "onnx" },
2654
+ options: [
2655
+ { value: "cpu", label: "CPU" },
2656
+ { value: "cuda", label: "CUDA (NVIDIA)" }
2657
+ ]
2658
+ }
2659
+ ]
2660
+ },
2661
+ {
2662
+ id: "thresholds",
2663
+ title: "Classification Settings",
2664
+ columns: 1,
2665
+ fields: [
2666
+ {
2667
+ key: "minScore",
2668
+ label: "Minimum Score",
2669
+ type: "slider",
2670
+ min: 0.05,
2671
+ max: 1,
2672
+ step: 0.05,
2673
+ default: 0.3
2674
+ }
2675
+ ]
2676
+ }
2677
+ ]
2678
+ };
2679
+ }
2680
+ getClassMap() {
2681
+ return AUDIO_CLASS_MAP;
2682
+ }
2683
+ getModelCatalog() {
2684
+ return [...AUDIO_CLASSIFICATION_MODELS];
2685
+ }
2686
+ getAvailableModels() {
2687
+ return [];
2688
+ }
2689
+ getActiveLabels() {
2690
+ return AUDIO_LABELS2;
2691
+ }
2692
+ async probe() {
2693
+ return {
2694
+ available: true,
2695
+ runtime: this.engine?.runtime ?? "onnx",
2696
+ device: this.engine?.device ?? "cpu",
2697
+ capabilities: ["fp32"]
2698
+ };
2699
+ }
2700
+ };
2701
+
2702
+ // src/addons/camera-native-detection/index.ts
2703
+ var NATIVE_LABELS = [
2704
+ { id: "person", name: "Person" },
2705
+ { id: "vehicle", name: "Vehicle" },
2706
+ { id: "motion", name: "Motion" },
2707
+ { id: "face", name: "Face" }
2708
+ ];
2709
+ var NATIVE_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2710
+ var CameraNativeDetectionAddon = class {
2711
+ id = "camera-native-detection";
2712
+ slot = "detector";
2713
+ inputClasses = null;
2714
+ outputClasses = ["person", "vehicle", "motion", "face"];
2715
+ slotPriority = 5;
2716
+ manifest = {
2717
+ id: "camera-native-detection",
2718
+ name: "Camera Native Detection",
2719
+ version: "0.1.0",
2720
+ description: "Passthrough adapter for camera-native events (Frigate, Scrypted, ONVIF) \u2014 no inference engine",
2721
+ packageName: "@camstack/addon-vision",
2722
+ slot: "detector",
2723
+ inputClasses: void 0,
2724
+ outputClasses: ["person", "vehicle", "motion", "face"],
2725
+ supportsCustomModels: false,
2726
+ mayRequirePython: false,
2727
+ defaultConfig: {}
2728
+ };
2729
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2730
+ async initialize(_ctx) {
2731
+ }
2732
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2733
+ async detect(_frame) {
2734
+ return {
2735
+ detections: [],
2736
+ inferenceMs: 0,
2737
+ modelId: "camera-native"
2738
+ };
2739
+ }
2740
+ async shutdown() {
2741
+ }
2742
+ getConfigSchema() {
2743
+ return {
2744
+ sections: [
2745
+ {
2746
+ id: "info",
2747
+ title: "Camera Native Detection",
2748
+ description: "This addon forwards detections from native camera events (Frigate webhooks, Scrypted push notifications, ONVIF events). No configuration required.",
2749
+ fields: []
2750
+ }
2751
+ ]
2752
+ };
2753
+ }
2754
+ getClassMap() {
2755
+ return NATIVE_CLASS_MAP;
2756
+ }
2757
+ getModelCatalog() {
2758
+ return [];
2759
+ }
2760
+ getAvailableModels() {
2761
+ return [];
2762
+ }
2763
+ getActiveLabels() {
2764
+ return NATIVE_LABELS;
2765
+ }
2766
+ async probe() {
2767
+ return {
2768
+ available: true,
2769
+ runtime: "onnx",
2770
+ // no runtime used; satisfies the type
2771
+ device: "cpu",
2772
+ capabilities: ["fp32"]
2773
+ };
2774
+ }
2775
+ };
2776
+
2777
+ // src/addons/bird-global-classifier/index.ts
2778
+ var fs3 = __toESM(require("fs"));
2779
+ var path4 = __toESM(require("path"));
2780
+ var SPECIES_LABEL = { id: "species", name: "Bird Species" };
2781
+ var SPECIES_LABELS = [SPECIES_LABEL];
2782
+ var BIRD_CLASS_MAP = { mapping: {}, preserveOriginal: true };
2783
+ function loadLabels(modelsDir, modelId) {
2784
+ const labelNames = [
2785
+ `camstack-${modelId}-labels.json`,
2786
+ `camstack-bird-species-525-labels.json`
2787
+ ];
2788
+ for (const name of labelNames) {
2789
+ const labelPath = path4.join(modelsDir, name);
2790
+ if (fs3.existsSync(labelPath)) {
2791
+ const raw = fs3.readFileSync(labelPath, "utf-8");
2792
+ return JSON.parse(raw);
2793
+ }
2794
+ }
2795
+ throw new Error(`BirdGlobalClassifierAddon: labels JSON not found in ${modelsDir}`);
2796
+ }
2797
+ function softmax(logits) {
2798
+ const max = logits.reduce((a, b) => Math.max(a, b), -Infinity);
2799
+ const exps = logits.map((v) => Math.exp(v - max));
2800
+ const sum = exps.reduce((a, b) => a + b, 0);
2801
+ return exps.map((v) => v / sum);
2802
+ }
2803
+ var BirdGlobalClassifierAddon = class {
2804
+ id = "bird-global-classifier";
2805
+ slot = "classifier";
2806
+ inputClasses = ["animal"];
2807
+ outputClasses = ["species:*"];
2808
+ slotPriority = 0;
2809
+ requiredSteps = [];
2810
+ manifest = {
2811
+ id: "bird-global-classifier",
2812
+ name: "Bird Classifier (Global, 525 species)",
2813
+ version: "0.1.0",
2814
+ description: "EfficientNet \u2014 525 worldwide bird species (MIT license, ONNX only)",
2815
+ packageName: "@camstack/addon-vision",
2816
+ slot: "classifier",
2817
+ inputClasses: ["animal"],
2818
+ outputClasses: ["species:*"],
2819
+ supportsCustomModels: false,
2820
+ mayRequirePython: false,
2821
+ defaultConfig: {
2822
+ modelId: "bird-species-525",
2823
+ runtime: "auto",
2824
+ backend: "cpu",
2825
+ minConfidence: 0.3
2826
+ }
2827
+ };
2828
+ engine;
2829
+ modelEntry;
2830
+ labels = [];
2831
+ minConfidence = 0.3;
2832
+ async initialize(ctx) {
2833
+ const cfg = ctx.addonConfig;
2834
+ const modelId = cfg["modelId"] ?? "bird-species-525";
2835
+ const runtime = cfg["runtime"] ?? "auto";
2836
+ const backend = cfg["backend"] ?? "cpu";
2837
+ this.minConfidence = cfg["minConfidence"] ?? 0.3;
2838
+ const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId);
2839
+ if (!entry) {
2840
+ throw new Error(`BirdGlobalClassifierAddon: unknown modelId "${modelId}"`);
2841
+ }
2842
+ this.modelEntry = entry;
2843
+ this.labels = loadLabels(ctx.locationPaths.models, modelId);
2844
+ const resolved = await resolveEngine({
2845
+ runtime,
2846
+ backend,
2847
+ modelEntry: entry,
2848
+ modelsDir: ctx.locationPaths.models
2849
+ });
2850
+ this.engine = resolved.engine;
2851
+ }
2852
+ async classify(input) {
2853
+ const start = Date.now();
2854
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
2855
+ const animalCrop = await cropRegion(input.frame.data, input.roi);
2856
+ const normalized = await resizeAndNormalize(animalCrop, inputW, inputH, "imagenet", "nchw");
2857
+ const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW]);
2858
+ const probs = softmax(rawOutput);
2859
+ let maxIdx = 0;
2860
+ let maxScore = probs[0] ?? 0;
2861
+ for (let i = 1; i < probs.length; i++) {
2862
+ const score = probs[i] ?? 0;
2863
+ if (score > maxScore) {
2864
+ maxScore = score;
2865
+ maxIdx = i;
2866
+ }
2867
+ }
2868
+ if (maxScore < this.minConfidence) {
2869
+ return {
2870
+ classifications: [],
2871
+ inferenceMs: Date.now() - start,
2872
+ modelId: this.modelEntry.id
2873
+ };
2874
+ }
2875
+ const label = this.labels[maxIdx] ?? `species_${maxIdx}`;
2876
+ return {
2877
+ classifications: [
2878
+ {
2879
+ class: label,
2880
+ score: maxScore
2881
+ }
2882
+ ],
2883
+ inferenceMs: Date.now() - start,
2884
+ modelId: this.modelEntry.id
2885
+ };
2886
+ }
2887
+ async shutdown() {
2888
+ await this.engine?.dispose();
2889
+ }
2890
+ getConfigSchema() {
2891
+ return {
2892
+ sections: [
2893
+ {
2894
+ id: "model",
2895
+ title: "Model",
2896
+ columns: 1,
2897
+ fields: [
2898
+ {
2899
+ key: "modelId",
2900
+ label: "Model",
2901
+ type: "model-selector",
2902
+ catalog: [...BIRD_SPECIES_MODELS],
2903
+ allowCustom: false,
2904
+ allowConversion: false,
2905
+ acceptFormats: ["onnx", "coreml", "openvino"],
2906
+ requiredMetadata: ["inputSize", "labels"],
2907
+ outputFormatHint: "classification"
2908
+ }
2909
+ ]
2910
+ },
2911
+ {
2912
+ id: "thresholds",
2913
+ title: "Classification Settings",
2914
+ columns: 1,
2915
+ fields: [
2916
+ {
2917
+ key: "minConfidence",
2918
+ label: "Minimum Confidence",
2919
+ type: "slider",
2920
+ min: 0.05,
2921
+ max: 1,
2922
+ step: 0.05,
2923
+ default: 0.3
2924
+ }
2925
+ ]
2926
+ },
2927
+ {
2928
+ id: "runtime",
2929
+ title: "Runtime",
2930
+ columns: 2,
2931
+ fields: [
2932
+ {
2933
+ key: "runtime",
2934
+ label: "Runtime",
2935
+ type: "select",
2936
+ options: [
2937
+ { value: "auto", label: "Auto (recommended)" },
2938
+ { value: "onnx", label: "ONNX Runtime" },
2939
+ { value: "coreml", label: "CoreML (Apple)" }
2940
+ ]
2941
+ },
2942
+ {
2943
+ key: "backend",
2944
+ label: "Backend",
2945
+ type: "select",
2946
+ dependsOn: { runtime: "onnx" },
2947
+ options: [
2948
+ { value: "cpu", label: "CPU" },
2949
+ { value: "coreml", label: "CoreML" },
2950
+ { value: "cuda", label: "CUDA (NVIDIA)" }
2951
+ ]
2952
+ }
2953
+ ]
2954
+ }
2955
+ ]
2956
+ };
2957
+ }
2958
+ getClassMap() {
2959
+ return BIRD_CLASS_MAP;
2960
+ }
2961
+ getModelCatalog() {
2962
+ return [...BIRD_SPECIES_MODELS];
2963
+ }
2964
+ getAvailableModels() {
2965
+ return [];
2966
+ }
2967
+ getActiveLabels() {
2968
+ return SPECIES_LABELS;
2969
+ }
2970
+ async probe() {
2971
+ return {
2972
+ available: true,
2973
+ runtime: this.engine?.runtime ?? "onnx",
2974
+ device: this.engine?.device ?? "cpu",
2975
+ capabilities: ["fp32"]
2976
+ };
2977
+ }
2978
+ };
2979
+
2980
+ // src/addons/bird-nabirds-classifier/index.ts
2981
+ var fs4 = __toESM(require("fs"));
2982
+ var path5 = __toESM(require("path"));
2983
+ var SPECIES_LABEL2 = { id: "species", name: "Bird Species" };
2984
+ var SPECIES_LABELS2 = [SPECIES_LABEL2];
2985
+ var BIRD_CLASS_MAP2 = { mapping: {}, preserveOriginal: true };
2986
+ function loadLabels2(modelsDir, modelId) {
2987
+ const labelNames = [
2988
+ `camstack-${modelId}-labels.json`,
2989
+ `camstack-bird-nabirds-404-labels.json`
2990
+ ];
2991
+ for (const name of labelNames) {
2992
+ const labelPath = path5.join(modelsDir, name);
2993
+ if (fs4.existsSync(labelPath)) {
2994
+ const raw = fs4.readFileSync(labelPath, "utf-8");
2995
+ return JSON.parse(raw);
2996
+ }
2997
+ }
2998
+ throw new Error(`BirdNABirdsClassifierAddon: labels JSON not found in ${modelsDir}`);
2999
+ }
3000
+ function softmax2(logits) {
3001
+ const max = logits.reduce((a, b) => Math.max(a, b), -Infinity);
3002
+ const exps = logits.map((v) => Math.exp(v - max));
3003
+ const sum = exps.reduce((a, b) => a + b, 0);
3004
+ return exps.map((v) => v / sum);
3005
+ }
3006
+ var BirdNABirdsClassifierAddon = class {
3007
+ id = "bird-nabirds-classifier";
3008
+ slot = "classifier";
3009
+ inputClasses = ["animal"];
3010
+ outputClasses = ["species:*"];
3011
+ slotPriority = 0;
3012
+ requiredSteps = [];
3013
+ manifest = {
3014
+ id: "bird-nabirds-classifier",
3015
+ name: "Bird Classifier (NABirds, 404 species)",
3016
+ version: "0.1.0",
3017
+ description: "ResNet50 \u2014 404 North American bird species (NABirds dataset, ONNX + CoreML + OpenVINO)",
3018
+ packageName: "@camstack/addon-vision",
3019
+ slot: "classifier",
3020
+ inputClasses: ["animal"],
3021
+ outputClasses: ["species:*"],
3022
+ supportsCustomModels: false,
3023
+ mayRequirePython: false,
3024
+ defaultConfig: {
3025
+ modelId: "bird-nabirds-404",
3026
+ runtime: "auto",
3027
+ backend: "cpu",
3028
+ minConfidence: 0.3
3029
+ }
3030
+ };
3031
+ engine;
3032
+ modelEntry;
3033
+ labels = [];
3034
+ minConfidence = 0.3;
3035
+ allowedSpecies;
3036
+ async initialize(ctx) {
3037
+ const cfg = ctx.addonConfig;
3038
+ const modelId = cfg["modelId"] ?? "bird-nabirds-404";
3039
+ const runtime = cfg["runtime"] ?? "auto";
3040
+ const backend = cfg["backend"] ?? "cpu";
3041
+ this.minConfidence = cfg["minConfidence"] ?? 0.3;
3042
+ this.allowedSpecies = cfg["allowedSpecies"];
3043
+ const entry = BIRD_NABIRDS_MODELS.find((m) => m.id === modelId);
3044
+ if (!entry) {
3045
+ throw new Error(`BirdNABirdsClassifierAddon: unknown modelId "${modelId}"`);
3046
+ }
3047
+ this.modelEntry = entry;
3048
+ this.labels = loadLabels2(ctx.locationPaths.models, modelId);
3049
+ const resolved = await resolveEngine({
3050
+ runtime,
3051
+ backend,
3052
+ modelEntry: entry,
3053
+ modelsDir: ctx.locationPaths.models
3054
+ });
3055
+ this.engine = resolved.engine;
3056
+ }
3057
+ applyRegionFilter(scores, labels) {
3058
+ if (!this.allowedSpecies || this.allowedSpecies.length === 0) return;
3059
+ const allowedSet = new Set(this.allowedSpecies.map((s) => s.toLowerCase()));
3060
+ for (let i = 0; i < scores.length; i++) {
3061
+ if (!allowedSet.has(labels[i].toLowerCase())) {
3062
+ scores[i] = 0;
3063
+ }
3064
+ }
3065
+ }
3066
+ async classify(input) {
3067
+ const start = Date.now();
3068
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
3069
+ const animalCrop = await cropRegion(input.frame.data, input.roi);
3070
+ const normalized = await resizeAndNormalize(animalCrop, inputW, inputH, "imagenet", "nchw");
3071
+ const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW]);
3072
+ const probs = softmax2(rawOutput);
3073
+ this.applyRegionFilter(probs, this.labels);
3074
+ const filteredSum = probs.reduce((a, b) => a + b, 0);
3075
+ if (filteredSum > 0) {
3076
+ for (let i = 0; i < probs.length; i++) {
3077
+ probs[i] = (probs[i] ?? 0) / filteredSum;
3078
+ }
3079
+ }
3080
+ let maxIdx = 0;
3081
+ let maxScore = probs[0] ?? 0;
3082
+ for (let i = 1; i < probs.length; i++) {
3083
+ const score = probs[i] ?? 0;
3084
+ if (score > maxScore) {
3085
+ maxScore = score;
3086
+ maxIdx = i;
3087
+ }
3088
+ }
3089
+ if (maxScore < this.minConfidence) {
3090
+ return {
3091
+ classifications: [],
3092
+ inferenceMs: Date.now() - start,
3093
+ modelId: this.modelEntry.id
3094
+ };
3095
+ }
3096
+ const label = this.labels[maxIdx] ?? `species_${maxIdx}`;
3097
+ return {
3098
+ classifications: [
3099
+ {
3100
+ class: label,
3101
+ score: maxScore
3102
+ }
3103
+ ],
3104
+ inferenceMs: Date.now() - start,
3105
+ modelId: this.modelEntry.id
3106
+ };
3107
+ }
3108
+ async shutdown() {
3109
+ await this.engine?.dispose();
3110
+ }
3111
+ getConfigSchema() {
3112
+ return {
3113
+ sections: [
3114
+ {
3115
+ id: "model",
3116
+ title: "Model",
3117
+ columns: 1,
3118
+ fields: [
3119
+ {
3120
+ key: "modelId",
3121
+ label: "Model",
3122
+ type: "model-selector",
3123
+ catalog: [...BIRD_NABIRDS_MODELS],
3124
+ allowCustom: false,
3125
+ allowConversion: false,
3126
+ acceptFormats: ["onnx", "coreml", "openvino"],
3127
+ requiredMetadata: ["inputSize", "labels"],
3128
+ outputFormatHint: "classification"
3129
+ }
3130
+ ]
3131
+ },
3132
+ {
3133
+ id: "thresholds",
3134
+ title: "Classification Settings",
3135
+ columns: 1,
3136
+ fields: [
3137
+ {
3138
+ key: "minConfidence",
3139
+ label: "Minimum Confidence",
3140
+ type: "slider",
3141
+ min: 0.05,
3142
+ max: 1,
3143
+ step: 0.05,
3144
+ default: 0.3
3145
+ }
3146
+ ]
3147
+ },
3148
+ {
3149
+ id: "region",
3150
+ title: "Regional Filter",
3151
+ columns: 1,
3152
+ fields: [
3153
+ {
3154
+ key: "regionFilter",
3155
+ label: "Region Preset",
3156
+ type: "select",
3157
+ options: [
3158
+ { value: "", label: "None (all 404 species)" },
3159
+ { value: "north-america-east", label: "North America \u2014 Eastern" },
3160
+ { value: "north-america-west", label: "North America \u2014 Western" },
3161
+ { value: "north-america-south", label: "North America \u2014 Southern" }
3162
+ ]
3163
+ },
3164
+ {
3165
+ key: "allowedSpecies",
3166
+ label: "Custom Allowed Species (comma-separated)",
3167
+ type: "text"
3168
+ }
3169
+ ]
3170
+ },
3171
+ {
3172
+ id: "runtime",
3173
+ title: "Runtime",
3174
+ columns: 2,
3175
+ fields: [
3176
+ {
3177
+ key: "runtime",
3178
+ label: "Runtime",
3179
+ type: "select",
3180
+ options: [
3181
+ { value: "auto", label: "Auto (recommended)" },
3182
+ { value: "onnx", label: "ONNX Runtime" },
3183
+ { value: "coreml", label: "CoreML (Apple)" },
3184
+ { value: "openvino", label: "OpenVINO (Intel)" }
3185
+ ]
3186
+ },
3187
+ {
3188
+ key: "backend",
3189
+ label: "Backend",
3190
+ type: "select",
3191
+ dependsOn: { runtime: "onnx" },
3192
+ options: [
3193
+ { value: "cpu", label: "CPU" },
3194
+ { value: "coreml", label: "CoreML" },
3195
+ { value: "cuda", label: "CUDA (NVIDIA)" }
3196
+ ]
3197
+ }
3198
+ ]
3199
+ }
3200
+ ]
3201
+ };
3202
+ }
3203
+ getClassMap() {
3204
+ return BIRD_CLASS_MAP2;
3205
+ }
3206
+ getModelCatalog() {
3207
+ return [...BIRD_NABIRDS_MODELS];
3208
+ }
3209
+ getAvailableModels() {
3210
+ return [];
3211
+ }
3212
+ getActiveLabels() {
3213
+ return SPECIES_LABELS2;
3214
+ }
3215
+ async probe() {
3216
+ return {
3217
+ available: true,
3218
+ runtime: this.engine?.runtime ?? "onnx",
3219
+ device: this.engine?.device ?? "cpu",
3220
+ capabilities: ["fp32"]
3221
+ };
3222
+ }
3223
+ };
3224
+
3225
+ // src/addons/animal-classifier/index.ts
3226
+ var ANIMAL_TYPE_LABEL2 = { id: "animal-type", name: "Animal Type" };
3227
+ var ANIMAL_TYPE_LABELS = [ANIMAL_TYPE_LABEL2];
3228
+ var ANIMAL_CLASS_MAP = { mapping: {}, preserveOriginal: true };
3229
+ var ANIMAL_10_CLASSES = [
3230
+ "cat",
3231
+ "cow",
3232
+ "dog",
3233
+ "dolphin",
3234
+ "eagle",
3235
+ "giant panda",
3236
+ "horse",
3237
+ "monkey",
3238
+ "sheep",
3239
+ "spider"
3240
+ ];
3241
+ function softmax3(logits) {
3242
+ const max = logits.reduce((a, b) => Math.max(a, b), -Infinity);
3243
+ const exps = logits.map((v) => Math.exp(v - max));
3244
+ const sum = exps.reduce((a, b) => a + b, 0);
3245
+ return exps.map((v) => v / sum);
3246
+ }
3247
+ var AnimalClassifierAddon = class {
3248
+ id = "animal-classifier";
3249
+ slot = "classifier";
3250
+ inputClasses = ["animal"];
3251
+ outputClasses = ["animal-type:*"];
3252
+ slotPriority = 0;
3253
+ requiredSteps = [];
3254
+ manifest = {
3255
+ id: "animal-classifier",
3256
+ name: "Animal Classifier",
3257
+ version: "0.1.0",
3258
+ description: "ViT-based animal type classifier \u2014 10 common species",
3259
+ packageName: "@camstack/addon-vision",
3260
+ slot: "classifier",
3261
+ inputClasses: ["animal"],
3262
+ outputClasses: ["animal-type:*"],
3263
+ supportsCustomModels: false,
3264
+ mayRequirePython: false,
3265
+ defaultConfig: {
3266
+ modelId: "animals-10",
3267
+ runtime: "auto",
3268
+ backend: "cpu",
3269
+ minConfidence: 0.3
3270
+ }
3271
+ };
3272
+ engine;
3273
+ modelEntry;
3274
+ minConfidence = 0.3;
3275
+ async initialize(ctx) {
3276
+ const cfg = ctx.addonConfig;
3277
+ const modelId = cfg["modelId"] ?? "animals-10";
3278
+ const runtime = cfg["runtime"] ?? "auto";
3279
+ const backend = cfg["backend"] ?? "cpu";
3280
+ this.minConfidence = cfg["minConfidence"] ?? 0.3;
3281
+ const entry = ANIMAL_TYPE_MODELS.find((m) => m.id === modelId);
3282
+ if (!entry) {
3283
+ throw new Error(`AnimalClassifierAddon: unknown modelId "${modelId}"`);
3284
+ }
3285
+ this.modelEntry = entry;
3286
+ const resolved = await resolveEngine({
3287
+ runtime,
3288
+ backend,
3289
+ modelEntry: entry,
3290
+ modelsDir: ctx.locationPaths.models
3291
+ });
3292
+ this.engine = resolved.engine;
3293
+ }
3294
+ async classify(input) {
3295
+ const start = Date.now();
3296
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
3297
+ const animalCrop = await cropRegion(input.frame.data, input.roi);
3298
+ const normalized = await resizeAndNormalize(animalCrop, inputW, inputH, "imagenet", "nchw");
3299
+ const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW]);
3300
+ const probs = softmax3(rawOutput);
3301
+ let maxIdx = 0;
3302
+ let maxScore = probs[0] ?? 0;
3303
+ for (let i = 1; i < probs.length; i++) {
3304
+ const score = probs[i] ?? 0;
3305
+ if (score > maxScore) {
3306
+ maxScore = score;
3307
+ maxIdx = i;
3308
+ }
3309
+ }
3310
+ if (maxScore < this.minConfidence) {
3311
+ return {
3312
+ classifications: [],
3313
+ inferenceMs: Date.now() - start,
3314
+ modelId: this.modelEntry.id
3315
+ };
3316
+ }
3317
+ const label = ANIMAL_10_CLASSES[maxIdx] ?? `animal_${maxIdx}`;
3318
+ return {
3319
+ classifications: [
3320
+ {
3321
+ class: label,
3322
+ score: maxScore
3323
+ }
3324
+ ],
3325
+ inferenceMs: Date.now() - start,
3326
+ modelId: this.modelEntry.id
3327
+ };
3328
+ }
3329
+ async shutdown() {
3330
+ await this.engine?.dispose();
3331
+ }
3332
+ getConfigSchema() {
3333
+ return {
3334
+ sections: [
3335
+ {
3336
+ id: "model",
3337
+ title: "Model",
3338
+ columns: 1,
3339
+ fields: [
3340
+ {
3341
+ key: "modelId",
3342
+ label: "Model",
3343
+ type: "model-selector",
3344
+ catalog: [...ANIMAL_TYPE_MODELS],
3345
+ allowCustom: false,
3346
+ allowConversion: false,
3347
+ acceptFormats: ["onnx", "coreml", "openvino"],
3348
+ requiredMetadata: ["inputSize", "labels"],
3349
+ outputFormatHint: "classification"
3350
+ }
3351
+ ]
3352
+ },
3353
+ {
3354
+ id: "thresholds",
3355
+ title: "Classification Settings",
3356
+ columns: 1,
3357
+ fields: [
3358
+ {
3359
+ key: "minConfidence",
3360
+ label: "Minimum Confidence",
3361
+ type: "slider",
3362
+ min: 0.05,
3363
+ max: 1,
3364
+ step: 0.05,
3365
+ default: 0.3
3366
+ }
3367
+ ]
3368
+ },
3369
+ {
3370
+ id: "runtime",
3371
+ title: "Runtime",
3372
+ columns: 2,
3373
+ fields: [
3374
+ {
3375
+ key: "runtime",
3376
+ label: "Runtime",
3377
+ type: "select",
3378
+ options: [
3379
+ { value: "auto", label: "Auto (recommended)" },
3380
+ { value: "onnx", label: "ONNX Runtime" },
3381
+ { value: "coreml", label: "CoreML (Apple)" }
3382
+ ]
3383
+ },
3384
+ {
3385
+ key: "backend",
3386
+ label: "Backend",
3387
+ type: "select",
3388
+ dependsOn: { runtime: "onnx" },
3389
+ options: [
3390
+ { value: "cpu", label: "CPU" },
3391
+ { value: "coreml", label: "CoreML" },
3392
+ { value: "cuda", label: "CUDA (NVIDIA)" }
3393
+ ]
3394
+ }
3395
+ ]
3396
+ }
3397
+ ]
3398
+ };
3399
+ }
3400
+ getClassMap() {
3401
+ return ANIMAL_CLASS_MAP;
3402
+ }
3403
+ getModelCatalog() {
3404
+ return [...ANIMAL_TYPE_MODELS];
3405
+ }
3406
+ getAvailableModels() {
3407
+ return [];
3408
+ }
3409
+ getActiveLabels() {
3410
+ return ANIMAL_TYPE_LABELS;
3411
+ }
3412
+ async probe() {
3413
+ return {
3414
+ available: true,
3415
+ runtime: this.engine?.runtime ?? "onnx",
3416
+ device: this.engine?.device ?? "cpu",
3417
+ capabilities: ["fp32"]
3418
+ };
3419
+ }
3420
+ };
3421
+ // Annotate the CommonJS export names for ESM import in node:
3422
+ 0 && (module.exports = {
3423
+ ANIMAL_TYPE_MODELS,
3424
+ AUDIO_CLASSIFICATION_MODELS,
3425
+ AnimalClassifierAddon,
3426
+ AudioClassificationAddon,
3427
+ BIRD_NABIRDS_MODELS,
3428
+ BIRD_SPECIES_MODELS,
3429
+ BirdGlobalClassifierAddon,
3430
+ BirdNABirdsClassifierAddon,
3431
+ CameraNativeDetectionAddon,
3432
+ FACE_DETECTION_MODELS,
3433
+ FACE_RECOGNITION_MODELS,
3434
+ FaceDetectionAddon,
3435
+ FaceRecognitionAddon,
3436
+ MotionDetectionAddon,
3437
+ NodeInferenceEngine,
3438
+ OBJECT_DETECTION_MODELS,
3439
+ ObjectDetectionAddon,
3440
+ PLATE_DETECTION_MODELS,
3441
+ PLATE_RECOGNITION_MODELS,
3442
+ PlateDetectionAddon,
3443
+ PlateRecognitionAddon,
3444
+ PythonInferenceEngine,
3445
+ SEGMENTATION_MODELS,
3446
+ cosineSimilarity,
3447
+ cropRegion,
3448
+ ctcDecode,
3449
+ detectMotion,
3450
+ iou,
3451
+ jpegToRgb,
3452
+ l2Normalize,
3453
+ letterbox,
3454
+ nms,
3455
+ probeOnnxBackends,
3456
+ resizeAndNormalize,
3457
+ resolveEngine,
3458
+ rgbToGrayscale,
3459
+ scrfdPostprocess,
3460
+ yamnetPostprocess,
3461
+ yoloPostprocess
3462
+ });
3463
+ //# sourceMappingURL=index.js.map