tempest-react-sdk 0.36.0 → 0.38.0

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.
@@ -2,13 +2,15 @@ import { Mask as e, RGBImage as t } from "../types.js";
2
2
  import { SpeedTimer as n } from "../core/timing.js";
3
3
  import { Boxes as r, Masks as i, SegmentationResults as a } from "../results.js";
4
4
  import { resolveLabels as o } from "../labels.js";
5
- import { OrtSession as s } from "../core/session.js";
6
- import { loadImage as c } from "../io/image.js";
7
- import { letterbox as l, toCHW as u, toFloat32 as d, toFloat32Tensor as f } from "../preprocess/image.js";
8
- import { decodeYoloSeg as p } from "../postprocess/segmentation.js";
9
- import { VisionTask as m } from "./base.js";
5
+ import { detectionNumClasses as s, resolveInputSize as c } from "../core/graph.js";
6
+ import { modelNames as l } from "../core/metadata.js";
7
+ import { OrtSession as u } from "../core/session.js";
8
+ import { loadImage as d } from "../io/image.js";
9
+ import { letterbox as f, toCHW as p, toFloat32 as m, toFloat32Tensor as h } from "../preprocess/image.js";
10
+ import { decodeYoloSeg as g } from "../postprocess/segmentation.js";
11
+ import { VisionTask as _ } from "./base.js";
10
12
  //#region src/vision/tasks/segmenter.ts
