@camstack/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 (104) 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 +469 -0
  4. package/dist/addons/animal-classifier/index.js.map +1 -0
  5. package/dist/addons/animal-classifier/index.mjs +9 -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 +411 -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 +475 -0
  16. package/dist/addons/bird-global-classifier/index.js.map +1 -0
  17. package/dist/addons/bird-global-classifier/index.mjs +9 -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 +517 -0
  22. package/dist/addons/bird-nabirds-classifier/index.js.map +1 -0
  23. package/dist/addons/bird-nabirds-classifier/index.mjs +9 -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 +513 -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 +437 -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 +25 -0
  50. package/dist/addons/object-detection/index.d.ts +25 -0
  51. package/dist/addons/object-detection/index.js +673 -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 +477 -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 +470 -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-3BKYLBBH.mjs +229 -0
  68. package/dist/chunk-3BKYLBBH.mjs.map +1 -0
  69. package/dist/chunk-4PC262GU.mjs +203 -0
  70. package/dist/chunk-4PC262GU.mjs.map +1 -0
  71. package/dist/chunk-6OR5TE7A.mjs +101 -0
  72. package/dist/chunk-6OR5TE7A.mjs.map +1 -0
  73. package/dist/chunk-7SZAISGP.mjs +210 -0
  74. package/dist/chunk-7SZAISGP.mjs.map +1 -0
  75. package/dist/chunk-AD2TFYZA.mjs +235 -0
  76. package/dist/chunk-AD2TFYZA.mjs.map +1 -0
  77. package/dist/chunk-CGYSSHHM.mjs +363 -0
  78. package/dist/chunk-CGYSSHHM.mjs.map +1 -0
  79. package/dist/chunk-IYHMGYGP.mjs +79 -0
  80. package/dist/chunk-IYHMGYGP.mjs.map +1 -0
  81. package/dist/chunk-J3IUBPRE.mjs +187 -0
  82. package/dist/chunk-J3IUBPRE.mjs.map +1 -0
  83. package/dist/chunk-KFZDJPYL.mjs +190 -0
  84. package/dist/chunk-KFZDJPYL.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-PXBY3QOA.mjs +152 -0
  88. package/dist/chunk-PXBY3QOA.mjs.map +1 -0
  89. package/dist/chunk-XUKDL23Y.mjs +216 -0
  90. package/dist/chunk-XUKDL23Y.mjs.map +1 -0
  91. package/dist/chunk-Z26BVC7S.mjs +214 -0
  92. package/dist/chunk-Z26BVC7S.mjs.map +1 -0
  93. package/dist/chunk-Z5AHZQEZ.mjs +258 -0
  94. package/dist/chunk-Z5AHZQEZ.mjs.map +1 -0
  95. package/dist/index.d.mts +152 -0
  96. package/dist/index.d.ts +152 -0
  97. package/dist/index.js +2775 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/index.mjs +205 -0
  100. package/dist/index.mjs.map +1 -0
  101. package/package.json +43 -0
  102. package/python/coreml_inference.py +67 -0
  103. package/python/openvino_inference.py +76 -0
  104. package/python/pytorch_inference.py +74 -0
