@camstack/addon-post-analysis 1.0.0 → 1.0.1
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/{dist-CS2K80so.js → dist-CrNToq_N.js} +0 -54
- package/dist/{dist-4mTLJ7BJ.mjs → dist-DbD5zJj7.mjs} +1 -37
- package/dist/embedding-encoder/index.js +98 -616
- package/dist/embedding-encoder/index.mjs +102 -615
- package/dist/enrichment-engine/index.js +2 -2
- package/dist/enrichment-engine/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CMX-fqEg.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CbefrDm1.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-B0pXhbcJ.mjs → hostInit-DZFXJWUU.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +2 -2
- package/dist/pipeline-analytics/index.mjs +1 -1
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/dist/{resolve-frame-5lMxmeI1.js → resolve-frame-9ayw6z10.js} +1 -1
- package/package.json +2 -9
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { ModelDownloadService } from "@camstack/core";
|
|
1
|
+
import { l as embeddingEncoderCapability, t as BaseAddon } from "../dist-DbD5zJj7.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import * as fs from "node:fs";
|
|
5
|
+
import { ModelDownloadService } from "@camstack/core";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
9
|
+
//#endregion
|
|
7
10
|
//#region src/embedding-encoder/catalogs/embedding-models.ts
|
|
8
11
|
var CLIP_IMAGE_MODELS = [
|
|
9
12
|
{
|
|
@@ -114,359 +117,51 @@ function createNoopLogger() {
|
|
|
114
117
|
return logger;
|
|
115
118
|
}
|
|
116
119
|
//#endregion
|
|
117
|
-
//#region src/embedding-encoder/shared/
|
|
118
|
-
var BACKEND_TO_DEVICE$1 = {
|
|
119
|
-
cpu: "cpu",
|
|
120
|
-
coreml: "gpu-mps",
|
|
121
|
-
cuda: "gpu-cuda",
|
|
122
|
-
tensorrt: "tensorrt"
|
|
123
|
-
};
|
|
120
|
+
//#region src/embedding-encoder/shared/python-raw-tensor-engine.ts
|
|
124
121
|
/**
|
|
125
|
-
* Raw
|
|
126
|
-
*
|
|
122
|
+
* Raw-tensor ONNX engine backed by an embedded-Python subprocess
|
|
123
|
+
* (`raw_tensor_inference.py`). Replaces the Node `onnxruntime-node` raw-tensor
|
|
124
|
+
* engine so the platform ships no Node ONNX runtime. The caller preprocesses to
|
|
125
|
+
* a Float32Array; this engine ships it to Python, which runs onnxruntime and
|
|
126
|
+
* returns the output tensor. Wire protocol = length-prefixed binary frames
|
|
127
|
+
* ([4B LE length][payload]).
|
|
127
128
|
*/
|
|
128
|
-
var
|
|
129
|
-
modelPath;
|
|
130
|
-
backend;
|
|
131
|
-
runtime = "onnx";
|
|
132
|
-
device;
|
|
133
|
-
session = null;
|
|
134
|
-
log;
|
|
135
|
-
constructor(modelPath, backend, logger) {
|
|
136
|
-
this.modelPath = modelPath;
|
|
137
|
-
this.backend = backend;
|
|
138
|
-
this.device = BACKEND_TO_DEVICE$1[backend] ?? "cpu";
|
|
139
|
-
this.log = logger ?? createNoopLogger();
|
|
140
|
-
}
|
|
141
|
-
async initialize() {
|
|
142
|
-
const ort = await import("onnxruntime-node");
|
|
143
|
-
const provider = this.backend === "coreml" ? "coreml" : this.backend === "cuda" ? "cuda" : "cpu";
|
|
144
|
-
const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
|
|
145
|
-
this.session = await ort.InferenceSession.create(absModelPath, { executionProviders: [provider] });
|
|
146
|
-
this.log.info("ONNX session loaded", { meta: {
|
|
147
|
-
modelPath: absModelPath,
|
|
148
|
-
backend: this.backend,
|
|
149
|
-
provider
|
|
150
|
-
} });
|
|
151
|
-
}
|
|
152
|
-
async run(input, inputShape) {
|
|
153
|
-
if (!this.session) throw new Error("NodeRawTensorEngine: not initialized — call initialize() first");
|
|
154
|
-
const ort = await import("onnxruntime-node");
|
|
155
|
-
const sess = this.session;
|
|
156
|
-
const inputName = sess.inputNames[0];
|
|
157
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
158
|
-
const feeds = { [inputName]: tensor };
|
|
159
|
-
const start = Date.now();
|
|
160
|
-
let results;
|
|
161
|
-
try {
|
|
162
|
-
results = await sess.run(feeds);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
165
|
-
this.log.error("Inference failed", { meta: { error: error.message } });
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
const outputName = sess.outputNames[0];
|
|
169
|
-
this.log.debug("Inference complete", { meta: {
|
|
170
|
-
durationMs: Date.now() - start,
|
|
171
|
-
outputKeys: [outputName],
|
|
172
|
-
preprocessMode: "raw-tensor"
|
|
173
|
-
} });
|
|
174
|
-
return results[outputName].data;
|
|
175
|
-
}
|
|
176
|
-
async dispose() {
|
|
177
|
-
this.session = null;
|
|
178
|
-
this.log.debug("Session disposed");
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region src/embedding-encoder/shared/image-utils.ts
|
|
183
|
-
/** Letterbox resize for YOLO: resize preserving aspect ratio, pad to square */
|
|
184
|
-
async function letterbox(jpeg, targetSize) {
|
|
185
|
-
const meta = await sharp(jpeg).metadata();
|
|
186
|
-
const originalWidth = meta.width ?? 0;
|
|
187
|
-
const originalHeight = meta.height ?? 0;
|
|
188
|
-
const scale = Math.min(targetSize / originalWidth, targetSize / originalHeight);
|
|
189
|
-
const scaledWidth = Math.round(originalWidth * scale);
|
|
190
|
-
const scaledHeight = Math.round(originalHeight * scale);
|
|
191
|
-
const padX = Math.floor((targetSize - scaledWidth) / 2);
|
|
192
|
-
const padY = Math.floor((targetSize - scaledHeight) / 2);
|
|
193
|
-
const { data } = await sharp(jpeg).resize(scaledWidth, scaledHeight).extend({
|
|
194
|
-
top: padY,
|
|
195
|
-
bottom: targetSize - scaledHeight - padY,
|
|
196
|
-
left: padX,
|
|
197
|
-
right: targetSize - scaledWidth - padX,
|
|
198
|
-
background: {
|
|
199
|
-
r: 114,
|
|
200
|
-
g: 114,
|
|
201
|
-
b: 114
|
|
202
|
-
}
|
|
203
|
-
}).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
204
|
-
const numPixels = targetSize * targetSize;
|
|
205
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
206
|
-
for (let i = 0; i < numPixels; i++) {
|
|
207
|
-
const srcBase = i * 3;
|
|
208
|
-
float32[0 * numPixels + i] = data[srcBase] / 255;
|
|
209
|
-
float32[1 * numPixels + i] = data[srcBase + 1] / 255;
|
|
210
|
-
float32[2 * numPixels + i] = data[srcBase + 2] / 255;
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
data: float32,
|
|
214
|
-
scale,
|
|
215
|
-
padX,
|
|
216
|
-
padY,
|
|
217
|
-
originalWidth,
|
|
218
|
-
originalHeight
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/** Resize and normalize to Float32Array */
|
|
222
|
-
async function resizeAndNormalize(jpeg, targetWidth, targetHeight, normalization, layout) {
|
|
223
|
-
const { data } = await sharp(jpeg).resize(targetWidth, targetHeight, { fit: "fill" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
224
|
-
const numPixels = targetWidth * targetHeight;
|
|
225
|
-
const float32 = new Float32Array(3 * numPixels);
|
|
226
|
-
const mean = [
|
|
227
|
-
.485,
|
|
228
|
-
.456,
|
|
229
|
-
.406
|
|
230
|
-
];
|
|
231
|
-
const std = [
|
|
232
|
-
.229,
|
|
233
|
-
.224,
|
|
234
|
-
.225
|
|
235
|
-
];
|
|
236
|
-
if (layout === "nchw") for (let i = 0; i < numPixels; i++) {
|
|
237
|
-
const srcBase = i * 3;
|
|
238
|
-
for (let c = 0; c < 3; c++) {
|
|
239
|
-
const raw = data[srcBase + c] / 255;
|
|
240
|
-
let val;
|
|
241
|
-
if (normalization === "zero-one") val = raw;
|
|
242
|
-
else if (normalization === "imagenet") val = (raw - mean[c]) / std[c];
|
|
243
|
-
else val = data[srcBase + c];
|
|
244
|
-
float32[c * numPixels + i] = val;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else for (let i = 0; i < numPixels; i++) {
|
|
248
|
-
const srcBase = i * 3;
|
|
249
|
-
for (let c = 0; c < 3; c++) {
|
|
250
|
-
const raw = data[srcBase + c] / 255;
|
|
251
|
-
let val;
|
|
252
|
-
if (normalization === "zero-one") val = raw;
|
|
253
|
-
else if (normalization === "imagenet") val = (raw - mean[c]) / std[c];
|
|
254
|
-
else val = data[srcBase + c];
|
|
255
|
-
float32[i * 3 + c] = val;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return float32;
|
|
259
|
-
}
|
|
260
|
-
//#endregion
|
|
261
|
-
//#region src/embedding-encoder/shared/node-engine.ts
|
|
262
|
-
var BACKEND_TO_PROVIDER = {
|
|
263
|
-
cpu: "cpu",
|
|
264
|
-
coreml: "coreml",
|
|
265
|
-
cuda: "cuda",
|
|
266
|
-
tensorrt: "tensorrt",
|
|
267
|
-
dml: "dml"
|
|
268
|
-
};
|
|
269
|
-
var BACKEND_TO_DEVICE = {
|
|
270
|
-
cpu: "cpu",
|
|
271
|
-
coreml: "gpu-mps",
|
|
272
|
-
cuda: "gpu-cuda",
|
|
273
|
-
tensorrt: "tensorrt"
|
|
274
|
-
};
|
|
275
|
-
var NodeInferenceEngine = class {
|
|
276
|
-
modelPath;
|
|
277
|
-
backend;
|
|
278
|
-
modelMeta;
|
|
279
|
-
runtime = "onnx";
|
|
280
|
-
device;
|
|
281
|
-
session = null;
|
|
282
|
-
log;
|
|
283
|
-
constructor(modelPath, backend, modelMeta, logger) {
|
|
284
|
-
this.modelPath = modelPath;
|
|
285
|
-
this.backend = backend;
|
|
286
|
-
this.modelMeta = modelMeta;
|
|
287
|
-
this.device = BACKEND_TO_DEVICE[backend] ?? "cpu";
|
|
288
|
-
this.log = logger ?? createNoopLogger();
|
|
289
|
-
}
|
|
290
|
-
async initialize() {
|
|
291
|
-
const ort = await import("onnxruntime-node");
|
|
292
|
-
const provider = BACKEND_TO_PROVIDER[this.backend] ?? "cpu";
|
|
293
|
-
const absModelPath = path.isAbsolute(this.modelPath) ? this.modelPath : path.resolve(process.cwd(), this.modelPath);
|
|
294
|
-
const sessionOptions = { executionProviders: [provider] };
|
|
295
|
-
this.session = await ort.InferenceSession.create(absModelPath, sessionOptions);
|
|
296
|
-
this.log.info("ONNX session loaded", { meta: {
|
|
297
|
-
modelPath: absModelPath,
|
|
298
|
-
backend: this.backend,
|
|
299
|
-
provider
|
|
300
|
-
} });
|
|
301
|
-
}
|
|
302
|
-
async infer(input) {
|
|
303
|
-
const jpeg = input.kind === "jpeg" ? input.data : await this.encodeRawAsJpeg(input.data, input.width, input.height, input.format);
|
|
304
|
-
const { data, letterboxMeta } = await this.preprocess(jpeg);
|
|
305
|
-
const { inputSize } = this.modelMeta;
|
|
306
|
-
const inputShape = this.modelMeta.preprocessMode === "letterbox" ? [
|
|
307
|
-
1,
|
|
308
|
-
3,
|
|
309
|
-
inputSize.height,
|
|
310
|
-
inputSize.width
|
|
311
|
-
] : [
|
|
312
|
-
1,
|
|
313
|
-
3,
|
|
314
|
-
inputSize.height,
|
|
315
|
-
inputSize.width
|
|
316
|
-
];
|
|
317
|
-
const start = Date.now();
|
|
318
|
-
let result;
|
|
319
|
-
try {
|
|
320
|
-
result = await this.runSession(data, inputShape);
|
|
321
|
-
} catch (err) {
|
|
322
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
323
|
-
this.log.error("Inference failed", { meta: { error: error.message } });
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
const durationMs = Date.now() - start;
|
|
327
|
-
if ("tensor" in result) {
|
|
328
|
-
this.log.debug("Inference complete", { meta: {
|
|
329
|
-
durationMs,
|
|
330
|
-
outputKeys: ["tensor"],
|
|
331
|
-
preprocessMode: this.modelMeta.preprocessMode
|
|
332
|
-
} });
|
|
333
|
-
return {
|
|
334
|
-
tensor: result.tensor,
|
|
335
|
-
letterbox: letterboxMeta,
|
|
336
|
-
inferenceMs: durationMs
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
this.log.debug("Inference complete", { meta: {
|
|
340
|
-
durationMs,
|
|
341
|
-
outputKeys: Object.keys(result.tensors),
|
|
342
|
-
preprocessMode: this.modelMeta.preprocessMode
|
|
343
|
-
} });
|
|
344
|
-
return {
|
|
345
|
-
tensors: result.tensors,
|
|
346
|
-
letterbox: letterboxMeta,
|
|
347
|
-
inferenceMs: durationMs
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
/** Preprocess JPEG to Float32Array using the configured mode */
|
|
351
|
-
async preprocess(jpeg) {
|
|
352
|
-
const { inputSize, inputNormalization, inputLayout, preprocessMode } = this.modelMeta;
|
|
353
|
-
if (preprocessMode === "letterbox") {
|
|
354
|
-
const result = await letterbox(jpeg, Math.max(inputSize.width, inputSize.height));
|
|
355
|
-
const letterboxMeta = {
|
|
356
|
-
scale: result.scale,
|
|
357
|
-
padX: result.padX,
|
|
358
|
-
padY: result.padY,
|
|
359
|
-
originalWidth: result.originalWidth,
|
|
360
|
-
originalHeight: result.originalHeight
|
|
361
|
-
};
|
|
362
|
-
return {
|
|
363
|
-
data: result.data,
|
|
364
|
-
letterboxMeta
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
return { data: await resizeAndNormalize(jpeg, inputSize.width, inputSize.height, inputNormalization, inputLayout) };
|
|
368
|
-
}
|
|
369
|
-
async encodeRawAsJpeg(raw, width, height, format) {
|
|
370
|
-
const sharp = (await import("sharp")).default;
|
|
371
|
-
return sharp(raw, { raw: {
|
|
372
|
-
width,
|
|
373
|
-
height,
|
|
374
|
-
channels: format === "gray" ? 1 : 3
|
|
375
|
-
} }).jpeg({
|
|
376
|
-
quality: 80,
|
|
377
|
-
mozjpeg: false
|
|
378
|
-
}).toBuffer();
|
|
379
|
-
}
|
|
380
|
-
/** Run an ONNX session with a single input, handling both single and multi-output models */
|
|
381
|
-
async runSession(input, inputShape) {
|
|
382
|
-
if (!this.session) throw new Error("NodeInferenceEngine: not initialized — call initialize() first");
|
|
383
|
-
const ort = await import("onnxruntime-node");
|
|
384
|
-
const sess = this.session;
|
|
385
|
-
const inputName = sess.inputNames[0];
|
|
386
|
-
const tensor = new ort.Tensor("float32", input, [...inputShape]);
|
|
387
|
-
const feeds = { [inputName]: tensor };
|
|
388
|
-
const results = await sess.run(feeds);
|
|
389
|
-
const outputNames = sess.outputNames;
|
|
390
|
-
if (outputNames.length === 1) return { tensor: results[outputNames[0]].data };
|
|
391
|
-
const tensors = {};
|
|
392
|
-
for (const name of outputNames) tensors[name] = results[name].data;
|
|
393
|
-
return { tensors };
|
|
394
|
-
}
|
|
395
|
-
async run(input, inputShape) {
|
|
396
|
-
const result = await this.runSession(input, inputShape);
|
|
397
|
-
if ("tensor" in result) return result.tensor;
|
|
398
|
-
const firstKey = Object.keys(result.tensors)[0];
|
|
399
|
-
return result.tensors[firstKey];
|
|
400
|
-
}
|
|
401
|
-
async dispose() {
|
|
402
|
-
this.session = null;
|
|
403
|
-
this.log.debug("Session disposed");
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
//#endregion
|
|
407
|
-
//#region src/embedding-encoder/shared/python-engine.ts
|
|
408
|
-
var PythonInferenceEngine = class {
|
|
129
|
+
var PythonRawTensorEngine = class {
|
|
409
130
|
pythonPath;
|
|
410
131
|
scriptPath;
|
|
411
132
|
modelPath;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
device;
|
|
133
|
+
runtime = "onnx";
|
|
134
|
+
device = "cpu";
|
|
415
135
|
process = null;
|
|
416
136
|
receiveBuffer = Buffer.alloc(0);
|
|
417
137
|
pendingResolve = null;
|
|
418
138
|
pendingReject = null;
|
|
419
139
|
log;
|
|
420
|
-
constructor(pythonPath, scriptPath,
|
|
140
|
+
constructor(pythonPath, scriptPath, modelPath, logger) {
|
|
421
141
|
this.pythonPath = pythonPath;
|
|
422
142
|
this.scriptPath = scriptPath;
|
|
423
143
|
this.modelPath = modelPath;
|
|
424
|
-
this.extraArgs = extraArgs;
|
|
425
|
-
this.runtime = runtime;
|
|
426
|
-
const runtimeDeviceMap = {
|
|
427
|
-
onnx: "cpu",
|
|
428
|
-
coreml: "gpu-mps",
|
|
429
|
-
pytorch: "cpu",
|
|
430
|
-
openvino: "cpu",
|
|
431
|
-
tflite: "cpu"
|
|
432
|
-
};
|
|
433
|
-
this.device = runtimeDeviceMap[runtime];
|
|
434
144
|
this.log = logger ?? createNoopLogger();
|
|
435
145
|
}
|
|
436
146
|
async initialize() {
|
|
437
|
-
|
|
438
|
-
this.scriptPath,
|
|
439
|
-
this.modelPath,
|
|
440
|
-
...this.extraArgs
|
|
441
|
-
];
|
|
442
|
-
this.process = spawn(this.pythonPath, args, { stdio: [
|
|
147
|
+
this.process = spawn(this.pythonPath, [this.scriptPath, this.modelPath], { stdio: [
|
|
443
148
|
"pipe",
|
|
444
149
|
"pipe",
|
|
445
150
|
"pipe"
|
|
446
151
|
] });
|
|
447
|
-
if (!this.process.stdout || !this.process.stdin) throw new Error("PythonInferenceEngine: failed to create process pipes");
|
|
448
|
-
this.log.info("Python process started", { meta: {
|
|
449
|
-
pythonPath: this.pythonPath,
|
|
450
|
-
scriptPath: this.scriptPath,
|
|
451
|
-
modelPath: this.modelPath
|
|
452
|
-
} });
|
|
453
152
|
this.process.stderr?.on("data", (chunk) => {
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
const trimmed = line.trim();
|
|
457
|
-
if (trimmed) this.log.warn(trimmed);
|
|
458
|
-
}
|
|
153
|
+
const text = chunk.toString().trim();
|
|
154
|
+
if (text) this.log.warn(text);
|
|
459
155
|
});
|
|
460
156
|
this.process.on("error", (err) => {
|
|
461
|
-
this.log.error("
|
|
157
|
+
this.log.error("Python raw-tensor process error", { meta: { error: err.message } });
|
|
462
158
|
this.pendingReject?.(err);
|
|
463
159
|
this.pendingReject = null;
|
|
464
160
|
this.pendingResolve = null;
|
|
465
161
|
});
|
|
466
162
|
this.process.on("exit", (code) => {
|
|
467
|
-
if (code !== 0) {
|
|
468
|
-
|
|
469
|
-
const err = /* @__PURE__ */ new Error(`PythonInferenceEngine: process exited with code ${code}`);
|
|
163
|
+
if (code !== 0 && code !== null) {
|
|
164
|
+
const err = /* @__PURE__ */ new Error(`PythonRawTensorEngine: process exited with code ${code}`);
|
|
470
165
|
this.pendingReject?.(err);
|
|
471
166
|
this.pendingReject = null;
|
|
472
167
|
this.pendingResolve = null;
|
|
@@ -474,70 +169,29 @@ var PythonInferenceEngine = class {
|
|
|
474
169
|
});
|
|
475
170
|
this.process.stdout.on("data", (chunk) => {
|
|
476
171
|
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
477
|
-
this.
|
|
172
|
+
this.tryReceive();
|
|
478
173
|
});
|
|
479
|
-
await
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
clearTimeout(timeout);
|
|
483
|
-
reject(err);
|
|
484
|
-
});
|
|
485
|
-
this.process?.on("exit", (code) => {
|
|
486
|
-
clearTimeout(timeout);
|
|
487
|
-
if (code !== 0) reject(/* @__PURE__ */ new Error(`PythonInferenceEngine: process exited early with code ${code}`));
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
_tryReceive() {
|
|
492
|
-
if (this.receiveBuffer.length < 4) return;
|
|
493
|
-
const length = this.receiveBuffer.readUInt32LE(0);
|
|
494
|
-
if (this.receiveBuffer.length < 4 + length) return;
|
|
495
|
-
const jsonBytes = this.receiveBuffer.subarray(4, 4 + length);
|
|
496
|
-
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
497
|
-
const resolve = this.pendingResolve;
|
|
498
|
-
const reject = this.pendingReject;
|
|
499
|
-
this.pendingResolve = null;
|
|
500
|
-
this.pendingReject = null;
|
|
501
|
-
if (!resolve) return;
|
|
502
|
-
try {
|
|
503
|
-
resolve(JSON.parse(jsonBytes.toString("utf8")));
|
|
504
|
-
} catch (err) {
|
|
505
|
-
reject?.(err instanceof Error ? err : new Error(String(err)));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
/** Run inference, returning structured detection results. Encodes raw input to JPEG when needed. */
|
|
509
|
-
async infer(input) {
|
|
510
|
-
const start = Date.now();
|
|
511
|
-
const jpeg = input.kind === "jpeg" ? input.data : await this.encodeRawAsJpeg(input.data, input.width, input.height, input.format);
|
|
512
|
-
const result = await this.sendJpeg(jpeg);
|
|
513
|
-
const durationMs = Date.now() - start;
|
|
514
|
-
this.log.debug("Inference complete", { meta: { durationMs } });
|
|
515
|
-
return {
|
|
516
|
-
structured: result,
|
|
517
|
-
inferenceMs: durationMs
|
|
518
|
-
};
|
|
174
|
+
const ready = await this.receiveFrame();
|
|
175
|
+
if (ready.length !== 1 || ready[0] !== 1) throw new Error("PythonRawTensorEngine: unexpected ready frame");
|
|
176
|
+
this.log.info("ONNX raw-tensor engine ready (embedded Python)", { meta: { modelPath: this.modelPath } });
|
|
519
177
|
}
|
|
520
|
-
async
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const lengthBuf = Buffer.allocUnsafe(4);
|
|
538
|
-
lengthBuf.writeUInt32LE(jpeg.length, 0);
|
|
539
|
-
this.process.stdin.write(Buffer.concat([lengthBuf, jpeg]));
|
|
540
|
-
});
|
|
178
|
+
async run(input, inputShape) {
|
|
179
|
+
if (!this.process?.stdin) throw new Error("PythonRawTensorEngine: not initialized — call initialize() first");
|
|
180
|
+
const ndims = inputShape.length;
|
|
181
|
+
const meta = Buffer.allocUnsafe(1 + ndims * 4);
|
|
182
|
+
meta.writeUInt8(ndims, 0);
|
|
183
|
+
for (let i = 0; i < ndims; i++) meta.writeUInt32LE(inputShape[i], 1 + i * 4);
|
|
184
|
+
const dataBuf = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
185
|
+
const payload = Buffer.concat([meta, dataBuf]);
|
|
186
|
+
const lenBuf = Buffer.allocUnsafe(4);
|
|
187
|
+
lenBuf.writeUInt32LE(payload.length, 0);
|
|
188
|
+
this.process.stdin.write(Buffer.concat([lenBuf, payload]));
|
|
189
|
+
const resp = await this.receiveFrame();
|
|
190
|
+
const floatStart = 1 + resp.readUInt8(0) * 4;
|
|
191
|
+
const count = (resp.length - floatStart) / 4;
|
|
192
|
+
const out = new Float32Array(count);
|
|
193
|
+
for (let i = 0; i < count; i++) out[i] = resp.readFloatLE(floatStart + i * 4);
|
|
194
|
+
return out;
|
|
541
195
|
}
|
|
542
196
|
async dispose() {
|
|
543
197
|
const proc = this.process;
|
|
@@ -545,168 +199,37 @@ var PythonInferenceEngine = class {
|
|
|
545
199
|
this.process = null;
|
|
546
200
|
proc.stdin?.end();
|
|
547
201
|
proc.kill("SIGTERM");
|
|
548
|
-
|
|
202
|
+
await new Promise((resolve) => {
|
|
549
203
|
const timer = setTimeout(() => {
|
|
550
|
-
|
|
204
|
+
try {
|
|
205
|
+
proc.kill("SIGKILL");
|
|
206
|
+
} catch {}
|
|
207
|
+
resolve();
|
|
551
208
|
}, 5e3);
|
|
552
209
|
proc.once("exit", () => {
|
|
553
210
|
clearTimeout(timer);
|
|
554
|
-
resolve(
|
|
211
|
+
resolve();
|
|
555
212
|
});
|
|
556
|
-
})
|
|
557
|
-
try {
|
|
558
|
-
proc.kill("SIGKILL");
|
|
559
|
-
} catch {}
|
|
560
|
-
this.log.warn("Python process did not exit gracefully — sent SIGKILL");
|
|
561
|
-
} else this.log.debug("Python process terminated");
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
//#endregion
|
|
565
|
-
//#region src/embedding-encoder/shared/engine-resolver.ts
|
|
566
|
-
/** Priority order for auto-selection of ONNX backends */
|
|
567
|
-
var AUTO_BACKEND_PRIORITY = [
|
|
568
|
-
"coreml",
|
|
569
|
-
"cuda",
|
|
570
|
-
"tensorrt",
|
|
571
|
-
"cpu"
|
|
572
|
-
];
|
|
573
|
-
var BACKEND_TO_FORMAT = BACKEND_TO_FORMAT$1;
|
|
574
|
-
var RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT$1;
|
|
575
|
-
function extractModelMeta(entry) {
|
|
576
|
-
return {
|
|
577
|
-
inputSize: entry.inputSize,
|
|
578
|
-
inputNormalization: entry.inputNormalization ?? "zero-one",
|
|
579
|
-
inputLayout: entry.inputLayout ?? "nchw",
|
|
580
|
-
preprocessMode: entry.preprocessMode ?? "letterbox"
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
function modelFilePath(modelsDir, modelEntry, format) {
|
|
584
|
-
const formatEntry = modelEntry.formats[format];
|
|
585
|
-
if (!formatEntry) throw new Error(`Model ${modelEntry.id} has no ${format} format`);
|
|
586
|
-
const urlParts = formatEntry.url.split("/");
|
|
587
|
-
const filename = urlParts[urlParts.length - 1] ?? `${modelEntry.id}.${format}`;
|
|
588
|
-
return path.join(modelsDir, filename);
|
|
589
|
-
}
|
|
590
|
-
function modelExists(filePath) {
|
|
591
|
-
try {
|
|
592
|
-
return fs.existsSync(filePath);
|
|
593
|
-
} catch {
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
async function resolveEngine(options) {
|
|
598
|
-
const { runtime, backend, modelEntry, modelsDir, models } = options;
|
|
599
|
-
const log = options.logger ?? createNoopLogger();
|
|
600
|
-
let selectedFormat;
|
|
601
|
-
let selectedBackend;
|
|
602
|
-
if (runtime === "auto") {
|
|
603
|
-
const available = await probeOnnxBackends();
|
|
604
|
-
let chosen = null;
|
|
605
|
-
for (const b of AUTO_BACKEND_PRIORITY) {
|
|
606
|
-
if (!available.includes(b)) continue;
|
|
607
|
-
const fmt = BACKEND_TO_FORMAT[b];
|
|
608
|
-
if (!fmt) continue;
|
|
609
|
-
if (!modelEntry.formats[fmt]) continue;
|
|
610
|
-
chosen = {
|
|
611
|
-
backend: b,
|
|
612
|
-
format: fmt
|
|
613
|
-
};
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
616
|
-
if (!chosen) throw new Error(`resolveEngine: no compatible backend found for model ${modelEntry.id}. Available backends: ${available.join(", ")}`);
|
|
617
|
-
selectedFormat = chosen.format;
|
|
618
|
-
selectedBackend = chosen.backend;
|
|
619
|
-
} else {
|
|
620
|
-
const fmt = RUNTIME_TO_FORMAT[runtime];
|
|
621
|
-
if (!fmt) throw new Error(`resolveEngine: unsupported runtime "${runtime}"`);
|
|
622
|
-
if (!modelEntry.formats[fmt]) if (fmt !== "onnx" && modelEntry.formats["onnx"]) {
|
|
623
|
-
selectedFormat = "onnx";
|
|
624
|
-
selectedBackend = backend || "cpu";
|
|
625
|
-
} else throw new Error(`resolveEngine: model ${modelEntry.id} has no ${fmt} format for runtime ${runtime}`);
|
|
626
|
-
else {
|
|
627
|
-
selectedFormat = fmt;
|
|
628
|
-
selectedBackend = runtime === "onnx" ? backend || "cpu" : runtime;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
let modelPath;
|
|
632
|
-
if (models) modelPath = await models.ensure(modelEntry.id, selectedFormat);
|
|
633
|
-
else {
|
|
634
|
-
modelPath = modelFilePath(modelsDir, modelEntry, selectedFormat);
|
|
635
|
-
if (!modelExists(modelPath)) throw new Error(`resolveEngine: model file not found at ${modelPath} and no model service provided`);
|
|
636
|
-
}
|
|
637
|
-
log.info("Engine resolved", { meta: {
|
|
638
|
-
format: selectedFormat,
|
|
639
|
-
backend: selectedBackend,
|
|
640
|
-
modelId: modelEntry.id
|
|
641
|
-
} });
|
|
642
|
-
if (selectedFormat === "onnx") {
|
|
643
|
-
const engine = new NodeInferenceEngine(modelPath, selectedBackend, extractModelMeta(modelEntry), options.logger);
|
|
644
|
-
await engine.initialize();
|
|
645
|
-
return {
|
|
646
|
-
engine,
|
|
647
|
-
format: selectedFormat,
|
|
648
|
-
modelPath
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
const effectiveRuntime = runtime === "auto" ? selectedBackend : runtime;
|
|
652
|
-
let { pythonPath } = options;
|
|
653
|
-
if (!pythonPath) {
|
|
654
|
-
const { execFileSync: efs } = await import("node:child_process");
|
|
655
|
-
for (const cmd of ["python3", "python"]) try {
|
|
656
|
-
efs(cmd, ["--version"], {
|
|
657
|
-
timeout: 3e3,
|
|
658
|
-
stdio: "ignore"
|
|
659
|
-
});
|
|
660
|
-
pythonPath = cmd;
|
|
661
|
-
break;
|
|
662
|
-
} catch {}
|
|
213
|
+
});
|
|
663
214
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
path.join(__dirname, "../../../python", scriptName)
|
|
670
|
-
];
|
|
671
|
-
const scriptPath = candidates.find((p) => fs.existsSync(p));
|
|
672
|
-
if (!scriptPath) throw new Error(`resolveEngine: Python script "${scriptName}" not found. Searched:\n${candidates.join("\n")}`);
|
|
673
|
-
const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
|
|
674
|
-
const engine = new PythonInferenceEngine(pythonPath, scriptPath, effectiveRuntime, modelPath, [`--input-size=${inputSize}`, `--confidence=0.25`], options.logger);
|
|
675
|
-
await engine.initialize();
|
|
676
|
-
return {
|
|
677
|
-
engine,
|
|
678
|
-
format: selectedFormat,
|
|
679
|
-
modelPath
|
|
680
|
-
};
|
|
215
|
+
receiveFrame() {
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
this.pendingResolve = resolve;
|
|
218
|
+
this.pendingReject = reject;
|
|
219
|
+
});
|
|
681
220
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
221
|
+
tryReceive() {
|
|
222
|
+
if (this.receiveBuffer.length < 4) return;
|
|
223
|
+
const length = this.receiveBuffer.readUInt32LE(0);
|
|
224
|
+
if (this.receiveBuffer.length < 4 + length) return;
|
|
225
|
+
const payload = Buffer.from(this.receiveBuffer.subarray(4, 4 + length));
|
|
226
|
+
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
227
|
+
const resolve = this.pendingResolve;
|
|
228
|
+
this.pendingResolve = null;
|
|
229
|
+
this.pendingReject = null;
|
|
230
|
+
resolve?.(payload);
|
|
691
231
|
}
|
|
692
|
-
|
|
693
|
-
}
|
|
694
|
-
/** Probe which ONNX execution providers are available on this system */
|
|
695
|
-
async function probeOnnxBackends() {
|
|
696
|
-
const available = ["cpu"];
|
|
697
|
-
try {
|
|
698
|
-
const ort = await import("onnxruntime-node");
|
|
699
|
-
const providers = ort.env?.webgl?.disabled !== void 0 ? ort.InferenceSession.getAvailableProviders?.() ?? [] : [];
|
|
700
|
-
for (const p of providers) {
|
|
701
|
-
const normalized = p.toLowerCase().replace("executionprovider", "");
|
|
702
|
-
if (normalized === "coreml") available.push("coreml");
|
|
703
|
-
else if (normalized === "cuda") available.push("cuda");
|
|
704
|
-
else if (normalized === "tensorrt") available.push("tensorrt");
|
|
705
|
-
}
|
|
706
|
-
} catch {}
|
|
707
|
-
if (process.platform === "darwin" && !available.includes("coreml")) available.push("coreml");
|
|
708
|
-
return [...new Set(available)];
|
|
709
|
-
}
|
|
232
|
+
};
|
|
710
233
|
//#endregion
|
|
711
234
|
//#region src/embedding-encoder/addon/clip-models.ts
|
|
712
235
|
var CLIP_MODEL_META = {
|
|
@@ -783,10 +306,7 @@ function l2Normalize(vec) {
|
|
|
783
306
|
var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
784
307
|
imageRawEngine = null;
|
|
785
308
|
textRawEngine = null;
|
|
786
|
-
imagePythonEngine = null;
|
|
787
|
-
textPythonEngine = null;
|
|
788
309
|
models = null;
|
|
789
|
-
isPython = false;
|
|
790
310
|
constructor() {
|
|
791
311
|
super({
|
|
792
312
|
modelId: DEFAULT_CLIP_MODEL,
|
|
@@ -810,19 +330,6 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
810
330
|
await this.ensureImageEngine();
|
|
811
331
|
const meta = getModelMeta(this.config.modelId);
|
|
812
332
|
const start = Date.now();
|
|
813
|
-
if (this.isPython && this.imagePythonEngine) {
|
|
814
|
-
const jpegBuffer = Buffer.isBuffer(crop) ? crop : Buffer.from(crop);
|
|
815
|
-
const result = await this.imagePythonEngine.infer({
|
|
816
|
-
kind: "jpeg",
|
|
817
|
-
data: jpegBuffer
|
|
818
|
-
});
|
|
819
|
-
const rawEmbedding = result.structured?.["embedding"];
|
|
820
|
-
const normalized = l2Normalize(new Float32Array(rawEmbedding));
|
|
821
|
-
return {
|
|
822
|
-
embedding: Array.from(normalized),
|
|
823
|
-
inferenceMs: result.inferenceMs ?? Date.now() - start
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
333
|
const preprocessed = preprocessForClip(Buffer.isBuffer(crop) ? crop : Buffer.from(crop), width, height, meta.inputSize, meta.inputSize);
|
|
827
334
|
const output = await this.imageRawEngine.run(preprocessed, [
|
|
828
335
|
1,
|
|
@@ -842,19 +349,6 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
842
349
|
await this.ensureTextEngine();
|
|
843
350
|
const meta = getModelMeta(this.config.modelId);
|
|
844
351
|
const start = Date.now();
|
|
845
|
-
if (this.isPython && this.textPythonEngine) {
|
|
846
|
-
const textBuffer = Buffer.from(JSON.stringify({ text }), "utf-8");
|
|
847
|
-
const result = await this.textPythonEngine.infer({
|
|
848
|
-
kind: "jpeg",
|
|
849
|
-
data: textBuffer
|
|
850
|
-
});
|
|
851
|
-
const rawEmbedding = result.structured?.["embedding"];
|
|
852
|
-
const normalized = l2Normalize(new Float32Array(rawEmbedding));
|
|
853
|
-
return {
|
|
854
|
-
embedding: Array.from(normalized),
|
|
855
|
-
inferenceMs: result.inferenceMs ?? Date.now() - start
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
352
|
const tokenIds = clipTokenize(text);
|
|
859
353
|
const inputTensor = new Float32Array(tokenIds);
|
|
860
354
|
const output = await this.textRawEngine.run(inputTensor, [1, tokenIds.length]);
|
|
@@ -870,57 +364,42 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
870
364
|
return {
|
|
871
365
|
modelId: this.config.modelId,
|
|
872
366
|
embeddingDim: meta.embeddingDim,
|
|
873
|
-
ready: this.imageRawEngine !== null
|
|
367
|
+
ready: this.imageRawEngine !== null
|
|
874
368
|
};
|
|
875
369
|
}
|
|
876
370
|
async ensureImageEngine() {
|
|
877
|
-
if (this.imageRawEngine
|
|
371
|
+
if (this.imageRawEngine) return;
|
|
878
372
|
const meta = getModelMeta(this.config.modelId);
|
|
879
373
|
const imageEntry = CLIP_IMAGE_MODELS.find((m) => m.id === meta.imageModelId);
|
|
880
374
|
if (!imageEntry) throw new Error(`EmbeddingEncoderAddon: unknown image model "${meta.imageModelId}"`);
|
|
881
375
|
await this.resolveForEntry(imageEntry, "image");
|
|
882
376
|
}
|
|
883
377
|
async ensureTextEngine() {
|
|
884
|
-
if (this.textRawEngine
|
|
378
|
+
if (this.textRawEngine) return;
|
|
885
379
|
const meta = getModelMeta(this.config.modelId);
|
|
886
380
|
const textEntry = CLIP_TEXT_MODELS.find((m) => m.id === meta.textModelId);
|
|
887
381
|
if (!textEntry) throw new Error(`EmbeddingEncoderAddon: unknown text model "${meta.textModelId}"`);
|
|
888
382
|
await this.resolveForEntry(textEntry, "text");
|
|
889
383
|
}
|
|
890
384
|
async resolveForEntry(entry, target) {
|
|
891
|
-
const runtime = this.config.runtime === "auto" ? "auto" : this.config.runtime === "node" ? "onnx" : this.config.runtime;
|
|
892
|
-
const modelsDir = this.models.getModelsDir();
|
|
893
385
|
const engineLogger = this.ctx.logger.withTags({
|
|
894
386
|
modelId: entry.id,
|
|
895
387
|
runtime: this.config.runtime,
|
|
896
388
|
backend: this.config.backend
|
|
897
389
|
});
|
|
898
|
-
await this.models.ensure(entry.id, "onnx");
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (resolved.format !== "onnx") {
|
|
908
|
-
this.isPython = true;
|
|
909
|
-
if (target === "image") this.imagePythonEngine = resolved.engine;
|
|
910
|
-
else this.textPythonEngine = resolved.engine;
|
|
911
|
-
} else {
|
|
912
|
-
const rawEngine = new NodeRawTensorEngine(resolved.modelPath, this.config.backend, engineLogger);
|
|
913
|
-
await rawEngine.initialize();
|
|
914
|
-
await resolved.engine.dispose();
|
|
915
|
-
if (target === "image") this.imageRawEngine = rawEngine;
|
|
916
|
-
else this.textRawEngine = rawEngine;
|
|
917
|
-
}
|
|
390
|
+
const modelPath = await this.models.ensure(entry.id, "onnx");
|
|
391
|
+
const pythonPath = await this.ctx.deps.ensurePython();
|
|
392
|
+
if (!pythonPath) throw new Error("EmbeddingEncoder: embedded Python is unavailable — cannot run ONNX embeddings. ctx.deps.ensurePython() returned null (portable Python download likely failed).");
|
|
393
|
+
const pythonDir = resolveEmbeddingPythonDir();
|
|
394
|
+
await this.ctx.deps.installPythonRequirements(path.join(pythonDir, "requirements-embedding.txt"));
|
|
395
|
+
const rawEngine = new PythonRawTensorEngine(pythonPath, path.join(pythonDir, "raw_tensor_inference.py"), modelPath, engineLogger);
|
|
396
|
+
await rawEngine.initialize();
|
|
397
|
+
if (target === "image") this.imageRawEngine = rawEngine;
|
|
398
|
+
else this.textRawEngine = rawEngine;
|
|
918
399
|
}
|
|
919
400
|
async onShutdown() {
|
|
920
401
|
await this.imageRawEngine?.dispose();
|
|
921
402
|
await this.textRawEngine?.dispose();
|
|
922
|
-
await this.imagePythonEngine?.dispose();
|
|
923
|
-
await this.textPythonEngine?.dispose();
|
|
924
403
|
}
|
|
925
404
|
globalSettingsSchema() {
|
|
926
405
|
return this.schema({ sections: [{
|
|
@@ -939,22 +418,15 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
939
418
|
type: "select",
|
|
940
419
|
key: "runtime",
|
|
941
420
|
label: "Runtime",
|
|
942
|
-
description: "Inference runtime
|
|
421
|
+
description: "Inference runtime — ONNX runs in the embedded Python.",
|
|
943
422
|
default: "auto",
|
|
944
|
-
options: [
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
value: "node"
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
label: "Python",
|
|
955
|
-
value: "python"
|
|
956
|
-
}
|
|
957
|
-
]
|
|
423
|
+
options: [{
|
|
424
|
+
label: "Auto",
|
|
425
|
+
value: "auto"
|
|
426
|
+
}, {
|
|
427
|
+
label: "Python",
|
|
428
|
+
value: "python"
|
|
429
|
+
}]
|
|
958
430
|
},
|
|
959
431
|
{
|
|
960
432
|
type: "select",
|
|
@@ -997,5 +469,20 @@ function clipTokenize(text, maxLength = 77) {
|
|
|
997
469
|
while (tokens.length < maxLength) tokens.push(0);
|
|
998
470
|
return tokens;
|
|
999
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Locate the addon's bundled `python/` dir (holds `raw_tensor_inference.py` +
|
|
474
|
+
* `requirements-embedding.txt`). Published package first, then `__dirname`
|
|
475
|
+
* candidates for the in-tree dev build.
|
|
476
|
+
*/
|
|
477
|
+
function resolveEmbeddingPythonDir() {
|
|
478
|
+
const candidates = [];
|
|
479
|
+
try {
|
|
480
|
+
const pkgPath = __require.resolve("@camstack/addon-post-analysis/package.json");
|
|
481
|
+
candidates.push(path.join(path.dirname(pkgPath), "python"));
|
|
482
|
+
} catch {}
|
|
483
|
+
candidates.push(path.join(__dirname, "../../python"), path.join(__dirname, "../../../python"), path.join(__dirname, "../python"), path.join(__dirname, "../../../../python"));
|
|
484
|
+
for (const c of candidates) if (fs.existsSync(path.join(c, "raw_tensor_inference.py"))) return c;
|
|
485
|
+
throw new Error(`EmbeddingEncoder: python/ dir (raw_tensor_inference.py) not found. Searched:\n${candidates.join("\n")}`);
|
|
486
|
+
}
|
|
1000
487
|
//#endregion
|
|
1001
488
|
export { EmbeddingEncoderAddon, EmbeddingEncoderAddon as default };
|