@camstack/addon-vision 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/addons/animal-classifier/index.js +999 -823
  2. package/dist/addons/animal-classifier/index.js.map +1 -1
  3. package/dist/addons/animal-classifier/index.mjs +242 -7
  4. package/dist/addons/animal-classifier/index.mjs.map +1 -1
  5. package/dist/addons/audio-classification/index.js +501 -379
  6. package/dist/addons/audio-classification/index.js.map +1 -1
  7. package/dist/addons/audio-classification/index.mjs +224 -4
  8. package/dist/addons/audio-classification/index.mjs.map +1 -1
  9. package/dist/addons/bird-global-classifier/index.js +1002 -826
  10. package/dist/addons/bird-global-classifier/index.js.map +1 -1
  11. package/dist/addons/bird-global-classifier/index.mjs +248 -7
  12. package/dist/addons/bird-global-classifier/index.mjs.map +1 -1
  13. package/dist/addons/bird-nabirds-classifier/index.js +1002 -826
  14. package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
  15. package/dist/addons/bird-nabirds-classifier/index.mjs +289 -7
  16. package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -1
  17. package/dist/addons/face-detection/index.js +1196 -935
  18. package/dist/addons/face-detection/index.js.map +1 -1
  19. package/dist/addons/face-detection/index.mjs +227 -7
  20. package/dist/addons/face-detection/index.mjs.map +1 -1
  21. package/dist/addons/face-recognition/index.js +1003 -808
  22. package/dist/addons/face-recognition/index.js.map +1 -1
  23. package/dist/addons/face-recognition/index.mjs +197 -6
  24. package/dist/addons/face-recognition/index.mjs.map +1 -1
  25. package/dist/addons/motion-detection/index.js +214 -111
  26. package/dist/addons/motion-detection/index.js.map +1 -1
  27. package/dist/addons/motion-detection/index.mjs +12 -9
  28. package/dist/addons/motion-detection/index.mjs.map +1 -1
  29. package/dist/addons/object-detection/index.js +1287 -1083
  30. package/dist/addons/object-detection/index.js.map +1 -1
  31. package/dist/addons/object-detection/index.mjs +373 -7
  32. package/dist/addons/object-detection/index.mjs.map +1 -1
  33. package/dist/addons/plate-detection/index.js +1075 -869
  34. package/dist/addons/plate-detection/index.js.map +1 -1
  35. package/dist/addons/plate-detection/index.mjs +230 -7
  36. package/dist/addons/plate-detection/index.mjs.map +1 -1
  37. package/dist/addons/plate-recognition/index.js +684 -506
  38. package/dist/addons/plate-recognition/index.js.map +1 -1
  39. package/dist/addons/plate-recognition/index.mjs +244 -5
  40. package/dist/addons/plate-recognition/index.mjs.map +1 -1
  41. package/dist/addons/segmentation-refiner/index.js +967 -791
  42. package/dist/addons/segmentation-refiner/index.js.map +1 -1
  43. package/dist/addons/segmentation-refiner/index.mjs +21 -17
  44. package/dist/addons/segmentation-refiner/index.mjs.map +1 -1
  45. package/dist/addons/vehicle-classifier/index.js +581 -411
  46. package/dist/addons/vehicle-classifier/index.js.map +1 -1
  47. package/dist/addons/vehicle-classifier/index.mjs +20 -16
  48. package/dist/addons/vehicle-classifier/index.mjs.map +1 -1
  49. package/dist/chunk-2YMA6QOV.mjs +193 -0
  50. package/dist/chunk-2YMA6QOV.mjs.map +1 -0
  51. package/dist/chunk-3IIFBJCD.mjs +45 -0
  52. package/dist/chunk-BS4DKYGN.mjs +48 -0
  53. package/dist/{chunk-7DYHXUPZ.mjs.map → chunk-BS4DKYGN.mjs.map} +1 -1
  54. package/dist/chunk-DE7I3VHO.mjs +106 -0
  55. package/dist/{chunk-KUO2BVFY.mjs.map → chunk-DE7I3VHO.mjs.map} +1 -1
  56. package/dist/chunk-F6D2OZ36.mjs +89 -0
  57. package/dist/chunk-F6D2OZ36.mjs.map +1 -0
  58. package/dist/chunk-GAOIFQDX.mjs +59 -0
  59. package/dist/chunk-GAOIFQDX.mjs.map +1 -0
  60. package/dist/chunk-HUIX2XVR.mjs +159 -0
  61. package/dist/chunk-HUIX2XVR.mjs.map +1 -0
  62. package/dist/chunk-K36R6HWY.mjs +51 -0
  63. package/dist/{chunk-XZ6ZMXXU.mjs.map → chunk-K36R6HWY.mjs.map} +1 -1
  64. package/dist/chunk-MBTAI3WE.mjs +78 -0
  65. package/dist/chunk-MBTAI3WE.mjs.map +1 -0
  66. package/dist/chunk-MGT6RUVX.mjs +423 -0
  67. package/dist/{chunk-BP7H4NFS.mjs.map → chunk-MGT6RUVX.mjs.map} +1 -1
  68. package/dist/chunk-PIFS7AIT.mjs +446 -0
  69. package/dist/chunk-PIFS7AIT.mjs.map +1 -0
  70. package/dist/chunk-WG66JYYW.mjs +116 -0
  71. package/dist/{chunk-22BHCDT5.mjs.map → chunk-WG66JYYW.mjs.map} +1 -1
  72. package/dist/chunk-XD7WGXHZ.mjs +82 -0
  73. package/dist/{chunk-DUN6XU3N.mjs.map → chunk-XD7WGXHZ.mjs.map} +1 -1
  74. package/dist/chunk-YYDM6V2F.mjs +113 -0
  75. package/dist/{chunk-BR2FPGOX.mjs.map → chunk-YYDM6V2F.mjs.map} +1 -1
  76. package/dist/chunk-ZK7P3TZN.mjs +286 -0
  77. package/dist/chunk-ZK7P3TZN.mjs.map +1 -0
  78. package/dist/index.js +4443 -3925
  79. package/dist/index.js.map +1 -1
  80. package/dist/index.mjs +2698 -250
  81. package/dist/index.mjs.map +1 -1
  82. package/package.json +2 -3
  83. package/dist/chunk-22BHCDT5.mjs +0 -101
  84. package/dist/chunk-6DJZZR64.mjs +0 -336
  85. package/dist/chunk-6DJZZR64.mjs.map +0 -1
  86. package/dist/chunk-7DYHXUPZ.mjs +0 -36
  87. package/dist/chunk-BJTO5JO5.mjs +0 -11
  88. package/dist/chunk-BP7H4NFS.mjs +0 -412
  89. package/dist/chunk-BR2FPGOX.mjs +0 -98
  90. package/dist/chunk-DNQNGDR4.mjs +0 -256
  91. package/dist/chunk-DNQNGDR4.mjs.map +0 -1
  92. package/dist/chunk-DUN6XU3N.mjs +0 -72
  93. package/dist/chunk-EPNWLSCG.mjs +0 -387
  94. package/dist/chunk-EPNWLSCG.mjs.map +0 -1
  95. package/dist/chunk-G32RCIUI.mjs +0 -645
  96. package/dist/chunk-G32RCIUI.mjs.map +0 -1
  97. package/dist/chunk-GR65KM6X.mjs +0 -289
  98. package/dist/chunk-GR65KM6X.mjs.map +0 -1
  99. package/dist/chunk-H7LMBTS5.mjs +0 -276
  100. package/dist/chunk-H7LMBTS5.mjs.map +0 -1
  101. package/dist/chunk-IK4XIQPC.mjs +0 -242
  102. package/dist/chunk-IK4XIQPC.mjs.map +0 -1
  103. package/dist/chunk-J6VNIIYX.mjs +0 -269
  104. package/dist/chunk-J6VNIIYX.mjs.map +0 -1
  105. package/dist/chunk-KUO2BVFY.mjs +0 -90
  106. package/dist/chunk-ML2JX43J.mjs +0 -248
  107. package/dist/chunk-ML2JX43J.mjs.map +0 -1
  108. package/dist/chunk-WUMV524J.mjs +0 -379
  109. package/dist/chunk-WUMV524J.mjs.map +0 -1
  110. package/dist/chunk-XZ6ZMXXU.mjs +0 -39
  111. /package/dist/{chunk-BJTO5JO5.mjs.map → chunk-3IIFBJCD.mjs.map} +0 -0
