@camstack/addon-pipeline 1.1.0 → 1.1.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.
Files changed (34) hide show
  1. package/dist/audio-analyzer/index.js +104 -29
  2. package/dist/audio-analyzer/index.mjs +100 -25
  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 +187 -56
  8. package/dist/detection-pipeline/index.mjs +175 -44
  9. package/dist/{dist-BA6DR_jV.mjs → dist-BWc-HYQz.mjs} +194 -1
  10. package/dist/{dist-BLcTVvol.js → dist-DnD2tm7T.js} +194 -1
  11. package/dist/{model-download-service-RxAOiYvX-CMAvhgO7.mjs → model-download-service-C-IHWnXx-3Mmeob3l.mjs} +1 -1
  12. package/dist/{model-download-service-RxAOiYvX-C8rTRJy_.js → model-download-service-C-IHWnXx-BnQ_awK4.js} +1 -1
  13. package/dist/motion-wasm/index.js +1 -1
  14. package/dist/motion-wasm/index.mjs +1 -1
  15. package/dist/pipeline-runner/index.js +14 -10
  16. package/dist/pipeline-runner/index.mjs +14 -10
  17. package/dist/recorder/index.js +4 -4
  18. package/dist/recorder/index.mjs +2 -2
  19. package/dist/stream-broker/_stub.js +1 -1
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Do7lgO8N.mjs} +3 -3
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-FRD2eBuz.mjs +26 -0
  22. package/dist/stream-broker/{hostInit-zLZbYJcg.mjs → hostInit-D5y5VMK8.mjs} +3 -3
  23. package/dist/stream-broker/index.js +8 -8
  24. package/dist/stream-broker/index.mjs +2 -2
  25. package/dist/stream-broker/remoteEntry.js +1 -1
  26. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js → MaskShapeCanvas-DI4BY7W2-5UPreLSr.js} +1 -1
  27. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CQzEnQoq.js → MotionZonesSettings-NcxxQN8r-Bxqs-CpZ.js} +1 -1
  28. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js → PrivacyMaskSettings-APgPLF7p-BDMPeMJd.js} +1 -1
  29. package/embed-dist/assets/{index-CSuLwWK-.js → index-BgGwqHYl.js} +9 -9
  30. package/embed-dist/index.html +1 -1
  31. package/package.json +1 -1
  32. package/python/postprocessors/saliency.py +47 -1
  33. package/python/postprocessors/test_saliency.py +23 -0
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +0 -26
@@ -1,6 +1,6 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { A as nodePin, B as BaseAddon, C as detectionPipelineCapability, J as hydrateSchema, L as supportedRuntimes$1, P as runtimeDevices$1, T as hfModelUrl, W as EventCategory, Z as parseJsonUnknown, a as COCO_TO_MACRO, et as sleep, i as COCO_80_LABELS, j as pipelineExecutorCapability, p as YAMNET_TO_MACRO, q as createEvent, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as evaluateZoneRules, x as defaultDeviceFor$1, z as errMsg } from "../dist-BA6DR_jV.mjs";
3
- import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-RxAOiYvX-CMAvhgO7.mjs";
2
+ import { A as nodePin, B as BaseAddon, C as detectionPipelineCapability, J as hydrateSchema, L as supportedRuntimes$1, P as runtimeDevices$1, T as hfModelUrl, W as EventCategory, Z as parseJsonUnknown, a as COCO_TO_MACRO, et as sleep, i as COCO_80_LABELS, j as pipelineExecutorCapability, p as YAMNET_TO_MACRO, q as createEvent, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as evaluateZoneRules, x as defaultDeviceFor$1, z as errMsg } from "../dist-BWc-HYQz.mjs";
3
+ import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } 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";
6
6
  import * as os from "node:os";
@@ -1036,6 +1036,26 @@ var ovFormat = (url, sizeMB) => {
1036
1036
  ...files ? { files } : {}
1037
1037
  };
1038
1038
  };
