@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.
Files changed (136) hide show
  1. package/dist/addons/animal-classifier/index.d.mts +30 -0
  2. package/dist/addons/animal-classifier/index.d.ts +30 -0
  3. package/dist/addons/animal-classifier/index.js +822 -999
  4. package/dist/addons/animal-classifier/index.js.map +1 -1
  5. package/dist/addons/animal-classifier/index.mjs +7 -242
  6. package/dist/addons/animal-classifier/index.mjs.map +1 -1
  7. package/dist/addons/audio-classification/index.d.mts +36 -0
  8. package/dist/addons/audio-classification/index.d.ts +36 -0
  9. package/dist/addons/audio-classification/index.js +378 -501
  10. package/dist/addons/audio-classification/index.js.map +1 -1
  11. package/dist/addons/audio-classification/index.mjs +4 -224
  12. package/dist/addons/audio-classification/index.mjs.map +1 -1
  13. package/dist/addons/bird-global-classifier/index.d.mts +31 -0
  14. package/dist/addons/bird-global-classifier/index.d.ts +31 -0
  15. package/dist/addons/bird-global-classifier/index.js +825 -1002
  16. package/dist/addons/bird-global-classifier/index.js.map +1 -1
  17. package/dist/addons/bird-global-classifier/index.mjs +7 -248
  18. package/dist/addons/bird-global-classifier/index.mjs.map +1 -1
  19. package/dist/addons/bird-nabirds-classifier/index.d.mts +33 -0
  20. package/dist/addons/bird-nabirds-classifier/index.d.ts +33 -0
  21. package/dist/addons/bird-nabirds-classifier/index.js +825 -1002
  22. package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
  23. package/dist/addons/bird-nabirds-classifier/index.mjs +7 -289
  24. package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -1
  25. package/dist/addons/face-detection/index.d.mts +29 -0
  26. package/dist/addons/face-detection/index.d.ts +29 -0
  27. package/dist/addons/face-detection/index.js +934 -1196
  28. package/dist/addons/face-detection/index.js.map +1 -1
  29. package/dist/addons/face-detection/index.mjs +7 -227
  30. package/dist/addons/face-detection/index.mjs.map +1 -1
  31. package/dist/addons/face-recognition/index.d.mts +29 -0
  32. package/dist/addons/face-recognition/index.d.ts +29 -0
  33. package/dist/addons/face-recognition/index.js +807 -1003
  34. package/dist/addons/face-recognition/index.js.map +1 -1
  35. package/dist/addons/face-recognition/index.mjs +6 -197
  36. package/dist/addons/face-recognition/index.mjs.map +1 -1
  37. package/dist/addons/motion-detection/index.d.mts +28 -0
  38. package/dist/addons/motion-detection/index.d.ts +28 -0
  39. package/dist/addons/motion-detection/index.js +111 -214
  40. package/dist/addons/motion-detection/index.js.map +1 -1
  41. package/dist/addons/motion-detection/index.mjs +9 -12
  42. package/dist/addons/motion-detection/index.mjs.map +1 -1
  43. package/dist/addons/object-detection/index.d.mts +31 -0
  44. package/dist/addons/object-detection/index.d.ts +31 -0
  45. package/dist/addons/object-detection/index.js +1082 -1287
  46. package/dist/addons/object-detection/index.js.map +1 -1
  47. package/dist/addons/object-detection/index.mjs +7 -373
  48. package/dist/addons/object-detection/index.mjs.map +1 -1
  49. package/dist/addons/plate-detection/index.d.mts +30 -0
  50. package/dist/addons/plate-detection/index.d.ts +30 -0
  51. package/dist/addons/plate-detection/index.js +868 -1075
  52. package/dist/addons/plate-detection/index.js.map +1 -1
  53. package/dist/addons/plate-detection/index.mjs +7 -230
  54. package/dist/addons/plate-detection/index.mjs.map +1 -1
  55. package/dist/addons/plate-recognition/index.d.mts +31 -0
  56. package/dist/addons/plate-recognition/index.d.ts +31 -0
  57. package/dist/addons/plate-recognition/index.js +505 -684
  58. package/dist/addons/plate-recognition/index.js.map +1 -1
  59. package/dist/addons/plate-recognition/index.mjs +5 -244
  60. package/dist/addons/plate-recognition/index.mjs.map +1 -1
  61. package/dist/addons/segmentation-refiner/index.d.mts +30 -0
  62. package/dist/addons/segmentation-refiner/index.d.ts +30 -0
  63. package/dist/addons/segmentation-refiner/index.js +790 -967
  64. package/dist/addons/segmentation-refiner/index.js.map +1 -1
  65. package/dist/addons/segmentation-refiner/index.mjs +17 -21
  66. package/dist/addons/segmentation-refiner/index.mjs.map +1 -1
  67. package/dist/addons/vehicle-classifier/index.d.mts +31 -0
  68. package/dist/addons/vehicle-classifier/index.d.ts +31 -0
  69. package/dist/addons/vehicle-classifier/index.js +410 -581
  70. package/dist/addons/vehicle-classifier/index.js.map +1 -1
  71. package/dist/addons/vehicle-classifier/index.mjs +16 -20
  72. package/dist/addons/vehicle-classifier/index.mjs.map +1 -1
  73. package/dist/chunk-22BHCDT5.mjs +101 -0
  74. package/dist/{chunk-WG66JYYW.mjs.map → chunk-22BHCDT5.mjs.map} +1 -1
  75. package/dist/chunk-2IOKI4ES.mjs +335 -0
  76. package/dist/{chunk-PIFS7AIT.mjs.map → chunk-2IOKI4ES.mjs.map} +1 -1
  77. package/dist/chunk-7DYHXUPZ.mjs +36 -0
  78. package/dist/{chunk-BS4DKYGN.mjs.map → chunk-7DYHXUPZ.mjs.map} +1 -1
  79. package/dist/chunk-BJTO5JO5.mjs +11 -0
  80. package/dist/chunk-BP7H4NFS.mjs +412 -0
  81. package/dist/{chunk-MGT6RUVX.mjs.map → chunk-BP7H4NFS.mjs.map} +1 -1
  82. package/dist/chunk-BR2FPGOX.mjs +98 -0
  83. package/dist/{chunk-YYDM6V2F.mjs.map → chunk-BR2FPGOX.mjs.map} +1 -1
  84. package/dist/chunk-D6WEHN33.mjs +276 -0
  85. package/dist/chunk-D6WEHN33.mjs.map +1 -0
  86. package/dist/chunk-DRYFGARD.mjs +289 -0
  87. package/dist/chunk-DRYFGARD.mjs.map +1 -0
  88. package/dist/chunk-DUN6XU3N.mjs +72 -0
  89. package/dist/{chunk-XD7WGXHZ.mjs.map → chunk-DUN6XU3N.mjs.map} +1 -1
  90. package/dist/chunk-ESLHNWWE.mjs +387 -0
  91. package/dist/chunk-ESLHNWWE.mjs.map +1 -0
  92. package/dist/chunk-JUQEW6ON.mjs +256 -0
  93. package/dist/chunk-JUQEW6ON.mjs.map +1 -0
  94. package/dist/chunk-KUO2BVFY.mjs +90 -0
  95. package/dist/{chunk-DE7I3VHO.mjs.map → chunk-KUO2BVFY.mjs.map} +1 -1
  96. package/dist/chunk-R5J3WAUI.mjs +645 -0
  97. package/dist/chunk-R5J3WAUI.mjs.map +1 -0
  98. package/dist/chunk-XZ6ZMXXU.mjs +39 -0
  99. package/dist/{chunk-K36R6HWY.mjs.map → chunk-XZ6ZMXXU.mjs.map} +1 -1
  100. package/dist/chunk-YPU4WTXZ.mjs +269 -0
  101. package/dist/chunk-YPU4WTXZ.mjs.map +1 -0
  102. package/dist/chunk-YUCD2TFH.mjs +242 -0
  103. package/dist/chunk-YUCD2TFH.mjs.map +1 -0
  104. package/dist/chunk-ZTJENCFC.mjs +379 -0
  105. package/dist/chunk-ZTJENCFC.mjs.map +1 -0
  106. package/dist/chunk-ZWYXXCXP.mjs +248 -0
  107. package/dist/chunk-ZWYXXCXP.mjs.map +1 -0
  108. package/dist/index.d.mts +183 -0
  109. package/dist/index.d.ts +183 -0
  110. package/dist/index.js +3930 -4449
  111. package/dist/index.js.map +1 -1
  112. package/dist/index.mjs +250 -2698
  113. package/dist/index.mjs.map +1 -1
  114. package/package.json +5 -5
  115. package/dist/chunk-2YMA6QOV.mjs +0 -193
  116. package/dist/chunk-2YMA6QOV.mjs.map +0 -1
  117. package/dist/chunk-3IIFBJCD.mjs +0 -45
  118. package/dist/chunk-BS4DKYGN.mjs +0 -48
  119. package/dist/chunk-DE7I3VHO.mjs +0 -106
  120. package/dist/chunk-F6D2OZ36.mjs +0 -89
  121. package/dist/chunk-F6D2OZ36.mjs.map +0 -1
  122. package/dist/chunk-GAOIFQDX.mjs +0 -59
  123. package/dist/chunk-GAOIFQDX.mjs.map +0 -1
  124. package/dist/chunk-HUIX2XVR.mjs +0 -159
  125. package/dist/chunk-HUIX2XVR.mjs.map +0 -1
  126. package/dist/chunk-K36R6HWY.mjs +0 -51
  127. package/dist/chunk-MBTAI3WE.mjs +0 -78
  128. package/dist/chunk-MBTAI3WE.mjs.map +0 -1
  129. package/dist/chunk-MGT6RUVX.mjs +0 -423
  130. package/dist/chunk-PIFS7AIT.mjs +0 -446
  131. package/dist/chunk-WG66JYYW.mjs +0 -116
  132. package/dist/chunk-XD7WGXHZ.mjs +0 -82
  133. package/dist/chunk-YYDM6V2F.mjs +0 -113
  134. package/dist/chunk-ZK7P3TZN.mjs +0 -286
  135. package/dist/chunk-ZK7P3TZN.mjs.map +0 -1
  136. /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/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 ────────────────────────────────────────
