@camstack/addon-post-analysis 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.
@@ -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([
@@ -6644,6 +6650,13 @@ object({
6644
6650
  unreachable: number()
6645
6651
  })
6646
6652
  });
6653
+ var LabelDefinitionSchema = object({
6654
+ id: string(),
6655
+ name: string(),
6656
+ category: string().optional(),
6657
+ description: string().optional(),
6658
+ icon: string().optional()
6659
+ });
6647
6660
  var MODEL_FORMATS = [
6648
6661
  "onnx",
6649
6662
  "coreml",
@@ -6652,6 +6665,120 @@ var MODEL_FORMATS = [
6652
6665
  "pt"
6653
6666
  ];
6654
6667
  /**
6668
+ * Multi-file format payload.
6669
+ *
6670
+ * - Directory formats (`isDirectory: true`, e.g. `.mlpackage`): files
6671
+ * relative to the directory root — the downloader fetches each from
6672
+ * `{url}/{file}` into `{modelDir}/{file}`. If omitted, it probes the
6673
+ * HuggingFace API (slower).
6674
+ * - Single-file formats (no `isDirectory`, e.g. OpenVINO IR): sibling
6675
+ * files fetched from the SAME remote directory as `url` and stored flat
6676
+ * alongside the main file — e.g. `['camstack-yolov9t.bin']` for the IR
6677
+ * weights next to `camstack-yolov9t.xml`.
6678
+ */
6679
+ var ModelFormatEntrySchema = object({
6680
+ url: string(),
6681
+ sizeMB: number(),
6682
+ /** Whether this format is a directory bundle (e.g., .mlpackage) rather than a single file */
6683
+ isDirectory: boolean().optional(),
6684
+ /** Multi-file payload (directory members or sibling files). */
6685
+ files: array(string()).readonly().optional(),
6686
+ /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
6687
+ runtimes: array(_enum(["node", "python"])).readonly().optional()
6688
+ });
6689
+ /**
6690
+ * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
6691
+ * The downloader fetches from `url` and saves to `{modelsDir}/{filename}`.
6692
+ */
6693
+ var ModelExtraFileSchema = object({
6694
+ url: string(),
6695
+ filename: string(),
6696
+ sizeMB: number()
6697
+ });
6698
+ /**
6699
+ * Per-format payload map. Modelled as an explicit object (one optional key
6700
+ * per `ModelFormat`) rather than `z.record(enum, …)` — zod v4's enum-keyed
6701
+ * record requires every key, but a catalog entry only ships a subset of
6702
+ * formats.
6703
+ */
6704
+ var ModelFormatsSchema = object({
6705
+ onnx: ModelFormatEntrySchema.optional(),
6706
+ coreml: ModelFormatEntrySchema.optional(),
6707
+ openvino: ModelFormatEntrySchema.optional(),
6708
+ tflite: ModelFormatEntrySchema.optional(),
6709
+ pt: ModelFormatEntrySchema.optional()
6710
+ });
6711
+ var ModelCatalogEntrySchema = object({
6712
+ id: string(),
6713
+ name: string(),
6714
+ description: string(),
6715
+ formats: ModelFormatsSchema,
6716
+ inputSize: object({
6717
+ width: number(),
6718
+ height: number()
6719
+ }),
6720
+ labels: array(LabelDefinitionSchema).readonly(),
6721
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6722
+ inputNormalization: _enum([
6723
+ "zero-one",
6724
+ "imagenet",
6725
+ "none"
6726
+ ]).optional(),
6727
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6728
+ /**
6729
+ * When true, the executor produces a landmark-aligned crop (similarity warp
6730
+ * onto the canonical template) before this step runs, instead of a plain
6731
+ * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
6732
+ * their embeddings are only discriminative on an aligned input. The face
6733
+ * detector that produced the parent detail must emit 5 landmarks.
6734
+ */
6735
+ faceAlignment: boolean().optional(),
6736
+ /**
6737
+ * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6738
+ * Downloaded into the same modelsDir alongside the model file.
6739
+ */
6740
+ extraFiles: array(ModelExtraFileSchema).readonly().optional()
6741
+ });
6742
+ var ConvertTargetSchema = discriminatedUnion("format", [object({
6743
+ format: literal("openvino"),
6744
+ precisions: array(_enum(["fp16", "int8"])).min(1).readonly()
6745
+ }), object({ format: literal("coreml") })]);
6746
+ var ModelConvertMetadataSchema = object({
6747
+ id: string().regex(/^[a-zA-Z0-9._-]+$/),
6748
+ name: string(),
6749
+ labels: array(LabelDefinitionSchema).readonly(),
6750
+ inputSize: object({
6751
+ width: number(),
6752
+ height: number()
6753
+ }),
6754
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6755
+ inputNormalization: _enum([
6756
+ "zero-one",
6757
+ "imagenet",
6758
+ "none"
6759
+ ]).optional(),
6760
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6761
+ outputFormat: _enum([
6762
+ "yolo",
6763
+ "ssd",
6764
+ "embedding",
6765
+ "classification",
6766
+ "ocr",
6767
+ "segmentation"
6768
+ ]),
6769
+ faceAlignment: boolean().optional()
6770
+ });
6771
+ var ConvertResultSchema = object({
6772
+ entry: ModelCatalogEntrySchema,
6773
+ artifacts: array(object({
6774
+ format: _enum(MODEL_FORMATS),
6775
+ precision: _enum(["fp16", "int8"]).optional(),
6776
+ sizeMB: number(),
6777
+ validated: boolean(),
6778
+ files: array(string()).readonly()
6779
+ })).readonly()
6780
+ });
6781
+ /**
6655
6782
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
6656
6783
  * Named `RecordingWeekday` to avoid collision with the string-union
6657
6784
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -12588,6 +12715,54 @@ var EnrichedWidgetMetadataSchema = WidgetMetadataSchema.extend({
12588
12715
  bundleUrl: string()
12589
12716
  });
12590
12717
  method(_void(), array(EnrichedWidgetMetadataSchema).readonly());
12718
+ /**
12719
+ * `custom-model-registry` — collection cap exposing operator-registered
12720
+ * custom detection models. Each provider (today: `addon-model-studio`)
12721
+ * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
12722
+ * them across providers (`concatCollection`).
12723
+ *
12724
+ * The detection-pipeline is *aware* of this cap: when at least one provider
12725
+ * exists it unions these descriptors into the per-step model picker and the
12726
+ * runtime model-resolution path, alongside the static catalog. When no
12727
+ * provider exists the consumer no-ops entirely (identical to the catalog-only
12728
+ * behaviour).
12729
+ *
12730
+ * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
12731
+ * the static catalog uses — so the existing download/resolution code consumes
12732
+ * it unchanged. `stepId` is the detection step the model targets
12733
+ * (e.g. `'object-detection'`).
12734
+ */
12735
+ var CustomModelDescriptorSchema = object({
12736
+ stepId: string(),
12737
+ entry: ModelCatalogEntrySchema
12738
+ });
12739
+ method(_void(), array(CustomModelDescriptorSchema).readonly());
12740
+ method(object({
12741
+ nodeId: string(),
12742
+ modelId: string(),
12743
+ format: _enum(MODEL_FORMATS),
12744
+ entry: ModelCatalogEntrySchema
12745
+ }), object({
12746
+ ok: boolean(),
12747
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
12748
+ sha256: string(),
12749
+ bytes: number(),
12750
+ /** The target node's modelsDir the artifact landed in. */
12751
+ path: string()
12752
+ }), {
12753
+ kind: "mutation",
12754
+ auth: "admin"
12755
+ });
12756
+ method(object({
12757
+ sourceUrl: string(),
12758
+ metadata: ModelConvertMetadataSchema,
12759
+ targets: array(ConvertTargetSchema).min(1).readonly(),
12760
+ calibrationRef: string().optional(),
12761
+ sessionId: string().optional()
12762
+ }), ConvertResultSchema, {
12763
+ kind: "mutation",
12764
+ auth: "admin"
12765
+ });
12591
12766
  var AddonHttpRouteSchema = object({
12592
12767
  method: _enum([
12593
12768
  "GET",
@@ -17601,6 +17776,12 @@ Object.freeze({
17601
17776
  addonId: null,
17602
17777
  access: "create"
17603
17778
  },
17779
+ "customModelRegistry.listModels": {
17780
+ capName: "custom-model-registry",
17781
+ capScope: "system",
17782
+ addonId: null,
17783
+ access: "view"
17784
+ },
17604
17785
  "decoder.createSession": {
17605
17786
  capName: "decoder",
17606
17787
  capScope: "system",
@@ -18825,6 +19006,18 @@ Object.freeze({
18825
19006
  addonId: null,
18826
19007
  access: "view"
18827
19008
  },
19009
+ "modelConvert.convert": {
19010
+ capName: "model-convert",
19011
+ capScope: "system",
19012
+ addonId: null,
19013
+ access: "create"
19014
+ },
19015
+ "modelDistributor.distributeModel": {
19016
+ capName: "model-distributor",
19017
+ capScope: "system",
19018
+ addonId: null,
19019
+ access: "create"
19020
+ },
18828
19021
  "motion.isDetected": {
18829
19022
  capName: "motion",
18830
19023
  capScope: "device",
@@ -4649,7 +4649,7 @@ function _instanceof(cls, params = {}) {
4649
4649
  return inst;
4650
4650
  }
4651
4651
  //#endregion
4652
- //#region ../types/dist/sleep-B1dKJAMJ.mjs
4652
+ //#region ../types/dist/sleep-BV7rLc6Y.mjs
4653
4653
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4654
4654
  EventCategory["SystemBoot"] = "system.boot";
4655
4655
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5128,6 +5128,12 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5128
5128
  * Payload: `{ deviceId, childDeviceIds, hiddenChildIds }`.
5129
5129
  */
5130
5130
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
5131
+ /**
5132
+ * Progress update from a running model conversion job.
5133
+ * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
5134
+ * Emitted by `addon-model-studio` on the converting node.
5135
+ */
5136
+ EventCategory["ModelConvertProgress"] = "model-convert.progress";
5131
5137
  return EventCategory;
5132
5138
  }({});
5133
5139
  Object.fromEntries([
@@ -6666,6 +6672,13 @@ object({
6666
6672
  unreachable: number()
6667
6673
  })
6668
6674
  });
6675
+ var LabelDefinitionSchema = object({
6676
+ id: string(),
6677
+ name: string(),
6678
+ category: string().optional(),
6679
+ description: string().optional(),
6680
+ icon: string().optional()
6681
+ });
6669
6682
  var MODEL_FORMATS = [
6670
6683
  "onnx",
6671
6684
  "coreml",
@@ -6674,6 +6687,120 @@ var MODEL_FORMATS = [
6674
6687
  "pt"
6675
6688
  ];
6676
6689
  /**
6690
+ * Multi-file format payload.
6691
+ *
6692
+ * - Directory formats (`isDirectory: true`, e.g. `.mlpackage`): files
6693
+ * relative to the directory root — the downloader fetches each from
6694
+ * `{url}/{file}` into `{modelDir}/{file}`. If omitted, it probes the
6695
+ * HuggingFace API (slower).
6696
+ * - Single-file formats (no `isDirectory`, e.g. OpenVINO IR): sibling
6697
+ * files fetched from the SAME remote directory as `url` and stored flat
6698
+ * alongside the main file — e.g. `['camstack-yolov9t.bin']` for the IR
6699
+ * weights next to `camstack-yolov9t.xml`.
6700
+ */
6701
+ var ModelFormatEntrySchema = object({
6702
+ url: string(),
6703
+ sizeMB: number(),
6704
+ /** Whether this format is a directory bundle (e.g., .mlpackage) rather than a single file */
6705
+ isDirectory: boolean().optional(),
6706
+ /** Multi-file payload (directory members or sibling files). */
6707
+ files: array(string()).readonly().optional(),
6708
+ /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
6709
+ runtimes: array(_enum(["node", "python"])).readonly().optional()
6710
+ });
6711
+ /**
6712
+ * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
6713
+ * The downloader fetches from `url` and saves to `{modelsDir}/{filename}`.
6714
+ */
6715
+ var ModelExtraFileSchema = object({
6716
+ url: string(),
6717
+ filename: string(),
6718
+ sizeMB: number()
6719
+ });
6720
+ /**
6721
+ * Per-format payload map. Modelled as an explicit object (one optional key
6722
+ * per `ModelFormat`) rather than `z.record(enum, …)` — zod v4's enum-keyed
6723
+ * record requires every key, but a catalog entry only ships a subset of
6724
+ * formats.
6725
+ */
6726
+ var ModelFormatsSchema = object({
6727
+ onnx: ModelFormatEntrySchema.optional(),
6728
+ coreml: ModelFormatEntrySchema.optional(),
6729
+ openvino: ModelFormatEntrySchema.optional(),
6730
+ tflite: ModelFormatEntrySchema.optional(),
6731
+ pt: ModelFormatEntrySchema.optional()
6732
+ });
6733
+ var ModelCatalogEntrySchema = object({
6734
+ id: string(),
6735
+ name: string(),
6736
+ description: string(),
6737
+ formats: ModelFormatsSchema,
6738
+ inputSize: object({
6739
+ width: number(),
6740
+ height: number()
6741
+ }),
6742
+ labels: array(LabelDefinitionSchema).readonly(),
6743
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6744
+ inputNormalization: _enum([
6745
+ "zero-one",
6746
+ "imagenet",
6747
+ "none"
6748
+ ]).optional(),
6749
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6750
+ /**
6751
+ * When true, the executor produces a landmark-aligned crop (similarity warp
6752
+ * onto the canonical template) before this step runs, instead of a plain
6753
+ * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
6754
+ * their embeddings are only discriminative on an aligned input. The face
6755
+ * detector that produced the parent detail must emit 5 landmarks.
6756
+ */
6757
+ faceAlignment: boolean().optional(),
6758
+ /**
6759
+ * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6760
+ * Downloaded into the same modelsDir alongside the model file.
6761
+ */
6762
+ extraFiles: array(ModelExtraFileSchema).readonly().optional()
6763
+ });
6764
+ var ConvertTargetSchema = discriminatedUnion("format", [object({
6765
+ format: literal("openvino"),
6766
+ precisions: array(_enum(["fp16", "int8"])).min(1).readonly()
6767
+ }), object({ format: literal("coreml") })]);
6768
+ var ModelConvertMetadataSchema = object({
6769
+ id: string().regex(/^[a-zA-Z0-9._-]+$/),
6770
+ name: string(),
6771
+ labels: array(LabelDefinitionSchema).readonly(),
6772
+ inputSize: object({
6773
+ width: number(),
6774
+ height: number()
6775
+ }),
6776
+ inputLayout: _enum(["nchw", "nhwc"]).optional(),
6777
+ inputNormalization: _enum([
6778
+ "zero-one",
6779
+ "imagenet",
6780
+ "none"
6781
+ ]).optional(),
6782
+ preprocessMode: _enum(["letterbox", "resize"]).optional(),
6783
+ outputFormat: _enum([
6784
+ "yolo",
6785
+ "ssd",
6786
+ "embedding",
6787
+ "classification",
6788
+ "ocr",
6789
+ "segmentation"
6790
+ ]),
6791
+ faceAlignment: boolean().optional()
6792
+ });
6793
+ var ConvertResultSchema = object({
6794
+ entry: ModelCatalogEntrySchema,
6795
+ artifacts: array(object({
6796
+ format: _enum(MODEL_FORMATS),
6797
+ precision: _enum(["fp16", "int8"]).optional(),
6798
+ sizeMB: number(),
6799
+ validated: boolean(),
6800
+ files: array(string()).readonly()
6801
+ })).readonly()
6802
+ });
6803
+ /**
6677
6804
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
6678
6805
  * Named `RecordingWeekday` to avoid collision with the string-union
6679
6806
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -12610,6 +12737,54 @@ var EnrichedWidgetMetadataSchema = WidgetMetadataSchema.extend({
12610
12737
  bundleUrl: string()
12611
12738
  });
12612
12739
  method(_void(), array(EnrichedWidgetMetadataSchema).readonly());
12740
+ /**
12741
+ * `custom-model-registry` — collection cap exposing operator-registered
12742
+ * custom detection models. Each provider (today: `addon-model-studio`)
12743
+ * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
12744
+ * them across providers (`concatCollection`).
12745
+ *
12746
+ * The detection-pipeline is *aware* of this cap: when at least one provider
12747
+ * exists it unions these descriptors into the per-step model picker and the
12748
+ * runtime model-resolution path, alongside the static catalog. When no
12749
+ * provider exists the consumer no-ops entirely (identical to the catalog-only
12750
+ * behaviour).
12751
+ *
12752
+ * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
12753
+ * the static catalog uses — so the existing download/resolution code consumes
12754
+ * it unchanged. `stepId` is the detection step the model targets
12755
+ * (e.g. `'object-detection'`).
12756
+ */
12757
+ var CustomModelDescriptorSchema = object({
12758
+ stepId: string(),
12759
+ entry: ModelCatalogEntrySchema
12760
+ });
12761
+ method(_void(), array(CustomModelDescriptorSchema).readonly());
12762
+ method(object({
12763
+ nodeId: string(),
12764
+ modelId: string(),
12765
+ format: _enum(MODEL_FORMATS),
12766
+ entry: ModelCatalogEntrySchema
12767
+ }), object({
12768
+ ok: boolean(),
12769
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
12770
+ sha256: string(),
12771
+ bytes: number(),
12772
+ /** The target node's modelsDir the artifact landed in. */
12773
+ path: string()
12774
+ }), {
12775
+ kind: "mutation",
12776
+ auth: "admin"
12777
+ });
12778
+ method(object({
12779
+ sourceUrl: string(),
12780
+ metadata: ModelConvertMetadataSchema,
12781
+ targets: array(ConvertTargetSchema).min(1).readonly(),
12782
+ calibrationRef: string().optional(),
12783
+ sessionId: string().optional()
12784
+ }), ConvertResultSchema, {
12785
+ kind: "mutation",
12786
+ auth: "admin"
12787
+ });
12613
12788
  var AddonHttpRouteSchema = object({
12614
12789
  method: _enum([
12615
12790
  "GET",
@@ -17623,6 +17798,12 @@ Object.freeze({
17623
17798
  addonId: null,
17624
17799
  access: "create"
17625
17800
  },
17801
+ "customModelRegistry.listModels": {
17802
+ capName: "custom-model-registry",
17803
+ capScope: "system",
17804
+ addonId: null,
17805
+ access: "view"
17806
+ },
17626
17807
  "decoder.createSession": {
17627
17808
  capName: "decoder",
17628
17809
  capScope: "system",
@@ -18847,6 +19028,18 @@ Object.freeze({
18847
19028
  addonId: null,
18848
19029
  access: "view"
18849
19030
  },
19031
+ "modelConvert.convert": {
19032
+ capName: "model-convert",
19033
+ capScope: "system",
19034
+ addonId: null,
19035
+ access: "create"
19036
+ },
19037
+ "modelDistributor.distributeModel": {
19038
+ capName: "model-distributor",
19039
+ capScope: "system",
19040
+ addonId: null,
19041
+ access: "create"
19042
+ },
18850
19043
  "motion.isDetected": {
18851
19044
  capName: "motion",
18852
19045
  capScope: "device",
@@ -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-BO4vHZS6.js");
5
+ const require_dist = require("../dist-BY1NQ5oi.js");
6
6
  let node_path = require("node:path");
7
7
  let node_path$1 = require_dist.__toESM(node_path, 1);
8
8
  node_path = require_dist.__toESM(node_path);
@@ -11,7 +11,25 @@ let node_fs$1 = require_dist.__toESM(node_fs, 1);
11
11
  node_fs = require_dist.__toESM(node_fs);
12
12
  require("node:crypto");
13
13
  let node_child_process = require("node:child_process");
14
- //#region ../system/dist/model-download-service-RxAOiYvX.mjs
14
+ //#region ../system/dist/model-download-service-C-IHWnXx.mjs
15
+ function isNonEmptyFile(filePath) {
16
+ return node_fs$1.existsSync(filePath) && node_fs$1.statSync(filePath).size > 0;
17
+ }
18
+ /**
19
+ * Sibling files of a single-file (non-directory) format — extra files the
20
+ * format needs, fetched from the same remote directory as `url` and stored
21
+ * flat alongside the main file in `modelsDir`. Catalog-declared via
22
+ * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
23
+ * `.xml`. The downloader stays format-agnostic; the convention lives in the
24
+ * catalog data (like `MLPACKAGE_FILES` for the directory case).
25
+ */
26
+ function siblingFilesFor(formatEntry) {
27
+ return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
28
+ }
29
+ /** Resolve a sibling's remote URL relative to the main file's directory. */
30
+ function siblingUrl(mainUrl, sibling) {
31
+ return mainUrl.replace(/[^/]+$/, sibling);
32
+ }
15
33
  /** Build fetch headers, including HF auth token for huggingface.co URLs */
16
34
  function buildHeaders(url) {
17
35
  const headers = { "User-Agent": "CamStack/1.0" };
@@ -107,15 +125,21 @@ var ModelDownloadService = class {
107
125
  if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
108
126
  await this.ensureExtraFiles(modelId);
109
127
  const modelPath = this.modelFilePath(entry, selectedFormat);
110
- if (node_fs$1.existsSync(modelPath)) if (formatEntry.isDirectory) if (!node_fs$1.existsSync(node_path$1.join(modelPath, "Manifest.json"))) node_fs$1.rmSync(modelPath, {
111
- recursive: true,
112
- force: true
113
- });
114
- else return modelPath;
115
- else return modelPath;
128
+ const siblings = siblingFilesFor(formatEntry);
129
+ if (node_fs$1.existsSync(modelPath)) {
130
+ if (formatEntry.isDirectory) if (!node_fs$1.existsSync(node_path$1.join(modelPath, "Manifest.json"))) node_fs$1.rmSync(modelPath, {
131
+ recursive: true,
132
+ force: true
133
+ });
134
+ else return modelPath;
135
+ else if (siblings.every((f) => isNonEmptyFile(node_path$1.join(this.modelsDir, f)))) return modelPath;
136
+ }
116
137
  node_fs$1.mkdirSync(this.modelsDir, { recursive: true });
117
138
  if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
118
- else await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
139
+ else {
140
+ await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
141
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path$1.join(this.modelsDir, sibling));
142
+ }
119
143
  return modelPath;
120
144
  }
121
145
  /**
@@ -149,7 +173,8 @@ var ModelDownloadService = class {
149
173
  const modelPath = this.modelFilePath(entry, selectedFormat);
150
174
  if (!node_fs$1.existsSync(modelPath)) return false;
151
175
  if (formatEntry.isDirectory) return node_fs$1.existsSync(node_path$1.join(modelPath, "Manifest.json"));
152
- return node_fs$1.statSync(modelPath).size > 0;
176
+ if (node_fs$1.statSync(modelPath).size <= 0) return false;
177
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path$1.join(this.modelsDir, f)));
153
178
  }
154
179
  /** Get the catalog entry for a model by ID. */
155
180
  getEntry(modelId) {
@@ -1,4 +1,4 @@
1
- import { d as BaseAddon, i as embeddingEncoderCapability } from "../dist-xZ1nMZGV.mjs";
1
+ import { d as BaseAddon, i as embeddingEncoderCapability } from "../dist-BUE-vLMt.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as path$1 from "node:path";
4
4
  import * as fs from "node:fs";
@@ -6,7 +6,25 @@ import { spawn } from "node:child_process";
6
6
  //#region \0rolldown/runtime.js
7
7
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
8
8
  //#endregion
9
- //#region ../system/dist/model-download-service-RxAOiYvX.mjs
9
+ //#region ../system/dist/model-download-service-C-IHWnXx.mjs
10
+ function isNonEmptyFile(filePath) {
11
+ return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
12
+ }
13
+ /**
14
+ * Sibling files of a single-file (non-directory) format — extra files the
15
+ * format needs, fetched from the same remote directory as `url` and stored
16
+ * flat alongside the main file in `modelsDir`. Catalog-declared via
17
+ * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
18
+ * `.xml`. The downloader stays format-agnostic; the convention lives in the
19
+ * catalog data (like `MLPACKAGE_FILES` for the directory case).
20
+ */
21
+ function siblingFilesFor(formatEntry) {
22
+ return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
23
+ }
24
+ /** Resolve a sibling's remote URL relative to the main file's directory. */
25
+ function siblingUrl(mainUrl, sibling) {
26
+ return mainUrl.replace(/[^/]+$/, sibling);
27
+ }
10
28
  /** Build fetch headers, including HF auth token for huggingface.co URLs */
11
29
  function buildHeaders(url) {
12
30
  const headers = { "User-Agent": "CamStack/1.0" };
@@ -102,15 +120,21 @@ var ModelDownloadService = class {
102
120
  if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
103
121
  await this.ensureExtraFiles(modelId);
104
122
  const modelPath = this.modelFilePath(entry, selectedFormat);
105
- if (fs.existsSync(modelPath)) if (formatEntry.isDirectory) if (!fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
106
- recursive: true,
107
- force: true
108
- });
109
- else return modelPath;
110
- else return modelPath;
123
+ const siblings = siblingFilesFor(formatEntry);
124
+ if (fs.existsSync(modelPath)) {
125
+ if (formatEntry.isDirectory) if (!fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
126
+ recursive: true,
127
+ force: true
128
+ });
129
+ else return modelPath;
130
+ else if (siblings.every((f) => isNonEmptyFile(path$1.join(this.modelsDir, f)))) return modelPath;
131
+ }
111
132
  fs.mkdirSync(this.modelsDir, { recursive: true });
112
133
  if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
113
- else await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
134
+ else {
135
+ await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
136
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(this.modelsDir, sibling));
137
+ }
114
138
  return modelPath;
115
139
  }
116
140
  /**
@@ -144,7 +168,8 @@ var ModelDownloadService = class {
144
168
  const modelPath = this.modelFilePath(entry, selectedFormat);
145
169
  if (!fs.existsSync(modelPath)) return false;
146
170
  if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
147
- return fs.statSync(modelPath).size > 0;
171
+ if (fs.statSync(modelPath).size <= 0) return false;
172
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(this.modelsDir, f)));
148
173
  }
149
174
  /** Get the catalog entry for a model by ID. */
150
175
  getEntry(modelId) {
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-BO4vHZS6.js");
6
- const require_resolve_frame = require("../resolve-frame-A6QovqGd.js");
5
+ const require_dist = require("../dist-BY1NQ5oi.js");
6
+ const require_resolve_frame = require("../resolve-frame-B2Do-e7z.js");
7
7
  let _camstack_shm_ring = require("@camstack/shm-ring");
8
8
  //#region src/enrichment-engine/types.ts
9
9
  var DEFAULT_ENRICHMENT_CONFIG = {
@@ -38,6 +38,9 @@ var DEFAULT_ENRICHMENT_CONFIG = {
38
38
  };
39
39
  //#endregion
40
40
  //#region src/enrichment-engine/workers/embedding-dispatcher.ts
41
+ function selectCropBbox(detection) {
42
+ return detection.refinedBbox ?? detection.bbox;
43
+ }
41
44
  var EmbeddingDispatcher = class {
42
45
  config;
43
46
  encoders;
@@ -155,7 +158,7 @@ var EmbeddingDispatcher = class {
155
158
  frameData,
156
159
  frameWidth,
157
160
  frameHeight,
158
- bbox: detection.bbox,
161
+ bbox: selectCropBbox(detection),
159
162
  receivedAt: now
160
163
  };
161
164
  switch (this.config.cropStrategy) {
@@ -1,4 +1,4 @@
1
- import { C as tuple, S as string, _ as _enum, b as number, d as BaseAddon, h as createEvent, m as asJsonObject, p as EventCategory, v as array, x as object, y as boolean } from "../dist-xZ1nMZGV.mjs";
1
+ import { C as tuple, S as string, _ as _enum, b as number, d as BaseAddon, h as createEvent, m as asJsonObject, p as EventCategory, v as array, x as object, y as boolean } from "../dist-BUE-vLMt.mjs";
2
2
  import { n as extractCrop, t as resolveFrame } from "../resolve-frame-CT1T1tWy.mjs";
3
3
  import { FrameRingReaderCache } from "@camstack/shm-ring";
4
4
  //#region src/enrichment-engine/types.ts
@@ -34,6 +34,9 @@ var DEFAULT_ENRICHMENT_CONFIG = {
34
34
  };
35
35
  //#endregion
36
36
  //#region src/enrichment-engine/workers/embedding-dispatcher.ts
37
+ function selectCropBbox(detection) {
38
+ return detection.refinedBbox ?? detection.bbox;
39
+ }
37
40
  var EmbeddingDispatcher = class {
38
41
  config;
39
42
  encoders;
@@ -151,7 +154,7 @@ var EmbeddingDispatcher = class {
151
154
  frameData,
152
155
  frameWidth,
153
156
  frameHeight,
154
- bbox: detection.bbox,
157
+ bbox: selectCropBbox(detection),
155
158
  receivedAt: now
156
159
  };
157
160
  switch (this.config.cropStrategy) {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.1.0",
6
+ version: "1.1.1",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.1.0",
21
+ version: "1.1.1",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.1.0",
36
+ version: "1.1.1",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.1.0",
39
+ version: "1.1.1",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.1.0",
48
+ version: "1.1.1",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.1.0",
84
+ version: "1.1.1",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-BO4vHZS6.js");
6
- const require_resolve_frame = require("../resolve-frame-A6QovqGd.js");
5
+ const require_dist = require("../dist-BY1NQ5oi.js");
6
+ const require_resolve_frame = require("../resolve-frame-B2Do-e7z.js");
7
7
  let _camstack_shm_ring = require("@camstack/shm-ring");
8
8
  let sharp = require("sharp");
9
9
  sharp = require_dist.__toESM(sharp);
@@ -1284,6 +1284,7 @@ var FrameProcessor = class {
1284
1284
  const firstLevelBboxById = /* @__PURE__ */ new Map();
1285
1285
  const faceBboxByBbox = /* @__PURE__ */ new Map();
1286
1286
  const plateByBbox = /* @__PURE__ */ new Map();
1287
+ const maskByBbox = /* @__PURE__ */ new Map();
1287
1288
  const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
1288
1289
  const bbox = {
1289
1290
  x: det.bbox.x,
@@ -1302,6 +1303,11 @@ var FrameProcessor = class {
1302
1303
  embedding: det.embedding,
1303
1304
  ...det.embeddingModelId !== void 0 ? { embeddingModelId: det.embeddingModelId } : {}
1304
1305
  });
1306
+ if (det.mask !== void 0 && det.maskWidth !== void 0 && det.maskHeight !== void 0) maskByBbox.set(bbox, {
1307
+ mask: Uint8Array.from(Buffer.from(det.mask, "base64")),
1308
+ width: det.maskWidth,
1309
+ height: det.maskHeight
1310
+ });
1305
1311
  firstLevelBboxById.set(det.id, bbox);
1306
1312
  return {
1307
1313
  ...bbox,
@@ -1344,8 +1350,9 @@ var FrameProcessor = class {
1344
1350
  const rawEvents = this.eventEmitter.emit(trackedDetections, objectStates, [], [], String(this.deviceId));
1345
1351
  const zonesByTrack = /* @__PURE__ */ new Map();
1346
1352
  for (const td of trackedDetections) {
1347
- const memberships = this.zoneEngine.annotateDetection(td.bbox, this.zones, frameWidth, frameHeight);
1348
- zonesByTrack.set(td.trackId, memberships.map((m) => m.zoneId));
1353
+ const m = maskByBbox.get(td.bbox);
1354
+ const memberships = this.zoneEngine.annotateDetection(td.bbox, this.zones, frameWidth, frameHeight, m?.mask, m?.width, m?.height);
1355
+ zonesByTrack.set(td.trackId, memberships.map((m2) => m2.zoneId));
1349
1356
  }
1350
1357
  const tracked = trackedDetections.map((td) => {
1351
1358
  const state = mapObjectStateToTrackState(objectStates.find((o) => o.trackId === td.trackId)?.state);
@@ -1,4 +1,4 @@
1
- import { a as faceGalleryCapability, b as number, c as videoclipsCapability, d as BaseAddon, f as DeviceType, g as hydrateSchema, l as zoneAnalyticsCapability, n as audioMetricsCapability, o as pipelineAnalyticsCapability, p as EventCategory, r as cosineSimilarity, s as plateGalleryCapability, t as addonWidgetsSourceCapability, u as errMsg, x as object, y as boolean } from "../dist-xZ1nMZGV.mjs";
1
+ import { a as faceGalleryCapability, b as number, c as videoclipsCapability, d as BaseAddon, f as DeviceType, g as hydrateSchema, l as zoneAnalyticsCapability, n as audioMetricsCapability, o as pipelineAnalyticsCapability, p as EventCategory, r as cosineSimilarity, s as plateGalleryCapability, t as addonWidgetsSourceCapability, u as errMsg, x as object, y as boolean } from "../dist-BUE-vLMt.mjs";
2
2
  import { n as extractCrop, t as resolveFrame } from "../resolve-frame-CT1T1tWy.mjs";
3
3
  import { FrameRingReaderCache } from "@camstack/shm-ring";
4
4
  import sharp from "sharp";
@@ -1279,6 +1279,7 @@ var FrameProcessor = class {
1279
1279
  const firstLevelBboxById = /* @__PURE__ */ new Map();
1280
1280
  const faceBboxByBbox = /* @__PURE__ */ new Map();
1281
1281
  const plateByBbox = /* @__PURE__ */ new Map();
1282
+ const maskByBbox = /* @__PURE__ */ new Map();
1282
1283
  const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
1283
1284
  const bbox = {
1284
1285
  x: det.bbox.x,
@@ -1297,6 +1298,11 @@ var FrameProcessor = class {
1297
1298
  embedding: det.embedding,
1298
1299
  ...det.embeddingModelId !== void 0 ? { embeddingModelId: det.embeddingModelId } : {}
1299
1300
  });
1301
+ if (det.mask !== void 0 && det.maskWidth !== void 0 && det.maskHeight !== void 0) maskByBbox.set(bbox, {
1302
+ mask: Uint8Array.from(Buffer.from(det.mask, "base64")),
1303
+ width: det.maskWidth,
1304
+ height: det.maskHeight
1305
+ });
1300
1306
  firstLevelBboxById.set(det.id, bbox);
1301
1307
  return {
1302
1308
  ...bbox,
@@ -1339,8 +1345,9 @@ var FrameProcessor = class {
1339
1345
  const rawEvents = this.eventEmitter.emit(trackedDetections, objectStates, [], [], String(this.deviceId));
1340
1346
  const zonesByTrack = /* @__PURE__ */ new Map();
1341
1347
  for (const td of trackedDetections) {
1342
- const memberships = this.zoneEngine.annotateDetection(td.bbox, this.zones, frameWidth, frameHeight);
1343
- zonesByTrack.set(td.trackId, memberships.map((m) => m.zoneId));
1348
+ const m = maskByBbox.get(td.bbox);
1349
+ const memberships = this.zoneEngine.annotateDetection(td.bbox, this.zones, frameWidth, frameHeight, m?.mask, m?.width, m?.height);
1350
+ zonesByTrack.set(td.trackId, memberships.map((m2) => m2.zoneId));
1344
1351
  }
1345
1352
  const tracked = trackedDetections.map((td) => {
1346
1353
  const state = mapObjectStateToTrackState(objectStates.find((o) => o.trackId === td.trackId)?.state);
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DnW0j4y0.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BidjVASi.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BO4vHZS6.js");
1
+ const require_dist = require("./dist-BY1NQ5oi.js");
2
2
  let sharp = require("sharp");
3
3
  sharp = require_dist.__toESM(sharp);
4
4
  //#region src/shared/frame/crop-extractor.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "CamStack Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",