@camstack/addon-vision 0.1.6 → 0.1.9
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 +30 -0
- package/dist/addons/animal-classifier/index.d.ts +30 -0
- package/dist/addons/animal-classifier/index.js +822 -999
- package/dist/addons/animal-classifier/index.js.map +1 -1
- package/dist/addons/animal-classifier/index.mjs +7 -242
- package/dist/addons/animal-classifier/index.mjs.map +1 -1
- package/dist/addons/audio-classification/index.d.mts +36 -0
- package/dist/addons/audio-classification/index.d.ts +36 -0
- package/dist/addons/audio-classification/index.js +378 -501
- package/dist/addons/audio-classification/index.js.map +1 -1
- package/dist/addons/audio-classification/index.mjs +4 -224
- package/dist/addons/audio-classification/index.mjs.map +1 -1
- package/dist/addons/bird-global-classifier/index.d.mts +31 -0
- package/dist/addons/bird-global-classifier/index.d.ts +31 -0
- package/dist/addons/bird-global-classifier/index.js +825 -1002
- package/dist/addons/bird-global-classifier/index.js.map +1 -1
- package/dist/addons/bird-global-classifier/index.mjs +7 -248
- package/dist/addons/bird-global-classifier/index.mjs.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.d.mts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.d.ts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.js +825 -1002
- package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.mjs +7 -289
- package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -1
- package/dist/addons/face-detection/index.d.mts +29 -0
- package/dist/addons/face-detection/index.d.ts +29 -0
- package/dist/addons/face-detection/index.js +934 -1196
- package/dist/addons/face-detection/index.js.map +1 -1
- package/dist/addons/face-detection/index.mjs +7 -227
- package/dist/addons/face-detection/index.mjs.map +1 -1
- package/dist/addons/face-recognition/index.d.mts +29 -0
- package/dist/addons/face-recognition/index.d.ts +29 -0
- package/dist/addons/face-recognition/index.js +807 -1003
- package/dist/addons/face-recognition/index.js.map +1 -1
- package/dist/addons/face-recognition/index.mjs +6 -197
- package/dist/addons/face-recognition/index.mjs.map +1 -1
- package/dist/addons/motion-detection/index.d.mts +28 -0
- package/dist/addons/motion-detection/index.d.ts +28 -0
- package/dist/addons/motion-detection/index.js +111 -214
- package/dist/addons/motion-detection/index.js.map +1 -1
- package/dist/addons/motion-detection/index.mjs +9 -12
- package/dist/addons/motion-detection/index.mjs.map +1 -1
- package/dist/addons/object-detection/index.d.mts +31 -0
- package/dist/addons/object-detection/index.d.ts +31 -0
- package/dist/addons/object-detection/index.js +1082 -1287
- package/dist/addons/object-detection/index.js.map +1 -1
- package/dist/addons/object-detection/index.mjs +7 -373
- package/dist/addons/object-detection/index.mjs.map +1 -1
- package/dist/addons/plate-detection/index.d.mts +30 -0
- package/dist/addons/plate-detection/index.d.ts +30 -0
- package/dist/addons/plate-detection/index.js +868 -1075
- package/dist/addons/plate-detection/index.js.map +1 -1
- package/dist/addons/plate-detection/index.mjs +7 -230
- package/dist/addons/plate-detection/index.mjs.map +1 -1
- package/dist/addons/plate-recognition/index.d.mts +31 -0
- package/dist/addons/plate-recognition/index.d.ts +31 -0
- package/dist/addons/plate-recognition/index.js +505 -684
- package/dist/addons/plate-recognition/index.js.map +1 -1
- package/dist/addons/plate-recognition/index.mjs +5 -244
- package/dist/addons/plate-recognition/index.mjs.map +1 -1
- 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 +790 -967
- package/dist/addons/segmentation-refiner/index.js.map +1 -1
- package/dist/addons/segmentation-refiner/index.mjs +17 -21
- package/dist/addons/segmentation-refiner/index.mjs.map +1 -1
- 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 +410 -581
- package/dist/addons/vehicle-classifier/index.js.map +1 -1
- package/dist/addons/vehicle-classifier/index.mjs +16 -20
- package/dist/addons/vehicle-classifier/index.mjs.map +1 -1
- package/dist/chunk-22BHCDT5.mjs +101 -0
- package/dist/{chunk-WG66JYYW.mjs.map → chunk-22BHCDT5.mjs.map} +1 -1
- package/dist/chunk-2IOKI4ES.mjs +335 -0
- package/dist/{chunk-PIFS7AIT.mjs.map → chunk-2IOKI4ES.mjs.map} +1 -1
- package/dist/chunk-7DYHXUPZ.mjs +36 -0
- package/dist/{chunk-BS4DKYGN.mjs.map → chunk-7DYHXUPZ.mjs.map} +1 -1
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BP7H4NFS.mjs +412 -0
- package/dist/{chunk-MGT6RUVX.mjs.map → chunk-BP7H4NFS.mjs.map} +1 -1
- package/dist/chunk-BR2FPGOX.mjs +98 -0
- package/dist/{chunk-YYDM6V2F.mjs.map → chunk-BR2FPGOX.mjs.map} +1 -1
- package/dist/chunk-D6WEHN33.mjs +276 -0
- package/dist/chunk-D6WEHN33.mjs.map +1 -0
- package/dist/chunk-DRYFGARD.mjs +289 -0
- package/dist/chunk-DRYFGARD.mjs.map +1 -0
- package/dist/chunk-DUN6XU3N.mjs +72 -0
- package/dist/{chunk-XD7WGXHZ.mjs.map → chunk-DUN6XU3N.mjs.map} +1 -1
- package/dist/chunk-ESLHNWWE.mjs +387 -0
- package/dist/chunk-ESLHNWWE.mjs.map +1 -0
- package/dist/chunk-JUQEW6ON.mjs +256 -0
- package/dist/chunk-JUQEW6ON.mjs.map +1 -0
- package/dist/chunk-KUO2BVFY.mjs +90 -0
- package/dist/{chunk-DE7I3VHO.mjs.map → chunk-KUO2BVFY.mjs.map} +1 -1
- package/dist/chunk-R5J3WAUI.mjs +645 -0
- package/dist/chunk-R5J3WAUI.mjs.map +1 -0
- package/dist/chunk-XZ6ZMXXU.mjs +39 -0
- package/dist/{chunk-K36R6HWY.mjs.map → chunk-XZ6ZMXXU.mjs.map} +1 -1
- package/dist/chunk-YPU4WTXZ.mjs +269 -0
- package/dist/chunk-YPU4WTXZ.mjs.map +1 -0
- package/dist/chunk-YUCD2TFH.mjs +242 -0
- package/dist/chunk-YUCD2TFH.mjs.map +1 -0
- package/dist/chunk-ZTJENCFC.mjs +379 -0
- package/dist/chunk-ZTJENCFC.mjs.map +1 -0
- package/dist/chunk-ZWYXXCXP.mjs +248 -0
- package/dist/chunk-ZWYXXCXP.mjs.map +1 -0
- package/dist/index.d.mts +183 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +3930 -4449
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -2698
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-2YMA6QOV.mjs +0 -193
- package/dist/chunk-2YMA6QOV.mjs.map +0 -1
- package/dist/chunk-3IIFBJCD.mjs +0 -45
- package/dist/chunk-BS4DKYGN.mjs +0 -48
- package/dist/chunk-DE7I3VHO.mjs +0 -106
- package/dist/chunk-F6D2OZ36.mjs +0 -89
- package/dist/chunk-F6D2OZ36.mjs.map +0 -1
- package/dist/chunk-GAOIFQDX.mjs +0 -59
- package/dist/chunk-GAOIFQDX.mjs.map +0 -1
- package/dist/chunk-HUIX2XVR.mjs +0 -159
- package/dist/chunk-HUIX2XVR.mjs.map +0 -1
- package/dist/chunk-K36R6HWY.mjs +0 -51
- package/dist/chunk-MBTAI3WE.mjs +0 -78
- package/dist/chunk-MBTAI3WE.mjs.map +0 -1
- package/dist/chunk-MGT6RUVX.mjs +0 -423
- package/dist/chunk-PIFS7AIT.mjs +0 -446
- package/dist/chunk-WG66JYYW.mjs +0 -116
- package/dist/chunk-XD7WGXHZ.mjs +0 -82
- package/dist/chunk-YYDM6V2F.mjs +0 -113
- package/dist/chunk-ZK7P3TZN.mjs +0 -286
- package/dist/chunk-ZK7P3TZN.mjs.map +0 -1
- /package/dist/{chunk-3IIFBJCD.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,711 +27,535 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// src/
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-latin.xml"),
|
|
61
|
-
sizeMB: 4,
|
|
62
|
-
runtimes: ["python"]
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
extraFiles: [
|
|
66
|
-
{
|
|
67
|
-
url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-dict.txt"),
|
|
68
|
-
filename: "camstack-paddleocr-latin-dict.txt",
|
|
69
|
-
sizeMB: 0.01
|
|
70
|
-
}
|
|
71
|
-
]
|
|
30
|
+
// src/addons/plate-recognition/index.ts
|
|
31
|
+
var plate_recognition_exports = {};
|
|
32
|
+
__export(plate_recognition_exports, {
|
|
33
|
+
default: () => PlateRecognitionAddon
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(plate_recognition_exports);
|
|
36
|
+
|
|
37
|
+
// src/catalogs/plate-recognition-models.ts
|
|
38
|
+
var import_types = require("@camstack/types");
|
|
39
|
+
var HF_REPO = "camstack/camstack-models";
|
|
40
|
+
var PLATE_TEXT_LABELS = [
|
|
41
|
+
{ id: "text", name: "Plate Text" }
|
|
42
|
+
];
|
|
43
|
+
var PLATE_RECOGNITION_MODELS = [
|
|
44
|
+
// ── PaddleOCR PP-OCRv5 ────────────────────────────────────────
|
|
45
|
+
{
|
|
46
|
+
id: "paddleocr-latin",
|
|
47
|
+
name: "PaddleOCR Latin",
|
|
48
|
+
description: "PaddleOCR PP-OCRv5 recognition model for Latin-script license plates",
|
|
49
|
+
inputSize: { width: 320, height: 48 },
|
|
50
|
+
labels: PLATE_TEXT_LABELS,
|
|
51
|
+
formats: {
|
|
52
|
+
// ONNX only — PaddleOCR has dynamic dimensions incompatible with CoreML native conversion.
|
|
53
|
+
// On Apple Silicon, ONNX Runtime uses CoreML EP automatically for acceleration.
|
|
54
|
+
onnx: {
|
|
55
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-rec.onnx"),
|
|
56
|
+
sizeMB: 7.5
|
|
72
57
|
},
|
|
58
|
+
openvino: {
|
|
59
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-latin.xml"),
|
|
60
|
+
sizeMB: 4,
|
|
61
|
+
runtimes: ["python"]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
extraFiles: [
|
|
73
65
|
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
extraFiles: [
|
|
91
|
-
{
|
|
92
|
-
url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-dict.txt"),
|
|
93
|
-
filename: "camstack-paddleocr-en-dict.txt",
|
|
94
|
-
sizeMB: 0.01
|
|
95
|
-
}
|
|
96
|
-
]
|
|
66
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-dict.txt"),
|
|
67
|
+
filename: "camstack-paddleocr-latin-dict.txt",
|
|
68
|
+
sizeMB: 0.01
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "paddleocr-en",
|
|
74
|
+
name: "PaddleOCR English",
|
|
75
|
+
description: "PaddleOCR PP-OCRv5 recognition model optimized for English license plates",
|
|
76
|
+
inputSize: { width: 320, height: 48 },
|
|
77
|
+
labels: PLATE_TEXT_LABELS,
|
|
78
|
+
formats: {
|
|
79
|
+
onnx: {
|
|
80
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-rec.onnx"),
|
|
81
|
+
sizeMB: 7.5
|
|
97
82
|
},
|
|
98
|
-
|
|
99
|
-
|
|
83
|
+
openvino: {
|
|
84
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-en.xml"),
|
|
85
|
+
sizeMB: 4,
|
|
86
|
+
runtimes: ["python"]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
extraFiles: [
|
|
100
90
|
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
91
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-dict.txt"),
|
|
92
|
+
filename: "camstack-paddleocr-en-dict.txt",
|
|
93
|
+
sizeMB: 0.01
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
// ── CRNN-MobileNetV3 (via OnnxTR/docTR) ─────────────────────────
|
|
98
|
+
// Simple CNN+LSTM+CTC architecture — good CoreML compatibility (no dynamic ops)
|
|
99
|
+
{
|
|
100
|
+
id: "crnn-mobilenet-v3-small",
|
|
101
|
+
name: "CRNN MobileNet V3 Small",
|
|
102
|
+
description: "CRNN MobileNetV3-Small \u2014 lightweight text recognition, CoreML compatible via OnnxTR",
|
|
103
|
+
inputSize: { width: 128, height: 32 },
|
|
104
|
+
labels: PLATE_TEXT_LABELS,
|
|
105
|
+
formats: {
|
|
106
|
+
onnx: {
|
|
107
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-small.onnx"),
|
|
108
|
+
sizeMB: 8
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
extraFiles: [
|
|
120
112
|
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
inputSize: { width: 128, height: 32 },
|
|
125
|
-
labels: PLATE_TEXT_LABELS2,
|
|
126
|
-
formats: {
|
|
127
|
-
onnx: {
|
|
128
|
-
url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-large.onnx"),
|
|
129
|
-
sizeMB: 17
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
extraFiles: [
|
|
133
|
-
{
|
|
134
|
-
url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
|
|
135
|
-
filename: "camstack-crnn-mobilenet-charset.txt",
|
|
136
|
-
sizeMB: 0.01
|
|
137
|
-
}
|
|
138
|
-
]
|
|
113
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
|
|
114
|
+
filename: "camstack-crnn-mobilenet-charset.txt",
|
|
115
|
+
sizeMB: 0.01
|
|
139
116
|
}
|
|
140
|
-
]
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "crnn-mobilenet-v3-large",
|
|
121
|
+
name: "CRNN MobileNet V3 Large",
|
|
122
|
+
description: "CRNN MobileNetV3-Large \u2014 higher accuracy text recognition, CoreML compatible",
|
|
123
|
+
inputSize: { width: 128, height: 32 },
|
|
124
|
+
labels: PLATE_TEXT_LABELS,
|
|
125
|
+
formats: {
|
|
126
|
+
onnx: {
|
|
127
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-large.onnx"),
|
|
128
|
+
sizeMB: 17
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
extraFiles: [
|
|
132
|
+
{
|
|
133
|
+
url: (0, import_types.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
|
|
134
|
+
filename: "camstack-crnn-mobilenet-charset.txt",
|
|
135
|
+
sizeMB: 0.01
|
|
136
|
+
}
|
|
137
|
+
]
|
|
141
138
|
}
|
|
142
|
-
|
|
139
|
+
];
|
|
143
140
|
|
|
144
|
-
// src/shared/image-utils.
|
|
145
|
-
var
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const originalWidth = meta.width ?? 0;
|
|
173
|
-
const originalHeight = meta.height ?? 0;
|
|
174
|
-
const scale = Math.min(targetSize / originalWidth, targetSize / originalHeight);
|
|
175
|
-
const scaledWidth = Math.round(originalWidth * scale);
|
|
176
|
-
const scaledHeight = Math.round(originalHeight * scale);
|
|
177
|
-
const padX = Math.floor((targetSize - scaledWidth) / 2);
|
|
178
|
-
const padY = Math.floor((targetSize - scaledHeight) / 2);
|
|
179
|
-
const { data } = await (0, sharp_1.default)(jpeg).resize(scaledWidth, scaledHeight).extend({
|
|
180
|
-
top: padY,
|
|
181
|
-
bottom: targetSize - scaledHeight - padY,
|
|
182
|
-
left: padX,
|
|
183
|
-
right: targetSize - scaledWidth - padX,
|
|
184
|
-
background: { r: 114, g: 114, b: 114 }
|
|
185
|
-
}).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
186
|
-
const numPixels = targetSize * targetSize;
|
|
187
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
188
|
-
for (let i = 0; i < numPixels; i++) {
|
|
189
|
-
const srcBase = i * 3;
|
|
190
|
-
float32[0 * numPixels + i] = data[srcBase] / 255;
|
|
191
|
-
float32[1 * numPixels + i] = data[srcBase + 1] / 255;
|
|
192
|
-
float32[2 * numPixels + i] = data[srcBase + 2] / 255;
|
|
193
|
-
}
|
|
194
|
-
return { data: float32, scale, padX, padY, originalWidth, originalHeight };
|
|
195
|
-
}
|
|
196
|
-
async function resizeAndNormalize2(jpeg, targetWidth, targetHeight, normalization, layout) {
|
|
197
|
-
const { data } = await (0, sharp_1.default)(jpeg).resize(targetWidth, targetHeight, { fit: "fill" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
198
|
-
const numPixels = targetWidth * targetHeight;
|
|
199
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
200
|
-
const mean = [0.485, 0.456, 0.406];
|
|
201
|
-
const std = [0.229, 0.224, 0.225];
|
|
202
|
-
if (layout === "nchw") {
|
|
203
|
-
for (let i = 0; i < numPixels; i++) {
|
|
204
|
-
const srcBase = i * 3;
|
|
205
|
-
for (let c = 0; c < 3; c++) {
|
|
206
|
-
const raw = data[srcBase + c] / 255;
|
|
207
|
-
let val;
|
|
208
|
-
if (normalization === "zero-one") {
|
|
209
|
-
val = raw;
|
|
210
|
-
} else if (normalization === "imagenet") {
|
|
211
|
-
val = (raw - mean[c]) / std[c];
|
|
212
|
-
} else {
|
|
213
|
-
val = data[srcBase + c];
|
|
214
|
-
}
|
|
215
|
-
float32[c * numPixels + i] = val;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
for (let i = 0; i < numPixels; i++) {
|
|
220
|
-
const srcBase = i * 3;
|
|
221
|
-
for (let c = 0; c < 3; c++) {
|
|
222
|
-
const raw = data[srcBase + c] / 255;
|
|
223
|
-
let val;
|
|
224
|
-
if (normalization === "zero-one") {
|
|
225
|
-
val = raw;
|
|
226
|
-
} else if (normalization === "imagenet") {
|
|
227
|
-
val = (raw - mean[c]) / std[c];
|
|
228
|
-
} else {
|
|
229
|
-
val = data[srcBase + c];
|
|
230
|
-
}
|
|
231
|
-
float32[i * 3 + c] = val;
|
|
232
|
-
}
|
|
141
|
+
// src/shared/image-utils.ts
|
|
142
|
+
var import_sharp = __toESM(require("sharp"));
|
|
143
|
+
async function cropRegion(jpeg, roi) {
|
|
144
|
+
return (0, import_sharp.default)(jpeg).extract({
|
|
145
|
+
left: Math.round(roi.x),
|
|
146
|
+
top: Math.round(roi.y),
|
|
147
|
+
width: Math.round(roi.w),
|
|
148
|
+
height: Math.round(roi.h)
|
|
149
|
+
}).jpeg().toBuffer();
|
|
150
|
+
}
|
|
151
|
+
async function resizeAndNormalize(jpeg, targetWidth, targetHeight, normalization, layout) {
|
|
152
|
+
const { data } = await (0, import_sharp.default)(jpeg).resize(targetWidth, targetHeight, { fit: "fill" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
153
|
+
const numPixels = targetWidth * targetHeight;
|
|
154
|
+
const float32 = new Float32Array(3 * numPixels);
|
|
155
|
+
const mean = [0.485, 0.456, 0.406];
|
|
156
|
+
const std = [0.229, 0.224, 0.225];
|
|
157
|
+
if (layout === "nchw") {
|
|
158
|
+
for (let i = 0; i < numPixels; i++) {
|
|
159
|
+
const srcBase = i * 3;
|
|
160
|
+
for (let c = 0; c < 3; c++) {
|
|
161
|
+
const raw = data[srcBase + c] / 255;
|
|
162
|
+
let val;
|
|
163
|
+
if (normalization === "zero-one") {
|
|
164
|
+
val = raw;
|
|
165
|
+
} else if (normalization === "imagenet") {
|
|
166
|
+
val = (raw - mean[c]) / std[c];
|
|
167
|
+
} else {
|
|
168
|
+
val = data[srcBase + c];
|
|
233
169
|
}
|
|
170
|
+
float32[c * numPixels + i] = val;
|
|
234
171
|
}
|
|
235
|
-
return float32;
|
|
236
172
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
for (let
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
173
|
+
} else {
|
|
174
|
+
for (let i = 0; i < numPixels; i++) {
|
|
175
|
+
const srcBase = i * 3;
|
|
176
|
+
for (let c = 0; c < 3; c++) {
|
|
177
|
+
const raw = data[srcBase + c] / 255;
|
|
178
|
+
let val;
|
|
179
|
+
if (normalization === "zero-one") {
|
|
180
|
+
val = raw;
|
|
181
|
+
} else if (normalization === "imagenet") {
|
|
182
|
+
val = (raw - mean[c]) / std[c];
|
|
183
|
+
} else {
|
|
184
|
+
val = data[srcBase + c];
|
|
185
|
+
}
|
|
186
|
+
float32[i * 3 + c] = val;
|
|
245
187
|
}
|
|
246
|
-
return gray;
|
|
247
188
|
}
|
|
248
189
|
}
|
|
249
|
-
|
|
190
|
+
return float32;
|
|
191
|
+
}
|
|
250
192
|
|
|
251
|
-
// src/shared/postprocess/paddleocr.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
for (let c = 1; c < numChars; c++) {
|
|
265
|
-
const val = output[offset + c];
|
|
266
|
-
if (val > bestVal) {
|
|
267
|
-
bestVal = val;
|
|
268
|
-
bestIdx = c;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
rawIndices.push(bestIdx);
|
|
272
|
-
totalLogScore += bestVal;
|
|
273
|
-
}
|
|
274
|
-
const collapsed = [];
|
|
275
|
-
for (let i = 0; i < rawIndices.length; i++) {
|
|
276
|
-
const cur = rawIndices[i];
|
|
277
|
-
if (i === 0 || cur !== rawIndices[i - 1]) {
|
|
278
|
-
collapsed.push(cur);
|
|
279
|
-
}
|
|
193
|
+
// src/shared/postprocess/paddleocr.ts
|
|
194
|
+
function ctcDecode(output, seqLen, numChars, charset) {
|
|
195
|
+
let totalLogScore = 0;
|
|
196
|
+
const rawIndices = [];
|
|
197
|
+
for (let t = 0; t < seqLen; t++) {
|
|
198
|
+
const offset = t * numChars;
|
|
199
|
+
let bestIdx = 0;
|
|
200
|
+
let bestVal = output[offset];
|
|
201
|
+
for (let c = 1; c < numChars; c++) {
|
|
202
|
+
const val = output[offset + c];
|
|
203
|
+
if (val > bestVal) {
|
|
204
|
+
bestVal = val;
|
|
205
|
+
bestIdx = c;
|
|
280
206
|
}
|
|
281
|
-
const filtered = collapsed.filter((idx) => idx !== 0);
|
|
282
|
-
const text = filtered.map((idx) => charset[idx] ?? "").join("");
|
|
283
|
-
const confidence = seqLen > 0 ? totalLogScore / seqLen : 0;
|
|
284
|
-
return { text, confidence };
|
|
285
207
|
}
|
|
208
|
+
rawIndices.push(bestIdx);
|
|
209
|
+
totalLogScore += bestVal;
|
|
286
210
|
}
|
|
287
|
-
|
|
211
|
+
const collapsed = [];
|
|
212
|
+
for (let i = 0; i < rawIndices.length; i++) {
|
|
213
|
+
const cur = rawIndices[i];
|
|
214
|
+
if (i === 0 || cur !== rawIndices[i - 1]) {
|
|
215
|
+
collapsed.push(cur);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const filtered = collapsed.filter((idx) => idx !== 0);
|
|
219
|
+
const text = filtered.map((idx) => charset[idx] ?? "").join("");
|
|
220
|
+
const confidence = seqLen > 0 ? totalLogScore / seqLen : 0;
|
|
221
|
+
return { text, confidence };
|
|
222
|
+
}
|
|
288
223
|
|
|
289
|
-
// src/shared/
|
|
290
|
-
var
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (mod != null) {
|
|
324
|
-
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
325
|
-
}
|
|
326
|
-
__setModuleDefault(result, mod);
|
|
327
|
-
return result;
|
|
328
|
-
};
|
|
329
|
-
})();
|
|
330
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
331
|
-
exports2.NodeInferenceEngine = void 0;
|
|
332
|
-
var path2 = __importStar(require("path"));
|
|
333
|
-
var BACKEND_TO_PROVIDER = {
|
|
334
|
-
cpu: "cpu",
|
|
335
|
-
coreml: "coreml",
|
|
336
|
-
cuda: "cuda",
|
|
337
|
-
tensorrt: "tensorrt",
|
|
338
|
-
dml: "dml"
|
|
224
|
+
// src/shared/engine-resolver.ts
|
|
225
|
+
var fs = __toESM(require("fs"));
|
|
226
|
+
var path2 = __toESM(require("path"));
|
|
227
|
+
|
|
228
|
+
// src/shared/node-engine.ts
|
|
229
|
+
var path = __toESM(require("path"));
|
|
230
|
+
var BACKEND_TO_PROVIDER = {
|
|
231
|
+
cpu: "cpu",
|
|
232
|
+
coreml: "coreml",
|
|
233
|
+
cuda: "cuda",
|
|
234
|
+
tensorrt: "tensorrt",
|
|
235
|
+
dml: "dml"
|
|
236
|
+
};
|
|
237
|
+
var BACKEND_TO_DEVICE = {
|
|
238
|
+
cpu: "cpu",
|
|
239
|
+
coreml: "gpu-mps",
|
|
240
|
+
cuda: "gpu-cuda",
|
|
241
|
+
tensorrt: "tensorrt"
|
|
242
|
+
};
|
|
243
|
+
var NodeInferenceEngine = class {
|
|
244
|
+
constructor(modelPath, backend) {
|
|
245
|
+
this.modelPath = modelPath;
|
|
246
|
+
this.backend = backend;
|
|
247
|
+
this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
|
|
248
|
+
}
|
|
249
|
+
runtime = "onnx";
|
|
250
|
+
device;
|
|
251
|
+
session = null;
|
|
252
|
+
async initialize() {
|
|
253
|
+
const ort = await import("onnxruntime-node");
|
|
254
|
+
const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
|
|
255
|
+
const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
|
|
256
|
+
const sessionOptions = {
|
|
257
|
+
executionProviders: [provider]
|
|
339
258
|
};
|
|
340
|
-
|
|
341
|
-
|
|
259
|
+
this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
|
|
260
|
+
}
|
|
261
|
+
async run(input, inputShape) {
|
|
262
|
+
if (!this.session) {
|
|
263
|
+
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
264
|
+
}
|
|
265
|
+
const ort = await import("onnxruntime-node");
|
|
266
|
+
const sess = this.session;
|
|
267
|
+
const inputName = sess.inputNames[0];
|
|
268
|
+
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
269
|
+
const feeds = { [inputName]: tensor };
|
|
270
|
+
const results = await sess.run(feeds);
|
|
271
|
+
const outputName = sess.outputNames[0];
|
|
272
|
+
const outputTensor = results[outputName];
|
|
273
|
+
return outputTensor.data;
|
|
274
|
+
}
|
|
275
|
+
async runMultiOutput(input, inputShape) {
|
|
276
|
+
if (!this.session) {
|
|
277
|
+
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
278
|
+
}
|
|
279
|
+
const ort = await import("onnxruntime-node");
|
|
280
|
+
const sess = this.session;
|
|
281
|
+
const inputName = sess.inputNames[0];
|
|
282
|
+
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
283
|
+
const feeds = { [inputName]: tensor };
|
|
284
|
+
const results = await sess.run(feeds);
|
|
285
|
+
const out = {};
|
|
286
|
+
for (const name of sess.outputNames) {
|
|
287
|
+
out[name] = results[name].data;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
async dispose() {
|
|
292
|
+
this.session = null;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/shared/python-engine.ts
|
|
297
|
+
var import_node_child_process = require("child_process");
|
|
298
|
+
var PythonInferenceEngine = class {
|
|
299
|
+
constructor(pythonPath, scriptPath, runtime, modelPath, extraArgs = []) {
|
|
300
|
+
this.pythonPath = pythonPath;
|
|
301
|
+
this.scriptPath = scriptPath;
|
|
302
|
+
this.modelPath = modelPath;
|
|
303
|
+
this.extraArgs = extraArgs;
|
|
304
|
+
this.runtime = runtime;
|
|
305
|
+
const runtimeDeviceMap = {
|
|
306
|
+
onnx: "cpu",
|
|
342
307
|
coreml: "gpu-mps",
|
|
343
|
-
|
|
344
|
-
|
|
308
|
+
pytorch: "cpu",
|
|
309
|
+
openvino: "cpu",
|
|
310
|
+
tflite: "cpu"
|
|
345
311
|
};
|
|
346
|
-
|
|
347
|
-
modelPath;
|
|
348
|
-
backend;
|
|
349
|
-
runtime = "onnx";
|
|
350
|
-
device;
|
|
351
|
-
session = null;
|
|
352
|
-
constructor(modelPath, backend) {
|
|
353
|
-
this.modelPath = modelPath;
|
|
354
|
-
this.backend = backend;
|
|
355
|
-
this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
|
|
356
|
-
}
|
|
357
|
-
async initialize() {
|
|
358
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
359
|
-
const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
|
|
360
|
-
const absModelPath = path2.isAbsolute(this.modelPath) ? this.modelPath : path2.resolve(process.cwd(), this.modelPath);
|
|
361
|
-
const sessionOptions = {
|
|
362
|
-
executionProviders: [provider]
|
|
363
|
-
};
|
|
364
|
-
this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
|
|
365
|
-
}
|
|
366
|
-
async run(input, inputShape) {
|
|
367
|
-
if (!this.session) {
|
|
368
|
-
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
369
|
-
}
|
|
370
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
371
|
-
const sess = this.session;
|
|
372
|
-
const inputName = sess.inputNames[0];
|
|
373
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
374
|
-
const feeds = { [inputName]: tensor };
|
|
375
|
-
const results = await sess.run(feeds);
|
|
376
|
-
const outputName = sess.outputNames[0];
|
|
377
|
-
const outputTensor = results[outputName];
|
|
378
|
-
return outputTensor.data;
|
|
379
|
-
}
|
|
380
|
-
async runMultiOutput(input, inputShape) {
|
|
381
|
-
if (!this.session) {
|
|
382
|
-
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
383
|
-
}
|
|
384
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
385
|
-
const sess = this.session;
|
|
386
|
-
const inputName = sess.inputNames[0];
|
|
387
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
388
|
-
const feeds = { [inputName]: tensor };
|
|
389
|
-
const results = await sess.run(feeds);
|
|
390
|
-
const out = {};
|
|
391
|
-
for (const name of sess.outputNames) {
|
|
392
|
-
out[name] = results[name].data;
|
|
393
|
-
}
|
|
394
|
-
return out;
|
|
395
|
-
}
|
|
396
|
-
async dispose() {
|
|
397
|
-
this.session = null;
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
exports2.NodeInferenceEngine = NodeInferenceEngine;
|
|
312
|
+
this.device = runtimeDeviceMap[runtime];
|
|
401
313
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this.
|
|
428
|
-
this.runtime = runtime;
|
|
429
|
-
const runtimeDeviceMap = {
|
|
430
|
-
onnx: "cpu",
|
|
431
|
-
coreml: "gpu-mps",
|
|
432
|
-
pytorch: "cpu",
|
|
433
|
-
openvino: "cpu",
|
|
434
|
-
tflite: "cpu"
|
|
435
|
-
};
|
|
436
|
-
this.device = runtimeDeviceMap[runtime];
|
|
437
|
-
}
|
|
438
|
-
async initialize() {
|
|
439
|
-
const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
|
|
440
|
-
this.process = (0, node_child_process_1.spawn)(this.pythonPath, args, {
|
|
441
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
442
|
-
});
|
|
443
|
-
if (!this.process.stdout || !this.process.stdin) {
|
|
444
|
-
throw new Error("PythonInferenceEngine: failed to create process pipes");
|
|
445
|
-
}
|
|
446
|
-
this.process.stderr?.on("data", (chunk) => {
|
|
447
|
-
process.stderr.write(`[python-engine] ${chunk.toString()}`);
|
|
448
|
-
});
|
|
449
|
-
this.process.on("error", (err) => {
|
|
450
|
-
this.pendingReject?.(err);
|
|
451
|
-
this.pendingReject = null;
|
|
452
|
-
this.pendingResolve = null;
|
|
453
|
-
});
|
|
454
|
-
this.process.on("exit", (code) => {
|
|
455
|
-
if (code !== 0) {
|
|
456
|
-
const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
|
|
457
|
-
this.pendingReject?.(err);
|
|
458
|
-
this.pendingReject = null;
|
|
459
|
-
this.pendingResolve = null;
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
this.process.stdout.on("data", (chunk) => {
|
|
463
|
-
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
464
|
-
this._tryReceive();
|
|
465
|
-
});
|
|
466
|
-
await new Promise((resolve, reject) => {
|
|
467
|
-
const timeout = setTimeout(() => resolve(), 2e3);
|
|
468
|
-
this.process?.on("error", (err) => {
|
|
469
|
-
clearTimeout(timeout);
|
|
470
|
-
reject(err);
|
|
471
|
-
});
|
|
472
|
-
this.process?.on("exit", (code) => {
|
|
473
|
-
clearTimeout(timeout);
|
|
474
|
-
if (code !== 0) {
|
|
475
|
-
reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
_tryReceive() {
|
|
481
|
-
if (this.receiveBuffer.length < 4)
|
|
482
|
-
return;
|
|
483
|
-
const length = this.receiveBuffer.readUInt32LE(0);
|
|
484
|
-
if (this.receiveBuffer.length < 4 + length)
|
|
485
|
-
return;
|
|
486
|
-
const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
|
|
487
|
-
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
488
|
-
const resolve = this.pendingResolve;
|
|
489
|
-
const reject = this.pendingReject;
|
|
490
|
-
this.pendingResolve = null;
|
|
314
|
+
runtime;
|
|
315
|
+
device;
|
|
316
|
+
process = null;
|
|
317
|
+
receiveBuffer = Buffer.alloc(0);
|
|
318
|
+
pendingResolve = null;
|
|
319
|
+
pendingReject = null;
|
|
320
|
+
async initialize() {
|
|
321
|
+
const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
|
|
322
|
+
this.process = (0, import_node_child_process.spawn)(this.pythonPath, args, {
|
|
323
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
324
|
+
});
|
|
325
|
+
if (!this.process.stdout || !this.process.stdin) {
|
|
326
|
+
throw new Error("PythonInferenceEngine: failed to create process pipes");
|
|
327
|
+
}
|
|
328
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
329
|
+
process.stderr.write(`[python-engine] ${chunk.toString()}`);
|
|
330
|
+
});
|
|
331
|
+
this.process.on("error", (err) => {
|
|
332
|
+
this.pendingReject?.(err);
|
|
333
|
+
this.pendingReject = null;
|
|
334
|
+
this.pendingResolve = null;
|
|
335
|
+
});
|
|
336
|
+
this.process.on("exit", (code) => {
|
|
337
|
+
if (code !== 0) {
|
|
338
|
+
const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
|
|
339
|
+
this.pendingReject?.(err);
|
|
491
340
|
this.pendingReject = null;
|
|
492
|
-
|
|
493
|
-
return;
|
|
494
|
-
try {
|
|
495
|
-
const parsed = JSON.parse(jsonBytes.toString("utf8"));
|
|
496
|
-
resolve(parsed);
|
|
497
|
-
} catch (err) {
|
|
498
|
-
reject?.(err instanceof Error ? err : new Error(String(err)));
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
/** Send JPEG buffer, receive JSON detection results */
|
|
502
|
-
async runJpeg(jpeg) {
|
|
503
|
-
if (!this.process?.stdin) {
|
|
504
|
-
throw new Error("PythonInferenceEngine: process not initialized");
|
|
505
|
-
}
|
|
506
|
-
return new Promise((resolve, reject) => {
|
|
507
|
-
this.pendingResolve = resolve;
|
|
508
|
-
this.pendingReject = reject;
|
|
509
|
-
const lengthBuf = Buffer.allocUnsafe(4);
|
|
510
|
-
lengthBuf.writeUInt32LE(jpeg.length, 0);
|
|
511
|
-
this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
/** IInferenceEngine.run — wraps runJpeg for compatibility */
|
|
515
|
-
async run(_input, _inputShape) {
|
|
516
|
-
throw new Error("PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input");
|
|
517
|
-
}
|
|
518
|
-
/** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
|
|
519
|
-
async runMultiOutput(_input, _inputShape) {
|
|
520
|
-
throw new Error("PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input");
|
|
341
|
+
this.pendingResolve = null;
|
|
521
342
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
343
|
+
});
|
|
344
|
+
this.process.stdout.on("data", (chunk) => {
|
|
345
|
+
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
346
|
+
this._tryReceive();
|
|
347
|
+
});
|
|
348
|
+
await new Promise((resolve2, reject) => {
|
|
349
|
+
const timeout = setTimeout(() => resolve2(), 2e3);
|
|
350
|
+
this.process?.on("error", (err) => {
|
|
351
|
+
clearTimeout(timeout);
|
|
352
|
+
reject(err);
|
|
353
|
+
});
|
|
354
|
+
this.process?.on("exit", (code) => {
|
|
355
|
+
clearTimeout(timeout);
|
|
356
|
+
if (code !== 0) {
|
|
357
|
+
reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
|
|
527
358
|
}
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
_tryReceive() {
|
|
363
|
+
if (this.receiveBuffer.length < 4) return;
|
|
364
|
+
const length = this.receiveBuffer.readUInt32LE(0);
|
|
365
|
+
if (this.receiveBuffer.length < 4 + length) return;
|
|
366
|
+
const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
|
|
367
|
+
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
368
|
+
const resolve2 = this.pendingResolve;
|
|
369
|
+
const reject = this.pendingReject;
|
|
370
|
+
this.pendingResolve = null;
|
|
371
|
+
this.pendingReject = null;
|
|
372
|
+
if (!resolve2) return;
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(jsonBytes.toString("utf8"));
|
|
375
|
+
resolve2(parsed);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
reject?.(err instanceof Error ? err : new Error(String(err)));
|
|
535
378
|
}
|
|
536
379
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return m[k];
|
|
549
|
-
} };
|
|
550
|
-
}
|
|
551
|
-
Object.defineProperty(o, k2, desc);
|
|
552
|
-
}) : (function(o, m, k, k2) {
|
|
553
|
-
if (k2 === void 0) k2 = k;
|
|
554
|
-
o[k2] = m[k];
|
|
555
|
-
}));
|
|
556
|
-
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
557
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
558
|
-
}) : function(o, v) {
|
|
559
|
-
o["default"] = v;
|
|
380
|
+
/** Send JPEG buffer, receive JSON detection results */
|
|
381
|
+
async runJpeg(jpeg) {
|
|
382
|
+
if (!this.process?.stdin) {
|
|
383
|
+
throw new Error("PythonInferenceEngine: process not initialized");
|
|
384
|
+
}
|
|
385
|
+
return new Promise((resolve2, reject) => {
|
|
386
|
+
this.pendingResolve = resolve2;
|
|
387
|
+
this.pendingReject = reject;
|
|
388
|
+
const lengthBuf = Buffer.allocUnsafe(4);
|
|
389
|
+
lengthBuf.writeUInt32LE(jpeg.length, 0);
|
|
390
|
+
this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
|
|
560
391
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
})();
|
|
580
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
581
|
-
exports2.resolveEngine = resolveEngine2;
|
|
582
|
-
exports2.probeOnnxBackends = probeOnnxBackends;
|
|
583
|
-
var fs2 = __importStar(require("fs"));
|
|
584
|
-
var path2 = __importStar(require("path"));
|
|
585
|
-
var node_engine_js_1 = require_node_engine();
|
|
586
|
-
var python_engine_js_1 = require_python_engine();
|
|
587
|
-
var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
|
|
588
|
-
var BACKEND_TO_FORMAT = {
|
|
589
|
-
cpu: "onnx",
|
|
590
|
-
coreml: "onnx",
|
|
591
|
-
cuda: "onnx",
|
|
592
|
-
tensorrt: "onnx"
|
|
593
|
-
};
|
|
594
|
-
var RUNTIME_TO_FORMAT = {
|
|
595
|
-
onnx: "onnx",
|
|
596
|
-
coreml: "coreml",
|
|
597
|
-
openvino: "openvino",
|
|
598
|
-
tflite: "tflite",
|
|
599
|
-
pytorch: "pt"
|
|
600
|
-
};
|
|
601
|
-
function modelFilePath(modelsDir, modelEntry, format) {
|
|
602
|
-
const formatEntry = modelEntry.formats[format];
|
|
603
|
-
if (!formatEntry) {
|
|
604
|
-
throw new Error(`Model ${modelEntry.id} has no ${format} format`);
|
|
605
|
-
}
|
|
606
|
-
const urlParts = formatEntry.url.split("/");
|
|
607
|
-
const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
|
|
608
|
-
return path2.join(modelsDir, filename);
|
|
392
|
+
}
|
|
393
|
+
/** IInferenceEngine.run — wraps runJpeg for compatibility */
|
|
394
|
+
async run(_input, _inputShape) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
"PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input"
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
/** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
|
|
400
|
+
async runMultiOutput(_input, _inputShape) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
"PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
async dispose() {
|
|
406
|
+
if (this.process) {
|
|
407
|
+
this.process.stdin?.end();
|
|
408
|
+
this.process.kill("SIGTERM");
|
|
409
|
+
this.process = null;
|
|
609
410
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/shared/engine-resolver.ts
|
|
415
|
+
var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
|
|
416
|
+
var BACKEND_TO_FORMAT = {
|
|
417
|
+
cpu: "onnx",
|
|
418
|
+
coreml: "onnx",
|
|
419
|
+
cuda: "onnx",
|
|
420
|
+
tensorrt: "onnx"
|
|
421
|
+
};
|
|
422
|
+
var RUNTIME_TO_FORMAT = {
|
|
423
|
+
onnx: "onnx",
|
|
424
|
+
coreml: "coreml",
|
|
425
|
+
openvino: "openvino",
|
|
426
|
+
tflite: "tflite",
|
|
427
|
+
pytorch: "pt"
|
|
428
|
+
};
|
|
429
|
+
function modelFilePath(modelsDir, modelEntry, format) {
|
|
430
|
+
const formatEntry = modelEntry.formats[format];
|
|
431
|
+
if (!formatEntry) {
|
|
432
|
+
throw new Error(`Model ${modelEntry.id} has no ${format} format`);
|
|
433
|
+
}
|
|
434
|
+
const urlParts = formatEntry.url.split("/");
|
|
435
|
+
const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
|
|
436
|
+
return path2.join(modelsDir, filename);
|
|
437
|
+
}
|
|
438
|
+
function modelExists(filePath) {
|
|
439
|
+
try {
|
|
440
|
+
return fs.existsSync(filePath);
|
|
441
|
+
} catch {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function resolveEngine(options) {
|
|
446
|
+
const { runtime, backend, modelEntry, modelsDir, models } = options;
|
|
447
|
+
let selectedFormat;
|
|
448
|
+
let selectedBackend;
|
|
449
|
+
if (runtime === "auto") {
|
|
450
|
+
const available = await probeOnnxBackends();
|
|
451
|
+
let chosen = null;
|
|
452
|
+
for (const b of AUTO_BACKEND_PRIORITY) {
|
|
453
|
+
if (!available.includes(b)) continue;
|
|
454
|
+
const fmt = BACKEND_TO_FORMAT[b];
|
|
455
|
+
if (!fmt) continue;
|
|
456
|
+
if (!modelEntry.formats[fmt]) continue;
|
|
457
|
+
chosen = { backend: b, format: fmt };
|
|
458
|
+
break;
|
|
616
459
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (runtime === "auto") {
|
|
622
|
-
const available = await probeOnnxBackends();
|
|
623
|
-
let chosen = null;
|
|
624
|
-
for (const b of AUTO_BACKEND_PRIORITY) {
|
|
625
|
-
if (!available.includes(b))
|
|
626
|
-
continue;
|
|
627
|
-
const fmt = BACKEND_TO_FORMAT[b];
|
|
628
|
-
if (!fmt)
|
|
629
|
-
continue;
|
|
630
|
-
if (!modelEntry.formats[fmt])
|
|
631
|
-
continue;
|
|
632
|
-
chosen = { backend: b, format: fmt };
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
if (!chosen) {
|
|
636
|
-
throw new Error(`resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`);
|
|
637
|
-
}
|
|
638
|
-
selectedFormat = chosen.format;
|
|
639
|
-
selectedBackend = chosen.backend;
|
|
640
|
-
} else {
|
|
641
|
-
const fmt = RUNTIME_TO_FORMAT[runtime];
|
|
642
|
-
if (!fmt) {
|
|
643
|
-
throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
|
|
644
|
-
}
|
|
645
|
-
if (!modelEntry.formats[fmt]) {
|
|
646
|
-
throw new Error(`resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`);
|
|
647
|
-
}
|
|
648
|
-
selectedFormat = fmt;
|
|
649
|
-
selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
|
|
650
|
-
}
|
|
651
|
-
let modelPath;
|
|
652
|
-
if (models) {
|
|
653
|
-
modelPath = await models.ensure(modelEntry.id, selectedFormat);
|
|
654
|
-
} else {
|
|
655
|
-
modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
|
|
656
|
-
if (!modelExists(modelPath)) {
|
|
657
|
-
throw new Error(`resolveEngine: model file not found at ${modelPath} and no model service provided`);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (selectedFormat === "onnx") {
|
|
661
|
-
const engine = new node_engine_js_1.NodeInferenceEngine(modelPath, selectedBackend);
|
|
662
|
-
await engine.initialize();
|
|
663
|
-
return { engine, format: selectedFormat, modelPath };
|
|
664
|
-
}
|
|
665
|
-
const { pythonPath } = options;
|
|
666
|
-
const PYTHON_SCRIPT_MAP = {
|
|
667
|
-
coreml: "coreml_inference.py",
|
|
668
|
-
pytorch: "pytorch_inference.py",
|
|
669
|
-
openvino: "openvino_inference.py"
|
|
670
|
-
};
|
|
671
|
-
const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
|
|
672
|
-
const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
|
|
673
|
-
if (scriptName && pythonPath) {
|
|
674
|
-
const candidates = [
|
|
675
|
-
path2.join(__dirname, "../../python", scriptName),
|
|
676
|
-
path2.join(__dirname, "../python", scriptName),
|
|
677
|
-
path2.join(__dirname, "../../../python", scriptName)
|
|
678
|
-
];
|
|
679
|
-
const scriptPath = candidates.find((p) => fs2.existsSync(p));
|
|
680
|
-
if (!scriptPath) {
|
|
681
|
-
throw new Error(`resolveEngine: Python script "${scriptName}" not found. Searched:
|
|
682
|
-
${candidates.join("\n")}`);
|
|
683
|
-
}
|
|
684
|
-
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
685
|
-
const engine = new python_engine_js_1.PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
|
|
686
|
-
`--input-size=${inputSize}`,
|
|
687
|
-
`--confidence=0.25`
|
|
688
|
-
]);
|
|
689
|
-
await engine.initialize();
|
|
690
|
-
return { engine, format: selectedFormat, modelPath };
|
|
691
|
-
}
|
|
692
|
-
const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
|
|
693
|
-
if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
|
|
694
|
-
const engine = new node_engine_js_1.NodeInferenceEngine(fallbackPath, "cpu");
|
|
695
|
-
await engine.initialize();
|
|
696
|
-
return { engine, format: "onnx", modelPath: fallbackPath };
|
|
697
|
-
}
|
|
698
|
-
throw new Error(`resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`);
|
|
460
|
+
if (!chosen) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
`resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`
|
|
463
|
+
);
|
|
699
464
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
else if (normalized === "tensorrt")
|
|
712
|
-
available.push("tensorrt");
|
|
713
|
-
}
|
|
714
|
-
} catch {
|
|
715
|
-
}
|
|
716
|
-
if (process.platform === "darwin" && !available.includes("coreml")) {
|
|
717
|
-
available.push("coreml");
|
|
718
|
-
}
|
|
719
|
-
return [...new Set(available)];
|
|
465
|
+
selectedFormat = chosen.format;
|
|
466
|
+
selectedBackend = chosen.backend;
|
|
467
|
+
} else {
|
|
468
|
+
const fmt = RUNTIME_TO_FORMAT[runtime];
|
|
469
|
+
if (!fmt) {
|
|
470
|
+
throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
|
|
471
|
+
}
|
|
472
|
+
if (!modelEntry.formats[fmt]) {
|
|
473
|
+
throw new Error(
|
|
474
|
+
`resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`
|
|
475
|
+
);
|
|
720
476
|
}
|
|
477
|
+
selectedFormat = fmt;
|
|
478
|
+
selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
|
|
721
479
|
}
|
|
722
|
-
|
|
480
|
+
let modelPath;
|
|
481
|
+
if (models) {
|
|
482
|
+
modelPath = await models.ensure(modelEntry.id, selectedFormat);
|
|
483
|
+
} else {
|
|
484
|
+
modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
|
|
485
|
+
if (!modelExists(modelPath)) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`resolveEngine: model file not found at ${modelPath} and no model service provided`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (selectedFormat === "onnx") {
|
|
492
|
+
const engine = new NodeInferenceEngine(modelPath, selectedBackend);
|
|
493
|
+
await engine.initialize();
|
|
494
|
+
return { engine, format: selectedFormat, modelPath };
|
|
495
|
+
}
|
|
496
|
+
const { pythonPath } = options;
|
|
497
|
+
const PYTHON_SCRIPT_MAP = {
|
|
498
|
+
coreml: "coreml_inference.py",
|
|
499
|
+
pytorch: "pytorch_inference.py",
|
|
500
|
+
openvino: "openvino_inference.py"
|
|
501
|
+
};
|
|
502
|
+
const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
|
|
503
|
+
const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
|
|
504
|
+
if (scriptName && pythonPath) {
|
|
505
|
+
const candidates = [
|
|
506
|
+
path2.join(__dirname, "../../python", scriptName),
|
|
507
|
+
path2.join(__dirname, "../python", scriptName),
|
|
508
|
+
path2.join(__dirname, "../../../python", scriptName)
|
|
509
|
+
];
|
|
510
|
+
const scriptPath = candidates.find((p) => fs.existsSync(p));
|
|
511
|
+
if (!scriptPath) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`resolveEngine: Python script "${scriptName}" not found. Searched:
|
|
514
|
+
${candidates.join("\n")}`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
518
|
+
const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
|
|
519
|
+
`--input-size=${inputSize}`,
|
|
520
|
+
`--confidence=0.25`
|
|
521
|
+
]);
|
|
522
|
+
await engine.initialize();
|
|
523
|
+
return { engine, format: selectedFormat, modelPath };
|
|
524
|
+
}
|
|
525
|
+
const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
|
|
526
|
+
if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
|
|
527
|
+
const engine = new NodeInferenceEngine(fallbackPath, "cpu");
|
|
528
|
+
await engine.initialize();
|
|
529
|
+
return { engine, format: "onnx", modelPath: fallbackPath };
|
|
530
|
+
}
|
|
531
|
+
throw new Error(
|
|
532
|
+
`resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
async function probeOnnxBackends() {
|
|
536
|
+
const available = ["cpu"];
|
|
537
|
+
try {
|
|
538
|
+
const ort = await import("onnxruntime-node");
|
|
539
|
+
const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
|
|
540
|
+
for (const p of providers) {
|
|
541
|
+
const normalized = p.toLowerCase().replace("executionprovider", "");
|
|
542
|
+
if (normalized === "coreml") available.push("coreml");
|
|
543
|
+
else if (normalized === "cuda") available.push("cuda");
|
|
544
|
+
else if (normalized === "tensorrt") available.push("tensorrt");
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
if (process.platform === "darwin" && !available.includes("coreml")) {
|
|
549
|
+
available.push("coreml");
|
|
550
|
+
}
|
|
551
|
+
return [...new Set(available)];
|
|
552
|
+
}
|
|
723
553
|
|
|
724
554
|
// src/addons/plate-recognition/index.ts
|
|
725
|
-
var
|
|
726
|
-
|
|
727
|
-
default: () => PlateRecognitionAddon
|
|
728
|
-
});
|
|
729
|
-
module.exports = __toCommonJS(plate_recognition_exports);
|
|
730
|
-
var import_plate_recognition_models = __toESM(require_plate_recognition_models());
|
|
731
|
-
var import_image_utils = __toESM(require_image_utils());
|
|
732
|
-
var import_paddleocr = __toESM(require_paddleocr());
|
|
733
|
-
var import_engine_resolver = __toESM(require_engine_resolver());
|
|
734
|
-
var fs = __toESM(require("fs"));
|
|
735
|
-
var path = __toESM(require("path"));
|
|
555
|
+
var fs2 = __toESM(require("fs"));
|
|
556
|
+
var path3 = __toESM(require("path"));
|
|
736
557
|
var PLATE_TEXT_LABEL = { id: "plate-text", name: "Plate Text" };
|
|
737
|
-
var
|
|
558
|
+
var PLATE_TEXT_LABELS2 = [PLATE_TEXT_LABEL];
|
|
738
559
|
var PLATE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
739
560
|
function loadCharset(modelsDir, modelId) {
|
|
740
561
|
const dictNames = [
|
|
@@ -744,9 +565,9 @@ function loadCharset(modelsDir, modelId) {
|
|
|
744
565
|
`camstack-crnn-mobilenet-charset.txt`
|
|
745
566
|
];
|
|
746
567
|
for (const name of dictNames) {
|
|
747
|
-
const dictPath =
|
|
748
|
-
if (
|
|
749
|
-
const lines =
|
|
568
|
+
const dictPath = path3.join(modelsDir, name);
|
|
569
|
+
if (fs2.existsSync(dictPath)) {
|
|
570
|
+
const lines = fs2.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
|
|
750
571
|
return ["", ...lines, " "];
|
|
751
572
|
}
|
|
752
573
|
}
|
|
@@ -792,7 +613,7 @@ var PlateRecognitionAddon = class {
|
|
|
792
613
|
"paddleocr-latin": { ram: 100, accuracy: 80 },
|
|
793
614
|
"paddleocr-en": { ram: 100, accuracy: 80 }
|
|
794
615
|
};
|
|
795
|
-
return
|
|
616
|
+
return PLATE_RECOGNITION_MODELS.map((m) => ({
|
|
796
617
|
modelId: m.id,
|
|
797
618
|
name: m.name,
|
|
798
619
|
minRAM_MB: scores[m.id]?.ram ?? 100,
|
|
@@ -808,7 +629,7 @@ var PlateRecognitionAddon = class {
|
|
|
808
629
|
const cfg = ctx.addonConfig;
|
|
809
630
|
const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "paddleocr-latin";
|
|
810
631
|
this.minConfidence = cfg["minConfidence"] ?? 0.5;
|
|
811
|
-
const entry =
|
|
632
|
+
const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
|
|
812
633
|
if (!entry) {
|
|
813
634
|
throw new Error(`PlateRecognitionAddon: unknown modelId "${modelId}"`);
|
|
814
635
|
}
|
|
@@ -819,17 +640,17 @@ var PlateRecognitionAddon = class {
|
|
|
819
640
|
const start = Date.now();
|
|
820
641
|
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
821
642
|
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}`);
|
|
822
|
-
const plateCrop = await
|
|
643
|
+
const plateCrop = await cropRegion(input.frame.data, input.roi);
|
|
823
644
|
console.log(`[plate-recognition] Crop size: ${plateCrop.length} bytes`);
|
|
824
645
|
try {
|
|
825
646
|
require("fs").writeFileSync("/tmp/plate-recognition-crop.jpg", plateCrop);
|
|
826
647
|
} catch {
|
|
827
648
|
}
|
|
828
|
-
const normalized = await
|
|
649
|
+
const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, "zero-one", "nchw");
|
|
829
650
|
const output = await this.engine.run(normalized, [1, 3, inputH, inputW]);
|
|
830
651
|
const numChars = this.charset.length;
|
|
831
652
|
const seqLen = output.length / numChars;
|
|
832
|
-
const { text, confidence } =
|
|
653
|
+
const { text, confidence } = ctcDecode(output, seqLen, numChars, this.charset);
|
|
833
654
|
return {
|
|
834
655
|
classifications: [
|
|
835
656
|
{
|
|
@@ -848,14 +669,14 @@ var PlateRecognitionAddon = class {
|
|
|
848
669
|
const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
|
|
849
670
|
const backend = config?.backend ?? "cpu";
|
|
850
671
|
const format = config?.format ?? "onnx";
|
|
851
|
-
const entry =
|
|
672
|
+
const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
|
|
852
673
|
this.modelEntry = entry;
|
|
853
674
|
const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
|
|
854
675
|
if (this.ctx.models) {
|
|
855
676
|
await this.ctx.models.ensure(modelId, format);
|
|
856
677
|
}
|
|
857
678
|
this.charset = loadCharset(modelsDir, modelId);
|
|
858
|
-
const resolved = await
|
|
679
|
+
const resolved = await resolveEngine({
|
|
859
680
|
runtime,
|
|
860
681
|
backend,
|
|
861
682
|
modelEntry: entry,
|
|
@@ -879,7 +700,7 @@ var PlateRecognitionAddon = class {
|
|
|
879
700
|
key: "modelId",
|
|
880
701
|
label: "Model",
|
|
881
702
|
type: "model-selector",
|
|
882
|
-
catalog: [...
|
|
703
|
+
catalog: [...PLATE_RECOGNITION_MODELS],
|
|
883
704
|
allowCustom: false,
|
|
884
705
|
allowConversion: false,
|
|
885
706
|
acceptFormats: ["onnx", "openvino"],
|
|
@@ -939,13 +760,13 @@ var PlateRecognitionAddon = class {
|
|
|
939
760
|
return PLATE_REC_CLASS_MAP;
|
|
940
761
|
}
|
|
941
762
|
getModelCatalog() {
|
|
942
|
-
return [...
|
|
763
|
+
return [...PLATE_RECOGNITION_MODELS];
|
|
943
764
|
}
|
|
944
765
|
getAvailableModels() {
|
|
945
766
|
return [];
|
|
946
767
|
}
|
|
947
768
|
getActiveLabels() {
|
|
948
|
-
return
|
|
769
|
+
return PLATE_TEXT_LABELS2;
|
|
949
770
|
}
|
|
950
771
|
async probe() {
|
|
951
772
|
return {
|