46
- {
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
- ]
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
- 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
- ]
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
- // ── CRNN-MobileNetV3 (via OnnxTR/docTR) ─────────────────────────
99
- // Simple CNN+LSTM+CTC architecture — good CoreML compatibility (no dynamic ops)
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
- 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
- },
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
- 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
- ]
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.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;
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
- 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);
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.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
- }
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/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"
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
- var BACKEND_TO_DEVICE = {
341
- cpu: "cpu",
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
- cuda: "gpu-cuda",
344
- tensorrt: "tensorrt"
308
+ pytorch: "cpu",
309
+ openvino: "cpu",
310
+ tflite: "cpu"
345
311
  };
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;
312
+ this.device = runtimeDeviceMap[runtime];
401
313
  }
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;
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
- 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
- }
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
- async dispose() {
523
- if (this.process) {
524
- this.process.stdin?.end();
525
- this.process.kill("SIGTERM");
526
- this.process = null;
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
- exports2.PythonInferenceEngine = PythonInferenceEngine;
531
- async function resolvePythonBinary(configPath, deps) {
532
- if (configPath)
533
- return configPath;
534
- return deps.ensurePython();
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
- // 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;
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
- 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);
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
- function modelExists(filePath) {
611
- try {
612
- return fs2.existsSync(filePath);
613
- } catch {
614
- return false;
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
- 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`);
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
- 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)];
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 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"));
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 PLATE_TEXT_LABELS = [PLATE_TEXT_LABEL];
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 = path.join(modelsDir, name);
748
- if (fs.existsSync(dictPath)) {
749
- const lines = fs.readFileSync(dictPath, "utf-8").split("\n").filter((l) => l.length > 0);
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 import_plate_recognition_models.PLATE_RECOGNITION_MODELS.map((m) => ({
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 = import_plate_recognition_models.PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId);
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 (0, import_image_utils.cropRegion)(input.frame.data, input.roi);
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 (0, import_image_utils.resizeAndNormalize)(plateCrop, inputW, inputH, "zero-one", "nchw");
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 } = (0, import_paddleocr.ctcDecode)(output, seqLen, numChars, this.charset);
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 = import_plate_recognition_models.PLATE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
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 (0, import_engine_resolver.resolveEngine)({
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: [...import_plate_recognition_models.PLATE_RECOGNITION_MODELS],
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 [...import_plate_recognition_models.PLATE_RECOGNITION_MODELS];
763
+ return [...PLATE_RECOGNITION_MODELS];
943
764
  }
944
765
  getAvailableModels() {
945
766
  return [];
946
767
  }
947
768
  getActiveLabels() {
948
- return PLATE_TEXT_LABELS;
769
+ return PLATE_TEXT_LABELS2;
949
770
  }
950
771
  async probe() {
951
772
  return {