@camstack/addon-model-studio 1.1.216 → 1.1.218

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 (20) hide show
  1. package/dist/{MotionZonesSettings-4HEAfNAZ.mjs → MotionZonesSettings-Capre9Um.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-CyrRi4XA.mjs → PrivacyMaskSettings-CTeuaW76.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-CprmQzlB.mjs → SceneMonitorEditor-B_wS3Aqg.mjs} +3 -3
  4. package/dist/_stub.js +2956 -2959
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DuZ4j4Tj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-WTUyarUL.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CE6l15x5.mjs +26 -0
  7. package/dist/{hostInit-DgQAw-Tm.mjs → hostInit-N-aNmJfG.mjs} +3 -3
  8. package/dist/model-studio.addon.js +266 -10
  9. package/dist/model-studio.addon.mjs +266 -10
  10. package/dist/{player-overlays-2W0Mwbzt.mjs → player-overlays-CYNFSDYV.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-B69r95CT.mjs → responsive-oZzHP3lx.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-CeTU5shK.mjs → scene-monitor-copy-C2YKn7wo.mjs} +1 -1
  14. package/dist/{square-Sw6O7lEO.mjs → square-DUBjO0Wq.mjs} +1 -1
  15. package/dist/{trash-2-Bess9rqf.mjs → trash-2-DpaexsRt.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BTcms_55.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BA6FOYZy.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/python/__tests__/test_calibration.py +56 -0
  19. package/python/convert_lib.py +45 -16
  20. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQ2lOvCS.mjs +0 -26
@@ -5738,7 +5738,7 @@ var ZodIssueCode = {
5738
5738
  var ZodFirstPartyTypeKind;
5739
5739
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5740
5740
  //#endregion
5741
- //#region ../types/dist/sleep-BNeXenvr.mjs
5741
+ //#region ../types/dist/sleep-B0JG7-_F.mjs
5742
5742
  /**
5743
5743
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5744
5744
  * window to float samples (D455).
@@ -15396,6 +15396,10 @@ var DeviceInfoSchema = object({
15396
15396
  isCamera: boolean(),
15397
15397
  /** Current config values — serializable snapshot */
15398
15398
  config: record(string(), unknown()),
15399
+ /** Paths inside `config` the device's schema declares secret (D783) —
15400
+ * what the non-admin redaction withholds. Paths, never values. Absent
15401
+ * when the owning runner has not reported them. */
15402
+ secretConfigPaths: array(string()).optional(),
15399
15403
  /** Hardware + identity blob (manufacturer / model / firmware / sn /
15400
15404
  * uid / mac / …). Populated by drivers; editable via setMetadata. */
15401
15405
  metadata: record(string(), unknown()).nullable().optional(),
@@ -15601,7 +15605,13 @@ var DeviceRegisterPayloadSchema = object({
15601
15605
  * call (workers re-register on init), so probe-driven updates
15602
15606
  * (e.g. PTZ flag flipping after the abilities probe) propagate. */
15603
15607
  features: array(string()).optional(),
15604
- config: record(string(), unknown())
15608
+ config: record(string(), unknown()),
15609
+ /** Config paths the device's own schema DECLARES secret
15610
+ * (`.meta({ secret: true })` — dotted, `[]` for array items, e.g.
15611
+ * `connection.password`). Re-published on every register; the non-admin
15612
+ * redaction withholds them at any depth (D783). Optional: an older runner
15613
+ * omits it and the keyword backstop applies. */
15614
+ secretConfigPaths: array(string()).optional()
15605
15615
  });
15606
15616
  var DeviceRemovePayloadSchema = object({ deviceId: number() });
15607
15617
  var DevicePersistConfigPayloadSchema = object({
@@ -17260,7 +17270,14 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
17260
17270
  icon: string().optional(),
17261
17271
  /** Addon-owned HTTP route the button navigates to (GET). */
17262
17272
  startUrl: string(),
17263
- stage: LoginStageEnum
17273
+ stage: LoginStageEnum,
17274
+ /**
17275
+ * The provider names an external identity a signed-in user can link to
17276
+ * their own account (D782): `auth.listLinkableProviders` /
17277
+ * `beginExternalLink` offer exactly the redirect methods that set this,
17278
+ * whatever their id. Absent / false — a login button only (magic-link).
17279
+ */
17280
+ linkable: boolean().optional()
17264
17281
  }),
17265
17282
  object({
17266
17283
  kind: literal("widget"),
@@ -22663,7 +22680,13 @@ var RetrainFrameSchema = object({
22663
22680
  sizeBytes: number().int(),
22664
22681
  width: number().int(),
22665
22682
  height: number().int(),
22666
- copiedAt: number()
22683
+ copiedAt: number(),
22684
+ /** When the CAMERA captured the frame. Absent on a copy taken before D785. */
22685
+ capturedAt: number().optional(),
22686
+ /** The operator's "every subject in this frame is boxed". Detector
22687
+ * evaluation keeps only complete frames: an unboxed subject would count as
22688
+ * a false positive. */
22689
+ complete: boolean()
22667
22690
  });
22668
22691
  /** Why a copy-on-select could not be honoured — named, never a silent skip. */
22669
22692
  var RetrainCopyRefusalSchema = _enum([
@@ -22684,6 +22707,51 @@ var RetrainFrameListSchema = object({
22684
22707
  /** What the page pre-selects — the native key frame when one survives. */
22685
22708
  autoPickMediaId: number().int().optional()
22686
22709
  });
22710
+ /**
22711
+ * Which frames an annotated export carries. Every field narrows; an empty
22712
+ * filter is every annotated copy. A dataset VERSION is a frozen `frameIds`
22713
+ * list owned by whoever trains (the training orchestrator), so `frameIds` is
22714
+ * the primitive a version export uses; `batchId` re-selects what an earlier
22715
+ * export carried (`exportedInBatch`).
22716
+ */
22717
+ var RetrainExportFilterSchema = object({
22718
+ deviceIds: array(number()).optional(),
22719
+ frameIds: array(string()).max(2e4).optional(),
22720
+ batchId: string().optional(),
22721
+ /** Only frames the operator marked complete (all subjects boxed). */
22722
+ completeOnly: boolean().optional()
22723
+ });
22724
+ /**
22725
+ * `frames`: the full frame copies, boxes in `annotations.json`.
22726
+ * `crops`: additionally one JPEG per subject, cut with the runtime detail-crop
22727
+ * convention — the classifier layout.
22728
+ */
22729
+ var RetrainExportLayoutSchema = _enum(["frames", "crops"]);
22730
+ /**
22731
+ * Where `writeRetrainArchive` streams the tar: one HTTP request whose body is
22732
+ * the archive, sent chunk by chunk. Covers a presigned PUT and a provider
22733
+ * addon's data-plane upload route alike. Headers are sent verbatim and never
22734
+ * logged.
22735
+ */
22736
+ var RetrainArchiveTargetSchema = object({
22737
+ kind: literal("http"),
22738
+ method: _enum(["PUT", "POST"]),
22739
+ url: string().url(),
22740
+ headers: record(string(), string()).optional()
22741
+ });
22742
+ var RetrainArchiveResultSchema = object({
22743
+ batchId: string(),
22744
+ frames: number().int(),
22745
+ crops: number().int(),
22746
+ annotations: number().int(),
22747
+ /** Frames planned but not shipped (unreadable copy, path too long). */
22748
+ missing: number().int(),
22749
+ bytes: number().int(),
22750
+ /** sha256 of the exact bytes sent. */
22751
+ sha256: string(),
22752
+ /** The target's HTTP status. */
22753
+ status: number().int()
22754
+ });
22687
22755
  /** What the operator asked the assist to look for. */
22688
22756
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
22689
22757
  kind: literal("package"),
@@ -23478,9 +23546,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
23478
23546
  }), RetrainTransitionResultSchema, {
23479
23547
  kind: "mutation",
23480
23548
  auth: "admin"
23481
- }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
23549
+ }), method(RetrainExportFilterSchema.extend({ layout: RetrainExportLayoutSchema.optional() }), object({ url: string() }), {
23482
23550
  kind: "query",
23483
23551
  auth: "admin"
23552
+ }), method(object({
23553
+ deviceId: number(),
23554
+ trackId: string(),
23555
+ frameId: string(),
23556
+ complete: boolean()
23557
+ }), RetrainFrameSchema, {
23558
+ kind: "mutation",
23559
+ auth: "admin"
23560
+ }), method(object({
23561
+ filter: RetrainExportFilterSchema,
23562
+ layout: RetrainExportLayoutSchema,
23563
+ target: RetrainArchiveTargetSchema
23564
+ }), RetrainArchiveResultSchema, {
23565
+ kind: "mutation",
23566
+ auth: "admin",
23567
+ timeoutMs: 60 * 6e4
23484
23568
  }), method(object({
23485
23569
  /** Which of the three event tables owns the row. */
23486
23570
  ownerType: EventOwnerTypeSchema,
@@ -26770,7 +26854,19 @@ var SsoBridgeClaimsSchema = object({
26770
26854
  * editing a descriptor must not retroactively extend or shorten a link the
26771
26855
  * operator already consented to.
26772
26856
  */
26773
- refreshTtl: union([number().int().positive(), literal("never")]).optional()
26857
+ refreshTtl: union([number().int().positive(), literal("never")]).optional(),
26858
+ /**
26859
+ * Opaque handle of a hub-side LINK intent (D782). Set by an OIDC callback
26860
+ * that was started from a signed-in user's "Link" button: the finish then
26861
+ * links this identity to the intent's user instead of signing anyone in.
26862
+ * The handle carries no user id — the hub resolves it, single-use.
26863
+ */
26864
+ linkIntent: string().optional(),
26865
+ /**
26866
+ * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
26867
+ * subject is unique only per issuer, so a link is bound to it.
26868
+ */
26869
+ issuer: string().optional()
26774
26870
  });
26775
26871
  method(object({
26776
26872
  claims: SsoBridgeClaimsSchema,
@@ -28676,7 +28772,22 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
28676
28772
  * field their hints were silently stripped by the schema and the
28677
28773
  * adaptive ladder started blind on the client's display size.
28678
28774
  */
28679
- hints: webrtcClientHintsSchema.optional()
28775
+ hints: webrtcClientHintsSchema.optional(),
28776
+ /**
28777
+ * Talk-back (D771): relay the PEER's audio to the device's speaker
28778
+ * through the `intercom` cap's push-talk triple. When `true` and the
28779
+ * offer lets the peer send audio, the answer's audio is `sendrecv`
28780
+ * and every inbound frame goes to `intercom.pushTalkAudio` for this
28781
+ * `deviceId`; absent/false answers `sendonly`, as for every viewer.
28782
+ *
28783
+ * TRUSTED-CALLER ONLY. Set by the Alexa export for an Echo on a camera
28784
+ * with `intercom`, when the linked user may control that camera. The
28785
+ * hub STRIPS it from every request-bound call (a browser), exactly as
28786
+ * it overwrites `relayOnly` — a viewer's talk permission is the
28787
+ * intercom cap's own surface (share tokens gate it there), and this
28788
+ * flag must not become a second door to the camera's speaker.
28789
+ */
28790
+ talkback: boolean().optional()
28680
28791
  }), object({
28681
28792
  sessionId: string(),
28682
28793
  sdpAnswer: string()
@@ -28978,6 +29089,59 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
28978
29089
  var RestartAddonResultSchema = unknown();
28979
29090
  var InstallPackageResultSchema = unknown();
28980
29091
  var ReloadPackagesResultSchema = unknown();
29092
+ /**
29093
+ * What an uninstall did on ONE node.
29094
+ * - `uninstalled` — removed: runner(s) stopped, caps gone, code deleted.
29095
+ * - `not-installed` — the node did not have it (nothing to do).
29096
+ * - `refused` — protected / bootstrap package; nothing touched.
29097
+ * - `failed` — attempted and did not complete; `error` says why.
29098
+ * - `pending-reconnect` — the node is OFFLINE: its record was pruned so the
29099
+ * back-fill never returns the package, and the stale-addon sweep removes the
29100
+ * copy when the node registers again (only once the hub no longer has it).
29101
+ */
29102
+ var UninstallNodeOutcomeSchema = _enum([
29103
+ "uninstalled",
29104
+ "not-installed",
29105
+ "refused",
29106
+ "failed",
29107
+ "pending-reconnect"
29108
+ ]);
29109
+ var UninstallNodeResultSchema = object({
29110
+ nodeId: string(),
29111
+ outcome: UninstallNodeOutcomeSchema,
29112
+ error: string().optional(),
29113
+ addonIds: array(string()).readonly(),
29114
+ runnersStopped: array(string()).readonly(),
29115
+ warnings: array(string()).readonly()
29116
+ });
29117
+ var UninstallPackageResultSchema = object({
29118
+ /** Every targeted node ended `uninstalled`, `not-installed` or `pending-reconnect`. */
29119
+ success: boolean(),
29120
+ packageName: string(),
29121
+ results: array(UninstallNodeResultSchema).readonly()
29122
+ });
29123
+ /**
29124
+ * `unreachable` lists online agents whose status read FAILED — absence from
29125
+ * `installations` is only "not installed" for a node that answered (D315).
29126
+ */
29127
+ var PackageInstallationsSchema = object({
29128
+ installations: array(object({
29129
+ packageName: string(),
29130
+ nodeId: string(),
29131
+ nodeName: string(),
29132
+ isHub: boolean(),
29133
+ isOnline: boolean(),
29134
+ version: string().nullable(),
29135
+ addonIds: array(string()).readonly(),
29136
+ source: _enum(["live", "recorded"]),
29137
+ /** The hub installs this package too. `false` on an agent row = an orphan. */
29138
+ onHub: boolean()
29139
+ })).readonly(),
29140
+ unreachable: array(object({
29141
+ nodeId: string(),
29142
+ error: string()
29143
+ })).readonly()
29144
+ });
28981
29145
  var BulkUpdateItemStatusSchema = _enum([
28982
29146
  "queued",
28983
29147
  "updating",
@@ -29060,10 +29224,14 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
29060
29224
  }), method(object({ packageName: string() }), InstallPackageResultSchema, {
29061
29225
  kind: "mutation",
29062
29226
  auth: "admin"
29063
- }), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({ packageName: string() }), unknown(), {
29227
+ }), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({
29228
+ packageName: string().min(1),
29229
+ nodeId: string().optional(),
29230
+ everywhere: boolean().optional()
29231
+ }), UninstallPackageResultSchema, {
29064
29232
  kind: "mutation",
29065
29233
  auth: "admin"
29066
- }), method(_void(), ReloadPackagesResultSchema, {
29234
+ }), method(object({ packageName: string().optional() }), PackageInstallationsSchema, { auth: "admin" }), method(_void(), ReloadPackagesResultSchema, {
29067
29235
  kind: "mutation",
29068
29236
  auth: "admin"
29069
29237
  }), method(object({ query: string().optional() }), array(SearchResultSchema)), method(object({ nodeId: string().optional() }), array(PackageUpdateSchema).readonly(), { auth: "admin" }), method(object({
@@ -36663,6 +36831,32 @@ var TotpStatusSchema = object({
36663
36831
  /** Null when no row exists OR the row is still pending confirmation. */
36664
36832
  confirmedAt: number().nullable()
36665
36833
  });
36834
+ /**
36835
+ * user-management — singleton capability for user accounts, API keys,
36836
+ * and scoped tokens.
36837
+ *
36838
+ * v2 model: every method gated by `auth: 'admin'`. Admin-only delegation
36839
+ * — the subset check on `setUserScopes` / `createScopedToken` is moot
36840
+ * because non-admins never reach these. If/when we open non-admin
36841
+ * delegation, structural caller-scope subset check goes inside the
36842
+ * provider.
36843
+ */
36844
+ var ExternalIdentitySchema = object({
36845
+ /** Login-method id of the provider instance, e.g. `auth-oidc/authentik`. */
36846
+ provider: string(),
36847
+ /** The IdP's stable subject (`sub`) for this account. */
36848
+ subject: string(),
36849
+ /** The verified `iss` of the IdP the identity was linked under — `sub` is unique only per issuer. */
36850
+ issuer: string(),
36851
+ userId: string(),
36852
+ /** What the IdP called the account at link time (username / email) — display only. */
36853
+ label: string().optional(),
36854
+ linkedAt: number()
36855
+ });
36856
+ var ExternalIdentityKeySchema = object({
36857
+ provider: string().min(1),
36858
+ subject: string().min(1)
36859
+ });
36666
36860
  method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
36667
36861
  kind: "mutation",
36668
36862
  auth: "admin",
@@ -36776,7 +36970,22 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
36776
36970
  userId: string(),
36777
36971
  username: string(),
36778
36972
  scopes: array(TokenScopeSchema)
36779
- }).nullable(), { access: "view" }), method(_void(), array(OauthSessionSummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: boolean() }), {
36973
+ }).nullable(), { access: "view" }), method(object({ userId: string() }), array(ExternalIdentitySchema), { auth: "admin" }), method(ExternalIdentityKeySchema, object({
36974
+ userId: string(),
36975
+ issuer: string()
36976
+ }).nullable(), { auth: "admin" }), method(ExternalIdentityKeySchema.extend({
36977
+ userId: string(),
36978
+ issuer: string().min(1),
36979
+ label: string().optional()
36980
+ }), object({ success: literal(true) }), {
36981
+ kind: "mutation",
36982
+ auth: "admin",
36983
+ access: "create"
36984
+ }), method(ExternalIdentityKeySchema.extend({ userId: string() }), object({ success: boolean() }), {
36985
+ kind: "mutation",
36986
+ auth: "admin",
36987
+ access: "delete"
36988
+ }), method(_void(), array(OauthSessionSummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: boolean() }), {
36780
36989
  kind: "mutation",
36781
36990
  auth: "admin",
36782
36991
  access: "delete"
@@ -37450,6 +37659,12 @@ Object.freeze({
37450
37659
  addonId: null,
37451
37660
  access: "view"
37452
37661
  },
37662
+ "addons.listInstallations": {
37663
+ capName: "addons",
37664
+ capScope: "system",
37665
+ addonId: null,
37666
+ access: "view"
37667
+ },
37453
37668
  "addons.listJobs": {
37454
37669
  capName: "addons",
37455
37670
  capScope: "system",
@@ -41674,6 +41889,12 @@ Object.freeze({
41674
41889
  addonId: null,
41675
41890
  access: "create"
41676
41891
  },
41892
+ "pipelineAnalytics.setRetrainFrameComplete": {
41893
+ capName: "pipeline-analytics",
41894
+ capScope: "device",
41895
+ addonId: null,
41896
+ access: "create"
41897
+ },
41677
41898
  "pipelineAnalytics.setTrackFlags": {
41678
41899
  capName: "pipeline-analytics",
41679
41900
  capScope: "device",
@@ -41710,6 +41931,12 @@ Object.freeze({
41710
41931
  addonId: null,
41711
41932
  access: "delete"
41712
41933
  },
41934
+ "pipelineAnalytics.writeRetrainArchive": {
41935
+ capName: "pipeline-analytics",
41936
+ capScope: "device",
41937
+ addonId: null,
41938
+ access: "create"
41939
+ },
41713
41940
  "pipelineExecutor.cacheFrameInPool": {
41714
41941
  capName: "pipeline-executor",
41715
41942
  capScope: "system",
@@ -44062,18 +44289,36 @@ Object.freeze({
44062
44289
  addonId: null,
44063
44290
  access: "delete"
44064
44291
  },
44292
+ "userManagement.findUserByExternalIdentity": {
44293
+ capName: "user-management",
44294
+ capScope: "system",
44295
+ addonId: null,
44296
+ access: "view"
44297
+ },
44065
44298
  "userManagement.getTotpStatus": {
44066
44299
  capName: "user-management",
44067
44300
  capScope: "system",
44068
44301
  addonId: null,
44069
44302
  access: "view"
44070
44303
  },
44304
+ "userManagement.linkExternalIdentity": {
44305
+ capName: "user-management",
44306
+ capScope: "system",
44307
+ addonId: null,
44308
+ access: "create"
44309
+ },
44071
44310
  "userManagement.listApiKeys": {
44072
44311
  capName: "user-management",
44073
44312
  capScope: "system",
44074
44313
  addonId: null,
44075
44314
  access: "view"
44076
44315
  },
44316
+ "userManagement.listExternalIdentities": {
44317
+ capName: "user-management",
44318
+ capScope: "system",
44319
+ addonId: null,
44320
+ access: "view"
44321
+ },
44077
44322
  "userManagement.listOauthSessions": {
44078
44323
  capName: "user-management",
44079
44324
  capScope: "system",
@@ -44152,6 +44397,12 @@ Object.freeze({
44152
44397
  addonId: null,
44153
44398
  access: "create"
44154
44399
  },
44400
+ "userManagement.unlinkExternalIdentity": {
44401
+ capName: "user-management",
44402
+ capScope: "system",
44403
+ addonId: null,
44404
+ access: "delete"
44405
+ },
44155
44406
  "userManagement.updateUser": {
44156
44407
  capName: "user-management",
44157
44408
  capScope: "system",
@@ -47491,6 +47742,11 @@ Object.freeze({
47491
47742
  form: "single",
47492
47743
  optional: false
47493
47744
  }],
47745
+ "pipelineAnalytics.setRetrainFrameComplete": [{
47746
+ name: "deviceId",
47747
+ form: "single",
47748
+ optional: false
47749
+ }],
47494
47750
  "pipelineAnalytics.setTrackFlags": [{
47495
47751
  name: "deviceId",
47496
47752
  form: "single",
@@ -1,5 +1,5 @@
1
1
  import { b as e, g as t, l as n, u as r } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-CHL8loOl.mjs";
2
- import { b as i, y as a } from "./responsive-B69r95CT.mjs";
2
+ import { b as i, y as a } from "./responsive-oZzHP3lx.mjs";
3
3
  import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-ds_Ehzaa.mjs";
4
4
  var l = i("chevron-down", [["path", {
5
5
  d: "m6 9 6 6 6-6",
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BTcms_55.mjs";
1
+ import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BA6FOYZy.mjs";
2
2
  export { t as get, e as init };
@@ -1,6 +1,6 @@
1
1
  import { _ as e, b as t, c as n, g as r, l as i, m as a, n as o, r as s, t as c, u as l } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-CHL8loOl.mjs";
2
2
  import "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-ds_Ehzaa.mjs";
3
- import { f as u } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQ2lOvCS.mjs";
3
+ import { f as u } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CE6l15x5.mjs";
4
4
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
5
5
  t();
6
6
  var d = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), f = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), p = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), m = (e) => {
@@ -1,5 +1,5 @@
1
1
  import { b as e, c as t, g as n, m as r } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-CHL8loOl.mjs";
2
- import { b as i } from "./responsive-B69r95CT.mjs";
2
+ import { b as i } from "./responsive-oZzHP3lx.mjs";
3
3
  var a = i("camera", [["path", {
4
4
  d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
5
5
  key: "18u6gg"
@@ -1,4 +1,4 @@
1
- import { b as e } from "./responsive-B69r95CT.mjs";
1
+ import { b as e } from "./responsive-oZzHP3lx.mjs";
2
2
  var t = e("eye-off", [
3
3
  ["path", {
4
4
  d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
@@ -1,4 +1,4 @@
1
- import { b as e } from "./responsive-B69r95CT.mjs";
1
+ import { b as e } from "./responsive-oZzHP3lx.mjs";
2
2
  var t = e("trash-2", [
3
3
  ["path", {
4
4
  d: "M10 11v6",
@@ -2753,7 +2753,7 @@ async function rr(e) {
2753
2753
  }
2754
2754
  }
2755
2755
  async function ir() {
2756
- return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DuZ4j4Tj.mjs")).catch((e) => {
2756
+ return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-WTUyarUL.mjs")).catch((e) => {
2757
2757
  throw tr = void 0, e;
2758
2758
  }), tr;
2759
2759
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-model-studio",
3
- "version": "1.1.216",
3
+ "version": "1.1.218",
4
4
  "description": "Custom detection model registry, conversion & distribution for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -0,0 +1,56 @@
1
+ """INT8 calibration inputs (cloud-training P6): which images, and how they are
2
+ fed. Calibration must see frames the way the runtime feeds the model — the
3
+ inference pool's letterbox is CENTRED (pad = (size - new) // 2, grey 114) —
4
+ and must be able to use the operator's own frames, i.e. the hub's annotated
5
+ export, whose frames sit in nested folders next to `annotations.json`."""
6
+ import numpy as np
7
+ import pytest
8
+
9
+ cv2 = pytest.importorskip("cv2")
10
+
11
+ from convert_lib import calibration_images, letterbox_for_calibration # noqa: E402
12
+
13
+
14
+ def _touch(path, data=b"x"):
15
+ path.parent.mkdir(parents=True, exist_ok=True)
16
+ path.write_bytes(data)
17
+
18
+
19
+ def test_a_flat_folder_is_read_in_file_name_order(tmp_path):
20
+ for name in ("b.jpg", "a.jpg", "c.png", "notes.txt"):
21
+ _touch(tmp_path / name)
22
+ assert [p.name for p in calibration_images(str(tmp_path), 10)] == ["a.jpg", "b.jpg", "c.png"]
23
+
24
+
25
+ def test_the_sample_count_is_a_cap(tmp_path):
26
+ for i in range(5):
27
+ _touch(tmp_path / f"{i}.jpg")
28
+ assert len(calibration_images(str(tmp_path), 3)) == 3
29
+
30
+
31
+ def test_an_export_directory_calibrates_on_its_frames_only(tmp_path):
32
+ _touch(tmp_path / "annotations.json", b"{}")
33
+ _touch(tmp_path / "frames" / "615" / "t1" / "a.jpg")
34
+ _touch(tmp_path / "frames" / "617" / "t2" / "b.jpg")
35
+ _touch(tmp_path / "crops" / "person" / "x.jpg")
36
+ names = [p.relative_to(tmp_path).as_posix() for p in calibration_images(str(tmp_path), 10)]
37
+ assert names == ["frames/615/t1/a.jpg", "frames/617/t2/b.jpg"]
38
+
39
+
40
+ def test_an_empty_folder_is_refused(tmp_path):
41
+ with pytest.raises(ValueError):
42
+ calibration_images(str(tmp_path), 10)
43
+
44
+
45
+ def test_the_letterbox_is_centred_like_the_runtime():
46
+ rgb = np.full((100, 200, 3), 255, dtype=np.uint8) # 2:1 white frame
47
+ out = letterbox_for_calibration(rgb, 64)
48
+ assert out.shape == (1, 3, 64, 64)
49
+ assert out.dtype == np.float32
50
+ # Content 64x32, centred: grey rows 0..15 and 48..63, white in between.
51
+ column = out[0, 0, :, 32]
52
+ assert column[0] == pytest.approx(114 / 255)
53
+ assert column[63] == pytest.approx(114 / 255)
54
+ assert column[32] == pytest.approx(1.0)
55
+ assert column[15] == pytest.approx(114 / 255)
56
+ assert column[16] == pytest.approx(1.0)
@@ -64,32 +64,61 @@ def validate_openvino_ir(xml_path: str) -> None:
64
64
  raise ValueError(f"IR {xml_path} has an empty-named tensor")
65
65
 
66
66
 
67
+ CALIBRATION_SUFFIXES = {".jpg", ".jpeg", ".png"}
68
+
69
+
70
+ def calibration_images(calib_dir: str, max_samples: int) -> "list[Path]":
71
+ """The first ``max_samples`` calibration images of ``calib_dir``, in path order.
72
+
73
+ A flat folder (COCO val2017) is read exactly as before: file-name order. A
74
+ folder is walked recursively, so the operator's own frames work as they
75
+ come. The hub's annotated export (a folder holding ``annotations.json``)
76
+ calibrates on its ``frames/`` only: its ``crops/`` are classifier inputs,
77
+ not what a root detector is fed.
78
+ """
79
+ root = Path(calib_dir)
80
+ if not root.is_dir():
81
+ raise ValueError(f"calibration folder {calib_dir} does not exist")
82
+ base = root / "frames" if (root / "annotations.json").is_file() else root
83
+ images = sorted(
84
+ (p for p in base.rglob("*") if p.is_file() and p.suffix.lower() in CALIBRATION_SUFFIXES),
85
+ key=lambda p: p.relative_to(base).as_posix(),
86
+ )[:max_samples]
87
+ if not images:
88
+ raise ValueError(f"no calibration images in {calib_dir}")
89
+ return images
90
+
91
+
92
+ def letterbox_for_calibration(rgb, imgsz: int):
93
+ """``rgb`` (HxWx3 uint8) → ``[1,3,imgsz,imgsz]`` float32 in [0,1], letterboxed
94
+ as the inference pool does (``inference_pool.letterbox_image``): scale by the
95
+ long side, truncate, CENTRE with grey 114 padding."""
96
+ import cv2
97
+ import numpy as np
98
+
99
+ h, w = rgb.shape[:2]
100
+ scale = imgsz / max(h, w)
101
+ nw, nh = int(w * scale), int(h * scale)
102
+ pad_x, pad_y = (imgsz - nw) // 2, (imgsz - nh) // 2
103
+ resized = rgb if (nw, nh) == (w, h) else cv2.resize(rgb, (nw, nh), interpolation=cv2.INTER_LINEAR)
104
+ canvas = np.full((imgsz, imgsz, 3), 114, dtype=np.uint8)
105
+ canvas[pad_y : pad_y + nh, pad_x : pad_x + nw] = resized
106
+ chw = canvas.transpose(2, 0, 1).astype(np.float32) / 255.0
107
+ return chw[np.newaxis, ...]
108
+
109
+
67
110
  def quantize_openvino_int8(
68
111
  onnx_path: str, out_xml: str, calib_dir: str, imgsz: int, max_samples: int = 300
69
112
  ) -> None:
70
113
  import cv2
71
- import numpy as np
72
114
  import openvino as ov
73
115
  import nncf
74
116
 
75
- calib = Path(calib_dir)
76
- images = sorted(
77
- p for p in calib.iterdir() if p.suffix.lower() in {".jpg", ".jpeg", ".png"}
78
- )[:max_samples]
79
- if not images:
80
- raise ValueError(f"no calibration images in {calib_dir}")
117
+ images = calibration_images(calib_dir, max_samples)
81
118
 
82
119
  def preprocess(path: Path):
83
120
  img = cv2.imread(str(path))
84
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
85
- h, w = img.shape[:2]
86
- scale = imgsz / max(h, w)
87
- nh, nw = int(round(h * scale)), int(round(w * scale))
88
- resized = cv2.resize(img, (nw, nh))
89
- canvas = np.full((imgsz, imgsz, 3), 114, dtype=np.uint8)
90
- canvas[:nh, :nw] = resized
91
- chw = canvas.transpose(2, 0, 1).astype(np.float32) / 255.0
92
- return chw[np.newaxis, ...]
121
+ return letterbox_for_calibration(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), imgsz)
93
122
 
94
123
  dataset = nncf.Dataset(images, lambda p: preprocess(p))
95
124
  ov_model = ov.convert_model(onnx_path)