@@ -5,6 +5,9 @@ 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
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,536 +30,711 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
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
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: [
33
+ // src/catalogs/plate-recognition-models.js
34
+ var require_plate_recognition_models = __commonJS({
35
+ "src/catalogs/plate-recognition-models.js"(exports2) {
36
+ "use strict";
37
+ Object.defineProperty(exports2, "__esModule", { value: true });
38
+ exports2.PLATE_RECOGNITION_MODELS = void 0;
39
+ var types_1 = require("@camstack/types");
40
+ var HF_REPO = "camstack/camstack-models";
41
+ var PLATE_TEXT_LABELS2 = [
42
+ { id: "text", name: "Plate Text" }
43
+ ];
44
+ exports2.PLATE_RECOGNITION_MODELS = [
45
+ // ── PaddleOCR PP-OCRv5 ────────────────────────────────────────
65
46
  {
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
47
+ id: "paddleocr-latin",
48
+ name: "PaddleOCR Latin",
49
+ description: "PaddleOCR PP-OCRv5 recognition model for Latin-script license plates",
50
+ inputSize: { width: 320, height: 48 },
51
+ labels: PLATE_TEXT_LABELS2,
52
+ formats: {
53
+ // ONNX only — PaddleOCR has dynamic dimensions incompatible with CoreML native conversion.
54
+ // On Apple Silicon, ONNX Runtime uses CoreML EP automatically for acceleration.
55
+ onnx: {
56
+ url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-latin-rec.onnx"),
57
+ sizeMB: 7.5
58
+ },
59
+ openvino: {
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
+ ]
82
72
  },
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: [
90
73
  {
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: [
74
+ id: "paddleocr-en",
75
+ name: "PaddleOCR English",
76
+ description: "PaddleOCR PP-OCRv5 recognition model optimized for English license plates",
77
+ inputSize: { width: 320, height: 48 },
78
+ labels: PLATE_TEXT_LABELS2,
79
+ formats: {
80
+ onnx: {
81
+ url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/onnx/camstack-paddleocr-en-rec.onnx"),
82
+ sizeMB: 7.5
83
+ },
84
+ openvino: {
85
+ url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/paddleocr/openvino/camstack-paddleocr-en.xml"),
86
+ sizeMB: 4,
87
+ runtimes: ["python"]
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
+ ]
97
+ },
98
+ // ── CRNN-MobileNetV3 (via OnnxTR/docTR) ─────────────────────────
99
+ // Simple CNN+LSTM+CTC architecture — good CoreML compatibility (no dynamic ops)
112
100
  {
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
116
- }
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: [
101
+ id: "crnn-mobilenet-v3-small",
102
+ name: "CRNN MobileNet V3 Small",
103
+ description: "CRNN MobileNetV3-Small \u2014 lightweight text recognition, CoreML compatible via OnnxTR",
104
+ inputSize: { width: 128, height: 32 },
105
+ labels: PLATE_TEXT_LABELS2,
106
+ formats: {
107
+ onnx: {
108
+ url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/onnx/camstack-crnn-mobilenet-v3-small.onnx"),
109
+ sizeMB: 8
110
+ }
111
+ },
112
+ extraFiles: [
113
+ {
114
+ url: (0, types_1.hfModelUrl)(HF_REPO, "plateRecognition/crnn-mobilenet/camstack-crnn-mobilenet-charset.txt"),
115
+ filename: "camstack-crnn-mobilenet-charset.txt",
116
+ sizeMB: 0.01
117
+ }
118
+ ]
119
+ },
132
120
  {
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
121
+ id: "crnn-mobilenet-v3-large",
122
+ name: "CRNN MobileNet V3 Large",
123
+ description: "CRNN MobileNetV3-Large \u2014 higher accuracy text recognition, CoreML compatible",
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
+ ]
136
139
  }
137
- ]
140
+ ];
138
141
  }
139
- ];
142
+ });
140
143
 
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];
169
- }
170
- float32[c * numPixels + i] = val;
144
+ // src/shared/image-utils.js
145
+ var require_image_utils = __commonJS({
146
+ "src/shared/image-utils.js"(exports2) {
147
+ "use strict";
148
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
149
+ return mod && mod.__esModule ? mod : { "default": mod };
150
+ };
151
+ Object.defineProperty(exports2, "__esModule", { value: true });
152
+ exports2.jpegToRgb = jpegToRgb;
153
+ exports2.cropRegion = cropRegion2;
154
+ exports2.letterbox = letterbox;
155
+ exports2.resizeAndNormalize = resizeAndNormalize2;
156
+ exports2.rgbToGrayscale = rgbToGrayscale;
157
+ var sharp_1 = __importDefault(require("sharp"));
158
+ async function jpegToRgb(jpeg) {
159
+ const { data, info } = await (0, sharp_1.default)(jpeg).removeAlpha().raw().toBuffer({ resolveWithObject: true });
160
+ return { data, width: info.width, height: info.height };
161
+ }
162
+ async function cropRegion2(jpeg, roi) {
163
+ return (0, sharp_1.default)(jpeg).extract({
164
+ left: Math.round(roi.x),
165
+ top: Math.round(roi.y),
166
+ width: Math.round(roi.w),
167
+ height: Math.round(roi.h)
168
+ }).jpeg().toBuffer();
169
+ }
170
+ async function letterbox(jpeg, targetSize) {
171
+ const meta = await (0, sharp_1.default)(jpeg).metadata();
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;
171
193
  }
194
+ return { data: float32, scale, padX, padY, originalWidth, originalHeight };
172
195
  }
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];
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
+ }
185
233
  }
