@camstack/addon-vision 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addons/animal-classifier/index.d.mts +30 -0
- package/dist/addons/animal-classifier/index.d.ts +30 -0
- package/dist/addons/animal-classifier/index.js +822 -999
- package/dist/addons/animal-classifier/index.js.map +1 -1
- package/dist/addons/animal-classifier/index.mjs +7 -242
- package/dist/addons/animal-classifier/index.mjs.map +1 -1
- package/dist/addons/audio-classification/index.d.mts +36 -0
- package/dist/addons/audio-classification/index.d.ts +36 -0
- package/dist/addons/audio-classification/index.js +378 -501
- package/dist/addons/audio-classification/index.js.map +1 -1
- package/dist/addons/audio-classification/index.mjs +4 -224
- package/dist/addons/audio-classification/index.mjs.map +1 -1
- package/dist/addons/bird-global-classifier/index.d.mts +31 -0
- package/dist/addons/bird-global-classifier/index.d.ts +31 -0
- package/dist/addons/bird-global-classifier/index.js +825 -1002
- package/dist/addons/bird-global-classifier/index.js.map +1 -1
- package/dist/addons/bird-global-classifier/index.mjs +7 -248
- package/dist/addons/bird-global-classifier/index.mjs.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.d.mts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.d.ts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.js +825 -1002
- package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.mjs +7 -289
- package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -1
- package/dist/addons/face-detection/index.d.mts +29 -0
- package/dist/addons/face-detection/index.d.ts +29 -0
- package/dist/addons/face-detection/index.js +934 -1196
- package/dist/addons/face-detection/index.js.map +1 -1
- package/dist/addons/face-detection/index.mjs +7 -227
- package/dist/addons/face-detection/index.mjs.map +1 -1
- package/dist/addons/face-recognition/index.d.mts +29 -0
- package/dist/addons/face-recognition/index.d.ts +29 -0
- package/dist/addons/face-recognition/index.js +807 -1003
- package/dist/addons/face-recognition/index.js.map +1 -1
- package/dist/addons/face-recognition/index.mjs +6 -197
- package/dist/addons/face-recognition/index.mjs.map +1 -1
- package/dist/addons/motion-detection/index.d.mts +28 -0
- package/dist/addons/motion-detection/index.d.ts +28 -0
- package/dist/addons/motion-detection/index.js +111 -214
- package/dist/addons/motion-detection/index.js.map +1 -1
- package/dist/addons/motion-detection/index.mjs +9 -12
- package/dist/addons/motion-detection/index.mjs.map +1 -1
- package/dist/addons/object-detection/index.d.mts +31 -0
- package/dist/addons/object-detection/index.d.ts +31 -0
- package/dist/addons/object-detection/index.js +1082 -1287
- package/dist/addons/object-detection/index.js.map +1 -1
- package/dist/addons/object-detection/index.mjs +7 -373
- package/dist/addons/object-detection/index.mjs.map +1 -1
- package/dist/addons/plate-detection/index.d.mts +30 -0
- package/dist/addons/plate-detection/index.d.ts +30 -0
- package/dist/addons/plate-detection/index.js +868 -1075
- package/dist/addons/plate-detection/index.js.map +1 -1
- package/dist/addons/plate-detection/index.mjs +7 -230
- package/dist/addons/plate-detection/index.mjs.map +1 -1
- package/dist/addons/plate-recognition/index.d.mts +31 -0
- package/dist/addons/plate-recognition/index.d.ts +31 -0
- package/dist/addons/plate-recognition/index.js +505 -684
- package/dist/addons/plate-recognition/index.js.map +1 -1
- package/dist/addons/plate-recognition/index.mjs +5 -244
- package/dist/addons/plate-recognition/index.mjs.map +1 -1
- package/dist/addons/segmentation-refiner/index.d.mts +30 -0
- package/dist/addons/segmentation-refiner/index.d.ts +30 -0
- package/dist/addons/segmentation-refiner/index.js +790 -967
- package/dist/addons/segmentation-refiner/index.js.map +1 -1
- package/dist/addons/segmentation-refiner/index.mjs +17 -21
- package/dist/addons/segmentation-refiner/index.mjs.map +1 -1
- package/dist/addons/vehicle-classifier/index.d.mts +31 -0
- package/dist/addons/vehicle-classifier/index.d.ts +31 -0
- package/dist/addons/vehicle-classifier/index.js +410 -581
- package/dist/addons/vehicle-classifier/index.js.map +1 -1
- package/dist/addons/vehicle-classifier/index.mjs +16 -20
- package/dist/addons/vehicle-classifier/index.mjs.map +1 -1
- package/dist/chunk-22BHCDT5.mjs +101 -0
- package/dist/{chunk-WG66JYYW.mjs.map → chunk-22BHCDT5.mjs.map} +1 -1
- package/dist/chunk-2IOKI4ES.mjs +335 -0
- package/dist/{chunk-PIFS7AIT.mjs.map → chunk-2IOKI4ES.mjs.map} +1 -1
- package/dist/chunk-7DYHXUPZ.mjs +36 -0
- package/dist/{chunk-BS4DKYGN.mjs.map → chunk-7DYHXUPZ.mjs.map} +1 -1
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BP7H4NFS.mjs +412 -0
- package/dist/{chunk-MGT6RUVX.mjs.map → chunk-BP7H4NFS.mjs.map} +1 -1
- package/dist/chunk-BR2FPGOX.mjs +98 -0
- package/dist/{chunk-YYDM6V2F.mjs.map → chunk-BR2FPGOX.mjs.map} +1 -1
- package/dist/chunk-D6WEHN33.mjs +276 -0
- package/dist/chunk-D6WEHN33.mjs.map +1 -0
- package/dist/chunk-DRYFGARD.mjs +289 -0
- package/dist/chunk-DRYFGARD.mjs.map +1 -0
- package/dist/chunk-DUN6XU3N.mjs +72 -0
- package/dist/{chunk-XD7WGXHZ.mjs.map → chunk-DUN6XU3N.mjs.map} +1 -1
- package/dist/chunk-ESLHNWWE.mjs +387 -0
- package/dist/chunk-ESLHNWWE.mjs.map +1 -0
- package/dist/chunk-JUQEW6ON.mjs +256 -0
- package/dist/chunk-JUQEW6ON.mjs.map +1 -0
- package/dist/chunk-KUO2BVFY.mjs +90 -0
- package/dist/{chunk-DE7I3VHO.mjs.map → chunk-KUO2BVFY.mjs.map} +1 -1
- package/dist/chunk-R5J3WAUI.mjs +645 -0
- package/dist/chunk-R5J3WAUI.mjs.map +1 -0
- package/dist/chunk-XZ6ZMXXU.mjs +39 -0
- package/dist/{chunk-K36R6HWY.mjs.map → chunk-XZ6ZMXXU.mjs.map} +1 -1
- package/dist/chunk-YPU4WTXZ.mjs +269 -0
- package/dist/chunk-YPU4WTXZ.mjs.map +1 -0
- package/dist/chunk-YUCD2TFH.mjs +242 -0
- package/dist/chunk-YUCD2TFH.mjs.map +1 -0
- package/dist/chunk-ZTJENCFC.mjs +379 -0
- package/dist/chunk-ZTJENCFC.mjs.map +1 -0
- package/dist/chunk-ZWYXXCXP.mjs +248 -0
- package/dist/chunk-ZWYXXCXP.mjs.map +1 -0
- package/dist/index.d.mts +183 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +3930 -4449
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -2698
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-2YMA6QOV.mjs +0 -193
- package/dist/chunk-2YMA6QOV.mjs.map +0 -1
- package/dist/chunk-3IIFBJCD.mjs +0 -45
- package/dist/chunk-BS4DKYGN.mjs +0 -48
- package/dist/chunk-DE7I3VHO.mjs +0 -106
- package/dist/chunk-F6D2OZ36.mjs +0 -89
- package/dist/chunk-F6D2OZ36.mjs.map +0 -1
- package/dist/chunk-GAOIFQDX.mjs +0 -59
- package/dist/chunk-GAOIFQDX.mjs.map +0 -1
- package/dist/chunk-HUIX2XVR.mjs +0 -159
- package/dist/chunk-HUIX2XVR.mjs.map +0 -1
- package/dist/chunk-K36R6HWY.mjs +0 -51
- package/dist/chunk-MBTAI3WE.mjs +0 -78
- package/dist/chunk-MBTAI3WE.mjs.map +0 -1
- package/dist/chunk-MGT6RUVX.mjs +0 -423
- package/dist/chunk-PIFS7AIT.mjs +0 -446
- package/dist/chunk-WG66JYYW.mjs +0 -116
- package/dist/chunk-XD7WGXHZ.mjs +0 -82
- package/dist/chunk-YYDM6V2F.mjs +0 -113
- package/dist/chunk-ZK7P3TZN.mjs +0 -286
- package/dist/chunk-ZK7P3TZN.mjs.map +0 -1
- /package/dist/{chunk-3IIFBJCD.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,601 +27,433 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// src/
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
30
|
+
// src/addons/vehicle-classifier/index.ts
|
|
31
|
+
var vehicle_classifier_exports = {};
|
|
32
|
+
__export(vehicle_classifier_exports, {
|
|
33
|
+
default: () => VehicleClassifierAddon
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(vehicle_classifier_exports);
|
|
36
|
+
|
|
37
|
+
// src/catalogs/vehicle-classification-models.ts
|
|
38
|
+
var import_types = require("@camstack/types");
|
|
39
|
+
var HF_REPO = "camstack/camstack-models";
|
|
40
|
+
var hf = (path4) => (0, import_types.hfModelUrl)(HF_REPO, path4);
|
|
41
|
+
var VEHICLE_LABELS = [
|
|
42
|
+
{ id: "vehicle-type", name: "Vehicle Type" }
|
|
43
|
+
];
|
|
44
|
+
var VEHICLE_TYPE_MODELS = [
|
|
45
|
+
{
|
|
46
|
+
id: "vehicle-type-efficientnet",
|
|
47
|
+
name: "Vehicle Type (EfficientNet)",
|
|
48
|
+
description: "EfficientNet-B4 vehicle make/model/year classifier \u2014 8,949 classes from VMMRdb",
|
|
49
|
+
inputSize: { width: 380, height: 380 },
|
|
50
|
+
inputNormalization: "imagenet",
|
|
51
|
+
labels: VEHICLE_LABELS,
|
|
52
|
+
formats: {
|
|
53
|
+
onnx: { url: hf("vehicleClassification/efficientnet/onnx/camstack-vehicle-type-efficientnet.onnx"), sizeMB: 135 },
|
|
54
|
+
coreml: {
|
|
55
|
+
url: hf("vehicleClassification/efficientnet/coreml/camstack-vehicle-type-efficientnet.mlpackage"),
|
|
56
|
+
sizeMB: 10,
|
|
57
|
+
isDirectory: true,
|
|
58
|
+
files: ["Manifest.json", "Data/com.apple.CoreML/model.mlmodel", "Data/com.apple.CoreML/weights/weight.bin"],
|
|
59
|
+
runtimes: ["python"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
extraFiles: [
|
|
46
63
|
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
inputSize: { width: 380, height: 380 },
|
|
51
|
-
inputNormalization: "imagenet",
|
|
52
|
-
labels: VEHICLE_LABELS,
|
|
53
|
-
formats: {
|
|
54
|
-
onnx: { url: hf("vehicleClassification/efficientnet/onnx/camstack-vehicle-type-efficientnet.onnx"), sizeMB: 135 },
|
|
55
|
-
coreml: {
|
|
56
|
-
url: hf("vehicleClassification/efficientnet/coreml/camstack-vehicle-type-efficientnet.mlpackage"),
|
|
57
|
-
sizeMB: 10,
|
|
58
|
-
isDirectory: true,
|
|
59
|
-
files: ["Manifest.json", "Data/com.apple.CoreML/model.mlmodel", "Data/com.apple.CoreML/weights/weight.bin"],
|
|
60
|
-
runtimes: ["python"]
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
extraFiles: [
|
|
64
|
-
{
|
|
65
|
-
url: hf("vehicleClassification/efficientnet/camstack-vehicle-type-labels.json"),
|
|
66
|
-
filename: "camstack-vehicle-type-labels.json",
|
|
67
|
-
sizeMB: 0.2
|
|
68
|
-
}
|
|
69
|
-
]
|
|
64
|
+
url: hf("vehicleClassification/efficientnet/camstack-vehicle-type-labels.json"),
|
|
65
|
+
filename: "camstack-vehicle-type-labels.json",
|
|
66
|
+
sizeMB: 0.2
|
|
70
67
|
}
|
|
71
|
-
]
|
|
68
|
+
]
|
|
72
69
|
}
|
|
73
|
-
|
|
70
|
+
];
|
|
74
71
|
|
|
75
|
-
// src/shared/image-utils.
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const originalWidth = meta.width ?? 0;
|
|
104
|
-
const originalHeight = meta.height ?? 0;
|
|
105
|
-
const scale = Math.min(targetSize / originalWidth, targetSize / originalHeight);
|
|
106
|
-
const scaledWidth = Math.round(originalWidth * scale);
|
|
107
|
-
const scaledHeight = Math.round(originalHeight * scale);
|
|
108
|
-
const padX = Math.floor((targetSize - scaledWidth) / 2);
|
|
109
|
-
const padY = Math.floor((targetSize - scaledHeight) / 2);
|
|
110
|
-
const { data } = await (0, sharp_1.default)(jpeg).resize(scaledWidth, scaledHeight).extend({
|
|
111
|
-
top: padY,
|
|
112
|
-
bottom: targetSize - scaledHeight - padY,
|
|
113
|
-
left: padX,
|
|
114
|
-
right: targetSize - scaledWidth - padX,
|
|
115
|
-
background: { r: 114, g: 114, b: 114 }
|
|
116
|
-
}).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
117
|
-
const numPixels = targetSize * targetSize;
|
|
118
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
119
|
-
for (let i = 0; i < numPixels; i++) {
|
|
120
|
-
const srcBase = i * 3;
|
|
121
|
-
float32[0 * numPixels + i] = data[srcBase] / 255;
|
|
122
|
-
float32[1 * numPixels + i] = data[srcBase + 1] / 255;
|
|
123
|
-
float32[2 * numPixels + i] = data[srcBase + 2] / 255;
|
|
124
|
-
}
|
|
125
|
-
return { data: float32, scale, padX, padY, originalWidth, originalHeight };
|
|
126
|
-
}
|
|
127
|
-
async function resizeAndNormalize2(jpeg, targetWidth, targetHeight, normalization, layout) {
|
|
128
|
-
const { data } = await (0, sharp_1.default)(jpeg).resize(targetWidth, targetHeight, { fit: "fill" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
129
|
-
const numPixels = targetWidth * targetHeight;
|
|
130
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
131
|
-
const mean = [0.485, 0.456, 0.406];
|
|
132
|
-
const std = [0.229, 0.224, 0.225];
|
|
133
|
-
if (layout === "nchw") {
|
|
134
|
-
for (let i = 0; i < numPixels; i++) {
|
|
135
|
-
const srcBase = i * 3;
|
|
136
|
-
for (let c = 0; c < 3; c++) {
|
|
137
|
-
const raw = data[srcBase + c] / 255;
|
|
138
|
-
let val;
|
|
139
|
-
if (normalization === "zero-one") {
|
|
140
|
-
val = raw;
|
|
141
|
-
} else if (normalization === "imagenet") {
|
|
142
|
-
val = (raw - mean[c]) / std[c];
|
|
143
|
-
} else {
|
|
144
|
-
val = data[srcBase + c];
|
|
145
|
-
}
|
|
146
|
-
float32[c * numPixels + i] = val;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
for (let i = 0; i < numPixels; i++) {
|
|
151
|
-
const srcBase = i * 3;
|
|
152
|
-
for (let c = 0; c < 3; c++) {
|
|
153
|
-
const raw = data[srcBase + c] / 255;
|
|
154
|
-
let val;
|
|
155
|
-
if (normalization === "zero-one") {
|
|
156
|
-
val = raw;
|
|
157
|
-
} else if (normalization === "imagenet") {
|
|
158
|
-
val = (raw - mean[c]) / std[c];
|
|
159
|
-
} else {
|
|
160
|
-
val = data[srcBase + c];
|
|
161
|
-
}
|
|
162
|
-
float32[i * 3 + c] = val;
|
|
163
|
-
}
|
|
72
|
+
// src/shared/image-utils.ts
|
|
73
|
+
var import_sharp = __toESM(require("sharp"));
|
|
74
|
+
async function cropRegion(jpeg, roi) {
|
|
75
|
+
return (0, import_sharp.default)(jpeg).extract({
|
|
76
|
+
left: Math.round(roi.x),
|
|
77
|
+
top: Math.round(roi.y),
|
|
78
|
+
width: Math.round(roi.w),
|
|
79
|
+
height: Math.round(roi.h)
|
|
80
|
+
}).jpeg().toBuffer();
|
|
81
|
+
}
|
|
82
|
+
async function resizeAndNormalize(jpeg, targetWidth, targetHeight, normalization, layout) {
|
|
83
|
+
const { data } = await (0, import_sharp.default)(jpeg).resize(targetWidth, targetHeight, { fit: "fill" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
84
|
+
const numPixels = targetWidth * targetHeight;
|
|
85
|
+
const float32 = new Float32Array(3 * numPixels);
|
|
86
|
+
const mean = [0.485, 0.456, 0.406];
|
|
87
|
+
const std = [0.229, 0.224, 0.225];
|
|
88
|
+
if (layout === "nchw") {
|
|
89
|
+
for (let i = 0; i < numPixels; i++) {
|
|
90
|
+
const srcBase = i * 3;
|
|
91
|
+
for (let c = 0; c < 3; c++) {
|
|
92
|
+
const raw = data[srcBase + c] / 255;
|
|
93
|
+
let val;
|
|
94
|
+
if (normalization === "zero-one") {
|
|
95
|
+
val = raw;
|
|
96
|
+
} else if (normalization === "imagenet") {
|
|
97
|
+
val = (raw - mean[c]) / std[c];
|
|
98
|
+
} else {
|
|
99
|
+
val = data[srcBase + c];
|
|
164
100
|
}
|
|
101
|
+
float32[c * numPixels + i] = val;
|
|
165
102
|
}
|
|
166
|
-
return float32;
|
|
167
103
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
for (let
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
104
|
+
} else {
|
|
105
|
+
for (let i = 0; i < numPixels; i++) {
|
|
106
|
+
const srcBase = i * 3;
|
|
107
|
+
for (let c = 0; c < 3; c++) {
|
|
108
|
+
const raw = data[srcBase + c] / 255;
|
|
109
|
+
let val;
|
|
110
|
+
if (normalization === "zero-one") {
|
|
111
|
+
val = raw;
|
|
112
|
+
} else if (normalization === "imagenet") {
|
|
113
|
+
val = (raw - mean[c]) / std[c];
|
|
114
|
+
} else {
|
|
115
|
+
val = data[srcBase + c];
|
|
116
|
+
}
|
|
117
|
+
float32[i * 3 + c] = val;
|
|
176
118
|
}
|
|
177
|
-
return gray;
|
|
178
119
|
}
|
|
179
120
|
}
|
|
180
|
-
|
|
121
|
+
return float32;
|
|
122
|
+
}
|
|
181
123
|
|
|
182
|
-
// src/shared/
|
|
183
|
-
var
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (mod != null) {
|
|
217
|
-
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
218
|
-
}
|
|
219
|
-
__setModuleDefault(result, mod);
|
|
220
|
-
return result;
|
|
221
|
-
};
|
|
222
|
-
})();
|
|
223
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
224
|
-
exports2.NodeInferenceEngine = void 0;
|
|
225
|
-
var path2 = __importStar(require("path"));
|
|
226
|
-
var BACKEND_TO_PROVIDER = {
|
|
227
|
-
cpu: "cpu",
|
|
228
|
-
coreml: "coreml",
|
|
229
|
-
cuda: "cuda",
|
|
230
|
-
tensorrt: "tensorrt",
|
|
231
|
-
dml: "dml"
|
|
124
|
+
// src/shared/engine-resolver.ts
|
|
125
|
+
var fs = __toESM(require("fs"));
|
|
126
|
+
var path2 = __toESM(require("path"));
|
|
127
|
+
|
|
128
|
+
// src/shared/node-engine.ts
|
|
129
|
+
var path = __toESM(require("path"));
|
|
130
|
+
var BACKEND_TO_PROVIDER = {
|
|
131
|
+
cpu: "cpu",
|
|
132
|
+
coreml: "coreml",
|
|
133
|
+
cuda: "cuda",
|
|
134
|
+
tensorrt: "tensorrt",
|
|
135
|
+
dml: "dml"
|
|
136
|
+
};
|
|
137
|
+
var BACKEND_TO_DEVICE = {
|
|
138
|
+
cpu: "cpu",
|
|
139
|
+
coreml: "gpu-mps",
|
|
140
|
+
cuda: "gpu-cuda",
|
|
141
|
+
tensorrt: "tensorrt"
|
|
142
|
+
};
|
|
143
|
+
var NodeInferenceEngine = class {
|
|
144
|
+
constructor(modelPath, backend) {
|
|
145
|
+
this.modelPath = modelPath;
|
|
146
|
+
this.backend = backend;
|
|
147
|
+
this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
|
|
148
|
+
}
|
|
149
|
+
runtime = "onnx";
|
|
150
|
+
device;
|
|
151
|
+
session = null;
|
|
152
|
+
async initialize() {
|
|
153
|
+
const ort = await import("onnxruntime-node");
|
|
154
|
+
const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
|
|
155
|
+
const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
|
|
156
|
+
const sessionOptions = {
|
|
157
|
+
executionProviders: [provider]
|
|
232
158
|
};
|
|
233
|
-
|
|
234
|
-
|
|
159
|
+
this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
|
|
160
|
+
}
|
|
161
|
+
async run(input, inputShape) {
|
|
162
|
+
if (!this.session) {
|
|
163
|
+
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
164
|
+
}
|
|
165
|
+
const ort = await import("onnxruntime-node");
|
|
166
|
+
const sess = this.session;
|
|
167
|
+
const inputName = sess.inputNames[0];
|
|
168
|
+
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
169
|
+
const feeds = { [inputName]: tensor };
|
|
170
|
+
const results = await sess.run(feeds);
|
|
171
|
+
const outputName = sess.outputNames[0];
|
|
172
|
+
const outputTensor = results[outputName];
|
|
173
|
+
return outputTensor.data;
|
|
174
|
+
}
|
|
175
|
+
async runMultiOutput(input, inputShape) {
|
|
176
|
+
if (!this.session) {
|
|
177
|
+
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
178
|
+
}
|
|
179
|
+
const ort = await import("onnxruntime-node");
|
|
180
|
+
const sess = this.session;
|
|
181
|
+
const inputName = sess.inputNames[0];
|
|
182
|
+
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
183
|
+
const feeds = { [inputName]: tensor };
|
|
184
|
+
const results = await sess.run(feeds);
|
|
185
|
+
const out = {};
|
|
186
|
+
for (const name of sess.outputNames) {
|
|
187
|
+
out[name] = results[name].data;
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
async dispose() {
|
|
192
|
+
this.session = null;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/shared/python-engine.ts
|
|
197
|
+
var import_node_child_process = require("child_process");
|
|
198
|
+
var PythonInferenceEngine = class {
|
|
199
|
+
constructor(pythonPath, scriptPath, runtime, modelPath, extraArgs = []) {
|
|
200
|
+
this.pythonPath = pythonPath;
|
|
201
|
+
this.scriptPath = scriptPath;
|
|
202
|
+
this.modelPath = modelPath;
|
|
203
|
+
this.extraArgs = extraArgs;
|
|
204
|
+
this.runtime = runtime;
|
|
205
|
+
const runtimeDeviceMap = {
|
|
206
|
+
onnx: "cpu",
|
|
235
207
|
coreml: "gpu-mps",
|
|
236
|
-
|
|
237
|
-
|
|
208
|
+
pytorch: "cpu",
|
|
209
|
+
openvino: "cpu",
|
|
210
|
+
tflite: "cpu"
|
|
238
211
|
};
|
|
239
|
-
|
|
240
|
-
modelPath;
|
|
241
|
-
backend;
|
|
242
|
-
runtime = "onnx";
|
|
243
|
-
device;
|
|
244
|
-
session = null;
|
|
245
|
-
constructor(modelPath, backend) {
|
|
246
|
-
this.modelPath = modelPath;
|
|
247
|
-
this.backend = backend;
|
|
248
|
-
this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
|
|
249
|
-
}
|
|
250
|
-
async initialize() {
|
|
251
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
252
|
-
const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
|
|
253
|
-
const absModelPath = path2.isAbsolute(this.modelPath) ? this.modelPath : path2.resolve(process.cwd(), this.modelPath);
|
|
254
|
-
const sessionOptions = {
|
|
255
|
-
executionProviders: [provider]
|
|
256
|
-
};
|
|
257
|
-
this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
|
|
258
|
-
}
|
|
259
|
-
async run(input, inputShape) {
|
|
260
|
-
if (!this.session) {
|
|
261
|
-
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
262
|
-
}
|
|
263
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
264
|
-
const sess = this.session;
|
|
265
|
-
const inputName = sess.inputNames[0];
|
|
266
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
267
|
-
const feeds = { [inputName]: tensor };
|
|
268
|
-
const results = await sess.run(feeds);
|
|
269
|
-
const outputName = sess.outputNames[0];
|
|
270
|
-
const outputTensor = results[outputName];
|
|
271
|
-
return outputTensor.data;
|
|
272
|
-
}
|
|
273
|
-
async runMultiOutput(input, inputShape) {
|
|
274
|
-
if (!this.session) {
|
|
275
|
-
throw new Error("NodeInferenceEngine: not initialized \u2014 call initialize() first");
|
|
276
|
-
}
|
|
277
|
-
const ort = await Promise.resolve().then(() => __importStar(require("onnxruntime-node")));
|
|
278
|
-
const sess = this.session;
|
|
279
|
-
const inputName = sess.inputNames[0];
|
|
280
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
281
|
-
const feeds = { [inputName]: tensor };
|
|
282
|
-
const results = await sess.run(feeds);
|
|
283
|
-
const out = {};
|
|
284
|
-
for (const name of sess.outputNames) {
|
|
285
|
-
out[name] = results[name].data;
|
|
286
|
-
}
|
|
287
|
-
return out;
|
|
288
|
-
}
|
|
289
|
-
async dispose() {
|
|
290
|
-
this.session = null;
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
exports2.NodeInferenceEngine = NodeInferenceEngine;
|
|
212
|
+
this.device = runtimeDeviceMap[runtime];
|
|
294
213
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.
|
|
321
|
-
this.runtime = runtime;
|
|
322
|
-
const runtimeDeviceMap = {
|
|
323
|
-
onnx: "cpu",
|
|
324
|
-
coreml: "gpu-mps",
|
|
325
|
-
pytorch: "cpu",
|
|
326
|
-
openvino: "cpu",
|
|
327
|
-
tflite: "cpu"
|
|
328
|
-
};
|
|
329
|
-
this.device = runtimeDeviceMap[runtime];
|
|
330
|
-
}
|
|
331
|
-
async initialize() {
|
|
332
|
-
const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
|
|
333
|
-
this.process = (0, node_child_process_1.spawn)(this.pythonPath, args, {
|
|
334
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
335
|
-
});
|
|
336
|
-
if (!this.process.stdout || !this.process.stdin) {
|
|
337
|
-
throw new Error("PythonInferenceEngine: failed to create process pipes");
|
|
338
|
-
}
|
|
339
|
-
this.process.stderr?.on("data", (chunk) => {
|
|
340
|
-
process.stderr.write(`[python-engine] ${chunk.toString()}`);
|
|
341
|
-
});
|
|
342
|
-
this.process.on("error", (err) => {
|
|
343
|
-
this.pendingReject?.(err);
|
|
344
|
-
this.pendingReject = null;
|
|
345
|
-
this.pendingResolve = null;
|
|
346
|
-
});
|
|
347
|
-
this.process.on("exit", (code) => {
|
|
348
|
-
if (code !== 0) {
|
|
349
|
-
const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
|
|
350
|
-
this.pendingReject?.(err);
|
|
351
|
-
this.pendingReject = null;
|
|
352
|
-
this.pendingResolve = null;
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
this.process.stdout.on("data", (chunk) => {
|
|
356
|
-
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
357
|
-
this._tryReceive();
|
|
358
|
-
});
|
|
359
|
-
await new Promise((resolve, reject) => {
|
|
360
|
-
const timeout = setTimeout(() => resolve(), 2e3);
|
|
361
|
-
this.process?.on("error", (err) => {
|
|
362
|
-
clearTimeout(timeout);
|
|
363
|
-
reject(err);
|
|
364
|
-
});
|
|
365
|
-
this.process?.on("exit", (code) => {
|
|
366
|
-
clearTimeout(timeout);
|
|
367
|
-
if (code !== 0) {
|
|
368
|
-
reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
_tryReceive() {
|
|
374
|
-
if (this.receiveBuffer.length < 4)
|
|
375
|
-
return;
|
|
376
|
-
const length = this.receiveBuffer.readUInt32LE(0);
|
|
377
|
-
if (this.receiveBuffer.length < 4 + length)
|
|
378
|
-
return;
|
|
379
|
-
const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
|
|
380
|
-
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
381
|
-
const resolve = this.pendingResolve;
|
|
382
|
-
const reject = this.pendingReject;
|
|
383
|
-
this.pendingResolve = null;
|
|
214
|
+
runtime;
|
|
215
|
+
device;
|
|
216
|
+
process = null;
|
|
217
|
+
receiveBuffer = Buffer.alloc(0);
|
|
218
|
+
pendingResolve = null;
|
|
219
|
+
pendingReject = null;
|
|
220
|
+
async initialize() {
|
|
221
|
+
const args = [this.scriptPath, this.modelPath, ...this.extraArgs];
|
|
222
|
+
this.process = (0, import_node_child_process.spawn)(this.pythonPath, args, {
|
|
223
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
224
|
+
});
|
|
225
|
+
if (!this.process.stdout || !this.process.stdin) {
|
|
226
|
+
throw new Error("PythonInferenceEngine: failed to create process pipes");
|
|
227
|
+
}
|
|
228
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
229
|
+
process.stderr.write(`[python-engine] ${chunk.toString()}`);
|
|
230
|
+
});
|
|
231
|
+
this.process.on("error", (err) => {
|
|
232
|
+
this.pendingReject?.(err);
|
|
233
|
+
this.pendingReject = null;
|
|
234
|
+
this.pendingResolve = null;
|
|
235
|
+
});
|
|
236
|
+
this.process.on("exit", (code) => {
|
|
237
|
+
if (code !== 0) {
|
|
238
|
+
const err = new Error(`PythonInferenceEngine: process exited with code ${code}`);
|
|
239
|
+
this.pendingReject?.(err);
|
|
384
240
|
this.pendingReject = null;
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
try {
|
|
388
|
-
const parsed = JSON.parse(jsonBytes.toString("utf8"));
|
|
389
|
-
resolve(parsed);
|
|
390
|
-
} catch (err) {
|
|
391
|
-
reject?.(err instanceof Error ? err : new Error(String(err)));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
/** Send JPEG buffer, receive JSON detection results */
|
|
395
|
-
async runJpeg(jpeg) {
|
|
396
|
-
if (!this.process?.stdin) {
|
|
397
|
-
throw new Error("PythonInferenceEngine: process not initialized");
|
|
398
|
-
}
|
|
399
|
-
return new Promise((resolve, reject) => {
|
|
400
|
-
this.pendingResolve = resolve;
|
|
401
|
-
this.pendingReject = reject;
|
|
402
|
-
const lengthBuf = Buffer.allocUnsafe(4);
|
|
403
|
-
lengthBuf.writeUInt32LE(jpeg.length, 0);
|
|
404
|
-
this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
/** IInferenceEngine.run — wraps runJpeg for compatibility */
|
|
408
|
-
async run(_input, _inputShape) {
|
|
409
|
-
throw new Error("PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input");
|
|
410
|
-
}
|
|
411
|
-
/** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
|
|
412
|
-
async runMultiOutput(_input, _inputShape) {
|
|
413
|
-
throw new Error("PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input");
|
|
241
|
+
this.pendingResolve = null;
|
|
414
242
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
243
|
+
});
|
|
244
|
+
this.process.stdout.on("data", (chunk) => {
|
|
245
|
+
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
246
|
+
this._tryReceive();
|
|
247
|
+
});
|
|
248
|
+
await new Promise((resolve2, reject) => {
|
|
249
|
+
const timeout = setTimeout(() => resolve2(), 2e3);
|
|
250
|
+
this.process?.on("error", (err) => {
|
|
251
|
+
clearTimeout(timeout);
|
|
252
|
+
reject(err);
|
|
253
|
+
});
|
|
254
|
+
this.process?.on("exit", (code) => {
|
|
255
|
+
clearTimeout(timeout);
|
|
256
|
+
if (code !== 0) {
|
|
257
|
+
reject(new Error(`PythonInferenceEngine: process exited early with code ${code}`));
|
|
420
258
|
}
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
_tryReceive() {
|
|
263
|
+
if (this.receiveBuffer.length < 4) return;
|
|
264
|
+
const length = this.receiveBuffer.readUInt32LE(0);
|
|
265
|
+
if (this.receiveBuffer.length < 4 + length) return;
|
|
266
|
+
const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
|
|
267
|
+
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
268
|
+
const resolve2 = this.pendingResolve;
|
|
269
|
+
const reject = this.pendingReject;
|
|
270
|
+
this.pendingResolve = null;
|
|
271
|
+
this.pendingReject = null;
|
|
272
|
+
if (!resolve2) return;
|
|
273
|
+
try {
|
|
274
|
+
const parsed = JSON.parse(jsonBytes.toString("utf8"));
|
|
275
|
+
resolve2(parsed);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
reject?.(err instanceof Error ? err : new Error(String(err)));
|
|
428
278
|
}
|
|
429
279
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return m[k];
|
|
442
|
-
} };
|
|
443
|
-
}
|
|
444
|
-
Object.defineProperty(o, k2, desc);
|
|
445
|
-
}) : (function(o, m, k, k2) {
|
|
446
|
-
if (k2 === void 0) k2 = k;
|
|
447
|
-
o[k2] = m[k];
|
|
448
|
-
}));
|
|
449
|
-
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
450
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
451
|
-
}) : function(o, v) {
|
|
452
|
-
o["default"] = v;
|
|
280
|
+
/** Send JPEG buffer, receive JSON detection results */
|
|
281
|
+
async runJpeg(jpeg) {
|
|
282
|
+
if (!this.process?.stdin) {
|
|
283
|
+
throw new Error("PythonInferenceEngine: process not initialized");
|
|
284
|
+
}
|
|
285
|
+
return new Promise((resolve2, reject) => {
|
|
286
|
+
this.pendingResolve = resolve2;
|
|
287
|
+
this.pendingReject = reject;
|
|
288
|
+
const lengthBuf = Buffer.allocUnsafe(4);
|
|
289
|
+
lengthBuf.writeUInt32LE(jpeg.length, 0);
|
|
290
|
+
this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
|
|
453
291
|
});
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
})();
|
|
473
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
474
|
-
exports2.resolveEngine = resolveEngine2;
|
|
475
|
-
exports2.probeOnnxBackends = probeOnnxBackends;
|
|
476
|
-
var fs2 = __importStar(require("fs"));
|
|
477
|
-
var path2 = __importStar(require("path"));
|
|
478
|
-
var node_engine_js_1 = require_node_engine();
|
|
479
|
-
var python_engine_js_1 = require_python_engine();
|
|
480
|
-
var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
|
|
481
|
-
var BACKEND_TO_FORMAT = {
|
|
482
|
-
cpu: "onnx",
|
|
483
|
-
coreml: "onnx",
|
|
484
|
-
cuda: "onnx",
|
|
485
|
-
tensorrt: "onnx"
|
|
486
|
-
};
|
|
487
|
-
var RUNTIME_TO_FORMAT = {
|
|
488
|
-
onnx: "onnx",
|
|
489
|
-
coreml: "coreml",
|
|
490
|
-
openvino: "openvino",
|
|
491
|
-
tflite: "tflite",
|
|
492
|
-
pytorch: "pt"
|
|
493
|
-
};
|
|
494
|
-
function modelFilePath(modelsDir, modelEntry, format) {
|
|
495
|
-
const formatEntry = modelEntry.formats[format];
|
|
496
|
-
if (!formatEntry) {
|
|
497
|
-
throw new Error(`Model ${modelEntry.id} has no ${format} format`);
|
|
498
|
-
}
|
|
499
|
-
const urlParts = formatEntry.url.split("/");
|
|
500
|
-
const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
|
|
501
|
-
return path2.join(modelsDir, filename);
|
|
292
|
+
}
|
|
293
|
+
/** IInferenceEngine.run — wraps runJpeg for compatibility */
|
|
294
|
+
async run(_input, _inputShape) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
"PythonInferenceEngine: use runJpeg() directly \u2014 this engine operates on JPEG input"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
/** IInferenceEngine.runMultiOutput — not supported by Python engine (operates on JPEG input) */
|
|
300
|
+
async runMultiOutput(_input, _inputShape) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"PythonInferenceEngine: runMultiOutput() is not supported \u2014 this engine operates on JPEG input"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
async dispose() {
|
|
306
|
+
if (this.process) {
|
|
307
|
+
this.process.stdin?.end();
|
|
308
|
+
this.process.kill("SIGTERM");
|
|
309
|
+
this.process = null;
|
|
502
310
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/shared/engine-resolver.ts
|
|
315
|
+
var AUTO_BACKEND_PRIORITY = ["coreml", "cuda", "tensorrt", "cpu"];
|
|
316
|
+
var BACKEND_TO_FORMAT = {
|
|
317
|
+
cpu: "onnx",
|
|
318
|
+
coreml: "onnx",
|
|
319
|
+
cuda: "onnx",
|
|
320
|
+
tensorrt: "onnx"
|
|
321
|
+
};
|
|
322
|
+
var RUNTIME_TO_FORMAT = {
|
|
323
|
+
onnx: "onnx",
|
|
324
|
+
coreml: "coreml",
|
|
325
|
+
openvino: "openvino",
|
|
326
|
+
tflite: "tflite",
|
|
327
|
+
pytorch: "pt"
|
|
328
|
+
};
|
|
329
|
+
function modelFilePath(modelsDir, modelEntry, format) {
|
|
330
|
+
const formatEntry = modelEntry.formats[format];
|
|
331
|
+
if (!formatEntry) {
|
|
332
|
+
throw new Error(`Model ${modelEntry.id} has no ${format} format`);
|
|
333
|
+
}
|
|
334
|
+
const urlParts = formatEntry.url.split("/");
|
|
335
|
+
const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
|
|
336
|
+
return path2.join(modelsDir, filename);
|
|
337
|
+
}
|
|
338
|
+
function modelExists(filePath) {
|
|
339
|
+
try {
|
|
340
|
+
return fs.existsSync(filePath);
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function resolveEngine(options) {
|
|
346
|
+
const { runtime, backend, modelEntry, modelsDir, models } = options;
|
|
347
|
+
let selectedFormat;
|
|
348
|
+
let selectedBackend;
|
|
349
|
+
if (runtime === "auto") {
|
|
350
|
+
const available = await probeOnnxBackends();
|
|
351
|
+
let chosen = null;
|
|
352
|
+
for (const b of AUTO_BACKEND_PRIORITY) {
|
|
353
|
+
if (!available.includes(b)) continue;
|
|
354
|
+
const fmt = BACKEND_TO_FORMAT[b];
|
|
355
|
+
if (!fmt) continue;
|
|
356
|
+
if (!modelEntry.formats[fmt]) continue;
|
|
357
|
+
chosen = { backend: b, format: fmt };
|
|
358
|
+
break;
|
|
509
359
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (runtime === "auto") {
|
|
515
|
-
const available = await probeOnnxBackends();
|
|
516
|
-
let chosen = null;
|
|
517
|
-
for (const b of AUTO_BACKEND_PRIORITY) {
|
|
518
|
-
if (!available.includes(b))
|
|
519
|
-
continue;
|
|
520
|
-
const fmt = BACKEND_TO_FORMAT[b];
|
|
521
|
-
if (!fmt)
|
|
522
|
-
continue;
|
|
523
|
-
if (!modelEntry.formats[fmt])
|
|
524
|
-
continue;
|
|
525
|
-
chosen = { backend: b, format: fmt };
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
if (!chosen) {
|
|
529
|
-
throw new Error(`resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`);
|
|
530
|
-
}
|
|
531
|
-
selectedFormat = chosen.format;
|
|
532
|
-
selectedBackend = chosen.backend;
|
|
533
|
-
} else {
|
|
534
|
-
const fmt = RUNTIME_TO_FORMAT[runtime];
|
|
535
|
-
if (!fmt) {
|
|
536
|
-
throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
|
|
537
|
-
}
|
|
538
|
-
if (!modelEntry.formats[fmt]) {
|
|
539
|
-
throw new Error(`resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`);
|
|
540
|
-
}
|
|
541
|
-
selectedFormat = fmt;
|
|
542
|
-
selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
|
|
543
|
-
}
|
|
544
|
-
let modelPath;
|
|
545
|
-
if (models) {
|
|
546
|
-
modelPath = await models.ensure(modelEntry.id, selectedFormat);
|
|
547
|
-
} else {
|
|
548
|
-
modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
|
|
549
|
-
if (!modelExists(modelPath)) {
|
|
550
|
-
throw new Error(`resolveEngine: model file not found at ${modelPath} and no model service provided`);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (selectedFormat === "onnx") {
|
|
554
|
-
const engine = new node_engine_js_1.NodeInferenceEngine(modelPath, selectedBackend);
|
|
555
|
-
await engine.initialize();
|
|
556
|
-
return { engine, format: selectedFormat, modelPath };
|
|
557
|
-
}
|
|
558
|
-
const { pythonPath } = options;
|
|
559
|
-
const PYTHON_SCRIPT_MAP = {
|
|
560
|
-
coreml: "coreml_inference.py",
|
|
561
|
-
pytorch: "pytorch_inference.py",
|
|
562
|
-
openvino: "openvino_inference.py"
|
|
563
|
-
};
|
|
564
|
-
const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
|
|
565
|
-
const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
|
|
566
|
-
if (scriptName && pythonPath) {
|
|
567
|
-
const candidates = [
|
|
568
|
-
path2.join(__dirname, "../../python", scriptName),
|
|
569
|
-
path2.join(__dirname, "../python", scriptName),
|
|
570
|
-
path2.join(__dirname, "../../../python", scriptName)
|
|
571
|
-
];
|
|
572
|
-
const scriptPath = candidates.find((p) => fs2.existsSync(p));
|
|
573
|
-
if (!scriptPath) {
|
|
574
|
-
throw new Error(`resolveEngine: Python script "${scriptName}" not found. Searched:
|
|
575
|
-
${candidates.join("\n")}`);
|
|
576
|
-
}
|
|
577
|
-
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
578
|
-
const engine = new python_engine_js_1.PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
|
|
579
|
-
`--input-size=${inputSize}`,
|
|
580
|
-
`--confidence=0.25`
|
|
581
|
-
]);
|
|
582
|
-
await engine.initialize();
|
|
583
|
-
return { engine, format: selectedFormat, modelPath };
|
|
584
|
-
}
|
|
585
|
-
const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
|
|
586
|
-
if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
|
|
587
|
-
const engine = new node_engine_js_1.NodeInferenceEngine(fallbackPath, "cpu");
|
|
588
|
-
await engine.initialize();
|
|
589
|
-
return { engine, format: "onnx", modelPath: fallbackPath };
|
|
590
|
-
}
|
|
591
|
-
throw new Error(`resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`);
|
|
360
|
+
if (!chosen) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`
|
|
363
|
+
);
|
|
592
364
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
else if (normalized === "tensorrt")
|
|
605
|
-
available.push("tensorrt");
|
|
606
|
-
}
|
|
607
|
-
} catch {
|
|
608
|
-
}
|
|
609
|
-
if (process.platform === "darwin" && !available.includes("coreml")) {
|
|
610
|
-
available.push("coreml");
|
|
611
|
-
}
|
|
612
|
-
return [...new Set(available)];
|
|
365
|
+
selectedFormat = chosen.format;
|
|
366
|
+
selectedBackend = chosen.backend;
|
|
367
|
+
} else {
|
|
368
|
+
const fmt = RUNTIME_TO_FORMAT[runtime];
|
|
369
|
+
if (!fmt) {
|
|
370
|
+
throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
|
|
371
|
+
}
|
|
372
|
+
if (!modelEntry.formats[fmt]) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`
|
|
375
|
+
);
|
|
613
376
|
}
|
|
377
|
+
selectedFormat = fmt;
|
|
378
|
+
selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
|
|
614
379
|
}
|
|
615
|
-
|
|
380
|
+
let modelPath;
|
|
381
|
+
if (models) {
|
|
382
|
+
modelPath = await models.ensure(modelEntry.id, selectedFormat);
|
|
383
|
+
} else {
|
|
384
|
+
modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
|
|
385
|
+
if (!modelExists(modelPath)) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`resolveEngine: model file not found at ${modelPath} and no model service provided`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (selectedFormat === "onnx") {
|
|
392
|
+
const engine = new NodeInferenceEngine(modelPath, selectedBackend);
|
|
393
|
+
await engine.initialize();
|
|
394
|
+
return { engine, format: selectedFormat, modelPath };
|
|
395
|
+
}
|
|
396
|
+
const { pythonPath } = options;
|
|
397
|
+
const PYTHON_SCRIPT_MAP = {
|
|
398
|
+
coreml: "coreml_inference.py",
|
|
399
|
+
pytorch: "pytorch_inference.py",
|
|
400
|
+
openvino: "openvino_inference.py"
|
|
401
|
+
};
|
|
402
|
+
const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
|
|
403
|
+
const scriptName = PYTHON_SCRIPT_MAP[effectiveRuntime];
|
|
404
|
+
if (scriptName && pythonPath) {
|
|
405
|
+
const candidates = [
|
|
406
|
+
path2.join(__dirname, "../../python", scriptName),
|
|
407
|
+
path2.join(__dirname, "../python", scriptName),
|
|
408
|
+
path2.join(__dirname, "../../../python", scriptName)
|
|
409
|
+
];
|
|
410
|
+
const scriptPath = candidates.find((p) => fs.existsSync(p));
|
|
411
|
+
if (!scriptPath) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
`resolveEngine: Python script "${scriptName}" not found. Searched:
|
|
414
|
+
${candidates.join("\n")}`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
418
|
+
const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [
|
|
419
|
+
`--input-size=${inputSize}`,
|
|
420
|
+
`--confidence=0.25`
|
|
421
|
+
]);
|
|
422
|
+
await engine.initialize();
|
|
423
|
+
return { engine, format: selectedFormat, modelPath };
|
|
424
|
+
}
|
|
425
|
+
const fallbackPath = modelFilePath(modelsDir, modelEntry, "onnx");
|
|
426
|
+
if (modelEntry.formats["onnx"] && modelExists(fallbackPath)) {
|
|
427
|
+
const engine = new NodeInferenceEngine(fallbackPath, "cpu");
|
|
428
|
+
await engine.initialize();
|
|
429
|
+
return { engine, format: "onnx", modelPath: fallbackPath };
|
|
430
|
+
}
|
|
431
|
+
throw new Error(
|
|
432
|
+
`resolveEngine: format ${selectedFormat} is not yet supported by NodeInferenceEngine, no Python runtime is available, and no ONNX fallback exists`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
async function probeOnnxBackends() {
|
|
436
|
+
const available = ["cpu"];
|
|
437
|
+
try {
|
|
438
|
+
const ort = await import("onnxruntime-node");
|
|
439
|
+
const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession?.getAvailableProviders?.() ?? [] : [];
|
|
440
|
+
for (const p of providers) {
|
|
441
|
+
const normalized = p.toLowerCase().replace("executionprovider", "");
|
|
442
|
+
if (normalized === "coreml") available.push("coreml");
|
|
443
|
+
else if (normalized === "cuda") available.push("cuda");
|
|
444
|
+
else if (normalized === "tensorrt") available.push("tensorrt");
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
if (process.platform === "darwin" && !available.includes("coreml")) {
|
|
449
|
+
available.push("coreml");
|
|
450
|
+
}
|
|
451
|
+
return [...new Set(available)];
|
|
452
|
+
}
|
|
616
453
|
|
|
617
454
|
// src/addons/vehicle-classifier/index.ts
|
|
618
|
-
var
|
|
619
|
-
|
|
620
|
-
default: () => VehicleClassifierAddon
|
|
621
|
-
});
|
|
622
|
-
module.exports = __toCommonJS(vehicle_classifier_exports);
|
|
623
|
-
var import_vehicle_classification_models = __toESM(require_vehicle_classification_models());
|
|
624
|
-
var import_image_utils = __toESM(require_image_utils());
|
|
625
|
-
var import_engine_resolver = __toESM(require_engine_resolver());
|
|
626
|
-
var fs = __toESM(require("fs"));
|
|
627
|
-
var path = __toESM(require("path"));
|
|
455
|
+
var fs2 = __toESM(require("fs"));
|
|
456
|
+
var path3 = __toESM(require("path"));
|
|
628
457
|
var VEHICLE_TYPE_LABEL = { id: "vehicle-type", name: "Vehicle Type" };
|
|
629
458
|
var VEHICLE_TYPE_LABELS = [VEHICLE_TYPE_LABEL];
|
|
630
459
|
var VEHICLE_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
@@ -634,9 +463,9 @@ function loadLabels(modelsDir, modelId) {
|
|
|
634
463
|
`camstack-vehicle-type-labels.json`
|
|
635
464
|
];
|
|
636
465
|
for (const name of labelNames) {
|
|
637
|
-
const labelPath =
|
|
638
|
-
if (
|
|
639
|
-
const raw =
|
|
466
|
+
const labelPath = path3.join(modelsDir, name);
|
|
467
|
+
if (fs2.existsSync(labelPath)) {
|
|
468
|
+
const raw = fs2.readFileSync(labelPath, "utf-8");
|
|
640
469
|
return JSON.parse(raw);
|
|
641
470
|
}
|
|
642
471
|
}
|
|
@@ -682,7 +511,7 @@ var VehicleClassifierAddon = class {
|
|
|
682
511
|
resolvedConfig = null;
|
|
683
512
|
ctx = null;
|
|
684
513
|
getModelRequirements() {
|
|
685
|
-
return
|
|
514
|
+
return VEHICLE_TYPE_MODELS.map((m) => ({
|
|
686
515
|
modelId: m.id,
|
|
687
516
|
name: m.name,
|
|
688
517
|
minRAM_MB: 120,
|
|
@@ -698,7 +527,7 @@ var VehicleClassifierAddon = class {
|
|
|
698
527
|
const cfg = ctx.addonConfig;
|
|
699
528
|
const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "vehicle-type-efficientnet";
|
|
700
529
|
this.minConfidence = cfg["minConfidence"] ?? 0.05;
|
|
701
|
-
const entry =
|
|
530
|
+
const entry = VEHICLE_TYPE_MODELS.find((m) => m.id === modelId);
|
|
702
531
|
if (!entry) {
|
|
703
532
|
throw new Error(`VehicleClassifierAddon: unknown modelId "${modelId}"`);
|
|
704
533
|
}
|
|
@@ -708,8 +537,8 @@ var VehicleClassifierAddon = class {
|
|
|
708
537
|
if (!this.engine) await this.ensureEngine();
|
|
709
538
|
const start = Date.now();
|
|
710
539
|
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
711
|
-
const vehicleCrop = await
|
|
712
|
-
const normalized = await
|
|
540
|
+
const vehicleCrop = await cropRegion(input.frame.data, input.roi);
|
|
541
|
+
const normalized = await resizeAndNormalize(vehicleCrop, inputW, inputH, "imagenet", "nchw");
|
|
713
542
|
const rawOutput = await this.engine.run(normalized, [1, 3, inputH, inputW]);
|
|
714
543
|
console.log(`[VehicleClassifier] Output length: ${rawOutput.length}, labels: ${this.labels.length}, first 5 raw: [${Array.from(rawOutput.slice(0, 5)).map((v) => v.toFixed(3)).join(", ")}]`);
|
|
715
544
|
const probs = softmax(rawOutput);
|
|
@@ -746,14 +575,14 @@ var VehicleClassifierAddon = class {
|
|
|
746
575
|
const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
|
|
747
576
|
const backend = config?.backend ?? "cpu";
|
|
748
577
|
const format = config?.format ?? "onnx";
|
|
749
|
-
const entry =
|
|
578
|
+
const entry = VEHICLE_TYPE_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
|
|
750
579
|
this.modelEntry = entry;
|
|
751
580
|
const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
|
|
752
581
|
if (this.ctx.models) {
|
|
753
582
|
await this.ctx.models.ensure(modelId, format);
|
|
754
583
|
}
|
|
755
584
|
this.labels = loadLabels(modelsDir, modelId);
|
|
756
|
-
const resolved = await
|
|
585
|
+
const resolved = await resolveEngine({
|
|
757
586
|
runtime,
|
|
758
587
|
backend,
|
|
759
588
|
modelEntry: entry,
|
|
@@ -777,7 +606,7 @@ var VehicleClassifierAddon = class {
|
|
|
777
606
|
key: "modelId",
|
|
778
607
|
label: "Model",
|
|
779
608
|
type: "model-selector",
|
|
780
|
-
catalog: [...
|
|
609
|
+
catalog: [...VEHICLE_TYPE_MODELS],
|
|
781
610
|
allowCustom: false,
|
|
782
611
|
allowConversion: false,
|
|
783
612
|
acceptFormats: ["onnx", "coreml", "openvino"],
|
|
@@ -839,7 +668,7 @@ var VehicleClassifierAddon = class {
|
|
|
839
668
|
return VEHICLE_CLASS_MAP;
|
|
840
669
|
}
|
|
841
670
|
getModelCatalog() {
|
|
842
|
-
return [...
|
|
671
|
+
return [...VEHICLE_TYPE_MODELS];
|
|
843
672
|
}
|
|
844
673
|
getAvailableModels() {
|
|
845
674
|
return [];
|