@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 @@
1
+ {"version":3,"sources":["../../../src/addons/plate-recognition/index.ts","../../../src/shared/image-utils.ts","../../../src/shared/postprocess/paddleocr.ts","../../../src/shared/engine-resolver.ts","../../../src/shared/node-engine.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 { PLATE_RECOGNITION_MODELS } from '@camstack/types'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { ctcDecode } from '../../shared/postprocess/paddleocr.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nconst PLATE_TEXT_LABEL: LabelDefinition = { id: 'plate-text', name: 'Plate Text' }\nconst PLATE_TEXT_LABELS: readonly LabelDefinition[] = [PLATE_TEXT_LABEL]\nconst PLATE_REC_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/** Load charset from dict.txt file — index 0 is the CTC blank token */\nfunction loadCharset(modelsDir: string, modelId: string): readonly string[] {\n // Try to find the dict file next to the model\n const dictNames = [\n `camstack-${modelId}-dict.txt`,\n `camstack-paddleocr-latin-dict.txt`,\n `camstack-paddleocr-en-dict.txt`,\n ]\n for (const name of dictNames) {\n const dictPath = path.join(modelsDir, name)\n if (fs.existsSync(dictPath)) {\n const lines = fs.readFileSync(dictPath, 'utf-8').split('\\n').filter((l) => l.length > 0)\n // PaddleOCR convention: blank token at index 0, then dict chars, then a space token at end\n return ['', ...lines, ' ']\n }\n }\n throw new Error(`PlateRecognitionAddon: dict.txt not found in ${modelsDir}`)\n}\n\n// Will be loaded during initialize()\nlet CHARSET: readonly string[] = []\n\nconst REQUIRED_STEPS: readonly RequiredStep[] = [\n { slot: 'cropper', outputClasses: ['plate'], description: 'Requires a plate detector' },\n]\n\nexport default class PlateRecognitionAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'plate-recognition'\n readonly slot = 'classifier' as const\n readonly inputClasses = ['plate'] as const\n readonly outputClasses = ['plate-text:*'] as const\n readonly slotPriority = 0\n readonly requiredSteps = REQUIRED_STEPS\n readonly manifest: AddonManifest = {\n id: 'plate-recognition',\n name: 'License Plate Recognition (OCR)',\n version: '0.1.0',\n description: 'PaddleOCR-based license plate text recognition',\n packageName: '@camstack/vision',\n slot: 'classifier',\n inputClasses: ['plate'],\n outputClasses: ['plate-text:*'],\n requiredSteps: REQUIRED_STEPS,\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'paddleocr-latin',\n runtime: 'auto',\n backend: 'cpu',\n minConfidence: 0.5,\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n private minConfidence = 0.5\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'paddleocr-latin'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n this.minConfidence = (cfg['minConfidence'] as number | undefined) ?? 0.5\n\n const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`PlateRecognitionAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n\n // Load charset from dict.txt\n CHARSET = loadCharset(ctx.locationPaths.models, modelId)\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 plate region\n const plateCrop = await cropRegion(input.frame.data, input.roi)\n\n // Resize to 320x48, normalize to [0,1], NCHW\n const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, 'zero-one', 'nchw')\n\n const output = await this.engine.run(normalized, [1, 3, inputH, inputW])\n\n // PaddleOCR CTC output shape: [1, seqLen, numChars]\n const numChars = CHARSET.length\n const seqLen = output.length / numChars\n const { text, confidence } = ctcDecode(output, seqLen, numChars, CHARSET)\n\n if (confidence < this.minConfidence || text.trim().length === 0) {\n return {\n classifications: [],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n return {\n classifications: [\n {\n class: 'plate-text',\n score: confidence,\n text,\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: [...PLATE_RECOGNITION_MODELS],\n allowCustom: false,\n allowConversion: false,\n acceptFormats: ['onnx', 'openvino'],\n requiredMetadata: ['inputSize', 'labels', 'outputFormat'],\n outputFormatHint: 'ocr',\n },\n ],\n },\n {\n id: 'thresholds',\n title: 'Recognition Settings',\n columns: 1,\n fields: [\n {\n key: 'minConfidence',\n label: 'Minimum Confidence',\n type: 'slider',\n min: 0.1,\n max: 1.0,\n step: 0.05,\n default: 0.5,\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return PLATE_REC_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...PLATE_RECOGNITION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return PLATE_TEXT_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","import sharp from 'sharp'\nimport type { BoundingBox } from '@camstack/types'\n\n/** Decode JPEG to raw RGB pixels */\nexport async function jpegToRgb(\n jpeg: Buffer,\n): Promise<{ data: Buffer; width: number; height: number }> {\n const { data, info } = await sharp(jpeg)\n .removeAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n return { data, width: info.width, height: info.height }\n}\n\n/** Crop a region from a JPEG buffer */\nexport async function cropRegion(jpeg: Buffer, roi: BoundingBox): Promise<Buffer> {\n return sharp(jpeg)\n .extract({\n left: Math.round(roi.x),\n top: Math.round(roi.y),\n width: Math.round(roi.w),\n height: Math.round(roi.h),\n })\n .jpeg()\n .toBuffer()\n}\n\n/** Letterbox resize for YOLO: resize preserving aspect ratio, pad to square */\nexport async function letterbox(\n jpeg: Buffer,\n targetSize: number,\n): Promise<{\n data: Float32Array\n scale: number\n padX: number\n padY: number\n originalWidth: number\n originalHeight: number\n}> {\n const meta = await sharp(jpeg).metadata()\n const originalWidth = meta.width ?? 0\n const originalHeight = meta.height ?? 0\n\n const scale = Math.min(targetSize / originalWidth, targetSize / originalHeight)\n const scaledWidth = Math.round(originalWidth * scale)\n const scaledHeight = Math.round(originalHeight * scale)\n\n const padX = Math.floor((targetSize - scaledWidth) / 2)\n const padY = Math.floor((targetSize - scaledHeight) / 2)\n\n const { data } = await sharp(jpeg)\n .resize(scaledWidth, scaledHeight)\n .extend({\n top: padY,\n bottom: targetSize - scaledHeight - padY,\n left: padX,\n right: targetSize - scaledWidth - padX,\n background: { r: 114, g: 114, b: 114 },\n })\n .removeAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n // Convert HWC uint8 to CHW float [0,1]\n const numPixels = targetSize * targetSize\n const float32 = new Float32Array(3 * numPixels)\n for (let i = 0; i < numPixels; i++) {\n const srcBase = i * 3\n float32[0 * numPixels + i] = (data[srcBase]! / 255)\n float32[1 * numPixels + i] = (data[srcBase + 1]! / 255)\n float32[2 * numPixels + i] = (data[srcBase + 2]! / 255)\n }\n\n return { data: float32, scale, padX, padY, originalWidth, originalHeight }\n}\n\n/** Resize and normalize to Float32Array */\nexport async function resizeAndNormalize(\n jpeg: Buffer,\n targetWidth: number,\n targetHeight: number,\n normalization: 'zero-one' | 'imagenet' | 'none',\n layout: 'nchw' | 'nhwc',\n): Promise<Float32Array> {\n const { data } = await sharp(jpeg)\n .resize(targetWidth, targetHeight)\n .removeAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n const numPixels = targetWidth * targetHeight\n const float32 = new Float32Array(3 * numPixels)\n\n // ImageNet mean and std per channel\n const mean = [0.485, 0.456, 0.406]\n const std = [0.229, 0.224, 0.225]\n\n if (layout === 'nchw') {\n for (let i = 0; i < numPixels; i++) {\n const srcBase = i * 3\n for (let c = 0; c < 3; c++) {\n const raw = data[srcBase + c]! / 255\n let val: number\n if (normalization === 'zero-one') {\n val = raw\n } else if (normalization === 'imagenet') {\n val = (raw - mean[c]!) / std[c]!\n } else {\n val = data[srcBase + c]!\n }\n float32[c * numPixels + i] = val\n }\n }\n } else {\n // nhwc\n for (let i = 0; i < numPixels; i++) {\n const srcBase = i * 3\n for (let c = 0; c < 3; c++) {\n const raw = data[srcBase + c]! / 255\n let val: number\n if (normalization === 'zero-one') {\n val = raw\n } else if (normalization === 'imagenet') {\n val = (raw - mean[c]!) / std[c]!\n } else {\n val = data[srcBase + c]!\n }\n float32[i * 3 + c] = val\n }\n }\n }\n\n return float32\n}\n\n/** Convert raw RGB to grayscale Uint8Array */\nexport function rgbToGrayscale(rgb: Buffer, width: number, height: number): Uint8Array {\n const numPixels = width * height\n const gray = new Uint8Array(numPixels)\n for (let i = 0; i < numPixels; i++) {\n const r = rgb[i * 3]!\n const g = rgb[i * 3 + 1]!\n const b = rgb[i * 3 + 2]!\n // BT.601 luma\n gray[i] = Math.round(0.299 * r + 0.587 * g + 0.114 * b)\n }\n return gray\n}\n","/** Decode CTC output to text.\n *\n * Output shape: [1, seqLen, numChars]\n * Algorithm: argmax per timestep → collapse consecutive duplicates → remove blank (index 0) → join\n */\nexport function ctcDecode(\n output: Float32Array,\n seqLen: number,\n numChars: number,\n charset: readonly string[], // index 0 = blank token\n): { text: string; confidence: number } {\n // Step 1: argmax per timestep + track confidence as mean of selected scores\n let totalLogScore = 0\n const rawIndices: number[] = []\n\n for (let t = 0; t < seqLen; t++) {\n const offset = t * numChars\n let bestIdx = 0\n let bestVal = output[offset]!\n\n for (let c = 1; c < numChars; c++) {\n const val = output[offset + c]!\n if (val > bestVal) {\n bestVal = val\n bestIdx = c\n }\n }\n\n rawIndices.push(bestIdx)\n totalLogScore += bestVal\n }\n\n // Step 2: collapse consecutive duplicates\n const collapsed: number[] = []\n for (let i = 0; i < rawIndices.length; i++) {\n const cur = rawIndices[i]!\n if (i === 0 || cur !== rawIndices[i - 1]) {\n collapsed.push(cur)\n }\n }\n\n // Step 3: remove blank (index 0)\n const filtered = collapsed.filter((idx) => idx !== 0)\n\n // Step 4: join characters\n const text = filtered.map((idx) => charset[idx] ?? '').join('')\n\n const confidence = seqLen > 0 ? totalLogScore / seqLen : 0\n\n return { text, confidence }\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","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"],"mappings":"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","names":["sharp","sharp","path","fs","path"]}
@@ -0,0 +1,9 @@
1
+ import {
2
+ PlateRecognitionAddon
3
+ } from "../../chunk-Z26BVC7S.mjs";
4
+ import "../../chunk-6OR5TE7A.mjs";
5
+ import "../../chunk-J3IUBPRE.mjs";
6
+ export {
7
+ PlateRecognitionAddon as default
8
+ };
9
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,229 @@
1
+ import {
2
+ jpegToRgb,
3
+ rgbToGrayscale
4
+ } from "./chunk-6OR5TE7A.mjs";
5
+
6
+ // src/addons/motion-detection/frame-diff.ts
7
+ function detectMotion(current, previous, width, height, threshold, minArea) {
8
+ const numPixels = width * height;
9
+ const mask = new Uint8Array(numPixels);
10
+ for (let i = 0; i < numPixels; i++) {
11
+ mask[i] = Math.abs((current[i] ?? 0) - (previous[i] ?? 0)) > threshold ? 1 : 0;
12
+ }
13
+ const labels = new Int32Array(numPixels).fill(0);
14
+ const parent = new Int32Array(numPixels + 1).fill(0);
15
+ let nextLabel = 1;
16
+ function findRoot(x) {
17
+ while (parent[x] !== x) {
18
+ parent[x] = parent[parent[x]];
19
+ x = parent[x];
20
+ }
21
+ return x;
22
+ }
23
+ function union(a, b) {
24
+ const ra = findRoot(a);
25
+ const rb = findRoot(b);
26
+ if (ra !== rb) parent[rb] = ra;
27
+ return ra;
28
+ }
29
+ for (let i = 0; i <= numPixels; i++) {
30
+ parent[i] = i;
31
+ }
32
+ for (let y = 0; y < height; y++) {
33
+ for (let x = 0; x < width; x++) {
34
+ const idx = y * width + x;
35
+ if (!mask[idx]) continue;
36
+ const above = y > 0 ? labels[(y - 1) * width + x] ?? 0 : 0;
37
+ const left = x > 0 ? labels[y * width + (x - 1)] ?? 0 : 0;
38
+ if (above === 0 && left === 0) {
39
+ labels[idx] = nextLabel;
40
+ parent[nextLabel] = nextLabel;
41
+ nextLabel++;
42
+ } else if (above !== 0 && left === 0) {
43
+ labels[idx] = above;
44
+ } else if (above === 0 && left !== 0) {
45
+ labels[idx] = left;
46
+ } else {
47
+ labels[idx] = union(above, left);
48
+ }
49
+ }
50
+ }
51
+ for (let i = 0; i < numPixels; i++) {
52
+ if (labels[i]) {
53
+ labels[i] = findRoot(labels[i]);
54
+ }
55
+ }
56
+ const bboxMap = /* @__PURE__ */ new Map();
57
+ for (let y = 0; y < height; y++) {
58
+ for (let x = 0; x < width; x++) {
59
+ const idx = y * width + x;
60
+ const label = labels[idx];
61
+ if (!label) continue;
62
+ const diff = Math.abs((current[idx] ?? 0) - (previous[idx] ?? 0));
63
+ const existing = bboxMap.get(label);
64
+ if (existing) {
65
+ existing.minX = Math.min(existing.minX, x);
66
+ existing.minY = Math.min(existing.minY, y);
67
+ existing.maxX = Math.max(existing.maxX, x);
68
+ existing.maxY = Math.max(existing.maxY, y);
69
+ existing.count++;
70
+ existing.intensitySum += diff;
71
+ } else {
72
+ bboxMap.set(label, {
73
+ minX: x,
74
+ minY: y,
75
+ maxX: x,
76
+ maxY: y,
77
+ count: 1,
78
+ intensitySum: diff
79
+ });
80
+ }
81
+ }
82
+ }
83
+ const regions = [];
84
+ for (const [, info] of bboxMap) {
85
+ if (info.count < minArea) continue;
86
+ regions.push({
87
+ bbox: {
88
+ x: info.minX,
89
+ y: info.minY,
90
+ w: info.maxX - info.minX + 1,
91
+ h: info.maxY - info.minY + 1
92
+ },
93
+ pixelCount: info.count,
94
+ intensity: info.intensitySum / info.count
95
+ });
96
+ }
97
+ return regions;
98
+ }
99
+
100
+ // src/addons/motion-detection/index.ts
101
+ var MOTION_LABEL = { id: "motion", name: "Motion" };
102
+ var MOTION_LABELS = [MOTION_LABEL];
103
+ var EMPTY_CLASS_MAP = { mapping: {}, preserveOriginal: true };
104
+ var MotionDetectionAddon = class {
105
+ id = "motion-detection";
106
+ slot = "detector";
107
+ inputClasses = null;
108
+ outputClasses = ["motion"];
109
+ slotPriority = 10;
110
+ // runs first — feeds other detectors
111
+ manifest = {
112
+ id: "motion-detection",
113
+ name: "Motion Detection",
114
+ version: "0.1.0",
115
+ description: "Frame-differencing motion detector \u2014 no inference engine required",
116
+ packageName: "@camstack/vision",
117
+ slot: "detector",
118
+ inputClasses: void 0,
119
+ outputClasses: ["motion"],
120
+ supportsCustomModels: false,
121
+ mayRequirePython: false,
122
+ defaultConfig: {
123
+ threshold: 25,
124
+ minArea: 500
125
+ }
126
+ };
127
+ previousGray = null;
128
+ previousWidth = 0;
129
+ previousHeight = 0;
130
+ threshold = 25;
131
+ minArea = 500;
132
+ async initialize(ctx) {
133
+ const cfg = ctx.addonConfig;
134
+ this.threshold = cfg["threshold"] ?? 25;
135
+ this.minArea = cfg["minArea"] ?? 500;
136
+ }
137
+ async detect(frame) {
138
+ const start = Date.now();
139
+ const { data, width, height } = await jpegToRgb(frame.data);
140
+ const currentGray = rgbToGrayscale(data, width, height);
141
+ if (!this.previousGray || this.previousWidth !== width || this.previousHeight !== height) {
142
+ this.previousGray = currentGray;
143
+ this.previousWidth = width;
144
+ this.previousHeight = height;
145
+ return { detections: [], inferenceMs: Date.now() - start, modelId: "frame-diff" };
146
+ }
147
+ const regions = detectMotion(
148
+ currentGray,
149
+ this.previousGray,
150
+ width,
151
+ height,
152
+ this.threshold,
153
+ this.minArea
154
+ );
155
+ this.previousGray = currentGray;
156
+ const detections = regions.map((r) => ({
157
+ class: "motion",
158
+ originalClass: "motion",
159
+ score: Math.min(1, r.intensity / 255),
160
+ bbox: r.bbox
161
+ }));
162
+ return {
163
+ detections,
164
+ inferenceMs: Date.now() - start,
165
+ modelId: "frame-diff"
166
+ };
167
+ }
168
+ async shutdown() {
169
+ this.previousGray = null;
170
+ }
171
+ getConfigSchema() {
172
+ return {
173
+ sections: [
174
+ {
175
+ id: "motion",
176
+ title: "Motion Detection",
177
+ columns: 2,
178
+ fields: [
179
+ {
180
+ key: "threshold",
181
+ label: "Pixel Difference Threshold",
182
+ description: "Minimum per-pixel intensity change to count as motion (0-255)",
183
+ type: "slider",
184
+ min: 5,
185
+ max: 100,
186
+ step: 5,
187
+ default: 25
188
+ },
189
+ {
190
+ key: "minArea",
191
+ label: "Minimum Region Area (px)",
192
+ description: "Minimum number of changed pixels to report a motion region",
193
+ type: "number",
194
+ min: 50,
195
+ max: 1e4
196
+ }
197
+ ]
198
+ }
199
+ ]
200
+ };
201
+ }
202
+ getClassMap() {
203
+ return EMPTY_CLASS_MAP;
204
+ }
205
+ getModelCatalog() {
206
+ return [];
207
+ }
208
+ getAvailableModels() {
209
+ return [];
210
+ }
211
+ getActiveLabels() {
212
+ return MOTION_LABELS;
213
+ }
214
+ async probe() {
215
+ return {
216
+ available: true,
217
+ runtime: "onnx",
218
+ // no inference; satisfies the type (any runtime works)
219
+ device: "cpu",
220
+ capabilities: ["fp32"]
221
+ };
222
+ }
223
+ };
224
+
225
+ export {
226
+ detectMotion,
227
+ MotionDetectionAddon
228
+ };
229
+ //# sourceMappingURL=chunk-3BKYLBBH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/addons/motion-detection/frame-diff.ts","../src/addons/motion-detection/index.ts"],"sourcesContent":["import type { BoundingBox } from '@camstack/types'\n\nexport interface MotionRegion {\n readonly bbox: BoundingBox\n readonly pixelCount: number\n readonly intensity: number\n}\n\n/**\n * Detect motion by frame differencing.\n *\n * @param current - Grayscale pixel array for the current frame (Uint8Array, length = width * height)\n * @param previous - Grayscale pixel array for the previous frame\n * @param width - Frame width in pixels\n * @param height - Frame height in pixels\n * @param threshold - Pixel diff threshold 0-255; differences below this are ignored\n * @param minArea - Minimum number of changed pixels for a region to be reported\n */\nexport function detectMotion(\n current: Uint8Array,\n previous: Uint8Array,\n width: number,\n height: number,\n threshold: number,\n minArea: number,\n): MotionRegion[] {\n const numPixels = width * height\n\n // Step 1: Compute binary mask — 1 where abs(current - previous) > threshold\n const mask = new Uint8Array(numPixels)\n for (let i = 0; i < numPixels; i++) {\n mask[i] = Math.abs((current[i] ?? 0) - (previous[i] ?? 0)) > threshold ? 1 : 0\n }\n\n // Step 2: Two-pass connected component labeling\n const labels = new Int32Array(numPixels).fill(0)\n const parent = new Int32Array(numPixels + 1).fill(0)\n let nextLabel = 1\n\n // Union-Find helpers\n function findRoot(x: number): number {\n while (parent[x] !== x) {\n parent[x] = parent[parent[x]!]! // path compression\n x = parent[x]!\n }\n return x\n }\n\n function union(a: number, b: number): number {\n const ra = findRoot(a)\n const rb = findRoot(b)\n if (ra !== rb) parent[rb] = ra\n return ra\n }\n\n // Initialize parent array as identity\n for (let i = 0; i <= numPixels; i++) {\n parent[i] = i\n }\n\n // First pass: assign provisional labels\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x\n if (!mask[idx]) continue\n\n const above = y > 0 ? labels[(y - 1) * width + x] ?? 0 : 0\n const left = x > 0 ? labels[y * width + (x - 1)] ?? 0 : 0\n\n if (above === 0 && left === 0) {\n labels[idx] = nextLabel\n parent[nextLabel] = nextLabel\n nextLabel++\n } else if (above !== 0 && left === 0) {\n labels[idx] = above\n } else if (above === 0 && left !== 0) {\n labels[idx] = left\n } else {\n // Both neighbors — merge\n labels[idx] = union(above, left)\n }\n }\n }\n\n // Second pass: resolve all labels to roots\n for (let i = 0; i < numPixels; i++) {\n if (labels[i]) {\n labels[i] = findRoot(labels[i]!)\n }\n }\n\n // Step 3: Collect bounding boxes and pixel counts per root label\n const bboxMap = new Map<\n number,\n { minX: number; minY: number; maxX: number; maxY: number; count: number; intensitySum: number }\n >()\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x\n const label = labels[idx]\n if (!label) continue\n\n const diff = Math.abs((current[idx] ?? 0) - (previous[idx] ?? 0))\n const existing = bboxMap.get(label)\n if (existing) {\n existing.minX = Math.min(existing.minX, x)\n existing.minY = Math.min(existing.minY, y)\n existing.maxX = Math.max(existing.maxX, x)\n existing.maxY = Math.max(existing.maxY, y)\n existing.count++\n existing.intensitySum += diff\n } else {\n bboxMap.set(label, {\n minX: x,\n minY: y,\n maxX: x,\n maxY: y,\n count: 1,\n intensitySum: diff,\n })\n }\n }\n }\n\n // Step 4: Filter by minimum area and build result\n const regions: MotionRegion[] = []\n for (const [, info] of bboxMap) {\n if (info.count < minArea) continue\n regions.push({\n bbox: {\n x: info.minX,\n y: info.minY,\n w: info.maxX - info.minX + 1,\n h: info.maxY - info.minY + 1,\n },\n pixelCount: info.count,\n intensity: info.intensitySum / info.count,\n })\n }\n\n return regions\n}\n","import type {\n IDetectorProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n FrameInput,\n DetectorOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n SpatialDetection,\n} from '@camstack/types'\nimport { jpegToRgb, rgbToGrayscale } from '../../shared/image-utils.js'\nimport { detectMotion } from './frame-diff.js'\n\nconst MOTION_LABEL: LabelDefinition = { id: 'motion', name: 'Motion' }\nconst MOTION_LABELS: readonly LabelDefinition[] = [MOTION_LABEL]\nconst EMPTY_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nexport default class MotionDetectionAddon implements IDetectorProvider, IDetectionAddon {\n readonly id = 'motion-detection'\n readonly slot = 'detector' as const\n readonly inputClasses: readonly string[] | null = null\n readonly outputClasses = ['motion'] as const\n readonly slotPriority = 10 // runs first — feeds other detectors\n readonly manifest: AddonManifest = {\n id: 'motion-detection',\n name: 'Motion Detection',\n version: '0.1.0',\n description: 'Frame-differencing motion detector — no inference engine required',\n packageName: '@camstack/vision',\n slot: 'detector',\n inputClasses: undefined,\n outputClasses: ['motion'],\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n threshold: 25,\n minArea: 500,\n },\n }\n\n private previousGray: Uint8Array | null = null\n private previousWidth = 0\n private previousHeight = 0\n private threshold = 25\n private minArea = 500\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n this.threshold = (cfg['threshold'] as number | undefined) ?? 25\n this.minArea = (cfg['minArea'] as number | undefined) ?? 500\n }\n\n async detect(frame: FrameInput): Promise<DetectorOutput> {\n const start = Date.now()\n\n const { data, width, height } = await jpegToRgb(frame.data)\n const currentGray = rgbToGrayscale(data, width, height)\n\n if (!this.previousGray || this.previousWidth !== width || this.previousHeight !== height) {\n // Store first frame and return empty — no previous to diff against\n this.previousGray = currentGray\n this.previousWidth = width\n this.previousHeight = height\n return { detections: [], inferenceMs: Date.now() - start, modelId: 'frame-diff' }\n }\n\n const regions = detectMotion(\n currentGray,\n this.previousGray,\n width,\n height,\n this.threshold,\n this.minArea,\n )\n\n this.previousGray = currentGray\n\n const detections: SpatialDetection[] = regions.map((r) => ({\n class: 'motion',\n originalClass: 'motion',\n score: Math.min(1, r.intensity / 255),\n bbox: r.bbox,\n }))\n\n return {\n detections,\n inferenceMs: Date.now() - start,\n modelId: 'frame-diff',\n }\n }\n\n async shutdown(): Promise<void> {\n this.previousGray = null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'motion',\n title: 'Motion Detection',\n columns: 2,\n fields: [\n {\n key: 'threshold',\n label: 'Pixel Difference Threshold',\n description: 'Minimum per-pixel intensity change to count as motion (0-255)',\n type: 'slider',\n min: 5,\n max: 100,\n step: 5,\n default: 25,\n },\n {\n key: 'minArea',\n label: 'Minimum Region Area (px)',\n description: 'Minimum number of changed pixels to report a motion region',\n type: 'number',\n min: 50,\n max: 10000,\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return EMPTY_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return []\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return MOTION_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: 'onnx', // no inference; satisfies the type (any runtime works)\n device: 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,203 @@
1
+ import {
2
+ resolveEngine
3
+ } from "./chunk-J3IUBPRE.mjs";
4
+
5
+ // src/addons/audio-classification/index.ts
6
+ import { AUDIO_CLASSIFICATION_MODELS } from "@camstack/types";
7
+
8
+ // src/shared/postprocess/yamnet.ts
9
+ function yamnetPostprocess(output, numFrames, numClasses, classNames, minScore) {
10
+ const avgScores = new Float32Array(numClasses);
11
+ for (let f = 0; f < numFrames; f++) {
12
+ for (let c = 0; c < numClasses; c++) {
13
+ const prev = avgScores[c] ?? 0;
14
+ avgScores[c] = prev + (output[f * numClasses + c] ?? 0);
15
+ }
16
+ }
17
+ if (numFrames > 0) {
18
+ for (let c = 0; c < numClasses; c++) {
19
+ const val = avgScores[c] ?? 0;
20
+ avgScores[c] = val / numFrames;
21
+ }
22
+ }
23
+ const results = [];
24
+ for (let c = 0; c < numClasses; c++) {
25
+ const score = avgScores[c];
26
+ if (score >= minScore) {
27
+ results.push({
28
+ className: classNames[c] ?? String(c),
29
+ score
30
+ });
31
+ }
32
+ }
33
+ return results.sort((a, b) => b.score - a.score);
34
+ }
35
+
36
+ // src/addons/audio-classification/index.ts
37
+ var YAMNET_NUM_CLASSES = 521;
38
+ var AUDIO_EVENT_LABEL = { id: "audio-event", name: "Audio Event" };
39
+ var AUDIO_LABELS = [AUDIO_EVENT_LABEL];
40
+ var AUDIO_CLASS_MAP = { mapping: {}, preserveOriginal: true };
41
+ var AudioClassificationAddon = class {
42
+ id = "audio-classification";
43
+ slot = "classifier";
44
+ inputClasses = null;
45
+ outputClasses = ["audio-event:*"];
46
+ slotPriority = 0;
47
+ manifest = {
48
+ id: "audio-classification",
49
+ name: "Audio Classification",
50
+ version: "0.1.0",
51
+ description: "YAMNet-based audio event classification from audio waveform",
52
+ packageName: "@camstack/vision",
53
+ slot: "classifier",
54
+ inputClasses: void 0,
55
+ outputClasses: ["audio-event:*"],
56
+ supportsCustomModels: false,
57
+ mayRequirePython: false,
58
+ defaultConfig: {
59
+ modelId: "yamnet",
60
+ runtime: "auto",
61
+ backend: "cpu",
62
+ minScore: 0.3
63
+ }
64
+ };
65
+ engine;
66
+ modelEntry;
67
+ minScore = 0.3;
68
+ async initialize(ctx) {
69
+ const cfg = ctx.addonConfig;
70
+ const modelId = cfg["modelId"] ?? "yamnet";
71
+ const runtime = cfg["runtime"] ?? "auto";
72
+ const backend = cfg["backend"] ?? "cpu";
73
+ this.minScore = cfg["minScore"] ?? 0.3;
74
+ const entry = AUDIO_CLASSIFICATION_MODELS.find((m) => m.id === modelId);
75
+ if (!entry) {
76
+ throw new Error(`AudioClassificationAddon: 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
+ /**
88
+ * classify() receives a CropInput but internally treats input.frame.data as raw audio context.
89
+ * For audio, the actual audio chunk data should be stored in frame.data as a Float32Array
90
+ * serialized into a Buffer (little-endian float32 samples at 16 kHz).
91
+ *
92
+ * The CropInput.roi is not used for audio — it is ignored.
93
+ */
94
+ async classify(input) {
95
+ const start = Date.now();
96
+ const buf = input.frame.data;
97
+ const numSamples = Math.floor(buf.length / 4);
98
+ const audioData = new Float32Array(numSamples);
99
+ for (let i = 0; i < numSamples; i++) {
100
+ audioData[i] = buf.readFloatLE(i * 4);
101
+ }
102
+ const output = await this.engine.run(audioData, [numSamples]);
103
+ const numFrames = output.length / YAMNET_NUM_CLASSES;
104
+ const classNames = this.modelEntry.labels.map((l) => l.id);
105
+ while (classNames.length < YAMNET_NUM_CLASSES) {
106
+ classNames.push(`class_${classNames.length}`);
107
+ }
108
+ const results = yamnetPostprocess(
109
+ output,
110
+ Math.round(numFrames),
111
+ YAMNET_NUM_CLASSES,
112
+ classNames,
113
+ this.minScore
114
+ );
115
+ const classifications = results.map((r) => ({
116
+ class: `audio-event:${r.className}`,
117
+ score: r.score
118
+ }));
119
+ return {
120
+ classifications,
121
+ inferenceMs: Date.now() - start,
122
+ modelId: this.modelEntry.id
123
+ };
124
+ }
125
+ async shutdown() {
126
+ await this.engine?.dispose();
127
+ }
128
+ getConfigSchema() {
129
+ return {
130
+ sections: [
131
+ {
132
+ id: "runtime",
133
+ title: "Runtime",
134
+ columns: 2,
135
+ fields: [
136
+ {
137
+ key: "runtime",
138
+ label: "Runtime",
139
+ type: "select",
140
+ options: [
141
+ { value: "auto", label: "Auto (recommended)" },
142
+ { value: "onnx", label: "ONNX Runtime" },
143
+ { value: "openvino", label: "OpenVINO (Intel)" }
144
+ ]
145
+ },
146
+ {
147
+ key: "backend",
148
+ label: "Backend",
149
+ type: "select",
150
+ dependsOn: { runtime: "onnx" },
151
+ options: [
152
+ { value: "cpu", label: "CPU" },
153
+ { value: "cuda", label: "CUDA (NVIDIA)" }
154
+ ]
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ id: "thresholds",
160
+ title: "Classification Settings",
161
+ columns: 1,
162
+ fields: [
163
+ {
164
+ key: "minScore",
165
+ label: "Minimum Score",
166
+ type: "slider",
167
+ min: 0.05,
168
+ max: 1,
169
+ step: 0.05,
170
+ default: 0.3
171
+ }
172
+ ]
173
+ }
174
+ ]
175
+ };
176
+ }
177
+ getClassMap() {
178
+ return AUDIO_CLASS_MAP;
179
+ }
180
+ getModelCatalog() {
181
+ return [...AUDIO_CLASSIFICATION_MODELS];
182
+ }
183
+ getAvailableModels() {
184
+ return [];
185
+ }
186
+ getActiveLabels() {
187
+ return AUDIO_LABELS;
188
+ }
189
+ async probe() {
190
+ return {
191
+ available: true,
192
+ runtime: this.engine?.runtime ?? "onnx",
193
+ device: this.engine?.device ?? "cpu",
194
+ capabilities: ["fp32"]
195
+ };
196
+ }
197
+ };
198
+
199
+ export {
200
+ yamnetPostprocess,
201
+ AudioClassificationAddon
202
+ };
203
+ //# sourceMappingURL=chunk-4PC262GU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/addons/audio-classification/index.ts","../src/shared/postprocess/yamnet.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} from '@camstack/types'\nimport { AUDIO_CLASSIFICATION_MODELS } from '@camstack/types'\nimport { yamnetPostprocess } from '../../shared/postprocess/yamnet.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\n// YAMNet recognizes 521 audio classes from the AudioSet ontology.\n// This is the top-level subset used for filtering. Full class list lives in the model catalog.\nconst YAMNET_NUM_CLASSES = 521\n\nconst AUDIO_EVENT_LABEL: LabelDefinition = { id: 'audio-event', name: 'Audio Event' }\nconst AUDIO_LABELS: readonly LabelDefinition[] = [AUDIO_EVENT_LABEL]\nconst AUDIO_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nexport default class AudioClassificationAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'audio-classification'\n readonly slot = 'classifier' as const\n readonly inputClasses: readonly string[] | null = null\n readonly outputClasses = ['audio-event:*'] as const\n readonly slotPriority = 0\n readonly manifest: AddonManifest = {\n id: 'audio-classification',\n name: 'Audio Classification',\n version: '0.1.0',\n description: 'YAMNet-based audio event classification from audio waveform',\n packageName: '@camstack/vision',\n slot: 'classifier',\n inputClasses: undefined,\n outputClasses: ['audio-event:*'],\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'yamnet',\n runtime: 'auto',\n backend: 'cpu',\n minScore: 0.3,\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n private minScore = 0.3\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'yamnet'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n this.minScore = (cfg['minScore'] as number | undefined) ?? 0.3\n\n const entry = AUDIO_CLASSIFICATION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`AudioClassificationAddon: 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 /**\n * classify() receives a CropInput but internally treats input.frame.data as raw audio context.\n * For audio, the actual audio chunk data should be stored in frame.data as a Float32Array\n * serialized into a Buffer (little-endian float32 samples at 16 kHz).\n *\n * The CropInput.roi is not used for audio — it is ignored.\n */\n async classify(input: CropInput): Promise<ClassifierOutput> {\n const start = Date.now()\n\n // Extract raw float32 audio samples from the buffer\n const buf = input.frame.data\n const numSamples = Math.floor(buf.length / 4)\n const audioData = new Float32Array(numSamples)\n for (let i = 0; i < numSamples; i++) {\n audioData[i] = buf.readFloatLE(i * 4)\n }\n\n // YAMNet expects 1D waveform at 16 kHz\n const output = await this.engine.run(audioData, [numSamples])\n\n // YAMNet output shape: [numFrames, numClasses]\n const numFrames = output.length / YAMNET_NUM_CLASSES\n\n // Use model label ids as class names (or index strings if catalog is sparse)\n const classNames: string[] = this.modelEntry.labels.map((l) => l.id)\n // Pad to full 521 classes if the catalog only has a subset\n while (classNames.length < YAMNET_NUM_CLASSES) {\n classNames.push(`class_${classNames.length}`)\n }\n\n const results = yamnetPostprocess(\n output,\n Math.round(numFrames),\n YAMNET_NUM_CLASSES,\n classNames,\n this.minScore,\n )\n\n const classifications = results.map((r) => ({\n class: `audio-event:${r.className}`,\n score: r.score,\n }))\n\n return {\n classifications,\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: '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: 'openvino', label: 'OpenVINO (Intel)' },\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: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n ],\n },\n {\n id: 'thresholds',\n title: 'Classification Settings',\n columns: 1,\n fields: [\n {\n key: 'minScore',\n label: 'Minimum Score',\n type: 'slider',\n min: 0.05,\n max: 1.0,\n step: 0.05,\n default: 0.3,\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return AUDIO_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...AUDIO_CLASSIFICATION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return AUDIO_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","export interface AudioClassification {\n readonly className: string\n readonly score: number\n}\n\n/** Average YAMNET scores across frames, return top classes above threshold */\nexport function yamnetPostprocess(\n output: Float32Array,\n numFrames: number,\n numClasses: number,\n classNames: readonly string[],\n minScore: number,\n): AudioClassification[] {\n // Average across frames\n const avgScores = new Float32Array(numClasses)\n for (let f = 0; f < numFrames; f++) {\n for (let c = 0; c < numClasses; c++) {\n const prev = avgScores[c] ?? 0\n avgScores[c] = prev + (output[f * numClasses + c] ?? 0)\n }\n }\n if (numFrames > 0) {\n for (let c = 0; c < numClasses; c++) {\n const val = avgScores[c] ?? 0\n avgScores[c] = val / numFrames\n }\n }\n\n // Collect classes above threshold\n const results: AudioClassification[] = []\n for (let c = 0; c < numClasses; c++) {\n const score = avgScores[c]!\n if (score >= minScore) {\n results.push({\n className: classNames[c] ?? String(c),\n score,\n })\n }\n }\n\n // Sort descending by score\n return results.sort((a, b) => b.score - a.score)\n}\n"],"mappings":"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","names":[]}