186
- float32[i * 3 + c] = val;
187
234
  }
235
+ return float32;
188
236
  }
189
- }
190
- return float32;
191
- }
192
-
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;
237
+ function rgbToGrayscale(rgb, width, height) {
238
+ const numPixels = width * height;
239
+ const gray = new Uint8Array(numPixels);
240
+ for (let i = 0; i < numPixels; i++) {
241
+ const r = rgb[i * 3];
242
+ const g = rgb[i * 3 + 1];
243
+ const b = rgb[i * 3 + 2];
244
+ gray[i] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
206
245
  }
207
- }
208
- rawIndices.push(bestIdx);
209
- totalLogScore += bestVal;
210
- }
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);
246
+ return gray;
216
247
  }
217
248
  }
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
- }
223
-
224
- // src/shared/engine-resolver.ts
225
- var fs = __toESM(require("fs"));
226
- var path2 = __toESM(require("path"));
249
+ });
227
250
 
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]
258
- };
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;
251
+ // src/shared/postprocess/paddleocr.js
252
+ var require_paddleocr = __commonJS({
253
+ "src/shared/postprocess/paddleocr.js"(exports2) {
254
+ "use strict";
255
+ Object.defineProperty(exports2, "__esModule", { value: true });
256
+ exports2.ctcDecode = ctcDecode2;
257
+ function ctcDecode2(output, seqLen, numChars, charset) {
258
+ let totalLogScore = 0;
259
+ const rawIndices = [];
260
+ for (let t = 0; t < seqLen; t++) {
261
+ const offset = t * numChars;
262
+ let bestIdx = 0;
263
+ let bestVal = output[offset];
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
+ }
280
+ }
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 };
288
285
  }
289
- return out;
290
286
  }
