@camstack/addon-vision 0.1.0 → 0.1.2
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 +6 -1
- package/dist/addons/animal-classifier/index.d.ts +6 -1
- package/dist/addons/animal-classifier/index.js +513 -49
- package/dist/addons/animal-classifier/index.js.map +1 -1
- package/dist/addons/animal-classifier/index.mjs +6 -4
- package/dist/addons/audio-classification/index.d.mts +6 -1
- package/dist/addons/audio-classification/index.d.ts +6 -1
- package/dist/addons/audio-classification/index.js +86 -26
- package/dist/addons/audio-classification/index.js.map +1 -1
- package/dist/addons/audio-classification/index.mjs +3 -2
- package/dist/addons/bird-global-classifier/index.d.mts +6 -1
- package/dist/addons/bird-global-classifier/index.d.ts +6 -1
- package/dist/addons/bird-global-classifier/index.js +514 -50
- package/dist/addons/bird-global-classifier/index.js.map +1 -1
- package/dist/addons/bird-global-classifier/index.mjs +6 -4
- package/dist/addons/bird-nabirds-classifier/index.d.mts +6 -1
- package/dist/addons/bird-nabirds-classifier/index.d.ts +6 -1
- package/dist/addons/bird-nabirds-classifier/index.js +523 -60
- package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.mjs +6 -4
- package/dist/addons/face-detection/index.d.mts +6 -1
- package/dist/addons/face-detection/index.d.ts +6 -1
- package/dist/addons/face-detection/index.js +538 -39
- package/dist/addons/face-detection/index.js.map +1 -1
- package/dist/addons/face-detection/index.mjs +5 -3
- package/dist/addons/face-recognition/index.d.mts +6 -1
- package/dist/addons/face-recognition/index.d.ts +6 -1
- package/dist/addons/face-recognition/index.js +487 -33
- package/dist/addons/face-recognition/index.js.map +1 -1
- package/dist/addons/face-recognition/index.mjs +5 -3
- package/dist/addons/motion-detection/index.d.mts +3 -1
- package/dist/addons/motion-detection/index.d.ts +3 -1
- package/dist/addons/motion-detection/index.js +11 -3
- package/dist/addons/motion-detection/index.js.map +1 -1
- package/dist/addons/motion-detection/index.mjs +140 -3
- package/dist/addons/motion-detection/index.mjs.map +1 -1
- package/dist/addons/object-detection/index.d.mts +6 -1
- package/dist/addons/object-detection/index.d.ts +6 -1
- package/dist/addons/object-detection/index.js +369 -72
- package/dist/addons/object-detection/index.js.map +1 -1
- package/dist/addons/object-detection/index.mjs +5 -3
- package/dist/addons/plate-detection/index.d.mts +6 -1
- package/dist/addons/plate-detection/index.d.ts +6 -1
- package/dist/addons/plate-detection/index.js +531 -31
- package/dist/addons/plate-detection/index.js.map +1 -1
- package/dist/addons/plate-detection/index.mjs +5 -3
- package/dist/addons/plate-recognition/index.d.mts +7 -1
- package/dist/addons/plate-recognition/index.d.ts +7 -1
- package/dist/addons/plate-recognition/index.js +176 -44
- package/dist/addons/plate-recognition/index.js.map +1 -1
- package/dist/addons/plate-recognition/index.mjs +4 -3
- package/dist/addons/segmentation-refiner/index.d.mts +30 -0
- package/dist/addons/segmentation-refiner/index.d.ts +30 -0
- package/dist/addons/segmentation-refiner/index.js +1048 -0
- package/dist/addons/segmentation-refiner/index.js.map +1 -0
- package/dist/addons/segmentation-refiner/index.mjs +209 -0
- package/dist/addons/segmentation-refiner/index.mjs.map +1 -0
- package/dist/addons/vehicle-classifier/index.d.mts +31 -0
- package/dist/addons/vehicle-classifier/index.d.ts +31 -0
- package/dist/addons/vehicle-classifier/index.js +688 -0
- package/dist/addons/vehicle-classifier/index.js.map +1 -0
- package/dist/addons/vehicle-classifier/index.mjs +250 -0
- package/dist/addons/vehicle-classifier/index.mjs.map +1 -0
- package/dist/{chunk-6OR5TE7A.mjs → chunk-22BHCDT5.mjs} +2 -2
- package/dist/chunk-22BHCDT5.mjs.map +1 -0
- package/dist/{chunk-LPI42WL6.mjs → chunk-2IOKI4ES.mjs} +23 -12
- package/dist/chunk-2IOKI4ES.mjs.map +1 -0
- package/dist/chunk-7DYHXUPZ.mjs +36 -0
- package/dist/chunk-7DYHXUPZ.mjs.map +1 -0
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BP7H4NFS.mjs +412 -0
- package/dist/chunk-BP7H4NFS.mjs.map +1 -0
- package/dist/chunk-BR2FPGOX.mjs +98 -0
- package/dist/chunk-BR2FPGOX.mjs.map +1 -0
- package/dist/{chunk-5AIQSN32.mjs → chunk-D6WEHN33.mjs} +66 -17
- package/dist/chunk-D6WEHN33.mjs.map +1 -0
- package/dist/{chunk-3MQFUDRU.mjs → chunk-DRYFGARD.mjs} +76 -47
- package/dist/chunk-DRYFGARD.mjs.map +1 -0
- package/dist/{chunk-ISOIDU4U.mjs → chunk-DUN6XU3N.mjs} +23 -5
- package/dist/chunk-DUN6XU3N.mjs.map +1 -0
- package/dist/{chunk-MEVASN3P.mjs → chunk-ESLHNWWE.mjs} +104 -22
- package/dist/chunk-ESLHNWWE.mjs.map +1 -0
- package/dist/{chunk-B3R66MPF.mjs → chunk-JUQEW6ON.mjs} +58 -21
- package/dist/chunk-JUQEW6ON.mjs.map +1 -0
- package/dist/{chunk-AYBFB7ID.mjs → chunk-R5J3WAUI.mjs} +200 -318
- package/dist/chunk-R5J3WAUI.mjs.map +1 -0
- package/dist/chunk-XZ6ZMXXU.mjs +39 -0
- package/dist/chunk-XZ6ZMXXU.mjs.map +1 -0
- package/dist/{chunk-5JJZGKL7.mjs → chunk-YPU4WTXZ.mjs} +102 -19
- package/dist/chunk-YPU4WTXZ.mjs.map +1 -0
- package/dist/{chunk-J4WRYHHY.mjs → chunk-YUCD2TFH.mjs} +66 -36
- package/dist/chunk-YUCD2TFH.mjs.map +1 -0
- package/dist/{chunk-PDSHDDPV.mjs → chunk-ZTJENCFC.mjs} +159 -35
- package/dist/chunk-ZTJENCFC.mjs.map +1 -0
- package/dist/{chunk-Q3SQOYG6.mjs → chunk-ZWYXXCXP.mjs} +67 -37
- package/dist/chunk-ZWYXXCXP.mjs.map +1 -0
- package/dist/index.d.mts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1343 -550
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +94 -18
- package/python/coreml_inference.py +61 -18
- package/python/openvino_inference.py +12 -4
- package/python/pytorch_inference.py +12 -4
- package/dist/addons/camera-native-detection/index.d.mts +0 -32
- package/dist/addons/camera-native-detection/index.d.ts +0 -32
- package/dist/addons/camera-native-detection/index.js +0 -99
- package/dist/addons/camera-native-detection/index.js.map +0 -1
- package/dist/addons/camera-native-detection/index.mjs +0 -7
- package/dist/chunk-3MQFUDRU.mjs.map +0 -1
- package/dist/chunk-5AIQSN32.mjs.map +0 -1
- package/dist/chunk-5JJZGKL7.mjs.map +0 -1
- package/dist/chunk-6OR5TE7A.mjs.map +0 -1
- package/dist/chunk-AYBFB7ID.mjs.map +0 -1
- package/dist/chunk-B3R66MPF.mjs.map +0 -1
- package/dist/chunk-DTOAB2CE.mjs +0 -79
- package/dist/chunk-DTOAB2CE.mjs.map +0 -1
- package/dist/chunk-ISOIDU4U.mjs.map +0 -1
- package/dist/chunk-J4WRYHHY.mjs.map +0 -1
- package/dist/chunk-LPI42WL6.mjs.map +0 -1
- package/dist/chunk-MEVASN3P.mjs.map +0 -1
- package/dist/chunk-PDSHDDPV.mjs.map +0 -1
- package/dist/chunk-Q3SQOYG6.mjs.map +0 -1
- package/dist/chunk-QIMDG34B.mjs +0 -229
- package/dist/chunk-QIMDG34B.mjs.map +0 -1
- package/python/__pycache__/coreml_inference.cpython-313.pyc +0 -0
- package/python/__pycache__/openvino_inference.cpython-313.pyc +0 -0
- package/python/__pycache__/pytorch_inference.cpython-313.pyc +0 -0
- /package/dist/{addons/camera-native-detection/index.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cropRegion,
|
|
3
3
|
resizeAndNormalize
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-22BHCDT5.mjs";
|
|
5
5
|
import {
|
|
6
6
|
resolveEngine
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2IOKI4ES.mjs";
|
|
8
|
+
import {
|
|
9
|
+
__require
|
|
10
|
+
} from "./chunk-BJTO5JO5.mjs";
|
|
8
11
|
|
|
9
12
|
// src/catalogs/plate-recognition-models.ts
|
|
10
13
|
import { hfModelUrl } from "@camstack/types";
|
|
@@ -13,27 +16,38 @@ var PLATE_TEXT_LABELS = [
|
|
|
13
16
|
{ id: "text", name: "Plate Text" }
|
|
14
17
|
];
|
|
15
18
|
var PLATE_RECOGNITION_MODELS = [
|
|
19
|
+
// ── PaddleOCR PP-OCRv5 ────────────────────────────────────────
|
|
16
20
|
{
|
|
17
21
|
id: "paddleocr-latin",
|
|
18
22
|
name: "PaddleOCR Latin",
|
|
19
|
-
description: "PaddleOCR recognition model for Latin-script license plates",
|
|
23
|
+
description: "PaddleOCR PP-OCRv5 recognition model for Latin-script license plates",
|
|
20
24
|
inputSize: { width: 320, height: 48 },
|
|
21
25
|
labels: PLATE_TEXT_LABELS,
|
|
22
26
|
formats: {
|
|
27
|
+
// ONNX only — PaddleOCR has dynamic dimensions incompatible with CoreML native conversion.
|
|
28
|
+
// On Apple Silicon, ONNX Runtime uses CoreML EP automatically for acceleration.
|
|
23
29
|
onnx: {
|
|
24
30
|
url: hfModelUrl(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-rec.onnx"),
|
|
25
31
|
sizeMB: 7.5
|
|
26
32
|
},
|
|
27
33
|
openvino: {
|
|
28
34
|
url: hfModelUrl(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-latin.xml"),
|
|
29
|
-
sizeMB: 4
|
|
35
|
+
sizeMB: 4,
|
|
36
|
+
runtimes: ["python"]
|
|
30
37
|
}
|
|
31
|
-
}
|
|
38
|
+
},
|
|
39
|
+
extraFiles: [
|
|
40
|
+
{
|
|
41
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-dict.txt"),
|
|
42
|
+
filename: "camstack-paddleocr-latin-dict.txt",
|
|
43
|
+
sizeMB: 0.01
|
|
44
|
+
}
|
|
45
|
+
]
|
|
32
46
|
},
|
|
33
47
|
{
|
|
34
48
|
id: "paddleocr-en",
|
|
35
49
|
name: "PaddleOCR English",
|
|
36
|
-
description: "PaddleOCR recognition model optimized for English license plates",
|
|
50
|
+
description: "PaddleOCR PP-OCRv5 recognition model optimized for English license plates",
|
|
37
51
|
inputSize: { width: 320, height: 48 },
|
|
38
52
|
labels: PLATE_TEXT_LABELS,
|
|
39
53
|
formats: {
|
|
@@ -43,9 +57,59 @@ var PLATE_RECOGNITION_MODELS = [
|
|
|
43
57
|
},
|
|
44
58
|
openvino: {
|
|
45
59
|
url: hfModelUrl(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-en.xml"),
|
|
46
|
-
sizeMB: 4
|
|
60
|
+
sizeMB: 4,
|
|
61
|
+
runtimes: ["python"]
|
|
47
62
|
}
|
|
48
|
-
}
|
|
63
|
+
},
|
|
64
|
+
extraFiles: [
|
|
65
|
+
{
|
|
66
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-dict.txt"),
|
|
67
|
+
filename: "camstack-paddleocr-en-dict.txt",
|
|
68
|
+
sizeMB: 0.01
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
// ── CRNN-MobileNetV3 (via OnnxTR/docTR) ─────────────────────────
|
|
73
|
+
// Simple CNN+LSTM+CTC architecture — good CoreML compatibility (no dynamic ops)
|
|
74
|
+
{
|
|
75
|
+
id: "crnn-mobilenet-v3-small",
|
|
76
|
+
name: "CRNN MobileNet V3 Small",
|
|
77
|
+
description: "CRNN MobileNetV3-Small \u2014 lightweight text recognition, CoreML compatible via OnnxTR",
|
|
78
|
+
inputSize: { width: 128, height: 32 },
|
|
79
|
+
labels: PLATE_TEXT_LABELS,
|
|
80
|
+
formats: {
|
|
81
|
+
onnx: {
|
|
82
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-small.onnx"),
|
|
83
|
+
sizeMB: 8
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
extraFiles: [
|
|
87
|
+
{
|
|
88
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
|
|
89
|
+
filename: "camstack-crnn-mobilenet-charset.txt",
|
|
90
|
+
sizeMB: 0.01
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "crnn-mobilenet-v3-large",
|
|
96
|
+
name: "CRNN MobileNet V3 Large",
|
|
97
|
+
description: "CRNN MobileNetV3-Large \u2014 higher accuracy text recognition, CoreML compatible",
|
|
98
|
+
inputSize: { width: 128, height: 32 },
|
|
99
|
+
labels: PLATE_TEXT_LABELS,
|
|
100
|
+
formats: {
|
|
101
|
+
onnx: {
|
|
102
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-large.onnx"),
|
|
103
|
+
sizeMB: 17
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
extraFiles: [
|
|
107
|
+
{
|
|
108
|
+
url: hfModelUrl(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
|
|
109
|
+
filename: "camstack-crnn-mobilenet-charset.txt",
|
|
110
|
+
sizeMB: 0.01
|
|
111
|
+
}
|
|
112
|
+
]
|
|
49
113
|
}
|
|
50
114
|
];
|
|
51
115
|
|
|
@@ -90,7 +154,8 @@ function loadCharset(modelsDir, modelId) {
|
|
|
90
154
|
const dictNames = [
|
|
91
155
|
`camstack-${modelId}-dict.txt`,
|
|
92
156
|
`camstack-paddleocr-latin-dict.txt`,
|
|
93
|
-
`camstack-paddleocr-en-dict.txt
|
|
157
|
+
`camstack-paddleocr-en-dict.txt`,
|
|
158
|
+
`camstack-crnn-mobilenet-charset.txt`
|
|
94
159
|
];
|
|
95
160
|
for (const name of dictNames) {
|
|
96
161
|
const dictPath = path.join(modelsDir, name);
|
|
@@ -101,7 +166,6 @@ function loadCharset(modelsDir, modelId) {
|
|
|
101
166
|
}
|
|
102
167
|
throw new Error(`PlateRecognitionAddon: dict.txt not found in ${modelsDir}`);
|
|
103
168
|
}
|
|
104
|
-
var CHARSET = [];
|
|
105
169
|
var REQUIRED_STEPS = [
|
|
106
170
|
{ slot: "cropper", outputClasses: ["plate"], description: "Requires a plate detector" }
|
|
107
171
|
];
|
|
@@ -117,8 +181,8 @@ var PlateRecognitionAddon = class {
|
|
|
117
181
|
name: "License Plate Recognition (OCR)",
|
|
118
182
|
version: "0.1.0",
|
|
119
183
|
description: "PaddleOCR-based license plate text recognition",
|
|
120
|
-
packageName: "@camstack/addon-vision",
|
|
121
184
|
slot: "classifier",
|
|
185
|
+
labelOutputType: "plate",
|
|
122
186
|
inputClasses: ["plate"],
|
|
123
187
|
outputClasses: ["plate-text:*"],
|
|
124
188
|
requiredSteps: REQUIRED_STEPS,
|
|
@@ -126,62 +190,94 @@ var PlateRecognitionAddon = class {
|
|
|
126
190
|
mayRequirePython: false,
|
|
127
191
|
defaultConfig: {
|
|
128
192
|
modelId: "paddleocr-latin",
|
|
129
|
-
runtime: "
|
|
193
|
+
runtime: "node",
|
|
130
194
|
backend: "cpu",
|
|
131
195
|
minConfidence: 0.5
|
|
132
196
|
}
|
|
133
197
|
};
|
|
134
|
-
engine;
|
|
198
|
+
engine = null;
|
|
135
199
|
modelEntry;
|
|
136
200
|
minConfidence = 0.5;
|
|
201
|
+
charset = [];
|
|
202
|
+
resolvedConfig = null;
|
|
203
|
+
ctx = null;
|
|
204
|
+
getModelRequirements() {
|
|
205
|
+
const scores = {
|
|
206
|
+
"paddleocr-latin": { ram: 100, accuracy: 80 },
|
|
207
|
+
"paddleocr-en": { ram: 100, accuracy: 80 }
|
|
208
|
+
};
|
|
209
|
+
return PLATE_RECOGNITION_MODELS.map((m) => ({
|
|
210
|
+
modelId: m.id,
|
|
211
|
+
name: m.name,
|
|
212
|
+
minRAM_MB: scores[m.id]?.ram ?? 100,
|
|
213
|
+
accuracyScore: scores[m.id]?.accuracy ?? 75,
|
|
214
|
+
formats: Object.keys(m.formats)
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
configure(config) {
|
|
218
|
+
this.resolvedConfig = config;
|
|
219
|
+
}
|
|
137
220
|
async initialize(ctx) {
|
|
221
|
+
this.ctx = ctx;
|
|
138
222
|
const cfg = ctx.addonConfig;
|
|
139
|
-
const modelId = cfg["modelId"] ?? "paddleocr-latin";
|
|
140
|
-
const runtime = cfg["runtime"] ?? "auto";
|
|
141
|
-
const backend = cfg["backend"] ?? "cpu";
|
|
223
|
+
const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "paddleocr-latin";
|
|
142
224
|
this.minConfidence = cfg["minConfidence"] ?? 0.5;
|
|
143
225
|
const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
|
|
144
226
|
if (!entry) {
|
|
145
227
|
throw new Error(`PlateRecognitionAddon: unknown modelId "${modelId}"`);
|
|
146
228
|
}
|
|
147
229
|
this.modelEntry = entry;
|
|
148
|
-
CHARSET = loadCharset(ctx.locationPaths.models, modelId);
|
|
149
|
-
const resolved = await resolveEngine({
|
|
150
|
-
runtime,
|
|
151
|
-
backend,
|
|
152
|
-
modelEntry: entry,
|
|
153
|
-
modelsDir: ctx.locationPaths.models
|
|
154
|
-
});
|
|
155
|
-
this.engine = resolved.engine;
|
|
156
230
|
}
|
|
157
231
|
async classify(input) {
|
|
232
|
+
if (!this.engine) await this.ensureEngine();
|
|
158
233
|
const start = Date.now();
|
|
159
234
|
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
235
|
+
console.log(`[plate-recognition] ROI: x=${input.roi?.x}, y=${input.roi?.y}, w=${input.roi?.w}, h=${input.roi?.h}, frameSize=${input.frame?.data?.length}`);
|
|
160
236
|
const plateCrop = await cropRegion(input.frame.data, input.roi);
|
|
237
|
+
console.log(`[plate-recognition] Crop size: ${plateCrop.length} bytes`);
|
|
238
|
+
try {
|
|
239
|
+
__require("fs").writeFileSync("/tmp/plate-recognition-crop.jpg", plateCrop);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
161
242
|
const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, "zero-one", "nchw");
|
|
162
243
|
const output = await this.engine.run(normalized, [1, 3, inputH, inputW]);
|
|
163
|
-
const numChars =
|
|
244
|
+
const numChars = this.charset.length;
|
|
164
245
|
const seqLen = output.length / numChars;
|
|
165
|
-
const { text, confidence } = ctcDecode(output, seqLen, numChars,
|
|
166
|
-
if (confidence < this.minConfidence || text.trim().length === 0) {
|
|
167
|
-
return {
|
|
168
|
-
classifications: [],
|
|
169
|
-
inferenceMs: Date.now() - start,
|
|
170
|
-
modelId: this.modelEntry.id
|
|
171
|
-
};
|
|
172
|
-
}
|
|
246
|
+
const { text, confidence } = ctcDecode(output, seqLen, numChars, this.charset);
|
|
173
247
|
return {
|
|
174
248
|
classifications: [
|
|
175
249
|
{
|
|
176
250
|
class: "plate-text",
|
|
177
251
|
score: confidence,
|
|
178
|
-
text
|
|
252
|
+
text: text.trim() || "(unreadable)"
|
|
179
253
|
}
|
|
180
254
|
],
|
|
181
255
|
inferenceMs: Date.now() - start,
|
|
182
256
|
modelId: this.modelEntry.id
|
|
183
257
|
};
|
|
184
258
|
}
|
|
259
|
+
async ensureEngine() {
|
|
260
|
+
const config = this.resolvedConfig;
|
|
261
|
+
const modelId = config?.modelId ?? this.modelEntry.id;
|
|
262
|
+
const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
|
|
263
|
+
const backend = config?.backend ?? "cpu";
|
|
264
|
+
const format = config?.format ?? "onnx";
|
|
265
|
+
const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
|
|
266
|
+
this.modelEntry = entry;
|
|
267
|
+
const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
|
|
268
|
+
if (this.ctx.models) {
|
|
269
|
+
await this.ctx.models.ensure(modelId, format);
|
|
270
|
+
}
|
|
271
|
+
this.charset = loadCharset(modelsDir, modelId);
|
|
272
|
+
const resolved = await resolveEngine({
|
|
273
|
+
runtime,
|
|
274
|
+
backend,
|
|
275
|
+
modelEntry: entry,
|
|
276
|
+
modelsDir,
|
|
277
|
+
models: this.ctx.models
|
|
278
|
+
});
|
|
279
|
+
this.engine = resolved.engine;
|
|
280
|
+
}
|
|
185
281
|
async shutdown() {
|
|
186
282
|
await this.engine?.dispose();
|
|
187
283
|
}
|
|
@@ -206,6 +302,34 @@ var PlateRecognitionAddon = class {
|
|
|
206
302
|
}
|
|
207
303
|
]
|
|
208
304
|
},
|
|
305
|
+
{
|
|
306
|
+
id: "runtime",
|
|
307
|
+
title: "Runtime",
|
|
308
|
+
columns: 2,
|
|
309
|
+
fields: [
|
|
310
|
+
{
|
|
311
|
+
key: "runtime",
|
|
312
|
+
label: "Runtime",
|
|
313
|
+
type: "select",
|
|
314
|
+
options: [
|
|
315
|
+
{ value: "auto", label: "Auto" },
|
|
316
|
+
{ value: "onnx", label: "ONNX Runtime" },
|
|
317
|
+
{ value: "openvino", label: "OpenVINO (Intel)" }
|
|
318
|
+
]
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
key: "backend",
|
|
322
|
+
label: "Backend",
|
|
323
|
+
type: "select",
|
|
324
|
+
showWhen: { field: "runtime", equals: "onnx" },
|
|
325
|
+
options: [
|
|
326
|
+
{ value: "auto", label: "Auto" },
|
|
327
|
+
{ value: "cpu", label: "CPU" },
|
|
328
|
+
{ value: "cuda", label: "CUDA (NVIDIA)" }
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
},
|
|
209
333
|
{
|
|
210
334
|
id: "thresholds",
|
|
211
335
|
title: "Recognition Settings",
|
|
@@ -252,4 +376,4 @@ export {
|
|
|
252
376
|
PLATE_RECOGNITION_MODELS,
|
|
253
377
|
PlateRecognitionAddon
|
|
254
378
|
};
|
|
255
|
-
//# sourceMappingURL=chunk-
|
|
379
|
+
//# sourceMappingURL=chunk-ZTJENCFC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalogs/plate-recognition-models.ts","../src/shared/postprocess/paddleocr.ts","../src/addons/plate-recognition/index.ts"],"sourcesContent":["import type { ModelCatalogEntry, LabelDefinition } from '@camstack/types'\nimport { hfModelUrl } from '@camstack/types'\n\nconst HF_REPO = 'camstack/camstack-models'\n\nconst PLATE_TEXT_LABELS: readonly LabelDefinition[] = [\n { id: 'text', name: 'Plate Text' },\n] as const\n\nexport const PLATE_RECOGNITION_MODELS: readonly ModelCatalogEntry[] = [\n // ── PaddleOCR PP-OCRv5 ────────────────────────────────────────\n {\n id: 'paddleocr-latin',\n name: 'PaddleOCR Latin',\n description: 'PaddleOCR PP-OCRv5 recognition model for Latin-script license plates',\n inputSize: { width: 320, height: 48 },\n labels: PLATE_TEXT_LABELS,\n formats: {\n // ONNX only — PaddleOCR has dynamic dimensions incompatible with CoreML native conversion.\n // On Apple Silicon, ONNX Runtime uses CoreML EP automatically for acceleration.\n onnx: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-rec.onnx'),\n sizeMB: 7.5,\n },\n openvino: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/openvino/camstack-paddleocr-latin.xml'),\n sizeMB: 4,\n runtimes: ['python'],\n },\n },\n extraFiles: [\n {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-dict.txt'),\n filename: 'camstack-paddleocr-latin-dict.txt',\n sizeMB: 0.01,\n },\n ],\n },\n {\n id: 'paddleocr-en',\n name: 'PaddleOCR English',\n description: 'PaddleOCR PP-OCRv5 recognition model optimized for English license plates',\n inputSize: { width: 320, height: 48 },\n labels: PLATE_TEXT_LABELS,\n formats: {\n onnx: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/onnx/camstack-paddleocr-en-rec.onnx'),\n sizeMB: 7.5,\n },\n openvino: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/openvino/camstack-paddleocr-en.xml'),\n sizeMB: 4,\n runtimes: ['python'],\n },\n },\n extraFiles: [\n {\n url: hfModelUrl(HF_REPO, 'plateRecognition/paddleocr/onnx/camstack-paddleocr-en-dict.txt'),\n filename: 'camstack-paddleocr-en-dict.txt',\n sizeMB: 0.01,\n },\n ],\n },\n\n // ── CRNN-MobileNetV3 (via OnnxTR/docTR) ─────────────────────────\n // Simple CNN+LSTM+CTC architecture — good CoreML compatibility (no dynamic ops)\n {\n id: 'crnn-mobilenet-v3-small',\n name: 'CRNN MobileNet V3 Small',\n description: 'CRNN MobileNetV3-Small — lightweight text recognition, CoreML compatible via OnnxTR',\n inputSize: { width: 128, height: 32 },\n labels: PLATE_TEXT_LABELS,\n formats: {\n onnx: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-small.onnx'),\n sizeMB: 8,\n },\n },\n extraFiles: [\n {\n url: hfModelUrl(HF_REPO, 'plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt'),\n filename: 'camstack-crnn-mobilenet-charset.txt',\n sizeMB: 0.01,\n },\n ],\n },\n {\n id: 'crnn-mobilenet-v3-large',\n name: 'CRNN MobileNet V3 Large',\n description: 'CRNN MobileNetV3-Large — higher accuracy text recognition, CoreML compatible',\n inputSize: { width: 128, height: 32 },\n labels: PLATE_TEXT_LABELS,\n formats: {\n onnx: {\n url: hfModelUrl(HF_REPO, 'plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-large.onnx'),\n sizeMB: 17,\n },\n },\n extraFiles: [\n {\n url: hfModelUrl(HF_REPO, 'plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt'),\n filename: 'camstack-crnn-mobilenet-charset.txt',\n sizeMB: 0.01,\n },\n ],\n },\n] as const\n","/** Decode CTC output to text.\n *\n * Output shape: [1, seqLen, numChars]\n * Algorithm: argmax per timestep → collapse consecutive duplicates → remove blank (index 0) → join\n */\nexport function ctcDecode(\n output: Float32Array,\n seqLen: number,\n numChars: number,\n charset: readonly string[], // index 0 = blank token\n): { text: string; confidence: number } {\n // Step 1: argmax per timestep + track confidence as mean of selected scores\n let totalLogScore = 0\n const rawIndices: number[] = []\n\n for (let t = 0; t < seqLen; t++) {\n const offset = t * numChars\n let bestIdx = 0\n let bestVal = output[offset]!\n\n for (let c = 1; c < numChars; c++) {\n const val = output[offset + c]!\n if (val > bestVal) {\n bestVal = val\n bestIdx = c\n }\n }\n\n rawIndices.push(bestIdx)\n totalLogScore += bestVal\n }\n\n // Step 2: collapse consecutive duplicates\n const collapsed: number[] = []\n for (let i = 0; i < rawIndices.length; i++) {\n const cur = rawIndices[i]!\n if (i === 0 || cur !== rawIndices[i - 1]) {\n collapsed.push(cur)\n }\n }\n\n // Step 3: remove blank (index 0)\n const filtered = collapsed.filter((idx) => idx !== 0)\n\n // Step 4: join characters\n const text = filtered.map((idx) => charset[idx] ?? '').join('')\n\n const confidence = seqLen > 0 ? totalLogScore / seqLen : 0\n\n return { text, confidence }\n}\n","import type {\n 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 ModelRequirement,\n ResolvedInferenceConfig,\n} from '@camstack/types'\nimport { PLATE_RECOGNITION_MODELS } from '../../catalogs/plate-recognition-models.js'\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 `camstack-crnn-mobilenet-charset.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\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\n description: 'PaddleOCR-based license plate text recognition',\n\n slot: 'classifier',\n labelOutputType: 'plate',\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: 'node',\n backend: 'cpu',\n minConfidence: 0.5,\n },\n }\n\n private engine: IInferenceEngine | null = null\n private modelEntry!: ModelCatalogEntry\n private minConfidence = 0.5\n private charset: readonly string[] = []\n private resolvedConfig: ResolvedInferenceConfig | null = null\n private ctx: AddonContext | null = null\n\n getModelRequirements(): ModelRequirement[] {\n const scores: Record<string, { ram: number; accuracy: number }> = {\n 'paddleocr-latin': { ram: 100, accuracy: 80 },\n 'paddleocr-en': { ram: 100, accuracy: 80 },\n }\n return PLATE_RECOGNITION_MODELS.map((m) => ({\n modelId: m.id,\n name: m.name,\n minRAM_MB: scores[m.id]?.ram ?? 100,\n accuracyScore: scores[m.id]?.accuracy ?? 75,\n formats: Object.keys(m.formats) as readonly string[],\n }))\n }\n\n configure(config: ResolvedInferenceConfig): void {\n this.resolvedConfig = config\n }\n\n async initialize(ctx: AddonContext): Promise<void> {\n this.ctx = ctx\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? this.resolvedConfig?.modelId ?? 'paddleocr-latin'\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\n async classify(input: CropInput): Promise<ClassifierOutput> {\n if (!this.engine) await this.ensureEngine()\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n\n // Crop the plate region\n console.log(`[plate-recognition] ROI: x=${input.roi?.x}, y=${input.roi?.y}, w=${input.roi?.w}, h=${input.roi?.h}, frameSize=${input.frame?.data?.length}`)\n const plateCrop = await cropRegion(input.frame.data, input.roi)\n console.log(`[plate-recognition] Crop size: ${plateCrop.length} bytes`)\n // DEBUG: save crop to /tmp for inspection\n try { require('fs').writeFileSync('/tmp/plate-recognition-crop.jpg', plateCrop) } catch {}\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 = this.charset.length\n const seqLen = output.length / numChars\n const { text, confidence } = ctcDecode(output, seqLen, numChars, this.charset)\n\n // Always return the result with confidence — let the pipeline/UI decide on thresholds\n return {\n classifications: [\n {\n class: 'plate-text',\n score: confidence,\n text: text.trim() || '(unreadable)',\n },\n ],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n private async ensureEngine(): Promise<void> {\n const config = this.resolvedConfig\n const modelId = config?.modelId ?? this.modelEntry.id\n const runtime = config?.runtime === 'python' ? 'coreml' : (config?.runtime === 'node' ? 'onnx' : 'auto')\n const backend = config?.backend ?? 'cpu'\n const format = config?.format ?? 'onnx'\n\n const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry\n this.modelEntry = entry\n\n const modelsDir = this.ctx!.models?.getModelsDir() ?? this.ctx!.locationPaths.models\n\n // Ensure model + extra files (dict.txt) are downloaded via unified service\n if (this.ctx!.models) {\n await this.ctx!.models.ensure(modelId, format as any)\n }\n\n // Load charset from dict.txt (lazy — only on first use)\n this.charset = loadCharset(modelsDir, modelId)\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir,\n models: this.ctx!.models,\n })\n this.engine = resolved.engine\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: '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' },\n { value: 'onnx', label: 'ONNX Runtime' },\n { value: 'openvino', label: 'OpenVINO (Intel)' },\n ],\n },\n {\n key: 'backend',\n label: 'Backend',\n type: 'select',\n showWhen: { field: 'runtime', equals: 'onnx' },\n options: [\n { value: 'auto', label: 'Auto' },\n { value: 'cpu', label: 'CPU' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\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"],"mappings":"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","names":["PLATE_TEXT_LABELS"]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BIRD_SPECIES_MODELS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DUN6XU3N.mjs";
|
|
4
4
|
import {
|
|
5
5
|
cropRegion,
|
|
6
6
|
resizeAndNormalize
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-22BHCDT5.mjs";
|
|
8
8
|
import {
|
|
9
9
|
resolveEngine
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2IOKI4ES.mjs";
|
|
11
11
|
|
|
12
12
|
// src/addons/bird-global-classifier/index.ts
|
|
13
13
|
import * as fs from "fs";
|
|
@@ -47,44 +47,50 @@ var BirdGlobalClassifierAddon = class {
|
|
|
47
47
|
name: "Bird Classifier (Global, 525 species)",
|
|
48
48
|
version: "0.1.0",
|
|
49
49
|
description: "EfficientNet \u2014 525 worldwide bird species (MIT license, ONNX only)",
|
|
50
|
-
packageName: "@camstack/addon-vision",
|
|
51
50
|
slot: "classifier",
|
|
51
|
+
labelOutputType: "classification",
|
|
52
52
|
inputClasses: ["animal"],
|
|
53
53
|
outputClasses: ["species:*"],
|
|
54
54
|
supportsCustomModels: false,
|
|
55
55
|
mayRequirePython: false,
|
|
56
56
|
defaultConfig: {
|
|
57
57
|
modelId: "bird-species-525",
|
|
58
|
-
runtime: "
|
|
58
|
+
runtime: "node",
|
|
59
59
|
backend: "cpu",
|
|
60
60
|
minConfidence: 0.3
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
-
engine;
|
|
63
|
+
engine = null;
|
|
64
64
|
modelEntry;
|
|
65
65
|
labels = [];
|
|
66
66
|
minConfidence = 0.3;
|
|
67
|
+
resolvedConfig = null;
|
|
68
|
+
ctx = null;
|
|
69
|
+
getModelRequirements() {
|
|
70
|
+
return BIRD_SPECIES_MODELS.map((m) => ({
|
|
71
|
+
modelId: m.id,
|
|
72
|
+
name: m.name,
|
|
73
|
+
minRAM_MB: 120,
|
|
74
|
+
accuracyScore: 80,
|
|
75
|
+
formats: Object.keys(m.formats)
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
configure(config) {
|
|
79
|
+
this.resolvedConfig = config;
|
|
80
|
+
}
|
|
67
81
|
async initialize(ctx) {
|
|
82
|
+
this.ctx = ctx;
|
|
68
83
|
const cfg = ctx.addonConfig;
|
|
69
|
-
const modelId = cfg["modelId"] ?? "bird-species-525";
|
|
70
|
-
const runtime = cfg["runtime"] ?? "auto";
|
|
71
|
-
const backend = cfg["backend"] ?? "cpu";
|
|
84
|
+
const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "bird-species-525";
|
|
72
85
|
this.minConfidence = cfg["minConfidence"] ?? 0.3;
|
|
73
86
|
const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId);
|
|
74
87
|
if (!entry) {
|
|
75
88
|
throw new Error(`BirdGlobalClassifierAddon: unknown modelId "${modelId}"`);
|
|
76
89
|
}
|
|
77
90
|
this.modelEntry = entry;
|
|
78
|
-
this.labels = loadLabels(ctx.locationPaths.models, modelId);
|
|
79
|
-
const resolved = await resolveEngine({
|
|
80
|
-
runtime,
|
|
81
|
-
backend,
|
|
82
|
-
modelEntry: entry,
|
|
83
|
-
modelsDir: ctx.locationPaths.models
|
|
84
|
-
});
|
|
85
|
-
this.engine = resolved.engine;
|
|
86
91
|
}
|
|
87
92
|
async classify(input) {
|
|
93
|
+
if (!this.engine) await this.ensureEngine();
|
|
88
94
|
const start = Date.now();
|
|
89
95
|
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
90
96
|
const animalCrop = await cropRegion(input.frame.data, input.roi);
|
|
@@ -119,6 +125,28 @@ var BirdGlobalClassifierAddon = class {
|
|
|
119
125
|
modelId: this.modelEntry.id
|
|
120
126
|
};
|
|
121
127
|
}
|
|
128
|
+
async ensureEngine() {
|
|
129
|
+
const config = this.resolvedConfig;
|
|
130
|
+
const modelId = config?.modelId ?? this.modelEntry.id;
|
|
131
|
+
const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
|
|
132
|
+
const backend = config?.backend ?? "cpu";
|
|
133
|
+
const format = config?.format ?? "onnx";
|
|
134
|
+
const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
|
|
135
|
+
this.modelEntry = entry;
|
|
136
|
+
const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
|
|
137
|
+
if (this.ctx.models) {
|
|
138
|
+
await this.ctx.models.ensure(modelId, format);
|
|
139
|
+
}
|
|
140
|
+
this.labels = loadLabels(modelsDir, modelId);
|
|
141
|
+
const resolved = await resolveEngine({
|
|
142
|
+
runtime,
|
|
143
|
+
backend,
|
|
144
|
+
modelEntry: entry,
|
|
145
|
+
modelsDir,
|
|
146
|
+
models: this.ctx.models
|
|
147
|
+
});
|
|
148
|
+
this.engine = resolved.engine;
|
|
149
|
+
}
|
|
122
150
|
async shutdown() {
|
|
123
151
|
await this.engine?.dispose();
|
|
124
152
|
}
|
|
@@ -143,22 +171,6 @@ var BirdGlobalClassifierAddon = class {
|
|
|
143
171
|
}
|
|
144
172
|
]
|
|
145
173
|
},
|
|
146
|
-
{
|
|
147
|
-
id: "thresholds",
|
|
148
|
-
title: "Classification Settings",
|
|
149
|
-
columns: 1,
|
|
150
|
-
fields: [
|
|
151
|
-
{
|
|
152
|
-
key: "minConfidence",
|
|
153
|
-
label: "Minimum Confidence",
|
|
154
|
-
type: "slider",
|
|
155
|
-
min: 0.05,
|
|
156
|
-
max: 1,
|
|
157
|
-
step: 0.05,
|
|
158
|
-
default: 0.3
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
},
|
|
162
174
|
{
|
|
163
175
|
id: "runtime",
|
|
164
176
|
title: "Runtime",
|
|
@@ -169,23 +181,41 @@ var BirdGlobalClassifierAddon = class {
|
|
|
169
181
|
label: "Runtime",
|
|
170
182
|
type: "select",
|
|
171
183
|
options: [
|
|
172
|
-
{ value: "auto", label: "Auto
|
|
184
|
+
{ value: "auto", label: "Auto" },
|
|
173
185
|
{ value: "onnx", label: "ONNX Runtime" },
|
|
174
|
-
{ value: "coreml", label: "CoreML (Apple)" }
|
|
186
|
+
{ value: "coreml", label: "CoreML (Apple)" },
|
|
187
|
+
{ value: "openvino", label: "OpenVINO (Intel)" }
|
|
175
188
|
]
|
|
176
189
|
},
|
|
177
190
|
{
|
|
178
191
|
key: "backend",
|
|
179
192
|
label: "Backend",
|
|
180
193
|
type: "select",
|
|
181
|
-
|
|
194
|
+
showWhen: { field: "runtime", equals: "onnx" },
|
|
182
195
|
options: [
|
|
196
|
+
{ value: "auto", label: "Auto" },
|
|
183
197
|
{ value: "cpu", label: "CPU" },
|
|
184
198
|
{ value: "coreml", label: "CoreML" },
|
|
185
199
|
{ value: "cuda", label: "CUDA (NVIDIA)" }
|
|
186
200
|
]
|
|
187
201
|
}
|
|
188
202
|
]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "thresholds",
|
|
206
|
+
title: "Classification Settings",
|
|
207
|
+
columns: 1,
|
|
208
|
+
fields: [
|
|
209
|
+
{
|
|
210
|
+
key: "minConfidence",
|
|
211
|
+
label: "Minimum Confidence",
|
|
212
|
+
type: "slider",
|
|
213
|
+
min: 0.05,
|
|
214
|
+
max: 1,
|
|
215
|
+
step: 0.05,
|
|
216
|
+
default: 0.3
|
|
217
|
+
}
|
|
218
|
+
]
|
|
189
219
|
}
|
|
190
220
|
]
|
|
191
221
|
};
|
|
@@ -215,4 +245,4 @@ var BirdGlobalClassifierAddon = class {
|
|
|
215
245
|
export {
|
|
216
246
|
BirdGlobalClassifierAddon
|
|
217
247
|
};
|
|
218
|
-
//# sourceMappingURL=chunk-
|
|
248
|
+
//# sourceMappingURL=chunk-ZWYXXCXP.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 ModelRequirement,\n ResolvedInferenceConfig,\n} from '@camstack/types'\nimport { BIRD_SPECIES_MODELS } from '../../catalogs/animal-classification-models.js'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\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\n description: 'EfficientNet — 525 worldwide bird species (MIT license, ONNX only)',\n\n slot: 'classifier',\n labelOutputType: 'classification',\n inputClasses: ['animal'],\n outputClasses: ['species:*'],\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'bird-species-525',\n runtime: 'node',\n backend: 'cpu',\n minConfidence: 0.3,\n },\n }\n\n private engine: IInferenceEngine | null = null\n private modelEntry!: ModelCatalogEntry\n private labels: readonly string[] = []\n private minConfidence = 0.3\n private resolvedConfig: ResolvedInferenceConfig | null = null\n private ctx: AddonContext | null = null\n\n getModelRequirements(): ModelRequirement[] {\n return BIRD_SPECIES_MODELS.map((m) => ({\n modelId: m.id,\n name: m.name,\n minRAM_MB: 120,\n accuracyScore: 80,\n formats: Object.keys(m.formats) as readonly string[],\n }))\n }\n\n configure(config: ResolvedInferenceConfig): void {\n this.resolvedConfig = config\n }\n\n async initialize(ctx: AddonContext): Promise<void> {\n this.ctx = ctx\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? this.resolvedConfig?.modelId ?? 'bird-species-525'\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\n async classify(input: CropInput): Promise<ClassifierOutput> {\n if (!this.engine) await this.ensureEngine()\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 private async ensureEngine(): Promise<void> {\n const config = this.resolvedConfig\n const modelId = config?.modelId ?? this.modelEntry.id\n const runtime = config?.runtime === 'python' ? 'coreml' : (config?.runtime === 'node' ? 'onnx' : 'auto')\n const backend = config?.backend ?? 'cpu'\n const format = config?.format ?? 'onnx'\n\n const entry = BIRD_SPECIES_MODELS.find((m) => m.id === modelId) ?? this.modelEntry\n this.modelEntry = entry\n\n const modelsDir = this.ctx!.models?.getModelsDir() ?? this.ctx!.locationPaths.models\n\n // Ensure model + extra files (labels JSON) are downloaded via unified service\n if (this.ctx!.models) {\n await this.ctx!.models.ensure(modelId, format as any)\n }\n\n // Load labels from JSON file (lazy — only on first use)\n this.labels = loadLabels(modelsDir, modelId)\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir,\n models: this.ctx!.models,\n })\n this.engine = resolved.engine\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: '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' },\n { value: 'onnx', label: 'ONNX Runtime' },\n { value: 'coreml', label: 'CoreML (Apple)' },\n { value: 'openvino', label: 'OpenVINO (Intel)' },\n ],\n },\n {\n key: 'backend',\n label: 'Backend',\n type: 'select',\n showWhen: { field: 'runtime', equals: 'onnx' },\n options: [\n { value: 'auto', label: 'Auto' },\n { value: 'cpu', label: 'CPU' },\n { value: 'coreml', label: 'CoreML' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n ],\n },\n {\n id: 'thresholds',\n title: 'Classification Settings',\n columns: 1,\n fields: [\n {\n key: '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 }\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":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { BoundingBox, SpatialDetection, IInferenceEngine, DetectionRuntime, DetectionDevice, ModelCatalogEntry, ModelFormat } from '@camstack/types';
|
|
1
|
+
import { BoundingBox, SpatialDetection, IInferenceEngine, DetectionRuntime, DetectionDevice, ModelCatalogEntry, IAddonModelManager, ModelFormat } from '@camstack/types';
|
|
2
2
|
export { default as ObjectDetectionAddon } from './addons/object-detection/index.mjs';
|
|
3
|
-
export { default as MotionDetectionAddon } from './addons/motion-detection/index.mjs';
|
|
4
3
|
export { default as FaceDetectionAddon } from './addons/face-detection/index.mjs';
|
|
5
4
|
export { default as FaceRecognitionAddon } from './addons/face-recognition/index.mjs';
|
|
6
5
|
export { default as PlateDetectionAddon } from './addons/plate-detection/index.mjs';
|
|
7
6
|
export { default as PlateRecognitionAddon } from './addons/plate-recognition/index.mjs';
|
|
8
7
|
export { default as AudioClassificationAddon } from './addons/audio-classification/index.mjs';
|
|
9
|
-
export { default as CameraNativeDetectionAddon } from './addons/camera-native-detection/index.mjs';
|
|
10
8
|
export { default as BirdGlobalClassifierAddon } from './addons/bird-global-classifier/index.mjs';
|
|
11
9
|
export { default as BirdNABirdsClassifierAddon } from './addons/bird-nabirds-classifier/index.mjs';
|
|
12
10
|
export { default as AnimalClassifierAddon } from './addons/animal-classifier/index.mjs';
|
|
@@ -122,7 +120,8 @@ interface EngineResolverOptions {
|
|
|
122
120
|
readonly modelEntry: ModelCatalogEntry;
|
|
123
121
|
readonly modelsDir: string;
|
|
124
122
|
readonly pythonPath?: string;
|
|
125
|
-
|
|
123
|
+
/** Model service for downloading models. When provided, used instead of raw filesystem probing. */
|
|
124
|
+
readonly models?: IAddonModelManager;
|
|
126
125
|
}
|
|
127
126
|
interface ResolvedEngine {
|
|
128
127
|
readonly engine: IInferenceEngine;
|
|
@@ -150,6 +149,8 @@ interface MotionRegion {
|
|
|
150
149
|
*/
|
|
151
150
|
declare function detectMotion(current: Uint8Array, previous: Uint8Array, width: number, height: number, threshold: number, minArea: number): MotionRegion[];
|
|
152
151
|
|
|
152
|
+
/** Standard files inside every .mlpackage directory bundle */
|
|
153
|
+
declare const MLPACKAGE_FILES: readonly ["Manifest.json", "Data/com.apple.CoreML/model.mlmodel", "Data/com.apple.CoreML/weights/weight.bin"];
|
|
153
154
|
declare const OBJECT_DETECTION_MODELS: readonly ModelCatalogEntry[];
|
|
154
155
|
|
|
155
156
|
declare const FACE_DETECTION_MODELS: readonly ModelCatalogEntry[];
|
|
@@ -160,6 +161,13 @@ declare const PLATE_DETECTION_MODELS: readonly ModelCatalogEntry[];
|
|
|
160
161
|
|
|
161
162
|
declare const PLATE_RECOGNITION_MODELS: readonly ModelCatalogEntry[];
|
|
162
163
|
|
|
164
|
+
/**
|
|
165
|
+
* General-purpose OCR models for scene text recognition in camera feeds.
|
|
166
|
+
* These complement the plate-specific PaddleOCR models for broader text detection
|
|
167
|
+
* (signs, labels, addresses, etc.).
|
|
168
|
+
*/
|
|
169
|
+
declare const GENERAL_OCR_MODELS: readonly ModelCatalogEntry[];
|
|
170
|
+
|
|
163
171
|
declare const AUDIO_CLASSIFICATION_MODELS: readonly ModelCatalogEntry[];
|
|
164
172
|
|
|
165
173
|
declare const SEGMENTATION_MODELS: readonly ModelCatalogEntry[];
|
|
@@ -168,4 +176,8 @@ declare const BIRD_SPECIES_MODELS: readonly ModelCatalogEntry[];
|
|
|
168
176
|
declare const BIRD_NABIRDS_MODELS: readonly ModelCatalogEntry[];
|
|
169
177
|
declare const ANIMAL_TYPE_MODELS: readonly ModelCatalogEntry[];
|
|
170
178
|
|
|
171
|
-
|
|
179
|
+
declare const VEHICLE_TYPE_MODELS: readonly ModelCatalogEntry[];
|
|
180
|
+
|
|
181
|
+
declare const SEGMENTATION_REFINER_MODELS: readonly ModelCatalogEntry[];
|
|
182
|
+
|
|
183
|
+
export { ANIMAL_TYPE_MODELS, AUDIO_CLASSIFICATION_MODELS, BIRD_NABIRDS_MODELS, BIRD_SPECIES_MODELS, type EngineResolverOptions, FACE_DETECTION_MODELS, FACE_RECOGNITION_MODELS, GENERAL_OCR_MODELS, MLPACKAGE_FILES, type MotionRegion, NodeInferenceEngine, OBJECT_DETECTION_MODELS, PLATE_DETECTION_MODELS, PLATE_RECOGNITION_MODELS, PythonInferenceEngine, type ResolvedEngine, SEGMENTATION_MODELS, SEGMENTATION_REFINER_MODELS, VEHICLE_TYPE_MODELS, cosineSimilarity, cropRegion, ctcDecode, detectMotion, iou, jpegToRgb, l2Normalize, letterbox, nms, probeOnnxBackends, resizeAndNormalize, resolveEngine, rgbToGrayscale, scrfdPostprocess, yamnetPostprocess, yoloPostprocess };
|