tempest-react-sdk 0.42.0 → 0.43.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 +7 -5
- package/bin/create-tempest-app.mjs +10 -1
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +34 -30
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +58 -30
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.js +6 -6
- package/dist/icons/material-symbols.cjs +2 -0
- package/dist/icons/material-symbols.cjs.map +1 -0
- package/dist/icons/material-symbols.js +32 -0
- package/dist/icons/material-symbols.js.map +1 -0
- package/dist/icons.cjs +1 -1
- package/dist/icons.d.ts +69 -0
- package/dist/icons.js +2 -1
- package/dist/router/AppRouter.cjs.map +1 -1
- package/dist/router/AppRouter.js.map +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +74 -7
- package/dist/tempest-react-sdk.js +6 -6
- package/dist/vision/index.cjs +1 -1
- package/dist/vision/index.cjs.map +1 -1
- package/dist/vision/index.js +1 -1
- 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 +43 -28
- package/dist/vision/preprocess/pipeline.js.map +1 -1
- package/dist/vision.d.ts +6 -3
- package/package.json +3 -2
- package/template/package.json +1 -1
package/dist/vision/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./core/exceptions.cjs"),t=require("./types.cjs"),n=require("./core/timing.cjs"),r=require("./results.cjs"),i=require("./labels.cjs"),a=require("./core/graph.cjs"),o=require("./core/metadata.cjs"),s=require("./core/providers.cjs"),c=require("./core/session.cjs"),l=require("./io/image.cjs"),u=require("./fusion.cjs"),d=require("./preprocess/image.cjs"),f=require("./preprocess/pipeline.cjs"),p=require("./postprocess/classification.cjs"),m=require("./postprocess/detection.cjs"),h=require("./postprocess/segmentation.cjs"),g=require("./tasks/base.cjs"),_=require("./tasks/classifier.cjs"),v=require("./tasks/detectClassify.cjs"),y=require("./tasks/detector.cjs"),b=require("./tasks/segmenter.cjs");var x=`0.7.
|
|
1
|
+
const e=require("./core/exceptions.cjs"),t=require("./types.cjs"),n=require("./core/timing.cjs"),r=require("./results.cjs"),i=require("./labels.cjs"),a=require("./core/graph.cjs"),o=require("./core/metadata.cjs"),s=require("./core/providers.cjs"),c=require("./core/session.cjs"),l=require("./io/image.cjs"),u=require("./fusion.cjs"),d=require("./preprocess/image.cjs"),f=require("./preprocess/pipeline.cjs"),p=require("./postprocess/classification.cjs"),m=require("./postprocess/detection.cjs"),h=require("./postprocess/segmentation.cjs"),g=require("./tasks/base.cjs"),_=require("./tasks/classifier.cjs"),v=require("./tasks/detectClassify.cjs"),y=require("./tasks/detector.cjs"),b=require("./tasks/segmenter.cjs");var x=`0.7.1`;exports.BoundingBox=t.BoundingBox,exports.Boxes=r.Boxes,exports.COCO_CLASSES=i.COCO_CLASSES,exports.ClassificationResults=r.ClassificationResults,exports.Classifier=_.Classifier,exports.DEFAULT_PROVIDERS=s.DEFAULT_PROVIDERS,exports.DetectClassify=v.DetectClassify,exports.DetectClassifyResults=r.DetectClassifyResults,exports.DetectionResults=r.DetectionResults,exports.Detector=y.Detector,exports.FUSION_KIND_DETECT_CLASSIFY=u.FUSION_KIND_DETECT_CLASSIFY,exports.FusionError=e.FusionError,exports.INPUT_IMAGE=u.INPUT_IMAGE,exports.INPUT_PAD=u.INPUT_PAD,exports.INPUT_SCALE=u.INPUT_SCALE,exports.INPUT_SOURCE=u.INPUT_SOURCE,exports.ImageLoadError=e.ImageLoadError,exports.InferenceError=e.InferenceError,exports.LabelMapError=e.LabelMapError,exports.LetterboxPipeline=f.LetterboxPipeline,exports.METADATA_PREFIX=u.METADATA_PREFIX,exports.Mask=t.Mask,exports.Masks=r.Masks,exports.ModelLoadError=e.ModelLoadError,exports.NoDetectionsError=e.NoDetectionsError,exports.OUTPUT_BOXES=u.OUTPUT_BOXES,exports.OUTPUT_CLASSES=u.OUTPUT_CLASSES,exports.OUTPUT_NUM_DETECTIONS=u.OUTPUT_NUM_DETECTIONS,exports.OUTPUT_PROBS=u.OUTPUT_PROBS,exports.OUTPUT_SCORES=u.OUTPUT_SCORES,exports.OrtSession=c.OrtSession,exports.OrtVisionError=e.OrtVisionError,exports.Probs=r.Probs,exports.ProviderNotAvailableError=e.ProviderNotAvailableError,exports.RGBImage=t.RGBImage,exports.ResizePipeline=f.ResizePipeline,exports.SegmentationResults=r.SegmentationResults,exports.Segmenter=b.Segmenter,exports.SpeedTimer=n.SpeedTimer,exports.VERSION=x,exports.VisionTask=g.VisionTask,exports.batchedNms=m.batchedNms,exports.classificationNumClasses=a.classificationNumClasses,exports.declaredShapesFrom=a.declaredShapesFrom,exports.decodeYolo=m.decodeYolo,exports.decodeYoloAnchors=m.decodeYoloAnchors,exports.decodeYoloSeg=h.decodeYoloSeg,exports.defaultLabels=i.defaultLabels,exports.detectionNumClasses=a.detectionNumClasses,exports.fromCv2=d.fromCv2,exports.letterbox=d.letterbox,exports.letterboxToTensorData=f.letterboxToTensorData,exports.loadImage=l.loadImage,exports.modelNames=o.modelNames,exports.nms=m.nms,exports.normalize=d.normalize,exports.parseNames=o.parseNames,exports.readFusionSpec=u.readFusionSpec,exports.readModelMetadata=o.readModelMetadata,exports.requireDetections=g.requireDetections,exports.resize=d.resize,exports.resizeToTensorData=f.resizeToTensorData,exports.resolveInputSize=a.resolveInputSize,exports.resolveLabels=i.resolveLabels,exports.resolveProviders=s.resolveProviders,exports.softmax=p.softmax,exports.spatialInputSize=a.spatialInputSize,exports.toCHW=d.toCHW,exports.toCv2=d.toCv2,exports.toFloat32=d.toFloat32,exports.toFloat32Tensor=d.toFloat32Tensor,exports.toTensor=d.toTensor,exports.topK=p.topK,exports.writePlanarFloat32=f.writePlanarFloat32,exports.zeroTensorData=f.zeroTensorData;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/vision/index.ts"],"sourcesContent":["/**\n * `tempest-react-sdk/vision` — browser computer-vision inference with ONNX\n * Runtime Web (classification, detection, segmentation).\n *\n * Vendored from `@mauriciobenjamin700/ort-vision-sdk-web@0.7.
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/vision/index.ts"],"sourcesContent":["/**\n * `tempest-react-sdk/vision` — browser computer-vision inference with ONNX\n * Runtime Web (classification, detection, segmentation).\n *\n * Vendored from `@mauriciobenjamin700/ort-vision-sdk-web@0.7.1` (MIT, same\n * author) so it ships inside this SDK without an extra package install.\n * `onnxruntime-web` stays an optional peer dependency — install it (and ship\n * the matching `.wasm` files) only when you use this subpath.\n *\n * Do not hand-edit — regenerate with `npm run vendor:vision`.\n */\n\nexport {\n BoundingBox,\n Mask,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n type SegmentationResult,\n} from \"./types\";\n\nexport {\n Boxes,\n ClassificationResults,\n DetectClassifyResults,\n DetectionResults,\n Masks,\n Probs,\n SegmentationResults,\n} from \"./results\";\n\nexport {\n COCO_CLASSES,\n type LabelSpec,\n type ResolveLabelsOptions,\n defaultLabels,\n resolveLabels,\n} from \"./labels\";\n\nexport {\n FusionError,\n ImageLoadError,\n InferenceError,\n LabelMapError,\n ModelLoadError,\n NoDetectionsError,\n OrtVisionError,\n ProviderNotAvailableError,\n} from \"./core/exceptions\";\n\nexport { type ModelSource, type OrtSessionOptions, OrtSession } from \"./core/session\";\nexport {\n type DeclaredDim,\n type DeclaredShape,\n type ResolveInputSizeOptions,\n classificationNumClasses,\n declaredShapesFrom,\n detectionNumClasses,\n resolveInputSize,\n spatialInputSize,\n} from \"./core/graph\";\nexport { modelNames, parseNames, readModelMetadata } from \"./core/metadata\";\nexport { DEFAULT_PROVIDERS, resolveProviders } from \"./core/providers\";\nexport { type Speed, SpeedTimer } from \"./core/timing\";\n\nexport { type ImageInput, loadImage } from \"./io/image\";\n\nexport {\n FUSION_KIND_DETECT_CLASSIFY,\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n METADATA_PREFIX,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type CropSource,\n type FusionSpec,\n readFusionSpec,\n} from \"./fusion\";\n\nexport {\n type LetterboxResult,\n fromCv2,\n letterbox,\n normalize,\n resize,\n toCHW,\n toCv2,\n toFloat32,\n toFloat32Tensor,\n toTensor,\n} from \"./preprocess/image\";\n\nexport {\n type FusedLetterboxResult,\n type FusedResizeResult,\n LetterboxPipeline,\n ResizePipeline,\n letterboxToTensorData,\n resizeToTensorData,\n writePlanarFloat32,\n zeroTensorData,\n} from \"./preprocess/pipeline\";\n\nexport { type TopKResult, softmax, topK } from \"./postprocess/classification\";\n\nexport {\n type DecodeYoloAnchorsOptions,\n type DecodeYoloOptions,\n type DecodedAnchors,\n type DecodedDetection,\n batchedNms,\n decodeYolo,\n decodeYoloAnchors,\n nms,\n} from \"./postprocess/detection\";\n\nexport {\n type DecodeYoloSegOptions,\n type DecodedSegmentation,\n decodeYoloSeg,\n} from \"./postprocess/segmentation\";\n\nexport { VisionTask, requireDetections } from \"./tasks/base\";\nexport {\n type ClassifierOptions,\n type ClassifierPredictOptions,\n Classifier,\n} from \"./tasks/classifier\";\nexport {\n type DetectClassifyOptions,\n type DetectClassifyPredictOptions,\n DetectClassify,\n} from \"./tasks/detectClassify\";\nexport {\n type DetectorHead,\n type DetectorOptions,\n type DetectorPredictOptions,\n Detector,\n} from \"./tasks/detector\";\nexport {\n type SegmenterHead,\n type SegmenterOptions,\n type SegmenterPredictOptions,\n Segmenter,\n} from \"./tasks/segmenter\";\n\nexport const VERSION: string = \"0.7.1\";\n"],"mappings":"0sBAwJA,IAAa,EAAkB"}
|
package/dist/vision/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { DetectClassify as _e } from "./tasks/detectClassify.js";
|
|
|
20
20
|
import { Detector as ve } from "./tasks/detector.js";
|
|
21
21
|
import { Segmenter as ye } from "./tasks/segmenter.js";
|
|
22
22
|
//#region src/vision/index.ts
|
|
23
|
-
var be = "0.7.
|
|
23
|
+
var be = "0.7.1";
|
|
24
24
|
//#endregion
|
|
25
25
|
export { c as BoundingBox, f as Boxes, y as COCO_CLASSES, p as ClassificationResults, ge as Classifier, A as DEFAULT_PROVIDERS, _e as DetectClassify, m as DetectClassifyResults, h as DetectionResults, ve as Detector, P as FUSION_KIND_DETECT_CLASSIFY, e as FusionError, F as INPUT_IMAGE, I as INPUT_PAD, L as INPUT_SCALE, R as INPUT_SOURCE, t as ImageLoadError, n as InferenceError, r as LabelMapError, te as LetterboxPipeline, z as METADATA_PREFIX, l as Mask, g as Masks, i as ModelLoadError, a as NoDetectionsError, B as OUTPUT_BOXES, V as OUTPUT_CLASSES, H as OUTPUT_NUM_DETECTIONS, U as OUTPUT_PROBS, W as OUTPUT_SCORES, M as OrtSession, o as OrtVisionError, _ as Probs, s as ProviderNotAvailableError, u as RGBImage, ne as ResizePipeline, v as SegmentationResults, ye as Segmenter, d as SpeedTimer, be as VERSION, me as VisionTask, le as batchedNms, S as classificationNumClasses, C as declaredShapesFrom, ue as decodeYolo, de as decodeYoloAnchors, pe as decodeYoloSeg, b as defaultLabels, w as detectionNumClasses, K as fromCv2, q as letterbox, re as letterboxToTensorData, N as loadImage, D as modelNames, fe as nms, J as normalize, O as parseNames, G as readFusionSpec, k as readModelMetadata, he as requireDetections, Y as resize, ie as resizeToTensorData, T as resolveInputSize, x as resolveLabels, j as resolveProviders, se as softmax, E as spatialInputSize, X as toCHW, Z as toCv2, Q as toFloat32, $ as toFloat32Tensor, ee as toTensor, ce as topK, ae as writePlanarFloat32, oe as zeroTensorData };
|
|
26
26
|
|
package/dist/vision/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/vision/index.ts"],"sourcesContent":["/**\n * `tempest-react-sdk/vision` — browser computer-vision inference with ONNX\n * Runtime Web (classification, detection, segmentation).\n *\n * Vendored from `@mauriciobenjamin700/ort-vision-sdk-web@0.7.
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/vision/index.ts"],"sourcesContent":["/**\n * `tempest-react-sdk/vision` — browser computer-vision inference with ONNX\n * Runtime Web (classification, detection, segmentation).\n *\n * Vendored from `@mauriciobenjamin700/ort-vision-sdk-web@0.7.1` (MIT, same\n * author) so it ships inside this SDK without an extra package install.\n * `onnxruntime-web` stays an optional peer dependency — install it (and ship\n * the matching `.wasm` files) only when you use this subpath.\n *\n * Do not hand-edit — regenerate with `npm run vendor:vision`.\n */\n\nexport {\n BoundingBox,\n Mask,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n type SegmentationResult,\n} from \"./types\";\n\nexport {\n Boxes,\n ClassificationResults,\n DetectClassifyResults,\n DetectionResults,\n Masks,\n Probs,\n SegmentationResults,\n} from \"./results\";\n\nexport {\n COCO_CLASSES,\n type LabelSpec,\n type ResolveLabelsOptions,\n defaultLabels,\n resolveLabels,\n} from \"./labels\";\n\nexport {\n FusionError,\n ImageLoadError,\n InferenceError,\n LabelMapError,\n ModelLoadError,\n NoDetectionsError,\n OrtVisionError,\n ProviderNotAvailableError,\n} from \"./core/exceptions\";\n\nexport { type ModelSource, type OrtSessionOptions, OrtSession } from \"./core/session\";\nexport {\n type DeclaredDim,\n type DeclaredShape,\n type ResolveInputSizeOptions,\n classificationNumClasses,\n declaredShapesFrom,\n detectionNumClasses,\n resolveInputSize,\n spatialInputSize,\n} from \"./core/graph\";\nexport { modelNames, parseNames, readModelMetadata } from \"./core/metadata\";\nexport { DEFAULT_PROVIDERS, resolveProviders } from \"./core/providers\";\nexport { type Speed, SpeedTimer } from \"./core/timing\";\n\nexport { type ImageInput, loadImage } from \"./io/image\";\n\nexport {\n FUSION_KIND_DETECT_CLASSIFY,\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n METADATA_PREFIX,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type CropSource,\n type FusionSpec,\n readFusionSpec,\n} from \"./fusion\";\n\nexport {\n type LetterboxResult,\n fromCv2,\n letterbox,\n normalize,\n resize,\n toCHW,\n toCv2,\n toFloat32,\n toFloat32Tensor,\n toTensor,\n} from \"./preprocess/image\";\n\nexport {\n type FusedLetterboxResult,\n type FusedResizeResult,\n LetterboxPipeline,\n ResizePipeline,\n letterboxToTensorData,\n resizeToTensorData,\n writePlanarFloat32,\n zeroTensorData,\n} from \"./preprocess/pipeline\";\n\nexport { type TopKResult, softmax, topK } from \"./postprocess/classification\";\n\nexport {\n type DecodeYoloAnchorsOptions,\n type DecodeYoloOptions,\n type DecodedAnchors,\n type DecodedDetection,\n batchedNms,\n decodeYolo,\n decodeYoloAnchors,\n nms,\n} from \"./postprocess/detection\";\n\nexport {\n type DecodeYoloSegOptions,\n type DecodedSegmentation,\n decodeYoloSeg,\n} from \"./postprocess/segmentation\";\n\nexport { VisionTask, requireDetections } from \"./tasks/base\";\nexport {\n type ClassifierOptions,\n type ClassifierPredictOptions,\n Classifier,\n} from \"./tasks/classifier\";\nexport {\n type DetectClassifyOptions,\n type DetectClassifyPredictOptions,\n DetectClassify,\n} from \"./tasks/detectClassify\";\nexport {\n type DetectorHead,\n type DetectorOptions,\n type DetectorPredictOptions,\n Detector,\n} from \"./tasks/detector\";\nexport {\n type SegmenterHead,\n type SegmenterOptions,\n type SegmenterPredictOptions,\n Segmenter,\n} from \"./tasks/segmenter\";\n\nexport const VERSION: string = \"0.7.1\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwJA,IAAa,KAAkB"}
|
|
@@ -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;
|
|
1
|
+
const e=require("../core/canvas.cjs");var t=1/255,n=class{_size;_buffer;_inUse=!1;constructor(e){this._size=e,this._buffer=new Float32Array(e)}claim(){return this._inUse?{data:new Float32Array(this._size),reused:!1}:(this._buffer.length!==this._size&&(this._buffer=new Float32Array(this._size)),this._inUse=!0,{data:this._buffer,reused:!0})}release(){this._inUse=!1}},r=class{_targetWidth;_targetHeight;_fill;_target;_targetContext;_buffer;_source=null;_sourceContext=null;constructor(t,r,i=[114,114,114]){if(t<=0||r<=0)throw Error(`Invalid letterbox target ${t}x${r}.`);this._targetWidth=t,this._targetHeight=r,this._fill=i,this._target=e.createCanvas(t,r),this._targetContext=e.get2DContext(this._target,{willReadFrequently:!0}),this._targetContext.imageSmoothingEnabled=!0,this._targetContext.imageSmoothingQuality=`high`,this._buffer=new n(3*r*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,{data:f,reused:p}=this._buffer.claim(),m=r*i;for(let e=0,n=0;e<m;e++,n+=4)f[e]=d[n]*t,f[m+e]=d[n+1]*t,f[2*m+e]=d[n+2]*t;return{data:f,scale:a,padLeft:c,padTop:l,reused:p}}release(){this._buffer.release()}_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 i(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 a=class{_targetWidth;_targetHeight;_mean;_std;_buffer;_target=null;_targetContext=null;_source=null;_sourceContext=null;constructor(e,t,r=[0,0,0],i=[1,1,1]){if(e<=0||t<=0)throw Error(`Invalid resize target ${e}x${t}.`);this._targetWidth=e,this._targetHeight=t,this._mean=r,this._std=i,this._buffer=new n(3*t*e)}get targetSize(){return[this._targetWidth,this._targetHeight]}run(t){let n=this._targetWidth,r=this._targetHeight,{data:a,reused:o}=this._buffer.claim();if(t.width===n&&t.height===r)return i(t.data,n,r,this._mean,this._std,a,3),{data:a,reused:o};this._ensureSource(t.width,t.height).putImageData(e.rgbToImageData(t),0,0);let s=this._ensureTarget();s.drawImage(this._source,0,0,n,r);let c=s.getImageData(0,0,n,r).data;return i(c,n,r,this._mean,this._std,a),{data:a,reused:o}}release(){this._buffer.release()}_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 o(e,t,n,r=[0,0,0],i=[1,1,1]){return new a(t,n,r,i).run(e)}function s(e,t){return new Float32Array(3*t*e)}function c(e,t,n,i=[114,114,114]){return new r(t,n,i).run(e)}exports.LetterboxPipeline=r,exports.ResizePipeline=a,exports.letterboxToTensorData=c,exports.resizeToTensorData=o,exports.writePlanarFloat32=i,exports.zeroTensorData=s;
|
|
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 * 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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"}
|
|
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/** One claim on a {@link ReusableBuffer}. */\ninterface BufferClaim {\n /** The buffer to write into, always `size` long. */\n readonly data: Float32Array;\n /** Whether {@link data} is the held buffer rather than a fresh allocation. */\n readonly reused: boolean;\n}\n\n/**\n * A `Float32Array` held across calls, handed out one claim at a time.\n *\n * Both pipelines want the same thing — allocate once, write into it every frame,\n * and fall back to a fresh array when a previous result has not been released\n * yet — so the bookkeeping lives here instead of twice.\n *\n * The part that is not obvious is {@link claim} re-allocating a buffer that is\n * *detached*. `ort.env.wasm.proxy` runs ONNX Runtime in a worker and posts the\n * input tensors with their `ArrayBuffer`s in the transfer list, which detaches\n * them on this side. A detached `Float32Array` is silently 0 long: writing to it\n * is a no-op, and the next `InferenceSession.run` rejects with\n * `Tensor's size(N) does not match data length(0)` on every other call — once for\n * the detached buffer, then the throw leaves the claim outstanding so the call\n * after it allocates and succeeds. Treating a buffer that changed length as spent\n * turns that into one extra allocation per transfer, which is what reuse was\n * avoiding, and keeps it correct for any consumer that transfers the tensor\n * rather than copying it.\n */\nclass ReusableBuffer {\n private readonly _size: number;\n private _buffer: Float32Array;\n private _inUse = false;\n\n /** @param size Length in floats of every buffer this hands out. */\n constructor(size: number) {\n this._size = size;\n this._buffer = new Float32Array(size);\n }\n\n /**\n * Take the held buffer, or a fresh one when it is unavailable.\n *\n * Unavailable means either still checked out by an unreleased claim, or\n * detached by whoever it was handed to. The first case allocates for this call\n * only; the second replaces the held buffer, so the allocation is paid once per\n * transfer rather than on every call after it.\n */\n claim(): BufferClaim {\n if (this._inUse) return { data: new Float32Array(this._size), reused: false };\n if (this._buffer.length !== this._size) this._buffer = new Float32Array(this._size);\n this._inUse = true;\n return { data: this._buffer, reused: true };\n }\n\n /** Mark the held buffer free for the next {@link claim}. */\n release(): void {\n this._inUse = false;\n }\n}\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: ReusableBuffer;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\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 ReusableBuffer(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. A buffer that was\n * detached by a consumer that transferred it is replaced rather than written\n * into — see {@link ReusableBuffer}.\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 { data, reused } = this._buffer.claim();\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._buffer.release();\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: ReusableBuffer;\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\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 ReusableBuffer(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 * Buffer reuse follows {@link ReusableBuffer}: held across calls, replaced when\n * a consumer detached it by transferring the tensor.\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 { data, reused } = this._buffer.claim();\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._buffer.release();\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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"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { createCanvas as e, get2DContext as t, rgbToImageData as n } from "../core/canvas.js";
|
|
2
2
|
//#region src/vision/preprocess/pipeline.ts
|
|
3
3
|
var r = 1 / 255, i = class {
|
|
4
|
+
_size;
|
|
5
|
+
_buffer;
|
|
6
|
+
_inUse = !1;
|
|
7
|
+
constructor(e) {
|
|
8
|
+
this._size = e, this._buffer = new Float32Array(e);
|
|
9
|
+
}
|
|
10
|
+
claim() {
|
|
11
|
+
return this._inUse ? {
|
|
12
|
+
data: new Float32Array(this._size),
|
|
13
|
+
reused: !1
|
|
14
|
+
} : (this._buffer.length !== this._size && (this._buffer = new Float32Array(this._size)), this._inUse = !0, {
|
|
15
|
+
data: this._buffer,
|
|
16
|
+
reused: !0
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
release() {
|
|
20
|
+
this._inUse = !1;
|
|
21
|
+
}
|
|
22
|
+
}, a = class {
|
|
4
23
|
_targetWidth;
|
|
5
24
|
_targetHeight;
|
|
6
25
|
_fill;
|
|
@@ -9,14 +28,13 @@ var r = 1 / 255, i = class {
|
|
|
9
28
|
_buffer;
|
|
10
29
|
_source = null;
|
|
11
30
|
_sourceContext = null;
|
|
12
|
-
|
|
13
|
-
constructor(n, r, i = [
|
|
31
|
+
constructor(n, r, a = [
|
|
14
32
|
114,
|
|
15
33
|
114,
|
|
16
34
|
114
|
|
17
35
|
]) {
|
|
18
36
|
if (n <= 0 || r <= 0) throw Error(`Invalid letterbox target ${n}x${r}.`);
|
|
19
|
-
this._targetWidth = n, this._targetHeight = r, this._fill =
|
|
37
|
+
this._targetWidth = n, this._targetHeight = r, this._fill = a, this._target = e(n, r), this._targetContext = t(this._target, { willReadFrequently: !0 }), this._targetContext.imageSmoothingEnabled = !0, this._targetContext.imageSmoothingQuality = "high", this._buffer = new i(3 * r * n);
|
|
20
38
|
}
|
|
21
39
|
get targetSize() {
|
|
22
40
|
return [this._targetWidth, this._targetHeight];
|
|
@@ -26,30 +44,28 @@ var r = 1 / 255, i = class {
|
|
|
26
44
|
this._ensureSource(e.width, e.height).putImageData(n(e), 0, 0);
|
|
27
45
|
let u = this._targetContext;
|
|
28
46
|
(c > 0 || l > 0 || o !== t || s !== i) && (u.fillStyle = `rgb(${this._fill[0]},${this._fill[1]},${this._fill[2]})`, u.fillRect(0, 0, t, i)), u.drawImage(this._source, 0, 0, e.width, e.height, c, l, o, s);
|
|
29
|
-
let d = u.getImageData(0, 0, t, i).data,
|
|
30
|
-
|
|
31
|
-
let m = t * i;
|
|
32
|
-
for (let e = 0, t = 0; e < m; e++, t += 4) p[e] = d[t] * r, p[m + e] = d[t + 1] * r, p[2 * m + e] = d[t + 2] * r;
|
|
47
|
+
let d = u.getImageData(0, 0, t, i).data, { data: f, reused: p } = this._buffer.claim(), m = t * i;
|
|
48
|
+
for (let e = 0, t = 0; e < m; e++, t += 4) f[e] = d[t] * r, f[m + e] = d[t + 1] * r, f[2 * m + e] = d[t + 2] * r;
|
|
33
49
|
return {
|
|
34
|
-
data:
|
|
50
|
+
data: f,
|
|
35
51
|
scale: a,
|
|
36
52
|
padLeft: c,
|
|
37
53
|
padTop: l,
|
|
38
|
-
reused:
|
|
54
|
+
reused: p
|
|
39
55
|
};
|
|
40
56
|
}
|
|
41
57
|
release() {
|
|
42
|
-
this.
|
|
58
|
+
this._buffer.release();
|
|
43
59
|
}
|
|
44
60
|
_ensureSource(n, r) {
|
|
45
61
|
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
62
|
}
|
|
47
63
|
};
|
|
48
|
-
function
|
|
64
|
+
function o(e, t, n, i, a, o, s = 4) {
|
|
49
65
|
let c = t * n, l = i[0], u = i[1], d = i[2], f = a[0], p = a[1], m = a[2];
|
|
50
66
|
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
67
|
}
|
|
52
|
-
var
|
|
68
|
+
var s = class {
|
|
53
69
|
_targetWidth;
|
|
54
70
|
_targetHeight;
|
|
55
71
|
_mean;
|
|
@@ -59,7 +75,6 @@ var o = class {
|
|
|
59
75
|
_targetContext = null;
|
|
60
76
|
_source = null;
|
|
61
77
|
_sourceContext = null;
|
|
62
|
-
_bufferInUse = !1;
|
|
63
78
|
constructor(e, t, n = [
|
|
64
79
|
0,
|
|
65
80
|
0,
|
|
@@ -70,28 +85,28 @@ var o = class {
|
|
|
70
85
|
1
|
|
71
86
|
]) {
|
|
72
87
|
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
|
|
88
|
+
this._targetWidth = e, this._targetHeight = t, this._mean = n, this._std = r, this._buffer = new i(3 * t * e);
|
|
74
89
|
}
|
|
75
90
|
get targetSize() {
|
|
76
91
|
return [this._targetWidth, this._targetHeight];
|
|
77
92
|
}
|
|
78
93
|
run(e) {
|
|
79
|
-
let t = this._targetWidth, r = this._targetHeight,
|
|
80
|
-
if (
|
|
81
|
-
data:
|
|
82
|
-
reused:
|
|
94
|
+
let t = this._targetWidth, r = this._targetHeight, { data: i, reused: a } = this._buffer.claim();
|
|
95
|
+
if (e.width === t && e.height === r) return o(e.data, t, r, this._mean, this._std, i, 3), {
|
|
96
|
+
data: i,
|
|
97
|
+
reused: a
|
|
83
98
|
};
|
|
84
99
|
this._ensureSource(e.width, e.height).putImageData(n(e), 0, 0);
|
|
85
100
|
let s = this._ensureTarget();
|
|
86
101
|
s.drawImage(this._source, 0, 0, t, r);
|
|
87
102
|
let c = s.getImageData(0, 0, t, r).data;
|
|
88
|
-
return
|
|
89
|
-
data:
|
|
90
|
-
reused:
|
|
103
|
+
return o(c, t, r, this._mean, this._std, i), {
|
|
104
|
+
data: i,
|
|
105
|
+
reused: a
|
|
91
106
|
};
|
|
92
107
|
}
|
|
93
108
|
release() {
|
|
94
|
-
this.
|
|
109
|
+
this._buffer.release();
|
|
95
110
|
}
|
|
96
111
|
_ensureTarget() {
|
|
97
112
|
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;
|
|
@@ -100,7 +115,7 @@ var o = class {
|
|
|
100
115
|
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
116
|
}
|
|
102
117
|
};
|
|
103
|
-
function
|
|
118
|
+
function c(e, t, n, r = [
|
|
104
119
|
0,
|
|
105
120
|
0,
|
|
106
121
|
0
|
|
@@ -109,19 +124,19 @@ function s(e, t, n, r = [
|
|
|
109
124
|
1,
|
|
110
125
|
1
|
|
111
126
|
]) {
|
|
112
|
-
return new
|
|
127
|
+
return new s(t, n, r, i).run(e);
|
|
113
128
|
}
|
|
114
|
-
function
|
|
129
|
+
function l(e, t) {
|
|
115
130
|
return new Float32Array(3 * t * e);
|
|
116
131
|
}
|
|
117
|
-
function
|
|
132
|
+
function u(e, t, n, r = [
|
|
118
133
|
114,
|
|
119
134
|
114,
|
|
120
135
|
114
|
|
121
136
|
]) {
|
|
122
|
-
return new
|
|
137
|
+
return new a(t, n, r).run(e);
|
|
123
138
|
}
|
|
124
139
|
//#endregion
|
|
125
|
-
export {
|
|
140
|
+
export { a as LetterboxPipeline, s as ResizePipeline, u as letterboxToTensorData, c as resizeToTensorData, o as writePlanarFloat32, l as zeroTensorData };
|
|
126
141
|
|
|
127
142
|
//# 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 * 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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"}
|
|
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/** One claim on a {@link ReusableBuffer}. */\ninterface BufferClaim {\n /** The buffer to write into, always `size` long. */\n readonly data: Float32Array;\n /** Whether {@link data} is the held buffer rather than a fresh allocation. */\n readonly reused: boolean;\n}\n\n/**\n * A `Float32Array` held across calls, handed out one claim at a time.\n *\n * Both pipelines want the same thing — allocate once, write into it every frame,\n * and fall back to a fresh array when a previous result has not been released\n * yet — so the bookkeeping lives here instead of twice.\n *\n * The part that is not obvious is {@link claim} re-allocating a buffer that is\n * *detached*. `ort.env.wasm.proxy` runs ONNX Runtime in a worker and posts the\n * input tensors with their `ArrayBuffer`s in the transfer list, which detaches\n * them on this side. A detached `Float32Array` is silently 0 long: writing to it\n * is a no-op, and the next `InferenceSession.run` rejects with\n * `Tensor's size(N) does not match data length(0)` on every other call — once for\n * the detached buffer, then the throw leaves the claim outstanding so the call\n * after it allocates and succeeds. Treating a buffer that changed length as spent\n * turns that into one extra allocation per transfer, which is what reuse was\n * avoiding, and keeps it correct for any consumer that transfers the tensor\n * rather than copying it.\n */\nclass ReusableBuffer {\n private readonly _size: number;\n private _buffer: Float32Array;\n private _inUse = false;\n\n /** @param size Length in floats of every buffer this hands out. */\n constructor(size: number) {\n this._size = size;\n this._buffer = new Float32Array(size);\n }\n\n /**\n * Take the held buffer, or a fresh one when it is unavailable.\n *\n * Unavailable means either still checked out by an unreleased claim, or\n * detached by whoever it was handed to. The first case allocates for this call\n * only; the second replaces the held buffer, so the allocation is paid once per\n * transfer rather than on every call after it.\n */\n claim(): BufferClaim {\n if (this._inUse) return { data: new Float32Array(this._size), reused: false };\n if (this._buffer.length !== this._size) this._buffer = new Float32Array(this._size);\n this._inUse = true;\n return { data: this._buffer, reused: true };\n }\n\n /** Mark the held buffer free for the next {@link claim}. */\n release(): void {\n this._inUse = false;\n }\n}\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: ReusableBuffer;\n private _source: Canvas2D | null = null;\n private _sourceContext: Context2D | null = null;\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 ReusableBuffer(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. A buffer that was\n * detached by a consumer that transferred it is replaced rather than written\n * into — see {@link ReusableBuffer}.\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 { data, reused } = this._buffer.claim();\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._buffer.release();\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: ReusableBuffer;\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\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 ReusableBuffer(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 * Buffer reuse follows {@link ReusableBuffer}: held across calls, replaced when\n * a consumer detached it by transferring the tensor.\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 { data, reused } = this._buffer.claim();\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._buffer.release();\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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"}
|
package/dist/vision.d.ts
CHANGED
|
@@ -1126,7 +1126,6 @@ export declare const DEFAULT_PROVIDERS: readonly string[];
|
|
|
1126
1126
|
private readonly _buffer;
|
|
1127
1127
|
private _source;
|
|
1128
1128
|
private _sourceContext;
|
|
1129
|
-
private _bufferInUse;
|
|
1130
1129
|
/**
|
|
1131
1130
|
* @param targetWidth Model input width in pixels.
|
|
1132
1131
|
* @param targetHeight Model input height in pixels.
|
|
@@ -1142,7 +1141,9 @@ export declare const DEFAULT_PROVIDERS: readonly string[];
|
|
|
1142
1141
|
* still checked out — {@link release} marks it free again. A second `run`
|
|
1143
1142
|
* before the first is released allocates a fresh buffer rather than
|
|
1144
1143
|
* corrupting it, so concurrent `predict()` calls on one task stay correct at
|
|
1145
|
-
* the cost of the allocation they were trying to avoid.
|
|
1144
|
+
* the cost of the allocation they were trying to avoid. A buffer that was
|
|
1145
|
+
* detached by a consumer that transferred it is replaced rather than written
|
|
1146
|
+
* into — see {@link ReusableBuffer}.
|
|
1146
1147
|
*
|
|
1147
1148
|
* @param image Source image in the SDK's canonical HWC RGB layout.
|
|
1148
1149
|
*/
|
|
@@ -1635,7 +1636,6 @@ export declare const DEFAULT_PROVIDERS: readonly string[];
|
|
|
1635
1636
|
private _targetContext;
|
|
1636
1637
|
private _source;
|
|
1637
1638
|
private _sourceContext;
|
|
1638
|
-
private _bufferInUse;
|
|
1639
1639
|
/**
|
|
1640
1640
|
* @param targetWidth Model input width in pixels.
|
|
1641
1641
|
* @param targetHeight Model input height in pixels.
|
|
@@ -1653,6 +1653,9 @@ export declare const DEFAULT_PROVIDERS: readonly string[];
|
|
|
1653
1653
|
* keeps the result identical to `resize()`, whose own fast path returns the
|
|
1654
1654
|
* input untouched.
|
|
1655
1655
|
*
|
|
1656
|
+
* Buffer reuse follows {@link ReusableBuffer}: held across calls, replaced when
|
|
1657
|
+
* a consumer detached it by transferring the tensor.
|
|
1658
|
+
*
|
|
1656
1659
|
* @param image Source image in the SDK's canonical HWC RGB layout.
|
|
1657
1660
|
*/
|
|
1658
1661
|
run(image: RGBImage): FusedResizeResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tempest-react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -138,7 +138,6 @@
|
|
|
138
138
|
"fflate": "^0.8.3",
|
|
139
139
|
"lucide-react": "^1.31.0",
|
|
140
140
|
"react-hook-form": "^7.76.0",
|
|
141
|
-
"react-router": "^8.3.0",
|
|
142
141
|
"zod": "^3.23.0 || ^4.0.0",
|
|
143
142
|
"zustand": "^4.0.0 || ^5.0.0"
|
|
144
143
|
},
|
|
@@ -150,6 +149,7 @@
|
|
|
150
149
|
"onnxruntime-web": ">=1.17.0",
|
|
151
150
|
"react": "^18.0.0 || ^19.0.0",
|
|
152
151
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
152
|
+
"react-router": "^7.0.0 || ^8.0.0",
|
|
153
153
|
"recharts": "^2.0.0 || ^3.0.0",
|
|
154
154
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
155
155
|
},
|
|
@@ -212,6 +212,7 @@
|
|
|
212
212
|
"react": "^19.1.0",
|
|
213
213
|
"react-dom": "^19.1.0",
|
|
214
214
|
"react-hook-form": "^7.76.0",
|
|
215
|
+
"react-router": "^8.3.0",
|
|
215
216
|
"recharts": "^3.9.0",
|
|
216
217
|
"size-limit": "^13.0.1",
|
|
217
218
|
"typescript": "~6.0.3",
|