@camstack/addon-pipeline 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +5 -5
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/audio-codec-nodeav/index.js +1 -1
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +276 -61
- package/dist/detection-pipeline/index.mjs +268 -55
- package/dist/{dist-7ewQjTle.js → dist-C1goFC50.js} +4 -4
- package/dist/{dist-C5jnNl0n.mjs → dist-XRXnZrVC.mjs} +4 -4
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +2 -2
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-qX99--rF.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-dGO6_Xee.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B0Z2W5UM.mjs +26 -0
- package/dist/stream-broker/{hostInit-Bx41KdYV.mjs → hostInit-ClKL4kbX.mjs} +3 -3
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +2 -2
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/MaskShapeCanvas-DI4BY7W2-DEYqSd2k.js +33 -0
- package/embed-dist/assets/MotionZonesSettings-C1EEbk2V-CRvYkSju.js +1 -0
- package/embed-dist/assets/PrivacyMaskSettings-APgPLF7p-CIq73-7y.js +1 -0
- package/embed-dist/assets/index-C5Az4io-.js +80 -0
- package/embed-dist/index.html +1 -1
- package/package.json +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C9WX5HNw.mjs +0 -26
- package/embed-dist/assets/index-B8VlSD0-.js +0 -150
|
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-C1goFC50.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
10
10
|
node_path = require_chunk.__toESM(node_path);
|
|
11
|
-
let
|
|
11
|
+
let _camstack_system = require("@camstack/system");
|
|
12
12
|
//#region src/audio-analyzer/audio-pipeline.ts
|
|
13
13
|
/**
|
|
14
14
|
* Create the appropriate audio pipeline.
|
|
@@ -27,7 +27,7 @@ async function createAudioPipeline(modelsDir, logger, options) {
|
|
|
27
27
|
* Canonical model URLs on the camstack HuggingFace mirror. Mirrors the
|
|
28
28
|
* convention every detection model follows (single point of truth =
|
|
29
29
|
* `HF_BASE_URL` from `@camstack/types`); the auto-download path uses
|
|
30
|
-
* `downloadFile` from `@camstack/
|
|
30
|
+
* `downloadFile` from `@camstack/system`, the SAME helper detection-
|
|
31
31
|
* pipeline uses to materialise its YOLO/face/plate models.
|
|
32
32
|
*
|
|
33
33
|
* {domain}/{family}/{format}/{filename}
|
|
@@ -83,7 +83,7 @@ var YamnetPythonPipeline = class {
|
|
|
83
83
|
url: YAMNET_MODEL_URL,
|
|
84
84
|
dest: modelPath
|
|
85
85
|
} });
|
|
86
|
-
await (0,
|
|
86
|
+
await (0, _camstack_system.downloadFile)(YAMNET_MODEL_URL, modelPath);
|
|
87
87
|
this.log.info("YAMNet ONNX model downloaded", { meta: { sizeBytes: node_fs.statSync(modelPath).size } });
|
|
88
88
|
}
|
|
89
89
|
if (!node_fs.existsSync(labelsPath)) {
|
|
@@ -91,7 +91,7 @@ var YamnetPythonPipeline = class {
|
|
|
91
91
|
url: YAMNET_LABELS_URL,
|
|
92
92
|
dest: labelsPath
|
|
93
93
|
} });
|
|
94
|
-
await (0,
|
|
94
|
+
await (0, _camstack_system.downloadFile)(YAMNET_LABELS_URL, labelsPath);
|
|
95
95
|
}
|
|
96
96
|
const pythonDir = resolveAudioPythonDir();
|
|
97
97
|
if (this.installPythonRequirements) await this.installPythonRequirements(node_path.join(pythonDir, "requirements-audio.txt"));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { L as mapAudioLabelToMacro, P as hydrateSchema, S as audioAnalyzerCapability, c as DEFAULT_AUDIO_ANALYZER_CONFIG, i as BaseAddon, j as errMsg, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, x as audioAnalysisCapability } from "../dist-
|
|
2
|
+
import { L as mapAudioLabelToMacro, P as hydrateSchema, S as audioAnalyzerCapability, c as DEFAULT_AUDIO_ANALYZER_CONFIG, i as BaseAddon, j as errMsg, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, x as audioAnalysisCapability } from "../dist-XRXnZrVC.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
5
|
-
import { downloadFile } from "@camstack/
|
|
5
|
+
import { downloadFile } from "@camstack/system";
|
|
6
6
|
//#region src/audio-analyzer/audio-pipeline.ts
|
|
7
7
|
/**
|
|
8
8
|
* Create the appropriate audio pipeline.
|
|
@@ -21,7 +21,7 @@ async function createAudioPipeline(modelsDir, logger, options) {
|
|
|
21
21
|
* Canonical model URLs on the camstack HuggingFace mirror. Mirrors the
|
|
22
22
|
* convention every detection model follows (single point of truth =
|
|
23
23
|
* `HF_BASE_URL` from `@camstack/types`); the auto-download path uses
|
|
24
|
-
* `downloadFile` from `@camstack/
|
|
24
|
+
* `downloadFile` from `@camstack/system`, the SAME helper detection-
|
|
25
25
|
* pipeline uses to materialise its YOLO/face/plate models.
|
|
26
26
|
*
|
|
27
27
|
* {domain}/{family}/{format}/{filename}
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-C1goFC50.js");
|
|
6
6
|
const require_codec_runtime = require("../codec-runtime-BOk-13PN.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/audio-codec-nodeav/addon/index.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as audioCodecCapability, i as BaseAddon, j as errMsg } from "../dist-
|
|
1
|
+
import { C as audioCodecCapability, i as BaseAddon, j as errMsg } from "../dist-XRXnZrVC.mjs";
|
|
2
2
|
import { t as DecodeRuntime } from "../codec-runtime-BsqlEjPi.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/audio-codec-nodeav/addon/index.ts
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-C1goFC50.js");
|
|
6
6
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/decoder-nodeav/frame-ring-sink.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as decoderCapability, _ as RingBuffer, h as HWACCEL_OPTIONS, i as BaseAddon, j as errMsg, l as DEFAULT_DECODER_HWACCEL_CONFIG } from "../dist-
|
|
1
|
+
import { O as decoderCapability, _ as RingBuffer, h as HWACCEL_OPTIONS, i as BaseAddon, j as errMsg, l as DEFAULT_DECODER_HWACCEL_CONFIG } from "../dist-XRXnZrVC.mjs";
|
|
2
2
|
import { FrameRingReaderCache, FrameRingWriter, MIN_RING_SLOTS, computeSegmentSize, computeSlotByteLength, createSegment, deriveSlotCount } from "@camstack/shm-ring";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/decoder-nodeav/frame-ring-sink.ts
|
|
@@ -3,14 +3,14 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-C1goFC50.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
10
10
|
node_path = require_chunk.__toESM(node_path);
|
|
11
11
|
let node_os = require("node:os");
|
|
12
12
|
node_os = require_chunk.__toESM(node_os);
|
|
13
|
-
let
|
|
13
|
+
let _camstack_system = require("@camstack/system");
|
|
14
14
|
let node_child_process = require("node:child_process");
|
|
15
15
|
let sharp = require("sharp");
|
|
16
16
|
sharp = require_chunk.__toESM(sharp);
|
|
@@ -3873,6 +3873,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
3873
3873
|
device: "cuda"
|
|
3874
3874
|
};
|
|
3875
3875
|
} catch {}
|
|
3876
|
+
try {
|
|
3877
|
+
const fsmod = require("node:fs");
|
|
3878
|
+
const isIntel = require("node:os").cpus()[0]?.model?.includes("Intel") ?? false;
|
|
3879
|
+
const hasIgpu = fsmod.existsSync("/dev/dri/renderD128");
|
|
3880
|
+
const hasNpu = fsmod.existsSync("/dev/accel/accel0") || fsmod.existsSync("/dev/accel");
|
|
3881
|
+
if (isIntel && (hasIgpu || hasNpu)) return {
|
|
3882
|
+
runtime: "python",
|
|
3883
|
+
backend: "openvino",
|
|
3884
|
+
format: "openvino",
|
|
3885
|
+
device: "auto"
|
|
3886
|
+
};
|
|
3887
|
+
} catch {}
|
|
3876
3888
|
return {
|
|
3877
3889
|
runtime: "python",
|
|
3878
3890
|
backend: "onnx",
|
|
@@ -3884,22 +3896,68 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
3884
3896
|
async setApi(addonCtx) {
|
|
3885
3897
|
this.addonCtx = addonCtx;
|
|
3886
3898
|
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Fetch platform-probe data for engine + device gating. Returns null for
|
|
3901
|
+
* both fields when the probe cap is not yet reachable (cold-start / probe
|
|
3902
|
+
* addon not installed), so callers fall back to the full static catalog.
|
|
3903
|
+
*/
|
|
3904
|
+
async fetchProbeGatingData() {
|
|
3905
|
+
try {
|
|
3906
|
+
const api = this.addonCtx?.api;
|
|
3907
|
+
if (!api) return {
|
|
3908
|
+
availableBackends: null,
|
|
3909
|
+
hardware: null
|
|
3910
|
+
};
|
|
3911
|
+
const caps = await api.platformProbe.getCapabilities.query();
|
|
3912
|
+
if (!caps?.scores) return {
|
|
3913
|
+
availableBackends: null,
|
|
3914
|
+
hardware: null
|
|
3915
|
+
};
|
|
3916
|
+
return {
|
|
3917
|
+
availableBackends: caps.scores.filter((s) => s.runtime === "python" && s.available).map((s) => s.backend),
|
|
3918
|
+
hardware: caps.hardware ?? null
|
|
3919
|
+
};
|
|
3920
|
+
} catch {
|
|
3921
|
+
return {
|
|
3922
|
+
availableBackends: null,
|
|
3923
|
+
hardware: null
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3887
3927
|
async getSchema(engine) {
|
|
3888
3928
|
if (!engine || !engine.runtime) engine = await this.getSelectedEngine();
|
|
3889
3929
|
const format = engine.format;
|
|
3890
3930
|
const slots = buildSchemaSlots(format, this.modelsDir);
|
|
3931
|
+
const { availableBackends, hardware } = await this.fetchProbeGatingData();
|
|
3891
3932
|
return {
|
|
3892
|
-
availableEngines: this.getAvailableEnginesWithDevices(),
|
|
3933
|
+
availableEngines: this.getAvailableEnginesWithDevices(availableBackends, hardware),
|
|
3893
3934
|
selectedEngine: { ...engine },
|
|
3894
3935
|
slots
|
|
3895
3936
|
};
|
|
3896
3937
|
}
|
|
3897
3938
|
async getAvailableEngines() {
|
|
3898
|
-
|
|
3939
|
+
const { availableBackends, hardware } = await this.fetchProbeGatingData();
|
|
3940
|
+
return this.getAvailableEnginesWithDevices(availableBackends, hardware).map((e) => e.engine);
|
|
3899
3941
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3942
|
+
/**
|
|
3943
|
+
* Build the engine catalog filtered by probe data.
|
|
3944
|
+
*
|
|
3945
|
+
* When `availableBackends` is not null, only engines whose backend is
|
|
3946
|
+
* reported as available by the platform-probe cap are included. This
|
|
3947
|
+
* prevents the benchmark picker from offering (e.g.) CoreML on an Intel
|
|
3948
|
+
* Linux host where the Python coremltools package is absent.
|
|
3949
|
+
*
|
|
3950
|
+
* When `hardware` is not null, the per-engine device list is narrowed to
|
|
3951
|
+
* entries that are valid on this host's hardware (no Intel NPU → no 'npu'
|
|
3952
|
+
* device for OpenVINO, no NVIDIA GPU → no 'cuda' for ONNX, etc.).
|
|
3953
|
+
*
|
|
3954
|
+
* When either argument is null (probe unreachable / cold-start), the full
|
|
3955
|
+
* static catalog is returned unchanged — conservative fallback so the UI
|
|
3956
|
+
* still renders all options rather than going blank.
|
|
3957
|
+
*/
|
|
3958
|
+
getAvailableEnginesWithDevices(availableBackends, hardware) {
|
|
3959
|
+
const allEngines = [];
|
|
3960
|
+
if (process.platform === "darwin") allEngines.push({
|
|
3903
3961
|
engine: {
|
|
3904
3962
|
runtime: "python",
|
|
3905
3963
|
backend: "coreml",
|
|
@@ -3908,7 +3966,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
3908
3966
|
devices: COREML_DEVICES,
|
|
3909
3967
|
defaultDevice: "all"
|
|
3910
3968
|
});
|
|
3911
|
-
|
|
3969
|
+
allEngines.push({
|
|
3912
3970
|
engine: {
|
|
3913
3971
|
runtime: "python",
|
|
3914
3972
|
backend: "openvino",
|
|
@@ -3917,7 +3975,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
3917
3975
|
devices: OPENVINO_DEVICES,
|
|
3918
3976
|
defaultDevice: "auto"
|
|
3919
3977
|
});
|
|
3920
|
-
|
|
3978
|
+
allEngines.push({
|
|
3921
3979
|
engine: {
|
|
3922
3980
|
runtime: "python",
|
|
3923
3981
|
backend: "onnx",
|
|
@@ -3926,7 +3984,19 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
3926
3984
|
devices: ONNX_PYTHON_DEVICES,
|
|
3927
3985
|
defaultDevice: "cpu"
|
|
3928
3986
|
});
|
|
3929
|
-
return
|
|
3987
|
+
if (!availableBackends) return allEngines;
|
|
3988
|
+
return allEngines.filter((e) => availableBackends.includes(e.engine.backend)).map((e) => {
|
|
3989
|
+
if (!hardware) return e;
|
|
3990
|
+
const filtered = filterDeviceOptionsByHardware(e.devices.map((d) => ({
|
|
3991
|
+
value: d.id,
|
|
3992
|
+
label: d.label
|
|
3993
|
+
})), e.engine.backend, hardware);
|
|
3994
|
+
const allowedIds = new Set(filtered.map((o) => o.value));
|
|
3995
|
+
return {
|
|
3996
|
+
...e,
|
|
3997
|
+
devices: e.devices.filter((d) => allowedIds.has(d.id))
|
|
3998
|
+
};
|
|
3999
|
+
});
|
|
3930
4000
|
}
|
|
3931
4001
|
async getDefaultSteps(engine) {
|
|
3932
4002
|
return buildDefaultStepTree(engine.format);
|
|
@@ -4001,7 +4071,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4001
4071
|
const formats = {};
|
|
4002
4072
|
for (const [formatKey, entry] of Object.entries(m.formats)) {
|
|
4003
4073
|
if (!entry) continue;
|
|
4004
|
-
const downloaded = (0,
|
|
4074
|
+
const downloaded = (0, _camstack_system.isModelDownloaded)(this.modelsDir, m, formatKey);
|
|
4005
4075
|
formats[formatKey] = {
|
|
4006
4076
|
url: entry.url,
|
|
4007
4077
|
sizeMB: entry.sizeMB,
|
|
@@ -4018,7 +4088,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4018
4088
|
};
|
|
4019
4089
|
})
|
|
4020
4090
|
}));
|
|
4021
|
-
const
|
|
4091
|
+
const { availableBackends, hardware } = await this.fetchProbeGatingData();
|
|
4092
|
+
const engines = this.getAvailableEnginesWithDevices(availableBackends, hardware);
|
|
4022
4093
|
const nodeBackends = [];
|
|
4023
4094
|
const pythonBackends = [];
|
|
4024
4095
|
for (const e of engines) if (e.engine.runtime === "node") nodeBackends.push({
|
|
@@ -4128,7 +4199,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4128
4199
|
const { modelId, format, addonId } = input;
|
|
4129
4200
|
const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
|
|
4130
4201
|
if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
|
|
4131
|
-
if (!(0,
|
|
4202
|
+
if (!(0, _camstack_system.deleteModelFromDisk)(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
|
|
4132
4203
|
this.log.info("Model deleted from disk", { meta: {
|
|
4133
4204
|
modelId,
|
|
4134
4205
|
format
|
|
@@ -4583,7 +4654,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4583
4654
|
const format = this.currentEngine?.format ?? "onnx";
|
|
4584
4655
|
for (const step of needed) {
|
|
4585
4656
|
const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
|
|
4586
|
-
if (modelEntry && !(0,
|
|
4657
|
+
if (modelEntry && !(0, _camstack_system.isModelDownloaded)(this.modelsDir, modelEntry, format)) {
|
|
4587
4658
|
this.log.info("Downloading model for step", { meta: {
|
|
4588
4659
|
modelId: step.modelId,
|
|
4589
4660
|
format,
|
|
@@ -4608,7 +4679,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4608
4679
|
/** Download a model with retry + exponential backoff */
|
|
4609
4680
|
async downloadWithRetry(entry, format, maxRetries, onProgress) {
|
|
4610
4681
|
for (let attempt = 1; attempt <= maxRetries; attempt++) try {
|
|
4611
|
-
await (0,
|
|
4682
|
+
await (0, _camstack_system.ensureModel)(this.modelsDir, entry, format, onProgress);
|
|
4612
4683
|
this.log.info("Model downloaded successfully", { meta: { modelId: entry.id } });
|
|
4613
4684
|
return;
|
|
4614
4685
|
} catch (err) {
|
|
@@ -5083,11 +5154,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5083
5154
|
} });
|
|
5084
5155
|
continue;
|
|
5085
5156
|
}
|
|
5086
|
-
if (!(0,
|
|
5157
|
+
if (!(0, _camstack_system.isModelDownloaded)(this.modelsDir, modelEntry, format)) this.log.info("Downloading model", { meta: {
|
|
5087
5158
|
modelId: step.modelId,
|
|
5088
5159
|
format
|
|
5089
5160
|
} });
|
|
5090
|
-
downloads.push((0,
|
|
5161
|
+
downloads.push((0, _camstack_system.ensureModel)(this.modelsDir, modelEntry, format).then(() => {}));
|
|
5091
5162
|
}
|
|
5092
5163
|
await Promise.all(downloads);
|
|
5093
5164
|
await this.ensureBackendDeps(this.currentEngine);
|
|
@@ -5119,19 +5190,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5119
5190
|
*/
|
|
5120
5191
|
async loadEngine() {
|
|
5121
5192
|
const store = await this.readStore();
|
|
5122
|
-
const
|
|
5123
|
-
const
|
|
5124
|
-
if (typeof
|
|
5193
|
+
const storedRuntime = store["engineRuntime"];
|
|
5194
|
+
const storedBackend = store["engineBackend"];
|
|
5195
|
+
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
5196
|
+
const backend = storedBackend;
|
|
5125
5197
|
const storedDevice = typeof store["engineDevice"] === "string" ? String(store["engineDevice"]) : "";
|
|
5126
5198
|
const detected = DetectionPipelineProvider.detectBestEngine();
|
|
5127
|
-
|
|
5199
|
+
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5200
|
+
if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
|
|
5128
5201
|
this.log.warn("Stored engine backend unavailable on this node — falling back to detected best", { meta: {
|
|
5129
|
-
stored:
|
|
5130
|
-
fallback: `${detected.
|
|
5202
|
+
stored: migratedBackend,
|
|
5203
|
+
fallback: `${detected.backend}`
|
|
5131
5204
|
} });
|
|
5132
5205
|
return detected;
|
|
5133
5206
|
}
|
|
5134
|
-
const migratedBackend = runtime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5135
5207
|
const device = storedDevice || detected.device;
|
|
5136
5208
|
return {
|
|
5137
5209
|
runtime: "python",
|
|
@@ -5301,11 +5373,42 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5301
5373
|
return { success: true };
|
|
5302
5374
|
}
|
|
5303
5375
|
async reprobeEngine() {
|
|
5304
|
-
const
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5376
|
+
const api = this.addonCtx?.api;
|
|
5377
|
+
let best;
|
|
5378
|
+
if (api) try {
|
|
5379
|
+
const caps = await api.platformProbe.getCapabilities.query();
|
|
5380
|
+
const bs = caps?.bestScore;
|
|
5381
|
+
if (bs && bs.runtime === "python") {
|
|
5382
|
+
const probeBackend = bs.backend;
|
|
5383
|
+
const probeDevice = (() => {
|
|
5384
|
+
const hw = caps.hardware;
|
|
5385
|
+
if (probeBackend === "coreml") return hw?.npu?.type === "apple-ane" ? "ane" : "all";
|
|
5386
|
+
if (probeBackend === "openvino") {
|
|
5387
|
+
if (hw?.npu?.type === "intel-npu") return "npu";
|
|
5388
|
+
if (hw?.gpu?.type === "intel") return "gpu";
|
|
5389
|
+
return "auto";
|
|
5390
|
+
}
|
|
5391
|
+
if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
|
|
5392
|
+
return "cpu";
|
|
5393
|
+
})();
|
|
5394
|
+
best = {
|
|
5395
|
+
runtime: "python",
|
|
5396
|
+
backend: probeBackend,
|
|
5397
|
+
format: backendToFormat(probeBackend),
|
|
5398
|
+
device: probeDevice
|
|
5399
|
+
};
|
|
5400
|
+
} else best = DetectionPipelineProvider.detectBestEngine();
|
|
5401
|
+
} catch {
|
|
5402
|
+
best = DetectionPipelineProvider.detectBestEngine();
|
|
5403
|
+
}
|
|
5404
|
+
else best = DetectionPipelineProvider.detectBestEngine();
|
|
5405
|
+
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
5406
|
+
await this.writeStore({
|
|
5407
|
+
probedBestEngine: probedLabel,
|
|
5408
|
+
engineBackend: best.backend,
|
|
5409
|
+
engineDevice: best.device ?? "cpu"
|
|
5410
|
+
});
|
|
5411
|
+
this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
|
|
5309
5412
|
backend: best.backend,
|
|
5310
5413
|
device: best.device ?? null,
|
|
5311
5414
|
probedBestEngine: probedLabel
|
|
@@ -5467,7 +5570,7 @@ function buildSchemaSlots(format, modelsDir) {
|
|
|
5467
5570
|
id: m.id,
|
|
5468
5571
|
name: m.name,
|
|
5469
5572
|
formats: Object.fromEntries(Object.entries(m.formats).map(([f, entry]) => [f, {
|
|
5470
|
-
downloaded: (0,
|
|
5573
|
+
downloaded: (0, _camstack_system.isModelDownloaded)(modelsDir, m, f),
|
|
5471
5574
|
sizeMB: entry.sizeMB
|
|
5472
5575
|
}]))
|
|
5473
5576
|
})),
|
|
@@ -5701,10 +5804,34 @@ function resolveAddonPythonDir() {
|
|
|
5701
5804
|
for (const c of candidates) if (node_fs.existsSync(node_path.join(c, "inference_pool.py"))) return c;
|
|
5702
5805
|
throw new Error(`addon-pipeline/detection-pipeline: python/ dir not found. Searched:\n${candidates.join("\n")}`);
|
|
5703
5806
|
}
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5807
|
+
/**
|
|
5808
|
+
* Returns true when proactive OpenVINO installation is warranted.
|
|
5809
|
+
*
|
|
5810
|
+
* Gate: Linux host + Intel iGPU or Intel NPU detected.
|
|
5811
|
+
*
|
|
5812
|
+
* Intentionally addon-local — addons are self-contained and cannot import
|
|
5813
|
+
* `@camstack/system` internals (see architecture invariant: no cross-addon
|
|
5814
|
+
* imports). This mirrors the logic in `resolveRuntimePackages` from that
|
|
5815
|
+
* package without importing it.
|
|
5816
|
+
*
|
|
5817
|
+
* darwin is never true: coremltools handles Apple Silicon + Intel Mac.
|
|
5818
|
+
* linux-amd (or any non-Intel linux GPU) is never true: the openvino
|
|
5819
|
+
* package has no AMD backend and would fail at import time.
|
|
5820
|
+
*
|
|
5821
|
+
* @internal exported only for unit tests in the same package
|
|
5822
|
+
*/
|
|
5823
|
+
function shouldInstallOpenvino(hardware) {
|
|
5824
|
+
if (hardware.platform !== "linux") return false;
|
|
5825
|
+
const hasIntelGpu = hardware.gpu?.type === "intel";
|
|
5826
|
+
const hasIntelNpu = hardware.npu?.type === "intel-npu";
|
|
5827
|
+
return hasIntelGpu || hasIntelNpu;
|
|
5828
|
+
}
|
|
5829
|
+
/**
|
|
5830
|
+
* Full catalog of execution providers. The settings UI only shows the subset
|
|
5831
|
+
* reported as available by the platform-probe cap (`getGlobalSettings` gates
|
|
5832
|
+
* options by probe scores). Kept as the static universe so the static schema
|
|
5833
|
+
* (returned before a live ctx is available) still lists all fields.
|
|
5834
|
+
*/
|
|
5708
5835
|
var BACKENDS_BY_RUNTIME = { python: [
|
|
5709
5836
|
{
|
|
5710
5837
|
value: "coreml",
|
|
@@ -5775,6 +5902,53 @@ var DEVICES_BY_BACKEND = {
|
|
|
5775
5902
|
label: "CPU"
|
|
5776
5903
|
}]
|
|
5777
5904
|
};
|
|
5905
|
+
/**
|
|
5906
|
+
* Filter the per-backend device option list by what the platform probe
|
|
5907
|
+
* reports as available hardware on this host. Rules (derived from the
|
|
5908
|
+
* confirmed target model):
|
|
5909
|
+
*
|
|
5910
|
+
* coreml:
|
|
5911
|
+
* - `ane` only when `hardware.npu?.type === 'apple-ane'`
|
|
5912
|
+
* - `gpu` and `all` always shown (CPU+GPU present on every Mac)
|
|
5913
|
+
* - `cpu` always shown
|
|
5914
|
+
*
|
|
5915
|
+
* openvino:
|
|
5916
|
+
* - `npu` only when `hardware.npu?.type === 'intel-npu'`
|
|
5917
|
+
* - `gpu` only when `hardware.gpu?.type === 'intel'`
|
|
5918
|
+
* - `auto` and `cpu` always shown
|
|
5919
|
+
*
|
|
5920
|
+
* onnx:
|
|
5921
|
+
* - `cuda` only when `hardware.gpu?.type === 'nvidia'`
|
|
5922
|
+
* - `coreml` only when `hardware.npu?.type === 'apple-ane'` (CoreML EP)
|
|
5923
|
+
* - `cpu` always shown
|
|
5924
|
+
*
|
|
5925
|
+
* When `hardware` is null (probe unreachable), the full catalog is returned
|
|
5926
|
+
* unchanged so the UI still renders all options.
|
|
5927
|
+
*/
|
|
5928
|
+
function filterDeviceOptionsByHardware(options, backend, hardware) {
|
|
5929
|
+
if (!hardware) return options;
|
|
5930
|
+
const hasAppleAne = hardware.npu?.type === "apple-ane";
|
|
5931
|
+
const hasIntelNpu = hardware.npu?.type === "intel-npu";
|
|
5932
|
+
const hasIntelGpu = hardware.gpu?.type === "intel";
|
|
5933
|
+
const hasNvidiaGpu = hardware.gpu?.type === "nvidia";
|
|
5934
|
+
switch (backend) {
|
|
5935
|
+
case "coreml": return options.filter((o) => {
|
|
5936
|
+
if (o.value === "ane") return hasAppleAne;
|
|
5937
|
+
return true;
|
|
5938
|
+
});
|
|
5939
|
+
case "openvino": return options.filter((o) => {
|
|
5940
|
+
if (o.value === "npu") return hasIntelNpu;
|
|
5941
|
+
if (o.value === "gpu") return hasIntelGpu;
|
|
5942
|
+
return true;
|
|
5943
|
+
});
|
|
5944
|
+
case "onnx": return options.filter((o) => {
|
|
5945
|
+
if (o.value === "cuda") return hasNvidiaGpu;
|
|
5946
|
+
if (o.value === "coreml") return hasAppleAne;
|
|
5947
|
+
return true;
|
|
5948
|
+
});
|
|
5949
|
+
default: return options;
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5778
5952
|
var BACKEND_TO_FORMAT = {
|
|
5779
5953
|
cpu: "onnx",
|
|
5780
5954
|
coreml: "coreml",
|
|
@@ -5906,13 +6080,13 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
5906
6080
|
title: "Object detection",
|
|
5907
6081
|
tab: "engine",
|
|
5908
6082
|
order: 0,
|
|
5909
|
-
description: "Vision-detection
|
|
6083
|
+
description: "Vision-detection execution provider (object detection, face detection, plate OCR). Inference always runs via the embedded Python runtime. Provider and hardware options are gated by what the platform probe reports as available on this host. Changes restart the addon.",
|
|
5910
6084
|
fields: [
|
|
5911
6085
|
this.field({
|
|
5912
6086
|
type: "text",
|
|
5913
6087
|
key: "probedBestEngine",
|
|
5914
6088
|
label: "Probed best",
|
|
5915
|
-
description: "Auto-detected best engine for this host (format:
|
|
6089
|
+
description: "Auto-detected best engine for this host (format: provider/device, e.g. \"openvino/npu\"). Click the refresh icon to re-run the probe — the detected provider and hardware device are written back into the two fields below, overwriting any manual override.",
|
|
5916
6090
|
readonlyField: true,
|
|
5917
6091
|
default: "",
|
|
5918
6092
|
actions: [{
|
|
@@ -5921,19 +6095,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
5921
6095
|
tooltip: "Re-probe engine"
|
|
5922
6096
|
}]
|
|
5923
6097
|
}),
|
|
5924
|
-
this.field({
|
|
5925
|
-
type: "select",
|
|
5926
|
-
key: "engineRuntime",
|
|
5927
|
-
label: "Runtime",
|
|
5928
|
-
options: [...RUNTIMES],
|
|
5929
|
-
default: DEFAULT_CONFIG.engineRuntime,
|
|
5930
|
-
immediate: true,
|
|
5931
|
-
requiresRestart: true
|
|
5932
|
-
}),
|
|
5933
6098
|
this.field({
|
|
5934
6099
|
type: "select",
|
|
5935
6100
|
key: "engineBackend",
|
|
5936
|
-
label: "
|
|
6101
|
+
label: "Execution provider",
|
|
5937
6102
|
options: [...BACKENDS_BY_RUNTIME.python],
|
|
5938
6103
|
default: DEFAULT_CONFIG.engineBackend,
|
|
5939
6104
|
immediate: true,
|
|
@@ -6071,14 +6236,16 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6071
6236
|
...overlay
|
|
6072
6237
|
} : stored;
|
|
6073
6238
|
const runtime = "python";
|
|
6074
|
-
const
|
|
6239
|
+
const probeResult = await this.resolveProbeData(ctx, runtime);
|
|
6240
|
+
const availableBackends = probeResult.availableBackends;
|
|
6241
|
+
const hardware = probeResult.hardware;
|
|
6075
6242
|
const runtimeBackends = availableBackends.length > 0 ? BACKENDS_BY_RUNTIME[runtime].filter((b) => availableBackends.includes(b.value)) : BACKENDS_BY_RUNTIME[runtime];
|
|
6076
6243
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6077
|
-
const backend = runtimeBackends.find((b) => b.value === storedBackend)?.value ?? runtimeBackends[0]?.value ?? "coreml";
|
|
6078
|
-
const deviceOptions = DEVICES_BY_BACKEND[backend] ?? [{
|
|
6244
|
+
const backend = runtimeBackends.find((b) => b.value === storedBackend)?.value ?? probeResult.bestBackend ?? runtimeBackends[0]?.value ?? "coreml";
|
|
6245
|
+
const deviceOptions = filterDeviceOptionsByHardware(DEVICES_BY_BACKEND[backend] ?? [{
|
|
6079
6246
|
value: "cpu",
|
|
6080
6247
|
label: "CPU"
|
|
6081
|
-
}];
|
|
6248
|
+
}], backend, hardware);
|
|
6082
6249
|
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6083
6250
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? deviceOptions[0]?.value ?? "cpu";
|
|
6084
6251
|
const raw = {
|
|
@@ -6128,28 +6295,47 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6128
6295
|
}, raw);
|
|
6129
6296
|
}
|
|
6130
6297
|
/**
|
|
6131
|
-
*
|
|
6132
|
-
*
|
|
6133
|
-
*
|
|
6134
|
-
*
|
|
6135
|
-
*
|
|
6136
|
-
*
|
|
6298
|
+
* Fetch the platform-probe capabilities and return:
|
|
6299
|
+
* - `availableBackends`: backends the probe reports as available for `runtime`
|
|
6300
|
+
* (empty → caller falls back to full catalog).
|
|
6301
|
+
* - `hardware`: the probed hardware info (null when probe not reachable).
|
|
6302
|
+
* - `bestBackend`: the backend from the probe's `bestScore` (null when unavailable).
|
|
6303
|
+
*
|
|
6304
|
+
* A single probe call is made so both backend AND device gating use the
|
|
6305
|
+
* same snapshot without doubling the cap round-trip.
|
|
6137
6306
|
*/
|
|
6138
|
-
async
|
|
6307
|
+
async resolveProbeData(ctx, runtime) {
|
|
6139
6308
|
try {
|
|
6140
6309
|
const api = ctx?.api;
|
|
6141
|
-
if (!api) return
|
|
6310
|
+
if (!api) return {
|
|
6311
|
+
availableBackends: [],
|
|
6312
|
+
hardware: null,
|
|
6313
|
+
bestBackend: null
|
|
6314
|
+
};
|
|
6142
6315
|
const caps = await api.platformProbe.getCapabilities.query();
|
|
6143
|
-
if (!caps?.scores) return
|
|
6316
|
+
if (!caps?.scores) return {
|
|
6317
|
+
availableBackends: [],
|
|
6318
|
+
hardware: null,
|
|
6319
|
+
bestBackend: null
|
|
6320
|
+
};
|
|
6144
6321
|
const out = /* @__PURE__ */ new Set();
|
|
6145
6322
|
for (const s of caps.scores) {
|
|
6146
6323
|
if (s.runtime !== runtime) continue;
|
|
6147
6324
|
if (!s.available) continue;
|
|
6148
6325
|
out.add(s.backend);
|
|
6149
6326
|
}
|
|
6150
|
-
|
|
6327
|
+
const bestBackend = caps.bestScore?.runtime === runtime ? caps.bestScore.backend ?? null : null;
|
|
6328
|
+
return {
|
|
6329
|
+
availableBackends: [...out],
|
|
6330
|
+
hardware: caps.hardware ?? null,
|
|
6331
|
+
bestBackend
|
|
6332
|
+
};
|
|
6151
6333
|
} catch {
|
|
6152
|
-
return
|
|
6334
|
+
return {
|
|
6335
|
+
availableBackends: [],
|
|
6336
|
+
hardware: null,
|
|
6337
|
+
bestBackend: null
|
|
6338
|
+
};
|
|
6153
6339
|
}
|
|
6154
6340
|
}
|
|
6155
6341
|
/**
|
|
@@ -6190,6 +6376,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6190
6376
|
const pythonPath = await this.ctx.deps.ensurePython();
|
|
6191
6377
|
if (pythonPath) this.pythonPath = pythonPath;
|
|
6192
6378
|
else this.ctx.logger.warn("Embedded Python unavailable — runtime=\"python\" pipelines will fail until the download succeeds.");
|
|
6379
|
+
await this.proactivelyInstallOpenvino();
|
|
6193
6380
|
const effectiveTuning = this.resolveBackendTuning();
|
|
6194
6381
|
this.provider = new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
|
|
6195
6382
|
concurrency: effectiveTuning.concurrency,
|
|
@@ -6255,6 +6442,32 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6255
6442
|
getModelCatalog() {
|
|
6256
6443
|
return ALL_STEPS.flatMap((s) => [...s.models]);
|
|
6257
6444
|
}
|
|
6445
|
+
/**
|
|
6446
|
+
* Proactively install the OpenVINO Python package when Intel hardware
|
|
6447
|
+
* (iGPU or NPU) is detected. Called once from `onInitialize`, before the
|
|
6448
|
+
* provider is constructed, so the module is available when the platform
|
|
6449
|
+
* probe runs its next `import openvino.runtime` check.
|
|
6450
|
+
*
|
|
6451
|
+
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
6452
|
+
* the onnx-cpu baseline. The hardware query itself is also best-effort —
|
|
6453
|
+
* if the platform-probe cap isn't wired yet the error is caught here.
|
|
6454
|
+
*/
|
|
6455
|
+
async proactivelyInstallOpenvino() {
|
|
6456
|
+
if (!this.pythonAddonDir || !this.pythonPath) return;
|
|
6457
|
+
try {
|
|
6458
|
+
const hardware = await this.ctx.api.platformProbe.getHardware.query();
|
|
6459
|
+
if (!shouldInstallOpenvino(hardware)) return;
|
|
6460
|
+
this.ctx.logger.info("Intel hardware detected — proactively installing OpenVINO Python package", { meta: {
|
|
6461
|
+
gpu: hardware.gpu?.type ?? null,
|
|
6462
|
+
npu: hardware.npu?.type ?? null
|
|
6463
|
+
} });
|
|
6464
|
+
const requirementsFile = node_path.join(this.pythonAddonDir, "requirements-openvino.txt");
|
|
6465
|
+
await this.ctx.deps.installPythonRequirements(requirementsFile);
|
|
6466
|
+
this.ctx.logger.info("Proactive OpenVINO install complete");
|
|
6467
|
+
} catch (err) {
|
|
6468
|
+
this.ctx.logger.warn("Proactive OpenVINO install failed — falling back to onnx-cpu baseline", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6258
6471
|
async onShutdown() {
|
|
6259
6472
|
if (this.engineMetricsTimer) {
|
|
6260
6473
|
clearInterval(this.engineMetricsTimer);
|
|
@@ -6347,5 +6560,7 @@ exports.ALL_STEPS = ALL_STEPS;
|
|
|
6347
6560
|
exports.DetectionPipelineProvider = DetectionPipelineProvider;
|
|
6348
6561
|
exports.backendToFormat = backendToFormat;
|
|
6349
6562
|
exports.default = DetectionPipelineAddon;
|
|
6563
|
+
exports.filterDeviceOptionsByHardware = filterDeviceOptionsByHardware;
|
|
6350
6564
|
exports.getDefaultModelForFormat = getDefaultModelForFormat;
|
|
6351
6565
|
exports.getStepDefinition = getStepDefinition;
|
|
6566
|
+
exports.shouldInstallOpenvino = shouldInstallOpenvino;
|