@camstack/addon-pipeline 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/audio-analyzer/index.js +16 -15
  2. package/dist/audio-analyzer/index.mjs +16 -15
  3. package/dist/audio-codec-nodeav/index.js +1 -1
  4. package/dist/audio-codec-nodeav/index.mjs +1 -1
  5. package/dist/decoder-nodeav/index.js +1 -1
  6. package/dist/decoder-nodeav/index.mjs +1 -1
  7. package/dist/detection-pipeline/index.js +195 -38
  8. package/dist/detection-pipeline/index.mjs +195 -38
  9. package/dist/{dist-OlOI9pHn.js → dist-BhpOMzzH.js} +28 -4
  10. package/dist/{dist-CAgJixja.mjs → dist-GFzs5acw.mjs} +28 -4
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +1 -1
  14. package/dist/pipeline-runner/index.mjs +1 -1
  15. package/dist/recorder/index.js +1 -1
  16. package/dist/recorder/index.mjs +1 -1
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D08yXBHc.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BR3_GlF1.mjs} +3 -3
  19. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-FRD2eBuz.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-S6ETjGtN.mjs} +1 -1
  20. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CHcXI1Wf.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-5vetl3CL.mjs} +1 -1
  21. package/dist/stream-broker/{hostInit-DPGCWkwA.mjs → hostInit-BN9_1DbU.mjs} +3 -3
  22. package/dist/stream-broker/index.js +1 -1
  23. package/dist/stream-broker/index.mjs +1 -1
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Ds2bhSfK.js → MaskShapeCanvas-DI4BY7W2-Bulflbu5.js} +1 -1
  26. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-W7yVVry1.js → MotionZonesSettings-NcxxQN8r-DJ9keq4q.js} +1 -1
  27. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-30DllvBT.js → PrivacyMaskSettings-APgPLF7p-CRsjkTB1.js} +1 -1
  28. package/embed-dist/assets/{index-D8CZDqZG.js → index-DBB2e2P1.js} +9 -9
  29. package/embed-dist/index.html +1 -1
  30. package/package.json +1 -1
  31. package/python/postprocessors/__init__.py +2 -0
  32. package/python/postprocessors/clip.py +35 -0
  33. package/python/postprocessors/test_clip.py +33 -0
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
6
- const require_dist = require("../dist-OlOI9pHn.js");
6
+ const require_dist = require("../dist-BhpOMzzH.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -719,14 +719,14 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
719
719
  * preview mode (operator typed but didn't save yet) — same
720
720
  * semantics detection-pipeline relies on.
721
721
  */
722
- async getGlobalSettings(overlay, nodeId) {
722
+ async getGlobalSettings(overlay, _cap, nodeId) {
723
723
  const ctx = this.ctxIfReady;
724
724
  const stored = ctx?.settings ? await ctx.settings.readAddonStore() ?? {} : {};
725
725
  const merged = overlay ? {
726
726
  ...stored,
727
727
  ...overlay
728
728
  } : stored;
729
- const isDarwin = await this.probeNodePlatform(nodeId) === "darwin";
729
+ const isDarwin = await this.resolveNodePlatform(nodeId) === "darwin";
730
730
  const backendOptions = require_dist.AUDIO_BACKEND_CHOICES.filter((o) => o.value !== "apple-soundanalysis" || isDarwin);
731
731
  const operatorChoiceRaw = typeof merged.audioBackend === "string" ? merged.audioBackend : require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
732
732
  const operatorChoice = backendOptions.some((o) => o.value === operatorChoiceRaw) ? operatorChoiceRaw : require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
@@ -765,21 +765,22 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
765
765
  }, raw);
766
766
  }
767
767
  /**
768
- * Probe the OS platform of `nodeId` (default: this node) via the
769
- * `platform-probe` cap the same source the detection-pipeline uses to gate
770
- * accelerator backends. Falls back to this host's `process.platform` when the
771
- * probe is unreachable (cold-start / probe addon absent), so the form stays
772
- * usable rather than collapsing.
768
+ * Resolve the OS platform of the target node WITHOUT the flaky platform-probe
769
+ * "hardware" cap. For the local node (or no nodeId) the running process is the
770
+ * authority. For a remote node the hub-centric settings form serves every
771
+ * node from the hub child read the node's registration-reported platform
772
+ * from the in-memory cluster topology (`nodes.topology`): a hub-local read, no
773
+ * remote round-trip, no probe. Falls back to this host's `process.platform`
774
+ * when topology is unavailable so the form stays usable.
773
775
  */
774
- async probeNodePlatform(nodeId) {
775
- const api = this.ctxIfReady?.api;
776
- if (!api) return process.platform;
776
+ async resolveNodePlatform(nodeId) {
777
777
  const localNode = this.ctxIfReady?.kernel?.localNodeId;
778
+ if (!nodeId || nodeId === localNode) return process.platform;
778
779
  try {
779
- return (nodeId && nodeId !== localNode ? await api.platformProbe.getCapabilities.query(void 0, require_dist.nodePin(nodeId)) : await api.platformProbe.getCapabilities.query())?.platform ?? process.platform;
780
- } catch {
781
- return process.platform;
782
- }
780
+ const entry = (await this.ctxIfReady?.api?.nodes?.topology.query())?.find((n) => n.id === nodeId);
781
+ if (entry?.platform) return entry.platform;
782
+ } catch {}
783
+ return process.platform;
783
784
  }
784
785
  /**
785
786
  * Re-run the platform probe and persist the detected backend into
@@ -1,5 +1,5 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { A as nodePin, B as BaseAddon, E as mapAudioLabelToMacro, J as hydrateSchema, g as audioAnalyzerCapability, h as audioAnalysisCapability, l as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, z as errMsg } from "../dist-CAgJixja.mjs";
2
+ import { B as BaseAddon, E as mapAudioLabelToMacro, J as hydrateSchema, g as audioAnalyzerCapability, h as audioAnalysisCapability, l as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, z as errMsg } from "../dist-GFzs5acw.mjs";
3
3
  import { r as downloadFile } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
4
4
  import * as fs from "node:fs";
5
5
  import * as path$1 from "node:path";
@@ -714,14 +714,14 @@ var AudioAnalyzerAddon = class extends BaseAddon {
714
714
  * preview mode (operator typed but didn't save yet) — same
715
715
  * semantics detection-pipeline relies on.
716
716
  */
717
- async getGlobalSettings(overlay, nodeId) {
717
+ async getGlobalSettings(overlay, _cap, nodeId) {
718
718
  const ctx = this.ctxIfReady;
719
719
  const stored = ctx?.settings ? await ctx.settings.readAddonStore() ?? {} : {};
720
720
  const merged = overlay ? {
721
721
  ...stored,
722
722
  ...overlay
723
723
  } : stored;
724
- const isDarwin = await this.probeNodePlatform(nodeId) === "darwin";
724
+ const isDarwin = await this.resolveNodePlatform(nodeId) === "darwin";
725
725
  const backendOptions = AUDIO_BACKEND_CHOICES.filter((o) => o.value !== "apple-soundanalysis" || isDarwin);
726
726
  const operatorChoiceRaw = typeof merged.audioBackend === "string" ? merged.audioBackend : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
727
727
  const operatorChoice = backendOptions.some((o) => o.value === operatorChoiceRaw) ? operatorChoiceRaw : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
@@ -760,21 +760,22 @@ var AudioAnalyzerAddon = class extends BaseAddon {
760
760
  }, raw);
761
761
  }
762
762
  /**
763
- * Probe the OS platform of `nodeId` (default: this node) via the
764
- * `platform-probe` cap the same source the detection-pipeline uses to gate
765
- * accelerator backends. Falls back to this host's `process.platform` when the
766
- * probe is unreachable (cold-start / probe addon absent), so the form stays
767
- * usable rather than collapsing.
763
+ * Resolve the OS platform of the target node WITHOUT the flaky platform-probe
764
+ * "hardware" cap. For the local node (or no nodeId) the running process is the
765
+ * authority. For a remote node the hub-centric settings form serves every
766
+ * node from the hub child read the node's registration-reported platform
767
+ * from the in-memory cluster topology (`nodes.topology`): a hub-local read, no
768
+ * remote round-trip, no probe. Falls back to this host's `process.platform`
769
+ * when topology is unavailable so the form stays usable.
768
770
  */
769
- async probeNodePlatform(nodeId) {
770
- const api = this.ctxIfReady?.api;
771
- if (!api) return process.platform;
771
+ async resolveNodePlatform(nodeId) {
772
772
  const localNode = this.ctxIfReady?.kernel?.localNodeId;
773
+ if (!nodeId || nodeId === localNode) return process.platform;
773
774
  try {
774
- return (nodeId && nodeId !== localNode ? await api.platformProbe.getCapabilities.query(void 0, nodePin(nodeId)) : await api.platformProbe.getCapabilities.query())?.platform ?? process.platform;
775
- } catch {
776
- return process.platform;
777
- }
775
+ const entry = (await this.ctxIfReady?.api?.nodes?.topology.query())?.find((n) => n.id === nodeId);
776
+ if (entry?.platform) return entry.platform;
777
+ } catch {}
778
+ return process.platform;
778
779
  }
779
780
  /**
780
781
  * Re-run the platform probe and persist the detected backend into
@@ -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-OlOI9pHn.js");
5
+ const require_dist = require("../dist-BhpOMzzH.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 { B as BaseAddon, _ as audioCodecCapability, z as errMsg } from "../dist-CAgJixja.mjs";
1
+ import { B as BaseAddon, _ as audioCodecCapability, z as errMsg } from "../dist-GFzs5acw.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-OlOI9pHn.js");
5
+ const require_dist = require("../dist-BhpOMzzH.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 { B as BaseAddon, b as decoderCapability, f as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, u as HWACCEL_OPTIONS, z as errMsg } from "../dist-CAgJixja.mjs";
1
+ import { B as BaseAddon, b as decoderCapability, f as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, u as HWACCEL_OPTIONS, z as errMsg } from "../dist-GFzs5acw.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,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
6
- const require_dist = require("../dist-OlOI9pHn.js");
6
+ const require_dist = require("../dist-BhpOMzzH.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1753,6 +1753,95 @@ var INSTANCE_SEGMENTATION_MODELS = [
1753
1753
  }
1754
1754
  }
1755
1755
  ];
1756
+ var CLIP_EMBEDDING_MODELS = [
1757
+ {
1758
+ id: "mobileclip-s0",
1759
+ name: "MobileCLIP S0",
1760
+ description: "MobileCLIP S0 — Apple ultra-fast CLIP vision encoder, 512-dim, 256×256 (40 MB ONNX)",
1761
+ inputSize: {
1762
+ width: 256,
1763
+ height: 256
1764
+ },
1765
+ labels: [{
1766
+ id: "embedding",
1767
+ name: "CLIP Embedding"
1768
+ }],
1769
+ preprocessMode: "resize",
1770
+ inputNormalization: "none",
1771
+ formats: {
1772
+ onnx: {
1773
+ url: hf("clip/mobileclip-s0/onnx/camstack-mobileclip-s0-vision.onnx"),
1774
+ sizeMB: 40
1775
+ },
1776
+ openvino: ovFormat(hf("clip/mobileclip-s0/openvino/camstack-mobileclip-s0-vision.xml"), 25),
1777
+ coreml: {
1778
+ url: hf("clip/mobileclip-s0/coreml/camstack-mobileclip-s0-vision.mlpackage"),
1779
+ sizeMB: 30,
1780
+ isDirectory: true,
1781
+ files: [...MLPACKAGE_FILES],
1782
+ runtimes: ["python"]
1783
+ }
1784
+ }
1785
+ },
1786
+ {
1787
+ id: "mobileclip-s1",
1788
+ name: "MobileCLIP S1",
1789
+ description: "MobileCLIP S1 — Apple balanced CLIP vision encoder, 512-dim, 256×256 (90 MB ONNX)",
1790
+ inputSize: {
1791
+ width: 256,
1792
+ height: 256
1793
+ },
1794
+ labels: [{
1795
+ id: "embedding",
1796
+ name: "CLIP Embedding"
1797
+ }],
1798
+ preprocessMode: "resize",
1799
+ inputNormalization: "none",
1800
+ formats: {
1801
+ onnx: {
1802
+ url: hf("clip/mobileclip-s1/onnx/camstack-mobileclip-s1-vision.onnx"),
1803
+ sizeMB: 90
1804
+ },
1805
+ openvino: ovFormat(hf("clip/mobileclip-s1/openvino/camstack-mobileclip-s1-vision.xml"), 55),
1806
+ coreml: {
1807
+ url: hf("clip/mobileclip-s1/coreml/camstack-mobileclip-s1-vision.mlpackage"),
1808
+ sizeMB: 65,
1809
+ isDirectory: true,
1810
+ files: [...MLPACKAGE_FILES],
1811
+ runtimes: ["python"]
1812
+ }
1813
+ }
1814
+ },
1815
+ {
1816
+ id: "mobileclip-s2",
1817
+ name: "MobileCLIP S2",
1818
+ description: "MobileCLIP S2 — Apple high-accuracy CLIP vision encoder, 512-dim, 256×256 (150 MB ONNX)",
1819
+ inputSize: {
1820
+ width: 256,
1821
+ height: 256
1822
+ },
1823
+ labels: [{
1824
+ id: "embedding",
1825
+ name: "CLIP Embedding"
1826
+ }],
1827
+ preprocessMode: "resize",
1828
+ inputNormalization: "none",
1829
+ formats: {
1830
+ onnx: {
1831
+ url: hf("clip/mobileclip-s2/onnx/camstack-mobileclip-s2-vision.onnx"),
1832
+ sizeMB: 150
1833
+ },
1834
+ openvino: ovFormat(hf("clip/mobileclip-s2/openvino/camstack-mobileclip-s2-vision.xml"), 90),
1835
+ coreml: {
1836
+ url: hf("clip/mobileclip-s2/coreml/camstack-mobileclip-s2-vision.mlpackage"),
1837
+ sizeMB: 110,
1838
+ isDirectory: true,
1839
+ files: [...MLPACKAGE_FILES],
1840
+ runtimes: ["python"]
1841
+ }
1842
+ }
1843
+ }
1844
+ ];
1756
1845
  var AUDIO_CLASSIFIER_MODELS = [{
1757
1846
  id: "yamnet-onnx",
1758
1847
  name: "YAMNet",
@@ -2024,6 +2113,21 @@ var STEP_FACE_EMBEDDING = new PipelineStepBase({
2024
2113
  defaultModelId: "arcface-r100",
2025
2114
  defaultConfidence: 0
2026
2115
  });
2116
+ var STEP_CLIP_EMBEDDING = new PipelineStepBase({
2117
+ id: "clip-embedding",
2118
+ name: "CLIP Embedding",
2119
+ slot: "classifier",
2120
+ postprocessor: "clip",
2121
+ extractMode: "crop-roi",
2122
+ inputClasses: null,
2123
+ outputClasses: ["embedding"],
2124
+ models: [...CLIP_EMBEDDING_MODELS],
2125
+ defaultModelId: "mobileclip-s1",
2126
+ enabledByDefault: false,
2127
+ defaultConfidence: 0,
2128
+ defaultMinParentScore: .4,
2129
+ group: "CLIP"
2130
+ });
2027
2131
  var STEP_PLATE_DETECTION = new PipelineStepBase({
2028
2132
  id: "plate-detection",
2029
2133
  name: "Plate Detection",
@@ -2113,6 +2217,7 @@ var ALL_PIPELINE_STEPS = [
2113
2217
  STEP_OBJECT_DETECTION_INSTANCE,
2114
2218
  STEP_FACE_DETECTION,
2115
2219
  STEP_FACE_EMBEDDING,
2220
+ STEP_CLIP_EMBEDDING,
2116
2221
  STEP_PLATE_DETECTION,
2117
2222
  STEP_PLATE_OCR,
2118
2223
  new AnimalClassifierStep(),
@@ -2663,6 +2768,22 @@ function postprocessArcface(output, _stepDef) {
2663
2768
  embeddingNorm: normalized
2664
2769
  };
2665
2770
  }
2771
+ function postprocessClip(output, _stepDef) {
2772
+ const structured = tryStructured(output);
2773
+ if (structured) return structured;
2774
+ const tensor = output.tensor;
2775
+ if (!tensor) throw new Error("CLIP postprocessor: no tensor in engine output");
2776
+ let sumSq = 0;
2777
+ for (let i = 0; i < tensor.length; i++) sumSq += tensor[i] * tensor[i];
2778
+ const norm = Math.sqrt(sumSq);
2779
+ const normalized = Array.from({ length: tensor.length });
2780
+ for (let i = 0; i < tensor.length; i++) normalized[i] = norm === 0 ? 0 : tensor[i] / norm;
2781
+ return {
2782
+ kind: "embedding",
2783
+ embedding: Array.from(tensor),
2784
+ embeddingNorm: normalized
2785
+ };
2786
+ }
2666
2787
  function postprocessSoftmax(output, stepDef) {
2667
2788
  const structured = tryStructured(output);
2668
2789
  if (structured) return structured;
@@ -2722,6 +2843,67 @@ function postprocessCtc(output, stepDef) {
2722
2843
  confidence: seqLen > 0 ? totalScore / seqLen : 0
2723
2844
  };
2724
2845
  }
2846
+ /**
2847
+ * Bounding box [x, y, w, h] (mask-pixel space) of the largest 4-connected
2848
+ * foreground component. Port of `_largest_component_bbox` from
2849
+ * `python/postprocessors/saliency.py`.
2850
+ *
2851
+ * Returns null when there are no foreground pixels.
2852
+ */
2853
+ function largestComponentBbox(binary, w, h) {
2854
+ const seeds = [];
2855
+ for (let row = 0; row < h; row++) for (let col = 0; col < w; col++) if ((binary[row * w + col] ?? 0) > 0) seeds.push([row, col]);
2856
+ if (seeds.length === 0) return null;
2857
+ const visited = new Uint8Array(w * h);
2858
+ let best = null;
2859
+ for (const [sy, sx] of seeds) {
2860
+ const seedIdx = sy * w + sx;
2861
+ if (visited[seedIdx] === 1) continue;
2862
+ const stack = [[sy, sx]];
2863
+ visited[seedIdx] = 1;
2864
+ let minX = sx;
2865
+ let maxX = sx;
2866
+ let minY = sy;
2867
+ let maxY = sy;
2868
+ let area = 0;
2869
+ while (stack.length > 0) {
2870
+ const [cy, cx] = stack.pop();
2871
+ area += 1;
2872
+ if (cx < minX) minX = cx;
2873
+ if (cx > maxX) maxX = cx;
2874
+ if (cy < minY) minY = cy;
2875
+ if (cy > maxY) maxY = cy;
2876
+ const neighbours = [
2877
+ [cy - 1, cx],
2878
+ [cy + 1, cx],
2879
+ [cy, cx - 1],
2880
+ [cy, cx + 1]
2881
+ ];
2882
+ for (const [ny, nx] of neighbours) {
2883
+ if (ny < 0 || ny >= h || nx < 0 || nx >= w) continue;
2884
+ const nIdx = ny * w + nx;
2885
+ if ((binary[nIdx] ?? 0) > 0 && visited[nIdx] !== 1) {
2886
+ visited[nIdx] = 1;
2887
+ stack.push([ny, nx]);
2888
+ }
2889
+ }
2890
+ }
2891
+ if (best === null || area > best.area) best = {
2892
+ area,
2893
+ minX,
2894
+ minY,
2895
+ maxX,
2896
+ maxY
2897
+ };
2898
+ }
2899
+ if (best === null) return null;
2900
+ return [
2901
+ best.minX,
2902
+ best.minY,
2903
+ best.maxX - best.minX + 1,
2904
+ best.maxY - best.minY + 1
2905
+ ];
2906
+ }
2725
2907
  function postprocessSaliency(output, _stepDef) {
2726
2908
  const structured = tryStructured(output);
2727
2909
  if (structured) return structured;
@@ -2730,9 +2912,18 @@ function postprocessSaliency(output, _stepDef) {
2730
2912
  const binary = new Uint8Array(tensor.length);
2731
2913
  for (let i = 0; i < tensor.length; i++) binary[i] = 1 / (1 + Math.exp(-tensor[i])) > .5 ? 255 : 0;
2732
2914
  const side = Math.round(Math.sqrt(tensor.length));
2915
+ const mask = Buffer.from(binary).toString("base64");
2916
+ const maskBbox = largestComponentBbox(binary, side, side);
2917
+ if (maskBbox !== null) return {
2918
+ kind: "mask",
2919
+ mask,
2920
+ maskWidth: side,
2921
+ maskHeight: side,
2922
+ maskBbox
2923
+ };
2733
2924
  return {
2734
2925
  kind: "mask",
2735
- mask: Buffer.from(binary).toString("base64"),
2926
+ mask,
2736
2927
  maskWidth: side,
2737
2928
  maskHeight: side
2738
2929
  };
@@ -2879,6 +3070,7 @@ var DISPATCH = {
2879
3070
  "yolo-seg": postprocessYoloSeg,
2880
3071
  scrfd: postprocessScrfd,
2881
3072
  arcface: postprocessArcface,
3073
+ clip: postprocessClip,
2882
3074
  softmax: postprocessSoftmax,
2883
3075
  ctc: postprocessCtc,
2884
3076
  saliency: postprocessSaliency,
@@ -5950,41 +6142,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5950
6142
  return readStored(raw, isEngineChoice);
5951
6143
  }
5952
6144
  /**
5953
- * Synchronous availability probe for a Python inference backend. Runs a
5954
- * short `<python> -c "import <mod>"` with a 3s timeout. Used at
5955
- * `loadEngine` time to reject a persisted backend choice the Python
5956
- * interpreter on this host can't actually import — without this the
5957
- * detection-pipeline child process exits with code 1 the moment
5958
- * `inference_pool.py` hits its `from openvino.runtime import Core`.
5959
- *
5960
- * MUST probe the EMBEDDED portable interpreter (`pythonPath`, resolved via
5961
- * `ctx.deps.ensurePython()`) — that's where `installPythonRequirements`
5962
- * puts the backend packages and what `SharedInferencePool` actually spawns.
5963
- * Probing the system `python3` checks the wrong site-packages (and on a
5964
- * slim container there is no system python3 at all → every backend would
5965
- * be reported unavailable, silently disabling ML). Falls back to `python3`
5966
- * only when no embedded interpreter is resolved.
5967
- */
5968
- static isPythonBackendAvailable(backend, pythonPath) {
5969
- const mod = {
5970
- coreml: "coremltools",
5971
- openvino: "openvino.runtime",
5972
- onnx: "onnxruntime"
5973
- }[backend];
5974
- if (mod === null) return true;
5975
- if (!mod) return false;
5976
- try {
5977
- const { execFileSync } = require("node:child_process");
5978
- execFileSync(pythonPath || "python3", ["-c", `import ${mod}`], {
5979
- timeout: 3e3,
5980
- stdio: "pipe"
5981
- });
5982
- return true;
5983
- } catch {
5984
- return false;
5985
- }
5986
- }
5987
- /**
5988
6145
  * Re-run the platform probe for the inference engine and persist the
5989
6146
  * result into the addon's own global-settings keys. Because those
5990
6147
  * fields are declared `requiresRestart: true`, the
@@ -7070,7 +7227,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7070
7227
  });
7071
7228
  this.probePhaseUnsub = this.ctx.eventBus?.subscribe({ category: require_dist.EventCategory.PlatformProbePhase }, (event) => {
7072
7229
  if (event.data?.phase !== "done") return;
7073
- this.provider.repickEngineOnProbeReady().catch((err) => {
7230
+ this.provider?.repickEngineOnProbeReady().catch((err) => {
7074
7231
  this.ctx.logger.warn("repick on platform-probe done failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
7075
7232
  });
7076
7233
  }) ?? null;