@camstack/addon-post-analysis 0.1.19 → 0.2.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.
- package/dist/dist-4mTLJ7BJ.mjs +20750 -0
- package/dist/dist-CS2K80so.js +20933 -0
- package/dist/embedding-encoder/index.js +977 -902
- package/dist/embedding-encoder/index.mjs +967 -860
- package/dist/enrichment-engine/index.js +834 -833
- package/dist/enrichment-engine/index.mjs +828 -832
- package/dist/pipeline-analytics/_stub.js +1680 -1397
- package/dist/pipeline-analytics/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DOSUJ-U0.mjs +156 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DJvmVCso.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs +26 -0
- package/dist/pipeline-analytics/dist-CYZr2fwk.mjs +2726 -0
- package/dist/pipeline-analytics/hostInit-BazRS2O7.mjs +129 -0
- package/dist/pipeline-analytics/index.js +7133 -2558
- package/dist/pipeline-analytics/index.mjs +7124 -2557
- package/dist/pipeline-analytics/remoteEntry.js +134 -2973
- package/dist/pipeline-analytics/remoteEntry.ssr.js +33 -0
- package/dist/pipeline-analytics/virtualExposes-BgYzpJZG.mjs +27 -0
- package/dist/pipeline-analytics/virtual_mf-exposes-ssr___mfe_internal__addon_pipeline_analytics_widgets__remoteEntry_js-D7qgWCKX.mjs +10 -0
- package/dist/resolve-frame-5lMxmeI1.js +57 -0
- package/dist/resolve-frame-CT1T1tWy.mjs +44 -0
- package/package.json +26 -32
- package/dist/embedding-encoder/index.js.map +0 -1
- package/dist/embedding-encoder/index.mjs.map +0 -1
- package/dist/enrichment-engine/index.js.map +0 -1
- package/dist/enrichment-engine/index.mjs.map +0 -1
- package/dist/ffmpeg-config-DRONlBsj.mjs +0 -56
- package/dist/ffmpeg-config-DRONlBsj.mjs.map +0 -1
- package/dist/ffmpeg-config-uANz3sV5.js +0 -73
- package/dist/ffmpeg-config-uANz3sV5.js.map +0 -1
- package/dist/index-BFbwYH1P.js +0 -14343
- package/dist/index-BFbwYH1P.js.map +0 -1
- package/dist/index-BrTlzsrE.mjs +0 -14344
- package/dist/index-BrTlzsrE.mjs.map +0 -1
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioHistoryChart.d.ts +0 -4
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioMetricsPanel.d.ts +0 -10
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/DetectionHistoryChart.d.ts +0 -4
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/LiveStatsTab.d.ts +0 -5
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/MotionHistoryChart.d.ts +0 -4
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyHistoryChart.d.ts +0 -4
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyPanel.d.ts +0 -10
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/chart-utils.d.ts +0 -97
- package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/index.d.ts +0 -29
- package/dist/pipeline-analytics/@mf-types/widgets.d.ts +0 -2
- package/dist/pipeline-analytics/@mf-types.d.ts +0 -3
- package/dist/pipeline-analytics/@mf-types.zip +0 -0
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs +0 -12
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-NjF4kxzW.mjs +0 -19
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-7HAAnpQu.mjs +0 -18
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-DoWbefqS.mjs +0 -104
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-52bfkwC8.mjs +0 -85
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-CVrnrGED.mjs +0 -62
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-BZTB2scQ.mjs +0 -88
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CJO5YKGV.mjs +0 -29
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-BsyrX6NO.mjs +0 -36
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-Dp8hqYOB.mjs +0 -45
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-B0h0AGOH.mjs +0 -6
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-BZjEt71l.mjs +0 -34
- package/dist/pipeline-analytics/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-kZBmgzMg.mjs +0 -156
- package/dist/pipeline-analytics/client-BlxIUpgf.mjs +0 -9836
- package/dist/pipeline-analytics/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +0 -211
- package/dist/pipeline-analytics/hostInit-qBB1Thhi.mjs +0 -168
- package/dist/pipeline-analytics/index-BoL0rgZt.mjs +0 -435
- package/dist/pipeline-analytics/index-CR1aiZDH.mjs +0 -185
- package/dist/pipeline-analytics/index-CWkKuNLr.mjs +0 -232
- package/dist/pipeline-analytics/index-DlhiA9R0.mjs +0 -2603
- package/dist/pipeline-analytics/index-DtdgkNgf.mjs +0 -725
- package/dist/pipeline-analytics/index-Dw6Q30NI.mjs +0 -1655
- package/dist/pipeline-analytics/index-Dy2V7VOm.mjs +0 -14379
- package/dist/pipeline-analytics/index-i47purqY.mjs +0 -37880
- package/dist/pipeline-analytics/index-xncRG7-x.mjs +0 -2713
- package/dist/pipeline-analytics/index.js.map +0 -1
- package/dist/pipeline-analytics/index.mjs.map +0 -1
- package/dist/pipeline-analytics/jsx-runtime-Dlbl3gpr.mjs +0 -55
- package/dist/pipeline-analytics/schemas-ClCuS4qa.mjs +0 -3594
- package/dist/pipeline-analytics/virtualExposes-8FzWTdq3.mjs +0 -42
- package/dist/playlist-generator-EhPaB7Hn.js +0 -48
- package/dist/playlist-generator-EhPaB7Hn.js.map +0 -1
- package/dist/playlist-generator-VTkgn53O.mjs +0 -48
- package/dist/playlist-generator-VTkgn53O.mjs.map +0 -1
- package/dist/recording/index.js +0 -257
- package/dist/recording/index.js.map +0 -1
- package/dist/recording/index.mjs +0 -235
- package/dist/recording/index.mjs.map +0 -1
- package/dist/recording-coordinator-BoGr5moz.js +0 -1052
- package/dist/recording-coordinator-BoGr5moz.js.map +0 -1
- package/dist/recording-coordinator-CsYH9LqF.mjs +0 -1012
- package/dist/recording-coordinator-CsYH9LqF.mjs.map +0 -1
- package/dist/recording-db-gOgaoQh0.js +0 -348
- package/dist/recording-db-gOgaoQh0.js.map +0 -1
- package/dist/recording-db-lIkSMTLq.mjs +0 -348
- package/dist/recording-db-lIkSMTLq.mjs.map +0 -1
- package/dist/recording-service-facade-B9lG6OFn.mjs +0 -123
- package/dist/recording-service-facade-B9lG6OFn.mjs.map +0 -1
- package/dist/recording-service-facade-Do1PKlAL.js +0 -123
- package/dist/recording-service-facade-Do1PKlAL.js.map +0 -1
- package/dist/storage-estimator-CRpoQc9j.js +0 -72
- package/dist/storage-estimator-CRpoQc9j.js.map +0 -1
- package/dist/storage-estimator-DzD8gWJH.mjs +0 -72
- package/dist/storage-estimator-DzD8gWJH.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/embedding-encoder/catalogs/embedding-models.ts","../../src/embedding-encoder/shared/noop-logger.ts","../../src/embedding-encoder/shared/node-raw-tensor-engine.ts","../../src/embedding-encoder/shared/image-utils.ts","../../src/embedding-encoder/shared/node-engine.ts","../../src/embedding-encoder/shared/python-engine.ts","../../src/embedding-encoder/shared/engine-resolver.ts","../../src/embedding-encoder/addon/clip-models.ts","../../src/embedding-encoder/addon/clip-preprocessing.ts","../../src/embedding-encoder/addon/index.ts"],"sourcesContent":["import type { ModelCatalogEntry } from '@camstack/types'\n\nexport const CLIP_IMAGE_MODELS: readonly ModelCatalogEntry[] = [\n {\n id: 'clip-vit-b32',\n name: 'CLIP ViT-B/32',\n description: 'OpenAI CLIP ViT-B/32 — fast, 512-dim, int8 quantized (85 MB)',\n inputSize: { width: 224, height: 224 },\n labels: [],\n inputLayout: 'nchw',\n inputNormalization: 'none',\n formats: {\n onnx: {\n url: 'https://huggingface.co/Xenova/clip-vit-base-patch32/resolve/main/onnx/vision_model_quantized.onnx',\n sizeMB: 85,\n },\n },\n },\n {\n id: 'clip-vit-b16',\n name: 'CLIP ViT-B/16',\n description: 'OpenAI CLIP ViT-B/16 — higher accuracy, 512-dim, int8 quantized (83 MB)',\n inputSize: { width: 224, height: 224 },\n labels: [],\n inputLayout: 'nchw',\n inputNormalization: 'none',\n formats: {\n onnx: {\n url: 'https://huggingface.co/Xenova/clip-vit-base-patch16/resolve/main/onnx/vision_model_quantized.onnx',\n sizeMB: 83,\n },\n },\n },\n {\n id: 'siglip2-b16-256',\n name: 'SigLIP2 Base/16 256',\n description: 'Google SigLIP2 — superior scene understanding, 768-dim, int8 quantized (90 MB)',\n inputSize: { width: 256, height: 256 },\n labels: [],\n inputLayout: 'nchw',\n inputNormalization: 'none',\n formats: {\n onnx: {\n url: 'https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/main/onnx/vision_model_quantized.onnx',\n sizeMB: 90,\n },\n },\n },\n]\n\nexport const CLIP_TEXT_MODELS: readonly ModelCatalogEntry[] = [\n {\n id: 'clip-vit-b32-text',\n name: 'CLIP ViT-B/32 Text Encoder',\n description: 'Text encoder for CLIP ViT-B/32, int8 quantized (62 MB)',\n inputSize: { width: 0, height: 0 },\n labels: [],\n formats: {\n onnx: {\n url: 'https://huggingface.co/Xenova/clip-vit-base-patch32/resolve/main/onnx/text_model_quantized.onnx',\n sizeMB: 62,\n },\n },\n },\n {\n id: 'clip-vit-b16-text',\n name: 'CLIP ViT-B/16 Text Encoder',\n description: 'Text encoder for CLIP ViT-B/16, int8 quantized (62 MB)',\n inputSize: { width: 0, height: 0 },\n labels: [],\n formats: {\n onnx: {\n url: 'https://huggingface.co/Xenova/clip-vit-base-patch16/resolve/main/onnx/text_model_quantized.onnx',\n sizeMB: 62,\n },\n },\n },\n {\n id: 'siglip2-b16-256-text',\n name: 'SigLIP2 Base/16 256 Text Encoder',\n description: 'Text encoder for SigLIP2, int8 quantized (270 MB)',\n inputSize: { width: 0, height: 0 },\n labels: [],\n formats: {\n onnx: {\n url: 'https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/main/onnx/text_model_quantized.onnx',\n sizeMB: 270,\n },\n },\n },\n]\n","import type { IScopedLogger, LogTags } from '@camstack/types'\n\nconst noop = (): void => { /* intentional no-op */ }\n\nexport function createNoopLogger(): IScopedLogger {\n const logger: IScopedLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n child: () => logger,\n withTags: (_tags: LogTags) => logger,\n }\n return logger\n}\n","import type { IRawTensorEngine, DetectionRuntime, DetectionDevice, IScopedLogger } from '@camstack/types'\nimport type { InferenceSession } from 'onnxruntime-node'\nimport { createNoopLogger } from './noop-logger.js'\nimport * as path from 'node:path'\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\n/**\n * Raw tensor engine — runs ONNX inference on pre-processed Float32Array input.\n * Used by addons that handle their own preprocessing (e.g. CLIP embedding encoder).\n */\nexport class NodeRawTensorEngine implements IRawTensorEngine {\n readonly runtime: DetectionRuntime = 'onnx'\n readonly device: DetectionDevice\n private session: InferenceSession | null = null\n private readonly log: IScopedLogger\n\n constructor(\n private readonly modelPath: string,\n private readonly backend: string,\n logger?: IScopedLogger,\n ) {\n this.device = BACKEND_TO_DEVICE[backend] ?? 'cpu'\n this.log = logger ?? createNoopLogger()\n }\n\n async initialize(): Promise<void> {\n const ort = await import('onnxruntime-node')\n const provider = this.backend === 'coreml' ? 'coreml' : this.backend === 'cuda' ? 'cuda' : 'cpu'\n\n const absModelPath = path.isAbsolute(this.modelPath)\n ? this.modelPath\n : path.resolve(process.cwd(), this.modelPath)\n\n this.session = await ort.InferenceSession.create(absModelPath, {\n executionProviders: [provider],\n })\n this.log.info('ONNX session loaded', { meta: { modelPath: absModelPath, backend: this.backend, provider } })\n }\n\n async run(input: Float32Array, inputShape: readonly number[]): Promise<Float32Array> {\n if (!this.session) {\n throw new Error('NodeRawTensorEngine: not initialized — call initialize() first')\n }\n\n const ort = await import('onnxruntime-node')\n const sess = this.session\n\n const inputName: string = sess.inputNames[0]!\n const tensor = new ort.Tensor('float32', input, [...inputShape])\n const feeds: Record<string, InstanceType<typeof ort.Tensor>> = { [inputName]: tensor }\n\n const start = Date.now()\n let results: Record<string, InstanceType<typeof ort.Tensor>>\n try {\n results = await sess.run(feeds)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n this.log.error('Inference failed', { meta: { error: error.message } })\n throw error\n }\n\n const outputName: string = sess.outputNames[0]!\n this.log.debug('Inference complete', { meta: { durationMs: Date.now() - start, outputKeys: [outputName], preprocessMode: 'raw-tensor' } })\n return results[outputName]!.data\n }\n\n async dispose(): Promise<void> {\n this.session = null\n this.log.debug('Session disposed')\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, { fit: 'fill' })\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","import type { IInferenceEngine, InferenceInput, DetectionRuntime, DetectionDevice, EngineOutput, LetterboxMeta, ModelInputMeta, IScopedLogger } from '@camstack/types'\nimport type { InferenceSession } from 'onnxruntime-node'\nimport { letterbox, resizeAndNormalize } from './image-utils.js'\nimport { createNoopLogger } from './noop-logger.js'\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: InferenceSession | null = null\n private readonly log: IScopedLogger\n\n constructor(\n private readonly modelPath: string,\n private readonly backend: string,\n private readonly modelMeta: ModelInputMeta,\n logger?: IScopedLogger,\n ) {\n this.device = (BACKEND_TO_DEVICE[backend] ?? 'cpu') as DetectionDevice\n this.log = logger ?? createNoopLogger()\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 = {\n executionProviders: [provider],\n }\n\n this.session = await ort.InferenceSession.create(absModelPath, sessionOptions)\n this.log.info('ONNX session loaded', { meta: { modelPath: absModelPath, backend: this.backend, provider } })\n }\n\n async infer(input: InferenceInput): Promise<EngineOutput> {\n // ONNX preprocess (sharp) reads JPEG bytes; raw inputs are\n // re-encoded to JPEG once before preprocess. Embedding paths almost\n // always feed JPEG crops, so this fallback rarely fires.\n const jpeg = input.kind === 'jpeg'\n ? input.data\n : await this.encodeRawAsJpeg(input.data, input.width, input.height, input.format)\n const { data, letterboxMeta } = await this.preprocess(jpeg)\n const { inputSize } = this.modelMeta\n\n const inputShape =\n this.modelMeta.preprocessMode === 'letterbox'\n ? [1, 3, inputSize.height, inputSize.width]\n : [1, 3, inputSize.height, inputSize.width]\n\n const start = Date.now()\n let result: { tensor: Float32Array } | { tensors: Record<string, Float32Array> }\n try {\n result = await this.runSession(data, inputShape)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n this.log.error('Inference failed', { meta: { error: error.message } })\n throw error\n }\n const durationMs = Date.now() - start\n\n if ('tensor' in result) {\n this.log.debug('Inference complete', { meta: { durationMs, outputKeys: ['tensor'], preprocessMode: this.modelMeta.preprocessMode } })\n return { tensor: result.tensor, letterbox: letterboxMeta, inferenceMs: durationMs }\n }\n this.log.debug('Inference complete', { meta: { durationMs, outputKeys: Object.keys(result.tensors), preprocessMode: this.modelMeta.preprocessMode } })\n return { tensors: result.tensors, letterbox: letterboxMeta, inferenceMs: durationMs }\n }\n\n /** Preprocess JPEG to Float32Array using the configured mode */\n private async preprocess(\n jpeg: Buffer,\n ): Promise<{ data: Float32Array; letterboxMeta?: LetterboxMeta }> {\n const { inputSize, inputNormalization, inputLayout, preprocessMode } = this.modelMeta\n\n if (preprocessMode === 'letterbox') {\n const targetSize = Math.max(inputSize.width, inputSize.height)\n const result = await letterbox(jpeg, targetSize)\n const letterboxMeta: LetterboxMeta = {\n scale: result.scale,\n padX: result.padX,\n padY: result.padY,\n originalWidth: result.originalWidth,\n originalHeight: result.originalHeight,\n }\n return { data: result.data, letterboxMeta }\n }\n\n // preprocessMode === 'resize'\n const data = await resizeAndNormalize(\n jpeg,\n inputSize.width,\n inputSize.height,\n inputNormalization,\n inputLayout,\n )\n return { data }\n }\n\n private async encodeRawAsJpeg(\n raw: Buffer,\n width: number,\n height: number,\n format: 'rgb' | 'bgr' | 'gray',\n ): Promise<Buffer> {\n const sharp = (await import('sharp')).default\n const channels = format === 'gray' ? 1 : 3\n return sharp(raw, { raw: { width, height, channels } })\n .jpeg({ quality: 80, mozjpeg: false })\n .toBuffer()\n }\n\n /** Run an ONNX session with a single input, handling both single and multi-output models */\n private async runSession(\n input: Float32Array,\n inputShape: readonly number[],\n ): Promise<{ tensor: Float32Array } | { tensors: 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 const sess = this.session\n\n const inputName: string = sess.inputNames[0]!\n const tensor = new ort.Tensor('float32', input, [...inputShape])\n const feeds: Record<string, InstanceType<typeof ort.Tensor>> = { [inputName]: tensor }\n\n const results = await sess.run(feeds)\n const outputNames: readonly string[] = sess.outputNames\n\n if (outputNames.length === 1) {\n const outputName = outputNames[0]!\n return { tensor: results[outputName]!.data }\n }\n\n const tensors: Record<string, Float32Array> = {}\n for (const name of outputNames) {\n tensors[name] = results[name]!.data\n }\n return { tensors }\n }\n\n async run(input: Float32Array, inputShape: readonly number[]): Promise<Float32Array> {\n const result = await this.runSession(input, inputShape)\n if ('tensor' in result) return result.tensor\n // Return first output tensor for multi-output models\n const firstKey = Object.keys(result.tensors)[0]!\n return result.tensors[firstKey]!\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 this.log.debug('Session disposed')\n }\n}\n","import type { IInferenceEngine, InferenceInput, DetectionRuntime, DetectionDevice, IAddonDepsManager, EngineOutput, IScopedLogger } from '@camstack/types'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { createNoopLogger } from './noop-logger.js'\n\nexport class PythonInferenceEngine implements IInferenceEngine {\n readonly runtime: DetectionRuntime\n readonly device: DetectionDevice\n private process: ChildProcess | null = null\n private receiveBuffer: Buffer = Buffer.alloc(0)\n private pendingResolve: ((value: Record<string, unknown>) => void) | null = null\n private pendingReject: ((reason: Error) => void) | null = null\n private readonly log: IScopedLogger\n\n constructor(\n private readonly pythonPath: string,\n private readonly scriptPath: string,\n runtime: DetectionRuntime,\n private readonly modelPath: string,\n private readonly extraArgs: readonly string[] = [],\n logger?: IScopedLogger,\n ) {\n this.runtime = runtime\n // Determine device from runtime\n const runtimeDeviceMap: Readonly<Record<DetectionRuntime, DetectionDevice>> = {\n onnx: 'cpu',\n coreml: 'gpu-mps',\n pytorch: 'cpu',\n openvino: 'cpu',\n tflite: 'cpu',\n }\n this.device = runtimeDeviceMap[runtime]\n this.log = logger ?? createNoopLogger()\n }\n\n async initialize(): Promise<void> {\n const args = [this.scriptPath, this.modelPath, ...this.extraArgs]\n this.process = spawn(this.pythonPath, args, {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n if (!this.process.stdout || !this.process.stdin) {\n throw new Error('PythonInferenceEngine: failed to create process pipes')\n }\n\n this.log.info('Python process started', { meta: { pythonPath: this.pythonPath, scriptPath: this.scriptPath, modelPath: this.modelPath } })\n\n this.process.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (trimmed) {\n this.log.warn(trimmed)\n }\n }\n })\n\n this.process.on('error', (err) => {\n this.log.error('Process error', { meta: { error: err.message } })\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n })\n\n this.process.on('exit', (code) => {\n if (code !== 0) {\n this.log.error('Process exited', { meta: { code } })\n const err = new Error(`PythonInferenceEngine: process exited with code ${code}`)\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n }\n })\n\n this.process.stdout.on('data', (chunk: Buffer) => {\n this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk])\n this._tryReceive()\n })\n\n // Give the process a moment to start up and load the model\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => resolve(), 2000)\n this.process?.on('error', (err) => {\n clearTimeout(timeout)\n reject(err)\n })\n this.process?.on('exit', (code) => {\n clearTimeout(timeout)\n if (code !== 0) {\n reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`))\n }\n })\n })\n }\n\n private _tryReceive(): void {\n // Binary IPC: [4 bytes LE uint32 length][JSON bytes]\n if (this.receiveBuffer.length < 4) return\n\n const length = this.receiveBuffer.readUInt32LE(0)\n if (this.receiveBuffer.length < 4 + length) return\n\n const jsonBytes = this.receiveBuffer.subarray(4, 4 + length)\n this.receiveBuffer = this.receiveBuffer.subarray(4 + length)\n\n const resolve = this.pendingResolve\n const reject = this.pendingReject\n this.pendingResolve = null\n this.pendingReject = null\n\n if (!resolve) return\n\n try {\n const parsed = JSON.parse(jsonBytes.toString('utf8')) as Record<string, unknown>\n resolve(parsed)\n } catch (err) {\n reject?.(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n /** Run inference, returning structured detection results. Encodes raw input to JPEG when needed. */\n async infer(input: InferenceInput): Promise<EngineOutput> {\n const start = Date.now()\n const jpeg = input.kind === 'jpeg'\n ? input.data\n : await this.encodeRawAsJpeg(input.data, input.width, input.height, input.format)\n const result = await this.sendJpeg(jpeg)\n const durationMs = Date.now() - start\n this.log.debug('Inference complete', { meta: { durationMs } })\n return { structured: result, inferenceMs: durationMs }\n }\n\n private async encodeRawAsJpeg(\n raw: Buffer,\n width: number,\n height: number,\n format: 'rgb' | 'bgr' | 'gray',\n ): Promise<Buffer> {\n const sharp = (await import('sharp')).default\n const channels = format === 'gray' ? 1 : 3\n return sharp(raw, { raw: { width, height, channels } })\n .jpeg({ quality: 80, mozjpeg: false })\n .toBuffer()\n }\n\n /** Send JPEG buffer via binary IPC, receive JSON detection results */\n private async sendJpeg(jpeg: Buffer): Promise<Record<string, unknown>> {\n if (!this.process?.stdin) {\n throw new Error('PythonInferenceEngine: process not initialized')\n }\n\n return new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pendingResolve = resolve\n this.pendingReject = reject\n\n // Binary IPC: [4 bytes LE uint32 length][JPEG bytes]\n const lengthBuf = Buffer.allocUnsafe(4)\n lengthBuf.writeUInt32LE(jpeg.length, 0)\n this.process!.stdin!.write(Buffer.concat([lengthBuf, jpeg]))\n })\n }\n\n async dispose(): Promise<void> {\n const proc = this.process\n if (!proc) return\n\n this.process = null\n proc.stdin?.end()\n proc.kill('SIGTERM')\n\n // Wait up to 5s for graceful exit, then force-kill\n const exited = await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n resolve(false)\n }, 5_000)\n proc.once('exit', () => {\n clearTimeout(timer)\n resolve(true)\n })\n })\n\n if (!exited) {\n try { proc.kill('SIGKILL') } catch { /* already dead */ }\n this.log.warn('Python process did not exit gracefully — sent SIGKILL')\n } else {\n this.log.debug('Python process terminated')\n }\n }\n}\n\n/**\n * Resolve Python binary for ML inference. Priority:\n * 1. Explicit config pythonPath\n * 2. Embedded (data/deps/python/bin/python3)\n * 3. System PATH (python3, python)\n * 4. Download standalone Python\n *\n * Returns null if Python is not available.\n */\nexport async function resolvePythonBinary(\n configPath: string | undefined,\n deps: IAddonDepsManager,\n): Promise<string | null> {\n if (configPath) return configPath\n return deps.ensurePython()\n}\n","// Engine resolver has inherent branching for runtime × backend × format × device combinations\n// TODO: Wire PythonInferenceEngine for PyTorch/OpenVINO/TFLite runtimes\n// Currently falls back to ONNX CPU when non-ONNX runtime is requested.\n//\n// WHY THIS FILE USES RAW FILESYSTEM PATHS (modelsDir: string):\n//\n// Model files must be loaded via absolute filesystem paths because inference\n// engines (ONNX Runtime, CoreML, etc.) require direct file access -- they do\n// not accept Buffer or stream inputs. This is fundamentally different from\n// user data (recordings, media) where IAddonFileStorage abstraction makes\n// sense. Models are closer to binaries or compiled artifacts that must live\n// on the local filesystem at runtime.\n//\n// IAddonFileStorage.readFile() returns a Buffer, but onnxruntime-node's\n// InferenceSession.create() only accepts a file path string or a Uint8Array\n// loaded into memory. For large models (hundreds of MB), loading into a\n// Uint8Array is impractical. Therefore, modelsDir stays as a raw string path\n// and is intentionally NOT replaced with IAddonFileStorage here.\n\nimport type {\n IInferenceEngine,\n DetectionRuntime,\n ModelCatalogEntry,\n ModelFormat,\n IAddonModelManager,\n ModelInputMeta,\n IScopedLogger,\n} from '@camstack/types'\nimport { createNoopLogger } from './noop-logger.js'\nimport {\n BACKEND_TO_FORMAT as CANONICAL_BACKEND_TO_FORMAT,\n RUNTIME_TO_FORMAT as CANONICAL_RUNTIME_TO_FORMAT,\n PYTHON_SCRIPT as CANONICAL_PYTHON_SCRIPT,\n} from '@camstack/types'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { NodeInferenceEngine } from './node-engine.js'\nimport { PythonInferenceEngine } from './python-engine.js'\nimport type { SharedPythonPool } from './python-pool.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 /** Model service for downloading models. When provided, used instead of raw filesystem probing. */\n readonly models?: IAddonModelManager\n /** Optional logger — tagged with modelId, runtime, backend by the caller. */\n readonly logger?: IScopedLogger\n /** Shared CoreML pool — when provided, CoreML models use the pool instead of a dedicated Python process. */\n readonly coremlPool?: SharedPythonPool\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// Use canonical mappings from @camstack/types (single source of truth)\nconst BACKEND_TO_FORMAT = CANONICAL_BACKEND_TO_FORMAT\nconst RUNTIME_TO_FORMAT = CANONICAL_RUNTIME_TO_FORMAT\n\nfunction extractModelMeta(entry: ModelCatalogEntry): ModelInputMeta {\n return {\n inputSize: entry.inputSize,\n inputNormalization: entry.inputNormalization ?? 'zero-one',\n inputLayout: entry.inputLayout ?? 'nchw',\n preprocessMode: entry.preprocessMode ?? 'letterbox',\n }\n}\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, models } = options\n const log = options.logger ?? createNoopLogger()\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 // Fallback: if the requested format isn't available but ONNX is, use ONNX\n // (ONNX Runtime can use CoreML/CUDA execution providers automatically)\n if (fmt !== 'onnx' && modelEntry.formats['onnx']) {\n selectedFormat = 'onnx'\n selectedBackend = backend || 'cpu'\n } else {\n throw new Error(\n `resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`,\n )\n }\n } else {\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\n // Download model and extra files via the unified service\n let modelPath: string\n\n if (models) {\n // Use the unified ModelDownloadService\n modelPath = await models.ensure(modelEntry.id, selectedFormat)\n } else {\n // Fallback: direct filesystem check (no download capability)\n modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat)\n if (!modelExists(modelPath)) {\n throw new Error(\n `resolveEngine: model file not found at ${modelPath} and no model service provided`,\n )\n }\n }\n\n log.info('Engine resolved', { meta: { format: selectedFormat, backend: selectedBackend, modelId: modelEntry.id } })\n\n // NodeInferenceEngine handles ONNX format only (with any ONNX backend including CoreML provider).\n // Native .mlpackage/.mlmodel files go to PythonInferenceEngine below.\n if (selectedFormat === 'onnx') {\n const engine = new NodeInferenceEngine(modelPath, selectedBackend, extractModelMeta(modelEntry), options.logger)\n await engine.initialize()\n return { engine, format: selectedFormat, modelPath }\n }\n\n // For non-ONNX formats, try PythonInferenceEngine\n const effectiveRuntime = runtime === 'auto' ? selectedBackend : runtime\n\n // Auto-discover python if not provided but needed\n let { pythonPath } = options\n if (!pythonPath) {\n const { execFileSync: efs } = await import('node:child_process')\n for (const cmd of ['python3', 'python']) {\n try { efs(cmd, ['--version'], { timeout: 3000, stdio: 'ignore' }); pythonPath = cmd; break } catch { /* not found */ }\n }\n }\n const scriptName = CANONICAL_PYTHON_SCRIPT[effectiveRuntime]\n\n if (scriptName && pythonPath) {\n // Resolve python script path -- search multiple locations:\n // - src/shared/ -> ../../python/ (source development)\n // - dist/shared/ -> ../../python/ (installed addon with python/ at package root)\n // - dist/ -> ../python/ (flat dist)\n const candidates = [\n path.join(__dirname, '../../python', scriptName),\n path.join(__dirname, '../python', scriptName),\n path.join(__dirname, '../../../python', scriptName),\n ]\n const scriptPath = candidates.find(p => fs.existsSync(p))\n if (!scriptPath) {\n throw new Error(\n `resolveEngine: Python script \"${scriptName}\" not found. Searched:\\n${candidates.join('\\n')}`,\n )\n }\n const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height)\n const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime as DetectionRuntime, modelPath, [\n `--input-size=${inputSize}`,\n `--confidence=0.25`,\n ], options.logger)\n await engine.initialize()\n return { engine, format: selectedFormat, modelPath }\n }\n\n // Final fallback: use ONNX CPU if available\n const fallbackPath = modelFilePath(modelsDir, modelEntry, 'onnx')\n if (modelEntry.formats['onnx'] && modelExists(fallbackPath)) {\n const engine = new NodeInferenceEngine(fallbackPath, 'cpu', extractModelMeta(modelEntry), options.logger)\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, ` +\n `no Python runtime is available, and no ONNX fallback exists`,\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 const providers: string[] = ort.env?.webgl?.disabled !== undefined\n ? (ort.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","export interface ClipModelMeta {\n readonly imageModelId: string\n readonly textModelId: string\n readonly embeddingDim: number\n readonly inputSize: number\n readonly tokenizerType: 'clip' | 'siglip'\n}\n\nexport const CLIP_MODEL_META: Readonly<Record<string, ClipModelMeta>> = {\n 'clip-vit-b32': {\n imageModelId: 'clip-vit-b32',\n textModelId: 'clip-vit-b32-text',\n embeddingDim: 512,\n inputSize: 224,\n tokenizerType: 'clip',\n },\n 'clip-vit-b16': {\n imageModelId: 'clip-vit-b16',\n textModelId: 'clip-vit-b16-text',\n embeddingDim: 512,\n inputSize: 224,\n tokenizerType: 'clip',\n },\n 'siglip2-b16-256': {\n imageModelId: 'siglip2-b16-256',\n textModelId: 'siglip2-b16-256-text',\n embeddingDim: 768,\n inputSize: 256,\n tokenizerType: 'siglip',\n },\n}\n\nexport const DEFAULT_CLIP_MODEL = 'clip-vit-b32'\n\nexport function getModelMeta(modelId: string): ClipModelMeta {\n return CLIP_MODEL_META[modelId] ?? CLIP_MODEL_META[DEFAULT_CLIP_MODEL]!\n}\n","// CLIP normalization constants (OpenAI CLIP)\nconst CLIP_MEAN = [0.48145466, 0.4578275, 0.40821073] as const\nconst CLIP_STD = [0.26862954, 0.26130258, 0.27577711] as const\n\n/**\n * Preprocess raw RGB buffer for CLIP inference.\n * Resizes (nearest-neighbor for speed), normalizes with CLIP mean/std, outputs NCHW Float32Array.\n * For production use, the caller should use sharp to resize the JPEG to targetW×targetH\n * before calling this with the raw RGB. This function handles normalization + layout.\n */\nexport function preprocessForClip(\n rgb: Buffer,\n srcWidth: number,\n srcHeight: number,\n targetWidth: number,\n targetHeight: number,\n): Float32Array {\n const pixels = targetWidth * targetHeight\n const result = new Float32Array(3 * pixels)\n\n for (let y = 0; y < targetHeight; y++) {\n for (let x = 0; x < targetWidth; x++) {\n // Nearest-neighbor sampling\n const srcX = Math.min(Math.floor((x / targetWidth) * srcWidth), srcWidth - 1)\n const srcY = Math.min(Math.floor((y / targetHeight) * srcHeight), srcHeight - 1)\n const srcIdx = (srcY * srcWidth + srcX) * 3\n const dstIdx = y * targetWidth + x\n\n for (let c = 0; c < 3; c++) {\n const val = (rgb[srcIdx + c] ?? 0) / 255.0\n result[c * pixels + dstIdx] = (val - CLIP_MEAN[c]!) / CLIP_STD[c]!\n }\n }\n }\n\n return result\n}\n\n/**\n * L2-normalize a vector in-place and return it.\n */\nexport function l2Normalize(vec: Float32Array): Float32Array {\n let norm = 0\n for (let i = 0; i < vec.length; i++) norm += vec[i]! * vec[i]!\n norm = Math.sqrt(norm)\n if (norm > 0) {\n for (let i = 0; i < vec.length; i++) vec[i]! /= norm\n }\n return vec\n}\n","import type {\n ProviderRegistration,\n IRawTensorEngine,\n IInferenceEngine,\n ModelCatalogEntry,\n IEmbeddingEncoderProvider,\n} from '@camstack/types'\nimport type { IAddonModelManager } from '@camstack/types'\nimport { BaseAddon, embeddingEncoderCapability } from '@camstack/types'\n\ntype EmbeddingEncodeOutput = Awaited<ReturnType<IEmbeddingEncoderProvider['encode']>>\ntype EmbeddingInfoOutput = Awaited<ReturnType<IEmbeddingEncoderProvider['getInfo']>>\n// eslint-disable-next-line no-restricted-imports -- ModelDownloadService is a build-time dep, not a runtime addon coupling\nimport { ModelDownloadService } from '@camstack/core'\nimport { CLIP_IMAGE_MODELS, CLIP_TEXT_MODELS } from '../catalogs/embedding-models.js'\nimport { NodeRawTensorEngine } from '../shared/node-raw-tensor-engine.js'\nimport { resolveEngine } from '../shared/engine-resolver.js'\nimport { getModelMeta, DEFAULT_CLIP_MODEL } from './clip-models.js'\nimport { preprocessForClip, l2Normalize } from './clip-preprocessing.js'\n\n/**\n * Embedding encoder uses two separate engines (image + text) with custom\n * CLIP preprocessing — cannot use BaseVisionAddon which assumes single engine + JPEG input.\n *\n * Uses IRawTensorEngine for ONNX (custom tensor preprocessing) and\n * IInferenceEngine.infer() for Python path (sends JPEG/JSON via binary IPC).\n */\ninterface EmbeddingConfig {\n modelId: string\n runtime: string\n backend: string\n}\n\nexport default class EmbeddingEncoderAddon extends BaseAddon<EmbeddingConfig> implements IEmbeddingEncoderProvider {\n private imageRawEngine: IRawTensorEngine | null = null\n private textRawEngine: IRawTensorEngine | null = null\n private imagePythonEngine: IInferenceEngine | null = null\n private textPythonEngine: IInferenceEngine | null = null\n\n private models: IAddonModelManager | null = null\n private isPython = false\n\n constructor() { super({ modelId: DEFAULT_CLIP_MODEL, runtime: 'auto', backend: 'cpu' }) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const modelsDir = await this.ctx.api.storage.resolve.query({ location: 'models', relativePath: '' })\n .catch(() => 'camstack-data/models')\n this.models = new ModelDownloadService(modelsDir, [])\n return [{ capability: embeddingEncoderCapability, provider: this }]\n }\n\n async encode(input: { crop: Uint8Array; width: number; height: number }): Promise<EmbeddingEncodeOutput> {\n const { crop, width, height } = input\n await this.ensureImageEngine()\n const meta = getModelMeta(this.config.modelId)\n const start = Date.now()\n\n // Python engine path — send JPEG directly\n if (this.isPython && this.imagePythonEngine) {\n const jpegBuffer = Buffer.isBuffer(crop) ? crop : Buffer.from(crop)\n const result = await this.imagePythonEngine.infer({ kind: 'jpeg', data: jpegBuffer })\n const rawEmbedding = result.structured?.['embedding'] as number[]\n const normalized = l2Normalize(new Float32Array(rawEmbedding))\n return {\n embedding: Array.from(normalized),\n inferenceMs: result.inferenceMs ?? (Date.now() - start),\n }\n }\n\n // ONNX path — custom CLIP preprocessing + raw tensor engine\n const cropBuffer = Buffer.isBuffer(crop) ? crop : Buffer.from(crop)\n const preprocessed = preprocessForClip(cropBuffer, width, height, meta.inputSize, meta.inputSize)\n const output = await this.imageRawEngine!.run(preprocessed, [1, 3, meta.inputSize, meta.inputSize])\n const sliced = output.length > meta.embeddingDim\n ? output.slice(0, meta.embeddingDim)\n : output\n const normalized = l2Normalize(new Float32Array(sliced))\n\n return {\n embedding: Array.from(normalized),\n inferenceMs: Date.now() - start,\n }\n }\n\n async encodeText(input: { text: string }): Promise<EmbeddingEncodeOutput> {\n const { text } = input\n await this.ensureTextEngine()\n const meta = getModelMeta(this.config.modelId)\n const start = Date.now()\n\n // Python engine path\n if (this.isPython && this.textPythonEngine) {\n const textBuffer = Buffer.from(JSON.stringify({ text }), 'utf-8')\n const result = await this.textPythonEngine.infer({ kind: 'jpeg', data: textBuffer })\n const rawEmbedding = result.structured?.['embedding'] as number[]\n const normalized = l2Normalize(new Float32Array(rawEmbedding))\n return {\n embedding: Array.from(normalized),\n inferenceMs: result.inferenceMs ?? (Date.now() - start),\n }\n }\n\n // ONNX path — tokenize + raw tensor engine\n const tokenIds = clipTokenize(text)\n const inputTensor = new Float32Array(tokenIds)\n const output = await this.textRawEngine!.run(inputTensor, [1, tokenIds.length])\n const sliced = output.length > meta.embeddingDim\n ? output.slice(0, meta.embeddingDim)\n : output\n const normalized = l2Normalize(new Float32Array(sliced))\n\n return {\n embedding: Array.from(normalized),\n inferenceMs: Date.now() - start,\n }\n }\n\n async getInfo(): Promise<EmbeddingInfoOutput> {\n const meta = getModelMeta(this.config.modelId)\n return {\n modelId: this.config.modelId,\n embeddingDim: meta.embeddingDim,\n ready: this.imageRawEngine !== null || this.imagePythonEngine !== null,\n }\n }\n\n private async ensureImageEngine(): Promise<void> {\n if (this.imageRawEngine || this.imagePythonEngine) return\n\n const meta = getModelMeta(this.config.modelId)\n const imageEntry = CLIP_IMAGE_MODELS.find((m) => m.id === meta.imageModelId)\n if (!imageEntry) {\n throw new Error(`EmbeddingEncoderAddon: unknown image model \"${meta.imageModelId}\"`)\n }\n\n await this.resolveForEntry(imageEntry, 'image')\n }\n\n private async ensureTextEngine(): Promise<void> {\n if (this.textRawEngine || this.textPythonEngine) return\n\n const meta = getModelMeta(this.config.modelId)\n const textEntry = CLIP_TEXT_MODELS.find((m) => m.id === meta.textModelId)\n if (!textEntry) {\n throw new Error(`EmbeddingEncoderAddon: unknown text model \"${meta.textModelId}\"`)\n }\n\n await this.resolveForEntry(textEntry, 'text')\n }\n\n private async resolveForEntry(entry: ModelCatalogEntry, target: 'image' | 'text'): Promise<void> {\n const runtime = this.config.runtime === 'auto' ? 'auto' : (this.config.runtime === 'node' ? 'onnx' : this.config.runtime)\n const modelsDir = this.models!.getModelsDir()\n\n const engineLogger = this.ctx!.logger.withTags({\n modelId: entry.id,\n runtime: this.config.runtime,\n backend: this.config.backend,\n })\n\n await this.models!.ensure(entry.id, 'onnx')\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend: this.config.backend,\n modelEntry: entry,\n modelsDir,\n models: this.models ?? undefined,\n logger: engineLogger,\n })\n\n // Check if the resolved engine is Python-based (has structured output)\n // by checking if the format is non-ONNX\n if (resolved.format !== 'onnx') {\n this.isPython = true\n if (target === 'image') {\n this.imagePythonEngine = resolved.engine\n } else {\n this.textPythonEngine = resolved.engine\n }\n } else {\n // ONNX — create a raw tensor engine for custom preprocessing\n const rawEngine = new NodeRawTensorEngine(resolved.modelPath, this.config.backend, engineLogger)\n await rawEngine.initialize()\n // Dispose the vision engine from resolveEngine (we don't need it)\n await resolved.engine.dispose()\n if (target === 'image') {\n this.imageRawEngine = rawEngine\n } else {\n this.textRawEngine = rawEngine\n }\n }\n }\n\n protected async onShutdown(): Promise<void> {\n await this.imageRawEngine?.dispose()\n await this.textRawEngine?.dispose()\n await this.imagePythonEngine?.dispose()\n await this.textPythonEngine?.dispose()\n }\n\n // ── Three-level settings API (Phase 3) ──────────────────────────────\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'embedding-encoder-settings',\n title: 'Embedding Encoder',\n columns: 2,\n fields: [\n {\n type: 'text',\n key: 'modelId',\n label: 'Model ID',\n description: 'CLIP model identifier to use for image/text embedding',\n default: DEFAULT_CLIP_MODEL,\n },\n {\n type: 'select',\n key: 'runtime',\n label: 'Runtime',\n description: 'Inference runtime (auto selects the best available)',\n default: 'auto',\n options: [\n { label: 'Auto', value: 'auto' },\n { label: 'Node (ONNX)', value: 'node' },\n { label: 'Python', value: 'python' },\n ],\n },\n {\n type: 'select',\n key: 'backend',\n label: 'Backend',\n description: 'Hardware backend for inference acceleration',\n default: 'cpu',\n options: [\n { label: 'CPU', value: 'cpu' },\n { label: 'CUDA', value: 'cuda' },\n { label: 'CoreML', value: 'coreml' },\n ],\n },\n ],\n },\n ],\n })\n }\n\n protected async onConfigChanged(): Promise<void> {\n // Config already updated by BaseAddon.resolveConfig() — nothing extra needed.\n // If model changed, ensureImageEngine will pick it up on next call.\n }\n}\n\n/**\n * Minimal CLIP tokenizer — encodes ASCII text to token IDs.\n * Production implementations should use a proper BPE tokenizer;\n * this is a simplified placeholder that maps characters to IDs\n * with SOT/EOT tokens for basic functionality.\n */\nfunction clipTokenize(text: string, maxLength = 77): number[] {\n const SOT_TOKEN = 49406\n const EOT_TOKEN = 49407\n const tokens: number[] = [SOT_TOKEN]\n\n for (let i = 0; i < text.length && tokens.length < maxLength - 1; i++) {\n tokens.push(text.charCodeAt(i) + 256)\n }\n tokens.push(EOT_TOKEN)\n\n while (tokens.length < maxLength) {\n tokens.push(0)\n }\n\n return tokens\n}\n"],"names":["BACKEND_TO_DEVICE","sharp","CANONICAL_BACKEND_TO_FORMAT","CANONICAL_RUNTIME_TO_FORMAT","CANONICAL_PYTHON_SCRIPT","normalized"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/enrichment-engine/types.ts","../../src/enrichment-engine/services/crop-extractor.ts","../../src/enrichment-engine/services/resolve-frame.ts","../../src/enrichment-engine/workers/embedding-dispatcher.ts","../../src/_analytics-schemas/persistence-records.ts","../../src/enrichment-engine/services/text-embedding-cache.ts","../../src/enrichment-engine/workers/scene-state-worker.ts","../../src/enrichment-engine/workers/activity-summary.ts","../../src/enrichment-engine/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Enrichment Engine — Types\n// ---------------------------------------------------------------------------\n\n// --- Normalized Rectangle (0-1 coordinate space) ---\n\nexport interface NormalizedRect {\n readonly x: number // 0-1, left edge\n readonly y: number // 0-1, top edge\n readonly w: number // 0-1, width\n readonly h: number // 0-1, height\n}\n\n// --- Scene State ---\n\nexport interface SceneStateDefinition {\n readonly id: string\n readonly label: string // e.g. \"open\", \"closed\"\n readonly textPrompts: readonly string[] // zero-shot: [\"an open gate\"]\n readonly referenceEmbeddings: readonly (readonly number[])[] // optional: higher accuracy\n readonly notify: boolean\n readonly severity: 'info' | 'warning' | 'alert'\n}\n\nexport interface SceneMonitor {\n readonly id: string\n readonly label: string // e.g. \"Main Gate\"\n readonly roi: NormalizedRect // region of interest\n readonly enabled: boolean\n readonly states: readonly SceneStateDefinition[]\n}\n\nexport interface SceneMonitorConfig {\n readonly monitors: readonly SceneMonitor[]\n}\n\n// --- Enrichment Config (global) ---\n\nexport interface EnrichmentConfig {\n readonly enabled: boolean\n\n readonly embedding: {\n readonly enabled: boolean\n readonly modelId: string // 'clip-vit-b32' | 'clip-vit-b16' | 'siglip2-b16-256'\n readonly agentId: string | 'local'\n readonly runtime: 'node' | 'python'\n readonly backend: 'cpu' | 'coreml' | 'cuda'\n readonly classes: readonly string[] // [] = all classes\n readonly minConfidence: number\n readonly maxPerSecPerCamera: number\n readonly cropStrategy: 'first' | 'best-confidence' | 'track-end'\n readonly retentionDays: number\n }\n\n readonly sceneMonitor: {\n readonly enabled: boolean\n readonly modelId: string // can differ from embedding model\n readonly pollIntervalSec: number\n readonly hysteresisCount: number\n }\n\n readonly activitySummary: {\n readonly enabled: boolean\n readonly intervalSec: number\n readonly activityThresholds: {\n readonly low: number\n readonly medium: number\n readonly high: number\n }\n }\n}\n\n// --- Events ---\n\nexport interface EmbeddingStoredEvent {\n readonly deviceId: number\n readonly trackId: string\n readonly class: string\n readonly embeddingId: string // reference in vector store\n readonly modelId: string\n readonly embeddingDim: number\n readonly inferenceMs: number\n readonly timestamp: number\n}\n\nexport interface SceneStateChangedEvent {\n readonly deviceId: number\n readonly monitorId: string\n readonly monitorLabel: string\n readonly previousState: string\n readonly currentState: string\n readonly confidence: number\n readonly timestamp: number\n}\n\nexport interface ZoneActivityEntry {\n readonly zoneId: string\n readonly entries: number\n readonly exits: number\n readonly avgDwellMs: number\n}\n\nexport interface StateChangeEntry {\n readonly monitorId: string\n readonly from: string\n readonly to: string\n readonly timestamp: number\n}\n\nexport interface ActivitySummaryEvent {\n readonly deviceId: number\n readonly periodStart: number\n readonly periodEnd: number\n readonly objectCounts: Readonly<Record<string, number>>\n readonly zoneActivity: readonly ZoneActivityEntry[]\n readonly stateChanges: readonly StateChangeEntry[]\n readonly activityLevel: 'none' | 'low' | 'medium' | 'high'\n}\n\n// --- Defaults ---\n\nexport const DEFAULT_ENRICHMENT_CONFIG: EnrichmentConfig = {\n enabled: false,\n\n embedding: {\n enabled: false,\n modelId: 'clip-vit-b32',\n agentId: 'local',\n runtime: 'node',\n backend: 'cpu',\n classes: [],\n minConfidence: 0.5,\n maxPerSecPerCamera: 1,\n cropStrategy: 'first',\n retentionDays: 30,\n },\n\n sceneMonitor: {\n enabled: false,\n modelId: 'clip-vit-b32',\n pollIntervalSec: 10,\n hysteresisCount: 3,\n },\n\n activitySummary: {\n enabled: false,\n intervalSec: 60,\n activityThresholds: {\n low: 2,\n medium: 10,\n high: 30,\n },\n },\n}\n","import sharp from 'sharp'\n\n/**\n * Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.\n * Coordinates are clamped to frame bounds to avoid out-of-range errors.\n */\nexport async function extractCrop(\n frameData: Buffer,\n frameWidth: number,\n frameHeight: number,\n bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },\n): Promise<{ crop: Buffer; width: number; height: number }> {\n // Convert normalized coordinates to pixel values\n const rawLeft = Math.round(bbox.x * frameWidth)\n const rawTop = Math.round(bbox.y * frameHeight)\n const rawWidth = Math.round(bbox.w * frameWidth)\n const rawHeight = Math.round(bbox.h * frameHeight)\n\n // Clamp to frame bounds\n const left = Math.max(0, Math.min(rawLeft, frameWidth - 1))\n const top = Math.max(0, Math.min(rawTop, frameHeight - 1))\n const width = Math.max(1, Math.min(rawWidth, frameWidth - left))\n const height = Math.max(1, Math.min(rawHeight, frameHeight - top))\n\n const crop = await sharp(frameData, {\n raw: { width: frameWidth, height: frameHeight, channels: 3 },\n })\n .extract({ left, top, width, height })\n .jpeg({ quality: 90 })\n .toBuffer()\n\n return { crop, width, height }\n}\n","/**\n * `resolveFrame` — turn a serialisable `FrameHandle` back into pixels.\n *\n * The handle's `nodeId` decides the path: when the consumer is co-located\n * with the producing decoder, the pixels are read directly from the shared\n * memory ring (`readers.read(handle)`, a `null` means the slot was recycled\n * before the consumer got to it — a dropped frame, the correct latest-wins\n * behaviour for live video). Otherwise the caller-supplied\n * `getRemoteFrame(handle)` performs a node-routed `decoder.getFrame` call.\n *\n * Keeping the remote fetch as an injected callback keeps this helper free of\n * any cap-client / tRPC coupling and trivially testable.\n */\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\n\nexport interface ResolveFrameDeps {\n /** Cluster node id of the consumer — used to detect co-location. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the caller to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\n/**\n * Resolve the pixels a `FrameHandle` refers to. Local shm read when the\n * handle's `nodeId` matches `deps.ownNodeId`, else routed via\n * `deps.getRemoteFrame`. Returns `null` when the frame is no longer\n * available (slot recycled locally, or the remote node reports no frame).\n */\nexport async function resolveFrame(\n handle: FrameHandle,\n deps: ResolveFrameDeps,\n): Promise<DecodedFrame | null> {\n if (handle.nodeId === deps.ownNodeId) {\n return deps.readers.read(handle)\n }\n return deps.getRemoteFrame(handle)\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type {\n DecodedFrame,\n FrameHandle,\n IEventBus,\n IScopedLogger,\n TypedSystemEvent,\n} from '@camstack/types'\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig, EmbeddingStoredEvent } from '../types.js'\nimport { extractCrop } from '../services/crop-extractor.js'\nimport { resolveFrame } from '../services/resolve-frame.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface PendingCrop {\n readonly trackId: string\n readonly deviceId: string\n readonly class: string\n readonly confidence: number\n readonly frameData: Buffer\n readonly frameWidth: number\n readonly frameHeight: number\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n readonly receivedAt: number\n}\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly score: number\n readonly trackId?: string\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n }\n readonly objectState?: {\n readonly state: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\nexport interface EmbeddingDispatcherDeps {\n readonly config: EnrichmentConfig['embedding']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n /** Cluster node id of the consumer — used to detect co-location with the decoder. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the engine to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\nexport class EmbeddingDispatcher {\n private readonly config: EnrichmentConfig['embedding']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly logger: IScopedLogger\n private readonly ownNodeId: string\n private readonly readers: FrameRingReaderCache\n private readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n\n private readonly lastEmbedTime = new Map<string, number>()\n private readonly pendingCrops = new Map<string, PendingCrop>()\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private unsubscribe: (() => void) | null = null\n\n private _processedCount = 0\n private _totalInferenceMs = 0\n private _encoderIndex = 0\n\n constructor(deps: EmbeddingDispatcherDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.logger = deps.logger\n this.ownNodeId = deps.ownNodeId\n this.readers = deps.readers\n this.getRemoteFrame = deps.getRemoteFrame\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('EmbeddingDispatcher disabled')\n return\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { void this.handleDetectionResult(event) },\n )\n\n // Flush timer for best-confidence strategy\n if (this.config.cropStrategy === 'best-confidence') {\n this.flushTimer = setInterval(() => { void this.flushPending() }, 1000)\n }\n\n this.logger.info('EmbeddingDispatcher started', { meta: { strategy: this.config.cropStrategy, maxPerSec: this.config.maxPerSecPerCamera } })\n }\n\n async stop(): Promise<void> {\n this.unsubscribe?.()\n this.unsubscribe = null\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n // Flush remaining\n await this.flushPending()\n }\n\n get processedCount(): number { return this._processedCount }\n get avgInferenceMs(): number { return this._processedCount > 0 ? this._totalInferenceMs / this._processedCount : 0 }\n get queueDepth(): number { return this.pendingCrops.size }\n\n private async handleDetectionResult(event: TypedSystemEvent<'detection.result'>): Promise<void> {\n const data = event.data\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const detections = (data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n\n // Resolve the underlying frame once per event via the shm frame plane.\n // The handle's `nodeId` decides the path — local shm read when co-located\n // with the producing decoder, else a node-routed `decoder.getFrame` call.\n const handle = data.frameHandle\n if (!handle) {\n this.logger.debug('skip: no frameHandle on DetectionResult', {\n meta: { deviceId },\n })\n return\n }\n\n let decoded: DecodedFrame | null\n try {\n decoded = await resolveFrame(handle, {\n ownNodeId: this.ownNodeId,\n readers: this.readers,\n getRemoteFrame: this.getRemoteFrame,\n })\n } catch (err) {\n this.logger.debug('skip: resolveFrame threw', {\n meta: { deviceId, shmId: handle.shmId, error: String(err) },\n })\n return\n }\n\n if (!decoded) {\n this.logger.debug('skip: frame recycled before resolve', {\n meta: { deviceId, shmId: handle.shmId },\n })\n return\n }\n\n // Defensive: the live detection stream is decoded as RGB by the pipeline\n // runner. `extractCrop` interprets the buffer as raw RGB — bail if not.\n if (decoded.format !== 'rgb') {\n this.logger.debug('skip: resolved frame is not RGB', {\n meta: { deviceId, format: decoded.format },\n })\n return\n }\n\n const frameData = Buffer.isBuffer(decoded.data) ? decoded.data : Buffer.from(decoded.data)\n const frameWidth = decoded.width\n const frameHeight = decoded.height\n\n for (const det of detections) {\n const detection = det.detection\n if (!detection) continue\n\n // Class filter\n if (this.config.classes.length > 0 && !this.config.classes.includes(detection.class)) continue\n\n // Confidence filter\n if (detection.score < this.config.minConfidence) continue\n\n // Rate limit\n const now = Date.now()\n const minInterval = 1000 / this.config.maxPerSecPerCamera\n const lastTime = this.lastEmbedTime.get(deviceId) ?? 0\n if (now - lastTime < minInterval) continue\n\n const trackId = detection.trackId ?? `${deviceId}-${now}`\n\n const pending: PendingCrop = {\n trackId,\n deviceId,\n class: detection.class,\n confidence: detection.score,\n frameData,\n frameWidth,\n frameHeight,\n bbox: detection.bbox,\n receivedAt: now,\n }\n\n switch (this.config.cropStrategy) {\n case 'first': {\n if (!this.pendingCrops.has(trackId)) {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n }\n break\n }\n case 'best-confidence': {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n break\n }\n case 'track-end': {\n const state = det.objectState?.state\n if (state === 'leaving') {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n } else {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n }\n break\n }\n }\n }\n }\n\n private async flushPending(): Promise<void> {\n const now = Date.now()\n const toFlush: PendingCrop[] = []\n\n for (const [trackId, pending] of this.pendingCrops) {\n if (now - pending.receivedAt > 3000) {\n toFlush.push(pending)\n this.pendingCrops.delete(trackId)\n }\n }\n\n await Promise.all(toFlush.map(p => this.processOne(p)))\n }\n\n private async processOne(pending: PendingCrop): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n const { crop, width, height } = await extractCrop(\n pending.frameData, pending.frameWidth, pending.frameHeight, pending.bbox,\n )\n\n // Round-robin across encoders\n const encoder = this.encoders[this._encoderIndex % this.encoders.length]!\n this._encoderIndex++\n\n const { embedding: _embedding, inferenceMs } = await encoder.encode(crop, width, height)\n const info = encoder.getInfo()\n\n this._processedCount++\n this._totalInferenceMs += inferenceMs\n this.lastEmbedTime.set(pending.deviceId, Date.now())\n this.pendingCrops.delete(pending.trackId)\n\n const embeddingId = `${pending.deviceId}/${pending.trackId}/${Date.now()}`\n\n const payload: EmbeddingStoredEvent = {\n deviceId: Number(pending.deviceId),\n trackId: pending.trackId,\n class: pending.class,\n embeddingId,\n modelId: info.modelId,\n embeddingDim: info.embeddingDim,\n inferenceMs,\n timestamp: Date.now(),\n }\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentEmbeddingStored,\n { type: 'addon', id: 'enrichment-engine' },\n payload,\n ))\n\n this.logger.debug('Embedded track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { class: pending.class, trackId: pending.trackId, inferenceMs: Number(inferenceMs.toFixed(1)) },\n })\n } catch (err) {\n this.logger.warn('Failed to embed track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { trackId: pending.trackId, error: String(err) },\n })\n }\n }\n}\n","/**\n * Zod schemas for analytics-suite persisted record types.\n * Used by persistence services to parse settings-store query results.\n */\nimport { z } from 'zod'\n\n// ── Track Trail ─────────────────────────────────────────────────────\n\nexport const TrackPositionSchema = z.object({\n x: z.number(),\n y: z.number(),\n timestamp: z.number(),\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]),\n})\n\nexport const TrackSnapshotSchema = z.object({\n timestamp: z.number(),\n position: TrackPositionSchema,\n thumbnailPath: z.string(),\n})\n\nexport const TrackTrailSchema = z.object({\n trackId: z.string(),\n deviceId: z.string(),\n className: z.string(),\n label: z.string().optional(),\n firstSeen: z.number(),\n lastSeen: z.number(),\n positions: z.array(TrackPositionSchema),\n snapshots: z.array(TrackSnapshotSchema),\n totalDistance: z.number(),\n zonesVisited: z.array(z.string()),\n active: z.boolean(),\n})\n\nexport type TrackTrail = z.infer<typeof TrackTrailSchema>\n\n// ── Scene Monitor ───────────────────────────────────────────────────\n\nexport const SceneMonitorConfigSchema = z.object({\n monitors: z.array(z.object({\n id: z.string(),\n label: z.string(),\n roi: z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }),\n enabled: z.boolean(),\n states: z.array(z.object({\n id: z.string(),\n label: z.string(),\n prompt: z.string().optional(),\n textPrompts: z.array(z.string()).optional(),\n referenceEmbeddings: z.array(z.array(z.number())).optional(),\n threshold: z.number().optional(),\n notify: z.boolean().optional(),\n severity: z.enum(['info', 'warning', 'alert']).optional(),\n })),\n })),\n})\n\nexport type SceneMonitorConfig = z.infer<typeof SceneMonitorConfigSchema>\n\n/** Single scene monitor entry (element of SceneMonitorConfig.monitors). */\nexport type SceneMonitor = SceneMonitorConfig['monitors'][number]\n","/**\n * In-memory cache for text prompt embeddings.\n *\n * Keys follow the convention `${monitorId}:${text}` so that all entries\n * belonging to a specific scene monitor can be invalidated in one call.\n */\nexport class TextEmbeddingCache {\n private readonly cache = new Map<string, Float32Array>()\n\n get(key: string): Float32Array | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, embedding: Float32Array): void {\n this.cache.set(key, embedding)\n }\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Deletes all entries whose key starts with `${monitorId}:`.\n * Used when a scene monitor's text prompts are updated.\n */\n invalidateMonitor(monitorId: string): void {\n const prefix = `${monitorId}:`\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n clear(): void {\n this.cache.clear()\n }\n\n get size(): number {\n return this.cache.size\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient } from '@camstack/types'\nimport type { EnrichmentConfig, SceneStateChangedEvent } from '../types.js'\nimport { SceneMonitorConfigSchema, type SceneMonitor } from '../../_analytics-schemas/persistence-records.js'\nimport { TextEmbeddingCache } from '../services/text-embedding-cache.js'\nimport { extractCrop } from '../services/crop-extractor.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n encodeText(text: string): Promise<{ embedding: Float32Array }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface StreamBrokerRegistry {\n getSnapshot(deviceId: string): Promise<{ data: Buffer; width: number; height: number } | null>\n}\n\ninterface CameraMonitorState {\n currentState: string | null\n pendingState: string | null\n pendingCount: number\n pendingConfidence: number\n}\n\nexport interface SceneStateWorkerDeps {\n readonly config: EnrichmentConfig['sceneMonitor']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly streamBrokerRegistry: StreamBrokerRegistry\n readonly logger: IScopedLogger\n}\n\nexport class SceneStateWorker {\n private readonly config: EnrichmentConfig['sceneMonitor']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly streamBrokerRegistry: StreamBrokerRegistry\n private readonly logger: IScopedLogger\n\n private readonly textCache = new TextEmbeddingCache()\n private readonly monitorStates = new Map<string, CameraMonitorState>()\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private _activeCount = 0\n\n constructor(deps: SceneStateWorkerDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.streamBrokerRegistry = deps.streamBrokerRegistry\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('SceneStateWorker disabled')\n return\n }\n\n this.pollTimer = setInterval(\n () => { void this.pollAll() },\n this.config.pollIntervalSec * 1000,\n )\n this.logger.info('SceneStateWorker started', { meta: { pollIntervalSec: this.config.pollIntervalSec, hysteresis: this.config.hysteresisCount } })\n }\n\n async stop(): Promise<void> {\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n }\n\n get activeCount(): number { return this._activeCount }\n\n private async pollAll(): Promise<void> {\n // Load all scene monitor configs from settings\n const rawConfigs = await this.store.query.query({ collection: 'device-settings', filter: {\n where: { id: 'enrichment:scene-monitor:%' },\n } })\n const allConfigs = rawConfigs.map(r => ({ id: r.id, data: SceneMonitorConfigSchema.parse(r.data) }))\n\n const deviceMonitors = new Map<string, SceneMonitor[]>()\n for (const record of allConfigs) {\n const deviceId = record.id.replace('enrichment:scene-monitor:', '')\n const config = record.data\n const active = config.monitors.filter(m => m.enabled)\n if (active.length > 0) {\n deviceMonitors.set(deviceId, active)\n }\n }\n\n this._activeCount = [...deviceMonitors.values()].reduce((sum, ms) => sum + ms.length, 0)\n\n // Process each camera (1 snapshot per camera)\n await Promise.all(\n [...deviceMonitors.entries()].map(([deviceId, monitors]) =>\n this.pollCamera(deviceId, monitors),\n ),\n )\n }\n\n private async pollCamera(deviceId: string, monitors: SceneMonitor[]): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n // Capture 1 snapshot\n const snapshot = await this.streamBrokerRegistry.getSnapshot(deviceId)\n if (!snapshot) return\n\n const encoder = this.encoders[0]!\n\n // Process each monitor (crop from same snapshot)\n for (const monitor of monitors) {\n try {\n await this.processMonitor(deviceId, monitor, snapshot, encoder)\n } catch (err) {\n this.logger.warn('SceneState error', { tags: { deviceId: Number(deviceId) }, meta: { monitorLabel: monitor.label, error: String(err) } })\n }\n }\n } catch (err) {\n this.logger.warn('Failed to capture snapshot', { tags: { deviceId: Number(deviceId) }, meta: { error: String(err) } })\n }\n }\n\n private async processMonitor(\n deviceId: string,\n monitor: SceneMonitor,\n snapshot: { data: Buffer; width: number; height: number },\n encoder: EmbeddingEncoder,\n ): Promise<void> {\n // Crop ROI (normalized → pixel coords)\n const bbox = {\n x: monitor.roi.x * snapshot.width,\n y: monitor.roi.y * snapshot.height,\n w: monitor.roi.w * snapshot.width,\n h: monitor.roi.h * snapshot.height,\n }\n const { crop, width, height } = await extractCrop(snapshot.data, snapshot.width, snapshot.height, bbox)\n\n // Encode crop\n const { embedding: imageEmb } = await encoder.encode(crop, width, height)\n\n // Find best matching state\n let bestState: string | null = null\n let bestScore = -1\n\n for (const state of monitor.states) {\n let score = 0\n\n if (state.referenceEmbeddings && state.referenceEmbeddings.length > 0) {\n for (const refEmb of state.referenceEmbeddings) {\n const ref = new Float32Array(refEmb)\n const sim = cosineSimilarity(imageEmb, ref)\n score = Math.max(score, sim)\n }\n } else if (state.textPrompts && state.textPrompts.length > 0) {\n for (const prompt of state.textPrompts) {\n const cacheKey = `${monitor.id}:${state.id}:${prompt}`\n let textEmb = this.textCache.get(cacheKey)\n if (!textEmb) {\n const result = await encoder.encodeText(prompt)\n textEmb = result.embedding\n this.textCache.set(cacheKey, textEmb)\n }\n const sim = cosineSimilarity(imageEmb, textEmb)\n score = Math.max(score, sim)\n }\n }\n\n if (score > bestScore) {\n bestScore = score\n bestState = state.label\n }\n }\n\n if (!bestState) return\n\n // Hysteresis\n const key = `${deviceId}:${monitor.id}`\n let ms = this.monitorStates.get(key)\n if (!ms) {\n ms = { currentState: null, pendingState: null, pendingCount: 0, pendingConfidence: 0 }\n this.monitorStates.set(key, ms)\n }\n\n if (bestState === ms.pendingState) {\n ms.pendingCount++\n ms.pendingConfidence = bestScore\n } else {\n ms.pendingState = bestState\n ms.pendingCount = 1\n ms.pendingConfidence = bestScore\n }\n\n if (ms.pendingCount < this.config.hysteresisCount) return\n if (bestState === ms.currentState) return\n\n // State changed!\n const previousState = ms.currentState ?? 'unknown'\n ms.currentState = bestState\n ms.pendingState = null\n ms.pendingCount = 0\n\n const payload: SceneStateChangedEvent = {\n deviceId: Number(deviceId),\n monitorId: monitor.id,\n monitorLabel: monitor.label,\n previousState,\n currentState: bestState,\n confidence: bestScore,\n timestamp: Date.now(),\n }\n\n const data: Record<string, unknown> = { ...payload }\n this.eventBus.emit({\n id: `scene-state-${deviceId}-${monitor.id}-${Date.now()}`,\n category: EventCategory.EnrichmentSceneStateChanged,\n source: { type: 'device', id: deviceId },\n timestamp: new Date(),\n data,\n })\n\n this.logger.info('Scene state changed', {\n tags: { deviceId: Number(deviceId) },\n meta: { monitorLabel: monitor.label, previousState, currentState: bestState, confidence: Number(bestScore.toFixed(2)) },\n })\n }\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n return dot / (Math.sqrt(normA) * Math.sqrt(normB))\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient, TypedSystemEvent } from '@camstack/types'\nimport type { EnrichmentConfig, ActivitySummaryEvent, StateChangeEntry } from '../types.js'\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly trackId?: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\ninterface TrackInfo {\n class: string\n deviceId: string\n firstSeen: number\n lastSeen: number\n zones: Set<string>\n}\n\ninterface ZoneEventInfo {\n type: 'enter' | 'exit'\n zoneId: string\n trackId: string\n timestamp: number\n}\n\ninterface ActivityBuffer {\n tracks: Map<string, TrackInfo>\n zoneEvents: ZoneEventInfo[]\n stateChanges: StateChangeEntry[]\n}\n\nexport interface ActivitySummaryWorkerDeps {\n readonly config: EnrichmentConfig['activitySummary']\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nexport class ActivitySummaryWorker {\n private readonly config: EnrichmentConfig['activitySummary']\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n private readonly buffers = new Map<string, ActivityBuffer>()\n private summaryTimer: ReturnType<typeof setInterval> | null = null\n private unsubDetection: (() => void) | null = null\n private unsubSceneState: (() => void) | null = null\n private _lastSummary: ActivitySummaryEvent | null = null\n\n constructor(deps: ActivitySummaryWorkerDeps) {\n this.config = deps.config\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('ActivitySummaryWorker disabled')\n return\n }\n\n this.unsubDetection = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { this.handleDetection(event) },\n )\n\n this.unsubSceneState = this.eventBus.subscribe(\n { category: EventCategory.EnrichmentSceneStateChanged },\n (event) => { this.handleSceneStateChange(event) },\n )\n\n this.summaryTimer = setInterval(\n () => { void this.emitSummaries() },\n this.config.intervalSec * 1000,\n )\n\n this.logger.info('ActivitySummaryWorker started', { meta: { intervalSec: this.config.intervalSec } })\n }\n\n async stop(): Promise<void> {\n this.unsubDetection?.()\n this.unsubSceneState?.()\n if (this.summaryTimer) {\n clearInterval(this.summaryTimer)\n this.summaryTimer = null\n }\n // Emit final summaries\n await this.emitSummaries()\n }\n\n get lastSummary(): ActivitySummaryEvent | null { return this._lastSummary }\n\n private getBuffer(deviceId: string): ActivityBuffer {\n let buf = this.buffers.get(deviceId)\n if (!buf) {\n buf = { tracks: new Map(), zoneEvents: [], stateChanges: [] }\n this.buffers.set(deviceId, buf)\n }\n return buf\n }\n\n private handleDetection(event: TypedSystemEvent<'detection.result'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const results = (event.data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n const buf = this.getBuffer(deviceId)\n const now = Date.now()\n\n for (const det of results) {\n const detection = det.detection\n if (!detection) continue\n\n const trackId = detection.trackId ?? `unknown-${now}`\n const existing = buf.tracks.get(trackId)\n\n if (existing) {\n existing.lastSeen = now\n } else {\n buf.tracks.set(trackId, {\n class: detection.class,\n deviceId,\n firstSeen: now,\n lastSeen: now,\n zones: new Set(),\n })\n }\n\n // Zone events\n const zoneEvents = det.zoneEvents ?? []\n for (const ze of zoneEvents) {\n const track = buf.tracks.get(trackId)\n if (track) track.zones.add(ze.zoneId)\n\n if (ze.type === 'zone-enter' || ze.type === 'zone-exit') {\n buf.zoneEvents.push({\n type: ze.type === 'zone-enter' ? 'enter' : 'exit',\n zoneId: ze.zoneId,\n trackId,\n timestamp: ze.timestamp ?? now,\n })\n }\n }\n }\n }\n\n private handleSceneStateChange(event: TypedSystemEvent<'enrichment.scene.state-changed'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n const data = event.data\n const buf = this.getBuffer(deviceId)\n buf.stateChanges.push({\n monitorId: data.monitorId,\n from: data.previousState,\n to: data.currentState,\n timestamp: data.timestamp,\n })\n }\n\n private async emitSummaries(): Promise<void> {\n const now = Date.now()\n\n for (const [deviceId, buf] of this.buffers) {\n if (buf.tracks.size === 0 && buf.zoneEvents.length === 0 && buf.stateChanges.length === 0) {\n continue\n }\n\n // Object counts by class\n const objectCounts: Record<string, number> = {}\n for (const track of buf.tracks.values()) {\n objectCounts[track.class] = (objectCounts[track.class] ?? 0) + 1\n }\n\n // Zone activity\n const zoneMap = new Map<string, { entries: number; exits: number; dwellTimes: number[] }>()\n for (const ze of buf.zoneEvents) {\n let z = zoneMap.get(ze.zoneId)\n if (!z) {\n z = { entries: 0, exits: 0, dwellTimes: [] }\n zoneMap.set(ze.zoneId, z)\n }\n if (ze.type === 'enter') z.entries++\n else z.exits++\n }\n\n const zoneActivity = [...zoneMap.entries()].map(([zoneId, z]) => ({\n zoneId,\n entries: z.entries,\n exits: z.exits,\n avgDwellMs: z.dwellTimes.length > 0\n ? z.dwellTimes.reduce((a, b) => a + b, 0) / z.dwellTimes.length\n : 0,\n }))\n\n // Activity level\n const totalEvents = buf.tracks.size + buf.zoneEvents.length\n const eventsPerMin = totalEvents / (this.config.intervalSec / 60)\n const activityLevel = eventsPerMin >= this.config.activityThresholds.high ? 'high'\n : eventsPerMin >= this.config.activityThresholds.medium ? 'medium'\n : eventsPerMin >= this.config.activityThresholds.low ? 'low'\n : 'none' as const\n\n const summary: ActivitySummaryEvent = {\n deviceId: Number(deviceId),\n periodStart: now - this.config.intervalSec * 1000,\n periodEnd: now,\n objectCounts,\n zoneActivity,\n stateChanges: [...buf.stateChanges],\n activityLevel,\n }\n\n this._lastSummary = summary\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentActivitySummary,\n { type: 'device', id: deviceId },\n summary,\n ))\n\n // Persist for timeline UI\n try {\n await this.store.insert.mutate({ collection: 'addon-settings', record: {\n id: `${deviceId}:${now}`,\n data: { ...summary },\n } })\n } catch {\n // Best-effort persistence\n }\n\n // Reset buffer\n buf.tracks.clear()\n buf.zoneEvents.length = 0\n buf.stateChanges.length = 0\n }\n }\n}\n","import { BaseAddon, asJsonObject } from '@camstack/types'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig } from './types.js'\nimport { DEFAULT_ENRICHMENT_CONFIG } from './types.js'\nimport { EmbeddingDispatcher } from './workers/embedding-dispatcher.js'\nimport type { EmbeddingDispatcherDeps } from './workers/embedding-dispatcher.js'\nimport { SceneStateWorker } from './workers/scene-state-worker.js'\nimport type { SceneStateWorkerDeps } from './workers/scene-state-worker.js'\nimport { ActivitySummaryWorker } from './workers/activity-summary.js'\n\n/**\n * Extended context shape injected at runtime by the server's capability wiring.\n * Not part of the base AddonContext interface because capabilities are resolved\n * after addon initialization.\n */\n\nexport class EnrichmentEngineAddon extends BaseAddon {\n private embeddingDispatcher: EmbeddingDispatcher | null = null\n private sceneStateWorker: SceneStateWorker | null = null\n private activitySummary: ActivitySummaryWorker | null = null\n /**\n * Shared shm-ring reader cache for downstream frame access. Owned by the\n * engine so the cached segments stay open across worker restarts and close\n * exactly once on shutdown.\n */\n private readers: FrameRingReaderCache | null = null\n private currentFlags = {\n embeddingEnabled: true,\n sceneMonitorEnabled: true,\n activitySummaryEnabled: true,\n }\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n const config = await this.loadConfig()\n\n // Encoders will be injected by the server via capability wiring.\n // The server extends AddonContext with a `capabilities` object at runtime.\n // Runtime bridges: capabilities.getCollection/get return unknown because\n // the registry holds heterogeneous providers. The shapes are guaranteed\n // by the capability declaration — see project_cast_elimination_checkpoint.md\n // pattern #7.\n const encoderCollection = this.capabilities?.getCollection?.('embedding-encoder') ?? []\n const streamBrokerRaw = this.capabilities?.get?.('stream-broker')\n\n // Resolve this consumer's cluster node id once. When the runner lives in\n // a sub-broker the localNodeId is hierarchical (`hub/post-analysis`); we\n // strip the suffix so the value matches the `FrameHandle.nodeId` set by\n // the producing decoder (always the cluster-visible parent node).\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const ownNodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n\n this.readers = new FrameRingReaderCache(this.ctx.logger.child('shm-readers'))\n\n // Stable arrow held by the engine so it doesn't get re-allocated on every\n // detection event. Routes the cross-node read through the decoder cap with\n // `nodeId: handle.nodeId` per the cap's `nodeIdMode: 'routing'` default.\n // The cap schema infers `data: Uint8Array` (the wire shape); the engine\n // hands the dispatcher a `DecodedFrame` with `Buffer` so downstream\n // `extractCrop` (sharp) gets the Node-native type it expects.\n const getRemoteFrame = async (handle: FrameHandle): Promise<DecodedFrame | null> => {\n const remote = await this.ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })\n if (!remote) return null\n return {\n data: Buffer.from(remote.data),\n width: remote.width,\n height: remote.height,\n format: remote.format,\n timestamp: remote.timestamp,\n }\n }\n\n this.embeddingDispatcher = new EmbeddingDispatcher({\n config: config.embedding,\n encoders: encoderCollection as EmbeddingDispatcherDeps['encoders'],\n eventBus: this.ctx.eventBus,\n logger: this.ctx.logger.child('EmbeddingDispatcher'),\n ownNodeId,\n readers: this.readers,\n getRemoteFrame,\n })\n\n this.sceneStateWorker = new SceneStateWorker({\n config: config.sceneMonitor,\n encoders: encoderCollection as SceneStateWorkerDeps['encoders'],\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n streamBrokerRegistry: (streamBrokerRaw ?? { getSnapshot: async () => null }) as SceneStateWorkerDeps['streamBrokerRegistry'],\n logger: this.ctx.logger.child('SceneStateWorker'),\n })\n\n this.activitySummary = new ActivitySummaryWorker({\n config: config.activitySummary,\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n logger: this.ctx.logger.child('ActivitySummary'),\n })\n\n this.currentFlags = {\n embeddingEnabled: config.embedding.enabled,\n sceneMonitorEnabled: config.sceneMonitor.enabled,\n activitySummaryEnabled: config.activitySummary.enabled,\n }\n\n await this.embeddingDispatcher.start()\n await this.sceneStateWorker.start()\n await this.activitySummary.start()\n\n this.ctx.logger.info('Enrichment engine initialized with 3 workers')\n }\n\n protected async onShutdown(): Promise<void> {\n await this.embeddingDispatcher?.stop()\n await this.sceneStateWorker?.stop()\n await this.activitySummary?.stop()\n this.embeddingDispatcher = null\n this.sceneStateWorker = null\n this.activitySummary = null\n // Engine owns the shm-readers cache — close exactly here, not inside\n // the dispatcher (the cache is shared, double-close would be a bug).\n this.readers?.close()\n this.readers = null\n }\n\n // ── Three-level settings API (Phase 3) ─────────────────────────────\n //\n // Enrichment engine is post-detection infra. The three boolean toggle\n // flags (embedding/scene/activity) live in `getGlobalSettings` until\n // the dedicated post-detection UI exists. The underlying\n // EnrichmentConfig blob (stored opaquely in 'addon-settings' →\n // 'enrichment:global') is a separate concern — the flags below mirror\n // the `.enabled` field of each worker's config.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'enrichment-engine-settings',\n title: 'Enrichment Engine',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'embeddingEnabled',\n label: 'Embedding Enabled',\n description: 'Compute face/object embeddings from detection crops.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'sceneMonitorEnabled',\n label: 'Scene Monitor Enabled',\n description: 'Run periodic scene state capture for change detection.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'activitySummaryEnabled',\n label: 'Activity Summary Enabled',\n description: 'Aggregate hourly activity summaries per camera.',\n default: true,\n },\n ],\n },\n ],\n })\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeAddonStore(patch)\n const prev = this.currentFlags\n const next = {\n embeddingEnabled: typeof patch['embeddingEnabled'] === 'boolean' ? patch['embeddingEnabled'] : prev.embeddingEnabled,\n sceneMonitorEnabled: typeof patch['sceneMonitorEnabled'] === 'boolean' ? patch['sceneMonitorEnabled'] : prev.sceneMonitorEnabled,\n activitySummaryEnabled: typeof patch['activitySummaryEnabled'] === 'boolean' ? patch['activitySummaryEnabled'] : prev.activitySummaryEnabled,\n }\n this.currentFlags = next\n\n // Toggle workers based on flag changes\n if (prev.embeddingEnabled && !next.embeddingEnabled) {\n this.ctx?.logger.info('Stopping embedding dispatcher (disabled via config)')\n await this.embeddingDispatcher?.stop()\n } else if (!prev.embeddingEnabled && next.embeddingEnabled) {\n this.ctx?.logger.info('Starting embedding dispatcher (enabled via config)')\n await this.embeddingDispatcher?.start()\n }\n\n if (prev.sceneMonitorEnabled && !next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Stopping scene state worker (disabled via config)')\n await this.sceneStateWorker?.stop()\n } else if (!prev.sceneMonitorEnabled && next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Starting scene state worker (enabled via config)')\n await this.sceneStateWorker?.start()\n }\n\n if (prev.activitySummaryEnabled && !next.activitySummaryEnabled) {\n this.ctx?.logger.info('Stopping activity summary worker (disabled via config)')\n await this.activitySummary?.stop()\n } else if (!prev.activitySummaryEnabled && next.activitySummaryEnabled) {\n this.ctx?.logger.info('Starting activity summary worker (enabled via config)')\n await this.activitySummary?.start()\n }\n\n this.ctx?.logger.info('Enrichment engine flags updated', { meta: { flags: this.currentFlags } })\n }\n\n private async loadConfig(): Promise<EnrichmentConfig> {\n try {\n const stored = asJsonObject(await this.ctx.api?.settingsStore.get.query({ collection: 'addon-settings', key: 'enrichment:global' }))\n if (stored) {\n // Shallow structural merge: only keys from DEFAULT_ENRICHMENT_CONFIG\n // are preserved; stored values override their counterparts when\n // present with a matching type.\n const merged: EnrichmentConfig = { ...DEFAULT_ENRICHMENT_CONFIG, ...stored }\n return merged\n }\n } catch {\n // First boot — use defaults\n }\n return DEFAULT_ENRICHMENT_CONFIG\n }\n}\n\n// Default export — kernel addon-loader prefers mod.default to identify the addon class\nexport default EnrichmentEngineAddon\n"],"names":["EventCategory","createEvent","z.object","z.number","z.tuple","z.string","z.array","z.boolean","z.enum","BaseAddon","FrameRingReaderCache","asJsonObject"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/enrichment-engine/types.ts","../../src/enrichment-engine/services/crop-extractor.ts","../../src/enrichment-engine/services/resolve-frame.ts","../../src/enrichment-engine/workers/embedding-dispatcher.ts","../../src/_analytics-schemas/persistence-records.ts","../../src/enrichment-engine/services/text-embedding-cache.ts","../../src/enrichment-engine/workers/scene-state-worker.ts","../../src/enrichment-engine/workers/activity-summary.ts","../../src/enrichment-engine/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Enrichment Engine — Types\n// ---------------------------------------------------------------------------\n\n// --- Normalized Rectangle (0-1 coordinate space) ---\n\nexport interface NormalizedRect {\n readonly x: number // 0-1, left edge\n readonly y: number // 0-1, top edge\n readonly w: number // 0-1, width\n readonly h: number // 0-1, height\n}\n\n// --- Scene State ---\n\nexport interface SceneStateDefinition {\n readonly id: string\n readonly label: string // e.g. \"open\", \"closed\"\n readonly textPrompts: readonly string[] // zero-shot: [\"an open gate\"]\n readonly referenceEmbeddings: readonly (readonly number[])[] // optional: higher accuracy\n readonly notify: boolean\n readonly severity: 'info' | 'warning' | 'alert'\n}\n\nexport interface SceneMonitor {\n readonly id: string\n readonly label: string // e.g. \"Main Gate\"\n readonly roi: NormalizedRect // region of interest\n readonly enabled: boolean\n readonly states: readonly SceneStateDefinition[]\n}\n\nexport interface SceneMonitorConfig {\n readonly monitors: readonly SceneMonitor[]\n}\n\n// --- Enrichment Config (global) ---\n\nexport interface EnrichmentConfig {\n readonly enabled: boolean\n\n readonly embedding: {\n readonly enabled: boolean\n readonly modelId: string // 'clip-vit-b32' | 'clip-vit-b16' | 'siglip2-b16-256'\n readonly agentId: string | 'local'\n readonly runtime: 'node' | 'python'\n readonly backend: 'cpu' | 'coreml' | 'cuda'\n readonly classes: readonly string[] // [] = all classes\n readonly minConfidence: number\n readonly maxPerSecPerCamera: number\n readonly cropStrategy: 'first' | 'best-confidence' | 'track-end'\n readonly retentionDays: number\n }\n\n readonly sceneMonitor: {\n readonly enabled: boolean\n readonly modelId: string // can differ from embedding model\n readonly pollIntervalSec: number\n readonly hysteresisCount: number\n }\n\n readonly activitySummary: {\n readonly enabled: boolean\n readonly intervalSec: number\n readonly activityThresholds: {\n readonly low: number\n readonly medium: number\n readonly high: number\n }\n }\n}\n\n// --- Events ---\n\nexport interface EmbeddingStoredEvent {\n readonly deviceId: number\n readonly trackId: string\n readonly class: string\n readonly embeddingId: string // reference in vector store\n readonly modelId: string\n readonly embeddingDim: number\n readonly inferenceMs: number\n readonly timestamp: number\n}\n\nexport interface SceneStateChangedEvent {\n readonly deviceId: number\n readonly monitorId: string\n readonly monitorLabel: string\n readonly previousState: string\n readonly currentState: string\n readonly confidence: number\n readonly timestamp: number\n}\n\nexport interface ZoneActivityEntry {\n readonly zoneId: string\n readonly entries: number\n readonly exits: number\n readonly avgDwellMs: number\n}\n\nexport interface StateChangeEntry {\n readonly monitorId: string\n readonly from: string\n readonly to: string\n readonly timestamp: number\n}\n\nexport interface ActivitySummaryEvent {\n readonly deviceId: number\n readonly periodStart: number\n readonly periodEnd: number\n readonly objectCounts: Readonly<Record<string, number>>\n readonly zoneActivity: readonly ZoneActivityEntry[]\n readonly stateChanges: readonly StateChangeEntry[]\n readonly activityLevel: 'none' | 'low' | 'medium' | 'high'\n}\n\n// --- Defaults ---\n\nexport const DEFAULT_ENRICHMENT_CONFIG: EnrichmentConfig = {\n enabled: false,\n\n embedding: {\n enabled: false,\n modelId: 'clip-vit-b32',\n agentId: 'local',\n runtime: 'node',\n backend: 'cpu',\n classes: [],\n minConfidence: 0.5,\n maxPerSecPerCamera: 1,\n cropStrategy: 'first',\n retentionDays: 30,\n },\n\n sceneMonitor: {\n enabled: false,\n modelId: 'clip-vit-b32',\n pollIntervalSec: 10,\n hysteresisCount: 3,\n },\n\n activitySummary: {\n enabled: false,\n intervalSec: 60,\n activityThresholds: {\n low: 2,\n medium: 10,\n high: 30,\n },\n },\n}\n","import sharp from 'sharp'\n\n/**\n * Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.\n * Coordinates are clamped to frame bounds to avoid out-of-range errors.\n */\nexport async function extractCrop(\n frameData: Buffer,\n frameWidth: number,\n frameHeight: number,\n bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },\n): Promise<{ crop: Buffer; width: number; height: number }> {\n // Convert normalized coordinates to pixel values\n const rawLeft = Math.round(bbox.x * frameWidth)\n const rawTop = Math.round(bbox.y * frameHeight)\n const rawWidth = Math.round(bbox.w * frameWidth)\n const rawHeight = Math.round(bbox.h * frameHeight)\n\n // Clamp to frame bounds\n const left = Math.max(0, Math.min(rawLeft, frameWidth - 1))\n const top = Math.max(0, Math.min(rawTop, frameHeight - 1))\n const width = Math.max(1, Math.min(rawWidth, frameWidth - left))\n const height = Math.max(1, Math.min(rawHeight, frameHeight - top))\n\n const crop = await sharp(frameData, {\n raw: { width: frameWidth, height: frameHeight, channels: 3 },\n })\n .extract({ left, top, width, height })\n .jpeg({ quality: 90 })\n .toBuffer()\n\n return { crop, width, height }\n}\n","/**\n * `resolveFrame` — turn a serialisable `FrameHandle` back into pixels.\n *\n * The handle's `nodeId` decides the path: when the consumer is co-located\n * with the producing decoder, the pixels are read directly from the shared\n * memory ring (`readers.read(handle)`, a `null` means the slot was recycled\n * before the consumer got to it — a dropped frame, the correct latest-wins\n * behaviour for live video). Otherwise the caller-supplied\n * `getRemoteFrame(handle)` performs a node-routed `decoder.getFrame` call.\n *\n * Keeping the remote fetch as an injected callback keeps this helper free of\n * any cap-client / tRPC coupling and trivially testable.\n */\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\n\nexport interface ResolveFrameDeps {\n /** Cluster node id of the consumer — used to detect co-location. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the caller to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\n/**\n * Resolve the pixels a `FrameHandle` refers to. Local shm read when the\n * handle's `nodeId` matches `deps.ownNodeId`, else routed via\n * `deps.getRemoteFrame`. Returns `null` when the frame is no longer\n * available (slot recycled locally, or the remote node reports no frame).\n */\nexport async function resolveFrame(\n handle: FrameHandle,\n deps: ResolveFrameDeps,\n): Promise<DecodedFrame | null> {\n if (handle.nodeId === deps.ownNodeId) {\n return deps.readers.read(handle)\n }\n return deps.getRemoteFrame(handle)\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type {\n DecodedFrame,\n FrameHandle,\n IEventBus,\n IScopedLogger,\n TypedSystemEvent,\n} from '@camstack/types'\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig, EmbeddingStoredEvent } from '../types.js'\nimport { extractCrop } from '../services/crop-extractor.js'\nimport { resolveFrame } from '../services/resolve-frame.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface PendingCrop {\n readonly trackId: string\n readonly deviceId: string\n readonly class: string\n readonly confidence: number\n readonly frameData: Buffer\n readonly frameWidth: number\n readonly frameHeight: number\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n readonly receivedAt: number\n}\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly score: number\n readonly trackId?: string\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n }\n readonly objectState?: {\n readonly state: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\nexport interface EmbeddingDispatcherDeps {\n readonly config: EnrichmentConfig['embedding']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n /** Cluster node id of the consumer — used to detect co-location with the decoder. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the engine to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\nexport class EmbeddingDispatcher {\n private readonly config: EnrichmentConfig['embedding']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly logger: IScopedLogger\n private readonly ownNodeId: string\n private readonly readers: FrameRingReaderCache\n private readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n\n private readonly lastEmbedTime = new Map<string, number>()\n private readonly pendingCrops = new Map<string, PendingCrop>()\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private unsubscribe: (() => void) | null = null\n\n private _processedCount = 0\n private _totalInferenceMs = 0\n private _encoderIndex = 0\n\n constructor(deps: EmbeddingDispatcherDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.logger = deps.logger\n this.ownNodeId = deps.ownNodeId\n this.readers = deps.readers\n this.getRemoteFrame = deps.getRemoteFrame\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('EmbeddingDispatcher disabled')\n return\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { void this.handleDetectionResult(event) },\n )\n\n // Flush timer for best-confidence strategy\n if (this.config.cropStrategy === 'best-confidence') {\n this.flushTimer = setInterval(() => { void this.flushPending() }, 1000)\n }\n\n this.logger.info('EmbeddingDispatcher started', { meta: { strategy: this.config.cropStrategy, maxPerSec: this.config.maxPerSecPerCamera } })\n }\n\n async stop(): Promise<void> {\n this.unsubscribe?.()\n this.unsubscribe = null\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n // Flush remaining\n await this.flushPending()\n }\n\n get processedCount(): number { return this._processedCount }\n get avgInferenceMs(): number { return this._processedCount > 0 ? this._totalInferenceMs / this._processedCount : 0 }\n get queueDepth(): number { return this.pendingCrops.size }\n\n private async handleDetectionResult(event: TypedSystemEvent<'detection.result'>): Promise<void> {\n const data = event.data\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const detections = (data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n\n // Resolve the underlying frame once per event via the shm frame plane.\n // The handle's `nodeId` decides the path — local shm read when co-located\n // with the producing decoder, else a node-routed `decoder.getFrame` call.\n const handle = data.frameHandle\n if (!handle) {\n this.logger.debug('skip: no frameHandle on DetectionResult', {\n meta: { deviceId },\n })\n return\n }\n\n let decoded: DecodedFrame | null\n try {\n decoded = await resolveFrame(handle, {\n ownNodeId: this.ownNodeId,\n readers: this.readers,\n getRemoteFrame: this.getRemoteFrame,\n })\n } catch (err) {\n this.logger.debug('skip: resolveFrame threw', {\n meta: { deviceId, shmId: handle.shmId, error: String(err) },\n })\n return\n }\n\n if (!decoded) {\n this.logger.debug('skip: frame recycled before resolve', {\n meta: { deviceId, shmId: handle.shmId },\n })\n return\n }\n\n // Defensive: the live detection stream is decoded as RGB by the pipeline\n // runner. `extractCrop` interprets the buffer as raw RGB — bail if not.\n if (decoded.format !== 'rgb') {\n this.logger.debug('skip: resolved frame is not RGB', {\n meta: { deviceId, format: decoded.format },\n })\n return\n }\n\n const frameData = Buffer.isBuffer(decoded.data) ? decoded.data : Buffer.from(decoded.data)\n const frameWidth = decoded.width\n const frameHeight = decoded.height\n\n for (const det of detections) {\n const detection = det.detection\n if (!detection) continue\n\n // Class filter\n if (this.config.classes.length > 0 && !this.config.classes.includes(detection.class)) continue\n\n // Confidence filter\n if (detection.score < this.config.minConfidence) continue\n\n // Rate limit\n const now = Date.now()\n const minInterval = 1000 / this.config.maxPerSecPerCamera\n const lastTime = this.lastEmbedTime.get(deviceId) ?? 0\n if (now - lastTime < minInterval) continue\n\n const trackId = detection.trackId ?? `${deviceId}-${now}`\n\n const pending: PendingCrop = {\n trackId,\n deviceId,\n class: detection.class,\n confidence: detection.score,\n frameData,\n frameWidth,\n frameHeight,\n bbox: detection.bbox,\n receivedAt: now,\n }\n\n switch (this.config.cropStrategy) {\n case 'first': {\n if (!this.pendingCrops.has(trackId)) {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n }\n break\n }\n case 'best-confidence': {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n break\n }\n case 'track-end': {\n const state = det.objectState?.state\n if (state === 'leaving') {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n } else {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n }\n break\n }\n }\n }\n }\n\n private async flushPending(): Promise<void> {\n const now = Date.now()\n const toFlush: PendingCrop[] = []\n\n for (const [trackId, pending] of this.pendingCrops) {\n if (now - pending.receivedAt > 3000) {\n toFlush.push(pending)\n this.pendingCrops.delete(trackId)\n }\n }\n\n await Promise.all(toFlush.map(p => this.processOne(p)))\n }\n\n private async processOne(pending: PendingCrop): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n const { crop, width, height } = await extractCrop(\n pending.frameData, pending.frameWidth, pending.frameHeight, pending.bbox,\n )\n\n // Round-robin across encoders\n const encoder = this.encoders[this._encoderIndex % this.encoders.length]!\n this._encoderIndex++\n\n const { embedding: _embedding, inferenceMs } = await encoder.encode(crop, width, height)\n const info = encoder.getInfo()\n\n this._processedCount++\n this._totalInferenceMs += inferenceMs\n this.lastEmbedTime.set(pending.deviceId, Date.now())\n this.pendingCrops.delete(pending.trackId)\n\n const embeddingId = `${pending.deviceId}/${pending.trackId}/${Date.now()}`\n\n const payload: EmbeddingStoredEvent = {\n deviceId: Number(pending.deviceId),\n trackId: pending.trackId,\n class: pending.class,\n embeddingId,\n modelId: info.modelId,\n embeddingDim: info.embeddingDim,\n inferenceMs,\n timestamp: Date.now(),\n }\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentEmbeddingStored,\n { type: 'addon', id: 'enrichment-engine' },\n payload,\n ))\n\n this.logger.debug('Embedded track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { class: pending.class, trackId: pending.trackId, inferenceMs: Number(inferenceMs.toFixed(1)) },\n })\n } catch (err) {\n this.logger.warn('Failed to embed track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { trackId: pending.trackId, error: String(err) },\n })\n }\n }\n}\n","/**\n * Zod schemas for analytics-suite persisted record types.\n * Used by persistence services to parse settings-store query results.\n */\nimport { z } from 'zod'\n\n// ── Track Trail ─────────────────────────────────────────────────────\n\nexport const TrackPositionSchema = z.object({\n x: z.number(),\n y: z.number(),\n timestamp: z.number(),\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]),\n})\n\nexport const TrackSnapshotSchema = z.object({\n timestamp: z.number(),\n position: TrackPositionSchema,\n thumbnailPath: z.string(),\n})\n\nexport const TrackTrailSchema = z.object({\n trackId: z.string(),\n deviceId: z.string(),\n className: z.string(),\n label: z.string().optional(),\n firstSeen: z.number(),\n lastSeen: z.number(),\n positions: z.array(TrackPositionSchema),\n snapshots: z.array(TrackSnapshotSchema),\n totalDistance: z.number(),\n zonesVisited: z.array(z.string()),\n active: z.boolean(),\n})\n\nexport type TrackTrail = z.infer<typeof TrackTrailSchema>\n\n// ── Scene Monitor ───────────────────────────────────────────────────\n\nexport const SceneMonitorConfigSchema = z.object({\n monitors: z.array(z.object({\n id: z.string(),\n label: z.string(),\n roi: z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }),\n enabled: z.boolean(),\n states: z.array(z.object({\n id: z.string(),\n label: z.string(),\n prompt: z.string().optional(),\n textPrompts: z.array(z.string()).optional(),\n referenceEmbeddings: z.array(z.array(z.number())).optional(),\n threshold: z.number().optional(),\n notify: z.boolean().optional(),\n severity: z.enum(['info', 'warning', 'alert']).optional(),\n })),\n })),\n})\n\nexport type SceneMonitorConfig = z.infer<typeof SceneMonitorConfigSchema>\n\n/** Single scene monitor entry (element of SceneMonitorConfig.monitors). */\nexport type SceneMonitor = SceneMonitorConfig['monitors'][number]\n","/**\n * In-memory cache for text prompt embeddings.\n *\n * Keys follow the convention `${monitorId}:${text}` so that all entries\n * belonging to a specific scene monitor can be invalidated in one call.\n */\nexport class TextEmbeddingCache {\n private readonly cache = new Map<string, Float32Array>()\n\n get(key: string): Float32Array | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, embedding: Float32Array): void {\n this.cache.set(key, embedding)\n }\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Deletes all entries whose key starts with `${monitorId}:`.\n * Used when a scene monitor's text prompts are updated.\n */\n invalidateMonitor(monitorId: string): void {\n const prefix = `${monitorId}:`\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n clear(): void {\n this.cache.clear()\n }\n\n get size(): number {\n return this.cache.size\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient } from '@camstack/types'\nimport type { EnrichmentConfig, SceneStateChangedEvent } from '../types.js'\nimport { SceneMonitorConfigSchema, type SceneMonitor } from '../../_analytics-schemas/persistence-records.js'\nimport { TextEmbeddingCache } from '../services/text-embedding-cache.js'\nimport { extractCrop } from '../services/crop-extractor.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n encodeText(text: string): Promise<{ embedding: Float32Array }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface StreamBrokerRegistry {\n getSnapshot(deviceId: string): Promise<{ data: Buffer; width: number; height: number } | null>\n}\n\ninterface CameraMonitorState {\n currentState: string | null\n pendingState: string | null\n pendingCount: number\n pendingConfidence: number\n}\n\nexport interface SceneStateWorkerDeps {\n readonly config: EnrichmentConfig['sceneMonitor']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly streamBrokerRegistry: StreamBrokerRegistry\n readonly logger: IScopedLogger\n}\n\nexport class SceneStateWorker {\n private readonly config: EnrichmentConfig['sceneMonitor']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly streamBrokerRegistry: StreamBrokerRegistry\n private readonly logger: IScopedLogger\n\n private readonly textCache = new TextEmbeddingCache()\n private readonly monitorStates = new Map<string, CameraMonitorState>()\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private _activeCount = 0\n\n constructor(deps: SceneStateWorkerDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.streamBrokerRegistry = deps.streamBrokerRegistry\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('SceneStateWorker disabled')\n return\n }\n\n this.pollTimer = setInterval(\n () => { void this.pollAll() },\n this.config.pollIntervalSec * 1000,\n )\n this.logger.info('SceneStateWorker started', { meta: { pollIntervalSec: this.config.pollIntervalSec, hysteresis: this.config.hysteresisCount } })\n }\n\n async stop(): Promise<void> {\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n }\n\n get activeCount(): number { return this._activeCount }\n\n private async pollAll(): Promise<void> {\n // Load all scene monitor configs from settings\n const rawConfigs = await this.store.query.query({ collection: 'device-settings', filter: {\n where: { id: 'enrichment:scene-monitor:%' },\n } })\n const allConfigs = rawConfigs.map(r => ({ id: r.id, data: SceneMonitorConfigSchema.parse(r.data) }))\n\n const deviceMonitors = new Map<string, SceneMonitor[]>()\n for (const record of allConfigs) {\n const deviceId = record.id.replace('enrichment:scene-monitor:', '')\n const config = record.data\n const active = config.monitors.filter(m => m.enabled)\n if (active.length > 0) {\n deviceMonitors.set(deviceId, active)\n }\n }\n\n this._activeCount = [...deviceMonitors.values()].reduce((sum, ms) => sum + ms.length, 0)\n\n // Process each camera (1 snapshot per camera)\n await Promise.all(\n [...deviceMonitors.entries()].map(([deviceId, monitors]) =>\n this.pollCamera(deviceId, monitors),\n ),\n )\n }\n\n private async pollCamera(deviceId: string, monitors: SceneMonitor[]): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n // Capture 1 snapshot\n const snapshot = await this.streamBrokerRegistry.getSnapshot(deviceId)\n if (!snapshot) return\n\n const encoder = this.encoders[0]!\n\n // Process each monitor (crop from same snapshot)\n for (const monitor of monitors) {\n try {\n await this.processMonitor(deviceId, monitor, snapshot, encoder)\n } catch (err) {\n this.logger.warn('SceneState error', { tags: { deviceId: Number(deviceId) }, meta: { monitorLabel: monitor.label, error: String(err) } })\n }\n }\n } catch (err) {\n this.logger.warn('Failed to capture snapshot', { tags: { deviceId: Number(deviceId) }, meta: { error: String(err) } })\n }\n }\n\n private async processMonitor(\n deviceId: string,\n monitor: SceneMonitor,\n snapshot: { data: Buffer; width: number; height: number },\n encoder: EmbeddingEncoder,\n ): Promise<void> {\n // Crop ROI (normalized → pixel coords)\n const bbox = {\n x: monitor.roi.x * snapshot.width,\n y: monitor.roi.y * snapshot.height,\n w: monitor.roi.w * snapshot.width,\n h: monitor.roi.h * snapshot.height,\n }\n const { crop, width, height } = await extractCrop(snapshot.data, snapshot.width, snapshot.height, bbox)\n\n // Encode crop\n const { embedding: imageEmb } = await encoder.encode(crop, width, height)\n\n // Find best matching state\n let bestState: string | null = null\n let bestScore = -1\n\n for (const state of monitor.states) {\n let score = 0\n\n if (state.referenceEmbeddings && state.referenceEmbeddings.length > 0) {\n for (const refEmb of state.referenceEmbeddings) {\n const ref = new Float32Array(refEmb)\n const sim = cosineSimilarity(imageEmb, ref)\n score = Math.max(score, sim)\n }\n } else if (state.textPrompts && state.textPrompts.length > 0) {\n for (const prompt of state.textPrompts) {\n const cacheKey = `${monitor.id}:${state.id}:${prompt}`\n let textEmb = this.textCache.get(cacheKey)\n if (!textEmb) {\n const result = await encoder.encodeText(prompt)\n textEmb = result.embedding\n this.textCache.set(cacheKey, textEmb)\n }\n const sim = cosineSimilarity(imageEmb, textEmb)\n score = Math.max(score, sim)\n }\n }\n\n if (score > bestScore) {\n bestScore = score\n bestState = state.label\n }\n }\n\n if (!bestState) return\n\n // Hysteresis\n const key = `${deviceId}:${monitor.id}`\n let ms = this.monitorStates.get(key)\n if (!ms) {\n ms = { currentState: null, pendingState: null, pendingCount: 0, pendingConfidence: 0 }\n this.monitorStates.set(key, ms)\n }\n\n if (bestState === ms.pendingState) {\n ms.pendingCount++\n ms.pendingConfidence = bestScore\n } else {\n ms.pendingState = bestState\n ms.pendingCount = 1\n ms.pendingConfidence = bestScore\n }\n\n if (ms.pendingCount < this.config.hysteresisCount) return\n if (bestState === ms.currentState) return\n\n // State changed!\n const previousState = ms.currentState ?? 'unknown'\n ms.currentState = bestState\n ms.pendingState = null\n ms.pendingCount = 0\n\n const payload: SceneStateChangedEvent = {\n deviceId: Number(deviceId),\n monitorId: monitor.id,\n monitorLabel: monitor.label,\n previousState,\n currentState: bestState,\n confidence: bestScore,\n timestamp: Date.now(),\n }\n\n const data: Record<string, unknown> = { ...payload }\n this.eventBus.emit({\n id: `scene-state-${deviceId}-${monitor.id}-${Date.now()}`,\n category: EventCategory.EnrichmentSceneStateChanged,\n source: { type: 'device', id: deviceId },\n timestamp: new Date(),\n data,\n })\n\n this.logger.info('Scene state changed', {\n tags: { deviceId: Number(deviceId) },\n meta: { monitorLabel: monitor.label, previousState, currentState: bestState, confidence: Number(bestScore.toFixed(2)) },\n })\n }\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n return dot / (Math.sqrt(normA) * Math.sqrt(normB))\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient, TypedSystemEvent } from '@camstack/types'\nimport type { EnrichmentConfig, ActivitySummaryEvent, StateChangeEntry } from '../types.js'\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly trackId?: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\ninterface TrackInfo {\n class: string\n deviceId: string\n firstSeen: number\n lastSeen: number\n zones: Set<string>\n}\n\ninterface ZoneEventInfo {\n type: 'enter' | 'exit'\n zoneId: string\n trackId: string\n timestamp: number\n}\n\ninterface ActivityBuffer {\n tracks: Map<string, TrackInfo>\n zoneEvents: ZoneEventInfo[]\n stateChanges: StateChangeEntry[]\n}\n\nexport interface ActivitySummaryWorkerDeps {\n readonly config: EnrichmentConfig['activitySummary']\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nexport class ActivitySummaryWorker {\n private readonly config: EnrichmentConfig['activitySummary']\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n private readonly buffers = new Map<string, ActivityBuffer>()\n private summaryTimer: ReturnType<typeof setInterval> | null = null\n private unsubDetection: (() => void) | null = null\n private unsubSceneState: (() => void) | null = null\n private _lastSummary: ActivitySummaryEvent | null = null\n\n constructor(deps: ActivitySummaryWorkerDeps) {\n this.config = deps.config\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('ActivitySummaryWorker disabled')\n return\n }\n\n this.unsubDetection = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { this.handleDetection(event) },\n )\n\n this.unsubSceneState = this.eventBus.subscribe(\n { category: EventCategory.EnrichmentSceneStateChanged },\n (event) => { this.handleSceneStateChange(event) },\n )\n\n this.summaryTimer = setInterval(\n () => { void this.emitSummaries() },\n this.config.intervalSec * 1000,\n )\n\n this.logger.info('ActivitySummaryWorker started', { meta: { intervalSec: this.config.intervalSec } })\n }\n\n async stop(): Promise<void> {\n this.unsubDetection?.()\n this.unsubSceneState?.()\n if (this.summaryTimer) {\n clearInterval(this.summaryTimer)\n this.summaryTimer = null\n }\n // Emit final summaries\n await this.emitSummaries()\n }\n\n get lastSummary(): ActivitySummaryEvent | null { return this._lastSummary }\n\n private getBuffer(deviceId: string): ActivityBuffer {\n let buf = this.buffers.get(deviceId)\n if (!buf) {\n buf = { tracks: new Map(), zoneEvents: [], stateChanges: [] }\n this.buffers.set(deviceId, buf)\n }\n return buf\n }\n\n private handleDetection(event: TypedSystemEvent<'detection.result'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const results = (event.data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n const buf = this.getBuffer(deviceId)\n const now = Date.now()\n\n for (const det of results) {\n const detection = det.detection\n if (!detection) continue\n\n const trackId = detection.trackId ?? `unknown-${now}`\n const existing = buf.tracks.get(trackId)\n\n if (existing) {\n existing.lastSeen = now\n } else {\n buf.tracks.set(trackId, {\n class: detection.class,\n deviceId,\n firstSeen: now,\n lastSeen: now,\n zones: new Set(),\n })\n }\n\n // Zone events\n const zoneEvents = det.zoneEvents ?? []\n for (const ze of zoneEvents) {\n const track = buf.tracks.get(trackId)\n if (track) track.zones.add(ze.zoneId)\n\n if (ze.type === 'zone-enter' || ze.type === 'zone-exit') {\n buf.zoneEvents.push({\n type: ze.type === 'zone-enter' ? 'enter' : 'exit',\n zoneId: ze.zoneId,\n trackId,\n timestamp: ze.timestamp ?? now,\n })\n }\n }\n }\n }\n\n private handleSceneStateChange(event: TypedSystemEvent<'enrichment.scene.state-changed'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n const data = event.data\n const buf = this.getBuffer(deviceId)\n buf.stateChanges.push({\n monitorId: data.monitorId,\n from: data.previousState,\n to: data.currentState,\n timestamp: data.timestamp,\n })\n }\n\n private async emitSummaries(): Promise<void> {\n const now = Date.now()\n\n for (const [deviceId, buf] of this.buffers) {\n if (buf.tracks.size === 0 && buf.zoneEvents.length === 0 && buf.stateChanges.length === 0) {\n continue\n }\n\n // Object counts by class\n const objectCounts: Record<string, number> = {}\n for (const track of buf.tracks.values()) {\n objectCounts[track.class] = (objectCounts[track.class] ?? 0) + 1\n }\n\n // Zone activity\n const zoneMap = new Map<string, { entries: number; exits: number; dwellTimes: number[] }>()\n for (const ze of buf.zoneEvents) {\n let z = zoneMap.get(ze.zoneId)\n if (!z) {\n z = { entries: 0, exits: 0, dwellTimes: [] }\n zoneMap.set(ze.zoneId, z)\n }\n if (ze.type === 'enter') z.entries++\n else z.exits++\n }\n\n const zoneActivity = [...zoneMap.entries()].map(([zoneId, z]) => ({\n zoneId,\n entries: z.entries,\n exits: z.exits,\n avgDwellMs: z.dwellTimes.length > 0\n ? z.dwellTimes.reduce((a, b) => a + b, 0) / z.dwellTimes.length\n : 0,\n }))\n\n // Activity level\n const totalEvents = buf.tracks.size + buf.zoneEvents.length\n const eventsPerMin = totalEvents / (this.config.intervalSec / 60)\n const activityLevel = eventsPerMin >= this.config.activityThresholds.high ? 'high'\n : eventsPerMin >= this.config.activityThresholds.medium ? 'medium'\n : eventsPerMin >= this.config.activityThresholds.low ? 'low'\n : 'none' as const\n\n const summary: ActivitySummaryEvent = {\n deviceId: Number(deviceId),\n periodStart: now - this.config.intervalSec * 1000,\n periodEnd: now,\n objectCounts,\n zoneActivity,\n stateChanges: [...buf.stateChanges],\n activityLevel,\n }\n\n this._lastSummary = summary\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentActivitySummary,\n { type: 'device', id: deviceId },\n summary,\n ))\n\n // Persist for timeline UI\n try {\n await this.store.insert.mutate({ collection: 'addon-settings', record: {\n id: `${deviceId}:${now}`,\n data: { ...summary },\n } })\n } catch {\n // Best-effort persistence\n }\n\n // Reset buffer\n buf.tracks.clear()\n buf.zoneEvents.length = 0\n buf.stateChanges.length = 0\n }\n }\n}\n","import { BaseAddon, asJsonObject } from '@camstack/types'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig } from './types.js'\nimport { DEFAULT_ENRICHMENT_CONFIG } from './types.js'\nimport { EmbeddingDispatcher } from './workers/embedding-dispatcher.js'\nimport type { EmbeddingDispatcherDeps } from './workers/embedding-dispatcher.js'\nimport { SceneStateWorker } from './workers/scene-state-worker.js'\nimport type { SceneStateWorkerDeps } from './workers/scene-state-worker.js'\nimport { ActivitySummaryWorker } from './workers/activity-summary.js'\n\n/**\n * Extended context shape injected at runtime by the server's capability wiring.\n * Not part of the base AddonContext interface because capabilities are resolved\n * after addon initialization.\n */\n\nexport class EnrichmentEngineAddon extends BaseAddon {\n private embeddingDispatcher: EmbeddingDispatcher | null = null\n private sceneStateWorker: SceneStateWorker | null = null\n private activitySummary: ActivitySummaryWorker | null = null\n /**\n * Shared shm-ring reader cache for downstream frame access. Owned by the\n * engine so the cached segments stay open across worker restarts and close\n * exactly once on shutdown.\n */\n private readers: FrameRingReaderCache | null = null\n private currentFlags = {\n embeddingEnabled: true,\n sceneMonitorEnabled: true,\n activitySummaryEnabled: true,\n }\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n const config = await this.loadConfig()\n\n // Encoders will be injected by the server via capability wiring.\n // The server extends AddonContext with a `capabilities` object at runtime.\n // Runtime bridges: capabilities.getCollection/get return unknown because\n // the registry holds heterogeneous providers. The shapes are guaranteed\n // by the capability declaration — see project_cast_elimination_checkpoint.md\n // pattern #7.\n const encoderCollection = this.capabilities?.getCollection?.('embedding-encoder') ?? []\n const streamBrokerRaw = this.capabilities?.get?.('stream-broker')\n\n // Resolve this consumer's cluster node id once. When the runner lives in\n // a sub-broker the localNodeId is hierarchical (`hub/post-analysis`); we\n // strip the suffix so the value matches the `FrameHandle.nodeId` set by\n // the producing decoder (always the cluster-visible parent node).\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const ownNodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n\n this.readers = new FrameRingReaderCache(this.ctx.logger.child('shm-readers'))\n\n // Stable arrow held by the engine so it doesn't get re-allocated on every\n // detection event. Routes the cross-node read through the decoder cap with\n // `nodeId: handle.nodeId` per the cap's `nodeIdMode: 'routing'` default.\n // The cap schema infers `data: Uint8Array` (the wire shape); the engine\n // hands the dispatcher a `DecodedFrame` with `Buffer` so downstream\n // `extractCrop` (sharp) gets the Node-native type it expects.\n const getRemoteFrame = async (handle: FrameHandle): Promise<DecodedFrame | null> => {\n const remote = await this.ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })\n if (!remote) return null\n return {\n data: Buffer.from(remote.data),\n width: remote.width,\n height: remote.height,\n format: remote.format,\n timestamp: remote.timestamp,\n }\n }\n\n this.embeddingDispatcher = new EmbeddingDispatcher({\n config: config.embedding,\n encoders: encoderCollection as EmbeddingDispatcherDeps['encoders'],\n eventBus: this.ctx.eventBus,\n logger: this.ctx.logger.child('EmbeddingDispatcher'),\n ownNodeId,\n readers: this.readers,\n getRemoteFrame,\n })\n\n this.sceneStateWorker = new SceneStateWorker({\n config: config.sceneMonitor,\n encoders: encoderCollection as SceneStateWorkerDeps['encoders'],\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n streamBrokerRegistry: (streamBrokerRaw ?? { getSnapshot: async () => null }) as SceneStateWorkerDeps['streamBrokerRegistry'],\n logger: this.ctx.logger.child('SceneStateWorker'),\n })\n\n this.activitySummary = new ActivitySummaryWorker({\n config: config.activitySummary,\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n logger: this.ctx.logger.child('ActivitySummary'),\n })\n\n this.currentFlags = {\n embeddingEnabled: config.embedding.enabled,\n sceneMonitorEnabled: config.sceneMonitor.enabled,\n activitySummaryEnabled: config.activitySummary.enabled,\n }\n\n await this.embeddingDispatcher.start()\n await this.sceneStateWorker.start()\n await this.activitySummary.start()\n\n this.ctx.logger.info('Enrichment engine initialized with 3 workers')\n }\n\n protected async onShutdown(): Promise<void> {\n await this.embeddingDispatcher?.stop()\n await this.sceneStateWorker?.stop()\n await this.activitySummary?.stop()\n this.embeddingDispatcher = null\n this.sceneStateWorker = null\n this.activitySummary = null\n // Engine owns the shm-readers cache — close exactly here, not inside\n // the dispatcher (the cache is shared, double-close would be a bug).\n this.readers?.close()\n this.readers = null\n }\n\n // ── Three-level settings API (Phase 3) ─────────────────────────────\n //\n // Enrichment engine is post-detection infra. The three boolean toggle\n // flags (embedding/scene/activity) live in `getGlobalSettings` until\n // the dedicated post-detection UI exists. The underlying\n // EnrichmentConfig blob (stored opaquely in 'addon-settings' →\n // 'enrichment:global') is a separate concern — the flags below mirror\n // the `.enabled` field of each worker's config.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'enrichment-engine-settings',\n title: 'Enrichment Engine',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'embeddingEnabled',\n label: 'Embedding Enabled',\n description: 'Compute face/object embeddings from detection crops.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'sceneMonitorEnabled',\n label: 'Scene Monitor Enabled',\n description: 'Run periodic scene state capture for change detection.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'activitySummaryEnabled',\n label: 'Activity Summary Enabled',\n description: 'Aggregate hourly activity summaries per camera.',\n default: true,\n },\n ],\n },\n ],\n })\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeAddonStore(patch)\n const prev = this.currentFlags\n const next = {\n embeddingEnabled: typeof patch['embeddingEnabled'] === 'boolean' ? patch['embeddingEnabled'] : prev.embeddingEnabled,\n sceneMonitorEnabled: typeof patch['sceneMonitorEnabled'] === 'boolean' ? patch['sceneMonitorEnabled'] : prev.sceneMonitorEnabled,\n activitySummaryEnabled: typeof patch['activitySummaryEnabled'] === 'boolean' ? patch['activitySummaryEnabled'] : prev.activitySummaryEnabled,\n }\n this.currentFlags = next\n\n // Toggle workers based on flag changes\n if (prev.embeddingEnabled && !next.embeddingEnabled) {\n this.ctx?.logger.info('Stopping embedding dispatcher (disabled via config)')\n await this.embeddingDispatcher?.stop()\n } else if (!prev.embeddingEnabled && next.embeddingEnabled) {\n this.ctx?.logger.info('Starting embedding dispatcher (enabled via config)')\n await this.embeddingDispatcher?.start()\n }\n\n if (prev.sceneMonitorEnabled && !next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Stopping scene state worker (disabled via config)')\n await this.sceneStateWorker?.stop()\n } else if (!prev.sceneMonitorEnabled && next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Starting scene state worker (enabled via config)')\n await this.sceneStateWorker?.start()\n }\n\n if (prev.activitySummaryEnabled && !next.activitySummaryEnabled) {\n this.ctx?.logger.info('Stopping activity summary worker (disabled via config)')\n await this.activitySummary?.stop()\n } else if (!prev.activitySummaryEnabled && next.activitySummaryEnabled) {\n this.ctx?.logger.info('Starting activity summary worker (enabled via config)')\n await this.activitySummary?.start()\n }\n\n this.ctx?.logger.info('Enrichment engine flags updated', { meta: { flags: this.currentFlags } })\n }\n\n private async loadConfig(): Promise<EnrichmentConfig> {\n try {\n const stored = asJsonObject(await this.ctx.api?.settingsStore.get.query({ collection: 'addon-settings', key: 'enrichment:global' }))\n if (stored) {\n // Shallow structural merge: only keys from DEFAULT_ENRICHMENT_CONFIG\n // are preserved; stored values override their counterparts when\n // present with a matching type.\n const merged: EnrichmentConfig = { ...DEFAULT_ENRICHMENT_CONFIG, ...stored }\n return merged\n }\n } catch {\n // First boot — use defaults\n }\n return DEFAULT_ENRICHMENT_CONFIG\n }\n}\n\n// Default export — kernel addon-loader prefers mod.default to identify the addon class\nexport default EnrichmentEngineAddon\n"],"names":["z.object","z.number","z.tuple","z.string","z.array","z.boolean","z.enum"],"mappings":"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"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
function detectPlatformDefaults(ffmpegPath = "ffmpeg") {
|
|
4
|
-
let hwaccels = [];
|
|
5
|
-
try {
|
|
6
|
-
const output = execFileSync(ffmpegPath, ["-hwaccels", "-hide_banner"], { encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
7
|
-
hwaccels = output.split("\n").map((l) => l.trim()).filter((l) => l && l !== "Hardware acceleration methods:");
|
|
8
|
-
} catch {
|
|
9
|
-
}
|
|
10
|
-
const platform = os.platform();
|
|
11
|
-
if (platform === "darwin" && hwaccels.includes("videotoolbox")) return { hwaccel: "videotoolbox", threads: 0 };
|
|
12
|
-
if (platform === "linux") {
|
|
13
|
-
for (const hw of ["vaapi", "qsv", "cuda", "v4l2m2m"]) {
|
|
14
|
-
if (hwaccels.includes(hw)) return { hwaccel: hw, threads: 0 };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return { hwaccel: "none", threads: Math.max(1, Math.floor(os.cpus().length / 2)) };
|
|
18
|
-
}
|
|
19
|
-
function resolveFfmpegConfig(deviceConfig, globalConfig, detected) {
|
|
20
|
-
return {
|
|
21
|
-
path: deviceConfig?.path ?? globalConfig.path ?? detected.path ?? "ffmpeg",
|
|
22
|
-
hwaccel: deviceConfig?.hwaccel ?? globalConfig.hwaccel ?? detected.hwaccel ?? "none",
|
|
23
|
-
inputArgs: deviceConfig?.inputArgs ?? globalConfig.inputArgs,
|
|
24
|
-
outputArgs: deviceConfig?.outputArgs ?? globalConfig.outputArgs,
|
|
25
|
-
videoCodec: deviceConfig?.videoCodec ?? globalConfig.videoCodec ?? "copy",
|
|
26
|
-
audioCodec: deviceConfig?.audioCodec ?? globalConfig.audioCodec ?? "copy",
|
|
27
|
-
threads: deviceConfig?.threads ?? globalConfig.threads ?? detected.threads
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function buildFfmpegInputArgs(config, inputUrl) {
|
|
31
|
-
const args = ["-hide_banner", "-loglevel", "warning"];
|
|
32
|
-
if (config.hwaccel && config.hwaccel !== "none") args.push("-hwaccel", config.hwaccel);
|
|
33
|
-
if (config.threads !== void 0) args.push("-threads", String(config.threads));
|
|
34
|
-
if (config.inputArgs?.length) args.push(...config.inputArgs);
|
|
35
|
-
args.push("-rtsp_transport", "tcp", "-i", inputUrl);
|
|
36
|
-
return args;
|
|
37
|
-
}
|
|
38
|
-
function buildFfmpegOutputArgs(config) {
|
|
39
|
-
const args = ["-c:v", config.videoCodec ?? "copy", "-c:a", config.audioCodec ?? "copy"];
|
|
40
|
-
if (config.outputArgs?.length) args.push(...config.outputArgs);
|
|
41
|
-
return args;
|
|
42
|
-
}
|
|
43
|
-
async function resolveFfmpegBinary(configPath, deps) {
|
|
44
|
-
if (configPath && configPath !== "ffmpeg") {
|
|
45
|
-
return configPath;
|
|
46
|
-
}
|
|
47
|
-
return deps.ensureFfmpeg();
|
|
48
|
-
}
|
|
49
|
-
export {
|
|
50
|
-
buildFfmpegInputArgs,
|
|
51
|
-
buildFfmpegOutputArgs,
|
|
52
|
-
detectPlatformDefaults,
|
|
53
|
-
resolveFfmpegBinary,
|
|
54
|
-
resolveFfmpegConfig
|
|
55
|
-
};
|
|
56
|
-
//# sourceMappingURL=ffmpeg-config-DRONlBsj.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg-config-DRONlBsj.mjs","sources":["../src/recording/recording/ffmpeg-config.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport * as os from 'node:os'\nimport type { FfmpegConfig, IAddonDepsManager } from '@camstack/types'\n\n/**\n * Probe ffmpeg binary and detect optimal hwaccel for the host platform.\n * Called once at server startup, result cached.\n */\nexport function detectPlatformDefaults(ffmpegPath: string = 'ffmpeg'): Partial<FfmpegConfig> {\n let hwaccels: string[] = []\n try {\n const output = execFileSync(ffmpegPath, ['-hwaccels', '-hide_banner'], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })\n hwaccels = output.split('\\n').map(l => l.trim()).filter(l => l && l !== 'Hardware acceleration methods:')\n } catch {\n // ffmpeg not found or hwaccel probe failed — fall through to defaults\n }\n\n const platform = os.platform()\n if (platform === 'darwin' && hwaccels.includes('videotoolbox')) return { hwaccel: 'videotoolbox', threads: 0 }\n if (platform === 'linux') {\n for (const hw of ['vaapi', 'qsv', 'cuda', 'v4l2m2m'] as const) {\n if (hwaccels.includes(hw)) return { hwaccel: hw, threads: 0 }\n }\n }\n return { hwaccel: 'none', threads: Math.max(1, Math.floor(os.cpus().length / 2)) }\n}\n\n/**\n * Resolve ffmpeg config for a device.\n * Merge order: detected <- global <- per-device (most specific wins).\n */\nexport function resolveFfmpegConfig(\n deviceConfig: Partial<FfmpegConfig> | undefined,\n globalConfig: Partial<FfmpegConfig>,\n detected: Partial<FfmpegConfig>,\n): FfmpegConfig {\n return {\n path: deviceConfig?.path ?? globalConfig.path ?? detected.path ?? 'ffmpeg',\n hwaccel: deviceConfig?.hwaccel ?? globalConfig.hwaccel ?? detected.hwaccel ?? 'none',\n inputArgs: deviceConfig?.inputArgs ?? globalConfig.inputArgs,\n outputArgs: deviceConfig?.outputArgs ?? globalConfig.outputArgs,\n videoCodec: deviceConfig?.videoCodec ?? globalConfig.videoCodec ?? 'copy',\n audioCodec: deviceConfig?.audioCodec ?? globalConfig.audioCodec ?? 'copy',\n threads: deviceConfig?.threads ?? globalConfig.threads ?? detected.threads,\n }\n}\n\n/**\n * Build common ffmpeg args from a resolved config.\n */\nexport function buildFfmpegInputArgs(config: FfmpegConfig, inputUrl: string): string[] {\n const args: string[] = ['-hide_banner', '-loglevel', 'warning']\n if (config.hwaccel && config.hwaccel !== 'none') args.push('-hwaccel', config.hwaccel)\n if (config.threads !== undefined) args.push('-threads', String(config.threads))\n if (config.inputArgs?.length) args.push(...config.inputArgs)\n args.push('-rtsp_transport', 'tcp', '-i', inputUrl)\n return args\n}\n\nexport function buildFfmpegOutputArgs(config: FfmpegConfig): string[] {\n const args: string[] = ['-c:v', config.videoCodec ?? 'copy', '-c:a', config.audioCodec ?? 'copy']\n if (config.outputArgs?.length) args.push(...config.outputArgs)\n return args\n}\n\n/**\n * Resolve ffmpeg binary path. Priority:\n * 1. Explicit config path\n * 2. Embedded (data/deps/ffmpeg)\n * 3. System PATH\n * 4. Download static build\n */\nexport async function resolveFfmpegBinary(\n configPath: string | undefined,\n deps: IAddonDepsManager,\n): Promise<string> {\n // Explicit config\n if (configPath && configPath !== 'ffmpeg') {\n return configPath\n }\n\n // Try embedded or download via deps manager\n return deps.ensureFfmpeg()\n}\n"],"names":[],"mappings":";;AAQO,SAAS,uBAAuB,aAAqB,UAAiC;AAC3F,MAAI,WAAqB,CAAA;AACzB,MAAI;AACF,UAAM,SAAS,aAAa,YAAY,CAAC,aAAa,cAAc,GAAG,EAAE,UAAU,QAAQ,SAAS,KAAM,OAAO,CAAC,QAAQ,QAAQ,MAAM,GAAG;AAC3I,eAAW,OAAO,MAAM,IAAI,EAAE,IAAI,CAAA,MAAK,EAAE,KAAA,CAAM,EAAE,OAAO,CAAA,MAAK,KAAK,MAAM,gCAAgC;AAAA,EAC1G,QAAQ;AAAA,EAER;AAEA,QAAM,WAAW,GAAG,SAAA;AACpB,MAAI,aAAa,YAAY,SAAS,SAAS,cAAc,EAAG,QAAO,EAAE,SAAS,gBAAgB,SAAS,EAAA;AAC3G,MAAI,aAAa,SAAS;AACxB,eAAW,MAAM,CAAC,SAAS,OAAO,QAAQ,SAAS,GAAY;AAC7D,UAAI,SAAS,SAAS,EAAE,UAAU,EAAE,SAAS,IAAI,SAAS,EAAA;AAAA,IAC5D;AAAA,EACF;AACA,SAAO,EAAE,SAAS,QAAQ,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG,KAAA,EAAO,SAAS,CAAC,CAAC,EAAA;AACjF;AAMO,SAAS,oBACd,cACA,cACA,UACc;AACd,SAAO;AAAA,IACL,MAAM,cAAc,QAAQ,aAAa,QAAQ,SAAS,QAAQ;AAAA,IAClE,SAAS,cAAc,WAAW,aAAa,WAAW,SAAS,WAAW;AAAA,IAC9E,WAAW,cAAc,aAAa,aAAa;AAAA,IACnD,YAAY,cAAc,cAAc,aAAa;AAAA,IACrD,YAAY,cAAc,cAAc,aAAa,cAAc;AAAA,IACnE,YAAY,cAAc,cAAc,aAAa,cAAc;AAAA,IACnE,SAAS,cAAc,WAAW,aAAa,WAAW,SAAS;AAAA,EAAA;AAEvE;AAKO,SAAS,qBAAqB,QAAsB,UAA4B;AACrF,QAAM,OAAiB,CAAC,gBAAgB,aAAa,SAAS;AAC9D,MAAI,OAAO,WAAW,OAAO,YAAY,OAAQ,MAAK,KAAK,YAAY,OAAO,OAAO;AACrF,MAAI,OAAO,YAAY,OAAW,MAAK,KAAK,YAAY,OAAO,OAAO,OAAO,CAAC;AAC9E,MAAI,OAAO,WAAW,aAAa,KAAK,GAAG,OAAO,SAAS;AAC3D,OAAK,KAAK,mBAAmB,OAAO,MAAM,QAAQ;AAClD,SAAO;AACT;AAEO,SAAS,sBAAsB,QAAgC;AACpE,QAAM,OAAiB,CAAC,QAAQ,OAAO,cAAc,QAAQ,QAAQ,OAAO,cAAc,MAAM;AAChG,MAAI,OAAO,YAAY,aAAa,KAAK,GAAG,OAAO,UAAU;AAC7D,SAAO;AACT;AASA,eAAsB,oBACpB,YACA,MACiB;AAEjB,MAAI,cAAc,eAAe,UAAU;AACzC,WAAO;AAAA,EACT;AAGA,SAAO,KAAK,aAAA;AACd;"}
|