291
- async dispose() {
292
- this.session = null;
293
- }
294
- };
287
+ });
295
288
 
296
- // src/shared/python-engine.ts
297
- var import_node_child_process = require("child_process");
298
- var import_core = require("@camstack/core");
299
- var PythonInferenceEngine = class {
300
- constructor(pythonPath, scriptPath, runtime, modelPath, extraArgs = []) {
301
- this.pythonPath = pythonPath;
302
- this.scriptPath = scriptPath;
303
- this.modelPath = modelPath;
304
- this.extraArgs = extraArgs;
305
- this.runtime = runtime;
306
- const runtimeDeviceMap = {
307
- onnx: "cpu",
289
+ // src/shared/node-engine.js
290
+ var require_node_engine = __commonJS({
291
+ "src/shared/node-engine.js"(exports2) {
292
+ "use strict";
293
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
294
+ if (k2 === void 0) k2 = k;
295
+ var desc = Object.getOwnPropertyDescriptor(m, k);
296
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
297
+ desc = { enumerable: true, get: function() {
298
+ return m[k];
299
+ } };
300
+ }
301
+ Object.defineProperty(o, k2, desc);
302
+ }) : (function(o, m, k, k2) {
303
+ if (k2 === void 0) k2 = k;
304
+ o[k2] = m[k];
305
+ }));
306
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
307
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
308
+ }) : function(o, v) {
309
+ o["default"] = v;
310
+ });
311
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
312
+ var ownKeys = function(o) {
313
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
314
+ var ar = [];
315
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
316
+ return ar;
317
+ };
318
+ return ownKeys(o);
319
+ };
320
+ return function(mod) {
321
+ if (mod && mod.__esModule) return mod;
322
+ var result = {};
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"
339
+ };
340
+ var BACKEND_TO_DEVICE = {
341
+ cpu: "cpu",
308
342
  coreml: "gpu-mps",
309
- pytorch: "cpu",
310
- openvino: "cpu",
311
- tflite: "cpu"
343
+ cuda: "gpu-cuda",
344
+ tensorrt: "tensorrt"
312
345
  };
