tempest-react-sdk 0.40.0 → 0.42.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.
- package/README.md +3 -3
- package/bin/tempest.mjs +3 -3
- package/dist/br.cjs +1 -1
- package/dist/br.cjs.map +1 -1
- package/dist/br.d.ts +0 -13
- package/dist/br.js +14 -15
- package/dist/br.js.map +1 -1
- package/dist/charts/palette.cjs +1 -1
- package/dist/charts/palette.cjs.map +1 -1
- package/dist/charts/palette.js +4 -4
- package/dist/charts/palette.js.map +1 -1
- package/dist/charts.cjs +1 -1
- package/dist/charts.d.ts +0 -3
- package/dist/charts.js +8 -8
- package/dist/icons/generated/aliases.cjs +1 -1
- package/dist/icons/generated/aliases.cjs.map +1 -1
- package/dist/icons/generated/aliases.js +9 -0
- package/dist/icons/generated/aliases.js.map +1 -1
- package/dist/icons/generated/icon-names.cjs +1 -1
- package/dist/icons/generated/icon-names.cjs.map +1 -1
- package/dist/icons/generated/icon-names.js +1 -1
- package/dist/icons/generated/icon-names.js.map +1 -1
- package/dist/icons/generated/shard-a.cjs +1 -1
- package/dist/icons/generated/shard-a.cjs.map +1 -1
- package/dist/icons/generated/shard-a.js +61 -61
- package/dist/icons/generated/shard-a.js.map +1 -1
- package/dist/icons/generated/shard-b.cjs +1 -1
- package/dist/icons/generated/shard-b.cjs.map +1 -1
- package/dist/icons/generated/shard-b.js +15 -13
- package/dist/icons/generated/shard-b.js.map +1 -1
- package/dist/icons/generated/shard-e.cjs +1 -1
- package/dist/icons/generated/shard-e.cjs.map +1 -1
- package/dist/icons/generated/shard-e.js +20 -19
- package/dist/icons/generated/shard-e.js.map +1 -1
- package/dist/icons/generated/shard-f.cjs +1 -1
- package/dist/icons/generated/shard-f.cjs.map +1 -1
- package/dist/icons/generated/shard-f.js +140 -134
- package/dist/icons/generated/shard-f.js.map +1 -1
- package/dist/icons/generated/shard-h.cjs +1 -1
- package/dist/icons/generated/shard-h.cjs.map +1 -1
- package/dist/icons/generated/shard-h.js +13 -14
- package/dist/icons/generated/shard-h.js.map +1 -1
- package/dist/icons/generated/shard-l.cjs +1 -1
- package/dist/icons/generated/shard-l.cjs.map +1 -1
- package/dist/icons/generated/shard-l.js +73 -69
- package/dist/icons/generated/shard-l.js.map +1 -1
- package/dist/icons/generated/shard-m.cjs +1 -1
- package/dist/icons/generated/shard-m.cjs.map +1 -1
- package/dist/icons/generated/shard-m.js +101 -98
- package/dist/icons/generated/shard-m.js.map +1 -1
- package/dist/icons/generated/shard-p.cjs +1 -1
- package/dist/icons/generated/shard-p.cjs.map +1 -1
- package/dist/icons/generated/shard-p.js +19 -20
- package/dist/icons/generated/shard-p.js.map +1 -1
- package/dist/icons/generated/shard-r.cjs +1 -1
- package/dist/icons/generated/shard-r.cjs.map +1 -1
- package/dist/icons/generated/shard-r.js +19 -17
- package/dist/icons/generated/shard-r.js.map +1 -1
- package/dist/icons/generated/shard-s.cjs +1 -1
- package/dist/icons/generated/shard-s.cjs.map +1 -1
- package/dist/icons/generated/shard-s.js +160 -159
- package/dist/icons/generated/shard-s.js.map +1 -1
- package/dist/icons/generated/shard-u.cjs +1 -1
- package/dist/icons/generated/shard-u.cjs.map +1 -1
- package/dist/icons/generated/shard-u.js +11 -10
- package/dist/icons/generated/shard-u.js.map +1 -1
- package/dist/icons/shard-cache.cjs.map +1 -1
- package/dist/icons/shard-cache.js.map +1 -1
- package/dist/icons.d.ts +5 -5
- package/dist/imaging/encode.cjs +1 -1
- package/dist/imaging/encode.cjs.map +1 -1
- package/dist/imaging/encode.js +1 -4
- package/dist/imaging/encode.js.map +1 -1
- package/dist/imaging.cjs +1 -1
- package/dist/imaging.d.ts +0 -7
- package/dist/imaging.js +6 -6
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +8 -8
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +13 -14
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.cjs +1 -1
- package/dist/tabular.d.ts +0 -9
- package/dist/tabular.js +5 -5
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +0 -299
- package/dist/tempest-react-sdk.js +183 -188
- package/dist/vision/index.cjs +1 -1
- package/dist/vision/index.cjs.map +1 -1
- package/dist/vision/index.js +11 -11
- package/dist/vision/index.js.map +1 -1
- package/dist/vision/preprocess/pipeline.cjs +1 -1
- package/dist/vision/preprocess/pipeline.cjs.map +1 -1
- package/dist/vision/preprocess/pipeline.js +69 -3
- package/dist/vision/preprocess/pipeline.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +38 -17
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision.cjs +1 -1
- package/dist/vision.d.ts +140 -0
- package/dist/vision.js +13 -13
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +0 -25
- package/dist/vite.js +6 -6
- package/package.json +5 -5
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../core/canvas.cjs");var t=1/255,n=class{_targetWidth;_targetHeight;_fill;_target;_targetContext;_buffer;_source=null;_sourceContext=null;_bufferInUse=!1;constructor(t,n,r=[114,114,114]){if(t<=0||n<=0)throw Error(`Invalid letterbox target ${t}x${n}.`);this._targetWidth=t,this._targetHeight=n,this._fill=r,this._target=e.createCanvas(t,n),this._targetContext=e.get2DContext(this._target,{willReadFrequently:!0}),this._targetContext.imageSmoothingEnabled=!0,this._targetContext.imageSmoothingQuality=`high`,this._buffer=new Float32Array(3*n*t)}get targetSize(){return[this._targetWidth,this._targetHeight]}run(n){let r=this._targetWidth,i=this._targetHeight,a=Math.min(r/n.width,i/n.height),o=Math.round(n.width*a),s=Math.round(n.height*a),c=Math.floor((r-o)/2),l=Math.floor((i-s)/2);this._ensureSource(n.width,n.height).putImageData(e.rgbToImageData(n),0,0);let u=this._targetContext;(c>0||l>0||o!==r||s!==i)&&(u.fillStyle=`rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`,u.fillRect(0,0,r,i)),u.drawImage(this._source,0,0,n.width,n.height,c,l,o,s);let d=u.getImageData(0,0,r,i).data,f=!this._bufferInUse,p=f?this._buffer:new Float32Array(3*i*r);this._bufferInUse=!0;let m=r*i;for(let e=0,n=0;e<m;e++,n+=4)p[e]=d[n]*t,p[m+e]=d[n+1]*t,p[2*m+e]=d[n+2]*t;return{data:p,scale:a,padLeft:c,padTop:l,reused:f}}release(){this._bufferInUse=!1}_ensureSource(t,n){return(this._source===null||this._source.width!==t||this._source.height!==n)&&(this._source=e.createCanvas(t,n),this._sourceContext=e.get2DContext(this._source)),this._sourceContext}};function r(e,t){
|
|
1
|
+
const e=require("../core/canvas.cjs");var t=1/255,n=class{_targetWidth;_targetHeight;_fill;_target;_targetContext;_buffer;_source=null;_sourceContext=null;_bufferInUse=!1;constructor(t,n,r=[114,114,114]){if(t<=0||n<=0)throw Error(`Invalid letterbox target ${t}x${n}.`);this._targetWidth=t,this._targetHeight=n,this._fill=r,this._target=e.createCanvas(t,n),this._targetContext=e.get2DContext(this._target,{willReadFrequently:!0}),this._targetContext.imageSmoothingEnabled=!0,this._targetContext.imageSmoothingQuality=`high`,this._buffer=new Float32Array(3*n*t)}get targetSize(){return[this._targetWidth,this._targetHeight]}run(n){let r=this._targetWidth,i=this._targetHeight,a=Math.min(r/n.width,i/n.height),o=Math.round(n.width*a),s=Math.round(n.height*a),c=Math.floor((r-o)/2),l=Math.floor((i-s)/2);this._ensureSource(n.width,n.height).putImageData(e.rgbToImageData(n),0,0);let u=this._targetContext;(c>0||l>0||o!==r||s!==i)&&(u.fillStyle=`rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`,u.fillRect(0,0,r,i)),u.drawImage(this._source,0,0,n.width,n.height,c,l,o,s);let d=u.getImageData(0,0,r,i).data,f=!this._bufferInUse,p=f?this._buffer:new Float32Array(3*i*r);this._bufferInUse=!0;let m=r*i;for(let e=0,n=0;e<m;e++,n+=4)p[e]=d[n]*t,p[m+e]=d[n+1]*t,p[2*m+e]=d[n+2]*t;return{data:p,scale:a,padLeft:c,padTop:l,reused:f}}release(){this._bufferInUse=!1}_ensureSource(t,n){return(this._source===null||this._source.width!==t||this._source.height!==n)&&(this._source=e.createCanvas(t,n),this._sourceContext=e.get2DContext(this._source)),this._sourceContext}};function r(e,n,r,i,a,o,s=4){let c=n*r,l=i[0],u=i[1],d=i[2],f=a[0],p=a[1],m=a[2];for(let n=0,r=0;n<c;n++,r+=s)o[n]=(e[r]*t-l)/f,o[c+n]=(e[r+1]*t-u)/p,o[2*c+n]=(e[r+2]*t-d)/m}var i=class{_targetWidth;_targetHeight;_mean;_std;_buffer;_target=null;_targetContext=null;_source=null;_sourceContext=null;_bufferInUse=!1;constructor(e,t,n=[0,0,0],r=[1,1,1]){if(e<=0||t<=0)throw Error(`Invalid resize target ${e}x${t}.`);this._targetWidth=e,this._targetHeight=t,this._mean=n,this._std=r,this._buffer=new Float32Array(3*t*e)}get targetSize(){return[this._targetWidth,this._targetHeight]}run(t){let n=this._targetWidth,i=this._targetHeight,a=!this._bufferInUse,o=a?this._buffer:new Float32Array(3*i*n);if(this._bufferInUse=!0,t.width===n&&t.height===i)return r(t.data,n,i,this._mean,this._std,o,3),{data:o,reused:a};this._ensureSource(t.width,t.height).putImageData(e.rgbToImageData(t),0,0);let s=this._ensureTarget();s.drawImage(this._source,0,0,n,i);let c=s.getImageData(0,0,n,i).data;return r(c,n,i,this._mean,this._std,o),{data:o,reused:a}}release(){this._bufferInUse=!1}_ensureTarget(){return this._target===null&&(this._target=e.createCanvas(this._targetWidth,this._targetHeight),this._targetContext=e.get2DContext(this._target,{willReadFrequently:!0}),this._targetContext.imageSmoothingEnabled=!0,this._targetContext.imageSmoothingQuality=`high`),this._targetContext}_ensureSource(t,n){return(this._source===null||this._source.width!==t||this._source.height!==n)&&(this._source=e.createCanvas(t,n),this._sourceContext=e.get2DContext(this._source)),this._sourceContext}};function a(e,t,n,r=[0,0,0],a=[1,1,1]){return new i(t,n,r,a).run(e)}function o(e,t){return new Float32Array(3*t*e)}function s(e,t,r,i=[114,114,114]){return new n(t,r,i).run(e)}exports.LetterboxPipeline=n,exports.ResizePipeline=i,exports.letterboxToTensorData=s,exports.resizeToTensorData=a,exports.writePlanarFloat32=r,exports.zeroTensorData=o;
|
|
2
2
|
//# sourceMappingURL=pipeline.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.cjs","names":[],"sources":["../../../src/vision/preprocess/pipeline.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Fused letterbox → CHW float32 pipeline with reusable buffers.\n *\n * The composable primitives in {@link ./image.js} each allocate and each walk\n * their input end to end, which is the right shape for a library but the wrong\n * shape for a video loop. Chaining them costs eleven full-buffer passes and six\n * large allocations per frame:\n *\n * `getImageData` → RGBA→RGB → RGB→RGBA → `putImageData` → `drawImage` →\n * `getImageData` → RGBA→RGB → fill → row copies → `toFloat32` → `toCHW`.\n *\n * This module collapses the second half of that into two: one `drawImage` that\n * resizes *and* positions the content inside the padded target in a single\n * accelerated operation, and one loop that reads the resulting RGBA and writes\n * planar float32 directly. The intermediate `RGBImage` at target size, the fill\n * loop, the row copies and the two 4.9 MB `Float32Array` allocations all go\n * away.\n *\n * The primitives stay exactly as they are — they are public API and they are\n * what makes a custom pipeline writable. This is the fast path the built-in\n * tasks take.\n */\n\nimport {\n createCanvas,\n get2DContext,\n rgbToImageData,\n type Canvas2D,\n type Context2D,\n} from \"../core/canvas\";\nimport type { RGBImage } from \"../types\";\n\nconst INV_255 = 1 / 255;\n\n/** Geometry of a letterbox, plus the planar tensor data it produced. */\nexport interface FusedLetterboxResult {\n /** CHW float32 in `[0, 1]`, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /** Factor applied to the original image (`< 1` if downscaled). */\n readonly scale: number;\n /** Horizontal padding in pixels. */\n readonly padLeft: number;\n /** Vertical padding in pixels. */\n readonly padTop: number;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link LetterboxPipeline.run} overwrites it, so a\n * caller keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable letterbox → tensor pipeline for one target resolution.\n *\n * Holds a target canvas and an output buffer across calls, so a steady stream\n * of frames at the same size allocates nothing. Create one per task, not per\n * frame.\n */\nexport class LetterboxPipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _fill: readonly [number, number, number];\n private readonly _target: Canvas2D;\n private readonly _targetContext: Context2D;\n private readonly _buffer: Float32Array;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid letterbox target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._fill = fill;\n this._target = createCanvas(targetWidth, targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline letterboxes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Letterbox an image and write it as planar float32.\n *\n * The returned buffer is reused between calls unless a previous result is\n * still checked out — {@link release} marks it free again. A second `run`\n * before the first is released allocates a fresh buffer rather than\n * corrupting it, so concurrent `predict()` calls on one task stay correct at\n * the cost of the allocation they were trying to avoid.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedLetterboxResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const scale = Math.min(targetWidth / image.width, targetHeight / image.height);\n const scaledWidth = Math.round(image.width * scale);\n const scaledHeight = Math.round(image.height * scale);\n const padLeft = Math.floor((targetWidth - scaledWidth) / 2);\n const padTop = Math.floor((targetHeight - scaledHeight) / 2);\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._targetContext;\n if (\n padLeft > 0 ||\n padTop > 0 ||\n scaledWidth !== targetWidth ||\n scaledHeight !== targetHeight\n ) {\n context.fillStyle = `rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`;\n context.fillRect(0, 0, targetWidth, targetHeight);\n }\n context.drawImage(\n this._source as CanvasImageSource,\n 0,\n 0,\n image.width,\n image.height,\n padLeft,\n padTop,\n scaledWidth,\n scaledHeight,\n );\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n const plane = targetWidth * targetHeight;\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += 4) {\n data[pixel] = (rgba[offset] as number) * INV_255;\n data[plane + pixel] = (rgba[offset + 1] as number) * INV_255;\n data[2 * plane + pixel] = (rgba[offset + 2] as number) * INV_255;\n }\n\n return { data, scale, padLeft, padTop, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Grow the scratch source canvas to fit an image, reusing it when possible.\n *\n * A canvas is only reallocated when a frame arrives at a different size than\n * the last one, which for a camera or video source is never after the first.\n *\n * @param width Source width in pixels.\n * @param height Source height in pixels.\n */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Build a zero-filled CHW tensor payload for a warm-up run.\n *\n * @param width Model input width in pixels.\n * @param height Model input height in pixels.\n */\nexport function zeroTensorData(width: number, height: number): Float32Array {\n return new Float32Array(3 * height * width);\n}\n\n/**\n * Letterbox an image into planar float32 without keeping any state.\n *\n * The allocation-free path is {@link LetterboxPipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\nexport function letterboxToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n): FusedLetterboxResult {\n return new LetterboxPipeline(targetWidth, targetHeight, fill).run(image);\n}\n"],"mappings":"sCAiCA,IAAM,EAAU,EAAI,IA4BP,EAAb,KAA+B,CAC3B,aACA,cACA,MACA,QACA,eACA,QACA,QAAmC,KACnC,eAA2C,KAC3C,aAAuB,GAOvB,YACI,EACA,EACA,EAA0C,CAAC,IAAK,IAAK,GAAG,EAC1D,CACE,GAAI,GAAe,GAAK,GAAgB,EACpC,MAAU,MAAM,4BAA4B,EAAY,GAAG,EAAa,EAAE,EAE9E,KAAK,aAAe,EACpB,KAAK,cAAgB,EACrB,KAAK,MAAQ,EACb,KAAK,QAAU,EAAA,aAAa,EAAa,CAAY,EACrD,KAAK,eAAiB,EAAA,aAAa,KAAK,QAAS,CAAE,mBAAoB,EAAK,CAAC,EAC7E,KAAK,eAAe,sBAAwB,GAC5C,KAAK,eAAe,sBAAwB,OAC5C,KAAK,QAAU,IAAI,aAAa,EAAI,EAAe,CAAW,CAClE,CAGA,IAAI,YAAwC,CACxC,MAAO,CAAC,KAAK,aAAc,KAAK,aAAa,CACjD,CAaA,IAAI,EAAuC,CACvC,IAAM,EAAc,KAAK,aACnB,EAAe,KAAK,cACpB,EAAQ,KAAK,IAAI,EAAc,EAAM,MAAO,EAAe,EAAM,MAAM,EACvE,EAAc,KAAK,MAAM,EAAM,MAAQ,CAAK,EAC5C,EAAe,KAAK,MAAM,EAAM,OAAS,CAAK,EAC9C,EAAU,KAAK,OAAO,EAAc,GAAe,CAAC,EACpD,EAAS,KAAK,OAAO,EAAe,GAAgB,CAAC,EAG3D,KADoB,cAAc,EAAM,MAAO,EAAM,MACrD,CAAA,CAAO,aAAa,EAAA,eAAe,CAAK,EAAG,EAAG,CAAC,EAE/C,IAAM,EAAU,KAAK,gBAEjB,EAAU,GACV,EAAS,GACT,IAAgB,GAChB,IAAiB,KAEjB,EAAQ,UAAY,OAAO,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,GAC3E,EAAQ,SAAS,EAAG,EAAG,EAAa,CAAY,GAEpD,EAAQ,UACJ,KAAK,QACL,EACA,EACA,EAAM,MACN,EAAM,OACN,EACA,EACA,EACA,CACJ,EAEA,IAAM,EAAO,EAAQ,aAAa,EAAG,EAAG,EAAa,CAAY,CAAC,CAAC,KAC7D,EAAS,CAAC,KAAK,aACf,EAAO,EAAS,KAAK,QAAU,IAAI,aAAa,EAAI,EAAe,CAAW,EACpF,KAAK,aAAe,GAEpB,IAAM,EAAQ,EAAc,EAC5B,IAAK,IAAI,EAAQ,EAAG,EAAS,EAAG,EAAQ,EAAO,IAAS,GAAU,EAC9D,EAAK,GAAU,EAAK,GAAqB,EACzC,EAAK,EAAQ,GAAU,EAAK,EAAS,GAAgB,EACrD,EAAK,EAAI,EAAQ,GAAU,EAAK,EAAS,GAAgB,EAG7D,MAAO,CAAE,OAAM,QAAO,UAAS,SAAQ,QAAO,CAClD,CASA,SAAgB,CACZ,KAAK,aAAe,EACxB,CAWA,cAAsB,EAAe,EAA2B,CAS5D,OAPI,KAAK,UAAY,MACjB,KAAK,QAAQ,QAAU,GACvB,KAAK,QAAQ,SAAW,KAExB,KAAK,QAAU,EAAA,aAAa,EAAO,CAAM,EACzC,KAAK,eAAiB,EAAA,aAAa,KAAK,OAAO,GAE5C,KAAK,cAChB,CACJ,EAQA,SAAgB,EAAe,EAAe,EAA8B,CACxE,OAAO,IAAI,aAAa,EAAI,EAAS,CAAK,CAC9C,CAaA,SAAgB,EACZ,EACA,EACA,EACA,EAA0C,CAAC,IAAK,IAAK,GAAG,EACpC,CACpB,OAAO,IAAI,EAAkB,EAAa,EAAc,CAAI,CAAC,CAAC,IAAI,CAAK,CAC3E"}
|
|
1
|
+
{"version":3,"file":"pipeline.cjs","names":[],"sources":["../../../src/vision/preprocess/pipeline.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Fused letterbox → CHW float32 pipeline with reusable buffers.\n *\n * The composable primitives in {@link ./image.js} each allocate and each walk\n * their input end to end, which is the right shape for a library but the wrong\n * shape for a video loop. Chaining them costs eleven full-buffer passes and six\n * large allocations per frame:\n *\n * `getImageData` → RGBA→RGB → RGB→RGBA → `putImageData` → `drawImage` →\n * `getImageData` → RGBA→RGB → fill → row copies → `toFloat32` → `toCHW`.\n *\n * This module collapses the second half of that into two: one `drawImage` that\n * resizes *and* positions the content inside the padded target in a single\n * accelerated operation, and one loop that reads the resulting RGBA and writes\n * planar float32 directly. The intermediate `RGBImage` at target size, the fill\n * loop, the row copies and the two 4.9 MB `Float32Array` allocations all go\n * away.\n *\n * The primitives stay exactly as they are — they are public API and they are\n * what makes a custom pipeline writable. This is the fast path the built-in\n * tasks take.\n */\n\nimport {\n createCanvas,\n get2DContext,\n rgbToImageData,\n type Canvas2D,\n type Context2D,\n} from \"../core/canvas\";\nimport type { RGBImage } from \"../types\";\n\nconst INV_255 = 1 / 255;\n\n/** Geometry of a letterbox, plus the planar tensor data it produced. */\nexport interface FusedLetterboxResult {\n /** CHW float32 in `[0, 1]`, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /** Factor applied to the original image (`< 1` if downscaled). */\n readonly scale: number;\n /** Horizontal padding in pixels. */\n readonly padLeft: number;\n /** Vertical padding in pixels. */\n readonly padTop: number;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link LetterboxPipeline.run} overwrites it, so a\n * caller keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable letterbox → tensor pipeline for one target resolution.\n *\n * Holds a target canvas and an output buffer across calls, so a steady stream\n * of frames at the same size allocates nothing. Create one per task, not per\n * frame.\n */\nexport class LetterboxPipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _fill: readonly [number, number, number];\n private readonly _target: Canvas2D;\n private readonly _targetContext: Context2D;\n private readonly _buffer: Float32Array;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid letterbox target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._fill = fill;\n this._target = createCanvas(targetWidth, targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline letterboxes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Letterbox an image and write it as planar float32.\n *\n * The returned buffer is reused between calls unless a previous result is\n * still checked out — {@link release} marks it free again. A second `run`\n * before the first is released allocates a fresh buffer rather than\n * corrupting it, so concurrent `predict()` calls on one task stay correct at\n * the cost of the allocation they were trying to avoid.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedLetterboxResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const scale = Math.min(targetWidth / image.width, targetHeight / image.height);\n const scaledWidth = Math.round(image.width * scale);\n const scaledHeight = Math.round(image.height * scale);\n const padLeft = Math.floor((targetWidth - scaledWidth) / 2);\n const padTop = Math.floor((targetHeight - scaledHeight) / 2);\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._targetContext;\n if (\n padLeft > 0 ||\n padTop > 0 ||\n scaledWidth !== targetWidth ||\n scaledHeight !== targetHeight\n ) {\n context.fillStyle = `rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`;\n context.fillRect(0, 0, targetWidth, targetHeight);\n }\n context.drawImage(\n this._source as CanvasImageSource,\n 0,\n 0,\n image.width,\n image.height,\n padLeft,\n padTop,\n scaledWidth,\n scaledHeight,\n );\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n const plane = targetWidth * targetHeight;\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += 4) {\n data[pixel] = (rgba[offset] as number) * INV_255;\n data[plane + pixel] = (rgba[offset + 1] as number) * INV_255;\n data[2 * plane + pixel] = (rgba[offset + 2] as number) * INV_255;\n }\n\n return { data, scale, padLeft, padTop, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Grow the scratch source canvas to fit an image, reusing it when possible.\n *\n * A canvas is only reallocated when a frame arrives at a different size than\n * the last one, which for a camera or video source is never after the first.\n *\n * @param width Source width in pixels.\n * @param height Source height in pixels.\n */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Write an RGBA buffer as normalized planar float32.\n *\n * Shared by {@link ResizePipeline} and exported for a custom pipeline that\n * already holds pixels and wants the SDK's exact arithmetic.\n *\n * The expression is `(value / 255 - mean) / std`, evaluated in that order on\n * purpose: folding it into a single multiply-add (`value * k + b`) computes the\n * same quantity but rounds differently, and the tasks' output is asserted to be\n * bit-identical to the composable `normalize` → `toCHW` path.\n *\n * @param rgba Source pixels, 4 bytes per pixel, `width * height` long.\n * @param width Image width in pixels.\n * @param height Image height in pixels.\n * @param mean Per-channel RGB mean, already in `[0, 1]`.\n * @param std Per-channel RGB standard deviation.\n * @param out Destination buffer, `3 * width * height` long.\n * @param stride Bytes per source pixel: 4 for canvas RGBA, 3 for packed RGB.\n */\nexport function writePlanarFloat32(\n rgba: Uint8ClampedArray | Uint8Array,\n width: number,\n height: number,\n mean: readonly [number, number, number],\n std: readonly [number, number, number],\n out: Float32Array,\n stride: number = 4,\n): void {\n const plane = width * height;\n const m0 = mean[0];\n const m1 = mean[1];\n const m2 = mean[2];\n const s0 = std[0];\n const s1 = std[1];\n const s2 = std[2];\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += stride) {\n out[pixel] = ((rgba[offset] as number) * INV_255 - m0) / s0;\n out[plane + pixel] = ((rgba[offset + 1] as number) * INV_255 - m1) / s1;\n out[2 * plane + pixel] = ((rgba[offset + 2] as number) * INV_255 - m2) / s2;\n }\n}\n\n/** Planar tensor data produced by {@link ResizePipeline}. */\nexport interface FusedResizeResult {\n /** CHW float32, normalized, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link ResizePipeline.run} overwrites it, so a caller\n * keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable stretch-resize → normalized tensor pipeline for one target size.\n *\n * The classification counterpart of {@link LetterboxPipeline}. A classifier\n * stretches to the model's square input instead of letterboxing into it — no\n * padding, no scale to invert later, because nothing is mapped back onto the\n * source image afterwards. That difference is why it cannot simply reuse the\n * letterbox path.\n *\n * What it does share is the technique. The composable route\n * (`resize` → `normalize` → `toCHW`) allocates an `RGBImage` and two\n * `Float32Array`s and walks each end to end on every call: about 1.4 MB of\n * fresh garbage per 224×224 `predict()`, produced at the exact moment a phone\n * near its memory ceiling can least afford it. Here one `drawImage` resizes,\n * and one loop reads the resulting RGBA and writes normalized planar float32\n * into a buffer held across calls.\n *\n * Create one per task, not per frame.\n */\nexport class ResizePipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _mean: readonly [number, number, number];\n private readonly _std: readonly [number, number, number];\n private readonly _buffer: Float32Array;\n private _target: Canvas2D | null = null;\n private _targetContext: Context2D | null = null;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param mean Per-channel RGB mean in `[0, 1]`. Defaults to no shift.\n * @param std Per-channel RGB standard deviation. Defaults to no scaling.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n mean: readonly [number, number, number] = [0, 0, 0],\n std: readonly [number, number, number] = [1, 1, 1],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid resize target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._mean = mean;\n this._std = std;\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline resizes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Resize an image to the target size and write it as normalized planar float32.\n *\n * An image that already arrives at the target size skips the canvas entirely\n * and is read straight out of its packed RGB — which is both faster and what\n * keeps the result identical to `resize()`, whose own fast path returns the\n * input untouched.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedResizeResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n if (image.width === targetWidth && image.height === targetHeight) {\n writePlanarFloat32(\n image.data,\n targetWidth,\n targetHeight,\n this._mean,\n this._std,\n data,\n 3,\n );\n return { data, reused };\n }\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._ensureTarget();\n context.drawImage(this._source as CanvasImageSource, 0, 0, targetWidth, targetHeight);\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n writePlanarFloat32(rgba, targetWidth, targetHeight, this._mean, this._std, data);\n return { data, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Build the target canvas on first use.\n *\n * Lazily, so a pipeline constructed where no canvas implementation exists\n * (a Node test, a worker without OffscreenCanvas) only fails if it is asked\n * to resize something.\n */\n private _ensureTarget(): Context2D {\n if (this._target === null) {\n this._target = createCanvas(this._targetWidth, this._targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n }\n return this._targetContext as Context2D;\n }\n\n /** Grow the scratch source canvas to fit an image, reusing it when possible. */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Resize an image into normalized planar float32 without keeping any state.\n *\n * The allocation-free path is {@link ResizePipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param mean Per-channel RGB mean in `[0, 1]`. Defaults to no shift.\n * @param std Per-channel RGB standard deviation. Defaults to no scaling.\n */\nexport function resizeToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n mean: readonly [number, number, number] = [0, 0, 0],\n std: readonly [number, number, number] = [1, 1, 1],\n): FusedResizeResult {\n return new ResizePipeline(targetWidth, targetHeight, mean, std).run(image);\n}\n\n/**\n * Build a zero-filled CHW tensor payload for a warm-up run.\n *\n * @param width Model input width in pixels.\n * @param height Model input height in pixels.\n */\nexport function zeroTensorData(width: number, height: number): Float32Array {\n return new Float32Array(3 * height * width);\n}\n\n/**\n * Letterbox an image into planar float32 without keeping any state.\n *\n * The allocation-free path is {@link LetterboxPipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\nexport function letterboxToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n): FusedLetterboxResult {\n return new LetterboxPipeline(targetWidth, targetHeight, fill).run(image);\n}\n"],"mappings":"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"}
|
|
@@ -45,10 +45,76 @@ var r = 1 / 255, i = class {
|
|
|
45
45
|
return (this._source === null || this._source.width !== n || this._source.height !== r) && (this._source = e(n, r), this._sourceContext = t(this._source)), this._sourceContext;
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
-
function a(e, t) {
|
|
48
|
+
function a(e, t, n, i, a, o, s = 4) {
|
|
49
|
+
let c = t * n, l = i[0], u = i[1], d = i[2], f = a[0], p = a[1], m = a[2];
|
|
50
|
+
for (let t = 0, n = 0; t < c; t++, n += s) o[t] = (e[n] * r - l) / f, o[c + t] = (e[n + 1] * r - u) / p, o[2 * c + t] = (e[n + 2] * r - d) / m;
|
|
51
|
+
}
|
|
52
|
+
var o = class {
|
|
53
|
+
_targetWidth;
|
|
54
|
+
_targetHeight;
|
|
55
|
+
_mean;
|
|
56
|
+
_std;
|
|
57
|
+
_buffer;
|
|
58
|
+
_target = null;
|
|
59
|
+
_targetContext = null;
|
|
60
|
+
_source = null;
|
|
61
|
+
_sourceContext = null;
|
|
62
|
+
_bufferInUse = !1;
|
|
63
|
+
constructor(e, t, n = [
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
0
|
|
67
|
+
], r = [
|
|
68
|
+
1,
|
|
69
|
+
1,
|
|
70
|
+
1
|
|
71
|
+
]) {
|
|
72
|
+
if (e <= 0 || t <= 0) throw Error(`Invalid resize target ${e}x${t}.`);
|
|
73
|
+
this._targetWidth = e, this._targetHeight = t, this._mean = n, this._std = r, this._buffer = new Float32Array(3 * t * e);
|
|
74
|
+
}
|
|
75
|
+
get targetSize() {
|
|
76
|
+
return [this._targetWidth, this._targetHeight];
|
|
77
|
+
}
|
|
78
|
+
run(e) {
|
|
79
|
+
let t = this._targetWidth, r = this._targetHeight, i = !this._bufferInUse, o = i ? this._buffer : new Float32Array(3 * r * t);
|
|
80
|
+
if (this._bufferInUse = !0, e.width === t && e.height === r) return a(e.data, t, r, this._mean, this._std, o, 3), {
|
|
81
|
+
data: o,
|
|
82
|
+
reused: i
|
|
83
|
+
};
|
|
84
|
+
this._ensureSource(e.width, e.height).putImageData(n(e), 0, 0);
|
|
85
|
+
let s = this._ensureTarget();
|
|
86
|
+
s.drawImage(this._source, 0, 0, t, r);
|
|
87
|
+
let c = s.getImageData(0, 0, t, r).data;
|
|
88
|
+
return a(c, t, r, this._mean, this._std, o), {
|
|
89
|
+
data: o,
|
|
90
|
+
reused: i
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
release() {
|
|
94
|
+
this._bufferInUse = !1;
|
|
95
|
+
}
|
|
96
|
+
_ensureTarget() {
|
|
97
|
+
return this._target === null && (this._target = e(this._targetWidth, this._targetHeight), this._targetContext = t(this._target, { willReadFrequently: !0 }), this._targetContext.imageSmoothingEnabled = !0, this._targetContext.imageSmoothingQuality = "high"), this._targetContext;
|
|
98
|
+
}
|
|
99
|
+
_ensureSource(n, r) {
|
|
100
|
+
return (this._source === null || this._source.width !== n || this._source.height !== r) && (this._source = e(n, r), this._sourceContext = t(this._source)), this._sourceContext;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
function s(e, t, n, r = [
|
|
104
|
+
0,
|
|
105
|
+
0,
|
|
106
|
+
0
|
|
107
|
+
], i = [
|
|
108
|
+
1,
|
|
109
|
+
1,
|
|
110
|
+
1
|
|
111
|
+
]) {
|
|
112
|
+
return new o(t, n, r, i).run(e);
|
|
113
|
+
}
|
|
114
|
+
function c(e, t) {
|
|
49
115
|
return new Float32Array(3 * t * e);
|
|
50
116
|
}
|
|
51
|
-
function
|
|
117
|
+
function l(e, t, n, r = [
|
|
52
118
|
114,
|
|
53
119
|
114,
|
|
54
120
|
114
|
|
@@ -56,6 +122,6 @@ function o(e, t, n, r = [
|
|
|
56
122
|
return new i(t, n, r).run(e);
|
|
57
123
|
}
|
|
58
124
|
//#endregion
|
|
59
|
-
export { i as LetterboxPipeline, o as letterboxToTensorData, a as zeroTensorData };
|
|
125
|
+
export { i as LetterboxPipeline, o as ResizePipeline, l as letterboxToTensorData, s as resizeToTensorData, a as writePlanarFloat32, c as zeroTensorData };
|
|
60
126
|
|
|
61
127
|
//# sourceMappingURL=pipeline.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","names":[],"sources":["../../../src/vision/preprocess/pipeline.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Fused letterbox → CHW float32 pipeline with reusable buffers.\n *\n * The composable primitives in {@link ./image.js} each allocate and each walk\n * their input end to end, which is the right shape for a library but the wrong\n * shape for a video loop. Chaining them costs eleven full-buffer passes and six\n * large allocations per frame:\n *\n * `getImageData` → RGBA→RGB → RGB→RGBA → `putImageData` → `drawImage` →\n * `getImageData` → RGBA→RGB → fill → row copies → `toFloat32` → `toCHW`.\n *\n * This module collapses the second half of that into two: one `drawImage` that\n * resizes *and* positions the content inside the padded target in a single\n * accelerated operation, and one loop that reads the resulting RGBA and writes\n * planar float32 directly. The intermediate `RGBImage` at target size, the fill\n * loop, the row copies and the two 4.9 MB `Float32Array` allocations all go\n * away.\n *\n * The primitives stay exactly as they are — they are public API and they are\n * what makes a custom pipeline writable. This is the fast path the built-in\n * tasks take.\n */\n\nimport {\n createCanvas,\n get2DContext,\n rgbToImageData,\n type Canvas2D,\n type Context2D,\n} from \"../core/canvas\";\nimport type { RGBImage } from \"../types\";\n\nconst INV_255 = 1 / 255;\n\n/** Geometry of a letterbox, plus the planar tensor data it produced. */\nexport interface FusedLetterboxResult {\n /** CHW float32 in `[0, 1]`, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /** Factor applied to the original image (`< 1` if downscaled). */\n readonly scale: number;\n /** Horizontal padding in pixels. */\n readonly padLeft: number;\n /** Vertical padding in pixels. */\n readonly padTop: number;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link LetterboxPipeline.run} overwrites it, so a\n * caller keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable letterbox → tensor pipeline for one target resolution.\n *\n * Holds a target canvas and an output buffer across calls, so a steady stream\n * of frames at the same size allocates nothing. Create one per task, not per\n * frame.\n */\nexport class LetterboxPipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _fill: readonly [number, number, number];\n private readonly _target: Canvas2D;\n private readonly _targetContext: Context2D;\n private readonly _buffer: Float32Array;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid letterbox target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._fill = fill;\n this._target = createCanvas(targetWidth, targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline letterboxes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Letterbox an image and write it as planar float32.\n *\n * The returned buffer is reused between calls unless a previous result is\n * still checked out — {@link release} marks it free again. A second `run`\n * before the first is released allocates a fresh buffer rather than\n * corrupting it, so concurrent `predict()` calls on one task stay correct at\n * the cost of the allocation they were trying to avoid.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedLetterboxResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const scale = Math.min(targetWidth / image.width, targetHeight / image.height);\n const scaledWidth = Math.round(image.width * scale);\n const scaledHeight = Math.round(image.height * scale);\n const padLeft = Math.floor((targetWidth - scaledWidth) / 2);\n const padTop = Math.floor((targetHeight - scaledHeight) / 2);\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._targetContext;\n if (\n padLeft > 0 ||\n padTop > 0 ||\n scaledWidth !== targetWidth ||\n scaledHeight !== targetHeight\n ) {\n context.fillStyle = `rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`;\n context.fillRect(0, 0, targetWidth, targetHeight);\n }\n context.drawImage(\n this._source as CanvasImageSource,\n 0,\n 0,\n image.width,\n image.height,\n padLeft,\n padTop,\n scaledWidth,\n scaledHeight,\n );\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n const plane = targetWidth * targetHeight;\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += 4) {\n data[pixel] = (rgba[offset] as number) * INV_255;\n data[plane + pixel] = (rgba[offset + 1] as number) * INV_255;\n data[2 * plane + pixel] = (rgba[offset + 2] as number) * INV_255;\n }\n\n return { data, scale, padLeft, padTop, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Grow the scratch source canvas to fit an image, reusing it when possible.\n *\n * A canvas is only reallocated when a frame arrives at a different size than\n * the last one, which for a camera or video source is never after the first.\n *\n * @param width Source width in pixels.\n * @param height Source height in pixels.\n */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Build a zero-filled CHW tensor payload for a warm-up run.\n *\n * @param width Model input width in pixels.\n * @param height Model input height in pixels.\n */\nexport function zeroTensorData(width: number, height: number): Float32Array {\n return new Float32Array(3 * height * width);\n}\n\n/**\n * Letterbox an image into planar float32 without keeping any state.\n *\n * The allocation-free path is {@link LetterboxPipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\nexport function letterboxToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n): FusedLetterboxResult {\n return new LetterboxPipeline(targetWidth, targetHeight, fill).run(image);\n}\n"],"mappings":";;AAiCA,IAAM,IAAU,IAAI,KA4BP,IAAb,MAA+B;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA,UAAmC;CACnC,iBAA2C;CAC3C,eAAuB;CAOvB,YACI,GACA,GACA,IAA0C;EAAC;EAAK;EAAK;CAAG,GAC1D;EACE,IAAI,KAAe,KAAK,KAAgB,GACpC,MAAU,MAAM,4BAA4B,EAAY,GAAG,EAAa,EAAE;EAS9E,AAPA,KAAK,eAAe,GACpB,KAAK,gBAAgB,GACrB,KAAK,QAAQ,GACb,KAAK,UAAU,EAAa,GAAa,CAAY,GACrD,KAAK,iBAAiB,EAAa,KAAK,SAAS,EAAE,oBAAoB,GAAK,CAAC,GAC7E,KAAK,eAAe,wBAAwB,IAC5C,KAAK,eAAe,wBAAwB,QAC5C,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;CAClE;CAGA,IAAI,aAAwC;EACxC,OAAO,CAAC,KAAK,cAAc,KAAK,aAAa;CACjD;CAaA,IAAI,GAAuC;EACvC,IAAM,IAAc,KAAK,cACnB,IAAe,KAAK,eACpB,IAAQ,KAAK,IAAI,IAAc,EAAM,OAAO,IAAe,EAAM,MAAM,GACvE,IAAc,KAAK,MAAM,EAAM,QAAQ,CAAK,GAC5C,IAAe,KAAK,MAAM,EAAM,SAAS,CAAK,GAC9C,IAAU,KAAK,OAAO,IAAc,KAAe,CAAC,GACpD,IAAS,KAAK,OAAO,IAAe,KAAgB,CAAC;EAG3D,KADoB,cAAc,EAAM,OAAO,EAAM,MACrD,CAAA,CAAO,aAAa,EAAe,CAAK,GAAG,GAAG,CAAC;EAE/C,IAAM,IAAU,KAAK;EAUrB,CARI,IAAU,KACV,IAAS,KACT,MAAgB,KAChB,MAAiB,OAEjB,EAAQ,YAAY,OAAO,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,IAC3E,EAAQ,SAAS,GAAG,GAAG,GAAa,CAAY,IAEpD,EAAQ,UACJ,KAAK,SACL,GACA,GACA,EAAM,OACN,EAAM,QACN,GACA,GACA,GACA,CACJ;EAEA,IAAM,IAAO,EAAQ,aAAa,GAAG,GAAG,GAAa,CAAY,CAAC,CAAC,MAC7D,IAAS,CAAC,KAAK,cACf,IAAO,IAAS,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;EACpF,KAAK,eAAe;EAEpB,IAAM,IAAQ,IAAc;EAC5B,KAAK,IAAI,IAAQ,GAAG,IAAS,GAAG,IAAQ,GAAO,KAAS,KAAU,GAG9D,AAFA,EAAK,KAAU,EAAK,KAAqB,GACzC,EAAK,IAAQ,KAAU,EAAK,IAAS,KAAgB,GACrD,EAAK,IAAI,IAAQ,KAAU,EAAK,IAAS,KAAgB;EAG7D,OAAO;GAAE;GAAM;GAAO;GAAS;GAAQ;EAAO;CAClD;CASA,UAAgB;EACZ,KAAK,eAAe;CACxB;CAWA,cAAsB,GAAe,GAA2B;EAS5D,QAPI,KAAK,YAAY,QACjB,KAAK,QAAQ,UAAU,KACvB,KAAK,QAAQ,WAAW,OAExB,KAAK,UAAU,EAAa,GAAO,CAAM,GACzC,KAAK,iBAAiB,EAAa,KAAK,OAAO,IAE5C,KAAK;CAChB;AACJ;AAQA,SAAgB,EAAe,GAAe,GAA8B;CACxE,OAAO,IAAI,aAAa,IAAI,IAAS,CAAK;AAC9C;AAaA,SAAgB,EACZ,GACA,GACA,GACA,IAA0C;CAAC;CAAK;CAAK;AAAG,GACpC;CACpB,OAAO,IAAI,EAAkB,GAAa,GAAc,CAAI,CAAC,CAAC,IAAI,CAAK;AAC3E"}
|
|
1
|
+
{"version":3,"file":"pipeline.js","names":[],"sources":["../../../src/vision/preprocess/pipeline.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Fused letterbox → CHW float32 pipeline with reusable buffers.\n *\n * The composable primitives in {@link ./image.js} each allocate and each walk\n * their input end to end, which is the right shape for a library but the wrong\n * shape for a video loop. Chaining them costs eleven full-buffer passes and six\n * large allocations per frame:\n *\n * `getImageData` → RGBA→RGB → RGB→RGBA → `putImageData` → `drawImage` →\n * `getImageData` → RGBA→RGB → fill → row copies → `toFloat32` → `toCHW`.\n *\n * This module collapses the second half of that into two: one `drawImage` that\n * resizes *and* positions the content inside the padded target in a single\n * accelerated operation, and one loop that reads the resulting RGBA and writes\n * planar float32 directly. The intermediate `RGBImage` at target size, the fill\n * loop, the row copies and the two 4.9 MB `Float32Array` allocations all go\n * away.\n *\n * The primitives stay exactly as they are — they are public API and they are\n * what makes a custom pipeline writable. This is the fast path the built-in\n * tasks take.\n */\n\nimport {\n createCanvas,\n get2DContext,\n rgbToImageData,\n type Canvas2D,\n type Context2D,\n} from \"../core/canvas\";\nimport type { RGBImage } from \"../types\";\n\nconst INV_255 = 1 / 255;\n\n/** Geometry of a letterbox, plus the planar tensor data it produced. */\nexport interface FusedLetterboxResult {\n /** CHW float32 in `[0, 1]`, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /** Factor applied to the original image (`< 1` if downscaled). */\n readonly scale: number;\n /** Horizontal padding in pixels. */\n readonly padLeft: number;\n /** Vertical padding in pixels. */\n readonly padTop: number;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link LetterboxPipeline.run} overwrites it, so a\n * caller keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable letterbox → tensor pipeline for one target resolution.\n *\n * Holds a target canvas and an output buffer across calls, so a steady stream\n * of frames at the same size allocates nothing. Create one per task, not per\n * frame.\n */\nexport class LetterboxPipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _fill: readonly [number, number, number];\n private readonly _target: Canvas2D;\n private readonly _targetContext: Context2D;\n private readonly _buffer: Float32Array;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid letterbox target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._fill = fill;\n this._target = createCanvas(targetWidth, targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline letterboxes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Letterbox an image and write it as planar float32.\n *\n * The returned buffer is reused between calls unless a previous result is\n * still checked out — {@link release} marks it free again. A second `run`\n * before the first is released allocates a fresh buffer rather than\n * corrupting it, so concurrent `predict()` calls on one task stay correct at\n * the cost of the allocation they were trying to avoid.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedLetterboxResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const scale = Math.min(targetWidth / image.width, targetHeight / image.height);\n const scaledWidth = Math.round(image.width * scale);\n const scaledHeight = Math.round(image.height * scale);\n const padLeft = Math.floor((targetWidth - scaledWidth) / 2);\n const padTop = Math.floor((targetHeight - scaledHeight) / 2);\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._targetContext;\n if (\n padLeft > 0 ||\n padTop > 0 ||\n scaledWidth !== targetWidth ||\n scaledHeight !== targetHeight\n ) {\n context.fillStyle = `rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`;\n context.fillRect(0, 0, targetWidth, targetHeight);\n }\n context.drawImage(\n this._source as CanvasImageSource,\n 0,\n 0,\n image.width,\n image.height,\n padLeft,\n padTop,\n scaledWidth,\n scaledHeight,\n );\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n const plane = targetWidth * targetHeight;\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += 4) {\n data[pixel] = (rgba[offset] as number) * INV_255;\n data[plane + pixel] = (rgba[offset + 1] as number) * INV_255;\n data[2 * plane + pixel] = (rgba[offset + 2] as number) * INV_255;\n }\n\n return { data, scale, padLeft, padTop, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Grow the scratch source canvas to fit an image, reusing it when possible.\n *\n * A canvas is only reallocated when a frame arrives at a different size than\n * the last one, which for a camera or video source is never after the first.\n *\n * @param width Source width in pixels.\n * @param height Source height in pixels.\n */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Write an RGBA buffer as normalized planar float32.\n *\n * Shared by {@link ResizePipeline} and exported for a custom pipeline that\n * already holds pixels and wants the SDK's exact arithmetic.\n *\n * The expression is `(value / 255 - mean) / std`, evaluated in that order on\n * purpose: folding it into a single multiply-add (`value * k + b`) computes the\n * same quantity but rounds differently, and the tasks' output is asserted to be\n * bit-identical to the composable `normalize` → `toCHW` path.\n *\n * @param rgba Source pixels, 4 bytes per pixel, `width * height` long.\n * @param width Image width in pixels.\n * @param height Image height in pixels.\n * @param mean Per-channel RGB mean, already in `[0, 1]`.\n * @param std Per-channel RGB standard deviation.\n * @param out Destination buffer, `3 * width * height` long.\n * @param stride Bytes per source pixel: 4 for canvas RGBA, 3 for packed RGB.\n */\nexport function writePlanarFloat32(\n rgba: Uint8ClampedArray | Uint8Array,\n width: number,\n height: number,\n mean: readonly [number, number, number],\n std: readonly [number, number, number],\n out: Float32Array,\n stride: number = 4,\n): void {\n const plane = width * height;\n const m0 = mean[0];\n const m1 = mean[1];\n const m2 = mean[2];\n const s0 = std[0];\n const s1 = std[1];\n const s2 = std[2];\n for (let pixel = 0, offset = 0; pixel < plane; pixel++, offset += stride) {\n out[pixel] = ((rgba[offset] as number) * INV_255 - m0) / s0;\n out[plane + pixel] = ((rgba[offset + 1] as number) * INV_255 - m1) / s1;\n out[2 * plane + pixel] = ((rgba[offset + 2] as number) * INV_255 - m2) / s2;\n }\n}\n\n/** Planar tensor data produced by {@link ResizePipeline}. */\nexport interface FusedResizeResult {\n /** CHW float32, normalized, length `3 * targetHeight * targetWidth`. */\n readonly data: Float32Array;\n /**\n * Whether {@link data} is the pipeline's reusable buffer.\n *\n * `true` means the next {@link ResizePipeline.run} overwrites it, so a caller\n * keeping the values past its own inference has to copy them.\n */\n readonly reused: boolean;\n}\n\n/**\n * Reusable stretch-resize → normalized tensor pipeline for one target size.\n *\n * The classification counterpart of {@link LetterboxPipeline}. A classifier\n * stretches to the model's square input instead of letterboxing into it — no\n * padding, no scale to invert later, because nothing is mapped back onto the\n * source image afterwards. That difference is why it cannot simply reuse the\n * letterbox path.\n *\n * What it does share is the technique. The composable route\n * (`resize` → `normalize` → `toCHW`) allocates an `RGBImage` and two\n * `Float32Array`s and walks each end to end on every call: about 1.4 MB of\n * fresh garbage per 224×224 `predict()`, produced at the exact moment a phone\n * near its memory ceiling can least afford it. Here one `drawImage` resizes,\n * and one loop reads the resulting RGBA and writes normalized planar float32\n * into a buffer held across calls.\n *\n * Create one per task, not per frame.\n */\nexport class ResizePipeline {\n private readonly _targetWidth: number;\n private readonly _targetHeight: number;\n private readonly _mean: readonly [number, number, number];\n private readonly _std: readonly [number, number, number];\n private readonly _buffer: Float32Array;\n private _target: Canvas2D | null = null;\n private _targetContext: Context2D | null = null;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\n private _bufferInUse = false;\n\n /**\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param mean Per-channel RGB mean in `[0, 1]`. Defaults to no shift.\n * @param std Per-channel RGB standard deviation. Defaults to no scaling.\n */\n constructor(\n targetWidth: number,\n targetHeight: number,\n mean: readonly [number, number, number] = [0, 0, 0],\n std: readonly [number, number, number] = [1, 1, 1],\n ) {\n if (targetWidth <= 0 || targetHeight <= 0) {\n throw new Error(`Invalid resize target ${targetWidth}x${targetHeight}.`);\n }\n this._targetWidth = targetWidth;\n this._targetHeight = targetHeight;\n this._mean = mean;\n this._std = std;\n this._buffer = new Float32Array(3 * targetHeight * targetWidth);\n }\n\n /** The `[width, height]` this pipeline resizes into. */\n get targetSize(): readonly [number, number] {\n return [this._targetWidth, this._targetHeight];\n }\n\n /**\n * Resize an image to the target size and write it as normalized planar float32.\n *\n * An image that already arrives at the target size skips the canvas entirely\n * and is read straight out of its packed RGB — which is both faster and what\n * keeps the result identical to `resize()`, whose own fast path returns the\n * input untouched.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n */\n run(image: RGBImage): FusedResizeResult {\n const targetWidth = this._targetWidth;\n const targetHeight = this._targetHeight;\n const reused = !this._bufferInUse;\n const data = reused ? this._buffer : new Float32Array(3 * targetHeight * targetWidth);\n this._bufferInUse = true;\n\n if (image.width === targetWidth && image.height === targetHeight) {\n writePlanarFloat32(\n image.data,\n targetWidth,\n targetHeight,\n this._mean,\n this._std,\n data,\n 3,\n );\n return { data, reused };\n }\n\n const source = this._ensureSource(image.width, image.height);\n source.putImageData(rgbToImageData(image), 0, 0);\n\n const context = this._ensureTarget();\n context.drawImage(this._source as CanvasImageSource, 0, 0, targetWidth, targetHeight);\n\n const rgba = context.getImageData(0, 0, targetWidth, targetHeight).data;\n writePlanarFloat32(rgba, targetWidth, targetHeight, this._mean, this._std, data);\n return { data, reused };\n }\n\n /**\n * Mark the reusable buffer free again.\n *\n * Call it once the tensor built from a {@link run} result has been handed to\n * ONNX Runtime and the run has resolved — after that the values are inside\n * the WASM heap and the buffer can be overwritten.\n */\n release(): void {\n this._bufferInUse = false;\n }\n\n /**\n * Build the target canvas on first use.\n *\n * Lazily, so a pipeline constructed where no canvas implementation exists\n * (a Node test, a worker without OffscreenCanvas) only fails if it is asked\n * to resize something.\n */\n private _ensureTarget(): Context2D {\n if (this._target === null) {\n this._target = createCanvas(this._targetWidth, this._targetHeight);\n this._targetContext = get2DContext(this._target, { willReadFrequently: true });\n this._targetContext.imageSmoothingEnabled = true;\n this._targetContext.imageSmoothingQuality = \"high\";\n }\n return this._targetContext as Context2D;\n }\n\n /** Grow the scratch source canvas to fit an image, reusing it when possible. */\n private _ensureSource(width: number, height: number): Context2D {\n if (\n this._source === null ||\n this._source.width !== width ||\n this._source.height !== height\n ) {\n this._source = createCanvas(width, height);\n this._sourceContext = get2DContext(this._source);\n }\n return this._sourceContext as Context2D;\n }\n}\n\n/**\n * Resize an image into normalized planar float32 without keeping any state.\n *\n * The allocation-free path is {@link ResizePipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param mean Per-channel RGB mean in `[0, 1]`. Defaults to no shift.\n * @param std Per-channel RGB standard deviation. Defaults to no scaling.\n */\nexport function resizeToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n mean: readonly [number, number, number] = [0, 0, 0],\n std: readonly [number, number, number] = [1, 1, 1],\n): FusedResizeResult {\n return new ResizePipeline(targetWidth, targetHeight, mean, std).run(image);\n}\n\n/**\n * Build a zero-filled CHW tensor payload for a warm-up run.\n *\n * @param width Model input width in pixels.\n * @param height Model input height in pixels.\n */\nexport function zeroTensorData(width: number, height: number): Float32Array {\n return new Float32Array(3 * height * width);\n}\n\n/**\n * Letterbox an image into planar float32 without keeping any state.\n *\n * The allocation-free path is {@link LetterboxPipeline}; this is the one-shot\n * form, for a caller who wants the fused behaviour without owning a pipeline.\n *\n * @param image Source image in the SDK's canonical HWC RGB layout.\n * @param targetWidth Model input width in pixels.\n * @param targetHeight Model input height in pixels.\n * @param fill RGB padding colour; defaults to YOLO grey.\n */\nexport function letterboxToTensorData(\n image: RGBImage,\n targetWidth: number,\n targetHeight: number,\n fill: readonly [number, number, number] = [114, 114, 114],\n): FusedLetterboxResult {\n return new LetterboxPipeline(targetWidth, targetHeight, fill).run(image);\n}\n"],"mappings":";;AAiCA,IAAM,IAAU,IAAI,KA4BP,IAAb,MAA+B;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA,UAAmC;CACnC,iBAA2C;CAC3C,eAAuB;CAOvB,YACI,GACA,GACA,IAA0C;EAAC;EAAK;EAAK;CAAG,GAC1D;EACE,IAAI,KAAe,KAAK,KAAgB,GACpC,MAAU,MAAM,4BAA4B,EAAY,GAAG,EAAa,EAAE;EAS9E,AAPA,KAAK,eAAe,GACpB,KAAK,gBAAgB,GACrB,KAAK,QAAQ,GACb,KAAK,UAAU,EAAa,GAAa,CAAY,GACrD,KAAK,iBAAiB,EAAa,KAAK,SAAS,EAAE,oBAAoB,GAAK,CAAC,GAC7E,KAAK,eAAe,wBAAwB,IAC5C,KAAK,eAAe,wBAAwB,QAC5C,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;CAClE;CAGA,IAAI,aAAwC;EACxC,OAAO,CAAC,KAAK,cAAc,KAAK,aAAa;CACjD;CAaA,IAAI,GAAuC;EACvC,IAAM,IAAc,KAAK,cACnB,IAAe,KAAK,eACpB,IAAQ,KAAK,IAAI,IAAc,EAAM,OAAO,IAAe,EAAM,MAAM,GACvE,IAAc,KAAK,MAAM,EAAM,QAAQ,CAAK,GAC5C,IAAe,KAAK,MAAM,EAAM,SAAS,CAAK,GAC9C,IAAU,KAAK,OAAO,IAAc,KAAe,CAAC,GACpD,IAAS,KAAK,OAAO,IAAe,KAAgB,CAAC;EAG3D,KADoB,cAAc,EAAM,OAAO,EAAM,MACrD,CAAA,CAAO,aAAa,EAAe,CAAK,GAAG,GAAG,CAAC;EAE/C,IAAM,IAAU,KAAK;EAUrB,CARI,IAAU,KACV,IAAS,KACT,MAAgB,KAChB,MAAiB,OAEjB,EAAQ,YAAY,OAAO,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,IAC3E,EAAQ,SAAS,GAAG,GAAG,GAAa,CAAY,IAEpD,EAAQ,UACJ,KAAK,SACL,GACA,GACA,EAAM,OACN,EAAM,QACN,GACA,GACA,GACA,CACJ;EAEA,IAAM,IAAO,EAAQ,aAAa,GAAG,GAAG,GAAa,CAAY,CAAC,CAAC,MAC7D,IAAS,CAAC,KAAK,cACf,IAAO,IAAS,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;EACpF,KAAK,eAAe;EAEpB,IAAM,IAAQ,IAAc;EAC5B,KAAK,IAAI,IAAQ,GAAG,IAAS,GAAG,IAAQ,GAAO,KAAS,KAAU,GAG9D,AAFA,EAAK,KAAU,EAAK,KAAqB,GACzC,EAAK,IAAQ,KAAU,EAAK,IAAS,KAAgB,GACrD,EAAK,IAAI,IAAQ,KAAU,EAAK,IAAS,KAAgB;EAG7D,OAAO;GAAE;GAAM;GAAO;GAAS;GAAQ;EAAO;CAClD;CASA,UAAgB;EACZ,KAAK,eAAe;CACxB;CAWA,cAAsB,GAAe,GAA2B;EAS5D,QAPI,KAAK,YAAY,QACjB,KAAK,QAAQ,UAAU,KACvB,KAAK,QAAQ,WAAW,OAExB,KAAK,UAAU,EAAa,GAAO,CAAM,GACzC,KAAK,iBAAiB,EAAa,KAAK,OAAO,IAE5C,KAAK;CAChB;AACJ;AAqBA,SAAgB,EACZ,GACA,GACA,GACA,GACA,GACA,GACA,IAAiB,GACb;CACJ,IAAM,IAAQ,IAAQ,GAChB,IAAK,EAAK,IACV,IAAK,EAAK,IACV,IAAK,EAAK,IACV,IAAK,EAAI,IACT,IAAK,EAAI,IACT,IAAK,EAAI;CACf,KAAK,IAAI,IAAQ,GAAG,IAAS,GAAG,IAAQ,GAAO,KAAS,KAAU,GAG9D,AAFA,EAAI,MAAW,EAAK,KAAqB,IAAU,KAAM,GACzD,EAAI,IAAQ,MAAW,EAAK,IAAS,KAAgB,IAAU,KAAM,GACrE,EAAI,IAAI,IAAQ,MAAW,EAAK,IAAS,KAAgB,IAAU,KAAM;AAEjF;AAkCA,IAAa,IAAb,MAA4B;CACxB;CACA;CACA;CACA;CACA;CACA,UAAmC;CACnC,iBAA2C;CAC3C,UAAmC;CACnC,iBAA2C;CAC3C,eAAuB;CAQvB,YACI,GACA,GACA,IAA0C;EAAC;EAAG;EAAG;CAAC,GAClD,IAAyC;EAAC;EAAG;EAAG;CAAC,GACnD;EACE,IAAI,KAAe,KAAK,KAAgB,GACpC,MAAU,MAAM,yBAAyB,EAAY,GAAG,EAAa,EAAE;EAM3E,AAJA,KAAK,eAAe,GACpB,KAAK,gBAAgB,GACrB,KAAK,QAAQ,GACb,KAAK,OAAO,GACZ,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;CAClE;CAGA,IAAI,aAAwC;EACxC,OAAO,CAAC,KAAK,cAAc,KAAK,aAAa;CACjD;CAYA,IAAI,GAAoC;EACpC,IAAM,IAAc,KAAK,cACnB,IAAe,KAAK,eACpB,IAAS,CAAC,KAAK,cACf,IAAO,IAAS,KAAK,UAAU,IAAI,aAAa,IAAI,IAAe,CAAW;EAGpF,IAFA,KAAK,eAAe,IAEhB,EAAM,UAAU,KAAe,EAAM,WAAW,GAUhD,OATA,EACI,EAAM,MACN,GACA,GACA,KAAK,OACL,KAAK,MACL,GACA,CACJ,GACO;GAAE;GAAM;EAAO;EAI1B,KADoB,cAAc,EAAM,OAAO,EAAM,MACrD,CAAA,CAAO,aAAa,EAAe,CAAK,GAAG,GAAG,CAAC;EAE/C,IAAM,IAAU,KAAK,cAAc;EACnC,EAAQ,UAAU,KAAK,SAA8B,GAAG,GAAG,GAAa,CAAY;EAEpF,IAAM,IAAO,EAAQ,aAAa,GAAG,GAAG,GAAa,CAAY,CAAC,CAAC;EAEnE,OADA,EAAmB,GAAM,GAAa,GAAc,KAAK,OAAO,KAAK,MAAM,CAAI,GACxE;GAAE;GAAM;EAAO;CAC1B;CASA,UAAgB;EACZ,KAAK,eAAe;CACxB;CASA,gBAAmC;EAO/B,OANI,KAAK,YAAY,SACjB,KAAK,UAAU,EAAa,KAAK,cAAc,KAAK,aAAa,GACjE,KAAK,iBAAiB,EAAa,KAAK,SAAS,EAAE,oBAAoB,GAAK,CAAC,GAC7E,KAAK,eAAe,wBAAwB,IAC5C,KAAK,eAAe,wBAAwB,SAEzC,KAAK;CAChB;CAGA,cAAsB,GAAe,GAA2B;EAS5D,QAPI,KAAK,YAAY,QACjB,KAAK,QAAQ,UAAU,KACvB,KAAK,QAAQ,WAAW,OAExB,KAAK,UAAU,EAAa,GAAO,CAAM,GACzC,KAAK,iBAAiB,EAAa,KAAK,OAAO,IAE5C,KAAK;CAChB;AACJ;AAcA,SAAgB,EACZ,GACA,GACA,GACA,IAA0C;CAAC;CAAG;CAAG;AAAC,GAClD,IAAyC;CAAC;CAAG;CAAG;AAAC,GAChC;CACjB,OAAO,IAAI,EAAe,GAAa,GAAc,GAAM,CAAG,CAAC,CAAC,IAAI,CAAK;AAC7E;AAQA,SAAgB,EAAe,GAAe,GAA8B;CACxE,OAAO,IAAI,aAAa,IAAI,IAAS,CAAK;AAC9C;AAaA,SAAgB,EACZ,GACA,GACA,GACA,IAA0C;CAAC;CAAK;CAAK;AAAG,GACpC;CACpB,OAAO,IAAI,EAAkB,GAAa,GAAc,CAAI,CAAC,CAAC,IAAI,CAAK;AAC3E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../core/timing.cjs"),t=require("../results.cjs"),n=require("../labels.cjs"),r=require("../core/graph.cjs"),i=require("../core/metadata.cjs"),a=require("../core/session.cjs"),o=require("../io/image.cjs"),s=require("../preprocess/image.cjs"),c=require("../postprocess/classification.cjs"),
|
|
1
|
+
const e=require("../core/timing.cjs"),t=require("../results.cjs"),n=require("../labels.cjs"),r=require("../core/graph.cjs"),i=require("../core/metadata.cjs"),a=require("../core/session.cjs"),o=require("../io/image.cjs"),s=require("../preprocess/image.cjs"),c=require("../preprocess/pipeline.cjs"),l=require("../postprocess/classification.cjs"),u=require("./base.cjs");var d=[.485,.456,.406],f=[.229,.224,.225],p=class p extends u.VisionTask{_labels;_names;_inputSize;_mean;_std;_applySoftmax;constructor(e,t,n,r,i,a,o){super(e),this._labels=t,this._names=n,this._inputSize=r,this._mean=i,this._std=a,this._applySoftmax=o}_pipelineCache=null;async warmup(e=1){let[t,n]=this._inputSize;for(let r=0;r<e;r++){let e=s.toFloat32Tensor(c.zeroTensorData(t,n),[1,3,n,t]);await this._session.run({[this._session.inputName]:e})}}get _pipeline(){if(this._pipelineCache===null){let[e,t]=this._inputSize;this._pipelineCache=new c.ResizePipeline(e,t,this._mean,this._std)}return this._pipelineCache}static async create(e,t={}){let o=await a.OrtSession.create(e,t),s=t.numClasses??r.classificationNumClasses(o.outputShape)??void 0,c=n.resolveLabels(t.labels??i.modelNames(o.metadata),{numClasses:s}),l={};for(let e=0;e<c.length;e++)l[e]=c[e];return new p(o,c,l,r.resolveInputSize({graphShape:o.inputShape,requested:t.inputSize,fallback:[224,224]}),t.mean??d,t.std??f,t.applySoftmax??!0)}get labels(){return this._labels}get names(){return this._names}get inputSize(){return this._inputSize}get numClasses(){return this._labels.length}async call(e,t={}){return this.predict(e,t)}async predict(n,r={}){let i=new e.SpeedTimer,a=typeof n==`string`?n:null,s=await o.loadImage(n);i.stage(`load`);let c=this._preprocess(s);i.stage(`preprocess`);let u=await this._session.run({[this._session.inputName]:c});this._pipeline.release(),i.stage(`inference`);let d=this._session.outputNames[0];if(d===void 0)throw Error(`Classifier model has no outputs.`);let f=u[d];if(f===void 0)throw Error(`Classifier model output ${d} missing from run() result.`);let p=this._postprocess(f.data),{indices:m,values:h}=l.topK(p,r.topK??null),g=[];for(let e=0;e<m.length;e++){let t=m[e],n=this._labels[t]??`class_${t}`;g.push({classId:t,className:n,probability:h[e],cls:t,name:n,conf:h[e]})}if(g.length===0)throw Error(`Classifier produced no probabilities (empty output).`);let _=g[0],v={classId:_.classId,className:_.className,confidence:_.probability,cls:_.classId,name:_.className,conf:_.probability,image:s,probabilities:g},y=[s.height,s.width],b=new t.Probs(p);return i.stage(`postprocess`),[new t.ClassificationResults(b,v,this._names,s,y,a,i.speed())]}_preprocess(e){let[t,n]=this._inputSize,{data:r}=this._pipeline.run(e);return s.toFloat32Tensor(r,[1,3,n,t])}_postprocess(e){return this._applySoftmax?l.softmax(e):new Float32Array(e)}};exports.Classifier=p;
|
|
2
2
|
//# sourceMappingURL=classifier.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classifier.cjs","names":[],"sources":["../../../src/vision/tasks/classifier.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Image classification task using ONNX Runtime Web.\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 { classificationNumClasses, resolveInputSize } from \"../core/graph\";\nimport { modelNames } from \"../core/metadata\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { normalize, resize, toCHW, toFloat32Tensor } from \"../preprocess/image\";\nimport { ClassificationResults, Probs } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type ClassProbability, type ClassificationResult, type RGBImage } from \"../types\";\n\nconst IMAGENET_MEAN: readonly [number, number, number] = [0.485, 0.456, 0.406];\nconst IMAGENET_STD: readonly [number, number, number] = [0.229, 0.224, 0.225];\n\nexport interface ClassifierOptions extends OrtSessionOptions {\n /**\n * Class label spec — see {@link resolveLabels}.\n *\n * Optional: when omitted, the names the export baked into the model are used\n * (Ultralytics writes them as `names` in the metadata map). Only when the\n * model carries none does this fall back to generated `class_<id>` labels.\n * Passing a spec always wins, for a model whose names are wrong or absent.\n */\n readonly labels?: LabelSpec;\n /**\n * Number of classes the model can predict.\n *\n * Optional: inferred from the classification head's declared output shape\n * `(B, nc)`. Pass it to validate that the supplied labels match the model.\n */\n readonly numClasses?: number;\n /**\n * Model input `[width, height]` in pixels.\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 `[224, 224]`.\n */\n readonly inputSize?: readonly [number, number];\n /** Per-channel RGB mean used for normalization. Defaults to ImageNet. */\n readonly mean?: readonly [number, number, number];\n /** Per-channel RGB standard deviation. Defaults to ImageNet. */\n readonly std?: readonly [number, number, number];\n /**\n * If `true` (default), apply softmax to the raw model output. Set to\n * `false` for models whose final layer already produces a probability\n * distribution.\n */\n readonly applySoftmax?: boolean;\n}\n\nexport interface ClassifierPredictOptions {\n /**\n * If set, the per-class probability list in `results[0].result.probabilities`\n * is truncated to the top-K entries. The bulk `probs` view always exposes\n * the full vector.\n */\n readonly topK?: number;\n}\n\n/**\n * Image classifier wrapping an ONNX model with ImageNet-style preprocessing.\n *\n * `predict()` returns `Promise<ClassificationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes a `probs`\n * collection (`top1`, `top1conf`, `top5`, `top5conf`, `data`) plus the\n * legacy per-class probability list with names already resolved.\n *\n * Defaults: 224×224 RGB input, `float32` normalized with ImageNet mean/std,\n * NCHW layout, batch size 1, softmax applied to the raw output.\n *\n * @example\n * ```typescript\n * const clf = await Classifier.create(\"/models/resnet50.onnx\", {\n * labels: [\"tench\", \"goldfish\", ...] // 1000 ImageNet labels\n * });\n * const r = (await clf.predict(\"/images/dog.jpg\"))[0];\n * console.log(r.cls, r.conf, r.name);\n * console.log(r.probs.top5, r.probs.top5conf);\n * ```\n */\nexport class Classifier extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _mean: readonly [number, number, number],\n private readonly _std: readonly [number, number, number],\n private readonly _applySoftmax: boolean,\n ) {\n super(session);\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: ClassifierOptions = {}): Promise<Classifier> {\n const session = await OrtSession.create(model, options);\n const numClasses =\n options.numClasses ?? classificationNumClasses(session.outputShape) ?? undefined;\n const labels = resolveLabels(options.labels ?? modelNames(session.metadata), {\n 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 Classifier(\n session,\n labels,\n names,\n resolveInputSize({\n graphShape: session.inputShape,\n requested: options.inputSize,\n fallback: [224, 224],\n }),\n options.mean ?? IMAGENET_MEAN,\n options.std ?? IMAGENET_STD,\n options.applySoftmax ?? true,\n );\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 can predict. */\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: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run classification on a single image. */\n async predict(\n image: ImageInput,\n options: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\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 = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n timer.stage(\"inference\");\n const firstOutputName = this._session.outputNames[0];\n if (firstOutputName === undefined) {\n throw new Error(\"Classifier model has no outputs.\");\n }\n const raw = outputs[firstOutputName];\n if (raw === undefined) {\n throw new Error(\n `Classifier model output ${firstOutputName} missing from run() result.`,\n );\n }\n const fullProbs = this._postprocess(raw.data as Float32Array);\n\n const { indices, values } = topK(fullProbs, options.topK ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const id = indices[i] as number;\n const className = this._labels[id] ?? `class_${id}`;\n probabilities.push({\n classId: id,\n className,\n probability: values[i] as number,\n cls: id,\n name: className,\n conf: values[i] as number,\n });\n }\n if (probabilities.length === 0) {\n throw new Error(\"Classifier produced no probabilities (empty output).\");\n }\n\n const top = probabilities[0] as ClassProbability;\n const result: ClassificationResult = {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image: original,\n probabilities,\n };\n\n const orig: readonly [number, number] = [original.height, original.width];\n const probs = new Probs(fullProbs);\n timer.stage(\"postprocess\");\n return [\n new ClassificationResults(\n probs,\n result,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): ort.Tensor {\n const [tw, th] = this._inputSize;\n const resized = resize(image, tw, th);\n const normalized = normalize(resized, this._mean, this._std);\n const chw = toCHW(normalized, resized.width, resized.height, 3);\n return toFloat32Tensor(chw, [1, 3, resized.height, resized.width]);\n }\n\n private _postprocess(raw: Float32Array): Float32Array {\n return this._applySoftmax ? softmax(raw) : new Float32Array(raw);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"classifier.cjs","names":[],"sources":["../../../src/vision/tasks/classifier.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Image classification task using ONNX Runtime Web.\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 { classificationNumClasses, resolveInputSize } from \"../core/graph\";\nimport { modelNames } from \"../core/metadata\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toFloat32Tensor } from \"../preprocess/image\";\nimport { ResizePipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { ClassificationResults, Probs } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type ClassProbability, type ClassificationResult, type RGBImage } from \"../types\";\n\nconst IMAGENET_MEAN: readonly [number, number, number] = [0.485, 0.456, 0.406];\nconst IMAGENET_STD: readonly [number, number, number] = [0.229, 0.224, 0.225];\n\nexport interface ClassifierOptions extends OrtSessionOptions {\n /**\n * Class label spec — see {@link resolveLabels}.\n *\n * Optional: when omitted, the names the export baked into the model are used\n * (Ultralytics writes them as `names` in the metadata map). Only when the\n * model carries none does this fall back to generated `class_<id>` labels.\n * Passing a spec always wins, for a model whose names are wrong or absent.\n */\n readonly labels?: LabelSpec;\n /**\n * Number of classes the model can predict.\n *\n * Optional: inferred from the classification head's declared output shape\n * `(B, nc)`. Pass it to validate that the supplied labels match the model.\n */\n readonly numClasses?: number;\n /**\n * Model input `[width, height]` in pixels.\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 `[224, 224]`.\n */\n readonly inputSize?: readonly [number, number];\n /** Per-channel RGB mean used for normalization. Defaults to ImageNet. */\n readonly mean?: readonly [number, number, number];\n /** Per-channel RGB standard deviation. Defaults to ImageNet. */\n readonly std?: readonly [number, number, number];\n /**\n * If `true` (default), apply softmax to the raw model output. Set to\n * `false` for models whose final layer already produces a probability\n * distribution.\n */\n readonly applySoftmax?: boolean;\n}\n\nexport interface ClassifierPredictOptions {\n /**\n * If set, the per-class probability list in `results[0].result.probabilities`\n * is truncated to the top-K entries. The bulk `probs` view always exposes\n * the full vector.\n */\n readonly topK?: number;\n}\n\n/**\n * Image classifier wrapping an ONNX model with ImageNet-style preprocessing.\n *\n * `predict()` returns `Promise<ClassificationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes a `probs`\n * collection (`top1`, `top1conf`, `top5`, `top5conf`, `data`) plus the\n * legacy per-class probability list with names already resolved.\n *\n * Defaults: 224×224 RGB input, `float32` normalized with ImageNet mean/std,\n * NCHW layout, batch size 1, softmax applied to the raw output.\n *\n * @example\n * ```typescript\n * const clf = await Classifier.create(\"/models/resnet50.onnx\", {\n * labels: [\"tench\", \"goldfish\", ...] // 1000 ImageNet labels\n * });\n * const r = (await clf.predict(\"/images/dog.jpg\"))[0];\n * console.log(r.cls, r.conf, r.name);\n * console.log(r.probs.top5, r.probs.top5conf);\n * ```\n */\nexport class Classifier extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _mean: readonly [number, number, number],\n private readonly _std: readonly [number, number, number],\n private readonly _applySoftmax: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: ResizePipeline | null = null;\n\n /**\n * Run the model once on a zero tensor, paying one-time costs up front.\n *\n * The first inference of a session is not representative: WebGPU compiles its\n * shaders on it and the WASM backend faults in its arenas. Calling this while\n * a loading spinner is still up moves that cost somewhere the user is already\n * waiting — which matters most for a classifier running as the last step of\n * an on-device analysis, where the delay lands right before the answer shows.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [tw, th] = this._inputSize;\n for (let i = 0; i < runs; i++) {\n const tensor = toFloat32Tensor(zeroTensorData(tw, th), [1, 3, th, tw]);\n await this._session.run({ [this._session.inputName]: tensor });\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it reserves the output buffer: a task built in\n * an environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): ResizePipeline {\n if (this._pipelineCache === null) {\n const [tw, th] = this._inputSize;\n this._pipelineCache = new ResizePipeline(tw, th, this._mean, this._std);\n }\n return this._pipelineCache;\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: ClassifierOptions = {}): Promise<Classifier> {\n const session = await OrtSession.create(model, options);\n const numClasses =\n options.numClasses ?? classificationNumClasses(session.outputShape) ?? undefined;\n const labels = resolveLabels(options.labels ?? modelNames(session.metadata), {\n 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 Classifier(\n session,\n labels,\n names,\n resolveInputSize({\n graphShape: session.inputShape,\n requested: options.inputSize,\n fallback: [224, 224],\n }),\n options.mean ?? IMAGENET_MEAN,\n options.std ?? IMAGENET_STD,\n options.applySoftmax ?? true,\n );\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 can predict. */\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: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run classification on a single image. */\n async predict(\n image: ImageInput,\n options: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\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 = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n this._pipeline.release();\n timer.stage(\"inference\");\n const firstOutputName = this._session.outputNames[0];\n if (firstOutputName === undefined) {\n throw new Error(\"Classifier model has no outputs.\");\n }\n const raw = outputs[firstOutputName];\n if (raw === undefined) {\n throw new Error(\n `Classifier model output ${firstOutputName} missing from run() result.`,\n );\n }\n const fullProbs = this._postprocess(raw.data as Float32Array);\n\n const { indices, values } = topK(fullProbs, options.topK ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const id = indices[i] as number;\n const className = this._labels[id] ?? `class_${id}`;\n probabilities.push({\n classId: id,\n className,\n probability: values[i] as number,\n cls: id,\n name: className,\n conf: values[i] as number,\n });\n }\n if (probabilities.length === 0) {\n throw new Error(\"Classifier produced no probabilities (empty output).\");\n }\n\n const top = probabilities[0] as ClassProbability;\n const result: ClassificationResult = {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image: original,\n probabilities,\n };\n\n const orig: readonly [number, number] = [original.height, original.width];\n const probs = new Probs(fullProbs);\n timer.stage(\"postprocess\");\n return [\n new ClassificationResults(\n probs,\n result,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): ort.Tensor {\n const [tw, th] = this._inputSize;\n const { data } = this._pipeline.run(image);\n return toFloat32Tensor(data, [1, 3, th, tw]);\n }\n\n private _postprocess(raw: Float32Array): Float32Array {\n return this._applySoftmax ? softmax(raw) : new Float32Array(raw);\n }\n}\n"],"mappings":"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"}
|
|
@@ -5,19 +5,20 @@ import { classificationNumClasses as i, resolveInputSize as a } from "../core/gr
|
|
|
5
5
|
import { modelNames as o } from "../core/metadata.js";
|
|
6
6
|
import { OrtSession as s } from "../core/session.js";
|
|
7
7
|
import { loadImage as c } from "../io/image.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { toFloat32Tensor as l } from "../preprocess/image.js";
|
|
9
|
+
import { ResizePipeline as u, zeroTensorData as d } from "../preprocess/pipeline.js";
|
|
10
|
+
import { softmax as f, topK as p } from "../postprocess/classification.js";
|
|
11
|
+
import { VisionTask as m } from "./base.js";
|
|
11
12
|
//#region src/vision/tasks/classifier.ts
|
|
12
|
-
var
|
|
13
|
+
var h = [
|
|
13
14
|
.485,
|
|
14
15
|
.456,
|
|
15
16
|
.406
|
|
16
|
-
],
|
|
17
|
+
], g = [
|
|
17
18
|
.229,
|
|
18
19
|
.224,
|
|
19
20
|
.225
|
|
20
|
-
],
|
|
21
|
+
], _ = class _ extends m {
|
|
21
22
|
_labels;
|
|
22
23
|
_names;
|
|
23
24
|
_inputSize;
|
|
@@ -27,14 +28,34 @@ var g = [
|
|
|
27
28
|
constructor(e, t, n, r, i, a, o) {
|
|
28
29
|
super(e), this._labels = t, this._names = n, this._inputSize = r, this._mean = i, this._std = a, this._applySoftmax = o;
|
|
29
30
|
}
|
|
31
|
+
_pipelineCache = null;
|
|
32
|
+
async warmup(e = 1) {
|
|
33
|
+
let [t, n] = this._inputSize;
|
|
34
|
+
for (let r = 0; r < e; r++) {
|
|
35
|
+
let e = l(d(t, n), [
|
|
36
|
+
1,
|
|
37
|
+
3,
|
|
38
|
+
n,
|
|
39
|
+
t
|
|
40
|
+
]);
|
|
41
|
+
await this._session.run({ [this._session.inputName]: e });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
get _pipeline() {
|
|
45
|
+
if (this._pipelineCache === null) {
|
|
46
|
+
let [e, t] = this._inputSize;
|
|
47
|
+
this._pipelineCache = new u(e, t, this._mean, this._std);
|
|
48
|
+
}
|
|
49
|
+
return this._pipelineCache;
|
|
50
|
+
}
|
|
30
51
|
static async create(e, t = {}) {
|
|
31
52
|
let n = await s.create(e, t), c = t.numClasses ?? i(n.outputShape) ?? void 0, l = r(t.labels ?? o(n.metadata), { numClasses: c }), u = {};
|
|
32
53
|
for (let e = 0; e < l.length; e++) u[e] = l[e];
|
|
33
|
-
return new
|
|
54
|
+
return new _(n, l, u, a({
|
|
34
55
|
graphShape: n.inputShape,
|
|
35
56
|
requested: t.inputSize,
|
|
36
57
|
fallback: [224, 224]
|
|
37
|
-
}), t.mean ??
|
|
58
|
+
}), t.mean ?? h, t.std ?? g, t.applySoftmax ?? !0);
|
|
38
59
|
}
|
|
39
60
|
get labels() {
|
|
40
61
|
return this._labels;
|
|
@@ -57,12 +78,12 @@ var g = [
|
|
|
57
78
|
let l = this._preprocess(s);
|
|
58
79
|
a.stage("preprocess");
|
|
59
80
|
let u = await this._session.run({ [this._session.inputName]: l });
|
|
60
|
-
a.stage("inference");
|
|
81
|
+
this._pipeline.release(), a.stage("inference");
|
|
61
82
|
let d = this._session.outputNames[0];
|
|
62
83
|
if (d === void 0) throw Error("Classifier model has no outputs.");
|
|
63
84
|
let f = u[d];
|
|
64
85
|
if (f === void 0) throw Error(`Classifier model output ${d} missing from run() result.`);
|
|
65
|
-
let
|
|
86
|
+
let m = this._postprocess(f.data), { indices: h, values: g } = p(m, i.topK ?? null), _ = [];
|
|
66
87
|
for (let e = 0; e < h.length; e++) {
|
|
67
88
|
let t = h[e], n = this._labels[t] ?? `class_${t}`;
|
|
68
89
|
_.push({
|
|
@@ -84,23 +105,23 @@ var g = [
|
|
|
84
105
|
conf: v.probability,
|
|
85
106
|
image: s,
|
|
86
107
|
probabilities: _
|
|
87
|
-
}, b = [s.height, s.width], x = new n(
|
|
108
|
+
}, b = [s.height, s.width], x = new n(m);
|
|
88
109
|
return a.stage("postprocess"), [new t(x, y, this._names, s, b, o, a.speed())];
|
|
89
110
|
}
|
|
90
111
|
_preprocess(e) {
|
|
91
|
-
let [t, n] = this._inputSize,
|
|
92
|
-
return
|
|
112
|
+
let [t, n] = this._inputSize, { data: r } = this._pipeline.run(e);
|
|
113
|
+
return l(r, [
|
|
93
114
|
1,
|
|
94
115
|
3,
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
n,
|
|
117
|
+
t
|
|
97
118
|
]);
|
|
98
119
|
}
|
|
99
120
|
_postprocess(e) {
|
|
100
|
-
return this._applySoftmax ?
|
|
121
|
+
return this._applySoftmax ? f(e) : new Float32Array(e);
|
|
101
122
|
}
|
|
102
123
|
};
|
|
103
124
|
//#endregion
|
|
104
|
-
export {
|
|
125
|
+
export { _ as Classifier };
|
|
105
126
|
|
|
106
127
|
//# sourceMappingURL=classifier.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classifier.js","names":[],"sources":["../../../src/vision/tasks/classifier.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Image classification task using ONNX Runtime Web.\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 { classificationNumClasses, resolveInputSize } from \"../core/graph\";\nimport { modelNames } from \"../core/metadata\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { normalize, resize, toCHW, toFloat32Tensor } from \"../preprocess/image\";\nimport { ClassificationResults, Probs } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type ClassProbability, type ClassificationResult, type RGBImage } from \"../types\";\n\nconst IMAGENET_MEAN: readonly [number, number, number] = [0.485, 0.456, 0.406];\nconst IMAGENET_STD: readonly [number, number, number] = [0.229, 0.224, 0.225];\n\nexport interface ClassifierOptions extends OrtSessionOptions {\n /**\n * Class label spec — see {@link resolveLabels}.\n *\n * Optional: when omitted, the names the export baked into the model are used\n * (Ultralytics writes them as `names` in the metadata map). Only when the\n * model carries none does this fall back to generated `class_<id>` labels.\n * Passing a spec always wins, for a model whose names are wrong or absent.\n */\n readonly labels?: LabelSpec;\n /**\n * Number of classes the model can predict.\n *\n * Optional: inferred from the classification head's declared output shape\n * `(B, nc)`. Pass it to validate that the supplied labels match the model.\n */\n readonly numClasses?: number;\n /**\n * Model input `[width, height]` in pixels.\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 `[224, 224]`.\n */\n readonly inputSize?: readonly [number, number];\n /** Per-channel RGB mean used for normalization. Defaults to ImageNet. */\n readonly mean?: readonly [number, number, number];\n /** Per-channel RGB standard deviation. Defaults to ImageNet. */\n readonly std?: readonly [number, number, number];\n /**\n * If `true` (default), apply softmax to the raw model output. Set to\n * `false` for models whose final layer already produces a probability\n * distribution.\n */\n readonly applySoftmax?: boolean;\n}\n\nexport interface ClassifierPredictOptions {\n /**\n * If set, the per-class probability list in `results[0].result.probabilities`\n * is truncated to the top-K entries. The bulk `probs` view always exposes\n * the full vector.\n */\n readonly topK?: number;\n}\n\n/**\n * Image classifier wrapping an ONNX model with ImageNet-style preprocessing.\n *\n * `predict()` returns `Promise<ClassificationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes a `probs`\n * collection (`top1`, `top1conf`, `top5`, `top5conf`, `data`) plus the\n * legacy per-class probability list with names already resolved.\n *\n * Defaults: 224×224 RGB input, `float32` normalized with ImageNet mean/std,\n * NCHW layout, batch size 1, softmax applied to the raw output.\n *\n * @example\n * ```typescript\n * const clf = await Classifier.create(\"/models/resnet50.onnx\", {\n * labels: [\"tench\", \"goldfish\", ...] // 1000 ImageNet labels\n * });\n * const r = (await clf.predict(\"/images/dog.jpg\"))[0];\n * console.log(r.cls, r.conf, r.name);\n * console.log(r.probs.top5, r.probs.top5conf);\n * ```\n */\nexport class Classifier extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _mean: readonly [number, number, number],\n private readonly _std: readonly [number, number, number],\n private readonly _applySoftmax: boolean,\n ) {\n super(session);\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: ClassifierOptions = {}): Promise<Classifier> {\n const session = await OrtSession.create(model, options);\n const numClasses =\n options.numClasses ?? classificationNumClasses(session.outputShape) ?? undefined;\n const labels = resolveLabels(options.labels ?? modelNames(session.metadata), {\n 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 Classifier(\n session,\n labels,\n names,\n resolveInputSize({\n graphShape: session.inputShape,\n requested: options.inputSize,\n fallback: [224, 224],\n }),\n options.mean ?? IMAGENET_MEAN,\n options.std ?? IMAGENET_STD,\n options.applySoftmax ?? true,\n );\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 can predict. */\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: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run classification on a single image. */\n async predict(\n image: ImageInput,\n options: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\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 = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n timer.stage(\"inference\");\n const firstOutputName = this._session.outputNames[0];\n if (firstOutputName === undefined) {\n throw new Error(\"Classifier model has no outputs.\");\n }\n const raw = outputs[firstOutputName];\n if (raw === undefined) {\n throw new Error(\n `Classifier model output ${firstOutputName} missing from run() result.`,\n );\n }\n const fullProbs = this._postprocess(raw.data as Float32Array);\n\n const { indices, values } = topK(fullProbs, options.topK ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const id = indices[i] as number;\n const className = this._labels[id] ?? `class_${id}`;\n probabilities.push({\n classId: id,\n className,\n probability: values[i] as number,\n cls: id,\n name: className,\n conf: values[i] as number,\n });\n }\n if (probabilities.length === 0) {\n throw new Error(\"Classifier produced no probabilities (empty output).\");\n }\n\n const top = probabilities[0] as ClassProbability;\n const result: ClassificationResult = {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image: original,\n probabilities,\n };\n\n const orig: readonly [number, number] = [original.height, original.width];\n const probs = new Probs(fullProbs);\n timer.stage(\"postprocess\");\n return [\n new ClassificationResults(\n probs,\n result,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): ort.Tensor {\n const [tw, th] = this._inputSize;\n const resized = resize(image, tw, th);\n const normalized = normalize(resized, this._mean, this._std);\n const chw = toCHW(normalized, resized.width, resized.height, 3);\n return toFloat32Tensor(chw, [1, 3, resized.height, resized.width]);\n }\n\n private _postprocess(raw: Float32Array): Float32Array {\n return this._applySoftmax ? softmax(raw) : new Float32Array(raw);\n }\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAmD;CAAC;CAAO;CAAO;AAAK,GACvE,IAAkD;CAAC;CAAO;CAAO;AAAK,GAqE/D,IAAb,MAAa,UAAmB,EAAW;CAGlB;CACA;CACA;CACA;CACA;CACA;CAPrB,YACI,GACA,GACA,GACA,GACA,GACA,GACA,GACF;EADmB,AAEjB,MAAM,CAAO,GAPI,KAAA,UAAA,GACA,KAAA,SAAA,GACA,KAAA,aAAA,GACA,KAAA,QAAA,GACA,KAAA,OAAA,GACA,KAAA,gBAAA;CAGrB;CAGA,aAAa,OAAO,GAAoB,IAA6B,CAAC,GAAwB;EAC1F,IAAM,IAAU,MAAM,EAAW,OAAO,GAAO,CAAO,GAChD,IACF,EAAQ,cAAc,EAAyB,EAAQ,WAAW,KAAK,KAAA,GACrE,IAAS,EAAc,EAAQ,UAAU,EAAW,EAAQ,QAAQ,GAAG,EACzE,cACJ,CAAC,GACK,IAAgC,CAAC;EACvC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAC/B,EAAM,KAAK,EAAO;EAEtB,OAAO,IAAI,EACP,GACA,GACA,GACA,EAAiB;GACb,YAAY,EAAQ;GACpB,WAAW,EAAQ;GACnB,UAAU,CAAC,KAAK,GAAG;EACvB,CAAC,GACD,EAAQ,QAAQ,GAChB,EAAQ,OAAO,GACf,EAAQ,gBAAgB,EAC5B;CACJ;CAGA,IAAI,SAA4B;EAC5B,OAAO,KAAK;CAChB;CAGA,IAAI,QAA0C;EAC1C,OAAO,KAAK;CAChB;CASA,IAAI,YAAuC;EACvC,OAAO,KAAK;CAChB;CAGA,IAAI,aAAqB;EACrB,OAAO,KAAK,QAAQ;CACxB;CAGA,MAAM,KACF,GACA,IAAoC,CAAC,GACL;EAChC,OAAO,KAAK,QAAQ,GAAO,CAAO;CACtC;CAGA,MAAM,QACF,GACA,IAAoC,CAAC,GACL;EAChC,IAAM,IAAQ,IAAI,EAAW,GACvB,IAAO,OAAO,KAAU,WAAW,IAAQ,MAC3C,IAAW,MAAM,EAAU,CAAK;EACtC,EAAM,MAAM,MAAM;EAClB,IAAM,IAAS,KAAK,YAAY,CAAQ;EACxC,EAAM,MAAM,YAAY;EACxB,IAAM,IAAU,MAAM,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,YAAY,EAAO,CAAC;EAC7E,EAAM,MAAM,WAAW;EACvB,IAAM,IAAkB,KAAK,SAAS,YAAY;EAClD,IAAI,MAAoB,KAAA,GACpB,MAAU,MAAM,kCAAkC;EAEtD,IAAM,IAAM,EAAQ;EACpB,IAAI,MAAQ,KAAA,GACR,MAAU,MACN,2BAA2B,EAAgB,4BAC/C;EAEJ,IAAM,IAAY,KAAK,aAAa,EAAI,IAAoB,GAEtD,EAAE,YAAS,cAAW,EAAK,GAAW,EAAQ,QAAQ,IAAI,GAC1D,IAAoC,CAAC;EAC3C,KAAK,IAAI,IAAI,GAAG,IAAI,EAAQ,QAAQ,KAAK;GACrC,IAAM,IAAK,EAAQ,IACb,IAAY,KAAK,QAAQ,MAAO,SAAS;GAC/C,EAAc,KAAK;IACf,SAAS;IACT;IACA,aAAa,EAAO;IACpB,KAAK;IACL,MAAM;IACN,MAAM,EAAO;GACjB,CAAC;EACL;EACA,IAAI,EAAc,WAAW,GACzB,MAAU,MAAM,sDAAsD;EAG1E,IAAM,IAAM,EAAc,IACpB,IAA+B;GACjC,SAAS,EAAI;GACb,WAAW,EAAI;GACf,YAAY,EAAI;GAChB,KAAK,EAAI;GACT,MAAM,EAAI;GACV,MAAM,EAAI;GACV,OAAO;GACP;EACJ,GAEM,IAAkC,CAAC,EAAS,QAAQ,EAAS,KAAK,GAClE,IAAQ,IAAI,EAAM,CAAS;EAEjC,OADA,EAAM,MAAM,aAAa,GAClB,CACH,IAAI,EACA,GACA,GACA,KAAK,QACL,GACA,GACA,GACA,EAAM,MAAM,CAChB,CACJ;CACJ;CAEA,YAAoB,GAA6B;EAC7C,IAAM,CAAC,GAAI,KAAM,KAAK,YAChB,IAAU,EAAO,GAAO,GAAI,CAAE,GAC9B,IAAa,EAAU,GAAS,KAAK,OAAO,KAAK,IAAI,GACrD,IAAM,EAAM,GAAY,EAAQ,OAAO,EAAQ,QAAQ,CAAC;EAC9D,OAAO,EAAgB,GAAK;GAAC;GAAG;GAAG,EAAQ;GAAQ,EAAQ;EAAK,CAAC;CACrE;CAEA,aAAqB,GAAiC;EAClD,OAAO,KAAK,gBAAgB,EAAQ,CAAG,IAAI,IAAI,aAAa,CAAG;CACnE;AACJ"}
|
|
1
|
+
{"version":3,"file":"classifier.js","names":[],"sources":["../../../src/vision/tasks/classifier.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Image classification task using ONNX Runtime Web.\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 { classificationNumClasses, resolveInputSize } from \"../core/graph\";\nimport { modelNames } from \"../core/metadata\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toFloat32Tensor } from \"../preprocess/image\";\nimport { ResizePipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { ClassificationResults, Probs } from \"../results\";\nimport { VisionTask } from \"./base\";\nimport { type ClassProbability, type ClassificationResult, type RGBImage } from \"../types\";\n\nconst IMAGENET_MEAN: readonly [number, number, number] = [0.485, 0.456, 0.406];\nconst IMAGENET_STD: readonly [number, number, number] = [0.229, 0.224, 0.225];\n\nexport interface ClassifierOptions extends OrtSessionOptions {\n /**\n * Class label spec — see {@link resolveLabels}.\n *\n * Optional: when omitted, the names the export baked into the model are used\n * (Ultralytics writes them as `names` in the metadata map). Only when the\n * model carries none does this fall back to generated `class_<id>` labels.\n * Passing a spec always wins, for a model whose names are wrong or absent.\n */\n readonly labels?: LabelSpec;\n /**\n * Number of classes the model can predict.\n *\n * Optional: inferred from the classification head's declared output shape\n * `(B, nc)`. Pass it to validate that the supplied labels match the model.\n */\n readonly numClasses?: number;\n /**\n * Model input `[width, height]` in pixels.\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 `[224, 224]`.\n */\n readonly inputSize?: readonly [number, number];\n /** Per-channel RGB mean used for normalization. Defaults to ImageNet. */\n readonly mean?: readonly [number, number, number];\n /** Per-channel RGB standard deviation. Defaults to ImageNet. */\n readonly std?: readonly [number, number, number];\n /**\n * If `true` (default), apply softmax to the raw model output. Set to\n * `false` for models whose final layer already produces a probability\n * distribution.\n */\n readonly applySoftmax?: boolean;\n}\n\nexport interface ClassifierPredictOptions {\n /**\n * If set, the per-class probability list in `results[0].result.probabilities`\n * is truncated to the top-K entries. The bulk `probs` view always exposes\n * the full vector.\n */\n readonly topK?: number;\n}\n\n/**\n * Image classifier wrapping an ONNX model with ImageNet-style preprocessing.\n *\n * `predict()` returns `Promise<ClassificationResults[]>` (length 1 for a\n * single image), mirroring Ultralytics' API. The envelope exposes a `probs`\n * collection (`top1`, `top1conf`, `top5`, `top5conf`, `data`) plus the\n * legacy per-class probability list with names already resolved.\n *\n * Defaults: 224×224 RGB input, `float32` normalized with ImageNet mean/std,\n * NCHW layout, batch size 1, softmax applied to the raw output.\n *\n * @example\n * ```typescript\n * const clf = await Classifier.create(\"/models/resnet50.onnx\", {\n * labels: [\"tench\", \"goldfish\", ...] // 1000 ImageNet labels\n * });\n * const r = (await clf.predict(\"/images/dog.jpg\"))[0];\n * console.log(r.cls, r.conf, r.name);\n * console.log(r.probs.top5, r.probs.top5conf);\n * ```\n */\nexport class Classifier extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _inputSize: readonly [number, number],\n private readonly _mean: readonly [number, number, number],\n private readonly _std: readonly [number, number, number],\n private readonly _applySoftmax: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: ResizePipeline | null = null;\n\n /**\n * Run the model once on a zero tensor, paying one-time costs up front.\n *\n * The first inference of a session is not representative: WebGPU compiles its\n * shaders on it and the WASM backend faults in its arenas. Calling this while\n * a loading spinner is still up moves that cost somewhere the user is already\n * waiting — which matters most for a classifier running as the last step of\n * an on-device analysis, where the delay lands right before the answer shows.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [tw, th] = this._inputSize;\n for (let i = 0; i < runs; i++) {\n const tensor = toFloat32Tensor(zeroTensorData(tw, th), [1, 3, th, tw]);\n await this._session.run({ [this._session.inputName]: tensor });\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it reserves the output buffer: a task built in\n * an environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): ResizePipeline {\n if (this._pipelineCache === null) {\n const [tw, th] = this._inputSize;\n this._pipelineCache = new ResizePipeline(tw, th, this._mean, this._std);\n }\n return this._pipelineCache;\n }\n\n /** Load the model and resolve labels. */\n static async create(model: ModelSource, options: ClassifierOptions = {}): Promise<Classifier> {\n const session = await OrtSession.create(model, options);\n const numClasses =\n options.numClasses ?? classificationNumClasses(session.outputShape) ?? undefined;\n const labels = resolveLabels(options.labels ?? modelNames(session.metadata), {\n 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 Classifier(\n session,\n labels,\n names,\n resolveInputSize({\n graphShape: session.inputShape,\n requested: options.inputSize,\n fallback: [224, 224],\n }),\n options.mean ?? IMAGENET_MEAN,\n options.std ?? IMAGENET_STD,\n options.applySoftmax ?? true,\n );\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 can predict. */\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: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\n return this.predict(image, options);\n }\n\n /** Run classification on a single image. */\n async predict(\n image: ImageInput,\n options: ClassifierPredictOptions = {},\n ): Promise<ClassificationResults[]> {\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 = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run({ [this._session.inputName]: tensor });\n this._pipeline.release();\n timer.stage(\"inference\");\n const firstOutputName = this._session.outputNames[0];\n if (firstOutputName === undefined) {\n throw new Error(\"Classifier model has no outputs.\");\n }\n const raw = outputs[firstOutputName];\n if (raw === undefined) {\n throw new Error(\n `Classifier model output ${firstOutputName} missing from run() result.`,\n );\n }\n const fullProbs = this._postprocess(raw.data as Float32Array);\n\n const { indices, values } = topK(fullProbs, options.topK ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const id = indices[i] as number;\n const className = this._labels[id] ?? `class_${id}`;\n probabilities.push({\n classId: id,\n className,\n probability: values[i] as number,\n cls: id,\n name: className,\n conf: values[i] as number,\n });\n }\n if (probabilities.length === 0) {\n throw new Error(\"Classifier produced no probabilities (empty output).\");\n }\n\n const top = probabilities[0] as ClassProbability;\n const result: ClassificationResult = {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image: original,\n probabilities,\n };\n\n const orig: readonly [number, number] = [original.height, original.width];\n const probs = new Probs(fullProbs);\n timer.stage(\"postprocess\");\n return [\n new ClassificationResults(\n probs,\n result,\n this._names,\n original,\n orig,\n path,\n timer.speed(),\n ),\n ];\n }\n\n private _preprocess(image: RGBImage): ort.Tensor {\n const [tw, th] = this._inputSize;\n const { data } = this._pipeline.run(image);\n return toFloat32Tensor(data, [1, 3, th, tw]);\n }\n\n private _postprocess(raw: Float32Array): Float32Array {\n return this._applySoftmax ? softmax(raw) : new Float32Array(raw);\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/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/fusion.cjs"),f=require("./vision/preprocess/image.cjs"),p=require("./vision/preprocess/pipeline.cjs"),m=require("./vision/postprocess/classification.cjs"),h=require("./vision/postprocess/detection.cjs"),g=require("./vision/postprocess/segmentation.cjs"),_=require("./vision/tasks/base.cjs"),v=require("./vision/tasks/classifier.cjs"),y=require("./vision/tasks/detectClassify.cjs"),b=require("./vision/tasks/detector.cjs"),x=require("./vision/tasks/segmenter.cjs"),S=require("./vision/index.cjs"),C=require("./vision/luminance.cjs"),w=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=v.Classifier,exports.DEFAULT_PROVIDERS=c.DEFAULT_PROVIDERS,exports.DetectClassify=y.DetectClassify,exports.DetectClassifyResults=i.DetectClassifyResults,exports.DetectionResults=i.DetectionResults,exports.Detector=b.Detector,exports.FUSION_KIND_DETECT_CLASSIFY=d.FUSION_KIND_DETECT_CLASSIFY,exports.FusionError=t.FusionError,exports.INPUT_IMAGE=d.INPUT_IMAGE,exports.INPUT_PAD=d.INPUT_PAD,exports.INPUT_SCALE=d.INPUT_SCALE,exports.INPUT_SOURCE=d.INPUT_SOURCE,exports.ImageLoadError=t.ImageLoadError,exports.InferenceError=t.InferenceError,exports.LUMINANCE_SAMPLE_MAX_EDGE=C.LUMINANCE_SAMPLE_MAX_EDGE,exports.LabelMapError=t.LabelMapError,exports.LetterboxPipeline=p.LetterboxPipeline,exports.LowLuminanceError=C.LowLuminanceError,exports.METADATA_PREFIX=d.METADATA_PREFIX,exports.Mask=n.Mask,exports.Masks=i.Masks,exports.ModelLoadError=t.ModelLoadError,exports.NoDetectionsError=t.NoDetectionsError,exports.OUTPUT_BOXES=d.OUTPUT_BOXES,exports.OUTPUT_CLASSES=d.OUTPUT_CLASSES,exports.OUTPUT_NUM_DETECTIONS=d.OUTPUT_NUM_DETECTIONS,exports.OUTPUT_PROBS=d.OUTPUT_PROBS,exports.OUTPUT_SCORES=d.OUTPUT_SCORES,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=x.Segmenter,exports.SpeedTimer=r.SpeedTimer,exports.VERSION=S.VERSION,exports.VisionTask=_.VisionTask,exports.batchedNms=h.batchedNms,exports.classificationNumClasses=o.classificationNumClasses,exports.computeImageLuminance=C.computeImageLuminance,exports.declaredShapesFrom=o.declaredShapesFrom,exports.decodeYolo=h.decodeYolo,exports.decodeYoloAnchors=h.decodeYoloAnchors,exports.decodeYoloSeg=g.decodeYoloSeg,exports.defaultLabels=a.defaultLabels,exports.detectionNumClasses=o.detectionNumClasses,exports.fromCv2=f.fromCv2,exports.isLuminanceAcceptable=C.isLuminanceAcceptable,exports.letterbox=f.letterbox,exports.letterboxToTensorData=p.letterboxToTensorData,exports.loadImage=u.loadImage,exports.modelNames=s.modelNames,exports.nms=h.nms,exports.normalize=f.normalize,exports.parseNames=s.parseNames,exports.readFusionSpec=d.readFusionSpec,exports.readModelMetadata=s.readModelMetadata,exports.requireDetections=_.requireDetections,exports.resize=f.resize,exports.resolveInputSize=o.resolveInputSize,exports.resolveLabels=a.resolveLabels,exports.resolveProviders=c.resolveProviders,exports.softmax=m.softmax,exports.spatialInputSize=o.spatialInputSize,exports.toCHW=f.toCHW,exports.toCv2=f.toCv2,exports.toFloat32=f.toFloat32,exports.toFloat32Tensor=f.toFloat32Tensor,exports.toTensor=f.toTensor,exports.topK=m.topK,exports.useCameraStream=e.useCameraStream,exports.useLiveLuminance=w.useLiveLuminance,exports.zeroTensorData=p.zeroTensorData;
|
|
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/fusion.cjs"),f=require("./vision/preprocess/image.cjs"),p=require("./vision/preprocess/pipeline.cjs"),m=require("./vision/postprocess/classification.cjs"),h=require("./vision/postprocess/detection.cjs"),g=require("./vision/postprocess/segmentation.cjs"),_=require("./vision/tasks/base.cjs"),v=require("./vision/tasks/classifier.cjs"),y=require("./vision/tasks/detectClassify.cjs"),b=require("./vision/tasks/detector.cjs"),x=require("./vision/tasks/segmenter.cjs"),S=require("./vision/index.cjs"),C=require("./vision/luminance.cjs"),w=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=v.Classifier,exports.DEFAULT_PROVIDERS=c.DEFAULT_PROVIDERS,exports.DetectClassify=y.DetectClassify,exports.DetectClassifyResults=i.DetectClassifyResults,exports.DetectionResults=i.DetectionResults,exports.Detector=b.Detector,exports.FUSION_KIND_DETECT_CLASSIFY=d.FUSION_KIND_DETECT_CLASSIFY,exports.FusionError=t.FusionError,exports.INPUT_IMAGE=d.INPUT_IMAGE,exports.INPUT_PAD=d.INPUT_PAD,exports.INPUT_SCALE=d.INPUT_SCALE,exports.INPUT_SOURCE=d.INPUT_SOURCE,exports.ImageLoadError=t.ImageLoadError,exports.InferenceError=t.InferenceError,exports.LUMINANCE_SAMPLE_MAX_EDGE=C.LUMINANCE_SAMPLE_MAX_EDGE,exports.LabelMapError=t.LabelMapError,exports.LetterboxPipeline=p.LetterboxPipeline,exports.LowLuminanceError=C.LowLuminanceError,exports.METADATA_PREFIX=d.METADATA_PREFIX,exports.Mask=n.Mask,exports.Masks=i.Masks,exports.ModelLoadError=t.ModelLoadError,exports.NoDetectionsError=t.NoDetectionsError,exports.OUTPUT_BOXES=d.OUTPUT_BOXES,exports.OUTPUT_CLASSES=d.OUTPUT_CLASSES,exports.OUTPUT_NUM_DETECTIONS=d.OUTPUT_NUM_DETECTIONS,exports.OUTPUT_PROBS=d.OUTPUT_PROBS,exports.OUTPUT_SCORES=d.OUTPUT_SCORES,exports.OrtSession=l.OrtSession,exports.OrtVisionError=t.OrtVisionError,exports.Probs=i.Probs,exports.ProviderNotAvailableError=t.ProviderNotAvailableError,exports.RGBImage=n.RGBImage,exports.ResizePipeline=p.ResizePipeline,exports.SegmentationResults=i.SegmentationResults,exports.Segmenter=x.Segmenter,exports.SpeedTimer=r.SpeedTimer,exports.VERSION=S.VERSION,exports.VisionTask=_.VisionTask,exports.batchedNms=h.batchedNms,exports.classificationNumClasses=o.classificationNumClasses,exports.computeImageLuminance=C.computeImageLuminance,exports.declaredShapesFrom=o.declaredShapesFrom,exports.decodeYolo=h.decodeYolo,exports.decodeYoloAnchors=h.decodeYoloAnchors,exports.decodeYoloSeg=g.decodeYoloSeg,exports.defaultLabels=a.defaultLabels,exports.detectionNumClasses=o.detectionNumClasses,exports.fromCv2=f.fromCv2,exports.isLuminanceAcceptable=C.isLuminanceAcceptable,exports.letterbox=f.letterbox,exports.letterboxToTensorData=p.letterboxToTensorData,exports.loadImage=u.loadImage,exports.modelNames=s.modelNames,exports.nms=h.nms,exports.normalize=f.normalize,exports.parseNames=s.parseNames,exports.readFusionSpec=d.readFusionSpec,exports.readModelMetadata=s.readModelMetadata,exports.requireDetections=_.requireDetections,exports.resize=f.resize,exports.resizeToTensorData=p.resizeToTensorData,exports.resolveInputSize=o.resolveInputSize,exports.resolveLabels=a.resolveLabels,exports.resolveProviders=c.resolveProviders,exports.softmax=m.softmax,exports.spatialInputSize=o.spatialInputSize,exports.toCHW=f.toCHW,exports.toCv2=f.toCv2,exports.toFloat32=f.toFloat32,exports.toFloat32Tensor=f.toFloat32Tensor,exports.toTensor=f.toTensor,exports.topK=m.topK,exports.useCameraStream=e.useCameraStream,exports.useLiveLuminance=w.useLiveLuminance,exports.writePlanarFloat32=p.writePlanarFloat32,exports.zeroTensorData=p.zeroTensorData;
|