11
- var h = class h extends m {
13
+ var v = class v extends _ {
12
14
  _head;
13
15
  _labels;
14
16
  _names;
@@ -23,9 +25,13 @@ var h = class h extends m {
23
25
  static async create(e, t = {}) {
24
26
  let n = t.head ?? "yolo-seg";
25
27
  if (n !== "yolo-seg") throw Error(`Unsupported segmenter head '${n}'. Supported: 'yolo-seg'.`);
26
- let r = await s.create(e, t), i = o(t.labels ?? "coco", { numClasses: t.numClasses }), a = {};
28
+ let r = await u.create(e, t), i = o(t.labels ?? l(r.metadata) ?? "coco", { numClasses: t.numClasses ?? s(r.outputShape) ?? void 0 }), a = {};
27
29
  for (let e = 0; e < i.length; e++) a[e] = i[e];
28
- return new h(r, n, i, a, t.inputSize ?? [640, 640], t.confThreshold ?? .25, t.iouThreshold ?? .45, t.maxDetections ?? 300, t.maskThreshold ?? .5);
30
+ return new v(r, n, i, a, c({
31
+ graphShape: r.inputShape,
32
+ requested: t.inputSize,
33
+ fallback: [640, 640]
34
+ }), t.confThreshold ?? .25, t.iouThreshold ?? .45, t.maxDetections ?? 300, t.maskThreshold ?? .5);
29
35
  }
30
36
  get head() {
31
37
  return this._head;
@@ -36,6 +42,9 @@ var h = class h extends m {
36
42
  get names() {
37
43
  return this._names;
38
44
  }
45
+ get inputSize() {
46
+ return this._inputSize;
47
+ }
39
48
  get numClasses() {
40
49
  return this._labels.length;
41
50
  }
@@ -43,35 +52,35 @@ var h = class h extends m {
43
52
  return this.predict(e, t);
44
53
  }
45
54
  async predict(e, t = {}) {
46
- let r = new n(), i = typeof e == "string" ? e : null, o = await c(e);
55
+ let r = new n(), i = typeof e == "string" ? e : null, o = await d(e);
47
56
  r.stage("load");
48
- let { tensor: s, scale: l, padLeft: u, padTop: d } = this._preprocess(o);
57
+ let { tensor: s, scale: c, padLeft: l, padTop: u } = this._preprocess(o);
49
58
  r.stage("preprocess");
50
59
  let f = await this._session.run({ [this._session.inputName]: s });
51
60
  r.stage("inference");
52
- let { perAnchor: m, prototypes: h } = this._splitOutputs(f), g = p(m.data, m.dims, h.data, h.dims, {
61
+ let { perAnchor: p, prototypes: m } = this._splitOutputs(f), h = g(p.data, p.dims, m.data, m.dims, {
53
62
  numClasses: this._labels.length,
54
63
  inputWidth: this._inputSize[0],
55
64
  inputHeight: this._inputSize[1],
56
65
  originalWidth: o.width,
57
66
  originalHeight: o.height,
58
- padLeft: u,
59
- padTop: d,
60
- scale: l,
67
+ padLeft: l,
68
+ padTop: u,
69
+ scale: c,
61
70
  confThreshold: t.confThreshold ?? this._confThreshold,
62
71
  iouThreshold: t.iouThreshold ?? this._iouThreshold,
63
72
  maxDetections: this._maxDetections,
64
73
  maskThreshold: this._maskThreshold
65
- }), _ = (t.classes === void 0 ? g : (() => {
74
+ }), _ = (t.classes === void 0 ? h : (() => {
66
75
  let e = new Set(t.classes);
67
- return g.filter((t) => e.has(t.classId));
76
+ return h.filter((t) => e.has(t.classId));
68
77
  })()).map((e) => this._buildResult(o, e.bbox, e.classId, e.confidence, e.mask)), v = [o.height, o.width], y = this._buildBoxes(_, v), b = this._buildMasks(_, v);
69
78
  return r.stage("postprocess"), [new a(y, b, _, this._names, o, v, i, r.speed())];
70
79
  }
71
80
  _preprocess(e) {
72
- let [t, n] = this._inputSize, r = l(e, t, n);
81
+ let [t, n] = this._inputSize, r = f(e, t, n);
73
82
  return {
74
- tensor: f(u(d(r.image), r.image.width, r.image.height, 3), [
83
+ tensor: h(p(m(r.image), r.image.width, r.image.height, 3), [
75
84
  1,
76
85
  3,
77
86
  r.image.height,
@@ -146,6 +155,6 @@ var h = class h extends m {
146
155
  }
147
156
  };
148
157
  //#endregion
149
- export { h as Segmenter };
158
+ export { v as Segmenter };
150
159
 
151
160
  //# sourceMappingURL=segmenter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"segmenter.js","names":[],"sources":["../../../src/vision/tasks/segmenter.ts"],"sourcesContent":["/**\n * Instance-segmentation task using YOLO seg ONNX models (v8-seg / v11-seg / ...).\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { decodeYoloSeg } from \"../postprocess/segmentation\";\nimport { letterbox, toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { Boxes, Masks, SegmentationResults } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type BoundingBox, type SegmentationResult, Mask, RGBImage } from \"../types\";\n\n/**\n * Decoder family for the segmentation head.\n *\n * - `\"yolo-seg\"`: YOLO instance-segmentation head with two outputs —\n * `[1, 4 + nc + nm, N]` per-anchor predictions plus `[1, nm, mh, mw]`\n * prototype masks. Covers YOLOv8-seg, v11-seg, v26-seg.\n *\n * The SDK does **not** auto-detect this — the caller is responsible for\n * picking a head that matches their export.\n */\nexport type SegmenterHead = \"yolo-seg\";\n\nexport interface SegmenterOptions extends OrtSessionOptions {\n /**\n * Decoder family for the segmentation head. Default `\"yolo-seg\"` covers\n * YOLOv8-seg/v11-seg/v26-seg.\n */\n readonly head?: SegmenterHead;\n /** Class label spec — see {@link resolveLabels}. Defaults to the COCO 80-class preset. */\n readonly labels?: LabelSpec;\n /** Number of classes — used to validate the supplied labels. */\n readonly numClasses?: number;\n /** Model input `[width, height]` for letterboxing. Defaults to `[640, 640]`. */\n readonly inputSize?: readonly [number, number];\n /** Default minimum class score to keep a candidate. */\n readonly confThreshold?: number;\n /** Default IoU threshold for non-maximum suppression. */\n readonly iouThreshold?: number;\n /** Maximum number of instances per image. */\n readonly maxDetections?: number;\n /** Probability cutoff applied to soft masks. Defaults to `0.5`. */\n readonly maskThreshold?: number;\n}\n\nexport interface SegmenterPredictOptions {\n readonly confThreshold?: number;\n readonly iouThreshold?: number;\n /**\n * If set, keep only instances whose `classId` is in this list.\n * Mirrors Ultralytics' `model.predict(img, classes=[0, 16])`.\n */\n readonly classes?: readonly number[];\n}\n\n/**\n * Instance segmenter for YOLO seg ONNX models (v8-seg / v11-seg / ...).\n *\n * The model is expected to expose two outputs:\n *\n * 1. `output0`: `(1, 4 + numClasses + numMaskCoefs, numAnchors)` — per-anchor\n * predictions (boxes, class scores, mask coefficients).\n * 2. `output1`: `(1, numMaskCoefs, maskH, maskW)` — prototype masks.\n *\n * `predict()` returns `Promise<SegmentationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes:\n *\n * - `boxes`: bulk numpy view (`xyxy`, `xywh`, `xyxyn`, `xywhn`, `cls`, `conf`).\n * - `masks`: per-instance binary masks cropped to each box.\n * - per-instance {@link SegmentationResult} via iteration.\n *\n * @example\n * ```typescript\n * const seg = await Segmenter.create(\"/models/yolov8n-seg.onnx\");\n * const r = (await seg.predict(\"/images/street.jpg\"))[0];\n * for (const inst of r) {\n * console.log(inst.cls, inst.conf, inst.box.xyxy);\n * }\n * ```\n */\nexport class Segmenter extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _head: SegmenterHead,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _confThreshold: number,\n private readonly _iouThreshold: number,\n private readonly _maxDetections: number,\n private readonly _maskThreshold: number,\n ) {\n super(session);\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: SegmenterOptions = {}): Promise<Segmenter> {\n const head: SegmenterHead = options.head ?? \"yolo-seg\";\n if (head !== \"yolo-seg\") {\n throw new Error(`Unsupported segmenter head '${head}'. Supported: 'yolo-seg'.`);\n }\n const session = await OrtSession.create(model, options);\n const labels = resolveLabels(options.labels ?? \"coco\", {\n numClasses: options.numClasses,\n });\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) {\n names[i] = labels[i] as string;\n }\n return new Segmenter(\n session,\n head,\n labels,\n names,\n options.inputSize ?? [640, 640],\n options.confThreshold ?? 0.25,\n options.iouThreshold ?? 0.45,\n options.maxDetections ?? 300,\n options.maskThreshold ?? 0.5,\n );\n }\n\n /** The decoder family used to interpret the model's output. */\n get head(): SegmenterHead {\n return this._head;\n }\n\n /** Class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Class id → class name dict (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /** Number of classes the model predicts. */\n get numClasses(): number {\n return this._labels.length;\n }\n\n /** Alias for {@link predict} (parity with PyTorch `nn.Module.__call__`). */\n async call(\n image: ImageInput,\n options: SegmenterPredictOptions = {},\n ): Promise<SegmentationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run instance segmentation on a single image. */\n async predict(\n image: ImageInput,\n options: SegmenterPredictOptions = {},\n ): Promise<SegmentationResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { tensor, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n timer.stage(\"inference\");\n\n const { perAnchor, prototypes } = this._splitOutputs(outputs);\n\n const decodedAll = decodeYoloSeg(\n perAnchor.data as Float32Array,\n perAnchor.dims,\n prototypes.data as Float32Array,\n prototypes.dims,\n {\n numClasses: this._labels.length,\n inputWidth: this._inputSize[0],\n inputHeight: this._inputSize[1],\n originalWidth: original.width,\n originalHeight: original.height,\n padLeft,\n padTop,\n scale,\n confThreshold: options.confThreshold ?? this._confThreshold,\n iouThreshold: options.iouThreshold ?? this._iouThreshold,\n maxDetections: this._maxDetections,\n maskThreshold: this._maskThreshold,\n },\n );\n\n const decoded =\n options.classes !== undefined\n ? (() => {\n const allowed = new Set(options.classes);\n return decodedAll.filter((d) => allowed.has(d.classId));\n })()\n : decodedAll;\n\n const detections = decoded.map((d) =>\n this._buildResult(original, d.bbox, d.classId, d.confidence, d.mask),\n );\n\n const orig: readonly [number, number] = [original.height, original.width];\n const boxes = this._buildBoxes(detections, orig);\n const masks = this._buildMasks(detections, orig);\n timer.stage(\"postprocess\");\n return [\n new SegmentationResults(\n boxes,\n masks,\n detections,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): {\n tensor: ort.Tensor;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [tw, th] = this._inputSize;\n const lb = letterbox(image, tw, th);\n const f32 = toFloat32(lb.image);\n const chw = toCHW(f32, lb.image.width, lb.image.height, 3);\n return {\n tensor: toFloat32Tensor(chw, [1, 3, lb.image.height, lb.image.width]),\n scale: lb.scale,\n padLeft: lb.padLeft,\n padTop: lb.padTop,\n };\n }\n\n private _splitOutputs(outputs: Record<string, ort.Tensor>): {\n perAnchor: ort.Tensor;\n prototypes: ort.Tensor;\n } {\n let perAnchor: ort.Tensor | undefined;\n let prototypes: ort.Tensor | undefined;\n for (const name of this._session.outputNames) {\n const t = outputs[name];\n if (t === undefined) continue;\n if (t.dims.length === 3 && perAnchor === undefined) {\n perAnchor = t;\n } else if (t.dims.length === 4 && prototypes === undefined) {\n prototypes = t;\n }\n }\n if (perAnchor === undefined || prototypes === undefined) {\n const shapes = this._session.outputNames.map(\n (n) => `${n}: ${JSON.stringify(outputs[n]?.dims ?? [])}`,\n );\n throw new Error(\n `Segmenter expected one 3-D and one 4-D output, got [${shapes.join(\", \")}].`,\n );\n }\n return { perAnchor, prototypes };\n }\n\n private _buildResult(\n original: RGBImage,\n bbox: BoundingBox,\n classId: number,\n confidence: number,\n mask: Mask,\n ): SegmentationResult {\n const [x1, y1, x2, y2] = bbox.asIntXyxy();\n const cx1 = Math.max(0, x1);\n const cy1 = Math.max(0, y1);\n const cx2 = Math.min(original.width, x2);\n const cy2 = Math.min(original.height, y2);\n\n let segmentedImage: RGBImage;\n let finalMask = mask;\n if (cx2 > cx1 && cy2 > cy1 && mask.data.length > 0) {\n const cropW = cx2 - cx1;\n const cropH = cy2 - cy1;\n const mw = Math.min(mask.width, cropW);\n const mh = Math.min(mask.height, cropH);\n const segData = new Uint8Array(mw * mh * 3);\n for (let row = 0; row < mh; row++) {\n const srcRowOffset = ((cy1 + row) * original.width + cx1) * 3;\n const dstRowOffset = row * mw * 3;\n const maskRowOffset = row * mask.width;\n for (let col = 0; col < mw; col++) {\n const m = mask.data[maskRowOffset + col];\n if (m !== 0) {\n const s = srcRowOffset + col * 3;\n const d = dstRowOffset + col * 3;\n segData[d] = original.data[s];\n segData[d + 1] = original.data[s + 1];\n segData[d + 2] = original.data[s + 2];\n }\n }\n }\n segmentedImage = new RGBImage(segData, mw, mh);\n if (mw !== mask.width || mh !== mask.height) {\n const trimmed = new Uint8Array(mw * mh);\n for (let row = 0; row < mh; row++) {\n trimmed.set(\n mask.data.subarray(row * mask.width, row * mask.width + mw),\n row * mw,\n );\n }\n finalMask = new Mask(trimmed, mw, mh);\n }\n } else {\n finalMask = new Mask(new Uint8Array(0), 0, 0);\n segmentedImage = new RGBImage(new Uint8Array(0), 0, 0);\n }\n\n const className = this._names[classId] ?? `class_${classId}`;\n\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n mask: finalMask,\n segmentedImage,\n };\n }\n\n private _buildBoxes(\n detections: readonly SegmentationResult[],\n origShape: readonly [number, number],\n ): Boxes {\n const n = detections.length;\n const xyxy = new Float32Array(n * 4);\n const cls = new Int32Array(n);\n const conf = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n const d = detections[i] as SegmentationResult;\n xyxy[i * 4] = d.bbox.x1;\n xyxy[i * 4 + 1] = d.bbox.y1;\n xyxy[i * 4 + 2] = d.bbox.x2;\n xyxy[i * 4 + 3] = d.bbox.y2;\n cls[i] = d.classId;\n conf[i] = d.confidence;\n }\n return new Boxes(xyxy, cls, conf, origShape);\n }\n\n private _buildMasks(\n detections: readonly SegmentationResult[],\n origShape: readonly [number, number],\n ): Masks {\n const xyxy = new Float32Array(detections.length * 4);\n for (let i = 0; i < detections.length; i++) {\n const d = detections[i] as SegmentationResult;\n xyxy[i * 4] = d.bbox.x1;\n xyxy[i * 4 + 1] = d.bbox.y1;\n xyxy[i * 4 + 2] = d.bbox.x2;\n xyxy[i * 4 + 3] = d.bbox.y2;\n }\n return new Masks(\n detections.map((d) => d.mask),\n xyxy,\n origShape,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"segmenter.js","names":[],"sources":["../../../src/vision/tasks/segmenter.ts"],"sourcesContent":["/**\n * Instance-segmentation task using YOLO seg ONNX models (v8-seg / v11-seg / ...).\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\n\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { detectionNumClasses, resolveInputSize } from \"../core/graph\";\nimport { modelNames } from \"../core/metadata\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { decodeYoloSeg } from \"../postprocess/segmentation\";\nimport { letterbox, toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { Boxes, Masks, SegmentationResults } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type BoundingBox, type SegmentationResult, Mask, RGBImage } from \"../types\";\n\n/**\n * Decoder family for the segmentation head.\n *\n * - `\"yolo-seg\"`: YOLO instance-segmentation head with two outputs —\n * `[1, 4 + nc + nm, N]` per-anchor predictions plus `[1, nm, mh, mw]`\n * prototype masks. Covers YOLOv8-seg, v11-seg, v26-seg.\n *\n * The SDK does **not** auto-detect this — the caller is responsible for\n * picking a head that matches their export.\n */\nexport type SegmenterHead = \"yolo-seg\";\n\nexport interface SegmenterOptions extends OrtSessionOptions {\n /**\n * Decoder family for the segmentation head. Default `\"yolo-seg\"` covers\n * YOLOv8-seg/v11-seg/v26-seg.\n */\n readonly head?: SegmenterHead;\n /** Class label spec — see {@link resolveLabels}. Defaults to the COCO 80-class preset. */\n readonly labels?: LabelSpec;\n /** Number of classes — used to validate the supplied labels. */\n readonly numClasses?: number;\n /**\n * Model input `[width, height]` in pixels for letterboxing.\n *\n * Only used when the model's graph leaves its spatial axes dynamic: a graph\n * that declares a static size always wins, since that is the only shape ONNX\n * Runtime will accept. Defaults to `[640, 640]`.\n */\n readonly inputSize?: readonly [number, number];\n /** Default minimum class score to keep a candidate. */\n readonly confThreshold?: number;\n /** Default IoU threshold for non-maximum suppression. */\n readonly iouThreshold?: number;\n /** Maximum number of instances per image. */\n readonly maxDetections?: number;\n /** Probability cutoff applied to soft masks. Defaults to `0.5`. */\n readonly maskThreshold?: number;\n}\n\nexport interface SegmenterPredictOptions {\n readonly confThreshold?: number;\n readonly iouThreshold?: number;\n /**\n * If set, keep only instances whose `classId` is in this list.\n * Mirrors Ultralytics' `model.predict(img, classes=[0, 16])`.\n */\n readonly classes?: readonly number[];\n}\n\n/**\n * Instance segmenter for YOLO seg ONNX models (v8-seg / v11-seg / ...).\n *\n * The model is expected to expose two outputs:\n *\n * 1. `output0`: `(1, 4 + numClasses + numMaskCoefs, numAnchors)` — per-anchor\n * predictions (boxes, class scores, mask coefficients).\n * 2. `output1`: `(1, numMaskCoefs, maskH, maskW)` — prototype masks.\n *\n * `predict()` returns `Promise<SegmentationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes:\n *\n * - `boxes`: bulk numpy view (`xyxy`, `xywh`, `xyxyn`, `xywhn`, `cls`, `conf`).\n * - `masks`: per-instance binary masks cropped to each box.\n * - per-instance {@link SegmentationResult} via iteration.\n *\n * @example\n * ```typescript\n * const seg = await Segmenter.create(\"/models/yolov8n-seg.onnx\");\n * const r = (await seg.predict(\"/images/street.jpg\"))[0];\n * for (const inst of r) {\n * console.log(inst.cls, inst.conf, inst.box.xyxy);\n * }\n * ```\n */\nexport class Segmenter extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _head: SegmenterHead,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _confThreshold: number,\n private readonly _iouThreshold: number,\n private readonly _maxDetections: number,\n private readonly _maskThreshold: number,\n ) {\n super(session);\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: SegmenterOptions = {}): Promise<Segmenter> {\n const head: SegmenterHead = options.head ?? \"yolo-seg\";\n if (head !== \"yolo-seg\") {\n throw new Error(`Unsupported segmenter head '${head}'. Supported: 'yolo-seg'.`);\n }\n const session = await OrtSession.create(model, options);\n const labels = resolveLabels(options.labels ?? modelNames(session.metadata) ?? \"coco\", {\n numClasses: options.numClasses ?? detectionNumClasses(session.outputShape) ?? undefined,\n });\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) {\n names[i] = labels[i] as string;\n }\n return new Segmenter(\n session,\n head,\n labels,\n names,\n resolveInputSize({\n graphShape: session.inputShape,\n requested: options.inputSize,\n fallback: [640, 640],\n }),\n options.confThreshold ?? 0.25,\n options.iouThreshold ?? 0.45,\n options.maxDetections ?? 300,\n options.maskThreshold ?? 0.5,\n );\n }\n\n /** The decoder family used to interpret the model's output. */\n get head(): SegmenterHead {\n return this._head;\n }\n\n /** Class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Class id → class name dict (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /**\n * The `[width, height]` this task preprocesses to.\n *\n * Resolved at creation time from the model's graph when it declares a static\n * input, so reading it back tells you the resolution inference really runs at\n * — not merely what was requested.\n */\n get inputSize(): readonly [number, number] {\n return this._inputSize;\n }\n\n /** Number of classes the model predicts. */\n get numClasses(): number {\n return this._labels.length;\n }\n\n /** Alias for {@link predict} (parity with PyTorch `nn.Module.__call__`). */\n async call(\n image: ImageInput,\n options: SegmenterPredictOptions = {},\n ): Promise<SegmentationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run instance segmentation on a single image. */\n async predict(\n image: ImageInput,\n options: SegmenterPredictOptions = {},\n ): Promise<SegmentationResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { tensor, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n timer.stage(\"inference\");\n\n const { perAnchor, prototypes } = this._splitOutputs(outputs);\n\n const decodedAll = decodeYoloSeg(\n perAnchor.data as Float32Array,\n perAnchor.dims,\n prototypes.data as Float32Array,\n prototypes.dims,\n {\n numClasses: this._labels.length,\n inputWidth: this._inputSize[0],\n inputHeight: this._inputSize[1],\n originalWidth: original.width,\n originalHeight: original.height,\n padLeft,\n padTop,\n scale,\n confThreshold: options.confThreshold ?? this._confThreshold,\n iouThreshold: options.iouThreshold ?? this._iouThreshold,\n maxDetections: this._maxDetections,\n maskThreshold: this._maskThreshold,\n },\n );\n\n const decoded =\n options.classes !== undefined\n ? (() => {\n const allowed = new Set(options.classes);\n return decodedAll.filter((d) => allowed.has(d.classId));\n })()\n : decodedAll;\n\n const detections = decoded.map((d) =>\n this._buildResult(original, d.bbox, d.classId, d.confidence, d.mask),\n );\n\n const orig: readonly [number, number] = [original.height, original.width];\n const boxes = this._buildBoxes(detections, orig);\n const masks = this._buildMasks(detections, orig);\n timer.stage(\"postprocess\");\n return [\n new SegmentationResults(\n boxes,\n masks,\n detections,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): {\n tensor: ort.Tensor;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [tw, th] = this._inputSize;\n const lb = letterbox(image, tw, th);\n const f32 = toFloat32(lb.image);\n const chw = toCHW(f32, lb.image.width, lb.image.height, 3);\n return {\n tensor: toFloat32Tensor(chw, [1, 3, lb.image.height, lb.image.width]),\n scale: lb.scale,\n padLeft: lb.padLeft,\n padTop: lb.padTop,\n };\n }\n\n private _splitOutputs(outputs: Record<string, ort.Tensor>): {\n perAnchor: ort.Tensor;\n prototypes: ort.Tensor;\n } {\n let perAnchor: ort.Tensor | undefined;\n let prototypes: ort.Tensor | undefined;\n for (const name of this._session.outputNames) {\n const t = outputs[name];\n if (t === undefined) continue;\n if (t.dims.length === 3 && perAnchor === undefined) {\n perAnchor = t;\n } else if (t.dims.length === 4 && prototypes === undefined) {\n prototypes = t;\n }\n }\n if (perAnchor === undefined || prototypes === undefined) {\n const shapes = this._session.outputNames.map(\n (n) => `${n}: ${JSON.stringify(outputs[n]?.dims ?? [])}`,\n );\n throw new Error(\n `Segmenter expected one 3-D and one 4-D output, got [${shapes.join(\", \")}].`,\n );\n }\n return { perAnchor, prototypes };\n }\n\n private _buildResult(\n original: RGBImage,\n bbox: BoundingBox,\n classId: number,\n confidence: number,\n mask: Mask,\n ): SegmentationResult {\n const [x1, y1, x2, y2] = bbox.asIntXyxy();\n const cx1 = Math.max(0, x1);\n const cy1 = Math.max(0, y1);\n const cx2 = Math.min(original.width, x2);\n const cy2 = Math.min(original.height, y2);\n\n let segmentedImage: RGBImage;\n let finalMask = mask;\n if (cx2 > cx1 && cy2 > cy1 && mask.data.length > 0) {\n const cropW = cx2 - cx1;\n const cropH = cy2 - cy1;\n const mw = Math.min(mask.width, cropW);\n const mh = Math.min(mask.height, cropH);\n const segData = new Uint8Array(mw * mh * 3);\n for (let row = 0; row < mh; row++) {\n const srcRowOffset = ((cy1 + row) * original.width + cx1) * 3;\n const dstRowOffset = row * mw * 3;\n const maskRowOffset = row * mask.width;\n for (let col = 0; col < mw; col++) {\n const m = mask.data[maskRowOffset + col];\n if (m !== 0) {\n const s = srcRowOffset + col * 3;\n const d = dstRowOffset + col * 3;\n segData[d] = original.data[s];\n segData[d + 1] = original.data[s + 1];\n segData[d + 2] = original.data[s + 2];\n }\n }\n }\n segmentedImage = new RGBImage(segData, mw, mh);\n if (mw !== mask.width || mh !== mask.height) {\n const trimmed = new Uint8Array(mw * mh);\n for (let row = 0; row < mh; row++) {\n trimmed.set(\n mask.data.subarray(row * mask.width, row * mask.width + mw),\n row * mw,\n );\n }\n finalMask = new Mask(trimmed, mw, mh);\n }\n } else {\n finalMask = new Mask(new Uint8Array(0), 0, 0);\n segmentedImage = new RGBImage(new Uint8Array(0), 0, 0);\n }\n\n const className = this._names[classId] ?? `class_${classId}`;\n\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n mask: finalMask,\n segmentedImage,\n };\n }\n\n private _buildBoxes(\n detections: readonly SegmentationResult[],\n origShape: readonly [number, number],\n ): Boxes {\n const n = detections.length;\n const xyxy = new Float32Array(n * 4);\n const cls = new Int32Array(n);\n const conf = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n const d = detections[i] as SegmentationResult;\n xyxy[i * 4] = d.bbox.x1;\n xyxy[i * 4 + 1] = d.bbox.y1;\n xyxy[i * 4 + 2] = d.bbox.x2;\n xyxy[i * 4 + 3] = d.bbox.y2;\n cls[i] = d.classId;\n conf[i] = d.confidence;\n }\n return new Boxes(xyxy, cls, conf, origShape);\n }\n\n private _buildMasks(\n detections: readonly SegmentationResult[],\n origShape: readonly [number, number],\n ): Masks {\n const xyxy = new Float32Array(detections.length * 4);\n for (let i = 0; i < detections.length; i++) {\n const d = detections[i] as SegmentationResult;\n xyxy[i * 4] = d.bbox.x1;\n xyxy[i * 4 + 1] = d.bbox.y1;\n xyxy[i * 4 + 2] = d.bbox.x2;\n xyxy[i * 4 + 3] = d.bbox.y2;\n }\n return new Masks(\n detections.map((d) => d.mask),\n xyxy,\n origShape,\n );\n }\n}\n"],"mappings":"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"}
package/dist/vision.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vision/use-camera-stream.cjs"),t=require("./vision/core/exceptions.cjs"),n=require("./vision/types.cjs"),r=require("./vision/core/timing.cjs"),i=require("./vision/results.cjs"),a=require("./vision/labels.cjs"),o=require("./vision/core/providers.cjs"),s=require("./vision/core/session.cjs"),c=require("./vision/io/image.cjs"),l=require("./vision/preprocess/image.cjs"),u=require("./vision/postprocess/classification.cjs"),d=require("./vision/postprocess/detection.cjs"),f=require("./vision/postprocess/segmentation.cjs"),p=require("./vision/tasks/base.cjs"),m=require("./vision/tasks/classifier.cjs"),h=require("./vision/tasks/detector.cjs"),g=require("./vision/tasks/segmenter.cjs"),_=require("./vision/index.cjs"),v=require("./vision/luminance.cjs"),y=require("./vision/use-live-luminance.cjs");exports.BoundingBox=n.BoundingBox,exports.Boxes=i.Boxes,exports.COCO_CLASSES=a.COCO_CLASSES,exports.ClassificationResults=i.ClassificationResults,exports.Classifier=m.Classifier,exports.DEFAULT_PROVIDERS=o.DEFAULT_PROVIDERS,exports.DetectionResults=i.DetectionResults,exports.Detector=h.Detector,exports.ImageLoadError=t.ImageLoadError,exports.InferenceError=t.InferenceError,exports.LUMINANCE_SAMPLE_MAX_EDGE=v.LUMINANCE_SAMPLE_MAX_EDGE,exports.LabelMapError=t.LabelMapError,exports.LowLuminanceError=v.LowLuminanceError,exports.Mask=n.Mask,exports.Masks=i.Masks,exports.ModelLoadError=t.ModelLoadError,exports.OrtSession=s.OrtSession,exports.OrtVisionError=t.OrtVisionError,exports.Probs=i.Probs,exports.ProviderNotAvailableError=t.ProviderNotAvailableError,exports.RGBImage=n.RGBImage,exports.SegmentationResults=i.SegmentationResults,exports.Segmenter=g.Segmenter,exports.SpeedTimer=r.SpeedTimer,exports.VERSION=_.VERSION,exports.VisionTask=p.VisionTask,exports.batchedNms=d.batchedNms,exports.computeImageLuminance=v.computeImageLuminance,exports.decodeYolo=d.decodeYolo,exports.decodeYoloAnchors=d.decodeYoloAnchors,exports.decodeYoloSeg=f.decodeYoloSeg,exports.decodeYoloV8=d.decodeYoloV8,exports.decodeYoloV8Anchors=d.decodeYoloV8Anchors,exports.decodeYoloV8Seg=f.decodeYoloV8Seg,exports.fromCv2=l.fromCv2,exports.isLuminanceAcceptable=v.isLuminanceAcceptable,exports.letterbox=l.letterbox,exports.loadImage=c.loadImage,exports.nms=d.nms,exports.normalize=l.normalize,exports.resize=l.resize,exports.resolveLabels=a.resolveLabels,exports.resolveProviders=o.resolveProviders,exports.softmax=u.softmax,exports.toCHW=l.toCHW,exports.toCv2=l.toCv2,exports.toFloat32=l.toFloat32,exports.toFloat32Tensor=l.toFloat32Tensor,exports.toTensor=l.toTensor,exports.topK=u.topK,exports.useCameraStream=e.useCameraStream,exports.useLiveLuminance=y.useLiveLuminance;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vision/use-camera-stream.cjs"),t=require("./vision/core/exceptions.cjs"),n=require("./vision/types.cjs"),r=require("./vision/core/timing.cjs"),i=require("./vision/results.cjs"),a=require("./vision/labels.cjs"),o=require("./vision/core/graph.cjs"),s=require("./vision/core/metadata.cjs"),c=require("./vision/core/providers.cjs"),l=require("./vision/core/session.cjs"),u=require("./vision/io/image.cjs"),d=require("./vision/preprocess/image.cjs"),f=require("./vision/postprocess/classification.cjs"),p=require("./vision/postprocess/detection.cjs"),m=require("./vision/postprocess/segmentation.cjs"),h=require("./vision/tasks/base.cjs"),g=require("./vision/tasks/classifier.cjs"),_=require("./vision/tasks/detector.cjs"),v=require("./vision/tasks/segmenter.cjs"),y=require("./vision/index.cjs"),b=require("./vision/luminance.cjs"),x=require("./vision/use-live-luminance.cjs");exports.BoundingBox=n.BoundingBox,exports.Boxes=i.Boxes,exports.COCO_CLASSES=a.COCO_CLASSES,exports.ClassificationResults=i.ClassificationResults,exports.Classifier=g.Classifier,exports.DEFAULT_PROVIDERS=c.DEFAULT_PROVIDERS,exports.DetectionResults=i.DetectionResults,exports.Detector=_.Detector,exports.ImageLoadError=t.ImageLoadError,exports.InferenceError=t.InferenceError,exports.LUMINANCE_SAMPLE_MAX_EDGE=b.LUMINANCE_SAMPLE_MAX_EDGE,exports.LabelMapError=t.LabelMapError,exports.LowLuminanceError=b.LowLuminanceError,exports.Mask=n.Mask,exports.Masks=i.Masks,exports.ModelLoadError=t.ModelLoadError,exports.OrtSession=l.OrtSession,exports.OrtVisionError=t.OrtVisionError,exports.Probs=i.Probs,exports.ProviderNotAvailableError=t.ProviderNotAvailableError,exports.RGBImage=n.RGBImage,exports.SegmentationResults=i.SegmentationResults,exports.Segmenter=v.Segmenter,exports.SpeedTimer=r.SpeedTimer,exports.VERSION=y.VERSION,exports.VisionTask=h.VisionTask,exports.batchedNms=p.batchedNms,exports.classificationNumClasses=o.classificationNumClasses,exports.computeImageLuminance=b.computeImageLuminance,exports.declaredShapesFrom=o.declaredShapesFrom,exports.decodeYolo=p.decodeYolo,exports.decodeYoloAnchors=p.decodeYoloAnchors,exports.decodeYoloSeg=m.decodeYoloSeg,exports.decodeYoloV8=p.decodeYoloV8,exports.decodeYoloV8Anchors=p.decodeYoloV8Anchors,exports.decodeYoloV8Seg=m.decodeYoloV8Seg,exports.detectionNumClasses=o.detectionNumClasses,exports.fromCv2=d.fromCv2,exports.isLuminanceAcceptable=b.isLuminanceAcceptable,exports.letterbox=d.letterbox,exports.loadImage=u.loadImage,exports.modelNames=s.modelNames,exports.nms=p.nms,exports.normalize=d.normalize,exports.readModelMetadata=s.readModelMetadata,exports.resize=d.resize,exports.resolveInputSize=o.resolveInputSize,exports.resolveLabels=a.resolveLabels,exports.resolveProviders=c.resolveProviders,exports.softmax=f.softmax,exports.spatialInputSize=o.spatialInputSize,exports.toCHW=d.toCHW,exports.toCv2=d.toCv2,exports.toFloat32=d.toFloat32,exports.toFloat32Tensor=d.toFloat32Tensor,exports.toTensor=d.toTensor,exports.topK=f.topK,exports.useCameraStream=e.useCameraStream,exports.useLiveLuminance=x.useLiveLuminance;
package/dist/vision.d.ts CHANGED
@@ -116,6 +116,16 @@ export declare type CameraStreamErrorKind = "unsupported" | "permission-denied"
116
116
  /** Lifecycle status of the camera stream. */
117
117
  export declare type CameraStreamStatus = "idle" | "loading" | "ready" | "error";
118
118
 
119
+ /**
120
+ * Infer how many classes a classification head emits.
121
+ *
122
+ * A classifier declares `(B, nc)`, so the count is the last static axis.
123
+ *
124
+ * @param shape Declared shape of the model's first output.
125
+ * @returns The class count, or `null` when the last axis is dynamic or absent.
126
+ */
127
+ export declare function classificationNumClasses(shape: DeclaredShape): number | null;
128
+
119
129
  /**
120
130
  * Output of an image classification inference.
121
131
  */
@@ -191,11 +201,19 @@ export declare class Classifier extends VisionTask {
191
201
  private readonly _applySoftmax;
192
202
  private constructor();
193
203
  /** Load the model and resolve labels. */
194
- static create(model: ModelSource, options: ClassifierOptions): Promise<Classifier>;
204
+ static create(model: ModelSource, options?: ClassifierOptions): Promise<Classifier>;
195
205
  /** Class labels indexed by class id. */
196
206
  get labels(): readonly string[];
197
207
  /** Class id → class name dict (matches Ultralytics' `model.names`). */
198
208
  get names(): Readonly<Record<number, string>>;
209
+ /**
210
+ * The `[width, height]` this task preprocesses to.
211
+ *
212
+ * Resolved at creation time from the model's graph when it declares a static
213
+ * input, so reading it back tells you the resolution inference really runs at
214
+ * — not merely what was requested.
215
+ */
216
+ get inputSize(): readonly [number, number];
199
217
  /** Number of classes the model can predict. */
200
218
  get numClasses(): number;
201
219
  /** Alias for {@link predict} (parity with PyTorch `nn.Module.__call__`). */
@@ -207,14 +225,29 @@ export declare class Classifier extends VisionTask {
207
225
  }
208
226
 
209
227
  export declare interface ClassifierOptions extends OrtSessionOptions {
210
- /** Class label spec — see {@link resolveLabels}. */
211
- readonly labels: LabelSpec;
212
228
  /**
213
- * Number of classes the model can predict. Required when `labels` is `null`
214
- * or when you want to validate that the supplied labels match the model.
229
+ * Class label spec see {@link resolveLabels}.
230
+ *
231
+ * Optional: when omitted, the names the export baked into the model are used
232
+ * (Ultralytics writes them as `names` in the metadata map). Only when the
233
+ * model carries none does this fall back to generated `class_<id>` labels.
234
+ * Passing a spec always wins, for a model whose names are wrong or absent.
235
+ */
236
+ readonly labels?: LabelSpec;
237
+ /**
238
+ * Number of classes the model can predict.
239
+ *
240
+ * Optional: inferred from the classification head's declared output shape
241
+ * `(B, nc)`. Pass it to validate that the supplied labels match the model.
215
242
  */
216
243
  readonly numClasses?: number;
217
- /** Model input `[width, height]` in pixels. Defaults to `[224, 224]`. */
244
+ /**
245
+ * Model input `[width, height]` in pixels.
246
+ *
247
+ * Only used when the model's graph leaves its spatial axes dynamic: a graph
248
+ * that declares a static size always wins, since that is the only shape ONNX
249
+ * Runtime will accept. Defaults to `[224, 224]`.
250
+ */
218
251
  readonly inputSize?: readonly [number, number];
219
252
  /** Per-channel RGB mean used for normalization. Defaults to ImageNet. */
220
253
  readonly mean?: readonly [number, number, number];
@@ -277,6 +310,26 @@ export declare const COCO_CLASSES: readonly string[];
277
310
  */
278
311
  export declare function computeImageLuminance(source: LuminanceSource, reusableCanvas?: HTMLCanvasElement): number;
279
312
 
313
+ /**
314
+ * One declared dimension: a number when the graph pins it, `null` when the
315
+ * dimension is symbolic (dynamic).
316
+ */
317
+ export declare type DeclaredDim = number | null;
318
+
319
+ /** A declared input/output shape, dynamic axes appearing as `null`. */
320
+ export declare type DeclaredShape = readonly DeclaredDim[];
321
+
322
+ /**
323
+ * Convert ORT value metadata into declared shapes.
324
+ *
325
+ * @param metadata Metadata as reported by `InferenceSession.inputMetadata`, or
326
+ * `undefined` on ORT builds that predate it (added in onnxruntime 1.21).
327
+ * @returns One shape per value, in declaration order. Non-tensor values and
328
+ * builds without metadata yield empty shapes, which read as "nothing
329
+ * declared" everywhere downstream.
330
+ */
331
+ export declare function declaredShapesFrom(metadata: readonly ort.InferenceSession.ValueMetadata[] | undefined): readonly DeclaredShape[];
332
+
280
333
  export declare interface DecodedAnchors {
281
334
  /** Indices into the original `numAnchors` axis, in descending confidence order. */
282
335
  readonly anchorIndices: Int32Array;
@@ -414,6 +467,19 @@ export declare type DecodeYoloV8SegOptions = DecodeYoloSegOptions;
414
467
  */
415
468
  export declare const DEFAULT_PROVIDERS: readonly string[];
416
469
 
470
+ /**
471
+ * Infer how many classes a YOLO detection/segmentation head emits.
472
+ *
473
+ * Such a head declares `(B, 4 + nc, N)` — four box coordinates stacked above one
474
+ * score per class, over `N` candidate anchors. `N` is in the thousands and the
475
+ * batch is 1, so the channel axis is the smallest static axis above 1.
476
+ *
477
+ * @param shape Declared shape of the model's first output.
478
+ * @returns The class count, or `null` when the shape leaves it undeterminable —
479
+ * fully dynamic, or too small to hold boxes plus at least one class.
480
+ */
481
+ export declare function detectionNumClasses(shape: DeclaredShape): number | null;
482
+
417
483
  /**
418
484
  * Single detected object produced by an object-detection model.
419
485
  */
@@ -504,6 +570,14 @@ export declare class Detector extends VisionTask {
504
570
  get labels(): readonly string[];
505
571
  /** Class id → class name dict (matches Ultralytics' `model.names`). */
506
572
  get names(): Readonly<Record<number, string>>;
573
+ /**
574
+ * The `[width, height]` this task preprocesses to.
575
+ *
576
+ * Resolved at creation time from the model's graph when it declares a static
577
+ * input, so reading it back tells you the resolution inference really runs at
578
+ * — not merely what was requested.
579
+ */
580
+ get inputSize(): readonly [number, number];
507
581
  /** Number of classes the model predicts. */
508
582
  get numClasses(): number;
509
583
  /**
@@ -548,7 +622,13 @@ export declare interface DetectorOptions extends OrtSessionOptions {
548
622
  readonly labels?: LabelSpec;
549
623
  /** Number of classes — used to validate the supplied labels. */
550
624
  readonly numClasses?: number;
551
- /** Model input `[width, height]` for letterboxing. Defaults to `[640, 640]`. */
625
+ /**
626
+ * Model input `[width, height]` in pixels for letterboxing.
627
+ *
628
+ * Only used when the model's graph leaves its spatial axes dynamic: a graph
629
+ * that declares a static size always wins, since that is the only shape ONNX
630
+ * Runtime will accept. Defaults to `[640, 640]`.
631
+ */
552
632
  readonly inputSize?: readonly [number, number];
553
633
  /** Default minimum class score to keep a candidate. */
554
634
  readonly confThreshold?: number;
@@ -737,6 +817,21 @@ export declare interface LetterboxResult {
737
817
  export declare class ModelLoadError extends OrtVisionError {
738
818
  }
739
819
 
820
+ /**
821
+ * Read the class names an export baked into the model metadata.
822
+ *
823
+ * Ultralytics writes `names` as the Python `repr` of a `dict[int, str]` — e.g.
824
+ * `"{0: 'deworm', 1: 'not_deworm'}"`. The value is parsed structurally (never
825
+ * evaluated), and anything unparseable, non-`dict`, or not keyed by contiguous
826
+ * integers from zero is rejected whole rather than half-applied: a partial name
827
+ * map would silently mislabel predictions.
828
+ *
829
+ * @param metadata A model's custom metadata map.
830
+ * @returns Class names in class-id order, or `null` when the model carries no
831
+ * usable `names` entry.
832
+ */
833
+ export declare function modelNames(metadata: Readonly<Record<string, string>> | undefined): readonly string[] | null;
834
+
740
835
  /** Anything `InferenceSession.create` accepts. */
741
836
  export declare type ModelSource = string | ArrayBufferLike | Uint8Array;
742
837
 
@@ -763,18 +858,21 @@ export declare interface LetterboxResult {
763
858
  /**
764
859
  * Wrap an ONNX Runtime Web `InferenceSession` with convenient metadata access.
765
860
  *
766
- * The wrapper exposes input/output names, manages execution-provider
767
- * selection, and provides a typed {@link OrtSession.run} method.
861
+ * The wrapper exposes input/output names and the shapes the graph declares,
862
+ * manages execution-provider selection, provides a typed {@link OrtSession.run}
863
+ * method, and releases the native session through {@link OrtSession.release}.
768
864
  */
769
865
  export declare class OrtSession {
770
866
  private readonly _session;
771
867
  readonly providers: readonly string[];
868
+ private readonly _metadata;
772
869
  private constructor();
773
870
  /**
774
871
  * Load an ONNX model into an ORT inference session.
775
872
  *
776
- * @param model Either a URL string fetched by ORT, or a `Uint8Array`/`ArrayBuffer` containing the model bytes.
777
- * @param options Provider list and pass-through `SessionOptions`.
873
+ * @param model Either a URL string, or a `Uint8Array`/`ArrayBuffer` containing the model bytes.
874
+ * @param options Provider list, pass-through `SessionOptions`, and whether to
875
+ * read the model's metadata map (see {@link OrtSessionOptions.readMetadata}).
778
876
  * @throws {@link ModelLoadError} if the model cannot be loaded.
779
877
  */
780
878
  static create(model: ModelSource, options?: OrtSessionOptions): Promise<OrtSession>;
@@ -784,6 +882,51 @@ export declare interface LetterboxResult {
784
882
  get inputName(): string;
785
883
  /** Names of the model's outputs, in declaration order. */
786
884
  get outputNames(): readonly string[];
885
+ /**
886
+ * Shapes the graph declares for its inputs, in declaration order.
887
+ *
888
+ * Dynamic (symbolic) axes appear as `null`. Empty shapes mean the runtime
889
+ * reported no metadata — either a non-tensor input, or an `onnxruntime-web`
890
+ * older than 1.21, which predates input metadata.
891
+ */
892
+ get inputShapes(): readonly DeclaredShape[];
893
+ /**
894
+ * Shape the graph declares for its first input, dynamic axes as `null`.
895
+ *
896
+ * Empty when the runtime reports no metadata for it.
897
+ */
898
+ get inputShape(): DeclaredShape;
899
+ /**
900
+ * Shapes the graph declares for its outputs, in declaration order.
901
+ *
902
+ * Dynamic (symbolic) axes appear as `null`. Reading them is how a task can
903
+ * tell how many classes a head emits without being told.
904
+ */
905
+ get outputShapes(): readonly DeclaredShape[];
906
+ /**
907
+ * Shape the graph declares for its first output, dynamic axes as `null`.
908
+ *
909
+ * Empty when the runtime reports no metadata for it.
910
+ */
911
+ get outputShape(): DeclaredShape;
912
+ /**
913
+ * The model's custom metadata map — `names`, `task`, `imgsz`, ... for an
914
+ * Ultralytics export.
915
+ *
916
+ * Read from the model's bytes at load time, since the runtime does not expose
917
+ * it. Empty when the session was created with `readMetadata: false`, from a
918
+ * URL that could not be fetched here, or from a model carrying no metadata.
919
+ */
920
+ get metadata(): Readonly<Record<string, string>>;
921
+ /**
922
+ * Release the native session and free its memory.
923
+ *
924
+ * Call it when a session is discarded while the page lives on — rebuilding a
925
+ * task at a different input size, swapping in a newer model. A failure from
926
+ * the runtime is ignored: a session being torn down has nothing left to fail
927
+ * at, and the caller is already moving on.
928
+ */
929
+ release(): Promise<void>;
787
930
  /** The underlying `onnxruntime-web` session, for advanced use cases. */
788
931
  get raw(): ort.InferenceSession;
789
932
  /**
@@ -800,6 +943,17 @@ export declare interface LetterboxResult {
800
943
  readonly providers?: readonly string[];
801
944
  /** Optional ORT session options forwarded to `InferenceSession.create`. */
802
945
  readonly sessionOptions?: ort.InferenceSession.SessionOptions;
946
+ /**
947
+ * Whether to read the model's custom metadata map (`names`, `task`, `imgsz`).
948
+ * Defaults to `true`.
949
+ *
950
+ * The runtime does not expose that map, so it is read from the file itself —
951
+ * which means a URL model is fetched here and handed to ORT as bytes instead
952
+ * of letting ORT fetch it. That is the same single download either way, and
953
+ * it is what lets a task resolve its labels off the model. Set to `false` to
954
+ * keep the URL path untouched and leave {@link OrtSession.metadata} empty.
955
+ */
956
+ readonly readMetadata?: boolean;
803
957
  }
804
958
 
805
959
  /**
@@ -840,9 +994,57 @@ export declare interface LetterboxResult {
840
994
  export declare class ProviderNotAvailableError extends OrtVisionError {
841
995
  }
842
996
 
997
+ /**
998
+ * Read the metadata an exporter baked into a `.onnx` file.
999
+ *
1000
+ * `onnxruntime-web` exposes input/output metadata but **not** the model's
1001
+ * custom metadata map, which is where Ultralytics writes `names`, `task` and
1002
+ * `imgsz`. The Python SDK gets it for free from
1003
+ * `InferenceSession.get_modelmeta().custom_metadata_map`; in the browser the
1004
+ * only way to the same information is to read it out of the file, so this
1005
+ * module walks just enough of the ModelProto wire format to collect
1006
+ * `metadata_props`.
1007
+ *
1008
+ * It never throws and never allocates unbounded: a truncated, hostile or
1009
+ * simply unexpected file yields an empty map, and every caller treats that as
1010
+ * "the model says nothing", falling back to what it was given.
1011
+ */
1012
+ /**
1013
+ * Collect a model's custom metadata map straight out of its bytes.
1014
+ *
1015
+ * @param model The `.onnx` file contents.
1016
+ * @returns Key/value metadata — `names`, `task`, `imgsz`, ... for an
1017
+ * Ultralytics export — or an empty object when the file carries none or
1018
+ * cannot be walked.
1019
+ */
1020
+ export declare function readModelMetadata(model: Uint8Array | ArrayBufferLike): Readonly<Record<string, string>>;
1021
+
843
1022
  /** Resize an image to `(targetWidth, targetHeight)` using high-quality canvas resampling. */
844
1023
  export declare function resize(image: RGBImage, targetWidth: number, targetHeight: number): RGBImage;
845
1024
 
1025
+ /**
1026
+ * Decide the input size a task will preprocess to.
1027
+ *
1028
+ * Precedence is graph → caller → fallback. The graph wins over an explicit
1029
+ * `inputSize` because a static shape is not a preference, it is what ORT will
1030
+ * accept: honoring the caller there would only turn a fixable mismatch into a
1031
+ * failed run. A disagreement is a configuration bug in the caller, so it is
1032
+ * reported through `console.warn` instead of being swallowed.
1033
+ *
1034
+ * @param options Graph shape, requested size and per-task fallback.
1035
+ * @returns The `[width, height]` to preprocess to.
1036
+ */
1037
+ export declare function resolveInputSize(options: ResolveInputSizeOptions): readonly [number, number];
1038
+
1039
+ export declare interface ResolveInputSizeOptions {
1040
+ /** Declared shape of the model's image input, from {@link declaredShapesFrom}. */
1041
+ readonly graphShape?: DeclaredShape;
1042
+ /** Size the caller asked for, if any. */
1043
+ readonly requested?: readonly [number, number];
1044
+ /** Size to use when neither the graph nor the caller pins one. */
1045
+ readonly fallback: readonly [number, number];
1046
+ }
1047
+
846
1048
  /**
847
1049
  * Resolve a labels specification into an ordered array of class names.
848
1050
  *
@@ -991,6 +1193,14 @@ export declare interface LetterboxResult {
991
1193
  get labels(): readonly string[];
992
1194
  /** Class id → class name dict (matches Ultralytics' `model.names`). */
993
1195
  get names(): Readonly<Record<number, string>>;
1196
+ /**
1197
+ * The `[width, height]` this task preprocesses to.
1198
+ *
1199
+ * Resolved at creation time from the model's graph when it declares a static
1200
+ * input, so reading it back tells you the resolution inference really runs at
1201
+ * — not merely what was requested.
1202
+ */
1203
+ get inputSize(): readonly [number, number];
994
1204
  /** Number of classes the model predicts. */
995
1205
  get numClasses(): number;
996
1206
  /** Alias for {@link predict} (parity with PyTorch `nn.Module.__call__`). */
@@ -1026,7 +1236,13 @@ export declare interface LetterboxResult {
1026
1236
  readonly labels?: LabelSpec;
1027
1237
  /** Number of classes — used to validate the supplied labels. */
1028
1238
  readonly numClasses?: number;
1029
- /** Model input `[width, height]` for letterboxing. Defaults to `[640, 640]`. */
1239
+ /**
1240
+ * Model input `[width, height]` in pixels for letterboxing.
1241
+ *
1242
+ * Only used when the model's graph leaves its spatial axes dynamic: a graph
1243
+ * that declares a static size always wins, since that is the only shape ONNX
1244
+ * Runtime will accept. Defaults to `[640, 640]`.
1245
+ */
1030
1246
  readonly inputSize?: readonly [number, number];
1031
1247
  /** Default minimum class score to keep a candidate. */
1032
1248
  readonly confThreshold?: number;
@@ -1054,6 +1270,16 @@ export declare interface LetterboxResult {
1054
1270
  /** Apply numerically-stable softmax to a 1-D vector of logits. */
1055
1271
  export declare function softmax(logits: Float32Array | readonly number[]): Float32Array;
1056
1272
 
1273
+ /**
1274
+ * Read the spatial input size out of a declared NCHW shape.
1275
+ *
1276
+ * @param shape The declared shape of the model's image input.
1277
+ * @returns `[width, height]` in pixels, or `null` when the shape is not 4D or
1278
+ * leaves either spatial axis dynamic — in which case the model accepts more
1279
+ * than one resolution and there is nothing to correct.
1280
+ */
1281
+ export declare function spatialInputSize(shape: DeclaredShape): readonly [number, number] | null;
1282
+
1057
1283
  /**
1058
1284
  * Per-stage timing for a single `predict()` call.
1059
1285
  *
package/dist/vision.js CHANGED
@@ -4,18 +4,20 @@ import { BoundingBox as s, Mask as c, RGBImage as l } from "./vision/types.js";
4
4
  import { SpeedTimer as u } from "./vision/core/timing.js";
5
5
  import { Boxes as d, ClassificationResults as f, DetectionResults as p, Masks as m, Probs as h, SegmentationResults as g } from "./vision/results.js";
6
6
  import { COCO_CLASSES as _, resolveLabels as v } from "./vision/labels.js";
7
- import { DEFAULT_PROVIDERS as y, resolveProviders as b } from "./vision/core/providers.js";
8
- import { OrtSession as x } from "./vision/core/session.js";
9
- import { loadImage as S } from "./vision/io/image.js";
10
- import { fromCv2 as C, letterbox as w, normalize as T, resize as E, toCHW as D, toCv2 as O, toFloat32 as k, toFloat32Tensor as A, toTensor as j } from "./vision/preprocess/image.js";
11
- import { softmax as M, topK as N } from "./vision/postprocess/classification.js";
12
- import { batchedNms as P, decodeYolo as F, decodeYoloAnchors as I, decodeYoloV8 as L, decodeYoloV8Anchors as R, nms as z } from "./vision/postprocess/detection.js";
13
- import { decodeYoloSeg as B, decodeYoloV8Seg as V } from "./vision/postprocess/segmentation.js";
14
- import { VisionTask as H } from "./vision/tasks/base.js";
15
- import { Classifier as U } from "./vision/tasks/classifier.js";
16
- import { Detector as W } from "./vision/tasks/detector.js";
17
- import { Segmenter as G } from "./vision/tasks/segmenter.js";
18
- import { VERSION as K } from "./vision/index.js";
19
- import { LUMINANCE_SAMPLE_MAX_EDGE as q, LowLuminanceError as J, computeImageLuminance as Y, isLuminanceAcceptable as X } from "./vision/luminance.js";
20
- import { useLiveLuminance as Z } from "./vision/use-live-luminance.js";
21
- export { s as BoundingBox, d as Boxes, _ as COCO_CLASSES, f as ClassificationResults, U as Classifier, y as DEFAULT_PROVIDERS, p as DetectionResults, W as Detector, t as ImageLoadError, n as InferenceError, q as LUMINANCE_SAMPLE_MAX_EDGE, r as LabelMapError, J as LowLuminanceError, c as Mask, m as Masks, i as ModelLoadError, x as OrtSession, a as OrtVisionError, h as Probs, o as ProviderNotAvailableError, l as RGBImage, g as SegmentationResults, G as Segmenter, u as SpeedTimer, K as VERSION, H as VisionTask, P as batchedNms, Y as computeImageLuminance, F as decodeYolo, I as decodeYoloAnchors, B as decodeYoloSeg, L as decodeYoloV8, R as decodeYoloV8Anchors, V as decodeYoloV8Seg, C as fromCv2, X as isLuminanceAcceptable, w as letterbox, S as loadImage, z as nms, T as normalize, E as resize, v as resolveLabels, b as resolveProviders, M as softmax, D as toCHW, O as toCv2, k as toFloat32, A as toFloat32Tensor, j as toTensor, N as topK, e as useCameraStream, Z as useLiveLuminance };
7
+ import { classificationNumClasses as y, declaredShapesFrom as b, detectionNumClasses as x, resolveInputSize as S, spatialInputSize as C } from "./vision/core/graph.js";
8
+ import { modelNames as w, readModelMetadata as T } from "./vision/core/metadata.js";
9
+ import { DEFAULT_PROVIDERS as E, resolveProviders as D } from "./vision/core/providers.js";
10
+ import { OrtSession as O } from "./vision/core/session.js";
11
+ import { loadImage as k } from "./vision/io/image.js";
12
+ import { fromCv2 as A, letterbox as j, normalize as M, resize as N, toCHW as P, toCv2 as F, toFloat32 as I, toFloat32Tensor as L, toTensor as R } from "./vision/preprocess/image.js";
13
+ import { softmax as z, topK as B } from "./vision/postprocess/classification.js";
14
+ import { batchedNms as V, decodeYolo as H, decodeYoloAnchors as U, decodeYoloV8 as W, decodeYoloV8Anchors as G, nms as K } from "./vision/postprocess/detection.js";
15
+ import { decodeYoloSeg as q, decodeYoloV8Seg as J } from "./vision/postprocess/segmentation.js";
16
+ import { VisionTask as Y } from "./vision/tasks/base.js";
17
+ import { Classifier as X } from "./vision/tasks/classifier.js";
18
+ import { Detector as Z } from "./vision/tasks/detector.js";
19
+ import { Segmenter as Q } from "./vision/tasks/segmenter.js";
20
+ import { VERSION as $ } from "./vision/index.js";
21
+ import { LUMINANCE_SAMPLE_MAX_EDGE as ee, LowLuminanceError as te, computeImageLuminance as ne, isLuminanceAcceptable as re } from "./vision/luminance.js";
22
+ import { useLiveLuminance as ie } from "./vision/use-live-luminance.js";
23
+ export { s as BoundingBox, d as Boxes, _ as COCO_CLASSES, f as ClassificationResults, X as Classifier, E as DEFAULT_PROVIDERS, p as DetectionResults, Z as Detector, t as ImageLoadError, n as InferenceError, ee as LUMINANCE_SAMPLE_MAX_EDGE, r as LabelMapError, te as LowLuminanceError, c as Mask, m as Masks, i as ModelLoadError, O as OrtSession, a as OrtVisionError, h as Probs, o as ProviderNotAvailableError, l as RGBImage, g as SegmentationResults, Q as Segmenter, u as SpeedTimer, $ as VERSION, Y as VisionTask, V as batchedNms, y as classificationNumClasses, ne as computeImageLuminance, b as declaredShapesFrom, H as decodeYolo, U as decodeYoloAnchors, q as decodeYoloSeg, W as decodeYoloV8, G as decodeYoloV8Anchors, J as decodeYoloV8Seg, x as detectionNumClasses, A as fromCv2, re as isLuminanceAcceptable, j as letterbox, k as loadImage, w as modelNames, K as nms, M as normalize, T as readModelMetadata, N as resize, S as resolveInputSize, v as resolveLabels, D as resolveProviders, z as softmax, C as spatialInputSize, P as toCHW, F as toCv2, I as toFloat32, L as toFloat32Tensor, R as toTensor, B as topK, e as useCameraStream, ie as useLiveLuminance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tempest-react-sdk",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
5
5
  "type": "module",
6
6
  "license": "MIT",