313
- this.device = runtimeDeviceMap[runtime];
346
+ var NodeInferenceEngine = class {
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;
314
401
  }
315
- runtime;
316
- device;
317
- process = null;
318
- receiveBuffer = Buffer.alloc(0);
319
- pendingResolve = null;
320
- pendingReject = null;
321
- async initialize() {
322
- const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
323
- this.process = (0, import_node_child_process.spawn)(this.pythonPath, args, {
324
- stdio: ["pipe", "pipe", "pipe"]
325
- });
326
- if (!this.process.stdout || !this.process.stdin) {
327
- throw new Error("PythonInferenceEngine: failed to create process pipes");
328
- }
329
- this.process.stderr?.on("data", (chunk) => {
330
- process.stderr.write(`[python-engine] ${chunk.toString()}`);
331
- });
332
- this.process.on("error", (err) => {
333
- this.pendingReject?.(err);
334
- this.pendingReject = null;
335
- this.pendingResolve = null;
336
- });
337
- this.process.on("exit", (code) => {
338
- if (code !== 0) {
339
- const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
340
- this.pendingReject?.(err);
341
- this.pendingReject = null;
402
+ });
403
+
404
+ // src/shared/python-engine.js
405
+ var require_python_engine = __commonJS({
406
+ "src/shared/python-engine.js"(exports2) {
407
+ "use strict";
408
+ Object.defineProperty(exports2, "__esModule", { value: true });
409
+ exports2.PythonInferenceEngine = void 0;
410
+ exports2.resolvePythonBinary = resolvePythonBinary;
411
+ var node_child_process_1 = require("child_process");
412
+ var PythonInferenceEngine = class {
413
+ pythonPath;
414
+ scriptPath;
415
+ modelPath;
416
+ extraArgs;
417
+ runtime;
418
+ device;
419
+ process = null;
420
+ receiveBuffer = Buffer.alloc(0);
421
+ pendingResolve = null;
422
+ pendingReject = null;
423
+ constructor(pythonPath, scriptPath, runtime, modelPath, extraArgs = []) {
424
+ this.pythonPath = pythonPath;
425
+ this.scriptPath = scriptPath;
426
+ this.modelPath = modelPath;
427
+ this.extraArgs = extraArgs;
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;
342
490
  this.pendingResolve = null;
491
+ this.pendingReject = null;
492
+ if (!resolve)
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
+ }
343
500
  }
344
- });
345
- this.process.stdout.on("data", (chunk) => {
346
- this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
347
- this._tryReceive();
348
- });
349
- await new Promise((resolve2, reject) => {
350
- const timeout = setTimeout(() => resolve2(), 2e3);
351
- this.process?.on("error", (err) => {
352
- clearTimeout(timeout);
353
- reject(err);
354
- });
355
- this.process?.on("exit", (code) => {
356
- clearTimeout(timeout);
357
- if (code !== 0) {
358
- reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
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");
359
505
  }
360
- });
361
- });
362
- }
363
- _tryReceive() {
364
- if (this.receiveBuffer.length < 4) return;
365
- const length = this.receiveBuffer.readUInt32LE(0);
366
- if (this.receiveBuffer.length < 4 + length) return;
367
- const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
368
- this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
369
- const resolve2 = this.pendingResolve;
370
- const reject = this.pendingReject;
371
- this.pendingResolve = null;
372
- this.pendingReject = null;
373
- if (!resolve2) return;
374
- try {
375
- const parsed = JSON.parse(jsonBytes.toString("utf8"));
376
- resolve2(parsed);
377
- } catch (err) {
378
- reject?.(err instanceof Error ? err : new Error(String(err)));
379
- }
380
- }
381
- /** Send JPEG buffer, receive JSON detection results */
382
- async runJpeg(jpeg) {
383
- if (!this.process?.stdin) {
384
- throw new Error("PythonInferenceEngine: process not initialized");
385
- }
386
- return new Promise((resolve2, reject) => {
387
- this.pendingResolve = resolve2;
388
- this.pendingReject = reject;
389
- const lengthBuf = Buffer.allocUnsafe(4);
390
- lengthBuf.writeUInt32LE(jpeg.length, 0);
391
- this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
392
- });
393
- }
394
- /** IInferenceEngine.run — wraps runJpeg for compatibility */
395
- async run(_input, _inputShape) {
396
- throw new Error(
397
- "PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input"
398
- );
399
- }
400
- /** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
401
- async runMultiOutput(_input, _inputShape) {
402
- throw new Error(
403
- "PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input"
404
- );
405
- }
406
- async dispose() {
407
- if (this.process) {
408
- this.process.stdin?.end();
409
- this.process.kill("SIGTERM");
410
- this.process = null;
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");
521
+ }
522
+ async dispose() {
523
+ if (this.process) {
524
+ this.process.stdin?.end();
525
+ this.process.kill("SIGTERM");
526
+ this.process = null;
527
+ }
528
+ }
529
+ };
530
+ exports2.PythonInferenceEngine = PythonInferenceEngine;
531
+ async function resolvePythonBinary(configPath, deps) {
532
+ if (configPath)
533
+ return configPath;
534
+ return deps.ensurePython();
411
535
  }
412
536
  }
413
- };
537
+ });
414
538
 
415
- // src/shared/engine-resolver.ts
416
- var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
417
- var BACKEND_TO_FORMAT = {
418
- cpu: "onnx",
419
- coreml: "onnx",
420
- cuda: "onnx",
421
- tensorrt: "onnx"
422
- };
423
- var RUNTIME_TO_FORMAT = {
424
- onnx: "onnx",
425
- coreml: "coreml",
426
- openvino: "openvino",
427
- tflite: "tflite",
428
- pytorch: "pt"
429
- };
430
- function modelFilePath(modelsDir, modelEntry, format) {
431
- const formatEntry = modelEntry.formats[format];
432
- if (!formatEntry) {
433
- throw new Error(`Model ${modelEntry.id} has no ${format} format`);
434
- }
435
- const urlParts = formatEntry.url.split("/");
436
- const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
437
- return path2.join(modelsDir, filename);
438
- }
439
- function modelExists(filePath) {
440
- try {
441
- return fs.existsSync(filePath);
442
- } catch {
443
- return false;
444
- }
445
- }
446
- async function resolveEngine(options) {
447
- const { runtime, backend, modelEntry, modelsDir, models } = options;
448
- let selectedFormat;
449
- let selectedBackend;
450
- if (runtime === "auto") {
451
- const available = await probeOnnxBackends();
452
- let chosen = null;
453
- for (const b of AUTO_BACKEND_PRIORITY) {
454
- if (!available.includes(b)) continue;
455
- const fmt = BACKEND_TO_FORMAT[b];
456
- if (!fmt) continue;
457
- if (!modelEntry.formats[fmt]) continue;
458
- chosen = { backend: b, format: fmt };
459
- break;
460
- }
461
- if (!chosen) {
462
- throw new Error(
463
- `resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`
464
- );
465
- }
466
- selectedFormat = chosen.format;
467
- selectedBackend = chosen.backend;
468
- } else {
469
- const fmt = RUNTIME_TO_FORMAT[runtime];
470
- if (!fmt) {
471
- throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
472
- }
473
- if (!modelEntry.formats[fmt]) {
474
- throw new Error(
475
- `resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`
476
- );
539
+ // src/shared/engine-resolver.js
540
+ var require_engine_resolver = __commonJS({
541
+ "src/shared/engine-resolver.js"(exports2) {
542
+ "use strict";
543
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
544
+ if (k2 === void 0) k2 = k;
545
+ var desc = Object.getOwnPropertyDescriptor(m, k);
546
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
547
+ desc = { enumerable: true, get: function() {
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;
560
+ });
561
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
562
+ var ownKeys = function(o) {
563
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
564
+ var ar = [];
565
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
566
+ return ar;
567
+ };
568
+ return ownKeys(o);
569
+ };
570
+ return function(mod) {
571
+ if (mod && mod.__esModule) return mod;
572
+ var result = {};
573
+ if (mod != null) {
574
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
575
+ }
576
+ __setModuleDefault(result, mod);
577
+ return result;
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);
477
609
  }
478
- selectedFormat = fmt;
479
- selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
480
- }
481
- let modelPath;
482
- if (models) {
483
- modelPath = await models.ensure(modelEntry.id, selectedFormat);
484
- } else {
485
- modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
486
- if (!modelExists(modelPath)) {
487
- throw new Error(
488
- `resolveEngine: model file not found at ${modelPath} and no model service provided`
489
- );
610
+ function modelExists(filePath) {
611
+ try {
612
+ return fs2.existsSync(filePath);
613
+ } catch {
614
+ return false;
615
+ }
490
616
  }
491
- }
492
- if (selectedFormat === "onnx") {
493
- const engine = new NodeInferenceEngine(modelPath, selectedBackend);
494
- await engine.initialize();
495
- return { engine, format: selectedFormat, modelPath };
496
- }
497
- const { pythonPath } = options;
498
- const PYTHON_SCRIPT_MAP = {
499
- coreml: "coreml_inference.py",
500
- pytorch: "pytorch_inference.py",
501
- openvino: "openvino_inference.py"
502
- };
503
- const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
504
- const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
505
- if (scriptName && pythonPath) {
506
- const candidates = [
507
- path2.join(__dirname, "../../python", scriptName),
508
- path2.join(__dirname, "../python", scriptName),
509
- path2.join(__dirname, "../../../python", scriptName)
510
- ];
511
- const scriptPath = candidates.find((p) => fs.existsSync(p));
512
- if (!scriptPath) {
513
- throw new Error(
514
- `resolveEngine: Python script "${scriptName}" not found. Searched:
515
- ${candidates.join("\n")}`
516
- );
617
+ async function resolveEngine2(options) {
618
+ const { runtime, backend, modelEntry, modelsDir, models } = options;
619
+ let selectedFormat;
620
+ let selectedBackend;
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`);
517
699
  }
518
- const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
519
- const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
520
- `--input-size=${inputSize}`,
521
- `--confidence=0.25`
522
- ]);
523
- await engine.initialize();
524
- return { engine, format: selectedFormat, modelPath };
525
- }
526
- const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
527
- if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
528
- const engine = new NodeInferenceEngine(fallbackPath, "cpu");
529
- await engine.initialize();
530
- return { engine, format: "onnx", modelPath: fallbackPath };
531
- }
532
- throw new Error(
533
- `resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`
534
- );
535
- }
536
- async function probeOnnxBackends() {
537
- const available = ["cpu"];
538
- try {
539
- const ort = await import("onnxruntime-node");
540
- const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
541
- for (const p of providers) {
542
- const normalized = p.toLowerCase().replace("executionprovider", "");
543
- if (normalized === "coreml") available.push("coreml");
544
- else if (normalized === "cuda") available.push("cuda");
545
- else if (normalized === "tensorrt") available.push("tensorrt");
700
+ async function probeOnnxBackends() {
701
+ const available = ["cpu"];
702
+ try {
703
+ const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
704
+ const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
705
+ for (const p of providers) {
706
+ const normalized = p.toLowerCase().replace("executionprovider", "");
707
+ if (normalized === "coreml")
708
+ available.push("coreml");
709
+ else if (normalized === "cuda")
710
+ available.push("cuda");
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)];
546
720
  }
547
- } catch {
548
- }
549
- if (process.platform === "darwin" && !available.includes("coreml")) {
550
- available.push("coreml");
551
721
  }
552
- return [...new Set(available)];
553
- }
722
+ });
554
723
 
555
724
  // src/addons/plate-recognition/index.ts
556
- var fs2 = __toESM(require("fs"));
557
- var path3 = __toESM(require("path"));
725
+ var plate_recognition_exports = {};
726
+ __export(plate_recognition_exports, {
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"));
558
736
  var PLATE_TEXT_LABEL = { id: "plate-text", name: "Plate Text" };
559
- var PLATE_TEXT_LABELS2 = [PLATE_TEXT_LABEL];
737
+ var PLATE_TEXT_LABELS = [PLATE_TEXT_LABEL];
560
738
  var PLATE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
561
739
  function loadCharset(modelsDir, modelId) {
562
740
  const dictNames = [
@@ -566,9 +744,9 @@ function loadCharset(modelsDir, modelId) {
566
744
  `camstack-crnn-mobilenet-charset.txt`
567
745
  ];
568
746
  for (const name of dictNames) {
569
- const dictPath = path3.join(modelsDir, name);
570
- if (fs2.existsSync(dictPath)) {
571
- const lines = fs2.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
747
+ const dictPath = path.join(modelsDir, name);
748
+ if (fs.existsSync(dictPath)) {
749
+ const lines = fs.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
572
750
  return ["", ...lines, " "];
573
751
  }
574
752
  }
@@ -614,7 +792,7 @@ var PlateRecognitionAddon = class {
614
792
  "paddleocr-latin": { ram: 100, accuracy: 80 },
615
793
  "paddleocr-en": { ram: 100, accuracy: 80 }
616
794
  };
617
- return PLATE_RECOGNITION_MODELS.map((m) => ({
795
+ return import_plate_recognition_models.PLATE_RECOGNITION_MODELS.map((m) => ({
618
796
  modelId: m.id,
619
797
  name: m.name,
620
798
  minRAM_MB: scores[m.id]?.ram ?? 100,
@@ -630,7 +808,7 @@ var PlateRecognitionAddon = class {
630
808
  const cfg = ctx.addonConfig;
631
809
  const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "paddleocr-latin";
632
810
  this.minConfidence = cfg["minConfidence"] ?? 0.5;
633
- const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
811
+ const entry = import_plate_recognition_models.PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
634
812
  if (!entry) {
635
813
  throw new Error(`PlateRecognitionAddon: unknown modelId "${modelId}"`);
636
814
  }
@@ -641,17 +819,17 @@ var PlateRecognitionAddon = class {
641
819
  const start = Date.now();
642
820
  const { width: inputW, height: inputH } = this.modelEntry.inputSize;
643
821
  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}`);
644
- const plateCrop = await cropRegion(input.frame.data, input.roi);
822
+ const plateCrop = await (0, import_image_utils.cropRegion)(input.frame.data, input.roi);
645
823
  console.log(`[plate-recognition] Crop size: ${plateCrop.length} bytes`);
646
824
  try {
647
825
  require("fs").writeFileSync("/tmp/plate-recognition-crop.jpg", plateCrop);
648
826
  } catch {
649
827
  }
650
- const normalized = await resizeAndNormalize(plateCrop, inputW, inputH, "zero-one", "nchw");
828
+ const normalized = await (0, import_image_utils.resizeAndNormalize)(plateCrop, inputW, inputH, "zero-one", "nchw");
651
829
  const output = await this.engine.run(normalized, [1, 3, inputH, inputW]);
652
830
  const numChars = this.charset.length;
653
831
  const seqLen = output.length / numChars;
654
- const { text, confidence } = ctcDecode(output, seqLen, numChars, this.charset);
832
+ const { text, confidence } = (0, import_paddleocr.ctcDecode)(output, seqLen, numChars, this.charset);
655
833
  return {
656
834
  classifications: [
657
835
  {
@@ -670,14 +848,14 @@ var PlateRecognitionAddon = class {
670
848
  const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
671
849
  const backend = config?.backend ?? "cpu";
672
850
  const format = config?.format ?? "onnx";
673
- const entry = PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
851
+ const entry = import_plate_recognition_models.PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
674
852
  this.modelEntry = entry;
675
853
  const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
676
854
  if (this.ctx.models) {
677
855
  await this.ctx.models.ensure(modelId, format);
678
856
  }
679
857
  this.charset = loadCharset(modelsDir, modelId);
680
- const resolved = await resolveEngine({
858
+ const resolved = await (0, import_engine_resolver.resolveEngine)({
681
859
  runtime,
682
860
  backend,
683
861
  modelEntry: entry,
@@ -701,7 +879,7 @@ var PlateRecognitionAddon = class {
701
879
  key: "modelId",
702
880
  label: "Model",
703
881
  type: "model-selector",
704
- catalog: [...PLATE_RECOGNITION_MODELS],
882
+ catalog: [...import_plate_recognition_models.PLATE_RECOGNITION_MODELS],
705
883
  allowCustom: false,
706
884
  allowConversion: false,
707
885
  acceptFormats: ["onnx", "openvino"],
@@ -761,13 +939,13 @@ var PlateRecognitionAddon = class {
761
939
  return PLATE_REC_CLASS_MAP;
762
940
  }
763
941
  getModelCatalog() {
764
- return [...PLATE_RECOGNITION_MODELS];
942
+ return [...import_plate_recognition_models.PLATE_RECOGNITION_MODELS];
765
943
  }
766
944
  getAvailableModels() {
767
945
  return [];
768
946
  }
769
947
  getActiveLabels() {
770
- return PLATE_TEXT_LABELS2;
948
+ return PLATE_TEXT_LABELS;
771
949
  }
772
950
  async probe() {
773
951
  return {