1039
+ /**
1040
+ * Build a precision-variant catalog entry (OpenVINO-only) derived from a base
1041
+ * detection model. fp16 halves the weights (Intel iGPU/NPU sweet spot); int8 is
1042
+ * NNCF post-training-quantized (~4× smaller, fastest on CPU/iGPU at a small
1043
+ * accuracy cost). The IRs live next to the base `.xml` on HF as
1044
+ * `camstack-<id>-<precision>.xml`. Lets an operator scale the model to the node
1045
+ * (e.g. yolo26x-int8 on a 265K, yolo26n-int8 on an N100).
1046
+ */
1047
+ var ovPrecisionVariant = (baseId, ovDir, baseName, precision, sizeMB) => ({
1048
+ id: `${baseId}-${precision}`,
1049
+ name: `${baseName} (${precision.toUpperCase()})`,
1050
+ description: `${baseName} — OpenVINO ${precision.toUpperCase()} variant for Intel iGPU/NPU; scale by hardware`,
1051
+ inputSize: {
1052
+ width: 640,
1053
+ height: 640
1054
+ },
1055
+ labels: [],
1056
+ preprocessMode: "letterbox",
1057
+ formats: { openvino: ovFormat(hf(`${ovDir}/camstack-${baseId}-${precision}.xml`), sizeMB) }
1058
+ });
1039
1059
  var MLPACKAGE_FILES = [
1040
1060
  "Manifest.json",
1041
1061
  "Data/com.apple.CoreML/model.mlmodel",
@@ -1341,7 +1361,21 @@ var OBJECT_DETECTION_MODELS = [
1341
1361
  },
1342
1362
  openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9m_relu.xml"), 38)
1343
1363
  }
1344
- }
1364
+ },
1365
+ ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "fp16", 5),
1366
+ ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "int8", 3),
1367
+ ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "fp16", 15),
1368
+ ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "int8", 8),
1369
+ ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "fp16", 5),
1370
+ ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "int8", 3),
1371
+ ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "fp16", 19),
1372
+ ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "int8", 10),
1373
+ ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "fp16", 41),
1374
+ ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "int8", 21),
1375
+ ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "fp16", 50),
1376
+ ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "int8", 25),
1377
+ ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "fp16", 112),
1378
+ ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "int8", 56)
1345
1379
  ];
