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,818 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: Repzo API - AI Object Detection Inference
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: |
|
|
6
|
+
**Per-task inference** runs the Ultralytics object-detection model on a
|
|
7
|
+
single `ai-object-detection-task` image and (optionally) persists the
|
|
8
|
+
predictions as a fresh `auto` annotation group on that task. It is the
|
|
9
|
+
server-side proxy in front of the detection lambda.
|
|
10
|
+
|
|
11
|
+
**Engines.** `engine` selects the detector:
|
|
12
|
+
- `trained` — the Ultralytics lambda with a trained model (the classic path).
|
|
13
|
+
- `zero_shot` — a hosted vision-language model (Qwen-VL over OpenRouter)
|
|
14
|
+
prompted with the namespace's label names as the open-vocabulary
|
|
15
|
+
detection prompt. For datasets/namespaces with NO trained model yet.
|
|
16
|
+
The VLM's reply is normalized into the exact lambda result shape, so
|
|
17
|
+
mapping/placement/analysis work unchanged; the saved annotation group
|
|
18
|
+
carries `engine: zero_shot` + `zero_shot_model`. Needs at least one
|
|
19
|
+
label in the namespace (their names are the vocabulary) — else `400`.
|
|
20
|
+
- `auto` (default) — trained when a model resolves; when the classic
|
|
21
|
+
dataset path finds nothing trained, falls back to zero-shot (when
|
|
22
|
+
configured) instead of failing. Explicit `model` / `model_version`
|
|
23
|
+
requests stay strict (no fallback).
|
|
24
|
+
|
|
25
|
+
**Model resolution (trained).** Three paths, in priority order:
|
|
26
|
+
1. `model_version` (and/or `model`) supplied in the body — used directly.
|
|
27
|
+
This **bypasses** the task's dataset/default-model requirement and is the
|
|
28
|
+
path used to place AR session frames. The lambda model id is
|
|
29
|
+
`model` if given, else the version's `model`; the YOLO class-index →
|
|
30
|
+
label fallback uses the version's `dataset_labels` order.
|
|
31
|
+
2. `model` alone — used directly as the lambda model id; the model's
|
|
32
|
+
`current_model_version` (when set) supplies the label order.
|
|
33
|
+
3. Neither — the classic path: the task's `task_dataset` must point at a
|
|
34
|
+
dataset with a `default_model` configured (else the zero-shot fallback
|
|
35
|
+
under `engine: auto`, or `400`).
|
|
36
|
+
|
|
37
|
+
**Label mapping.** Each prediction is mapped to a label by NAME
|
|
38
|
+
(case-insensitive, unique per namespace), falling back to the YOLO class
|
|
39
|
+
index into the version's `dataset_labels`. Unmapped predictions are
|
|
40
|
+
counted in `unmapped` and never become annotations.
|
|
41
|
+
|
|
42
|
+
**AR back-projection (session frames).** When the task carries `frame_meta`
|
|
43
|
+
(pose + intrinsics) and a `depth_media` blob, each detection is back-projected
|
|
44
|
+
into world coordinates: `world dims = bbox × depth × intrinsics × pose`.
|
|
45
|
+
Placed annotations get `world_position`, `world_size`, `depth_at_center`,
|
|
46
|
+
`depth_confidence`, `placement_confidence` and a `placed_box` snapshot;
|
|
47
|
+
un-placeable ones get an `ignore_reason` (`no_pose` | `no_intrinsics` |
|
|
48
|
+
`no_depth` | `empty_depth_region` | `insufficient_depth_pixels` |
|
|
49
|
+
`behind_shelf`) naming exactly why. Both are persisted so downstream
|
|
50
|
+
session analysis sees all of them — the kept/merged/ignored CONCLUSION is
|
|
51
|
+
made there, not here. For non-session tasks (no `frame_meta`) the
|
|
52
|
+
placement step is skipped. After placement the same DIMS RECLASSIFIER
|
|
53
|
+
(`config.reclassify_labels`, off by default — re-labels a detection whose
|
|
54
|
+
measured size fits a sibling label better, keeping `original_label` +
|
|
55
|
+
`reclassification_reason`) and SIZE GATE (`config.size_gate`, on by
|
|
56
|
+
default — flags `size_rejected` + `size_reject_detail` when the measured
|
|
57
|
+
size exceeds the label's expected dims beyond the allowance) run as in
|
|
58
|
+
the analysis stage.
|
|
59
|
+
|
|
60
|
+
**Explainability (XAI).** `explain: true` makes this service call the
|
|
61
|
+
dedicated DIAGNOSIS lambda (a separate function with its own memory
|
|
62
|
+
budget, so per-request inference stays lean) after the prediction. It
|
|
63
|
+
re-runs the identical forward pass with introspection hooks and returns an
|
|
64
|
+
`xai` object, passed through unchanged in this response: per-detection
|
|
65
|
+
top-k class scores ("why label A and not B"), an EigenCAM heatmap ("where
|
|
66
|
+
did the model look", an RGBA PNG whose alpha channel is the activation —
|
|
67
|
+
overlay it on the photo with an opacity slider), a TRUE gradient Grad-CAM
|
|
68
|
+
heatmap ("what evidence drove the detections" — class-discriminative,
|
|
69
|
+
computed via a gradient-enabled second pass), per-stage feature-map grids,
|
|
70
|
+
2D UMAP/t-SNE/PCA embeddings of the detections, and the model version's
|
|
71
|
+
training-time confusion-matrix image URLs. When the group is saved, the
|
|
72
|
+
XAI results are persisted on it — heatmap/Grad-CAM/feature-map images are
|
|
73
|
+
uploaded to media storage (refs + publicUrl snapshots), never inline in
|
|
74
|
+
Mongo. Trained engine only; ignored for zero-shot. Each part degrades
|
|
75
|
+
independently into `xai.notes`, and a diagnosis-lambda failure (or a
|
|
76
|
+
missing `aiObjectDetectionDiagnosisUrl` config) degrades to `xai: null` —
|
|
77
|
+
XAI never fails the prediction. When the diagnosis pass's detections do
|
|
78
|
+
not align with this response's `predictions`, the index-bearing parts
|
|
79
|
+
(`class_scores`, `gradcam.per_detection`, `embeddings.points`) are
|
|
80
|
+
dropped with a note rather than mis-attributed.
|
|
81
|
+
|
|
82
|
+
**XAI backfill (`explain_only: true`).** Explains the EXISTING detections:
|
|
83
|
+
the detector is skipped, only the diagnosis lambda runs, and the XAI is
|
|
84
|
+
grafted onto the task's already-saved unconfirmed auto group of the
|
|
85
|
+
resolved model version — annotation `_id`s and provenance
|
|
86
|
+
(`original_label`, size gating, cluster ids) survive, so stored analysis
|
|
87
|
+
ledgers keep resolving. Falls through to a normal full inference when no
|
|
88
|
+
such group exists (or when `save` is false). Rejected with `400` for the
|
|
89
|
+
zero-shot engine (including the auto→zero-shot fallback): a VLM has no
|
|
90
|
+
trained internals to diagnose, and falling through would re-detect —
|
|
91
|
+
mutating the very group the caller asked to explain. This is how the
|
|
92
|
+
dashboard's per-group Explain action computes XAI for an already-annotated
|
|
93
|
+
task without churning its detections. (Session analysis itself does NOT
|
|
94
|
+
take explain options — explaining is a per-task, per-group action.)
|
|
95
|
+
|
|
96
|
+
**Saving.** With `save: true` (default) the mapped predictions become a
|
|
97
|
+
new `auto`, unconfirmed group (`time` = now, `usable` left to its default).
|
|
98
|
+
Only the unconfirmed auto group(s) from the SAME source are replaced
|
|
99
|
+
(trained: same `model_version`; zero-shot: same VLM) — auto groups from
|
|
100
|
+
other versions and every confirmed / manual group are kept. A re-run that
|
|
101
|
+
maps zero annotations never wipes a populated same-source group (fresh
|
|
102
|
+
XAI is grafted onto it instead). The write is a compare-and-swap on
|
|
103
|
+
`updatedAt` (5 attempts); losing every attempt answers `saved: false`.
|
|
104
|
+
`editor`, `edit_time` and `annotated` are restamped like a task update.
|
|
105
|
+
|
|
106
|
+
**Who calls it.** Admins / reps with a valid JWT or api-key. The tenant
|
|
107
|
+
key (`company_namespace`) is taken from the caller's token, not the body,
|
|
108
|
+
and the caller's own credential is forwarded to the lambdas. When
|
|
109
|
+
predictions are saved, the service emits `update-object-detection-task`
|
|
110
|
+
so the review canvas live-updates.
|
|
111
|
+
|
|
112
|
+
**Only `create` (POST) is allowed.** `find`, `get`, `update`, `patch`, and
|
|
113
|
+
`remove` all reject with `400`. Cross-frame fusion lives in
|
|
114
|
+
`ai-object-detection-session-analysis`. There is no stored document for
|
|
115
|
+
this service — the response is computed per call.
|
|
116
|
+
servers:
|
|
117
|
+
- url: https://sv.api.repzo.me
|
|
118
|
+
security:
|
|
119
|
+
- ApiKeyAuth: []
|
|
120
|
+
- JwtAuth: []
|
|
121
|
+
paths:
|
|
122
|
+
/ai-object-detection-inference:
|
|
123
|
+
post:
|
|
124
|
+
summary: Run detection on one task
|
|
125
|
+
description: |
|
|
126
|
+
Resolves the model, calls the detection lambda, maps predictions to
|
|
127
|
+
labels, optionally back-projects each detection into world coordinates
|
|
128
|
+
for AR session frames, and (by default) saves them as a fresh `auto`
|
|
129
|
+
annotation group on the task.
|
|
130
|
+
operationId: createAiObjectDetectionInference
|
|
131
|
+
requestBody:
|
|
132
|
+
required: true
|
|
133
|
+
content:
|
|
134
|
+
application/json:
|
|
135
|
+
schema:
|
|
136
|
+
$ref: "#/components/schemas/InferenceRequest"
|
|
137
|
+
responses:
|
|
138
|
+
"201":
|
|
139
|
+
description: The inference result, including mapped annotations and counts.
|
|
140
|
+
content:
|
|
141
|
+
application/json:
|
|
142
|
+
schema:
|
|
143
|
+
$ref: "#/components/schemas/InferenceResult"
|
|
144
|
+
"400":
|
|
145
|
+
description: |
|
|
146
|
+
`task_id` missing; the task has no media / public URL; the given
|
|
147
|
+
`model_version` / `model` was not found in the namespace; the task
|
|
148
|
+
is not assigned to a dataset with a default model (and no zero-shot
|
|
149
|
+
fallback applies); `explain_only` with the zero-shot engine; an
|
|
150
|
+
unsupported `zero_shot_model`; or zero-shot requested with no labels
|
|
151
|
+
in the namespace.
|
|
152
|
+
"404":
|
|
153
|
+
description: The task was not found in the caller's namespace.
|
|
154
|
+
"500":
|
|
155
|
+
description: The detection / zero-shot request failed, or the inference URL is not configured.
|
|
156
|
+
components:
|
|
157
|
+
securitySchemes:
|
|
158
|
+
ApiKeyAuth:
|
|
159
|
+
type: apiKey
|
|
160
|
+
in: header
|
|
161
|
+
name: api-key
|
|
162
|
+
description: |
|
|
163
|
+
Server-issued API key. Also accepted via the `x-api-key` header or the
|
|
164
|
+
`?apiKey=` query parameter as fallbacks.
|
|
165
|
+
JwtAuth:
|
|
166
|
+
type: apiKey
|
|
167
|
+
in: header
|
|
168
|
+
name: Authorization
|
|
169
|
+
description: |
|
|
170
|
+
Raw JWT in the `Authorization` header — **no `Bearer ` prefix**.
|
|
171
|
+
Obtained from `POST /authenticate` (admin / rep / client login).
|
|
172
|
+
schemas:
|
|
173
|
+
InferenceRequest:
|
|
174
|
+
type: object
|
|
175
|
+
required:
|
|
176
|
+
- task_id
|
|
177
|
+
properties:
|
|
178
|
+
task_id:
|
|
179
|
+
type: string
|
|
180
|
+
description: The `ai-object-detection-task` to infer on.
|
|
181
|
+
model_version:
|
|
182
|
+
type: string
|
|
183
|
+
description: |
|
|
184
|
+
`ai-object-detection-model-version` _id. When set, resolves the
|
|
185
|
+
lambda model + label order directly and bypasses the dataset
|
|
186
|
+
requirement.
|
|
187
|
+
model:
|
|
188
|
+
type: string
|
|
189
|
+
description: |
|
|
190
|
+
Object-detection model `_id` to infer with. Used directly as the
|
|
191
|
+
lambda model id when given.
|
|
192
|
+
engine:
|
|
193
|
+
type: string
|
|
194
|
+
enum: [auto, trained, zero_shot]
|
|
195
|
+
default: auto
|
|
196
|
+
description: |
|
|
197
|
+
Detector selection — see the intro. `zero_shot` needs no trained
|
|
198
|
+
model; `auto` falls back to zero-shot only when the classic dataset
|
|
199
|
+
path resolves nothing trained.
|
|
200
|
+
zero_shot_model:
|
|
201
|
+
type: string
|
|
202
|
+
enum:
|
|
203
|
+
- qwen/qwen3-vl-8b-instruct
|
|
204
|
+
- qwen/qwen3-vl-32b-instruct
|
|
205
|
+
- qwen/qwen3-vl-235b-a22b-instruct
|
|
206
|
+
- qwen/qwen2.5-vl-72b-instruct
|
|
207
|
+
description: |
|
|
208
|
+
Zero-shot VLM override. Defaults to the server-configured model,
|
|
209
|
+
else `qwen/qwen3-vl-8b-instruct`. Unsupported names are rejected
|
|
210
|
+
with `400`.
|
|
211
|
+
conf:
|
|
212
|
+
type: number
|
|
213
|
+
description: |
|
|
214
|
+
Detection confidence threshold. Defaults to the model's
|
|
215
|
+
`predict_settings[0].conf`, else `0.25`.
|
|
216
|
+
example: 0.25
|
|
217
|
+
iou:
|
|
218
|
+
type: number
|
|
219
|
+
minimum: 0
|
|
220
|
+
maximum: 1
|
|
221
|
+
description: |
|
|
222
|
+
NMS intersection-over-union threshold. Defaults to the model's
|
|
223
|
+
`predict_settings[0].iou`, else `0.7`.
|
|
224
|
+
example: 0.45
|
|
225
|
+
agnostic_nms:
|
|
226
|
+
type: boolean
|
|
227
|
+
description: |
|
|
228
|
+
Class-agnostic NMS. When true, non-max suppression merges
|
|
229
|
+
overlapping boxes across all classes instead of per-class. Defaults
|
|
230
|
+
to the model's `predict_settings[0].agnostic_nms`, else `false`.
|
|
231
|
+
Applies to the `trained` (YOLO) engine only; ignored for zero-shot.
|
|
232
|
+
example: true
|
|
233
|
+
save:
|
|
234
|
+
type: boolean
|
|
235
|
+
default: true
|
|
236
|
+
description: |
|
|
237
|
+
When true (default), the mapped predictions are persisted as a new
|
|
238
|
+
`auto` annotation group on the task (replacing only the unconfirmed
|
|
239
|
+
auto group from the same source).
|
|
240
|
+
explain:
|
|
241
|
+
type: boolean
|
|
242
|
+
default: false
|
|
243
|
+
description: |
|
|
244
|
+
Explainable-AI opt-in. When true (trained engine only), the
|
|
245
|
+
diagnosis lambda is called after the prediction, the response
|
|
246
|
+
gains an `xai` object and the saved annotation group persists it
|
|
247
|
+
(images as media refs + URL snapshots). Adds a second lambda
|
|
248
|
+
round-trip of latency; see the intro.
|
|
249
|
+
explain_parts:
|
|
250
|
+
type: array
|
|
251
|
+
items:
|
|
252
|
+
type: string
|
|
253
|
+
enum:
|
|
254
|
+
[
|
|
255
|
+
class_scores,
|
|
256
|
+
heatmap,
|
|
257
|
+
gradcam,
|
|
258
|
+
feature_maps,
|
|
259
|
+
embeddings,
|
|
260
|
+
confusion_matrix,
|
|
261
|
+
all,
|
|
262
|
+
]
|
|
263
|
+
description: |
|
|
264
|
+
Subset of XAI parts to compute. Omit (or send empty / `["all"]`)
|
|
265
|
+
for every part. Unknown names are dropped server-side.
|
|
266
|
+
explain_topk:
|
|
267
|
+
type: integer
|
|
268
|
+
default: 5
|
|
269
|
+
minimum: 1
|
|
270
|
+
description: Candidate classes returned per detection in `class_scores`.
|
|
271
|
+
explain_gradcam_detections:
|
|
272
|
+
type: integer
|
|
273
|
+
default: 20
|
|
274
|
+
minimum: 0
|
|
275
|
+
description: |
|
|
276
|
+
Top-K cap on the per-detection Grad-CAM maps
|
|
277
|
+
(`xai.gradcam.per_detection`), highest-confidence first. Omitted,
|
|
278
|
+
the top 20 detections get their own map — each is a cheap
|
|
279
|
+
head-only backward, and the lambda sheds lowest-confidence maps
|
|
280
|
+
only if the response exceeds its ~6 MB payload limit. 0 disables
|
|
281
|
+
per-detection maps.
|
|
282
|
+
explain_embed_method:
|
|
283
|
+
type: string
|
|
284
|
+
enum: [auto, umap, tsne, pca]
|
|
285
|
+
default: auto
|
|
286
|
+
description: |
|
|
287
|
+
2D projection for the embeddings scatter. `auto` picks by
|
|
288
|
+
detection count (UMAP from 10, t-SNE from 5, PCA below); an
|
|
289
|
+
explicit choice degrades down the umap -> tsne -> pca chain when
|
|
290
|
+
infeasible, and `xai.embeddings.method` reports what actually ran.
|
|
291
|
+
explain_only:
|
|
292
|
+
type: boolean
|
|
293
|
+
default: false
|
|
294
|
+
description: |
|
|
295
|
+
XAI backfill for the EXISTING detections. When true (implies
|
|
296
|
+
`explain`; trained engine + `save` only), the detector is skipped:
|
|
297
|
+
only the diagnosis lambda runs and its XAI is grafted onto the
|
|
298
|
+
task's already-saved unconfirmed auto group of the resolved model
|
|
299
|
+
version — annotation `_id`s and provenance stay untouched, and
|
|
300
|
+
the response carries `explained_existing: true`. Falls through to
|
|
301
|
+
a normal full inference (+explain) when no such group exists.
|
|
302
|
+
Rejected with `400` when the resolved engine is zero-shot
|
|
303
|
+
(explicit `engine: zero_shot`, or `auto` falling back to it) —
|
|
304
|
+
a VLM cannot be diagnosed, and falling through would re-detect
|
|
305
|
+
and replace the group instead of explaining it.
|
|
306
|
+
config:
|
|
307
|
+
$ref: "#/components/schemas/SceneMathConfig"
|
|
308
|
+
company_namespace:
|
|
309
|
+
type: array
|
|
310
|
+
items: { type: string }
|
|
311
|
+
description: Optional tenant namespace override for SDK callers. Not read by this endpoint — the namespace is taken from the caller's token.
|
|
312
|
+
SceneMathConfig:
|
|
313
|
+
type: object
|
|
314
|
+
description: |
|
|
315
|
+
Optional scene-engine tuning for AR session frames. Omit to use the
|
|
316
|
+
defaults. Inference honours the depth-sampling knobs, the dims
|
|
317
|
+
reclassifier and the size gate; any other `SceneMathConfig` key
|
|
318
|
+
(clustering, plane merge, shelf composition, point cloud, RANSAC — the
|
|
319
|
+
session-analysis knobs) is accepted and ignored here.
|
|
320
|
+
properties:
|
|
321
|
+
min_depth_m:
|
|
322
|
+
type: number
|
|
323
|
+
default: 0.05
|
|
324
|
+
description: Ignore depth samples below this (meters).
|
|
325
|
+
max_depth_m:
|
|
326
|
+
type: number
|
|
327
|
+
default: 6
|
|
328
|
+
description: Ignore depth samples above this (meters).
|
|
329
|
+
conf_threshold:
|
|
330
|
+
type: number
|
|
331
|
+
default: 1
|
|
332
|
+
description: Minimum ARKit depth-confidence (0/1/2) to keep a pixel.
|
|
333
|
+
front_percentile:
|
|
334
|
+
type: number
|
|
335
|
+
default: 30
|
|
336
|
+
description: Percentile of bbox depths to take (front-biased).
|
|
337
|
+
shelf_tolerance_m:
|
|
338
|
+
type: number
|
|
339
|
+
default: 0.3
|
|
340
|
+
description: Depth-gate slack beyond `distance_to_shelf_m`.
|
|
341
|
+
reclassify_labels:
|
|
342
|
+
type: boolean
|
|
343
|
+
default: false
|
|
344
|
+
description: Dims reclassifier master switch — re-label a placed detection to a sibling label (same label group) whose expected dims fit its measured size better.
|
|
345
|
+
reclassify_keep_dev:
|
|
346
|
+
type: number
|
|
347
|
+
default: 0.15
|
|
348
|
+
description: Fit deviation at/below which the detected label is kept outright.
|
|
349
|
+
reclassify_target_dev:
|
|
350
|
+
type: number
|
|
351
|
+
default: 0.15
|
|
352
|
+
description: A sibling must fit within this deviation to steal the detection.
|
|
353
|
+
reclassify_min_margin:
|
|
354
|
+
type: number
|
|
355
|
+
default: 0.06
|
|
356
|
+
description: Base margin the sibling's fit must beat the original's by.
|
|
357
|
+
reclassify_conf_margin_scale:
|
|
358
|
+
type: number
|
|
359
|
+
default: 0.5
|
|
360
|
+
description: "Margin × (1 + scale · detector_conf); 0 = ignore confidence."
|
|
361
|
+
reclassify_weight_scale:
|
|
362
|
+
type: number
|
|
363
|
+
default: 1.0
|
|
364
|
+
description: Weight of the size mismatch in the fit score.
|
|
365
|
+
reclassify_weight_aspect:
|
|
366
|
+
type: number
|
|
367
|
+
default: 0.5
|
|
368
|
+
description: Weight of the aspect mismatch in the fit score.
|
|
369
|
+
reclassify_min_depth_confidence:
|
|
370
|
+
type: number
|
|
371
|
+
default: 0.5
|
|
372
|
+
description: Skip detections whose depth confidence is below this.
|
|
373
|
+
size_gate:
|
|
374
|
+
type: boolean
|
|
375
|
+
default: true
|
|
376
|
+
description: "Size gate master switch — flag detections whose measured size exceeds the label's expected dims beyond the allowance (`size_rejected` + `size_reject_detail`)."
|
|
377
|
+
size_gate_dims_allowance:
|
|
378
|
+
type: number
|
|
379
|
+
default: 0.35
|
|
380
|
+
description: "Per-axis allowance: reject when measured w or h > (1 + this) × expected."
|
|
381
|
+
size_gate_area_allowance:
|
|
382
|
+
type: number
|
|
383
|
+
default: 0.35
|
|
384
|
+
description: "Area allowance: reject when measured w·h > (1 + this) × expected area."
|
|
385
|
+
size_gate_min_depth_confidence:
|
|
386
|
+
type: number
|
|
387
|
+
default: 0.5
|
|
388
|
+
description: Skip gating detections whose depth confidence is below this.
|
|
389
|
+
additionalProperties: true
|
|
390
|
+
Box:
|
|
391
|
+
type: object
|
|
392
|
+
description: "Normalized box stored YOLO-style: `x1` = center x, `y1` = center y, `x2` = width, `y2` = height."
|
|
393
|
+
properties:
|
|
394
|
+
x1: { type: number }
|
|
395
|
+
y1: { type: number }
|
|
396
|
+
x2: { type: number }
|
|
397
|
+
y2: { type: number }
|
|
398
|
+
Annotation:
|
|
399
|
+
type: object
|
|
400
|
+
description: A mapped detection (same schema as a task annotation). World-dim fields appear only for placed AR frames.
|
|
401
|
+
properties:
|
|
402
|
+
_id:
|
|
403
|
+
type: string
|
|
404
|
+
description: "Present only on an `explained_existing` response (the stored group's annotations are echoed)."
|
|
405
|
+
box:
|
|
406
|
+
$ref: "#/components/schemas/Box"
|
|
407
|
+
confidence:
|
|
408
|
+
type: number
|
|
409
|
+
label_id:
|
|
410
|
+
type: string
|
|
411
|
+
label_state:
|
|
412
|
+
type: string
|
|
413
|
+
enum: [auto, manual]
|
|
414
|
+
placed_box:
|
|
415
|
+
allOf:
|
|
416
|
+
- $ref: "#/components/schemas/Box"
|
|
417
|
+
description: Snapshot of the box geometry placement ran for (set whenever placement was attempted on a session frame).
|
|
418
|
+
ignore_reason:
|
|
419
|
+
type: string
|
|
420
|
+
enum:
|
|
421
|
+
- no_pose
|
|
422
|
+
- no_intrinsics
|
|
423
|
+
- no_depth
|
|
424
|
+
- empty_depth_region
|
|
425
|
+
- insufficient_depth_pixels
|
|
426
|
+
- behind_shelf
|
|
427
|
+
description: |
|
|
428
|
+
Present when an AR session-frame detection could NOT be placed in
|
|
429
|
+
world coordinates — names the exact failing stage. Unset for placed
|
|
430
|
+
detections and for non-session tasks.
|
|
431
|
+
world_position:
|
|
432
|
+
type: object
|
|
433
|
+
description: Back-projected centroid in world coordinates, metres (placed only).
|
|
434
|
+
properties:
|
|
435
|
+
x: { type: number }
|
|
436
|
+
y: { type: number }
|
|
437
|
+
z: { type: number }
|
|
438
|
+
world_size:
|
|
439
|
+
type: object
|
|
440
|
+
description: "Physical front-face size, centimetres (`w` = world-horizontal, `h` = vertical; placed only)."
|
|
441
|
+
properties:
|
|
442
|
+
w: { type: number }
|
|
443
|
+
h: { type: number }
|
|
444
|
+
depth_at_center:
|
|
445
|
+
type: number
|
|
446
|
+
description: Sampled depth at the box (meters, placed only).
|
|
447
|
+
depth_confidence:
|
|
448
|
+
type: number
|
|
449
|
+
description: Normalized 0..1 depth confidence (placed only).
|
|
450
|
+
placement_confidence:
|
|
451
|
+
type: number
|
|
452
|
+
description: detection_conf × depth_conf × tracking (placed only).
|
|
453
|
+
original_label:
|
|
454
|
+
type: string
|
|
455
|
+
description: Set when the dims reclassifier moved this detection to a sibling label — the label the detector originally produced.
|
|
456
|
+
reclassification_reason:
|
|
457
|
+
type: string
|
|
458
|
+
enum: [dims_match_sibling, group_consensus, manual]
|
|
459
|
+
size_rejected:
|
|
460
|
+
type: boolean
|
|
461
|
+
description: Flagged by the size gate when the measured size exceeded the label's expected dims beyond the allowance.
|
|
462
|
+
size_reject_detail:
|
|
463
|
+
type: object
|
|
464
|
+
properties:
|
|
465
|
+
exceeded: { type: string, enum: [width, height, area] }
|
|
466
|
+
measured_w_cm: { type: number }
|
|
467
|
+
measured_h_cm: { type: number }
|
|
468
|
+
expected_w_cm: { type: number }
|
|
469
|
+
expected_h_cm: { type: number }
|
|
470
|
+
ratio:
|
|
471
|
+
type: number
|
|
472
|
+
description: measured / (expected × (1 + allowance)) for the tripped check.
|
|
473
|
+
cluster_id:
|
|
474
|
+
type: string
|
|
475
|
+
description: "Present only on an `explained_existing` response — the analysis object this stored detection was clustered into."
|
|
476
|
+
Prediction:
|
|
477
|
+
type: object
|
|
478
|
+
description: |
|
|
479
|
+
One raw detector result (`images[0].results[]` of the Ultralytics
|
|
480
|
+
lambda; zero-shot replies are normalized to the same shape with
|
|
481
|
+
`class: -1`). Passed through unchanged.
|
|
482
|
+
properties:
|
|
483
|
+
name: { type: string }
|
|
484
|
+
class: { type: integer }
|
|
485
|
+
confidence: { type: number }
|
|
486
|
+
box:
|
|
487
|
+
type: object
|
|
488
|
+
properties:
|
|
489
|
+
x1:
|
|
490
|
+
{ type: number, description: Pixel corner on the original image. }
|
|
491
|
+
y1: { type: number }
|
|
492
|
+
x2: { type: number }
|
|
493
|
+
y2: { type: number }
|
|
494
|
+
x_center: { type: number, description: Normalized 0..1. }
|
|
495
|
+
y_center: { type: number }
|
|
496
|
+
width: { type: number }
|
|
497
|
+
height: { type: number }
|
|
498
|
+
additionalProperties: true
|
|
499
|
+
InferenceResult:
|
|
500
|
+
type: object
|
|
501
|
+
properties:
|
|
502
|
+
task_id:
|
|
503
|
+
type: string
|
|
504
|
+
engine:
|
|
505
|
+
type: string
|
|
506
|
+
enum: [trained, zero_shot]
|
|
507
|
+
description: The detector that actually ran.
|
|
508
|
+
zero_shot_model:
|
|
509
|
+
type: string
|
|
510
|
+
nullable: true
|
|
511
|
+
description: The VLM used, when `engine` is `zero_shot`.
|
|
512
|
+
model:
|
|
513
|
+
type: string
|
|
514
|
+
nullable: true
|
|
515
|
+
description: "`null` for zero-shot runs."
|
|
516
|
+
model_version:
|
|
517
|
+
type: string
|
|
518
|
+
nullable: true
|
|
519
|
+
image_url:
|
|
520
|
+
type: string
|
|
521
|
+
shape:
|
|
522
|
+
type: array
|
|
523
|
+
items: { type: number }
|
|
524
|
+
nullable: true
|
|
525
|
+
description: |
|
|
526
|
+
[height, width] of the inferred image (detector-reported). `null`
|
|
527
|
+
on an `explained_existing` response — no detector ran.
|
|
528
|
+
conf:
|
|
529
|
+
type: number
|
|
530
|
+
saved:
|
|
531
|
+
type: boolean
|
|
532
|
+
explained_existing:
|
|
533
|
+
type: boolean
|
|
534
|
+
description: |
|
|
535
|
+
Present (true) when `explain_only` grafted the XAI onto the
|
|
536
|
+
existing annotation group — no detector ran: `predictions` is
|
|
537
|
+
empty, `shape` / `placed_count` / `unplaced_count` / `unmapped`
|
|
538
|
+
are null, and `annotations` echoes the existing group's
|
|
539
|
+
annotations.
|
|
540
|
+
annotations_count:
|
|
541
|
+
type: number
|
|
542
|
+
placed_count:
|
|
543
|
+
type: number
|
|
544
|
+
nullable: true
|
|
545
|
+
description: Annotations placed in world coordinates (0 for non-session tasks).
|
|
546
|
+
unplaced_count:
|
|
547
|
+
type: number
|
|
548
|
+
nullable: true
|
|
549
|
+
description: Annotations left 2D-only — each carries an `ignore_reason`.
|
|
550
|
+
unmapped:
|
|
551
|
+
type: number
|
|
552
|
+
nullable: true
|
|
553
|
+
description: Predictions that could not be mapped to a label.
|
|
554
|
+
annotations:
|
|
555
|
+
type: array
|
|
556
|
+
items:
|
|
557
|
+
$ref: "#/components/schemas/Annotation"
|
|
558
|
+
predictions:
|
|
559
|
+
type: array
|
|
560
|
+
items:
|
|
561
|
+
$ref: "#/components/schemas/Prediction"
|
|
562
|
+
description: Raw lambda predictions (passthrough).
|
|
563
|
+
xai:
|
|
564
|
+
nullable: true
|
|
565
|
+
allOf:
|
|
566
|
+
- $ref: "#/components/schemas/Xai"
|
|
567
|
+
description: |
|
|
568
|
+
Raw DIAGNOSIS-lambda Explainable-AI payload (passthrough) —
|
|
569
|
+
present only when the request set `explain` / `explain_only` on
|
|
570
|
+
the trained engine and the diagnosis lambda succeeded, else `null`.
|
|
571
|
+
task:
|
|
572
|
+
type: object
|
|
573
|
+
nullable: true
|
|
574
|
+
description: "The saved `ai-object-detection-task` document when the group (or grafted XAI) was persisted, else `null`."
|
|
575
|
+
Xai:
|
|
576
|
+
type: object
|
|
577
|
+
description: |
|
|
578
|
+
Explainable-AI results, all derived from the SAME forward pass as the
|
|
579
|
+
prediction. Any part can be missing — its failure reason is then
|
|
580
|
+
appended to `notes` (XAI never fails the prediction). `class_scores`
|
|
581
|
+
and `embeddings.points` reference detections by `index` into
|
|
582
|
+
`predictions` and are self-describing (box/name/confidence), because
|
|
583
|
+
unmapped predictions never become annotations.
|
|
584
|
+
properties:
|
|
585
|
+
parts:
|
|
586
|
+
type: array
|
|
587
|
+
items: { type: string }
|
|
588
|
+
description: The parts that were requested.
|
|
589
|
+
class_names:
|
|
590
|
+
type: object
|
|
591
|
+
additionalProperties: { type: string }
|
|
592
|
+
description: "YOLO class index (stringified) to class name, from the model weights."
|
|
593
|
+
notes:
|
|
594
|
+
type: array
|
|
595
|
+
items: { type: string }
|
|
596
|
+
description: Human-readable reasons for any part that could not be produced.
|
|
597
|
+
class_scores:
|
|
598
|
+
type: array
|
|
599
|
+
items:
|
|
600
|
+
$ref: "#/components/schemas/XaiClassScore"
|
|
601
|
+
heatmap:
|
|
602
|
+
$ref: "#/components/schemas/XaiHeatmap"
|
|
603
|
+
gradcam:
|
|
604
|
+
$ref: "#/components/schemas/XaiGradcam"
|
|
605
|
+
feature_maps:
|
|
606
|
+
type: array
|
|
607
|
+
items:
|
|
608
|
+
$ref: "#/components/schemas/XaiFeatureMap"
|
|
609
|
+
embeddings:
|
|
610
|
+
$ref: "#/components/schemas/XaiEmbeddings"
|
|
611
|
+
confusion_matrix:
|
|
612
|
+
nullable: true
|
|
613
|
+
allOf:
|
|
614
|
+
- $ref: "#/components/schemas/XaiConfusionMatrix"
|
|
615
|
+
XaiClassScore:
|
|
616
|
+
type: object
|
|
617
|
+
description: |
|
|
618
|
+
Per-detection class-score inspection — the full class-score row of the
|
|
619
|
+
pre-NMS anchor that produced the detection, top-k. A near-tie between
|
|
620
|
+
the top candidates means the model could not distinguish those classes.
|
|
621
|
+
properties:
|
|
622
|
+
index:
|
|
623
|
+
type: integer
|
|
624
|
+
description: "Position of the detection in `predictions`."
|
|
625
|
+
class:
|
|
626
|
+
type: integer
|
|
627
|
+
description: Predicted YOLO class index.
|
|
628
|
+
name:
|
|
629
|
+
type: string
|
|
630
|
+
description: Predicted class name.
|
|
631
|
+
confidence:
|
|
632
|
+
type: number
|
|
633
|
+
box:
|
|
634
|
+
type: object
|
|
635
|
+
description: Detection box in original-image pixels (xyxy).
|
|
636
|
+
properties:
|
|
637
|
+
x1: { type: number }
|
|
638
|
+
y1: { type: number }
|
|
639
|
+
x2: { type: number }
|
|
640
|
+
y2: { type: number }
|
|
641
|
+
candidates:
|
|
642
|
+
type: array
|
|
643
|
+
description: Top-k candidate classes of the winning anchor, best first.
|
|
644
|
+
items:
|
|
645
|
+
type: object
|
|
646
|
+
properties:
|
|
647
|
+
class: { type: integer }
|
|
648
|
+
name: { type: string }
|
|
649
|
+
score: { type: number }
|
|
650
|
+
match_iou:
|
|
651
|
+
type: number
|
|
652
|
+
description: |
|
|
653
|
+
IoU between the detection box and the matched pre-NMS anchor box —
|
|
654
|
+
values near 1 mean the anchor match is exact.
|
|
655
|
+
XaiHeatmap:
|
|
656
|
+
type: object
|
|
657
|
+
description: |
|
|
658
|
+
EigenCAM activation heatmap ("where did the model look") sized to the
|
|
659
|
+
original image aspect. The PNG is RGBA with alpha = activation, so the
|
|
660
|
+
dashboard overlays it directly on the photo with an opacity slider.
|
|
661
|
+
properties:
|
|
662
|
+
png_base64:
|
|
663
|
+
type: string
|
|
664
|
+
description: Base64 RGBA PNG (JET-colored, alpha-carrying).
|
|
665
|
+
method:
|
|
666
|
+
type: string
|
|
667
|
+
enum: [eigencam]
|
|
668
|
+
layers:
|
|
669
|
+
type: string
|
|
670
|
+
description: Which activations were used (detect-head inputs, averaged).
|
|
671
|
+
width: { type: integer }
|
|
672
|
+
height: { type: integer }
|
|
673
|
+
XaiGradcam:
|
|
674
|
+
type: object
|
|
675
|
+
description: |
|
|
676
|
+
TRUE gradient-based Grad-CAM ("what evidence drove the detections").
|
|
677
|
+
A second, gradient-enabled forward pass backpropagates the summed
|
|
678
|
+
confident class scores and weights each activation channel by its
|
|
679
|
+
pooled gradient — class-discriminative, unlike EigenCAM. Same overlay
|
|
680
|
+
contract: RGBA PNG with alpha = activation. Computed by the diagnosis
|
|
681
|
+
lambda only (the backward pass needs its memory budget).
|
|
682
|
+
properties:
|
|
683
|
+
png_base64:
|
|
684
|
+
type: string
|
|
685
|
+
description: Base64 RGBA PNG (JET-colored, alpha-carrying).
|
|
686
|
+
method:
|
|
687
|
+
type: string
|
|
688
|
+
enum: [gradcam]
|
|
689
|
+
layers:
|
|
690
|
+
type: string
|
|
691
|
+
target:
|
|
692
|
+
type: string
|
|
693
|
+
description: What was backpropagated (the Grad-CAM objective).
|
|
694
|
+
imgsz:
|
|
695
|
+
type: integer
|
|
696
|
+
description: |
|
|
697
|
+
Input size of the gradient pass — capped (default 960) so the
|
|
698
|
+
backward pass stays within the diagnosis lambda's memory,
|
|
699
|
+
independent of the inference imgsz.
|
|
700
|
+
width: { type: integer }
|
|
701
|
+
height: { type: integer }
|
|
702
|
+
per_detection:
|
|
703
|
+
type: array
|
|
704
|
+
description: |
|
|
705
|
+
One map PER DETECTION ("why did the model call THIS box this
|
|
706
|
+
class") — each backpropagates ONLY the matched anchor's score for
|
|
707
|
+
the detection's own class, with pixel-wise (LayerCAM-style)
|
|
708
|
+
gradient weighting so the evidence localizes to that instance
|
|
709
|
+
instead of lighting up every look-alike. The top
|
|
710
|
+
`explain_gradcam_detections` detections by confidence get a map
|
|
711
|
+
(default 20; `per_detection_note` reports truncation; the lambda
|
|
712
|
+
also sheds lowest-confidence maps if the response exceeds its
|
|
713
|
+
~6 MB payload limit). `index` references the `predictions` array
|
|
714
|
+
like class_scores.
|
|
715
|
+
items:
|
|
716
|
+
type: object
|
|
717
|
+
properties:
|
|
718
|
+
index: { type: integer }
|
|
719
|
+
class: { type: integer }
|
|
720
|
+
name: { type: string }
|
|
721
|
+
confidence: { type: number }
|
|
722
|
+
box:
|
|
723
|
+
type: object
|
|
724
|
+
description: Detection box in original-image pixels (xyxy).
|
|
725
|
+
properties:
|
|
726
|
+
x1: { type: number }
|
|
727
|
+
y1: { type: number }
|
|
728
|
+
x2: { type: number }
|
|
729
|
+
y2: { type: number }
|
|
730
|
+
match_iou:
|
|
731
|
+
type: number
|
|
732
|
+
description: |
|
|
733
|
+
IoU between the detection and the anchor of the gradient
|
|
734
|
+
pass it was matched to — low values mean the (size-capped)
|
|
735
|
+
gradient pass barely saw this object and the map is weak
|
|
736
|
+
evidence.
|
|
737
|
+
png_base64:
|
|
738
|
+
type: string
|
|
739
|
+
description: Base64 RGBA PNG (JET-colored, alpha-carrying).
|
|
740
|
+
method: { type: string, enum: [gradcam] }
|
|
741
|
+
layers: { type: string }
|
|
742
|
+
target: { type: string }
|
|
743
|
+
width: { type: integer }
|
|
744
|
+
height: { type: integer }
|
|
745
|
+
per_detection_note:
|
|
746
|
+
type: string
|
|
747
|
+
description: Present when detections were truncated to the top-K.
|
|
748
|
+
XaiFeatureMap:
|
|
749
|
+
type: object
|
|
750
|
+
description: One per-stage feature-map grid from ultralytics visualize=True.
|
|
751
|
+
properties:
|
|
752
|
+
stage:
|
|
753
|
+
type: string
|
|
754
|
+
description: "Network stage, e.g. `stage12_C2f`."
|
|
755
|
+
jpg_base64:
|
|
756
|
+
type: string
|
|
757
|
+
description: Base64 JPEG of the stage's channel grid, downscaled.
|
|
758
|
+
XaiEmbeddings:
|
|
759
|
+
type: object
|
|
760
|
+
description: |
|
|
761
|
+
Per-detection embeddings (pooled from the finest detect-head input
|
|
762
|
+
scale) projected to 2D for a scatter plot — detections the model sees
|
|
763
|
+
as similar land near each other.
|
|
764
|
+
properties:
|
|
765
|
+
method:
|
|
766
|
+
type: string
|
|
767
|
+
enum: [umap, tsne, pca, none]
|
|
768
|
+
description: |
|
|
769
|
+
The projection that actually ran. With `explain_embed_method:
|
|
770
|
+
auto` — UMAP for 10+ detections, t-SNE for 5-9, PCA for 2-4,
|
|
771
|
+
`none` for a single point; explicit requests degrade down the
|
|
772
|
+
umap -> tsne -> pca chain when infeasible.
|
|
773
|
+
requested_method:
|
|
774
|
+
type: string
|
|
775
|
+
enum: [auto, umap, tsne, pca]
|
|
776
|
+
description: What the caller asked for (differs from `method` on fallback).
|
|
777
|
+
layer:
|
|
778
|
+
type: string
|
|
779
|
+
embedding_dim:
|
|
780
|
+
type: integer
|
|
781
|
+
description: Dimensionality of the pooled embedding before projection.
|
|
782
|
+
points:
|
|
783
|
+
type: array
|
|
784
|
+
items:
|
|
785
|
+
$ref: "#/components/schemas/XaiEmbeddingPoint"
|
|
786
|
+
XaiEmbeddingPoint:
|
|
787
|
+
type: object
|
|
788
|
+
properties:
|
|
789
|
+
index:
|
|
790
|
+
type: integer
|
|
791
|
+
description: "Position of the detection in `predictions`."
|
|
792
|
+
class: { type: integer }
|
|
793
|
+
name: { type: string }
|
|
794
|
+
confidence: { type: number }
|
|
795
|
+
x:
|
|
796
|
+
type: number
|
|
797
|
+
description: Projected coordinate, min-max normalized to [0, 1].
|
|
798
|
+
y:
|
|
799
|
+
type: number
|
|
800
|
+
description: Projected coordinate, min-max normalized to [0, 1].
|
|
801
|
+
XaiConfusionMatrix:
|
|
802
|
+
type: object
|
|
803
|
+
description: |
|
|
804
|
+
Training-time confusion-matrix images of the model version (computed on
|
|
805
|
+
the validation set during training — inference has no ground truth to
|
|
806
|
+
build one from). URLs point at the stored media artifacts.
|
|
807
|
+
properties:
|
|
808
|
+
url:
|
|
809
|
+
type: string
|
|
810
|
+
nullable: true
|
|
811
|
+
description: Rendered confusion-matrix image (absolute counts).
|
|
812
|
+
normalized_url:
|
|
813
|
+
type: string
|
|
814
|
+
nullable: true
|
|
815
|
+
description: Row-normalized variant.
|
|
816
|
+
source:
|
|
817
|
+
type: string
|
|
818
|
+
enum: [training_artifacts]
|