@camstack/vision 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addons/animal-classifier/index.d.mts +25 -0
- package/dist/addons/animal-classifier/index.d.ts +25 -0
- package/dist/addons/animal-classifier/index.js +469 -0
- package/dist/addons/animal-classifier/index.js.map +1 -0
- package/dist/addons/animal-classifier/index.mjs +9 -0
- package/dist/addons/animal-classifier/index.mjs.map +1 -0
- package/dist/addons/audio-classification/index.d.mts +31 -0
- package/dist/addons/audio-classification/index.d.ts +31 -0
- package/dist/addons/audio-classification/index.js +411 -0
- package/dist/addons/audio-classification/index.js.map +1 -0
- package/dist/addons/audio-classification/index.mjs +8 -0
- package/dist/addons/audio-classification/index.mjs.map +1 -0
- package/dist/addons/bird-global-classifier/index.d.mts +26 -0
- package/dist/addons/bird-global-classifier/index.d.ts +26 -0
- package/dist/addons/bird-global-classifier/index.js +475 -0
- package/dist/addons/bird-global-classifier/index.js.map +1 -0
- package/dist/addons/bird-global-classifier/index.mjs +9 -0
- package/dist/addons/bird-global-classifier/index.mjs.map +1 -0
- package/dist/addons/bird-nabirds-classifier/index.d.mts +28 -0
- package/dist/addons/bird-nabirds-classifier/index.d.ts +28 -0
- package/dist/addons/bird-nabirds-classifier/index.js +517 -0
- package/dist/addons/bird-nabirds-classifier/index.js.map +1 -0
- package/dist/addons/bird-nabirds-classifier/index.mjs +9 -0
- package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -0
- package/dist/addons/camera-native-detection/index.d.mts +32 -0
- package/dist/addons/camera-native-detection/index.d.ts +32 -0
- package/dist/addons/camera-native-detection/index.js +99 -0
- package/dist/addons/camera-native-detection/index.js.map +1 -0
- package/dist/addons/camera-native-detection/index.mjs +7 -0
- package/dist/addons/camera-native-detection/index.mjs.map +1 -0
- package/dist/addons/face-detection/index.d.mts +24 -0
- package/dist/addons/face-detection/index.d.ts +24 -0
- package/dist/addons/face-detection/index.js +513 -0
- package/dist/addons/face-detection/index.js.map +1 -0
- package/dist/addons/face-detection/index.mjs +10 -0
- package/dist/addons/face-detection/index.mjs.map +1 -0
- package/dist/addons/face-recognition/index.d.mts +24 -0
- package/dist/addons/face-recognition/index.d.ts +24 -0
- package/dist/addons/face-recognition/index.js +437 -0
- package/dist/addons/face-recognition/index.js.map +1 -0
- package/dist/addons/face-recognition/index.mjs +9 -0
- package/dist/addons/face-recognition/index.mjs.map +1 -0
- package/dist/addons/motion-detection/index.d.mts +26 -0
- package/dist/addons/motion-detection/index.d.ts +26 -0
- package/dist/addons/motion-detection/index.js +273 -0
- package/dist/addons/motion-detection/index.js.map +1 -0
- package/dist/addons/motion-detection/index.mjs +8 -0
- package/dist/addons/motion-detection/index.mjs.map +1 -0
- package/dist/addons/object-detection/index.d.mts +25 -0
- package/dist/addons/object-detection/index.d.ts +25 -0
- package/dist/addons/object-detection/index.js +673 -0
- package/dist/addons/object-detection/index.js.map +1 -0
- package/dist/addons/object-detection/index.mjs +10 -0
- package/dist/addons/object-detection/index.mjs.map +1 -0
- package/dist/addons/plate-detection/index.d.mts +25 -0
- package/dist/addons/plate-detection/index.d.ts +25 -0
- package/dist/addons/plate-detection/index.js +477 -0
- package/dist/addons/plate-detection/index.js.map +1 -0
- package/dist/addons/plate-detection/index.mjs +10 -0
- package/dist/addons/plate-detection/index.mjs.map +1 -0
- package/dist/addons/plate-recognition/index.d.mts +25 -0
- package/dist/addons/plate-recognition/index.d.ts +25 -0
- package/dist/addons/plate-recognition/index.js +470 -0
- package/dist/addons/plate-recognition/index.js.map +1 -0
- package/dist/addons/plate-recognition/index.mjs +9 -0
- package/dist/addons/plate-recognition/index.mjs.map +1 -0
- package/dist/chunk-3BKYLBBH.mjs +229 -0
- package/dist/chunk-3BKYLBBH.mjs.map +1 -0
- package/dist/chunk-4PC262GU.mjs +203 -0
- package/dist/chunk-4PC262GU.mjs.map +1 -0
- package/dist/chunk-6OR5TE7A.mjs +101 -0
- package/dist/chunk-6OR5TE7A.mjs.map +1 -0
- package/dist/chunk-7SZAISGP.mjs +210 -0
- package/dist/chunk-7SZAISGP.mjs.map +1 -0
- package/dist/chunk-AD2TFYZA.mjs +235 -0
- package/dist/chunk-AD2TFYZA.mjs.map +1 -0
- package/dist/chunk-CGYSSHHM.mjs +363 -0
- package/dist/chunk-CGYSSHHM.mjs.map +1 -0
- package/dist/chunk-IYHMGYGP.mjs +79 -0
- package/dist/chunk-IYHMGYGP.mjs.map +1 -0
- package/dist/chunk-J3IUBPRE.mjs +187 -0
- package/dist/chunk-J3IUBPRE.mjs.map +1 -0
- package/dist/chunk-KFZDJPYL.mjs +190 -0
- package/dist/chunk-KFZDJPYL.mjs.map +1 -0
- package/dist/chunk-KUO2BVFY.mjs +90 -0
- package/dist/chunk-KUO2BVFY.mjs.map +1 -0
- package/dist/chunk-PXBY3QOA.mjs +152 -0
- package/dist/chunk-PXBY3QOA.mjs.map +1 -0
- package/dist/chunk-XUKDL23Y.mjs +216 -0
- package/dist/chunk-XUKDL23Y.mjs.map +1 -0
- package/dist/chunk-Z26BVC7S.mjs +214 -0
- package/dist/chunk-Z26BVC7S.mjs.map +1 -0
- package/dist/chunk-Z5AHZQEZ.mjs +258 -0
- package/dist/chunk-Z5AHZQEZ.mjs.map +1 -0
- package/dist/index.d.mts +152 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +2775 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +205 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -0
- package/python/coreml_inference.py +67 -0
- package/python/openvino_inference.py +76 -0
- package/python/pytorch_inference.py +74 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
yoloPostprocess
|
|
3
|
+
} from "./chunk-KUO2BVFY.mjs";
|
|
4
|
+
import {
|
|
5
|
+
cropRegion,
|
|
6
|
+
letterbox
|
|
7
|
+
} from "./chunk-6OR5TE7A.mjs";
|
|
8
|
+
import {
|
|
9
|
+
resolveEngine
|
|
10
|
+
} from "./chunk-J3IUBPRE.mjs";
|
|
11
|
+
|
|
12
|
+
// src/addons/plate-detection/index.ts
|
|
13
|
+
import { PLATE_DETECTION_MODELS } from "@camstack/types";
|
|
14
|
+
var PLATE_LABEL = { id: "plate", name: "License Plate" };
|
|
15
|
+
var PLATE_LABELS = [PLATE_LABEL];
|
|
16
|
+
var PLATE_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
17
|
+
var PlateDetectionAddon = class {
|
|
18
|
+
id = "plate-detection";
|
|
19
|
+
slot = "cropper";
|
|
20
|
+
inputClasses = ["vehicle"];
|
|
21
|
+
outputClasses = ["plate"];
|
|
22
|
+
slotPriority = 0;
|
|
23
|
+
manifest = {
|
|
24
|
+
id: "plate-detection",
|
|
25
|
+
name: "License Plate Detection",
|
|
26
|
+
version: "0.1.0",
|
|
27
|
+
description: "YOLO-based license plate detector \u2014 crops plate regions from vehicle detections",
|
|
28
|
+
packageName: "@camstack/vision",
|
|
29
|
+
slot: "cropper",
|
|
30
|
+
inputClasses: ["vehicle"],
|
|
31
|
+
outputClasses: ["plate"],
|
|
32
|
+
supportsCustomModels: false,
|
|
33
|
+
mayRequirePython: false,
|
|
34
|
+
defaultConfig: {
|
|
35
|
+
modelId: "yolov8n-plate",
|
|
36
|
+
runtime: "auto",
|
|
37
|
+
backend: "cpu",
|
|
38
|
+
confidence: 0.5,
|
|
39
|
+
iouThreshold: 0.45
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
engine;
|
|
43
|
+
modelEntry;
|
|
44
|
+
confidence = 0.5;
|
|
45
|
+
iouThreshold = 0.45;
|
|
46
|
+
async initialize(ctx) {
|
|
47
|
+
const cfg = ctx.addonConfig;
|
|
48
|
+
const modelId = cfg["modelId"] ?? "yolov8n-plate";
|
|
49
|
+
const runtime = cfg["runtime"] ?? "auto";
|
|
50
|
+
const backend = cfg["backend"] ?? "cpu";
|
|
51
|
+
this.confidence = cfg["confidence"] ?? 0.5;
|
|
52
|
+
this.iouThreshold = cfg["iouThreshold"] ?? 0.45;
|
|
53
|
+
const entry = PLATE_DETECTION_MODELS.find((m) => m.id === modelId);
|
|
54
|
+
if (!entry) {
|
|
55
|
+
throw new Error(`PlateDetectionAddon: unknown modelId "${modelId}"`);
|
|
56
|
+
}
|
|
57
|
+
this.modelEntry = entry;
|
|
58
|
+
const resolved = await resolveEngine({
|
|
59
|
+
runtime,
|
|
60
|
+
backend,
|
|
61
|
+
modelEntry: entry,
|
|
62
|
+
modelsDir: ctx.locationPaths.models
|
|
63
|
+
});
|
|
64
|
+
this.engine = resolved.engine;
|
|
65
|
+
}
|
|
66
|
+
async crop(input) {
|
|
67
|
+
const start = Date.now();
|
|
68
|
+
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
69
|
+
const targetSize = Math.max(inputW, inputH);
|
|
70
|
+
const vehicleCrop = await cropRegion(input.frame.data, input.roi);
|
|
71
|
+
const lb = await letterbox(vehicleCrop, targetSize);
|
|
72
|
+
const output = await this.engine.run(lb.data, [1, 3, targetSize, targetSize]);
|
|
73
|
+
const numClasses = this.modelEntry.labels.length;
|
|
74
|
+
const numBoxes = output.length / (4 + numClasses);
|
|
75
|
+
const labels = this.modelEntry.labels.map((l) => l.id);
|
|
76
|
+
const plates = yoloPostprocess(output, numClasses, numBoxes, {
|
|
77
|
+
confidence: this.confidence,
|
|
78
|
+
iouThreshold: this.iouThreshold,
|
|
79
|
+
labels,
|
|
80
|
+
scale: lb.scale,
|
|
81
|
+
padX: lb.padX,
|
|
82
|
+
padY: lb.padY,
|
|
83
|
+
originalWidth: lb.originalWidth,
|
|
84
|
+
originalHeight: lb.originalHeight
|
|
85
|
+
});
|
|
86
|
+
const crops = plates.map((p) => ({ ...p, class: "plate", originalClass: p.originalClass }));
|
|
87
|
+
return {
|
|
88
|
+
crops,
|
|
89
|
+
inferenceMs: Date.now() - start,
|
|
90
|
+
modelId: this.modelEntry.id
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async shutdown() {
|
|
94
|
+
await this.engine?.dispose();
|
|
95
|
+
}
|
|
96
|
+
getConfigSchema() {
|
|
97
|
+
return {
|
|
98
|
+
sections: [
|
|
99
|
+
{
|
|
100
|
+
id: "thresholds",
|
|
101
|
+
title: "Detection Thresholds",
|
|
102
|
+
columns: 2,
|
|
103
|
+
fields: [
|
|
104
|
+
{
|
|
105
|
+
key: "confidence",
|
|
106
|
+
label: "Confidence Threshold",
|
|
107
|
+
type: "slider",
|
|
108
|
+
min: 0.1,
|
|
109
|
+
max: 1,
|
|
110
|
+
step: 0.05,
|
|
111
|
+
default: 0.5
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "iouThreshold",
|
|
115
|
+
label: "IoU Threshold (NMS)",
|
|
116
|
+
type: "slider",
|
|
117
|
+
min: 0.1,
|
|
118
|
+
max: 1,
|
|
119
|
+
step: 0.05,
|
|
120
|
+
default: 0.45
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
getClassMap() {
|
|
128
|
+
return PLATE_CLASS_MAP;
|
|
129
|
+
}
|
|
130
|
+
getModelCatalog() {
|
|
131
|
+
return [...PLATE_DETECTION_MODELS];
|
|
132
|
+
}
|
|
133
|
+
getAvailableModels() {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
getActiveLabels() {
|
|
137
|
+
return PLATE_LABELS;
|
|
138
|
+
}
|
|
139
|
+
async probe() {
|
|
140
|
+
return {
|
|
141
|
+
available: true,
|
|
142
|
+
runtime: this.engine?.runtime ?? "onnx",
|
|
143
|
+
device: this.engine?.device ?? "cpu",
|
|
144
|
+
capabilities: ["fp32"]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
PlateDetectionAddon
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=chunk-PXBY3QOA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/addons/plate-detection/index.ts"],"sourcesContent":["import type {\n ICropperProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n CropInput,\n CropperOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n IInferenceEngine,\n} from '@camstack/types'\nimport { PLATE_DETECTION_MODELS } from '@camstack/types'\nimport { cropRegion, letterbox } from '../../shared/image-utils.js'\nimport { yoloPostprocess } from '../../shared/postprocess/yolo.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nconst PLATE_LABEL: LabelDefinition = { id: 'plate', name: 'License Plate' }\nconst PLATE_LABELS: readonly LabelDefinition[] = [PLATE_LABEL]\nconst PLATE_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nexport default class PlateDetectionAddon implements ICropperProvider, IDetectionAddon {\n readonly id = 'plate-detection'\n readonly slot = 'cropper' as const\n readonly inputClasses = ['vehicle'] as const\n readonly outputClasses = ['plate'] as const\n readonly slotPriority = 0\n readonly manifest: AddonManifest = {\n id: 'plate-detection',\n name: 'License Plate Detection',\n version: '0.1.0',\n description: 'YOLO-based license plate detector — crops plate regions from vehicle detections',\n packageName: '@camstack/vision',\n slot: 'cropper',\n inputClasses: ['vehicle'],\n outputClasses: ['plate'],\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'yolov8n-plate',\n runtime: 'auto',\n backend: 'cpu',\n confidence: 0.5,\n iouThreshold: 0.45,\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n private confidence = 0.5\n private iouThreshold = 0.45\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'yolov8n-plate'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n this.confidence = (cfg['confidence'] as number | undefined) ?? 0.5\n this.iouThreshold = (cfg['iouThreshold'] as number | undefined) ?? 0.45\n\n const entry = PLATE_DETECTION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`PlateDetectionAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir: ctx.locationPaths.models,\n })\n this.engine = resolved.engine\n }\n\n async crop(input: CropInput): Promise<CropperOutput> {\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n const targetSize = Math.max(inputW, inputH)\n\n // Crop the vehicle region from the full frame\n const vehicleCrop = await cropRegion(input.frame.data, input.roi)\n\n const lb = await letterbox(vehicleCrop, targetSize)\n const output = await this.engine.run(lb.data, [1, 3, targetSize, targetSize])\n\n const numClasses = this.modelEntry.labels.length\n const numBoxes = output.length / (4 + numClasses)\n const labels = this.modelEntry.labels.map((l) => l.id)\n\n const plates = yoloPostprocess(output, numClasses, numBoxes, {\n confidence: this.confidence,\n iouThreshold: this.iouThreshold,\n labels,\n scale: lb.scale,\n padX: lb.padX,\n padY: lb.padY,\n originalWidth: lb.originalWidth,\n originalHeight: lb.originalHeight,\n })\n\n // Override class to 'plate'\n const crops = plates.map((p) => ({ ...p, class: 'plate', originalClass: p.originalClass }))\n\n return {\n crops,\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.dispose()\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'thresholds',\n title: 'Detection Thresholds',\n columns: 2,\n fields: [\n {\n key: 'confidence',\n label: 'Confidence Threshold',\n type: 'slider',\n min: 0.1,\n max: 1.0,\n step: 0.05,\n default: 0.5,\n },\n {\n key: 'iouThreshold',\n label: 'IoU Threshold (NMS)',\n type: 'slider',\n min: 0.1,\n max: 1.0,\n step: 0.05,\n default: 0.45,\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return PLATE_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...PLATE_DETECTION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return PLATE_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: this.engine?.runtime ?? 'onnx',\n device: this.engine?.device ?? 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cropRegion,
|
|
3
|
+
resizeAndNormalize
|
|
4
|
+
} from "./chunk-6OR5TE7A.mjs";
|
|
5
|
+
import {
|
|
6
|
+
resolveEngine
|
|
7
|
+
} from "./chunk-J3IUBPRE.mjs";
|
|
8
|
+
|
|
9
|
+
// src/addons/bird-global-classifier/index.ts
|
|
10
|
+
import { BIRD_SPECIES_MODELS } from "@camstack/types";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
var SPECIES_LABEL = { id: "species", name: "Bird Species" };
|
|
14
|
+
var SPECIES_LABELS = [SPECIES_LABEL];
|
|
15
|
+
var BIRD_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
16
|
+
function loadLabels(modelsDir, modelId) {
|
|
17
|
+
const labelNames = [
|
|
18
|
+
`camstack-${modelId}-labels.json`,
|
|
19
|
+
`camstack-bird-species-525-labels.json`
|
|
20
|
+
];
|
|
21
|
+
for (const name of labelNames) {
|
|
22
|
+
const labelPath = path.join(modelsDir, name);
|
|
23
|
+
if (fs.existsSync(labelPath)) {
|
|
24
|
+
const raw = fs.readFileSync(labelPath, "utf-8");
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`BirdGlobalClassifierAddon: labels JSON not found in ${modelsDir}`);
|
|
29
|
+
}
|
|
30
|
+
function softmax(logits) {
|
|
31
|
+
const max = logits.reduce((a, b) => Math.max(a, b), -Infinity);
|
|
32
|
+
const exps = logits.map((v) => Math.exp(v - max));
|
|
33
|
+
const sum = exps.reduce((a, b) => a + b, 0);
|
|
34
|
+
return exps.map((v) => v / sum);
|
|
35
|
+
}
|
|
36
|
+
var BirdGlobalClassifierAddon = class {
|
|
37
|
+
id = "bird-global-classifier";
|
|
38
|
+
slot = "classifier";
|
|
39
|
+
inputClasses = ["animal"];
|
|
40
|
+
outputClasses = ["species:*"];
|
|
41
|
+
slotPriority = 0;
|
|
42
|
+
requiredSteps = [];
|
|
43
|
+
manifest = {
|
|
44
|
+
id: "bird-global-classifier",
|
|
45
|
+
name: "Bird Classifier (Global, 525 species)",
|
|
46
|
+
version: "0.1.0",
|
|
47
|
+
description: "EfficientNet \u2014 525 worldwide bird species (MIT license, ONNX only)",
|
|
48
|
+
packageName: "@camstack/vision",
|
|
49
|
+
slot: "classifier",
|
|
50
|
+
inputClasses: ["animal"],
|
|
51
|
+
outputClasses: ["species:*"],
|
|
52
|
+
supportsCustomModels: false,
|
|
53
|
+
mayRequirePython: false,
|
|
54
|
+
defaultConfig: {
|
|
55
|
+
modelId: "bird-species-525",
|
|
56
|
+
runtime: "auto",
|
|
57
|
+
backend: "cpu",
|
|
58
|
+
minConfidence: 0.3
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
engine;
|
|
62
|
+
modelEntry;
|
|
63
|
+
labels = [];
|
|
64
|
+
minConfidence = 0.3;
|
|
65
|
+
async initialize(ctx) {
|
|
66
|
+
const cfg = ctx.addonConfig;
|
|
67
|
+
const modelId = cfg["modelId"] ?? "bird-species-525";
|
|
68
|
+
const runtime = cfg["runtime"] ?? "auto";
|
|
69
|
+
const backend = cfg["backend"] ?? "cpu";
|
|
70
|
+
this.minConfidence = cfg["minConfidence"] ?? 0.3;
|
|
71
|
+
const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId);
|
|
72
|
+
if (!entry) {
|
|
73
|
+
throw new Error(`BirdGlobalClassifierAddon: unknown modelId "${modelId}"`);
|
|
74
|
+
}
|
|
75
|
+
this.modelEntry = entry;
|
|
76
|
+
this.labels = loadLabels(ctx.locationPaths.models, modelId);
|
|
77
|
+
const resolved = await resolveEngine({
|
|
78
|
+
runtime,
|
|
79
|
+
backend,
|
|
80
|
+
modelEntry: entry,
|
|
81
|
+
modelsDir: ctx.locationPaths.models
|
|
82
|
+
});
|
|
83
|
+
this.engine = resolved.engine;
|
|
84
|
+
}
|
|
85
|
+
async classify(input) {
|
|
86
|
+
const start = Date.now();
|
|
87
|
+
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
88
|
+
const animalCrop = await cropRegion(input.frame.data, input.roi);
|
|
89
|
+
const normalized = await resizeAndNormalize(animalCrop, inputW, inputH, "imagenet", "nchw");
|
|
90
|
+
const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW]);
|
|
91
|
+
const probs = softmax(rawOutput);
|
|
92
|
+
let maxIdx = 0;
|
|
93
|
+
let maxScore = probs[0] ?? 0;
|
|
94
|
+
for (let i = 1; i < probs.length; i++) {
|
|
95
|
+
const score = probs[i] ?? 0;
|
|
96
|
+
if (score > maxScore) {
|
|
97
|
+
maxScore = score;
|
|
98
|
+
maxIdx = i;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (maxScore < this.minConfidence) {
|
|
102
|
+
return {
|
|
103
|
+
classifications: [],
|
|
104
|
+
inferenceMs: Date.now() - start,
|
|
105
|
+
modelId: this.modelEntry.id
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const label = this.labels[maxIdx] ?? `species_${maxIdx}`;
|
|
109
|
+
return {
|
|
110
|
+
classifications: [
|
|
111
|
+
{
|
|
112
|
+
class: label,
|
|
113
|
+
score: maxScore
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
inferenceMs: Date.now() - start,
|
|
117
|
+
modelId: this.modelEntry.id
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async shutdown() {
|
|
121
|
+
await this.engine?.dispose();
|
|
122
|
+
}
|
|
123
|
+
getConfigSchema() {
|
|
124
|
+
return {
|
|
125
|
+
sections: [
|
|
126
|
+
{
|
|
127
|
+
id: "model",
|
|
128
|
+
title: "Model",
|
|
129
|
+
columns: 1,
|
|
130
|
+
fields: [
|
|
131
|
+
{
|
|
132
|
+
key: "modelId",
|
|
133
|
+
label: "Model",
|
|
134
|
+
type: "model-selector",
|
|
135
|
+
catalog: [...BIRD_SPECIES_MODELS],
|
|
136
|
+
allowCustom: false,
|
|
137
|
+
allowConversion: false,
|
|
138
|
+
acceptFormats: ["onnx", "coreml", "openvino"],
|
|
139
|
+
requiredMetadata: ["inputSize", "labels"],
|
|
140
|
+
outputFormatHint: "classification"
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "thresholds",
|
|
146
|
+
title: "Classification Settings",
|
|
147
|
+
columns: 1,
|
|
148
|
+
fields: [
|
|
149
|
+
{
|
|
150
|
+
key: "minConfidence",
|
|
151
|
+
label: "Minimum Confidence",
|
|
152
|
+
type: "slider",
|
|
153
|
+
min: 0.05,
|
|
154
|
+
max: 1,
|
|
155
|
+
step: 0.05,
|
|
156
|
+
default: 0.3
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "runtime",
|
|
162
|
+
title: "Runtime",
|
|
163
|
+
columns: 2,
|
|
164
|
+
fields: [
|
|
165
|
+
{
|
|
166
|
+
key: "runtime",
|
|
167
|
+
label: "Runtime",
|
|
168
|
+
type: "select",
|
|
169
|
+
options: [
|
|
170
|
+
{ value: "auto", label: "Auto (recommended)" },
|
|
171
|
+
{ value: "onnx", label: "ONNX Runtime" },
|
|
172
|
+
{ value: "coreml", label: "CoreML (Apple)" }
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "backend",
|
|
177
|
+
label: "Backend",
|
|
178
|
+
type: "select",
|
|
179
|
+
dependsOn: { runtime: "onnx" },
|
|
180
|
+
options: [
|
|
181
|
+
{ value: "cpu", label: "CPU" },
|
|
182
|
+
{ value: "coreml", label: "CoreML" },
|
|
183
|
+
{ value: "cuda", label: "CUDA (NVIDIA)" }
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
getClassMap() {
|
|
192
|
+
return BIRD_CLASS_MAP;
|
|
193
|
+
}
|
|
194
|
+
getModelCatalog() {
|
|
195
|
+
return [...BIRD_SPECIES_MODELS];
|
|
196
|
+
}
|
|
197
|
+
getAvailableModels() {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
getActiveLabels() {
|
|
201
|
+
return SPECIES_LABELS;
|
|
202
|
+
}
|
|
203
|
+
async probe() {
|
|
204
|
+
return {
|
|
205
|
+
available: true,
|
|
206
|
+
runtime: this.engine?.runtime ?? "onnx",
|
|
207
|
+
device: this.engine?.device ?? "cpu",
|
|
208
|
+
capabilities: ["fp32"]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
BirdGlobalClassifierAddon
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=chunk-XUKDL23Y.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/addons/bird-global-classifier/index.ts"],"sourcesContent":["import type {\n IClassifierProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n CropInput,\n ClassifierOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n IInferenceEngine,\n} from '@camstack/types'\nimport { BIRD_SPECIES_MODELS } from '@camstack/types'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\nconst SPECIES_LABEL: LabelDefinition = { id: 'species', name: 'Bird Species' }\nconst SPECIES_LABELS: readonly LabelDefinition[] = [SPECIES_LABEL]\nconst BIRD_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\n/** Load bird species labels from JSON file in modelsDir */\nfunction loadLabels(modelsDir: string, modelId: string): readonly string[] {\n const labelNames = [\n `camstack-${modelId}-labels.json`,\n `camstack-bird-species-525-labels.json`,\n ]\n for (const name of labelNames) {\n const labelPath = path.join(modelsDir, name)\n if (fs.existsSync(labelPath)) {\n const raw = fs.readFileSync(labelPath, 'utf-8')\n return JSON.parse(raw) as string[]\n }\n }\n throw new Error(`BirdGlobalClassifierAddon: labels JSON not found in ${modelsDir}`)\n}\n\nfunction softmax(logits: Float32Array): Float32Array {\n const max = logits.reduce((a, b) => Math.max(a, b), -Infinity)\n const exps = logits.map((v) => Math.exp(v - max))\n const sum = exps.reduce((a, b) => a + b, 0)\n return exps.map((v) => v / sum) as unknown as Float32Array\n}\n\nexport default class BirdGlobalClassifierAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'bird-global-classifier'\n readonly slot = 'classifier' as const\n readonly inputClasses = ['animal'] as const\n readonly outputClasses = ['species:*'] as const\n readonly slotPriority = 0\n readonly requiredSteps = [] as const\n readonly manifest: AddonManifest = {\n id: 'bird-global-classifier',\n name: 'Bird Classifier (Global, 525 species)',\n version: '0.1.0',\n description: 'EfficientNet — 525 worldwide bird species (MIT license, ONNX only)',\n packageName: '@camstack/vision',\n slot: 'classifier',\n inputClasses: ['animal'],\n outputClasses: ['species:*'],\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'bird-species-525',\n runtime: 'auto',\n backend: 'cpu',\n minConfidence: 0.3,\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n private labels: readonly string[] = []\n private minConfidence = 0.3\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'bird-species-525'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n this.minConfidence = (cfg['minConfidence'] as number | undefined) ?? 0.3\n\n const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`BirdGlobalClassifierAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n\n // Load labels from JSON file\n this.labels = loadLabels(ctx.locationPaths.models, modelId)\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir: ctx.locationPaths.models,\n })\n this.engine = resolved.engine\n }\n\n async classify(input: CropInput): Promise<ClassifierOutput> {\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n\n // Crop the animal region\n const animalCrop = await cropRegion(input.frame.data, input.roi)\n\n // Resize to 224x224, ImageNet normalization, NCHW\n const normalized = await resizeAndNormalize(animalCrop, inputW, inputH, 'imagenet', 'nchw')\n\n // Run inference — output shape: [1, 525]\n const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW])\n\n // Softmax to get probabilities\n const probs = softmax(rawOutput)\n\n // Find argmax\n let maxIdx = 0\n let maxScore = probs[0] ?? 0\n for (let i = 1; i < probs.length; i++) {\n const score = probs[i] ?? 0\n if (score > maxScore) {\n maxScore = score\n maxIdx = i\n }\n }\n\n if (maxScore < this.minConfidence) {\n return {\n classifications: [],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n const label = this.labels[maxIdx] ?? `species_${maxIdx}`\n\n return {\n classifications: [\n {\n class: label,\n score: maxScore,\n },\n ],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.dispose()\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'model',\n title: 'Model',\n columns: 1,\n fields: [\n {\n key: 'modelId',\n label: 'Model',\n type: 'model-selector',\n catalog: [...BIRD_SPECIES_MODELS],\n allowCustom: false,\n allowConversion: false,\n acceptFormats: ['onnx', 'coreml', 'openvino'],\n requiredMetadata: ['inputSize', 'labels'],\n outputFormatHint: 'classification',\n },\n ],\n },\n {\n id: 'thresholds',\n title: 'Classification Settings',\n columns: 1,\n fields: [\n {\n key: 'minConfidence',\n label: 'Minimum Confidence',\n type: 'slider',\n min: 0.05,\n max: 1.0,\n step: 0.05,\n default: 0.3,\n },\n ],\n },\n {\n id: 'runtime',\n title: 'Runtime',\n columns: 2,\n fields: [\n {\n key: 'runtime',\n label: 'Runtime',\n type: 'select',\n options: [\n { value: 'auto', label: 'Auto (recommended)' },\n { value: 'onnx', label: 'ONNX Runtime' },\n { value: 'coreml', label: 'CoreML (Apple)' },\n ],\n },\n {\n key: 'backend',\n label: 'Backend',\n type: 'select',\n dependsOn: { runtime: 'onnx' },\n options: [\n { value: 'cpu', label: 'CPU' },\n { value: 'coreml', label: 'CoreML' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return BIRD_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...BIRD_SPECIES_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return SPECIES_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: this.engine?.runtime ?? 'onnx',\n device: this.engine?.device ?? 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cropRegion,
|
|
3
|
+
resizeAndNormalize
|
|
4
|
+
} from "./chunk-6OR5TE7A.mjs";
|
|
5
|
+
import {
|
|
6
|
+
resolveEngine
|
|
7
|
+
} from "./chunk-J3IUBPRE.mjs";
|
|
8
|
+
|
|
9
|
+
// src/addons/plate-recognition/index.ts
|
|
10
|
+
import { PLATE_RECOGNITION_MODELS } from "@camstack/types";
|
|
11
|
+
|
|
12
|
+
// src/shared/postprocess/paddleocr.ts
|
|
13
|
+
function ctcDecode(output, seqLen, numChars, charset) {
|
|
14
|
+
let totalLogScore = 0;
|
|
15
|
+
const rawIndices = [];
|
|
16
|
+
for (let t = 0; t < seqLen; t++) {
|
|
17
|
+
const offset = t * numChars;
|
|
18
|
+
let bestIdx = 0;
|
|
19
|
+
let bestVal = output[offset];
|
|
20
|
+
for (let c = 1; c < numChars; c++) {
|
|
21
|
+
const val = output[offset + c];
|
|
22
|
+
if (val > bestVal) {
|
|
23
|
+
bestVal = val;
|
|
24
|
+
bestIdx = c;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
rawIndices.push(bestIdx);
|
|
28
|
+
totalLogScore += bestVal;
|
|
29
|
+
}
|
|
30
|
+
const collapsed = [];
|
|
31
|
+
for (let i = 0; i < rawIndices.length; i++) {
|
|
32
|
+
const cur = rawIndices[i];
|
|
33
|
+
if (i === 0 || cur !== rawIndices[i - 1]) {
|
|
34
|
+
collapsed.push(cur);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const filtered = collapsed.filter((idx) => idx !== 0);
|
|
38
|
+
const text = filtered.map((idx) => charset[idx] ?? "").join("");
|
|
39
|
+
const confidence = seqLen > 0 ? totalLogScore / seqLen : 0;
|
|
40
|
+
return { text, confidence };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/addons/plate-recognition/index.ts
|
|
44
|
+
import * as fs from "fs";
|
|
45
|
+
import * as path from "path";
|
|
46
|
+
var PLATE_TEXT_LABEL = { id: "plate-text", name: "Plate Text" };
|
|
47
|
+
var PLATE_TEXT_LABELS = [PLATE_TEXT_LABEL];
|
|
48
|
+
var PLATE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
49
|
+
function loadCharset(modelsDir, modelId) {
|
|
50
|
+
const dictNames = [
|
|
51
|
+
`camstack-${modelId}-dict.txt`,
|
|
52
|
+
`camstack-paddleocr-latin-dict.txt`,
|
|
53
|
+
`camstack-paddleocr-en-dict.txt`
|
|
54
|
+
];
|
|
55
|
+
for (const name of dictNames) {
|
|
56
|
+
const dictPath = path.join(modelsDir, name);
|
|
57
|
+
if (fs.existsSync(dictPath)) {
|
|
58
|
+
const lines = fs.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
|
|
59
|
+
return ["", ...lines, " "];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`PlateRecognitionAddon: dict.txt not found in ${modelsDir}`);
|
|
63
|
+
}
|
|
64
|
+
var CHARSET = [];
|
|
65
|
+
var REQUIRED_STEPS = [
|
|
66
|
+
{ slot: "cropper", outputClasses: ["plate"], description: "Requires a plate detector" }
|
|
67
|
+
];
|
|
68
|
+
var PlateRecognitionAddon = class {
|
|
69
|
+
id = "plate-recognition";
|
|
70
|
+
slot = "classifier";
|
|
71
|
+
inputClasses = ["plate"];
|
|
72
|
+
outputClasses = ["plate-text:*"];
|
|
73
|
+
slotPriority = 0;
|
|
74
|
+
requiredSteps = REQUIRED_STEPS;
|
|
75
|
+
manifest = {
|
|
76
|
+
id: "plate-recognition",
|
|
77
|
+
name: "License Plate Recognition (OCR)",
|
|
78
|
+
version: "0.1.0",
|
|
79
|
+
description: "PaddleOCR-based license plate text recognition",
|
|
80
|
+
packageName: "@camstack/vision",
|
|
81
|
+
slot: "classifier",
|
|
82
|
+
inputClasses: ["plate"],
|
|
83
|
+
outputClasses: ["plate-text:*"],
|
|
84
|
+
requiredSteps: REQUIRED_STEPS,
|
|
85
|
+
supportsCustomModels: false,
|
|
86
|
+
mayRequirePython: false,
|
|
87
|
+
defaultConfig: {
|
|
88
|
+
modelId: "paddleocr-latin",
|
|
89
|
+
runtime: "auto",
|
|
90
|
+
backend: "cpu",
|
|
91
|
+
minConfidence: 0.5
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
engine;
|
|
95
|
+
modelEntry;
|
|
96
|
+
minConfidence = 0.5;
|
|
97
|
+
async initialize(ctx) {
|
|
98
|
+
const cfg = ctx.addonConfig;
|
|
99
|
+
const modelId = cfg["modelId"] ?? "paddleocr-latin";
|
|
100
|
+
const runtime = cfg["runtime"] ?? "auto";
|
|
101
|
+
const backend = cfg["backend"] ?? "cpu";
|
|
102
|
+
this.minConfidence = cfg["minConfidence"] ?? 0.5;
|
|
103
|
+
const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
|
|
104
|
+
if (!entry) {
|
|
105
|
+
throw new Error(`PlateRecognitionAddon: unknown modelId "${modelId}"`);
|
|
106
|
+
}
|
|
107
|
+
this.modelEntry = entry;
|
|
108
|
+
CHARSET = loadCharset(ctx.locationPaths.models, modelId);
|
|
109
|
+
const resolved = await resolveEngine({
|
|
110
|
+
runtime,
|
|
111
|
+
backend,
|
|
112
|
+
modelEntry: entry,
|
|
113
|
+
modelsDir: ctx.locationPaths.models
|
|
114
|
+
});
|
|
115
|
+
this.engine = resolved.engine;
|
|
116
|
+
}
|
|
117
|
+
async classify(input) {
|
|
118
|
+
const start = Date.now();
|
|
119
|
+
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
120
|
+
const plateCrop = await cropRegion(input.frame.data, input.roi);
|
|
121
|
+
const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, "zero-one", "nchw");
|
|
122
|
+
const output = await this.engine.run(normalized, [1, 3, inputH, inputW]);
|
|
123
|
+
const numChars = CHARSET.length;
|
|
124
|
+
const seqLen = output.length / numChars;
|
|
125
|
+
const { text, confidence } = ctcDecode(output, seqLen, numChars, CHARSET);
|
|
126
|
+
if (confidence < this.minConfidence || text.trim().length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
classifications: [],
|
|
129
|
+
inferenceMs: Date.now() - start,
|
|
130
|
+
modelId: this.modelEntry.id
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
classifications: [
|
|
135
|
+
{
|
|
136
|
+
class: "plate-text",
|
|
137
|
+
score: confidence,
|
|
138
|
+
text
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
inferenceMs: Date.now() - start,
|
|
142
|
+
modelId: this.modelEntry.id
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async shutdown() {
|
|
146
|
+
await this.engine?.dispose();
|
|
147
|
+
}
|
|
148
|
+
getConfigSchema() {
|
|
149
|
+
return {
|
|
150
|
+
sections: [
|
|
151
|
+
{
|
|
152
|
+
id: "model",
|
|
153
|
+
title: "Model",
|
|
154
|
+
columns: 1,
|
|
155
|
+
fields: [
|
|
156
|
+
{
|
|
157
|
+
key: "modelId",
|
|
158
|
+
label: "Model",
|
|
159
|
+
type: "model-selector",
|
|
160
|
+
catalog: [...PLATE_RECOGNITION_MODELS],
|
|
161
|
+
allowCustom: false,
|
|
162
|
+
allowConversion: false,
|
|
163
|
+
acceptFormats: ["onnx", "openvino"],
|
|
164
|
+
requiredMetadata: ["inputSize", "labels", "outputFormat"],
|
|
165
|
+
outputFormatHint: "ocr"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "thresholds",
|
|
171
|
+
title: "Recognition Settings",
|
|
172
|
+
columns: 1,
|
|
173
|
+
fields: [
|
|
174
|
+
{
|
|
175
|
+
key: "minConfidence",
|
|
176
|
+
label: "Minimum Confidence",
|
|
177
|
+
type: "slider",
|
|
178
|
+
min: 0.1,
|
|
179
|
+
max: 1,
|
|
180
|
+
step: 0.05,
|
|
181
|
+
default: 0.5
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
getClassMap() {
|
|
189
|
+
return PLATE_REC_CLASS_MAP;
|
|
190
|
+
}
|
|
191
|
+
getModelCatalog() {
|
|
192
|
+
return [...PLATE_RECOGNITION_MODELS];
|
|
193
|
+
}
|
|
194
|
+
getAvailableModels() {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
getActiveLabels() {
|
|
198
|
+
return PLATE_TEXT_LABELS;
|
|
199
|
+
}
|
|
200
|
+
async probe() {
|
|
201
|
+
return {
|
|
202
|
+
available: true,
|
|
203
|
+
runtime: this.engine?.runtime ?? "onnx",
|
|
204
|
+
device: this.engine?.device ?? "cpu",
|
|
205
|
+
capabilities: ["fp32"]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
ctcDecode,
|
|
212
|
+
PlateRecognitionAddon
|
|
213
|
+
};
|
|
214
|
+
//# sourceMappingURL=chunk-Z26BVC7S.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/addons/plate-recognition/index.ts","../src/shared/postprocess/paddleocr.ts"],"sourcesContent":["import type {\n IClassifierProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n CropInput,\n ClassifierOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n IInferenceEngine,\n RequiredStep,\n} from '@camstack/types'\nimport { PLATE_RECOGNITION_MODELS } from '@camstack/types'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { ctcDecode } from '../../shared/postprocess/paddleocr.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nconst PLATE_TEXT_LABEL: LabelDefinition = { id: 'plate-text', name: 'Plate Text' }\nconst PLATE_TEXT_LABELS: readonly LabelDefinition[] = [PLATE_TEXT_LABEL]\nconst PLATE_REC_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/** Load charset from dict.txt file — index 0 is the CTC blank token */\nfunction loadCharset(modelsDir: string, modelId: string): readonly string[] {\n // Try to find the dict file next to the model\n const dictNames = [\n `camstack-${modelId}-dict.txt`,\n `camstack-paddleocr-latin-dict.txt`,\n `camstack-paddleocr-en-dict.txt`,\n ]\n for (const name of dictNames) {\n const dictPath = path.join(modelsDir, name)\n if (fs.existsSync(dictPath)) {\n const lines = fs.readFileSync(dictPath, 'utf-8').split('\\n').filter((l) => l.length > 0)\n // PaddleOCR convention: blank token at index 0, then dict chars, then a space token at end\n return ['', ...lines, ' ']\n }\n }\n throw new Error(`PlateRecognitionAddon: dict.txt not found in ${modelsDir}`)\n}\n\n// Will be loaded during initialize()\nlet CHARSET: readonly string[] = []\n\nconst REQUIRED_STEPS: readonly RequiredStep[] = [\n { slot: 'cropper', outputClasses: ['plate'], description: 'Requires a plate detector' },\n]\n\nexport default class PlateRecognitionAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'plate-recognition'\n readonly slot = 'classifier' as const\n readonly inputClasses = ['plate'] as const\n readonly outputClasses = ['plate-text:*'] as const\n readonly slotPriority = 0\n readonly requiredSteps = REQUIRED_STEPS\n readonly manifest: AddonManifest = {\n id: 'plate-recognition',\n name: 'License Plate Recognition (OCR)',\n version: '0.1.0',\n description: 'PaddleOCR-based license plate text recognition',\n packageName: '@camstack/vision',\n slot: 'classifier',\n inputClasses: ['plate'],\n outputClasses: ['plate-text:*'],\n requiredSteps: REQUIRED_STEPS,\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'paddleocr-latin',\n runtime: 'auto',\n backend: 'cpu',\n minConfidence: 0.5,\n },\n }\n\n private engine!: IInferenceEngine\n private modelEntry!: ModelCatalogEntry\n private minConfidence = 0.5\n\n async initialize(ctx: AddonContext): Promise<void> {\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? 'paddleocr-latin'\n const runtime = (cfg['runtime'] as string | undefined) ?? 'auto'\n const backend = (cfg['backend'] as string | undefined) ?? 'cpu'\n this.minConfidence = (cfg['minConfidence'] as number | undefined) ?? 0.5\n\n const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`PlateRecognitionAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n\n // Load charset from dict.txt\n CHARSET = loadCharset(ctx.locationPaths.models, modelId)\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir: ctx.locationPaths.models,\n })\n this.engine = resolved.engine\n }\n\n async classify(input: CropInput): Promise<ClassifierOutput> {\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n\n // Crop the plate region\n const plateCrop = await cropRegion(input.frame.data, input.roi)\n\n // Resize to 320x48, normalize to [0,1], NCHW\n const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, 'zero-one', 'nchw')\n\n const output = await this.engine.run(normalized, [1, 3, inputH, inputW])\n\n // PaddleOCR CTC output shape: [1, seqLen, numChars]\n const numChars = CHARSET.length\n const seqLen = output.length / numChars\n const { text, confidence } = ctcDecode(output, seqLen, numChars, CHARSET)\n\n if (confidence < this.minConfidence || text.trim().length === 0) {\n return {\n classifications: [],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n return {\n classifications: [\n {\n class: 'plate-text',\n score: confidence,\n text,\n },\n ],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.dispose()\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'model',\n title: 'Model',\n columns: 1,\n fields: [\n {\n key: 'modelId',\n label: 'Model',\n type: 'model-selector',\n catalog: [...PLATE_RECOGNITION_MODELS],\n allowCustom: false,\n allowConversion: false,\n acceptFormats: ['onnx', 'openvino'],\n requiredMetadata: ['inputSize', 'labels', 'outputFormat'],\n outputFormatHint: 'ocr',\n },\n ],\n },\n {\n id: 'thresholds',\n title: 'Recognition Settings',\n columns: 1,\n fields: [\n {\n key: 'minConfidence',\n label: 'Minimum Confidence',\n type: 'slider',\n min: 0.1,\n max: 1.0,\n step: 0.05,\n default: 0.5,\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return PLATE_REC_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...PLATE_RECOGNITION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return PLATE_TEXT_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: this.engine?.runtime ?? 'onnx',\n device: this.engine?.device ?? 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n","/** Decode CTC output to text.\n *\n * Output shape: [1, seqLen, numChars]\n * Algorithm: argmax per timestep → collapse consecutive duplicates → remove blank (index 0) → join\n */\nexport function ctcDecode(\n output: Float32Array,\n seqLen: number,\n numChars: number,\n charset: readonly string[], // index 0 = blank token\n): { text: string; confidence: number } {\n // Step 1: argmax per timestep + track confidence as mean of selected scores\n let totalLogScore = 0\n const rawIndices: number[] = []\n\n for (let t = 0; t < seqLen; t++) {\n const offset = t * numChars\n let bestIdx = 0\n let bestVal = output[offset]!\n\n for (let c = 1; c < numChars; c++) {\n const val = output[offset + c]!\n if (val > bestVal) {\n bestVal = val\n bestIdx = c\n }\n }\n\n rawIndices.push(bestIdx)\n totalLogScore += bestVal\n }\n\n // Step 2: collapse consecutive duplicates\n const collapsed: number[] = []\n for (let i = 0; i < rawIndices.length; i++) {\n const cur = rawIndices[i]!\n if (i === 0 || cur !== rawIndices[i - 1]) {\n collapsed.push(cur)\n }\n }\n\n // Step 3: remove blank (index 0)\n const filtered = collapsed.filter((idx) => idx !== 0)\n\n // Step 4: join characters\n const text = filtered.map((idx) => charset[idx] ?? '').join('')\n\n const confidence = seqLen > 0 ? totalLogScore / seqLen : 0\n\n return { text, confidence }\n}\n"],"mappings":"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","names":[]}
|