1346
1380
  var FACE_DETECTION_MODELS = [{
1347
1381
  id: "scrfd-2.5g",
@@ -1788,7 +1822,7 @@ var ObjectDetectionStep = class {
1788
1822
  "animal"
1789
1823
  ],
1790
1824
  models: [...OBJECT_DETECTION_MODELS],
1791
- defaultModelId: "yolov9s",
1825
+ defaultModelId: "yolo26n",
1792
1826
  defaultConfidence: .5,
1793
1827
  labels: COCO_80_LABELS.map((l) => l.id),
1794
1828
  classMap: COCO_TO_MACRO
@@ -2037,9 +2071,9 @@ var STEP_VEHICLE_CLASSIFIER = new ClassifierWithMinConfidence({
2037
2071
  enabledByDefault: false,
2038
2072
  defaultConfidence: .3
2039
2073
  });
2040
- var STEP_SEGMENTATION_REFINER = new PipelineStepBase({
2041
- id: "segmentation-refiner",
2042
- name: "Saliency Segmentation",
2074
+ var STEP_SEGMENTATION = new PipelineStepBase({
2075
+ id: "segmentation",
2076
+ name: "Segmentation",
2043
2077
  slot: "refiner",
2044
2078
  postprocessor: "saliency",
2045
2079
  extractMode: "crop-roi",
@@ -2051,7 +2085,7 @@ var STEP_SEGMENTATION_REFINER = new PipelineStepBase({
2051
2085
  defaultConfidence: 0,
2052
2086
  group: "Segmentation"
2053
2087
  });
2054
- var STEP_INSTANCE_SEGMENTATION = new PipelineStepBase({
2088
+ new PipelineStepBase({
2055
2089
  id: "instance-segmentation",
2056
2090
  name: "Instance Segmentation",
2057
2091
  slot: "refiner",
@@ -2078,8 +2112,7 @@ var ALL_PIPELINE_STEPS = [
2078
2112
  new AnimalClassifierStep(),
2079
2113
  STEP_BIRD_CLASSIFIER,
2080
2114
  STEP_VEHICLE_CLASSIFIER,
2081
- STEP_SEGMENTATION_REFINER,
2082
- STEP_INSTANCE_SEGMENTATION,
2115
+ STEP_SEGMENTATION,
2083
2116
  STEP_AUDIO_CLASSIFIER_INSTANCE
2084
2117
  ];
2085
2118
  /** Compat: flat array of StepDefinition for existing consumers */
@@ -3250,6 +3283,18 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
3250
3283
  parent.mask = output.mask;
3251
3284
  parent.maskWidth = output.maskWidth;
3252
3285
  parent.maskHeight = output.maskHeight;
3286
+ if (output.maskBbox !== void 0 && output.maskWidth > 0 && output.maskHeight > 0) {
3287
+ const [px1, py1, px2, py2] = parent.bbox;
3288
+ const parentW = px2 - px1;
3289
+ const parentH = py2 - py1;
3290
+ const [mbx, mby, mbw, mbh] = output.maskBbox;
3291
+ parent.refinedBbox = [
3292
+ px1 + mbx / output.maskWidth * parentW,
3293
+ py1 + mby / output.maskHeight * parentH,
3294
+ px1 + (mbx + mbw) / output.maskWidth * parentW,
3295
+ py1 + (mby + mbh) / output.maskHeight * parentH
3296
+ ];
3297
+ }
3253
3298
  break;
3254
3299
  }
3255
3300
  }
@@ -3300,6 +3345,12 @@ function buildFrameResult(input) {
3300
3345
  macroClass: m.macroClass,
3301
3346
  score: m.score,
3302
3347
  bbox,
3348
+ ...m.refinedBbox !== void 0 ? { refinedBbox: bboxTupleToRect(m.refinedBbox) } : {},
3349
+ ...m.mask !== void 0 && m.maskWidth !== void 0 && m.maskHeight !== void 0 ? {
3350
+ mask: m.mask,
3351
+ maskWidth: m.maskWidth,
3352
+ maskHeight: m.maskHeight
3353
+ } : {},
3303
3354
  labels,
3304
3355
  ...m.parentId !== void 0 ? { parentId: m.parentId } : {},
3305
3356
  ...embedding !== void 0 ? {
@@ -3790,6 +3841,40 @@ function walkFieldsForDefaults(fields, out) {
3790
3841
  }
3791
3842
  }
3792
3843
  //#endregion
3844
+ //#region src/detection-pipeline/registry/custom-models.ts
3845
+ /**
3846
+ * Group a flat list of custom-model descriptors (as returned by the
3847
+ * `custom-model-registry` collection cap) into a `stepId → entries` map for
3848
+ * the picker / resolution union. Pure; order within a step preserved.
3849
+ */
3850
+ function groupCustomModelsByStep(descriptors) {
3851
+ const byStep = /* @__PURE__ */ new Map();
3852
+ for (const d of descriptors) {
3853
+ const arr = byStep.get(d.stepId) ?? [];
3854
+ arr.push(d.entry);
3855
+ byStep.set(d.stepId, arr);
3856
+ }
3857
+ return byStep;
3858
+ }
3859
+ /**
3860
+ * Union a step's static catalog models with operator-registered custom
3861
+ * models. On an `id` collision the static catalog entry wins — a custom
3862
+ * model can never shadow a built-in one.
3863
+ *
3864
+ * Pure + side-effect-free so it can be unit-tested in isolation and called
3865
+ * from the (free) `buildSchemaSlots` builder without any addon context.
3866
+ */
3867
+ function mergeCustomModels(staticModels, customModels) {
3868
+ const seen = new Set(staticModels.map((m) => m.id));
3869
+ const merged = [...staticModels];
3870
+ for (const m of customModels) {
3871
+ if (seen.has(m.id)) continue;
3872
+ seen.add(m.id);
3873
+ merged.push(m);
3874
+ }
3875
+ return merged;
3876
+ }
3877
+ //#endregion
3793
3878
  //#region src/detection-pipeline/provider.ts
3794
3879
  /**
3795
3880
  * DetectionPipelineProvider — implements IPipelineExecutorProvider.
@@ -4025,6 +4110,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4025
4110
  /** Addon context — ctx.api resolves lazily (direct caller created after boot) */
4026
4111
  addonCtx = null;
4027
4112
  /**
4113
+ * Short-lived cache of custom models pulled from the `custom-model-registry`
4114
+ * collection cap, grouped by step id. TTL-bounded so the picker / resolution
4115
+ * paths don't issue a cap round-trip on every call. Empty map = no provider
4116
+ * (or a query failure) → behaviour identical to the static-catalog-only path.
4117
+ */
4118
+ customModelsCache = null;
4119
+ /**
4028
4120
  * Per-device {@link DeviceProxy} cache used for zone gating at the
4029
4121
  * runtime path. Reads `state.zones.value` + `state.zoneRules.value`
4030
4122
  * synchronously per frame so detections inside an `exclude` zone
@@ -4353,8 +4445,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4353
4445
  * runtime through installing → verifying → ready.
4354
4446
  */
4355
4447
  async onEngineSelectionChanged() {
4448
+ const prev = this.currentEngine;
4356
4449
  const stored = await this.loadEngine();
4357
4450
  if (stored) this.currentEngine = stored;
4451
+ if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
4452
+ this.log.info("engine selection changed — rebuilding pool in place", { meta: {
4453
+ from: `${prev.backend}/${prev.device ?? "default"}`,
4454
+ to: `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`
4455
+ } });
4456
+ try {
4457
+ await this.engineFactory.dispose();
4458
+ } catch {}
4459
+ this.engineFactory = null;
4460
+ this.executor = null;
4461
+ }
4358
4462
  this.startProvisioningForCurrentEngine();
4359
4463
  }
4360
4464
  /**
@@ -4392,7 +4496,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4392
4496
  if (!steps || steps.length === 0) return;
4393
4497
  for (const step of flattenSteps(steps)) {
4394
4498
  if (!step.enabled) continue;
4395
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
4499
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
4396
4500
  if (!modelEntry) continue;
4397
4501
  if (isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
4398
4502
  await this.downloadWithRetry(modelEntry, format, 3);
@@ -4470,10 +4574,44 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4470
4574
  return { hardware: null };
4471
4575
  }
4472
4576
  }
4577
+ /**
4578
+ * Pull custom models from the `custom-model-registry` collection cap,
4579
+ * grouped by step id. 5s TTL cache. Fully graceful: when no provider is
4580
+ * registered (or the query fails) it returns an empty map and logs at most
4581
+ * a warning — callers then behave exactly like the static-catalog path.
4582
+ */
4583
+ async getCustomModels() {
4584
+ const now = Date.now();
4585
+ if (this.customModelsCache && now - this.customModelsCache.at < 5e3) return this.customModelsCache.byStep;
4586
+ let byStep = /* @__PURE__ */ new Map();
4587
+ try {
4588
+ const api = this.addonCtx?.api;
4589
+ if (api) {
4590
+ if ((await api.addons.listCapabilityProviders.query({ capName: "custom-model-registry" })).some((p) => p.isActive)) byStep = groupCustomModelsByStep(await api.customModelRegistry.listModels.query());
4591
+ }
4592
+ } catch (err) {
4593
+ this.log.warn("custom-model-registry query failed — using static catalog only", { meta: { error: errMsg(err) } });
4594
+ }
4595
+ this.customModelsCache = {
4596
+ at: now,
4597
+ byStep
4598
+ };
4599
+ return byStep;
4600
+ }
4601
+ /**
4602
+ * Resolve a model id within a step to a catalog entry — static catalog
4603
+ * first, then the custom registry. Returns undefined if neither has it.
4604
+ */
4605
+ async resolveModelEntry(addonId, modelId) {
4606
+ const fromCatalog = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4607
+ if (fromCatalog) return fromCatalog;
4608
+ return (await this.getCustomModels()).get(addonId)?.find((m) => m.id === modelId);
4609
+ }
4473
4610
  async getSchema(engine) {
4474
4611
  if (!engine || !engine.runtime) engine = await this.getSelectedEngine();
4475
4612
  const format = engine.format;
4476
- const slots = buildSchemaSlots(format, this.modelsDir);
4613
+ const customByStep = await this.getCustomModels();
4614
+ const slots = buildSchemaSlots(format, this.modelsDir, customByStep);
4477
4615
  const { hardware } = await this.fetchProbeGatingData();
4478
4616
  const env = runtimeEnvFromProcess(toProbedHardware(hardware));
4479
4617
  return {
@@ -4660,7 +4798,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4660
4798
  }
4661
4799
  async downloadModel(input) {
4662
4800
  const { modelId, format, addonId } = input;
4663
- const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4801
+ const modelEntry = await this.resolveModelEntry(addonId, modelId);
4664
4802
  if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
4665
4803
  const formatEntry = modelEntry.formats[format];
4666
4804
  if (!formatEntry) throw new Error(`Model "${modelId}" has no ${format} format. Available: ${Object.keys(modelEntry.formats).join(", ")}`);
@@ -4723,7 +4861,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4723
4861
  }
4724
4862
  async deleteModel(input) {
4725
4863
  const { modelId, format, addonId } = input;
4726
- const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4864
+ const modelEntry = await this.resolveModelEntry(addonId, modelId);
4727
4865
  if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
4728
4866
  if (!deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
4729
4867
  this.log.info("Model deleted from disk", { meta: {
@@ -5179,7 +5317,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5179
5317
  const work = (async () => {
5180
5318
  const format = this.currentEngine?.format ?? "onnx";
5181
5319
  for (const step of needed) {
5182
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
5320
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
5183
5321
  if (modelEntry && !isModelDownloaded(this.modelsDir, modelEntry, format)) {
5184
5322
  this.log.info("Downloading model for step", { meta: {
5185
5323
  modelId: step.modelId,
@@ -5674,7 +5812,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5674
5812
  const format = this.currentEngine.format;
5675
5813
  const downloads = [];
5676
5814
  for (const step of flattenSteps(steps)) {
5677
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
5815
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
5678
5816
  if (!modelEntry) {
5679
5817
  this.log.warn("Model not found in step catalog — skipping download", { meta: {
5680
5818
  modelId: step.modelId,
@@ -6077,11 +6215,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6077
6215
  }
6078
6216
  }
6079
6217
  };
6080
- function buildSchemaSlots(format, modelsDir) {
6218
+ function buildSchemaSlots(format, modelsDir, customByStep) {
6081
6219
  const slotMap = /* @__PURE__ */ new Map();
6082
6220
  for (const pipelineStep of ALL_PIPELINE_STEPS) {
6083
6221
  const step = pipelineStep.definition;
6084
- const availableModels = step.models.filter((m) => m.formats[format]);
6222
+ const availableModels = mergeCustomModels(step.models, customByStep?.get(step.id) ?? []).filter((m) => m.formats[format]);
6085
6223
  if (availableModels.length === 0) continue;
6086
6224
  const slot = step.slot;
6087
6225
  if (!slotMap.has(slot)) slotMap.set(slot, []);
@@ -6186,8 +6324,7 @@ function buildDefaultStepTree(format) {
6186
6324
  makeStep("animal-classifier", [], { enabled: false }),
6187
6325
  makeStep("bird-classifier", [], { enabled: false }),
6188
6326
  makeStep("vehicle-classifier", [], { enabled: false }),
6189
- makeStep("segmentation-refiner", [], { enabled: false }),
6190
- makeStep("instance-segmentation", [], { enabled: false })
6327
+ makeStep("segmentation", [], { enabled: false })
6191
6328
  ].filter((s) => s !== null));
6192
6329
  const audioEngine = getDefaultModelForFormat("audio-classifier", format) === "apple-soundanalysis" ? {
6193
6330
  runtime: "python",
@@ -6477,8 +6614,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6477
6614
  label: "Execution provider",
6478
6615
  options: [...STATIC_BACKEND_OPTIONS],
6479
6616
  default: DEFAULT_CONFIG.engineBackend,
6480
- immediate: true,
6481
- requiresRestart: true
6617
+ immediate: true
6482
6618
  }),
6483
6619
  this.field({
6484
6620
  type: "select",
@@ -6486,8 +6622,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6486
6622
  label: "Hardware device",
6487
6623
  options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
6488
6624
  default: DEFAULT_CONFIG.engineDevice,
6489
- immediate: true,
6490
- requiresRestart: true
6625
+ immediate: true
6491
6626
  })
6492
6627
  ]
6493
6628
  }, {
@@ -6526,8 +6661,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6526
6661
  "onnx",
6527
6662
  "cpu"
6528
6663
  ]
6529
- },
6530
- requiresRestart: true
6664
+ }
6531
6665
  }),
6532
6666
  this.field({
6533
6667
  type: "slider",
@@ -6544,8 +6678,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6544
6678
  showWhen: {
6545
6679
  field: "batchMode",
6546
6680
  notEquals: "none"
6547
- },
6548
- requiresRestart: true
6681
+ }
6549
6682
  }),
6550
6683
  this.field({
6551
6684
  type: "slider",
@@ -6562,8 +6695,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6562
6695
  showWhen: {
6563
6696
  field: "batchMode",
6564
6697
  notEquals: "none"
6565
- },
6566
- requiresRestart: true
6698
+ }
6567
6699
  }),
6568
6700
  this.field({
6569
6701
  type: "slider",
@@ -6576,8 +6708,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6576
6708
  default: void 0,
6577
6709
  showValue: true,
6578
6710
  nullable: true,
6579
- nullLabel: "Auto",
6580
- requiresRestart: true
6711
+ nullLabel: "Auto"
6581
6712
  }),
6582
6713
  this.field({
6583
6714
  type: "slider",
@@ -6590,8 +6721,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
6590
6721
  default: void 0,
6591
6722
  showValue: true,
6592
6723
  nullable: true,
6593
- nullLabel: "Auto",
6594
- requiresRestart: true
6724
+ nullLabel: "Auto"
6595
6725
  })
6596
6726
  ]
6597
6727
  }] });
@@ -6964,16 +7094,17 @@ var DetectionPipelineAddon = class extends BaseAddon {
6964
7094
  return false;
6965
7095
  }
6966
7096
  /**
6967
- * BaseAddon calls `onConfigChanged` after every settings write.
6968
- * Group-runner addons can't honour the schema's `requiresRestart:
6969
- * true` flag (the restart cap returns "process not found" for
6970
- * processes spawned in a kernel group worker). To make tuning
6971
- * changes actually take effect, watch the pool-bound subset and
6972
- * respawn the provider in place when it flips.
6973
- *
6974
- * Engine cascade (`engineRuntime/Backend/Device`) and audio
6975
- * settings don't require this those still rely on the addon
6976
- * lifecycle (engineFactory rebuild on next runPipeline).
7097
+ * BaseAddon calls `onConfigChanged` after every settings write. This is the
7098
+ * SOLE apply path for engine + tuning changes — none of those fields declare
7099
+ * `requiresRestart` anymore (an addon restart re-probes hardware + reloads
7100
+ * every model on every set, which is exactly what we want to avoid). Instead:
7101
+ * - Pool-bound tuning (`concurrency`/`batchMode`/`windowMs`/…) in-place
7102
+ * pool respawn when the snapshot flips (`poolConfigChanged` below).
7103
+ * - Engine cascade (`engineBackend`/`engineDevice`) → the provider's
7104
+ * `onEngineSelectionChanged` disposes the device-bound factory so the next
7105
+ * runPipeline rebuilds the pool on the new selection, in place.
7106
+ * Both apply optimistically the inference gate stays closed for the short
7107
+ * re-spin, frames are dropped (never crashed), no addon bounce.
6977
7108
  */
6978
7109
  async onConfigChanged() {
6979
7110
  await this.refreshNodeEngineFromStore();
@@ -4627,7 +4627,7 @@ function _instanceof(cls, params = {}) {
4627
4627
  return inst;
4628
4628
  }
4629
4629
  //#endregion
4630
- //#region ../types/dist/sleep-B1dKJAMJ.mjs
4630
+ //#region ../types/dist/sleep-BV7rLc6Y.mjs
4631
4631
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4632
4632
  EventCategory["SystemBoot"] = "system.boot";
4633
4633
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5106,6 +5106,12 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5106
5106
  * Payload: `{ deviceId, childDeviceIds, hiddenChildIds }`.
5107
5107
  */
5108
5108
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
5109
+ /**
5110
+ * Progress update from a running model conversion job.
5111
+ * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
5112
+ * Emitted by `addon-model-studio` on the converting node.
5113
+ */
5114
+ EventCategory["ModelConvertProgress"] = "model-convert.progress";
5109
5115
  return EventCategory;
5110
5116
  }({});
5111
5117
  Object.fromEntries([
@@ -6756,6 +6762,13 @@ object({
6756
6762
  unreachable: number()
6757
6763
  })
6758
6764
  });
6765
+ var LabelDefinitionSchema = object({
6766
+ id: string(),
6767
+ name: string(),
6768
+ category: string().optional(),
6769
+ description: string().optional(),
6770
+ icon: string().optional()
6771
+ });
6759
6772
  var MODEL_FORMATS = [
6760
6773
  "onnx",
6761
6774
  "coreml",
@@ -6764,6 +6777,120 @@ var MODEL_FORMATS = [
6764
6777
  "pt"
6765
6778
  ];
6766
6779
  /**
6780
+ * Multi-file format payload.
6781
+ *
6782
+ * - Directory formats (`isDirectory: true`, e.g. `.mlpackage`): files
6783
+ * relative to the directory root — the downloader fetches each from
6784
+ * `{url}/{file}` into `{modelDir}/{file}`. If omitted, it probes the
6785
+ * HuggingFace API (slower).
6786
+ * - Single-file formats (no `isDirectory`, e.g. OpenVINO IR): sibling
6787
+ * files fetched from the SAME remote directory as `url` and stored flat
6788
+ * alongside the main file — e.g. `['camstack-yolov9t.bin']` for the IR
6789
+ * weights next to `camstack-yolov9t.xml`.
6790
+ */
6791
+ var ModelFormatEntrySchema = object({
6792
+ url: string(),
6793
+ sizeMB: number(),
6794
+ /** Whether this format is a directory bundle (e.g., .mlpackage) rather than a single file */
6795
+ isDirectory: boolean().optional(),
6796
+ /** Multi-file payload (directory members or sibling files). */
6797
+ files: array(string()).readonly().optional(),
6798
+ /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
6799
+ runtimes: array(_enum(["node", "python"])).readonly().optional()
6800
+ });
6801
+ /**
6802
+ * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
6803
+ * The downloader fetches from `url` and saves to `{modelsDir}/{filename}`.
6804
+ */
6805
+ var ModelExtraFileSchema = object({
6806
+ url: string(),
6807
+ filename: string(),
6808
+ sizeMB: number()
6809
+ });
6810
+ /**
6811
+ * Per-format payload map. Modelled as an explicit object (one optional key
6812
+ * per `ModelFormat`) rather than `z.record(enum, …)` — zod v4's enum-keyed
6813
+ * record requires every key, but a catalog entry only ships a subset of
6814
+ * formats.
6815
+ */
6816
+ var ModelFormatsSchema = object({
6817
+ onnx: ModelFormatEntrySchema.optional(),
6818
+ coreml: ModelFormatEntrySchema.optional(),
6819
+ openvino: ModelFormatEntrySchema.optional(),
6820
+ tflite: ModelFormatEntrySchema.optional(),
6821
+ pt: ModelFormatEntrySchema.optional()
6822
+ });
6823
+ var ModelCatalogEntrySchema = object({
6824
+ id: string(),
6825
+ name: string(),
6826
+ description: string(),
6827
+ formats: ModelFormatsSchema,
6828
+ inputSize: object({
6829
+ width: number(),
6830
+ height: number()
6831
+ }),
6832
+ labels: array(LabelDefinitionSchema).readonly(),
6833
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6834
+ inputNormalization: _enum([
6835
+ "zero-one",
6836
+ "imagenet",
6837
+ "none"
6838
+ ]).optional(),
6839
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6840
+ /**
6841
+ * When true, the executor produces a landmark-aligned crop (similarity warp
6842
+ * onto the canonical template) before this step runs, instead of a plain
6843
+ * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
6844
+ * their embeddings are only discriminative on an aligned input. The face
6845
+ * detector that produced the parent detail must emit 5 landmarks.
6846
+ */
6847
+ faceAlignment: boolean().optional(),
6848
+ /**
6849
+ * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6850
+ * Downloaded into the same modelsDir alongside the model file.
6851
+ */
6852
+ extraFiles: array(ModelExtraFileSchema).readonly().optional()
6853
+ });
6854
+ var ConvertTargetSchema = discriminatedUnion("format", [object({
6855
+ format: literal("openvino"),
6856
+ precisions: array(_enum(["fp16", "int8"])).min(1).readonly()
6857
+ }), object({ format: literal("coreml") })]);
6858
+ var ModelConvertMetadataSchema = object({
6859
+ id: string().regex(/^[a-zA-Z0-9._-]+$/),
6860
+ name: string(),
6861
+ labels: array(LabelDefinitionSchema).readonly(),
6862
+ inputSize: object({
6863
+ width: number(),
6864
+ height: number()
6865
+ }),
6866
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6867
+ inputNormalization: _enum([
6868
+ "zero-one",
6869
+ "imagenet",
6870
+ "none"
6871
+ ]).optional(),
6872
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6873
+ outputFormat: _enum([
6874
+ "yolo",
6875
+ "ssd",
6876
+ "embedding",
6877
+ "classification",
6878
+ "ocr",
6879
+ "segmentation"
6880
+ ]),
6881
+ faceAlignment: boolean().optional()
6882
+ });
6883
+ var ConvertResultSchema = object({
6884
+ entry: ModelCatalogEntrySchema,
6885
+ artifacts: array(object({
6886
+ format: _enum(MODEL_FORMATS),
6887
+ precision: _enum(["fp16", "int8"]).optional(),
6888
+ sizeMB: number(),
6889
+ validated: boolean(),
6890
+ files: array(string()).readonly()
6891
+ })).readonly()
6892
+ });
6893
+ /**
6767
6894
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
6768
6895
  * Named `RecordingWeekday` to avoid collision with the string-union
6769
6896
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -13667,6 +13794,54 @@ var EnrichedWidgetMetadataSchema = WidgetMetadataSchema.extend({
13667
13794
  bundleUrl: string()
13668
13795
  });
13669
13796
  method(_void(), array(EnrichedWidgetMetadataSchema).readonly());
13797
+ /**
13798
+ * `custom-model-registry` — collection cap exposing operator-registered
13799
+ * custom detection models. Each provider (today: `addon-model-studio`)
13800
+ * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
13801
+ * them across providers (`concatCollection`).
13802
+ *
13803
+ * The detection-pipeline is *aware* of this cap: when at least one provider
13804
+ * exists it unions these descriptors into the per-step model picker and the
13805
+ * runtime model-resolution path, alongside the static catalog. When no
13806
+ * provider exists the consumer no-ops entirely (identical to the catalog-only
13807
+ * behaviour).
13808
+ *
13809
+ * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
13810
+ * the static catalog uses — so the existing download/resolution code consumes
13811
+ * it unchanged. `stepId` is the detection step the model targets
13812
+ * (e.g. `'object-detection'`).
13813
+ */
13814
+ var CustomModelDescriptorSchema = object({
13815
+ stepId: string(),
13816
+ entry: ModelCatalogEntrySchema
13817
+ });
13818
+ method(_void(), array(CustomModelDescriptorSchema).readonly());
13819
+ method(object({
13820
+ nodeId: string(),
13821
+ modelId: string(),
13822
+ format: _enum(MODEL_FORMATS),
13823
+ entry: ModelCatalogEntrySchema
13824
+ }), object({
13825
+ ok: boolean(),
13826
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
13827
+ sha256: string(),
13828
+ bytes: number(),
13829
+ /** The target node's modelsDir the artifact landed in. */
13830
+ path: string()
13831
+ }), {
13832
+ kind: "mutation",
13833
+ auth: "admin"
13834
+ });
13835
+ method(object({
13836
+ sourceUrl: string(),
13837
+ metadata: ModelConvertMetadataSchema,
13838
+ targets: array(ConvertTargetSchema).min(1).readonly(),
13839
+ calibrationRef: string().optional(),
13840
+ sessionId: string().optional()
13841
+ }), ConvertResultSchema, {
13842
+ kind: "mutation",
13843
+ auth: "admin"
13844
+ });
13670
13845
  var AddonHttpRouteSchema = object({
13671
13846
  method: _enum([
13672
13847
  "GET",
@@ -18916,6 +19091,12 @@ Object.freeze({
18916
19091
  addonId: null,
18917
19092
  access: "create"
18918
19093
  },
19094
+ "customModelRegistry.listModels": {
19095
+ capName: "custom-model-registry",
19096
+ capScope: "system",
19097
+ addonId: null,
19098
+ access: "view"
19099
+ },
18919
19100
  "decoder.createSession": {
18920
19101
  capName: "decoder",
18921
19102
  capScope: "system",
@@ -20140,6 +20321,18 @@ Object.freeze({
20140
20321
  addonId: null,
20141
20322
  access: "view"
20142
20323
  },
20324
+ "modelConvert.convert": {
20325
+ capName: "model-convert",
20326
+ capScope: "system",
20327
+ addonId: null,
20328
+ access: "create"
20329
+ },
20330
+ "modelDistributor.distributeModel": {
20331
+ capName: "model-distributor",
20332
+ capScope: "system",
20333
+ addonId: null,
20334
+ access: "create"
20335
+ },
20143
20336
  "motion.isDetected": {
20144
20337
  capName: "motion",
20145
20338
  capScope: "device",