repzo 1.0.292 → 1.0.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +28 -0
- package/lib/index.d.ts +244 -2
- package/lib/index.js +541 -0
- package/lib/types/index.d.ts +4530 -1
- package/package.json +1 -1
- package/src/index.ts +1270 -0
- package/src/oas/activity-ai-object-detection-session-frame.yaml +600 -0
- package/src/oas/ai-object-detection-assigned-missions.yaml +283 -0
- package/src/oas/ai-object-detection-assignment-rule.yaml +357 -0
- package/src/oas/ai-object-detection-category.yaml +338 -0
- package/src/oas/ai-object-detection-dataset.yaml +342 -0
- package/src/oas/ai-object-detection-detection-settings.yaml +410 -0
- package/src/oas/ai-object-detection-inference.yaml +818 -0
- package/src/oas/ai-object-detection-label-group.yaml +265 -0
- package/src/oas/ai-object-detection-label-report.yaml +366 -0
- package/src/oas/ai-object-detection-label.yaml +395 -0
- package/src/oas/ai-object-detection-metric-result.yaml +734 -0
- package/src/oas/ai-object-detection-metric.yaml +559 -0
- package/src/oas/ai-object-detection-mission-results.yaml +370 -0
- package/src/oas/ai-object-detection-mission-set.yaml +250 -0
- package/src/oas/ai-object-detection-mission.yaml +349 -0
- package/src/oas/ai-object-detection-model-version-epoch.yaml +214 -0
- package/src/oas/ai-object-detection-model-version-train-agent.yaml +88 -0
- package/src/oas/ai-object-detection-model-version.yaml +567 -0
- package/src/oas/ai-object-detection-model.yaml +369 -0
- package/src/oas/ai-object-detection-segment.yaml +301 -0
- package/src/oas/ai-object-detection-session-analysis.yaml +1306 -0
- package/src/oas/ai-object-detection-session-election.yaml +193 -0
- package/src/oas/ai-object-detection-session-insight.yaml +418 -0
- package/src/oas/ai-object-detection-session.yaml +840 -0
- package/src/oas/ai-object-detection-settings.yaml +249 -0
- package/src/oas/ai-object-detection-task.yaml +1007 -0
- package/src/oas/object-detection-analytics-report.yaml +674 -0
- package/src/types/index.ts +5156 -1
|
@@ -0,0 +1,1306 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: Repzo API - AI Object Detection Session Analysis
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: |
|
|
6
|
+
**Session analysis** is the cross-frame fusion stage of the Object Detection
|
|
7
|
+
Sessions feature. Given a capture `session` (a stream of frames materialized
|
|
8
|
+
as `ai-object-detection-task` rows) plus a `model_version`, it produces a
|
|
9
|
+
versioned, audit-friendly analysis document with the fused 3D objects on the
|
|
10
|
+
shelf and a kept/ignored breakdown per label.
|
|
11
|
+
|
|
12
|
+
**Asynchronous.** `create` is non-blocking: it opens a `pending` analysis
|
|
13
|
+
document and returns it **immediately** (with a `message`), then runs the
|
|
14
|
+
heavy work in the background. Inferring every frame can take a while, so the
|
|
15
|
+
client should not wait on the request — poll the returned document (or list
|
|
16
|
+
analyses for the session) until `status` flips to `success`/`failed`. On
|
|
17
|
+
completion the backend emits `inferred-od-session`, which notifies reps
|
|
18
|
+
(`update-object-detection-task`) so the dashboard can refresh.
|
|
19
|
+
|
|
20
|
+
**What the background job does.**
|
|
21
|
+
1. Loads the session and all its tasks, sorted in CAPTURE order
|
|
22
|
+
(`frame_meta.frame_id`, then device timestamp).
|
|
23
|
+
2. Infers any task not yet placed — delegates to the per-task
|
|
24
|
+
`ai-object-detection-inference` path with the given `model_version`
|
|
25
|
+
(or the zero-shot VLM when `engine` requests it / nothing is trained).
|
|
26
|
+
Already-inferred tasks are reused as-is.
|
|
27
|
+
3. Collects annotations FRAME BY FRAME; un-placeable ones become
|
|
28
|
+
`unplaced` ledger entries carrying the placement failure reason.
|
|
29
|
+
4. **MERGE (frame walk)** — walks the frames in capture order: detections
|
|
30
|
+
matching an object seen in an EARLIER frame (same label, within the
|
|
31
|
+
merge radius, one per object per frame) merge into it as
|
|
32
|
+
re-observations; the rest spawn new objects. Same-frame drops happen
|
|
33
|
+
only for explicit quality edge cases (duplicate box, same world spot,
|
|
34
|
+
low confidence, implausible size) — a frame's own detection count is
|
|
35
|
+
authoritative, so two adjacent identical facings stay distinct.
|
|
36
|
+
5. **ARBITRATION** — scores every member of a cluster with the weighted
|
|
37
|
+
6-factor view score (detector confidence, depth confidence, tracking,
|
|
38
|
+
box centering, box size, label agreement) and picks the best VIEW as
|
|
39
|
+
the winner; the full per-member breakdown is persisted.
|
|
40
|
+
6. Persists `objects[]` (kept + merged), `concluded_labels[]` (per-label
|
|
41
|
+
kept/merged/ignored/dropped counts), the per-detection `detections[]`
|
|
42
|
+
ledger and the `funnel` summary, then emits `inferred-od-session`.
|
|
43
|
+
|
|
44
|
+
**Storage & multi-tenancy.** Each run is a new document in the
|
|
45
|
+
`ai.objectDetectionSessionAnalyses` collection, scoped by
|
|
46
|
+
`company_namespace` (injected from the caller's token) and soft-deleted via
|
|
47
|
+
`disabled: true`. The run is opened with `status: pending`, flips to
|
|
48
|
+
`in_progress` when the background job starts, then to `success` (or
|
|
49
|
+
`failed` with `_errors`) on completion. References
|
|
50
|
+
`aiObjectDetectionSession`, `ai.objectDetectionModelVersions`, and
|
|
51
|
+
`sv.aiObjectDetectionLabels`.
|
|
52
|
+
|
|
53
|
+
**Who calls it.** Admins / reps with a valid JWT. `create` triggers analysis
|
|
54
|
+
(or, with `recompute: compose`, synchronously recomposes the shelf structure
|
|
55
|
+
of an existing analysis); `find` / `get` are standard reads; `update` (PUT)
|
|
56
|
+
is a raw document update with a server-stamped `editor`; `patch` is the bulk
|
|
57
|
+
`writeQuery` update over the find filters; `remove` soft-deletes.
|
|
58
|
+
|
|
59
|
+
**Population.** `?populatedKeys[]=` supports `session`, `model_version`,
|
|
60
|
+
`objects.label_id`, `objects.winning_image_media` and
|
|
61
|
+
`concluded_labels.label_id`; every key is populated IN PLACE (the id is
|
|
62
|
+
replaced by the referenced document).
|
|
63
|
+
servers:
|
|
64
|
+
- url: https://sv.api.repzo.me
|
|
65
|
+
security:
|
|
66
|
+
- ApiKeyAuth: []
|
|
67
|
+
- JwtAuth: []
|
|
68
|
+
paths:
|
|
69
|
+
/ai-object-detection-session-analysis:
|
|
70
|
+
get:
|
|
71
|
+
summary: List session analyses
|
|
72
|
+
description: Paginated list of analysis documents, newest first.
|
|
73
|
+
operationId: findAiObjectDetectionSessionAnalysis
|
|
74
|
+
parameters:
|
|
75
|
+
- in: query
|
|
76
|
+
name: _id
|
|
77
|
+
description: Filter by document `_id`.
|
|
78
|
+
schema:
|
|
79
|
+
oneOf:
|
|
80
|
+
- type: string
|
|
81
|
+
- type: array
|
|
82
|
+
items: { type: string }
|
|
83
|
+
- in: query
|
|
84
|
+
name: session
|
|
85
|
+
description: Filter by the parent session `_id` (one or many).
|
|
86
|
+
schema:
|
|
87
|
+
oneOf:
|
|
88
|
+
- type: string
|
|
89
|
+
- type: array
|
|
90
|
+
items: { type: string }
|
|
91
|
+
- in: query
|
|
92
|
+
name: model_version
|
|
93
|
+
description: Filter by the model version used (one or many).
|
|
94
|
+
schema:
|
|
95
|
+
oneOf:
|
|
96
|
+
- type: string
|
|
97
|
+
- type: array
|
|
98
|
+
items: { type: string }
|
|
99
|
+
- in: query
|
|
100
|
+
name: status
|
|
101
|
+
description: Filter by run status (one or many).
|
|
102
|
+
schema:
|
|
103
|
+
oneOf:
|
|
104
|
+
- type: string
|
|
105
|
+
enum: [pending, in_progress, success, failed]
|
|
106
|
+
- type: array
|
|
107
|
+
items:
|
|
108
|
+
type: string
|
|
109
|
+
enum: [pending, in_progress, success, failed]
|
|
110
|
+
- in: query
|
|
111
|
+
name: creator._id
|
|
112
|
+
description: Filter by the id of the rep/admin who triggered the analysis.
|
|
113
|
+
schema:
|
|
114
|
+
oneOf:
|
|
115
|
+
- type: string
|
|
116
|
+
- type: array
|
|
117
|
+
items: { type: string }
|
|
118
|
+
- in: query
|
|
119
|
+
name: from_createdAt
|
|
120
|
+
description: Return analyses created at/after this Unix timestamp (ms).
|
|
121
|
+
schema: { type: number }
|
|
122
|
+
- in: query
|
|
123
|
+
name: to_createdAt
|
|
124
|
+
description: Return analyses created at/before this Unix timestamp (ms).
|
|
125
|
+
schema: { type: number }
|
|
126
|
+
- in: query
|
|
127
|
+
name: from_updatedAt
|
|
128
|
+
description: Cursor — analyses with `updatedAt` greater than this Unix timestamp (ms).
|
|
129
|
+
schema: { type: number }
|
|
130
|
+
- in: query
|
|
131
|
+
name: to_updatedAt
|
|
132
|
+
description: Cursor — analyses with `updatedAt` less than this Unix timestamp (ms).
|
|
133
|
+
schema: { type: number }
|
|
134
|
+
- in: query
|
|
135
|
+
name: disabled
|
|
136
|
+
description: Include disabled (soft-deleted) analyses. Defaults to `false`.
|
|
137
|
+
schema: { type: boolean, default: false }
|
|
138
|
+
- in: query
|
|
139
|
+
name: per_page
|
|
140
|
+
description: Page size. Defaults to the server's configured pagination limit.
|
|
141
|
+
schema: { type: integer, minimum: 1, maximum: 500 }
|
|
142
|
+
example: 50
|
|
143
|
+
- in: query
|
|
144
|
+
name: page
|
|
145
|
+
description: 1-indexed page number.
|
|
146
|
+
schema: { type: integer, minimum: 1 }
|
|
147
|
+
example: 1
|
|
148
|
+
- in: query
|
|
149
|
+
name: sort
|
|
150
|
+
description: Field to sort by. Defaults to `_id`.
|
|
151
|
+
schema: { type: string, default: _id }
|
|
152
|
+
- in: query
|
|
153
|
+
name: sortPageOrder
|
|
154
|
+
description: Sort direction. Defaults to descending.
|
|
155
|
+
schema: { type: string, enum: [asc, dsc], default: dsc }
|
|
156
|
+
- in: query
|
|
157
|
+
name: populatedKeys
|
|
158
|
+
description: |
|
|
159
|
+
Embed referenced documents (populated in place). Supported:
|
|
160
|
+
`session`, `model_version`, `objects.label_id`,
|
|
161
|
+
`objects.winning_image_media`, `concluded_labels.label_id`.
|
|
162
|
+
schema:
|
|
163
|
+
type: array
|
|
164
|
+
items:
|
|
165
|
+
type: string
|
|
166
|
+
enum:
|
|
167
|
+
- session
|
|
168
|
+
- model_version
|
|
169
|
+
- objects.label_id
|
|
170
|
+
- objects.winning_image_media
|
|
171
|
+
- concluded_labels.label_id
|
|
172
|
+
responses:
|
|
173
|
+
"200":
|
|
174
|
+
description: Paginated list of analysis documents (standard envelope).
|
|
175
|
+
content:
|
|
176
|
+
application/json:
|
|
177
|
+
schema:
|
|
178
|
+
$ref: "#/components/schemas/AnalysisFindResult"
|
|
179
|
+
post:
|
|
180
|
+
summary: Start cross-frame analysis on a session (async)
|
|
181
|
+
description: |
|
|
182
|
+
Opens a `pending` analysis document and returns it immediately, then
|
|
183
|
+
infers any not-yet-placed tasks, clusters the kept world detections, and
|
|
184
|
+
arbitrates a winner per cluster **in the background**. Poll the returned
|
|
185
|
+
document (or list analyses for the session) until `status` becomes
|
|
186
|
+
`success`/`failed`.
|
|
187
|
+
|
|
188
|
+
**Model-version semantics.** When `model_version` is given, tasks whose
|
|
189
|
+
auto annotations came from a *different* version are re-inferred with
|
|
190
|
+
the requested one; tasks already inferred by the same version are
|
|
191
|
+
reused. Without `model_version`, any existing auto annotations are
|
|
192
|
+
reused as-is.
|
|
193
|
+
|
|
194
|
+
**Concurrency & staleness.** Only one live run per session: creating an
|
|
195
|
+
analysis while another is `pending`/`in_progress` returns `400`. Zombie
|
|
196
|
+
runs (a server restart lost the background job) are detected via
|
|
197
|
+
`heartbeat_at` — any run without a heartbeat for 15 minutes is
|
|
198
|
+
automatically marked `failed` by the next create, so a stuck run never
|
|
199
|
+
blocks the session permanently.
|
|
200
|
+
|
|
201
|
+
**Side effects on success.** The parent session's `status` transitions
|
|
202
|
+
`infer_in_progress → inferred` (or `failed`), its `detections_count` /
|
|
203
|
+
`objects_count` aggregates are refreshed, each contributing task
|
|
204
|
+
annotation gets its `cluster_id` set to the concluded object's `_id`
|
|
205
|
+
(stale links from earlier runs are cleared first), and best-effort
|
|
206
|
+
scene outputs are stored: the dominant plane (`scene.plane`), the
|
|
207
|
+
RANSAC shelf planes (`scene.planes`), and the voxel point cloud
|
|
208
|
+
(`scene.point_cloud`, packed blob in `bin` media — disable with
|
|
209
|
+
`config.build_point_cloud: false`).
|
|
210
|
+
operationId: createAiObjectDetectionSessionAnalysis
|
|
211
|
+
requestBody:
|
|
212
|
+
required: true
|
|
213
|
+
content:
|
|
214
|
+
application/json:
|
|
215
|
+
schema:
|
|
216
|
+
$ref: "#/components/schemas/AnalysisRequest"
|
|
217
|
+
responses:
|
|
218
|
+
"201":
|
|
219
|
+
description: |
|
|
220
|
+
The freshly-created analysis document with `status: pending` plus a
|
|
221
|
+
`message`. The heavy work runs in the background — the fused
|
|
222
|
+
`objects[]` / `concluded_labels[]` are filled in later, once the
|
|
223
|
+
status flips to `success`.
|
|
224
|
+
content:
|
|
225
|
+
application/json:
|
|
226
|
+
schema:
|
|
227
|
+
allOf:
|
|
228
|
+
- $ref: "#/components/schemas/SessionAnalysis"
|
|
229
|
+
- type: object
|
|
230
|
+
properties:
|
|
231
|
+
message:
|
|
232
|
+
type: string
|
|
233
|
+
description: Human-readable note that the job started in the background.
|
|
234
|
+
"400":
|
|
235
|
+
description: |
|
|
236
|
+
The session has no frames/tasks, or a live (non-stale) analysis is
|
|
237
|
+
already running for it — wait for that run to finish or for the
|
|
238
|
+
15-minute stale watchdog to fail it.
|
|
239
|
+
patch:
|
|
240
|
+
summary: Bulk-update analyses
|
|
241
|
+
description: |
|
|
242
|
+
Bulk update via the `patch-action` writeQuery shape. The rows to
|
|
243
|
+
update are selected by the SAME query filters as the list endpoint
|
|
244
|
+
(`_id`, `session`, `model_version`, `status`, `creator._id`,
|
|
245
|
+
`from_/to_createdAt`, `from_/to_updatedAt`, `disabled`). Returns
|
|
246
|
+
`{ nFound, nModified }`.
|
|
247
|
+
operationId: patchAiObjectDetectionSessionAnalysis
|
|
248
|
+
parameters:
|
|
249
|
+
- in: query
|
|
250
|
+
name: _id
|
|
251
|
+
schema:
|
|
252
|
+
oneOf:
|
|
253
|
+
- type: string
|
|
254
|
+
- type: array
|
|
255
|
+
items: { type: string }
|
|
256
|
+
- in: query
|
|
257
|
+
name: session
|
|
258
|
+
schema:
|
|
259
|
+
oneOf:
|
|
260
|
+
- type: string
|
|
261
|
+
- type: array
|
|
262
|
+
items: { type: string }
|
|
263
|
+
- in: query
|
|
264
|
+
name: model_version
|
|
265
|
+
schema:
|
|
266
|
+
oneOf:
|
|
267
|
+
- type: string
|
|
268
|
+
- type: array
|
|
269
|
+
items: { type: string }
|
|
270
|
+
- in: query
|
|
271
|
+
name: status
|
|
272
|
+
schema:
|
|
273
|
+
oneOf:
|
|
274
|
+
- type: string
|
|
275
|
+
- type: array
|
|
276
|
+
items: { type: string }
|
|
277
|
+
- in: query
|
|
278
|
+
name: creator._id
|
|
279
|
+
schema:
|
|
280
|
+
oneOf:
|
|
281
|
+
- type: string
|
|
282
|
+
- type: array
|
|
283
|
+
items: { type: string }
|
|
284
|
+
- in: query
|
|
285
|
+
name: from_createdAt
|
|
286
|
+
schema: { type: number }
|
|
287
|
+
- in: query
|
|
288
|
+
name: to_createdAt
|
|
289
|
+
schema: { type: number }
|
|
290
|
+
- in: query
|
|
291
|
+
name: from_updatedAt
|
|
292
|
+
schema: { type: number }
|
|
293
|
+
- in: query
|
|
294
|
+
name: to_updatedAt
|
|
295
|
+
schema: { type: number }
|
|
296
|
+
- in: query
|
|
297
|
+
name: disabled
|
|
298
|
+
schema: { type: boolean }
|
|
299
|
+
requestBody:
|
|
300
|
+
required: true
|
|
301
|
+
content:
|
|
302
|
+
application/json:
|
|
303
|
+
schema:
|
|
304
|
+
$ref: "#/components/schemas/PatchActionBody"
|
|
305
|
+
responses:
|
|
306
|
+
"200":
|
|
307
|
+
description: Bulk-update result.
|
|
308
|
+
content:
|
|
309
|
+
application/json:
|
|
310
|
+
schema:
|
|
311
|
+
$ref: "#/components/schemas/PatchActionResult"
|
|
312
|
+
/ai-object-detection-session-analysis/{id}:
|
|
313
|
+
get:
|
|
314
|
+
summary: Get one session analysis
|
|
315
|
+
operationId: getAiObjectDetectionSessionAnalysis
|
|
316
|
+
parameters:
|
|
317
|
+
- in: path
|
|
318
|
+
name: id
|
|
319
|
+
required: true
|
|
320
|
+
schema: { type: string }
|
|
321
|
+
- in: query
|
|
322
|
+
name: populatedKeys
|
|
323
|
+
description: Same population keys as the list endpoint (populated in place).
|
|
324
|
+
schema:
|
|
325
|
+
type: array
|
|
326
|
+
items:
|
|
327
|
+
type: string
|
|
328
|
+
enum:
|
|
329
|
+
- session
|
|
330
|
+
- model_version
|
|
331
|
+
- objects.label_id
|
|
332
|
+
- objects.winning_image_media
|
|
333
|
+
- concluded_labels.label_id
|
|
334
|
+
responses:
|
|
335
|
+
"200":
|
|
336
|
+
description: The analysis document.
|
|
337
|
+
content:
|
|
338
|
+
application/json:
|
|
339
|
+
schema:
|
|
340
|
+
$ref: "#/components/schemas/SessionAnalysis"
|
|
341
|
+
put:
|
|
342
|
+
summary: Update one session analysis
|
|
343
|
+
description: |
|
|
344
|
+
Raw document update (`updateOne` with the body; `editor` is
|
|
345
|
+
server-stamped). Intended for minimal edits such as `disabled: true`
|
|
346
|
+
(soft-delete) — overwriting run output fields would corrupt the
|
|
347
|
+
analysis. The tenant key is derived from the caller's session.
|
|
348
|
+
operationId: updateAiObjectDetectionSessionAnalysis
|
|
349
|
+
parameters:
|
|
350
|
+
- in: path
|
|
351
|
+
name: id
|
|
352
|
+
required: true
|
|
353
|
+
schema: { type: string }
|
|
354
|
+
requestBody:
|
|
355
|
+
required: true
|
|
356
|
+
content:
|
|
357
|
+
application/json:
|
|
358
|
+
schema:
|
|
359
|
+
$ref: "#/components/schemas/AnalysisUpdateBody"
|
|
360
|
+
responses:
|
|
361
|
+
"200":
|
|
362
|
+
description: The analysis document after the update is applied.
|
|
363
|
+
content:
|
|
364
|
+
application/json:
|
|
365
|
+
schema:
|
|
366
|
+
$ref: "#/components/schemas/SessionAnalysis"
|
|
367
|
+
"404":
|
|
368
|
+
description: No analysis with that id in the caller's namespace.
|
|
369
|
+
delete:
|
|
370
|
+
summary: Soft-delete one session analysis
|
|
371
|
+
operationId: removeAiObjectDetectionSessionAnalysis
|
|
372
|
+
parameters:
|
|
373
|
+
- in: path
|
|
374
|
+
name: id
|
|
375
|
+
required: true
|
|
376
|
+
schema: { type: string }
|
|
377
|
+
responses:
|
|
378
|
+
"200":
|
|
379
|
+
description: The analysis document after `disabled` is set to true.
|
|
380
|
+
content:
|
|
381
|
+
application/json:
|
|
382
|
+
schema:
|
|
383
|
+
$ref: "#/components/schemas/SessionAnalysis"
|
|
384
|
+
components:
|
|
385
|
+
securitySchemes:
|
|
386
|
+
ApiKeyAuth:
|
|
387
|
+
type: apiKey
|
|
388
|
+
in: header
|
|
389
|
+
name: api-key
|
|
390
|
+
description: |
|
|
391
|
+
Server-issued API key. Also accepted via the `x-api-key` header or the
|
|
392
|
+
`?apiKey=` query parameter as fallbacks.
|
|
393
|
+
JwtAuth:
|
|
394
|
+
type: apiKey
|
|
395
|
+
in: header
|
|
396
|
+
name: Authorization
|
|
397
|
+
description: |
|
|
398
|
+
Raw JWT in the `Authorization` header — **no `Bearer ` prefix**.
|
|
399
|
+
Obtained from `POST /authenticate` (admin / rep / client login).
|
|
400
|
+
schemas:
|
|
401
|
+
AnalysisRequest:
|
|
402
|
+
type: object
|
|
403
|
+
required:
|
|
404
|
+
- session
|
|
405
|
+
properties:
|
|
406
|
+
session:
|
|
407
|
+
type: string
|
|
408
|
+
description: The `ai-object-detection-session` _id to analyze.
|
|
409
|
+
model_version:
|
|
410
|
+
type: string
|
|
411
|
+
description: |
|
|
412
|
+
`ai.objectDetectionModelVersions` _id used to infer any
|
|
413
|
+
not-yet-placed tasks.
|
|
414
|
+
model:
|
|
415
|
+
type: string
|
|
416
|
+
description: Optional object-detection model `_id` override.
|
|
417
|
+
engine:
|
|
418
|
+
type: string
|
|
419
|
+
enum: [auto, trained, zero_shot]
|
|
420
|
+
default: auto
|
|
421
|
+
description: |
|
|
422
|
+
Detector for tasks inferred during this run. `trained` uses the
|
|
423
|
+
Ultralytics lambda; `zero_shot` uses a hosted Qwen-VL prompted with
|
|
424
|
+
the namespace's label names (for sessions with no trained model
|
|
425
|
+
yet); `auto` (default) tries trained and falls back to zero-shot
|
|
426
|
+
when nothing trained resolves.
|
|
427
|
+
zero_shot_model:
|
|
428
|
+
type: string
|
|
429
|
+
description: |
|
|
430
|
+
Zero-shot VLM override, e.g. `qwen/qwen3-vl-8b-instruct`. Must be
|
|
431
|
+
one of the supported models (see `ai-object-detection-inference`).
|
|
432
|
+
conf:
|
|
433
|
+
type: number
|
|
434
|
+
description: Optional detector confidence override.
|
|
435
|
+
iou:
|
|
436
|
+
type: number
|
|
437
|
+
minimum: 0
|
|
438
|
+
maximum: 1
|
|
439
|
+
description: Optional detector NMS IoU override.
|
|
440
|
+
agnostic_nms:
|
|
441
|
+
type: boolean
|
|
442
|
+
description: Optional class-agnostic NMS override.
|
|
443
|
+
force_reinference:
|
|
444
|
+
type: boolean
|
|
445
|
+
default: false
|
|
446
|
+
description: |
|
|
447
|
+
Re-infer EVERY task even if it already has an auto annotation group
|
|
448
|
+
for this `model_version`. Normal reuse keys on the group's stored
|
|
449
|
+
`model_version`; if an earlier run stamped that version but the boxes
|
|
450
|
+
were produced by a different net (stale provenance), those boxes
|
|
451
|
+
would be reused forever. Set this once to overwrite them.
|
|
452
|
+
inference_concurrency:
|
|
453
|
+
type: integer
|
|
454
|
+
minimum: 1
|
|
455
|
+
maximum: 10
|
|
456
|
+
default: 4
|
|
457
|
+
description: |
|
|
458
|
+
Parallel inference calls for this run. Each call runs on its own
|
|
459
|
+
lambda instance (~30 s/frame on CPU), so a session's inference
|
|
460
|
+
wall-clock divides by this. `1` restores serial behavior; capped at
|
|
461
|
+
10 until GPU inference lands. May also ride inside `config` (the
|
|
462
|
+
dashboard dialog and category model_settings put it there); this
|
|
463
|
+
top-level field wins when both are present.
|
|
464
|
+
recompute:
|
|
465
|
+
type: string
|
|
466
|
+
enum: [compose]
|
|
467
|
+
description: |
|
|
468
|
+
STAGE RECOMPUTE — "compose" re-runs ONLY the shelf composition of
|
|
469
|
+
the existing analysis given in `analysis`, synchronously and in
|
|
470
|
+
place, using the `shelf_*` keys from `config` merged over the
|
|
471
|
+
stored config (non-shelf keys in `config` are IGNORED on this
|
|
472
|
+
path — accepting them would update their stage fingerprints
|
|
473
|
+
without recomputing those stages). Fused objects, ledger and funnel are untouched
|
|
474
|
+
(their config slices didn't change). Milliseconds instead of a
|
|
475
|
+
full run — the path a shelf-knob change takes. Returns the
|
|
476
|
+
updated analysis document directly (no background job).
|
|
477
|
+
analysis:
|
|
478
|
+
type: string
|
|
479
|
+
description: The existing analysis _id to recompute (required with `recompute`).
|
|
480
|
+
config:
|
|
481
|
+
$ref: "#/components/schemas/SceneMathConfig"
|
|
482
|
+
company_namespace:
|
|
483
|
+
type: array
|
|
484
|
+
items: { type: string }
|
|
485
|
+
description: Optional tenant namespace override for SDK callers.
|
|
486
|
+
AnalysisUpdateBody:
|
|
487
|
+
type: object
|
|
488
|
+
description: |
|
|
489
|
+
Body for `PUT /{id}` — any stored field; `editor` is server-stamped.
|
|
490
|
+
Set `disabled: true` to soft-delete.
|
|
491
|
+
properties:
|
|
492
|
+
disabled: { type: boolean }
|
|
493
|
+
config:
|
|
494
|
+
$ref: "#/components/schemas/SceneMathConfig"
|
|
495
|
+
status:
|
|
496
|
+
type: string
|
|
497
|
+
enum: [pending, in_progress, success, failed]
|
|
498
|
+
additionalProperties: true
|
|
499
|
+
PatchActionWrite:
|
|
500
|
+
type: object
|
|
501
|
+
properties:
|
|
502
|
+
key: { type: string }
|
|
503
|
+
command:
|
|
504
|
+
type: string
|
|
505
|
+
enum: [set, addToSet, pull]
|
|
506
|
+
value: {}
|
|
507
|
+
PatchActionBody:
|
|
508
|
+
type: object
|
|
509
|
+
description: Bulk-update body. `writeQuery[]` describes the writes applied to filtered rows.
|
|
510
|
+
required: [writeQuery]
|
|
511
|
+
properties:
|
|
512
|
+
writeQuery:
|
|
513
|
+
type: array
|
|
514
|
+
items:
|
|
515
|
+
$ref: "#/components/schemas/PatchActionWrite"
|
|
516
|
+
PatchActionResult:
|
|
517
|
+
type: object
|
|
518
|
+
properties:
|
|
519
|
+
nFound: { type: number }
|
|
520
|
+
nModified: { type: number }
|
|
521
|
+
AnalysisFindResult:
|
|
522
|
+
type: object
|
|
523
|
+
description: Standard paginated result envelope.
|
|
524
|
+
properties:
|
|
525
|
+
data:
|
|
526
|
+
type: array
|
|
527
|
+
items:
|
|
528
|
+
$ref: "#/components/schemas/SessionAnalysis"
|
|
529
|
+
total_result: { type: number }
|
|
530
|
+
current_count: { type: number }
|
|
531
|
+
total_pages: { type: number }
|
|
532
|
+
current_page: { type: number }
|
|
533
|
+
per_page: { type: number }
|
|
534
|
+
first_page_url: { type: string }
|
|
535
|
+
last_page_url: { type: string }
|
|
536
|
+
next_page_url: { type: string, nullable: true }
|
|
537
|
+
prev_page_url: { type: string, nullable: true }
|
|
538
|
+
path: { type: string }
|
|
539
|
+
SceneMathConfig:
|
|
540
|
+
type: object
|
|
541
|
+
description: Optional clustering / back-projection tuning. Omit for defaults.
|
|
542
|
+
properties:
|
|
543
|
+
inference_concurrency:
|
|
544
|
+
type: integer
|
|
545
|
+
minimum: 1
|
|
546
|
+
maximum: 10
|
|
547
|
+
default: 4
|
|
548
|
+
description: |
|
|
549
|
+
Run-level alias of the top-level `inference_concurrency`, carried
|
|
550
|
+
inside `config` by the dashboard's analyze dialog and by category
|
|
551
|
+
`model_settings` (the category auto-analysis forwards only
|
|
552
|
+
`config`). The top-level field wins when both are present.
|
|
553
|
+
cluster_eps_m:
|
|
554
|
+
type: number
|
|
555
|
+
default: 0.08
|
|
556
|
+
description: Base world distance to merge detections into one object.
|
|
557
|
+
class_agree_bonus_m:
|
|
558
|
+
type: number
|
|
559
|
+
default: 0.02
|
|
560
|
+
description: Extra merge radius when labels match.
|
|
561
|
+
block_same_frame:
|
|
562
|
+
type: boolean
|
|
563
|
+
default: true
|
|
564
|
+
description: |
|
|
565
|
+
A scene object absorbs at most ONE detection per frame during the
|
|
566
|
+
walk — each frame's detection count stays authoritative for how
|
|
567
|
+
many objects exist there.
|
|
568
|
+
same_frame_iou_thresh:
|
|
569
|
+
type: number
|
|
570
|
+
default: 0.92
|
|
571
|
+
description: |
|
|
572
|
+
Same-frame quality gate — two same-label boxes with 2D IoU at/above
|
|
573
|
+
this are a detector double-fire; the weaker one is dropped
|
|
574
|
+
(`duplicate_box_in_frame`).
|
|
575
|
+
same_frame_min_separation_m:
|
|
576
|
+
type: number
|
|
577
|
+
default: 0.02
|
|
578
|
+
description: |
|
|
579
|
+
Same-frame quality gate — two same-label detections placed closer
|
|
580
|
+
than this in world coords cannot be two physical objects; the
|
|
581
|
+
weaker one is dropped (`same_spot_in_frame`).
|
|
582
|
+
min_detection_confidence:
|
|
583
|
+
type: number
|
|
584
|
+
default: 0
|
|
585
|
+
description: |
|
|
586
|
+
Same-frame quality gate — drop detections whose detector confidence
|
|
587
|
+
is below this floor (`below_min_confidence`). 0 disables the gate.
|
|
588
|
+
min_object_size_cm:
|
|
589
|
+
type: number
|
|
590
|
+
default: 0.5
|
|
591
|
+
description: Same-frame quality gate — drop implausibly small front faces.
|
|
592
|
+
max_object_size_cm:
|
|
593
|
+
type: number
|
|
594
|
+
default: 500
|
|
595
|
+
description: Same-frame quality gate — drop implausibly large front faces.
|
|
596
|
+
plane_merge:
|
|
597
|
+
type: boolean
|
|
598
|
+
default: true
|
|
599
|
+
description: |
|
|
600
|
+
Cross-frame matcher: project every placed detection onto the
|
|
601
|
+
session's consensus shelf plane (covariance normal over detection
|
|
602
|
+
positions, gravity-flattened; offset = median projection) and
|
|
603
|
+
merge same-label detections whose projected rects overlap at the
|
|
604
|
+
same plane depth. Falls back to the `cluster_eps_m` distance rule
|
|
605
|
+
when the plane is degenerate (< 3 detections, collinear stack) or
|
|
606
|
+
a detection has no world size.
|
|
607
|
+
plane_merge_iou:
|
|
608
|
+
type: number
|
|
609
|
+
default: 0.1
|
|
610
|
+
description: |
|
|
611
|
+
Minimum IoU of the two plane-projected front-face rects to treat
|
|
612
|
+
them as the same physical object. Adjacent identical facings
|
|
613
|
+
project side-by-side (IoU ~ 0), so they are never merged by this
|
|
614
|
+
rule. Default 0.1 — lenient so viewpoint-skewed or border-clipped
|
|
615
|
+
re-observations (whose projected overlap drops well below 0.3)
|
|
616
|
+
still merge, while side-by-side facings stay separate.
|
|
617
|
+
plane_merge_depth_delta_m:
|
|
618
|
+
type: number
|
|
619
|
+
default: 0.25
|
|
620
|
+
description: |
|
|
621
|
+
Maximum difference of the two detections' distances to the
|
|
622
|
+
consensus plane (metres) — the "virtual object depth". Overlapping
|
|
623
|
+
rects farther apart than this along the shelf normal are different
|
|
624
|
+
objects (front row vs back row), not a re-observation.
|
|
625
|
+
reclassify_labels:
|
|
626
|
+
type: boolean
|
|
627
|
+
default: false
|
|
628
|
+
description: |
|
|
629
|
+
Dims reclassifier master switch — re-labels a placed detection to
|
|
630
|
+
a SIBLING label (same label_group) when its MEASURED physical
|
|
631
|
+
size fits the sibling's expected dims better. Annotations keep
|
|
632
|
+
`original_label` + `reclassification_reason` provenance; running
|
|
633
|
+
with the switch OFF reverts earlier auto reclassifications.
|
|
634
|
+
reclassify_keep_dev:
|
|
635
|
+
type: number
|
|
636
|
+
default: 0.15
|
|
637
|
+
description: "Fit error at/below which the ORIGINAL label is kept outright (~ ±16% size). Raising it protects the detected label."
|
|
638
|
+
reclassify_target_dev:
|
|
639
|
+
type: number
|
|
640
|
+
default: 0.15
|
|
641
|
+
description: "A sibling must fit within this error to steal the detection. Raising it favors re-labeling."
|
|
642
|
+
reclassify_min_margin:
|
|
643
|
+
type: number
|
|
644
|
+
default: 0.06
|
|
645
|
+
description: "Base margin E(original) − E(best sibling) must exceed. Raising it protects the detected label."
|
|
646
|
+
reclassify_conf_margin_scale:
|
|
647
|
+
type: number
|
|
648
|
+
default: 0.5
|
|
649
|
+
description: "Margin scales ×(1 + this × detector confidence) — confident detections are harder to overturn; 0 ignores confidence."
|
|
650
|
+
reclassify_weight_scale:
|
|
651
|
+
type: number
|
|
652
|
+
default: 1.0
|
|
653
|
+
description: "Weight of the SIZE mismatch in the fit error. Raising it favors re-labeling (size separates variants)."
|
|
654
|
+
reclassify_weight_aspect:
|
|
655
|
+
type: number
|
|
656
|
+
default: 0.5
|
|
657
|
+
description: "Weight of the SHAPE mismatch in the fit error. Raising it protects the detected label (siblings share shape)."
|
|
658
|
+
reclassify_min_depth_confidence:
|
|
659
|
+
type: number
|
|
660
|
+
default: 0.5
|
|
661
|
+
description: "Detections with depth confidence below this are never reclassified."
|
|
662
|
+
group_consensus_merge:
|
|
663
|
+
type: boolean
|
|
664
|
+
default: true
|
|
665
|
+
description: |
|
|
666
|
+
Post-walk safety net (active only with reclassify_labels) — merge
|
|
667
|
+
clusters of DIFFERENT labels from the same label_group that
|
|
668
|
+
overlap on the consensus plane; the members are re-labeled to the
|
|
669
|
+
best dims fit.
|
|
670
|
+
size_gate:
|
|
671
|
+
type: boolean
|
|
672
|
+
default: true
|
|
673
|
+
description: |
|
|
674
|
+
Physical SIZE GATE — runs AFTER the reclassifier: a detection
|
|
675
|
+
whose measured size still exceeds (1+allowance)× its (possibly
|
|
676
|
+
re-labeled) label's expected width/height/area is DROPPED from
|
|
677
|
+
the walk (ledger disposition `size_rejected`; the annotation is
|
|
678
|
+
kept and flagged `size_rejected` + `size_reject_detail`).
|
|
679
|
+
One-sided by design — under-size is routinely a partially
|
|
680
|
+
occluded real product. No-op for labels without both dims.
|
|
681
|
+
size_gate_dims_allowance:
|
|
682
|
+
type: number
|
|
683
|
+
default: 0.35
|
|
684
|
+
description: "Per-axis allowance: reject when measured width or height > (1+this) × expected. Raising it keeps more (looser gate)."
|
|
685
|
+
size_gate_area_allowance:
|
|
686
|
+
type: number
|
|
687
|
+
default: 0.35
|
|
688
|
+
description: "Area allowance: reject when measured w·h > (1+this) × expected area. Noise compounds in area, so this is the binding check. Raising it keeps more."
|
|
689
|
+
size_gate_min_depth_confidence:
|
|
690
|
+
type: number
|
|
691
|
+
default: 0.5
|
|
692
|
+
description: "Detections with depth confidence below this are never size-gated (unmeasurable ≠ oversized)."
|
|
693
|
+
min_depth_m:
|
|
694
|
+
type: number
|
|
695
|
+
default: 0.05
|
|
696
|
+
max_depth_m:
|
|
697
|
+
type: number
|
|
698
|
+
default: 6
|
|
699
|
+
conf_threshold:
|
|
700
|
+
type: number
|
|
701
|
+
default: 1
|
|
702
|
+
front_percentile:
|
|
703
|
+
type: number
|
|
704
|
+
default: 30
|
|
705
|
+
shelf_tolerance_m:
|
|
706
|
+
type: number
|
|
707
|
+
default: 0.3
|
|
708
|
+
shelf_analysis:
|
|
709
|
+
type: boolean
|
|
710
|
+
default: true
|
|
711
|
+
description: |
|
|
712
|
+
Master switch for SHELF COMPOSITION — after fusion, build the
|
|
713
|
+
planogram structure (shelves → stacks → objects) from the fused
|
|
714
|
+
objects' world geometry and persist it as `shelf_composition`.
|
|
715
|
+
Shelf BOARDS are found first (density modes of object bottoms,
|
|
716
|
+
accepted strongest-first at a minimum physical spacing); a STACK
|
|
717
|
+
is then everything between two boards at one horizontal slot —
|
|
718
|
+
two stacks can never sit on top of each other by construction,
|
|
719
|
+
and stacking never hinges on fragile box-touch tolerances.
|
|
720
|
+
shelf_support_min_overlap:
|
|
721
|
+
type: number
|
|
722
|
+
default: 0.5
|
|
723
|
+
description: |
|
|
724
|
+
Min horizontal overlap ratio (of the narrower object, 0..1) for
|
|
725
|
+
an object to belong to the same column as the one below it.
|
|
726
|
+
Increase: demands straighter piles — offset cans become separate
|
|
727
|
+
stacks. Decrease: diagonal neighbours chain into one stack,
|
|
728
|
+
understating facing counts.
|
|
729
|
+
shelf_gap_split_m:
|
|
730
|
+
type: number
|
|
731
|
+
default: 0.06
|
|
732
|
+
description: |
|
|
733
|
+
Half-width (metres) of the bottom-density window used to find
|
|
734
|
+
candidate shelf levels — bottoms within this of a mode belong to
|
|
735
|
+
it. Increase: nearby levels blur into one candidate. Decrease:
|
|
736
|
+
measurement noise splinters a level into weak candidates.
|
|
737
|
+
shelf_min_spacing_m:
|
|
738
|
+
type: number
|
|
739
|
+
default: 0.12
|
|
740
|
+
description: |
|
|
741
|
+
Minimum vertical distance between two shelf BOARDS (board
|
|
742
|
+
thickness + product clearance — hard store physics). Candidate
|
|
743
|
+
levels are accepted strongest-first, each at least this far from
|
|
744
|
+
every accepted board, which is what keeps a jar-on-jar stacking
|
|
745
|
+
layer (bottoms +8–11 cm above their base) from ever becoming a
|
|
746
|
+
phantom shelf. Increase: genuinely shallow shelves read as
|
|
747
|
+
stacking. Decrease: stacking layers start reading as shelves.
|
|
748
|
+
shelf_stack_max_penetration_m:
|
|
749
|
+
type: number
|
|
750
|
+
default: 0.035
|
|
751
|
+
description: |
|
|
752
|
+
Measurement slack for stacked boxes (metres). Two objects cannot
|
|
753
|
+
co-occupy space: a joiner whose bottom sinks deeper than this
|
|
754
|
+
into an overlapped column member is a side-by-side facing with an
|
|
755
|
+
inflated box, never a pile member. Increase: inflated neighbours
|
|
756
|
+
glue into false towers. Decrease: noisy true piles split apart.
|
|
757
|
+
shelf_row_split_m:
|
|
758
|
+
type: number
|
|
759
|
+
default: 0.15
|
|
760
|
+
description: |
|
|
761
|
+
Plane-depth gap separating front/back rows within one shelf
|
|
762
|
+
(metres). Row 0 is the frontmost (closest to the shopper).
|
|
763
|
+
Increase: everything collapses into a single row. Decrease: minor
|
|
764
|
+
depth noise becomes extra rows.
|
|
765
|
+
shelf_min_stacks:
|
|
766
|
+
type: number
|
|
767
|
+
default: 1
|
|
768
|
+
description: |
|
|
769
|
+
A level must hold at least this many stacks to become a shelf;
|
|
770
|
+
leaner levels are reported under `unshelved` instead. Increase:
|
|
771
|
+
filters hook-wall/pegboard singletons and stray mis-placed bases
|
|
772
|
+
out of the shelf list. Decrease (1): every level becomes a shelf,
|
|
773
|
+
including one-off outliers.
|
|
774
|
+
build_point_cloud:
|
|
775
|
+
type: boolean
|
|
776
|
+
default: true
|
|
777
|
+
description: |
|
|
778
|
+
Build + store the scene voxel point cloud (and RANSAC shelf
|
|
779
|
+
planes) during the run. Downloads each frame's depth blob + RGB
|
|
780
|
+
image server-side, so disable for very large sessions when the 3D
|
|
781
|
+
scene's cloud layer is not needed.
|
|
782
|
+
pc_stride:
|
|
783
|
+
type: number
|
|
784
|
+
default: 2
|
|
785
|
+
description: Depth-grid stride when back-projecting the cloud (every Nth pixel).
|
|
786
|
+
pc_voxel_size_m:
|
|
787
|
+
type: number
|
|
788
|
+
default: 0.02
|
|
789
|
+
description: Voxel edge for point-cloud downsampling, metres.
|
|
790
|
+
ransac_max_planes:
|
|
791
|
+
type: number
|
|
792
|
+
default: 4
|
|
793
|
+
description: Max shelf planes to peel off the voxel cloud.
|
|
794
|
+
ransac_min_inlier_ratio:
|
|
795
|
+
type: number
|
|
796
|
+
default: 0.05
|
|
797
|
+
description: Stop peeling planes below this inlier share.
|
|
798
|
+
ransac_distance_thresh_m:
|
|
799
|
+
type: number
|
|
800
|
+
default: 0.02
|
|
801
|
+
description: Point-to-plane inlier distance for RANSAC, metres.
|
|
802
|
+
AnalysisObject:
|
|
803
|
+
type: object
|
|
804
|
+
description: |
|
|
805
|
+
A concluded object on the shelf (one per cross-frame cluster). `state` is
|
|
806
|
+
`kept` for a single facing or `merged` for several facings fused.
|
|
807
|
+
properties:
|
|
808
|
+
_id:
|
|
809
|
+
type: string
|
|
810
|
+
description: |
|
|
811
|
+
Cluster link target — contributing task annotations carry this id
|
|
812
|
+
in their `cluster_id` after the run succeeds.
|
|
813
|
+
label_id: { type: string }
|
|
814
|
+
label_name: { type: string }
|
|
815
|
+
state:
|
|
816
|
+
type: string
|
|
817
|
+
enum: [kept, merged]
|
|
818
|
+
confidence:
|
|
819
|
+
type: number
|
|
820
|
+
description: Fused placement confidence (cluster mean), 0..1.
|
|
821
|
+
world:
|
|
822
|
+
type: object
|
|
823
|
+
description: Centroid in world coordinates, **metres**.
|
|
824
|
+
properties:
|
|
825
|
+
x: { type: number }
|
|
826
|
+
y: { type: number }
|
|
827
|
+
z: { type: number }
|
|
828
|
+
size:
|
|
829
|
+
type: object
|
|
830
|
+
description: Physical front-face size, **centimetres**.
|
|
831
|
+
properties:
|
|
832
|
+
w: { type: number }
|
|
833
|
+
h: { type: number }
|
|
834
|
+
depth:
|
|
835
|
+
type: number
|
|
836
|
+
description: Distance from the camera, **metres**.
|
|
837
|
+
height_above_ground_m:
|
|
838
|
+
type: number
|
|
839
|
+
nullable: true
|
|
840
|
+
description: |
|
|
841
|
+
Metres above the device-locked floor (`session.ground.y_world`) —
|
|
842
|
+
the world frame is gravity-aligned, so this is the object's true
|
|
843
|
+
shelf height. Compare against `session.eye_level_m` for shelf-band
|
|
844
|
+
analytics (eye level ≈ premium band). `null` when the rep skipped
|
|
845
|
+
the floor point at capture entry.
|
|
846
|
+
yaw:
|
|
847
|
+
type: number
|
|
848
|
+
nullable: true
|
|
849
|
+
description: |
|
|
850
|
+
Facing rotation about world-Y, **radians** — the direction from the
|
|
851
|
+
object centroid to the winning frame's camera. `null` when the
|
|
852
|
+
winning task lacks a pose.
|
|
853
|
+
cluster_size:
|
|
854
|
+
type: number
|
|
855
|
+
description: How many facings/frames contributed.
|
|
856
|
+
bbox_3d:
|
|
857
|
+
type: object
|
|
858
|
+
properties:
|
|
859
|
+
min: { type: array, items: { type: number } }
|
|
860
|
+
max: { type: array, items: { type: number } }
|
|
861
|
+
winning_task: { type: string }
|
|
862
|
+
winning_annotation_id: { type: string }
|
|
863
|
+
winning_box:
|
|
864
|
+
type: object
|
|
865
|
+
description: |
|
|
866
|
+
Winner's 2D crop region on `winning_image_media` (YOLO-normalized
|
|
867
|
+
cx, cy, w, h). Front-ends texture the object with this sub-region.
|
|
868
|
+
properties:
|
|
869
|
+
x1: { type: number }
|
|
870
|
+
y1: { type: number }
|
|
871
|
+
x2: { type: number }
|
|
872
|
+
y2: { type: number }
|
|
873
|
+
winning_image_media:
|
|
874
|
+
type: string
|
|
875
|
+
description: |
|
|
876
|
+
`media.mediaStorages` id of the winning frame image (the crop
|
|
877
|
+
source). Populate with `objects.winning_image_media` to get its URL.
|
|
878
|
+
contributing_tasks:
|
|
879
|
+
type: array
|
|
880
|
+
items: { type: string }
|
|
881
|
+
arbitration:
|
|
882
|
+
type: object
|
|
883
|
+
description: |
|
|
884
|
+
Full winner-selection rationale: `strategy` (`weighted_view_score`),
|
|
885
|
+
the factor `weights`, and per-member `scores[]` — each with the six
|
|
886
|
+
normalized inputs (`det`, `depth`, `track`, `center`, `size`,
|
|
887
|
+
`agree`), the weighted `score`, and a `winner` flag. Renders the
|
|
888
|
+
dashboard's "why did this view win" inspector.
|
|
889
|
+
properties:
|
|
890
|
+
strategy: { type: string }
|
|
891
|
+
weights:
|
|
892
|
+
type: object
|
|
893
|
+
additionalProperties: { type: number }
|
|
894
|
+
scores:
|
|
895
|
+
type: array
|
|
896
|
+
items:
|
|
897
|
+
type: object
|
|
898
|
+
properties:
|
|
899
|
+
task_id: { type: string }
|
|
900
|
+
annotation_id: { type: string }
|
|
901
|
+
frame_id: { type: number }
|
|
902
|
+
score: { type: number }
|
|
903
|
+
winner: { type: boolean }
|
|
904
|
+
inputs:
|
|
905
|
+
type: object
|
|
906
|
+
properties:
|
|
907
|
+
det: { type: number }
|
|
908
|
+
depth: { type: number }
|
|
909
|
+
track: { type: number }
|
|
910
|
+
center: { type: number }
|
|
911
|
+
size: { type: number }
|
|
912
|
+
agree: { type: number }
|
|
913
|
+
ConcludedLabel:
|
|
914
|
+
type: object
|
|
915
|
+
description: Per-label conclusion — detection counts by outcome.
|
|
916
|
+
properties:
|
|
917
|
+
label_id: { type: string }
|
|
918
|
+
label_name: { type: string }
|
|
919
|
+
kept:
|
|
920
|
+
type: number
|
|
921
|
+
description: Detections placed as a single facing.
|
|
922
|
+
merged:
|
|
923
|
+
type: number
|
|
924
|
+
description: Detections fused into multi-facing objects.
|
|
925
|
+
ignored:
|
|
926
|
+
type: number
|
|
927
|
+
description: Detections that could not be placed.
|
|
928
|
+
dropped:
|
|
929
|
+
type: number
|
|
930
|
+
description: Detections removed by same-frame quality gates.
|
|
931
|
+
size_rejected:
|
|
932
|
+
type: number
|
|
933
|
+
description: Detections dropped by the physical-size gate.
|
|
934
|
+
object_count:
|
|
935
|
+
type: number
|
|
936
|
+
description: Resulting objects (kept + merged clusters).
|
|
937
|
+
IgnoredDetection:
|
|
938
|
+
type: object
|
|
939
|
+
description: A detection that could not be placed in world coordinates.
|
|
940
|
+
properties:
|
|
941
|
+
task: { type: string }
|
|
942
|
+
annotation_id: { type: string }
|
|
943
|
+
label_id: { type: string }
|
|
944
|
+
frame_id: { type: number }
|
|
945
|
+
reason:
|
|
946
|
+
type: string
|
|
947
|
+
description: |
|
|
948
|
+
Why placement was impossible: `no_pose`, `no_intrinsics`,
|
|
949
|
+
`no_depth`, `empty_depth_region`, `insufficient_depth_pixels`,
|
|
950
|
+
`behind_shelf`, or `not_placed` (legacy annotations without a
|
|
951
|
+
recorded reason).
|
|
952
|
+
DetectionLedgerEntry:
|
|
953
|
+
type: object
|
|
954
|
+
description: |
|
|
955
|
+
The audit trail of ONE detection through the fusion pipeline — where it
|
|
956
|
+
went and why. `new_object` spawned an object; `re_observation` merged
|
|
957
|
+
into an object first seen in an earlier frame; `dropped_same_frame` was
|
|
958
|
+
removed by a quality gate (see `reason` + `kept_by_annotation_id`);
|
|
959
|
+
`size_rejected` was dropped by the physical-size gate (annotation kept
|
|
960
|
+
and flagged); `unplaced` never had world data (see `reason`).
|
|
961
|
+
properties:
|
|
962
|
+
task: { type: string }
|
|
963
|
+
annotation_id: { type: string }
|
|
964
|
+
label_id: { type: string }
|
|
965
|
+
label_name: { type: string }
|
|
966
|
+
frame_id: { type: number }
|
|
967
|
+
disposition:
|
|
968
|
+
type: string
|
|
969
|
+
enum:
|
|
970
|
+
[
|
|
971
|
+
new_object,
|
|
972
|
+
re_observation,
|
|
973
|
+
dropped_same_frame,
|
|
974
|
+
size_rejected,
|
|
975
|
+
unplaced,
|
|
976
|
+
]
|
|
977
|
+
reason:
|
|
978
|
+
type: string
|
|
979
|
+
description: |
|
|
980
|
+
Machine cause — `first_observation`, `re_observation`,
|
|
981
|
+
`duplicate_box_in_frame`, `same_spot_in_frame`,
|
|
982
|
+
`below_min_confidence`, `implausible_size`, `size_gate`, or an
|
|
983
|
+
unplaced reason (`no_pose`, `no_depth`, `behind_shelf`, ...).
|
|
984
|
+
detail:
|
|
985
|
+
type: string
|
|
986
|
+
description: Human-readable explanation, ready to render.
|
|
987
|
+
object_id:
|
|
988
|
+
type: string
|
|
989
|
+
description: The `objects[]._id` this detection created or merged into.
|
|
990
|
+
kept_by_annotation_id:
|
|
991
|
+
type: string
|
|
992
|
+
description: For same-frame drops — the stronger sibling that was kept.
|
|
993
|
+
matched_distance_m:
|
|
994
|
+
type: number
|
|
995
|
+
description: For re-observations — world distance to the object, metres.
|
|
996
|
+
world:
|
|
997
|
+
type: object
|
|
998
|
+
properties:
|
|
999
|
+
x: { type: number }
|
|
1000
|
+
y: { type: number }
|
|
1001
|
+
z: { type: number }
|
|
1002
|
+
box:
|
|
1003
|
+
type: object
|
|
1004
|
+
description: The detection's 2D box (YOLO-normalized cx, cy, w, h).
|
|
1005
|
+
properties:
|
|
1006
|
+
x1: { type: number }
|
|
1007
|
+
y1: { type: number }
|
|
1008
|
+
x2: { type: number }
|
|
1009
|
+
y2: { type: number }
|
|
1010
|
+
confidence: { type: number }
|
|
1011
|
+
placement_confidence: { type: number }
|
|
1012
|
+
AnalysisFunnel:
|
|
1013
|
+
type: object
|
|
1014
|
+
description: Where every detection went — the dashboard's fusion summary.
|
|
1015
|
+
properties:
|
|
1016
|
+
tasks: { type: number }
|
|
1017
|
+
tasks_manual:
|
|
1018
|
+
type: number
|
|
1019
|
+
description: |
|
|
1020
|
+
Frames whose boxes came from a confirmed HUMAN annotation group
|
|
1021
|
+
(manual / auto_edited) — the "computed on N human-verified
|
|
1022
|
+
frames" number business metrics report.
|
|
1023
|
+
detections_total: { type: number }
|
|
1024
|
+
unplaced: { type: number }
|
|
1025
|
+
size_rejected:
|
|
1026
|
+
type: number
|
|
1027
|
+
description: Detections dropped by the physical-size gate.
|
|
1028
|
+
frames: { type: number }
|
|
1029
|
+
detections_placed: { type: number }
|
|
1030
|
+
dropped_same_frame: { type: number }
|
|
1031
|
+
re_observations: { type: number }
|
|
1032
|
+
new_objects: { type: number }
|
|
1033
|
+
clusters_final: { type: number }
|
|
1034
|
+
SessionAnalysis:
|
|
1035
|
+
type: object
|
|
1036
|
+
properties:
|
|
1037
|
+
_id: { type: string }
|
|
1038
|
+
disabled: { type: boolean }
|
|
1039
|
+
session: { type: string }
|
|
1040
|
+
model_version: { type: string }
|
|
1041
|
+
config:
|
|
1042
|
+
$ref: "#/components/schemas/SceneMathConfig"
|
|
1043
|
+
frame_sources:
|
|
1044
|
+
type: array
|
|
1045
|
+
description: |
|
|
1046
|
+
PROVENANCE — which annotation group each frame contributed, chosen
|
|
1047
|
+
human-truth-first (a confirmed manual/auto_edited group beats
|
|
1048
|
+
every auto group regardless of the requested model version). The
|
|
1049
|
+
dashboard resolves the exact boxes this analysis consumed from
|
|
1050
|
+
here; `edit_time` lets the UI flag analyses older than a frame's
|
|
1051
|
+
latest correction.
|
|
1052
|
+
items:
|
|
1053
|
+
type: object
|
|
1054
|
+
properties:
|
|
1055
|
+
task: { type: string }
|
|
1056
|
+
group_id: { type: string }
|
|
1057
|
+
annotation_state:
|
|
1058
|
+
type: string
|
|
1059
|
+
enum: [auto, manual, auto_edited]
|
|
1060
|
+
model_version: { type: string }
|
|
1061
|
+
edit_time: { type: number }
|
|
1062
|
+
stage_fingerprints:
|
|
1063
|
+
type: object
|
|
1064
|
+
description: |
|
|
1065
|
+
Per-stage config hashes (detect / fuse / compose slices of
|
|
1066
|
+
`config`). Staleness is a fingerprint diff — a shelf-knob change
|
|
1067
|
+
flips only `compose`, unlocking the compose-only `recompute`.
|
|
1068
|
+
properties:
|
|
1069
|
+
detect: { type: string }
|
|
1070
|
+
fuse: { type: string }
|
|
1071
|
+
compose: { type: string }
|
|
1072
|
+
status:
|
|
1073
|
+
type: string
|
|
1074
|
+
enum: [pending, in_progress, success, failed]
|
|
1075
|
+
started_at: { type: number }
|
|
1076
|
+
finished_at: { type: number }
|
|
1077
|
+
heartbeat_at:
|
|
1078
|
+
type: number
|
|
1079
|
+
description: |
|
|
1080
|
+
Liveness heartbeat (epoch ms) refreshed by the background run after
|
|
1081
|
+
each task inference; the stale-run watchdog measures against it.
|
|
1082
|
+
tasks_total: { type: number }
|
|
1083
|
+
tasks_inferred:
|
|
1084
|
+
type: number
|
|
1085
|
+
description: Tasks inferred during this run (not previously placed).
|
|
1086
|
+
kept_count:
|
|
1087
|
+
type: number
|
|
1088
|
+
description: Detections kept (single facings).
|
|
1089
|
+
merged_count:
|
|
1090
|
+
type: number
|
|
1091
|
+
description: Detections merged into objects.
|
|
1092
|
+
ignored_count:
|
|
1093
|
+
type: number
|
|
1094
|
+
description: Detections that could not be placed.
|
|
1095
|
+
dropped_count:
|
|
1096
|
+
type: number
|
|
1097
|
+
description: Detections removed by same-frame quality gates.
|
|
1098
|
+
size_rejected_count:
|
|
1099
|
+
type: number
|
|
1100
|
+
description: Detections dropped by the physical-size gate.
|
|
1101
|
+
objects_count:
|
|
1102
|
+
type: number
|
|
1103
|
+
description: Resulting objects (kept + merged).
|
|
1104
|
+
objects:
|
|
1105
|
+
type: array
|
|
1106
|
+
items:
|
|
1107
|
+
$ref: "#/components/schemas/AnalysisObject"
|
|
1108
|
+
concluded_labels:
|
|
1109
|
+
type: array
|
|
1110
|
+
items:
|
|
1111
|
+
$ref: "#/components/schemas/ConcludedLabel"
|
|
1112
|
+
ignored:
|
|
1113
|
+
type: array
|
|
1114
|
+
items:
|
|
1115
|
+
$ref: "#/components/schemas/IgnoredDetection"
|
|
1116
|
+
detections:
|
|
1117
|
+
type: array
|
|
1118
|
+
description: Per-detection fusion audit trail (one entry per analyzed annotation).
|
|
1119
|
+
items:
|
|
1120
|
+
$ref: "#/components/schemas/DetectionLedgerEntry"
|
|
1121
|
+
funnel:
|
|
1122
|
+
$ref: "#/components/schemas/AnalysisFunnel"
|
|
1123
|
+
shelf_composition:
|
|
1124
|
+
type: object
|
|
1125
|
+
description: |
|
|
1126
|
+
PLANOGRAM STRUCTURE composed from the fused objects' world
|
|
1127
|
+
geometry (present unless `config.shelf_analysis: false` or nothing
|
|
1128
|
+
was placed). Ordering contract: `shelves[0]` is the LOWEST shelf,
|
|
1129
|
+
`shelves[].stacks[0]` is the SHOPPER's leftmost stack, and
|
|
1130
|
+
`stacks[].object_ids` list the pile bottom-up. Compliance rules
|
|
1131
|
+
(same-shelf exclusions, adjacency, share-of-shelf) read directly
|
|
1132
|
+
off this structure.
|
|
1133
|
+
properties:
|
|
1134
|
+
plane:
|
|
1135
|
+
type: object
|
|
1136
|
+
description: |
|
|
1137
|
+
Orthonormal world basis. `n` is the horizontal unit normal of
|
|
1138
|
+
the shelf face pointing TOWARD the shopper (the session's
|
|
1139
|
+
cameras); `u = up × n` points to the shopper's RIGHT. A plane
|
|
1140
|
+
coordinate maps back to world as `û·u + Ŷ·y + n̂·s`.
|
|
1141
|
+
properties:
|
|
1142
|
+
n:
|
|
1143
|
+
type: array
|
|
1144
|
+
items: { type: number }
|
|
1145
|
+
u:
|
|
1146
|
+
type: array
|
|
1147
|
+
items: { type: number }
|
|
1148
|
+
orientation:
|
|
1149
|
+
type: string
|
|
1150
|
+
enum: [shopper]
|
|
1151
|
+
description: Stacks are indexed in the shopper's frame (0 = leftmost).
|
|
1152
|
+
shelves:
|
|
1153
|
+
type: array
|
|
1154
|
+
items:
|
|
1155
|
+
type: object
|
|
1156
|
+
properties:
|
|
1157
|
+
index:
|
|
1158
|
+
type: number
|
|
1159
|
+
description: 0 = the lowest shelf.
|
|
1160
|
+
y_world:
|
|
1161
|
+
type: number
|
|
1162
|
+
description: |
|
|
1163
|
+
Board level (world Y, metres) — a low percentile (p15)
|
|
1164
|
+
of the member stack bases, so the board sits UNDER its
|
|
1165
|
+
products rather than through the middle of the noisy
|
|
1166
|
+
base band.
|
|
1167
|
+
height_above_ground_m:
|
|
1168
|
+
type: number
|
|
1169
|
+
nullable: true
|
|
1170
|
+
description: Shelf level above the device-locked floor; null when the floor was not locked.
|
|
1171
|
+
u_from:
|
|
1172
|
+
type: number
|
|
1173
|
+
description: Occupied horizontal extent start (metres, plane coords).
|
|
1174
|
+
u_to:
|
|
1175
|
+
type: number
|
|
1176
|
+
s:
|
|
1177
|
+
type: number
|
|
1178
|
+
description: Median plane depth of member stacks (metres).
|
|
1179
|
+
stacks:
|
|
1180
|
+
type: array
|
|
1181
|
+
items:
|
|
1182
|
+
type: object
|
|
1183
|
+
properties:
|
|
1184
|
+
index:
|
|
1185
|
+
type: number
|
|
1186
|
+
description: 0 = leftmost as the shopper sees the shelf.
|
|
1187
|
+
u_from: { type: number }
|
|
1188
|
+
u_to: { type: number }
|
|
1189
|
+
y_from: { type: number }
|
|
1190
|
+
y_to: { type: number }
|
|
1191
|
+
s: { type: number }
|
|
1192
|
+
row:
|
|
1193
|
+
type: number
|
|
1194
|
+
description: Depth row within the shelf — 0 = front row (closest to the shopper).
|
|
1195
|
+
object_ids:
|
|
1196
|
+
type: array
|
|
1197
|
+
items: { type: string }
|
|
1198
|
+
description: "Refs into `objects[]._id`, ordered bottom-up (index 0 sits on the shelf)."
|
|
1199
|
+
unshelved:
|
|
1200
|
+
type: array
|
|
1201
|
+
description: Objects that didn't land on a shelf level (hook walls, sparse levels).
|
|
1202
|
+
items:
|
|
1203
|
+
type: object
|
|
1204
|
+
properties:
|
|
1205
|
+
object_id: { type: string }
|
|
1206
|
+
reason: { type: string }
|
|
1207
|
+
scene:
|
|
1208
|
+
type: object
|
|
1209
|
+
description: |
|
|
1210
|
+
Scene-level outputs. All best-effort — each part is omitted when it
|
|
1211
|
+
can't be computed (too few points, media unavailable, or
|
|
1212
|
+
`config.build_point_cloud: false`).
|
|
1213
|
+
properties:
|
|
1214
|
+
plane:
|
|
1215
|
+
type: object
|
|
1216
|
+
description: |
|
|
1217
|
+
Dominant (shelf) plane fitted over all placed detections' world
|
|
1218
|
+
positions (covariance eigen; legacy single plane, kept for
|
|
1219
|
+
back-compat). Omitted when fewer than 3 placed detections
|
|
1220
|
+
exist or the fit fails.
|
|
1221
|
+
properties:
|
|
1222
|
+
normal:
|
|
1223
|
+
type: array
|
|
1224
|
+
items: { type: number }
|
|
1225
|
+
description: "Unit normal [x, y, z]."
|
|
1226
|
+
d:
|
|
1227
|
+
type: number
|
|
1228
|
+
description: "Plane equation term: n·p + d = 0."
|
|
1229
|
+
centroid:
|
|
1230
|
+
type: array
|
|
1231
|
+
items: { type: number }
|
|
1232
|
+
description: "Centroid [x, y, z], metres."
|
|
1233
|
+
inliers:
|
|
1234
|
+
type: number
|
|
1235
|
+
description: Points used in the fit.
|
|
1236
|
+
planes:
|
|
1237
|
+
type: array
|
|
1238
|
+
description: |
|
|
1239
|
+
Bounded shelf planes peeled off the voxel point cloud by
|
|
1240
|
+
iterative RANSAC, strongest first. Front-ends render each as a
|
|
1241
|
+
translucent rectangle sized by its extent corners.
|
|
1242
|
+
items:
|
|
1243
|
+
type: object
|
|
1244
|
+
properties:
|
|
1245
|
+
normal:
|
|
1246
|
+
type: array
|
|
1247
|
+
items: { type: number }
|
|
1248
|
+
description: "Unit normal [x, y, z]."
|
|
1249
|
+
point:
|
|
1250
|
+
type: array
|
|
1251
|
+
items: { type: number }
|
|
1252
|
+
description: "A point on the plane (inlier centroid), metres."
|
|
1253
|
+
extent_min:
|
|
1254
|
+
type: array
|
|
1255
|
+
items: { type: number }
|
|
1256
|
+
description: One corner of the fitted inlier rectangle (world coords).
|
|
1257
|
+
extent_max:
|
|
1258
|
+
type: array
|
|
1259
|
+
items: { type: number }
|
|
1260
|
+
description: The opposite corner.
|
|
1261
|
+
inlier_count: { type: number }
|
|
1262
|
+
inlier_ratio:
|
|
1263
|
+
type: number
|
|
1264
|
+
description: "Inliers / cloud points, 0..1."
|
|
1265
|
+
point_cloud:
|
|
1266
|
+
type: object
|
|
1267
|
+
description: |
|
|
1268
|
+
Voxel-downsampled scene point cloud metadata. The packed
|
|
1269
|
+
little-endian Float32 `[x, y, z, r, g, b, w]` blob is stored as
|
|
1270
|
+
`bin` media (`media`) — the `view-3d-scene` service inlines it
|
|
1271
|
+
as base64 for the dashboard.
|
|
1272
|
+
properties:
|
|
1273
|
+
media:
|
|
1274
|
+
type: string
|
|
1275
|
+
description: "`media.mediaStorages` `_id` of the packed blob."
|
|
1276
|
+
num_points: { type: number }
|
|
1277
|
+
voxel_size_m: { type: number }
|
|
1278
|
+
aabb_min:
|
|
1279
|
+
type: array
|
|
1280
|
+
items: { type: number }
|
|
1281
|
+
description: "Axis-aligned bounds min [x, y, z], metres."
|
|
1282
|
+
aabb_max:
|
|
1283
|
+
type: array
|
|
1284
|
+
items: { type: number }
|
|
1285
|
+
_errors:
|
|
1286
|
+
type: array
|
|
1287
|
+
items: { type: object }
|
|
1288
|
+
creator:
|
|
1289
|
+
$ref: "#/components/schemas/UserRef"
|
|
1290
|
+
editor:
|
|
1291
|
+
$ref: "#/components/schemas/UserRef"
|
|
1292
|
+
company_namespace:
|
|
1293
|
+
type: array
|
|
1294
|
+
items: { type: string }
|
|
1295
|
+
description: Tenant key. Server-injected — never accept from clients.
|
|
1296
|
+
createdAt: { type: string, format: date-time }
|
|
1297
|
+
updatedAt: { type: string, format: date-time }
|
|
1298
|
+
UserRef:
|
|
1299
|
+
type: object
|
|
1300
|
+
description: Compact actor reference (rep or admin).
|
|
1301
|
+
properties:
|
|
1302
|
+
_id: { type: string }
|
|
1303
|
+
type: { type: string, enum: [admin, rep] }
|
|
1304
|
+
name: { type: string }
|
|
1305
|
+
rep: { type: string }
|
|
1306
|
+
admin: { type: string }
|