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