@@ -0,0 +1,187 @@
1
+ // src/shared/node-engine.ts
2
+ import * as path from "path";
3
+ var BACKEND_TO_PROVIDER = {
4
+ cpu: "cpu",
5
+ coreml: "coreml",
6
+ cuda: "cuda",
7
+ tensorrt: "tensorrt",
8
+ dml: "dml"
9
+ };
10
+ var BACKEND_TO_DEVICE = {
11
+ cpu: "cpu",
12
+ coreml: "gpu-mps",
13
+ cuda: "gpu-cuda",
14
+ tensorrt: "tensorrt"
15
+ };
16
+ var NodeInferenceEngine = class {
17
+ constructor(modelPath, backend) {
18
+ this.modelPath = modelPath;
19
+ this.backend = backend;
20
+ this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
21
+ }
22
+ runtime = "onnx";
23
+ device;
24
+ session = null;
25
+ async initialize() {
26
+ const ort = await import("onnxruntime-node");
27
+ const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
28
+ const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
29
+ const sessionOptions = {
30
+ executionProviders: [provider]
31
+ };
32
+ this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
33
+ }
34
+ async run(input, inputShape) {
35
+ if (!this.session) {
36
+ throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
37
+ }
38
+ const ort = await import("onnxruntime-node");
39
+ const sess = this.session;
40
+ const inputName = sess.inputNames[0];
41
+ const tensor = new ort.Tensor("float32", input, [...inputShape]);
42
+ const feeds = { [inputName]: tensor };
43
+ const results = await sess.run(feeds);
44
+ const outputName = sess.outputNames[0];
45
+ const outputTensor = results[outputName];
46
+ return outputTensor.data;
47
+ }
48
+ async runMultiOutput(input, inputShape) {
49
+ if (!this.session) {
50
+ throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
51
+ }
52
+ const ort = await import("onnxruntime-node");
53
+ const sess = this.session;
54
+ const inputName = sess.inputNames[0];
55
+ const tensor = new ort.Tensor("float32", input, [...inputShape]);
56
+ const feeds = { [inputName]: tensor };
57
+ const results = await sess.run(feeds);
58
+ const out = {};
59
+ for (const name of sess.outputNames) {
60
+ out[name] = results[name].data;
61
+ }
62
+ return out;
63
+ }
64
+ async dispose() {
65
+ this.session = null;
66
+ }
67
+ };
68
+
69
+ // src/shared/engine-resolver.ts
70
+ import * as fs from "fs";
71
+ import * as path2 from "path";
72
+ var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
73
+ var BACKEND_TO_FORMAT = {
74
+ cpu: "onnx",
75
+ coreml: "coreml",
76
+ cuda: "onnx",
77
+ tensorrt: "onnx"
78
+ };
79
+ var RUNTIME_TO_FORMAT = {
80
+ onnx: "onnx",
81
+ coreml: "coreml",
82
+ openvino: "openvino",
83
+ tflite: "tflite",
84
+ pytorch: "pt"
85
+ };
86
+ function modelFilePath(modelsDir, modelEntry, format) {
87
+ const formatEntry = modelEntry.formats[format];
88
+ if (!formatEntry) {
89
+ throw new Error(`Model ${modelEntry.id} has no ${format} format`);
90
+ }
91
+ const urlParts = formatEntry.url.split("/");
92
+ const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
93
+ return path2.join(modelsDir, filename);
94
+ }
95
+ function modelExists(filePath) {
96
+ try {
97
+ return fs.existsSync(filePath);
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ async function resolveEngine(options) {
103
+ const { runtime, backend, modelEntry, modelsDir, downloadModel } = options;
104
+ let selectedFormat;
105
+ let selectedBackend;
106
+ if (runtime === "auto") {
107
+ const available = await probeOnnxBackends();
108
+ let chosen = null;
109
+ for (const b of AUTO_BACKEND_PRIORITY) {
110
+ if (!available.includes(b)) continue;
111
+ const fmt = BACKEND_TO_FORMAT[b];
112
+ if (!fmt) continue;
113
+ if (!modelEntry.formats[fmt]) continue;
114
+ chosen = { backend: b, format: fmt };
115
+ break;
116
+ }
117
+ if (!chosen) {
118
+ throw new Error(
119
+ `resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`
120
+ );
121
+ }
122
+ selectedFormat = chosen.format;
123
+ selectedBackend = chosen.backend;
124
+ } else {
125
+ const fmt = RUNTIME_TO_FORMAT[runtime];
126
+ if (!fmt) {
127
+ throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
128
+ }
129
+ if (!modelEntry.formats[fmt]) {
130
+ throw new Error(
131
+ `resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`
132
+ );
133
+ }
134
+ selectedFormat = fmt;
135
+ selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
136
+ }
137
+ let modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
138
+ if (!modelExists(modelPath)) {
139
+ if (downloadModel) {
140
+ const formatEntry = modelEntry.formats[selectedFormat];
141
+ modelPath = await downloadModel(formatEntry.url, modelsDir);
142
+ } else {
143
+ throw new Error(
144
+ `resolveEngine: model file not found at ${modelPath} and no downloadModel function provided`
145
+ );
146
+ }
147
+ }
148
+ if (selectedFormat === "onnx" || selectedFormat === "coreml") {
149
+ const engine = new NodeInferenceEngine(modelPath, selectedBackend);
150
+ await engine.initialize();
151
+ return { engine, format: selectedFormat, modelPath };
152
+ }
153
+ const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
154
+ if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
155
+ const engine = new NodeInferenceEngine(fallbackPath, "cpu");
156
+ await engine.initialize();
157
+ return { engine, format: "onnx", modelPath: fallbackPath };
158
+ }
159
+ throw new Error(
160
+ `resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine and no ONNX fallback is available`
161
+ );
162
+ }
163
+ async function probeOnnxBackends() {
164
+ const available = ["cpu"];
165
+ try {
166
+ const ort = await import("onnxruntime-node");
167
+ const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
168
+ for (const p of providers) {
169
+ const normalized = p.toLowerCase().replace("executionprovider", "");
170
+ if (normalized === "coreml") available.push("coreml");
171
+ else if (normalized === "cuda") available.push("cuda");
172
+ else if (normalized === "tensorrt") available.push("tensorrt");
173
+ }
174
+ } catch {
175
+ }
176
+ if (process.platform === "darwin" && !available.includes("coreml")) {
177
+ available.push("coreml");
178
+ }
179
+ return [...new Set(available)];
180
+ }
181
+
182
+ export {
183
+ NodeInferenceEngine,
184
+ resolveEngine,
185
+ probeOnnxBackends
186
+ };
187
+ //# sourceMappingURL=chunk-J3IUBPRE.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/node-engine.ts","../src/shared/engine-resolver.ts"],"sourcesContent":["import type { IInferenceEngine, DetectionRuntime, DetectionDevice } from '@camstack/types'\nimport * as path from 'node:path'\n\nconst BACKEND_TO_PROVIDER: Readonly<Record<string, string>> = {\n cpu: 'cpu',\n coreml: 'coreml',\n cuda: 'cuda',\n tensorrt: 'tensorrt',\n dml: 'dml',\n} as const\n\nconst BACKEND_TO_DEVICE: Readonly<Record<string, DetectionDevice>> = {\n cpu: 'cpu',\n coreml: 'gpu-mps',\n cuda: 'gpu-cuda',\n tensorrt: 'tensorrt',\n} as const\n\nexport class NodeInferenceEngine implements IInferenceEngine {\n readonly runtime: DetectionRuntime = 'onnx'\n readonly device: DetectionDevice\n private session: unknown = null\n\n constructor(\n private readonly modelPath: string,\n private readonly backend: string,\n ) {\n this.device = (BACKEND_TO_DEVICE[backend] ?? 'cpu') as DetectionDevice\n }\n\n async initialize(): Promise<void> {\n const ort = await import('onnxruntime-node')\n const provider = BACKEND_TO_PROVIDER[this.backend] ?? 'cpu'\n\n // Resolve absolute path\n const absModelPath = path.isAbsolute(this.modelPath)\n ? this.modelPath\n : path.resolve(process.cwd(), this.modelPath)\n\n const sessionOptions: Record<string, unknown> = {\n executionProviders: [provider],\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.session = await (ort as any).InferenceSession.create(absModelPath, sessionOptions)\n }\n\n async run(input: Float32Array, inputShape: readonly number[]): Promise<Float32Array> {\n if (!this.session) {\n throw new Error('NodeInferenceEngine: not initialized — call initialize() first')\n }\n\n const ort = await import('onnxruntime-node')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const sess = this.session as any\n\n // Get the first input name\n const inputName: string = sess.inputNames[0]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tensor = new (ort as any).Tensor('float32', input, [...inputShape])\n const feeds: Record<string, unknown> = { [inputName]: tensor }\n\n const results = await sess.run(feeds)\n const outputName: string = sess.outputNames[0]\n const outputTensor = results[outputName]\n\n return outputTensor.data as Float32Array\n }\n\n async runMultiOutput(\n input: Float32Array,\n inputShape: readonly number[],\n ): Promise<Record<string, Float32Array>> {\n if (!this.session) {\n throw new Error('NodeInferenceEngine: not initialized — call initialize() first')\n }\n\n const ort = await import('onnxruntime-node')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const sess = this.session as any\n\n const inputName: string = sess.inputNames[0]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tensor = new (ort as any).Tensor('float32', input, [...inputShape])\n const feeds: Record<string, unknown> = { [inputName]: tensor }\n\n const results = await sess.run(feeds)\n\n const out: Record<string, Float32Array> = {}\n for (const name of sess.outputNames as string[]) {\n out[name] = results[name].data as Float32Array\n }\n return out\n }\n\n async dispose(): Promise<void> {\n // onnxruntime-node sessions don't have explicit close in all versions\n // but we clear the reference\n this.session = null\n }\n}\n","import type {\n IInferenceEngine,\n DetectionRuntime,\n ModelCatalogEntry,\n ModelFormat,\n} from '@camstack/types'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { NodeInferenceEngine } from './node-engine.js'\n\nexport interface EngineResolverOptions {\n readonly runtime: DetectionRuntime | 'auto'\n readonly backend: string\n readonly modelEntry: ModelCatalogEntry\n readonly modelsDir: string\n readonly pythonPath?: string\n readonly downloadModel?: (url: string, destDir: string) => Promise<string>\n}\n\nexport interface ResolvedEngine {\n readonly engine: IInferenceEngine\n readonly format: ModelFormat\n readonly modelPath: string\n}\n\n/** Priority order for auto-selection of ONNX backends */\nconst AUTO_BACKEND_PRIORITY = ['coreml', 'cuda', 'tensorrt', 'cpu'] as const\n\n/** Map backend names to the model format they require */\nconst BACKEND_TO_FORMAT: Readonly<Record<string, ModelFormat>> = {\n cpu: 'onnx',\n coreml: 'coreml',\n cuda: 'onnx',\n tensorrt: 'onnx',\n} as const\n\n/** Map DetectionRuntime to ModelFormat */\nconst RUNTIME_TO_FORMAT: Readonly<Partial<Record<DetectionRuntime, ModelFormat>>> = {\n onnx: 'onnx',\n coreml: 'coreml',\n openvino: 'openvino',\n tflite: 'tflite',\n pytorch: 'pt',\n} as const\n\nfunction modelFilePath(modelsDir: string, modelEntry: ModelCatalogEntry, format: ModelFormat): string {\n const formatEntry = modelEntry.formats[format]\n if (!formatEntry) {\n throw new Error(`Model ${modelEntry.id} has no ${format} format`)\n }\n // Derive filename from URL\n const urlParts = formatEntry.url.split('/')\n const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`\n return path.join(modelsDir, filename)\n}\n\nfunction modelExists(filePath: string): boolean {\n try {\n return fs.existsSync(filePath)\n } catch {\n return false\n }\n}\n\nexport async function resolveEngine(options: EngineResolverOptions): Promise<ResolvedEngine> {\n const { runtime, backend, modelEntry, modelsDir, downloadModel } = options\n\n let selectedFormat: ModelFormat\n let selectedBackend: string\n\n if (runtime === 'auto') {\n // Probe available ONNX backends and pick best\n const available = await probeOnnxBackends()\n\n // Pick first priority backend that has a corresponding model format available\n let chosen: { backend: string; format: ModelFormat } | null = null\n\n for (const b of AUTO_BACKEND_PRIORITY) {\n if (!available.includes(b)) continue\n const fmt = BACKEND_TO_FORMAT[b]\n if (!fmt) continue\n if (!modelEntry.formats[fmt]) continue\n chosen = { backend: b, format: fmt }\n break\n }\n\n if (!chosen) {\n throw new Error(\n `resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(', ')}`,\n )\n }\n\n selectedFormat = chosen.format\n selectedBackend = chosen.backend\n } else {\n // Explicit runtime requested\n const fmt = RUNTIME_TO_FORMAT[runtime]\n if (!fmt) {\n throw new Error(`resolveEngine: unsupported runtime \"${runtime}\"`)\n }\n if (!modelEntry.formats[fmt]) {\n throw new Error(\n `resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`,\n )\n }\n selectedFormat = fmt\n // For onnx runtime, use the provided backend; otherwise use the runtime name\n selectedBackend = runtime === 'onnx' ? (backend || 'cpu') : runtime\n }\n\n // Resolve model path\n let modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat)\n\n if (!modelExists(modelPath)) {\n if (downloadModel) {\n const formatEntry = modelEntry.formats[selectedFormat]!\n modelPath = await downloadModel(formatEntry.url, modelsDir)\n } else {\n throw new Error(\n `resolveEngine: model file not found at ${modelPath} and no downloadModel function provided`,\n )\n }\n }\n\n // Only ONNX runtime is handled by NodeInferenceEngine currently\n if (selectedFormat === 'onnx' || selectedFormat === 'coreml') {\n const engine = new NodeInferenceEngine(modelPath, selectedBackend)\n await engine.initialize()\n return { engine, format: selectedFormat, modelPath }\n }\n\n // For other formats (openvino, tflite, pt), fall back to ONNX cpu with a warning\n // until those engines are implemented\n const fallbackPath = modelFilePath(modelsDir, modelEntry, 'onnx')\n if (modelEntry.formats['onnx'] && modelExists(fallbackPath)) {\n const engine = new NodeInferenceEngine(fallbackPath, 'cpu')\n await engine.initialize()\n return { engine, format: 'onnx', modelPath: fallbackPath }\n }\n\n throw new Error(\n `resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine and no ONNX fallback is available`,\n )\n}\n\n/** Probe which ONNX execution providers are available on this system */\nexport async function probeOnnxBackends(): Promise<string[]> {\n const available: string[] = ['cpu'] // CPU is always available\n\n try {\n const ort = await import('onnxruntime-node')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const providers: string[] = (ort as any).env?.webgl?.disabled !== undefined\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ? ((ort as any).InferenceSession?.getAvailableProviders?.() ?? [])\n : []\n\n for (const p of providers) {\n const normalized = p.toLowerCase().replace('executionprovider', '')\n if (normalized === 'coreml') available.push('coreml')\n else if (normalized === 'cuda') available.push('cuda')\n else if (normalized === 'tensorrt') available.push('tensorrt')\n }\n } catch {\n // onnxruntime-node may not be installed; CPU only\n }\n\n // Platform-specific hints when getAvailableProviders isn't exposed\n if (process.platform === 'darwin' && !available.includes('coreml')) {\n available.push('coreml')\n }\n\n return [...new Set(available)]\n}\n"],"mappings":"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","names":["path"]}
@@ -0,0 +1,190 @@
1
+ import {
2
+ cropRegion,
3
+ resizeAndNormalize
4
+ } from "./chunk-6OR5TE7A.mjs";
5
+ import {
6
+ resolveEngine
7
+ } from "./chunk-J3IUBPRE.mjs";
8
+
9
+ // src/addons/face-recognition/index.ts
10
+ import { FACE_RECOGNITION_MODELS } from "@camstack/types";
11
+
12
+ // src/shared/postprocess/arcface.ts
13
+ function l2Normalize(vec) {
14
+ let sumSq = 0;
15
+ for (let i = 0; i < vec.length; i++) {
16
+ sumSq += vec[i] * vec[i];
17
+ }
18
+ const norm = Math.sqrt(sumSq);
19
+ if (norm === 0) return new Float32Array(vec.length);
20
+ const out = new Float32Array(vec.length);
21
+ for (let i = 0; i < vec.length; i++) {
22
+ out[i] = vec[i] / norm;
23
+ }
24
+ return out;
25
+ }
26
+ function cosineSimilarity(a, b) {
27
+ if (a.length !== b.length) throw new Error("Embedding length mismatch");
28
+ let dot = 0;
29
+ for (let i = 0; i < a.length; i++) {
30
+ dot += a[i] * b[i];
31
+ }
32
+ return dot;
33
+ }
34
+
35
+ // src/addons/face-recognition/index.ts
36
+ var IDENTITY_LABEL = { id: "identity", name: "Identity" };
37
+ var IDENTITY_LABELS = [IDENTITY_LABEL];
38
+ var FACE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
39
+ var REQUIRED_STEPS = [
40
+ { slot: "cropper", outputClasses: ["face"], description: "Requires a face detector" }
41
+ ];
42
+ var FaceRecognitionAddon = class {
43
+ id = "face-recognition";
44
+ slot = "classifier";
45
+ inputClasses = ["face"];
46
+ outputClasses = ["identity:*"];
47
+ slotPriority = 0;
48
+ requiredSteps = REQUIRED_STEPS;
49
+ manifest = {
50
+ id: "face-recognition",
51
+ name: "Face Recognition",
52
+ version: "0.1.0",
53
+ description: "ArcFace-based face recognition \u2014 produces 512-d identity embeddings",
54
+ packageName: "@camstack/vision",
55
+ slot: "classifier",
56
+ inputClasses: ["face"],
57
+ outputClasses: ["identity:*"],
58
+ requiredSteps: REQUIRED_STEPS,
59
+ supportsCustomModels: false,
60
+ mayRequirePython: false,
61
+ defaultConfig: {
62
+ modelId: "arcface-r100",
63
+ runtime: "auto",
64
+ backend: "cpu"
65
+ }
66
+ };
67
+ engine;
68
+ modelEntry;
69
+ async initialize(ctx) {
70
+ const cfg = ctx.addonConfig;
71
+ const modelId = cfg["modelId"] ?? "arcface-r100";
72
+ const runtime = cfg["runtime"] ?? "auto";
73
+ const backend = cfg["backend"] ?? "cpu";
74
+ const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId);
75
+ if (!entry) {
76
+ throw new Error(`FaceRecognitionAddon: unknown modelId "${modelId}"`);
77
+ }
78
+ this.modelEntry = entry;
79
+ const resolved = await resolveEngine({
80
+ runtime,
81
+ backend,
82
+ modelEntry: entry,
83
+ modelsDir: ctx.locationPaths.models
84
+ });
85
+ this.engine = resolved.engine;
86
+ }
87
+ async classify(input) {
88
+ const start = Date.now();
89
+ const { width: inputW, height: inputH } = this.modelEntry.inputSize;
90
+ const faceCrop = await cropRegion(input.frame.data, input.roi);
91
+ const layout = this.modelEntry.inputLayout ?? "nhwc";
92
+ const normalization = this.modelEntry.inputNormalization ?? "zero-one";
93
+ const normalized = await resizeAndNormalize(faceCrop, inputW, inputH, normalization, layout);
94
+ const rawEmbedding = await this.engine.run(normalized, [1, inputH, inputW, 3]);
95
+ const embedding = l2Normalize(rawEmbedding);
96
+ return {
97
+ classifications: [
98
+ {
99
+ class: "identity",
100
+ score: 1,
101
+ embedding
102
+ }
103
+ ],
104
+ inferenceMs: Date.now() - start,
105
+ modelId: this.modelEntry.id
106
+ };
107
+ }
108
+ async shutdown() {
109
+ await this.engine?.dispose();
110
+ }
111
+ getConfigSchema() {
112
+ return {
113
+ sections: [
114
+ {
115
+ id: "model",
116
+ title: "Model",
117
+ columns: 1,
118
+ fields: [
119
+ {
120
+ key: "modelId",
121
+ label: "Model",
122
+ type: "model-selector",
123
+ catalog: [...FACE_RECOGNITION_MODELS],
124
+ allowCustom: false,
125
+ allowConversion: false,
126
+ acceptFormats: ["onnx", "coreml", "openvino"],
127
+ requiredMetadata: ["inputSize", "labels", "outputFormat"],
128
+ outputFormatHint: "embedding"
129
+ }
130
+ ]
131
+ },
132
+ {
133
+ id: "runtime",
134
+ title: "Runtime",
135
+ columns: 2,
136
+ fields: [
137
+ {
138
+ key: "runtime",
139
+ label: "Runtime",
140
+ type: "select",
141
+ options: [
142
+ { value: "auto", label: "Auto (recommended)" },
143
+ { value: "onnx", label: "ONNX Runtime" },
144
+ { value: "coreml", label: "CoreML (Apple)" }
145
+ ]
146
+ },
147
+ {
148
+ key: "backend",
149
+ label: "Backend",
150
+ type: "select",
151
+ dependsOn: { runtime: "onnx" },
152
+ options: [
153
+ { value: "cpu", label: "CPU" },
154
+ { value: "coreml", label: "CoreML" },
155
+ { value: "cuda", label: "CUDA (NVIDIA)" }
156
+ ]
157
+ }
158
+ ]
159
+ }
160
+ ]
161
+ };
162
+ }
163
+ getClassMap() {
164
+ return FACE_REC_CLASS_MAP;
165
+ }
166
+ getModelCatalog() {
167
+ return [...FACE_RECOGNITION_MODELS];
168
+ }
169
+ getAvailableModels() {
170
+ return [];
171
+ }
172
+ getActiveLabels() {
173
+ return IDENTITY_LABELS;
174
+ }
175
+ async probe() {
176
+ return {
177
+ available: true,
178
+ runtime: this.engine?.runtime ?? "onnx",
179
+ device: this.engine?.device ?? "cpu",
180
+ capabilities: ["fp32"]
181
+ };
182
+ }
183
+ };
184
+
185
+ export {
186
+ l2Normalize,
187
+ cosineSimilarity,
188
+ FaceRecognitionAddon
189
+ };
190
+ //# sourceMappingURL=chunk-KFZDJPYL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/addons/face-recognition/index.ts","../src/shared/postprocess/arcface.ts"],"sourcesContent":["import type {\n IClassifierProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n CropInput,\n ClassifierOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n IInferenceEngine,\n RequiredStep,\n} from '@camstack/types'\nimport { FACE_RECOGNITION_MODELS } from '@camstack/types'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { l2Normalize } from '../../shared/postprocess/arcface.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nconst IDENTITY_LABEL: LabelDefinition = { id: 'identity', name: 'Identity' }\nconst IDENTITY_LABELS: readonly LabelDefinition[] = [IDENTITY_LABEL]\nconst FACE_REC_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nconst REQUIRED_STEPS: readonly RequiredStep[] = [\n { slot: 'cropper', outputClasses: ['face'], description: 'Requires a face detector' },\n]\n\nexport default class FaceRecognitionAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'face-recognition'\n readonly slot = 'classifier' as const\n readonly inputClasses = ['face'] as const\n readonly outputClasses = ['identity:*'] as const\n readonly slotPriority = 0\n readonly requiredSteps = REQUIRED_STEPS\n readonly manifest: AddonManifest = {\n id: 'face-recognition',\n name: 'Face Recognition',\n version: '0.1.0',\n description: 'ArcFace-based face recognition — produces 512-d identity embeddings',\n packageName: '@camstack/vision',\n slot: 'classifier',\n inputClasses: ['face'],\n outputClasses: ['identity:*'],\n requiredSteps: REQUIRED_STEPS,\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'arcface-r100',\n runtime: 'auto',\n backend: 'cpu',\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'arcface-r100'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n\n const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`FaceRecognitionAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir: ctx.locationPaths.models,\n })\n this.engine = resolved.engine\n }\n\n async classify(input: CropInput): Promise<ClassifierOutput> {\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n\n // Crop the face region\n const faceCrop = await cropRegion(input.frame.data, input.roi)\n\n // Resize to 112x112, NHWC layout (ArcFace expects NHWC)\n const layout = this.modelEntry.inputLayout ?? 'nhwc'\n const normalization = this.modelEntry.inputNormalization ?? 'zero-one'\n const normalized = await resizeAndNormalize(faceCrop, inputW, inputH, normalization, layout)\n\n // Run inference — output is 512-d embedding\n const rawEmbedding = await this.engine.run(normalized, [1, inputH, inputW, 3])\n\n // L2 normalize the embedding\n const embedding = l2Normalize(rawEmbedding)\n\n return {\n classifications: [\n {\n class: 'identity',\n score: 1.0,\n embedding,\n },\n ],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.dispose()\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'model',\n title: 'Model',\n columns: 1,\n fields: [\n {\n key: 'modelId',\n label: 'Model',\n type: 'model-selector',\n catalog: [...FACE_RECOGNITION_MODELS],\n allowCustom: false,\n allowConversion: false,\n acceptFormats: ['onnx', 'coreml', 'openvino'],\n requiredMetadata: ['inputSize', 'labels', 'outputFormat'],\n outputFormatHint: 'embedding',\n },\n ],\n },\n {\n id: 'runtime',\n title: 'Runtime',\n columns: 2,\n fields: [\n {\n key: 'runtime',\n label: 'Runtime',\n type: 'select',\n options: [\n { value: 'auto', label: 'Auto (recommended)' },\n { value: 'onnx', label: 'ONNX Runtime' },\n { value: 'coreml', label: 'CoreML (Apple)' },\n ],\n },\n {\n key: 'backend',\n label: 'Backend',\n type: 'select',\n dependsOn: { runtime: 'onnx' },\n options: [\n { value: 'cpu', label: 'CPU' },\n { value: 'coreml', label: 'CoreML' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return FACE_REC_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...FACE_RECOGNITION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return IDENTITY_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: this.engine?.runtime ?? 'onnx',\n device: this.engine?.device ?? 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n","/** L2 normalize a vector in-place, returning a new Float32Array */\nexport function l2Normalize(vec: Float32Array): Float32Array {\n let sumSq = 0\n for (let i = 0; i < vec.length; i++) {\n sumSq += vec[i]! * vec[i]!\n }\n const norm = Math.sqrt(sumSq)\n if (norm === 0) return new Float32Array(vec.length)\n\n const out = new Float32Array(vec.length)\n for (let i = 0; i < vec.length; i++) {\n out[i] = vec[i]! / norm\n }\n return out\n}\n\n/** Cosine similarity between two embeddings (assumes they are already L2-normalized) */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) throw new Error('Embedding length mismatch')\n let dot = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n }\n return dot\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,90 @@
1
+ // src/shared/postprocess/yolo.ts
2
+ function iou(a, b) {
3
+ const ax1 = a.x;
4
+ const ay1 = a.y;
5
+ const ax2 = a.x + a.w;
6
+ const ay2 = a.y + a.h;
7
+ const bx1 = b.x;
8
+ const by1 = b.y;
9
+ const bx2 = b.x + b.w;
10
+ const by2 = b.y + b.h;
11
+ const interX1 = Math.max(ax1, bx1);
12
+ const interY1 = Math.max(ay1, by1);
13
+ const interX2 = Math.min(ax2, bx2);
14
+ const interY2 = Math.min(ay2, by2);
15
+ const interW = Math.max(0, interX2 - interX1);
16
+ const interH = Math.max(0, interY2 - interY1);
17
+ const interArea = interW * interH;
18
+ if (interArea === 0) return 0;
19
+ const areaA = a.w * a.h;
20
+ const areaB = b.w * b.h;
21
+ const unionArea = areaA + areaB - interArea;
22
+ return unionArea === 0 ? 0 : interArea / unionArea;
23
+ }
24
+ function nms(boxes, iouThreshold) {
25
+ const indices = boxes.map((_, i) => i).sort((a, b) => boxes[b].score - boxes[a].score);
26
+ const kept = [];
27
+ const suppressed = /* @__PURE__ */ new Set();
28
+ for (const idx of indices) {
29
+ if (suppressed.has(idx)) continue;
30
+ kept.push(idx);
31
+ for (const other of indices) {
32
+ if (other === idx || suppressed.has(other)) continue;
33
+ if (iou(boxes[idx].bbox, boxes[other].bbox) > iouThreshold) {
34
+ suppressed.add(other);
35
+ }
36
+ }
37
+ }
38
+ return kept;
39
+ }
40
+ function yoloPostprocess(output, numClasses, numBoxes, options) {
41
+ const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options;
42
+ const candidates = [];
43
+ for (let i = 0; i < numBoxes; i++) {
44
+ const cx = output[0 * numBoxes + i];
45
+ const cy = output[1 * numBoxes + i];
46
+ const w = output[2 * numBoxes + i];
47
+ const h = output[3 * numBoxes + i];
48
+ let bestScore = -Infinity;
49
+ let bestClass = 0;
50
+ for (let j = 0; j < numClasses; j++) {
51
+ const score = output[(4 + j) * numBoxes + i];
52
+ if (score > bestScore) {
53
+ bestScore = score;
54
+ bestClass = j;
55
+ }
56
+ }
57
+ if (bestScore < confidence) continue;
58
+ const bbox = {
59
+ x: cx - w / 2,
60
+ y: cy - h / 2,
61
+ w,
62
+ h
63
+ };
64
+ candidates.push({ bbox, score: bestScore, classIdx: bestClass });
65
+ }
66
+ if (candidates.length === 0) return [];
67
+ const keptIndices = nms(candidates, iouThreshold);
68
+ return keptIndices.map((idx) => {
69
+ const { bbox, score, classIdx } = candidates[idx];
70
+ const label = labels[classIdx] ?? String(classIdx);
71
+ const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale));
72
+ const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale));
73
+ const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale));
74
+ const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale));
75
+ const finalBbox = { x, y, w: x2 - x, h: y2 - y };
76
+ return {
77
+ class: label,
78
+ originalClass: label,
79
+ score,
80
+ bbox: finalBbox
81
+ };
82
+ });
83
+ }
84
+
85
+ export {
86
+ iou,
87
+ nms,
88
+ yoloPostprocess
89
+ };
90
+ //# sourceMappingURL=chunk-KUO2BVFY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/postprocess/yolo.ts"],"sourcesContent":["import type { SpatialDetection, BoundingBox } from '@camstack/types'\n\nexport interface YoloPostprocessOptions {\n readonly confidence: number\n readonly iouThreshold: number\n readonly labels: readonly string[]\n readonly scale: number\n readonly padX: number\n readonly padY: number\n readonly originalWidth: number\n readonly originalHeight: number\n}\n\n/** Calculate IoU between two bounding boxes */\nexport function iou(a: BoundingBox, b: BoundingBox): number {\n const ax1 = a.x\n const ay1 = a.y\n const ax2 = a.x + a.w\n const ay2 = a.y + a.h\n\n const bx1 = b.x\n const by1 = b.y\n const bx2 = b.x + b.w\n const by2 = b.y + b.h\n\n const interX1 = Math.max(ax1, bx1)\n const interY1 = Math.max(ay1, by1)\n const interX2 = Math.min(ax2, bx2)\n const interY2 = Math.min(ay2, by2)\n\n const interW = Math.max(0, interX2 - interX1)\n const interH = Math.max(0, interY2 - interY1)\n const interArea = interW * interH\n\n if (interArea === 0) return 0\n\n const areaA = a.w * a.h\n const areaB = b.w * b.h\n const unionArea = areaA + areaB - interArea\n\n return unionArea === 0 ? 0 : interArea / unionArea\n}\n\n/** Non-maximum suppression — returns indices of kept boxes (sorted by score desc) */\nexport function nms(\n boxes: ReadonlyArray<{ readonly bbox: BoundingBox; readonly score: number }>,\n iouThreshold: number,\n): number[] {\n const indices = boxes\n .map((_, i) => i)\n .sort((a, b) => (boxes[b]!.score) - (boxes[a]!.score))\n\n const kept: number[] = []\n const suppressed = new Set<number>()\n\n for (const idx of indices) {\n if (suppressed.has(idx)) continue\n kept.push(idx)\n for (const other of indices) {\n if (other === idx || suppressed.has(other)) continue\n if (iou(boxes[idx]!.bbox, boxes[other]!.bbox) > iouThreshold) {\n suppressed.add(other)\n }\n }\n }\n\n return kept\n}\n\n/** Full YOLO v8/v9 postprocessing: filter → NMS → scale back to original coords */\nexport function yoloPostprocess(\n output: Float32Array,\n numClasses: number,\n numBoxes: number,\n options: YoloPostprocessOptions,\n): SpatialDetection[] {\n const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options\n\n interface Candidate {\n readonly bbox: BoundingBox\n readonly score: number\n readonly classIdx: number\n }\n\n const candidates: Candidate[] = []\n\n for (let i = 0; i < numBoxes; i++) {\n // YOLO v8/v9 output layout: [1, 4+numClasses, numBoxes] stored row-major\n const cx = output[0 * numBoxes + i]!\n const cy = output[1 * numBoxes + i]!\n const w = output[2 * numBoxes + i]!\n const h = output[3 * numBoxes + i]!\n\n let bestScore = -Infinity\n let bestClass = 0\n\n for (let j = 0; j < numClasses; j++) {\n const score = output[(4 + j) * numBoxes + i]!\n if (score > bestScore) {\n bestScore = score\n bestClass = j\n }\n }\n\n if (bestScore < confidence) continue\n\n // Convert cx,cy,w,h to x,y,w,h (top-left origin)\n const bbox: BoundingBox = {\n x: cx - w / 2,\n y: cy - h / 2,\n w,\n h,\n }\n\n candidates.push({ bbox, score: bestScore, classIdx: bestClass })\n }\n\n if (candidates.length === 0) return []\n\n const keptIndices = nms(candidates, iouThreshold)\n\n return keptIndices.map((idx) => {\n const { bbox, score, classIdx } = candidates[idx]!\n const label = labels[classIdx] ?? String(classIdx)\n\n // Transform from letterbox coords back to original image coords\n const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale))\n const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale))\n const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale))\n const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale))\n\n const finalBbox: BoundingBox = { x, y, w: x2 - x, h: y2 - y }\n\n return {\n class: label,\n originalClass: label,\n score,\n bbox: finalBbox,\n } satisfies SpatialDetection\n })\n}\n"],"mappings":"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","names":[]}