onnxruntime/js/common/lib/tensor-factory-impl.ts
Yulong Wang abdc31de40
[js] change default formatter for JavaScript/TypeScript from clang-format to Prettier (#21728)
### Description

See
454996d496
for manual changes (excluded auto-generated formatting changes)

### Why

Because the toolsets for old clang-format is out-of-date. This reduces
the development efficiency.

- The NPM package `clang-format` is already in maintenance mode. not
updated since 2 years ago.
- The VSCode extension for clang-format is not maintained for a while,
and a recent Node.js security update made it not working at all in
Windows.

No one in community seems interested in fixing those.

Choose Prettier as it is the most popular TS/JS formatter.

### How to merge

It's easy to break the build:
- Be careful of any new commits on main not included in this PR.
- Be careful that after this PR is merged, other PRs that already passed
CI can merge.

So, make sure there is no new commits before merging this one, and
invalidate js PRs that already passed CI, force them to merge to latest.
2024-08-14 16:51:22 -07:00

320 lines
11 KiB
TypeScript

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {
OptionsDimensions,
OptionsFormat,
OptionsNormalizationParameters,
OptionsTensorFormat,
OptionsTensorLayout,
TensorFromGpuBufferOptions,
TensorFromImageBitmapOptions,
TensorFromImageDataOptions,
TensorFromImageElementOptions,
TensorFromTextureOptions,
TensorFromUrlOptions,
} from './tensor-factory.js';
import { Tensor } from './tensor-impl.js';
import { Tensor as TensorInterface } from './tensor.js';
interface BufferToTensorOptions
extends OptionsDimensions,
OptionsTensorLayout,
OptionsNormalizationParameters,
OptionsFormat,
OptionsTensorFormat {}
/**
* Create a new tensor object from image object
*
* @param buffer - Extracted image buffer data - assuming RGBA format
* @param imageFormat - input image configuration - required configurations height, width, format
* @param tensorFormat - output tensor configuration - Default is RGB format
*/
export const bufferToTensor = (buffer: Uint8ClampedArray | undefined, options: BufferToTensorOptions): Tensor => {
if (buffer === undefined) {
throw new Error('Image buffer must be defined');
}
if (options.height === undefined || options.width === undefined) {
throw new Error('Image height and width must be defined');
}
if (options.tensorLayout === 'NHWC') {
throw new Error('NHWC Tensor layout is not supported yet');
}
const { height, width } = options;
const norm = options.norm ?? { mean: 255, bias: 0 };
let normMean: [number, number, number, number];
let normBias: [number, number, number, number];
if (typeof norm.mean === 'number') {
normMean = [norm.mean, norm.mean, norm.mean, norm.mean];
} else {
normMean = [norm.mean![0], norm.mean![1], norm.mean![2], norm.mean![3] ?? 255];
}
if (typeof norm.bias === 'number') {
normBias = [norm.bias, norm.bias, norm.bias, norm.bias];
} else {
normBias = [norm.bias![0], norm.bias![1], norm.bias![2], norm.bias![3] ?? 0];
}
const inputformat = options.format !== undefined ? options.format : 'RGBA';
// default value is RGBA since imagedata and HTMLImageElement uses it
const outputformat =
options.tensorFormat !== undefined ? (options.tensorFormat !== undefined ? options.tensorFormat : 'RGB') : 'RGB';
const stride = height * width;
const float32Data = outputformat === 'RGBA' ? new Float32Array(stride * 4) : new Float32Array(stride * 3);
// Default pointer assignments
let step = 4,
rImagePointer = 0,
gImagePointer = 1,
bImagePointer = 2,
aImagePointer = 3;
let rTensorPointer = 0,
gTensorPointer = stride,
bTensorPointer = stride * 2,
aTensorPointer = -1;
// Updating the pointer assignments based on the input image format
if (inputformat === 'RGB') {
step = 3;
rImagePointer = 0;
gImagePointer = 1;
bImagePointer = 2;
aImagePointer = -1;
}
// Updating the pointer assignments based on the output tensor format
if (outputformat === 'RGBA') {
aTensorPointer = stride * 3;
} else if (outputformat === 'RBG') {
rTensorPointer = 0;
bTensorPointer = stride;
gTensorPointer = stride * 2;
} else if (outputformat === 'BGR') {
bTensorPointer = 0;
gTensorPointer = stride;
rTensorPointer = stride * 2;
}
for (
let i = 0;
i < stride;
i++, rImagePointer += step, bImagePointer += step, gImagePointer += step, aImagePointer += step
) {
float32Data[rTensorPointer++] = (buffer[rImagePointer] + normBias[0]) / normMean[0];
float32Data[gTensorPointer++] = (buffer[gImagePointer] + normBias[1]) / normMean[1];
float32Data[bTensorPointer++] = (buffer[bImagePointer] + normBias[2]) / normMean[2];
if (aTensorPointer !== -1 && aImagePointer !== -1) {
float32Data[aTensorPointer++] = (buffer[aImagePointer] + normBias[3]) / normMean[3];
}
}
// Float32Array -> ort.Tensor
const outputTensor =
outputformat === 'RGBA'
? new Tensor('float32', float32Data, [1, 4, height, width])
: new Tensor('float32', float32Data, [1, 3, height, width]);
return outputTensor;
};
/**
* implementation of Tensor.fromImage().
*/
export const tensorFromImage = async (
image: ImageData | HTMLImageElement | ImageBitmap | string,
options?:
| TensorFromImageDataOptions
| TensorFromImageElementOptions
| TensorFromImageBitmapOptions
| TensorFromUrlOptions,
): Promise<Tensor> => {
// checking the type of image object
const isHTMLImageEle = typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement;
const isImageDataEle = typeof ImageData !== 'undefined' && image instanceof ImageData;
const isImageBitmap = typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap;
const isString = typeof image === 'string';
let data: Uint8ClampedArray | undefined;
let bufferToTensorOptions: BufferToTensorOptions = options ?? {};
const createCanvas = () => {
if (typeof document !== 'undefined') {
return document.createElement('canvas');
} else if (typeof OffscreenCanvas !== 'undefined') {
return new OffscreenCanvas(1, 1);
} else {
throw new Error('Canvas is not supported');
}
};
const createCanvasContext = (canvas: HTMLCanvasElement | OffscreenCanvas) => {
if (canvas instanceof HTMLCanvasElement) {
return canvas.getContext('2d');
} else if (canvas instanceof OffscreenCanvas) {
return canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;
} else {
return null;
}
};
// filling and checking image configuration options
if (isHTMLImageEle) {
// HTMLImageElement - image object - format is RGBA by default
const canvas = createCanvas();
canvas.width = image.width;
canvas.height = image.height;
const pixels2DContext = createCanvasContext(canvas);
if (pixels2DContext != null) {
let height = image.height;
let width = image.width;
if (options !== undefined && options.resizedHeight !== undefined && options.resizedWidth !== undefined) {
height = options.resizedHeight;
width = options.resizedWidth;
}
if (options !== undefined) {
bufferToTensorOptions = options;
if (options.tensorFormat !== undefined) {
throw new Error('Image input config format must be RGBA for HTMLImageElement');
} else {
bufferToTensorOptions.tensorFormat = 'RGBA';
}
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
} else {
bufferToTensorOptions.tensorFormat = 'RGBA';
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
}
pixels2DContext.drawImage(image, 0, 0);
data = pixels2DContext.getImageData(0, 0, width, height).data;
} else {
throw new Error('Can not access image data');
}
} else if (isImageDataEle) {
let height: number;
let width: number;
if (options !== undefined && options.resizedWidth !== undefined && options.resizedHeight !== undefined) {
height = options.resizedHeight;
width = options.resizedWidth;
} else {
height = image.height;
width = image.width;
}
if (options !== undefined) {
bufferToTensorOptions = options;
}
bufferToTensorOptions.format = 'RGBA';
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
if (options !== undefined) {
const tempCanvas = createCanvas();
tempCanvas.width = width;
tempCanvas.height = height;
const pixels2DContext = createCanvasContext(tempCanvas);
if (pixels2DContext != null) {
pixels2DContext.putImageData(image, 0, 0);
data = pixels2DContext.getImageData(0, 0, width, height).data;
} else {
throw new Error('Can not access image data');
}
} else {
data = image.data;
}
} else if (isImageBitmap) {
// ImageBitmap - image object - format must be provided by user
if (options === undefined) {
throw new Error('Please provide image config with format for Imagebitmap');
}
const canvas = createCanvas();
canvas.width = image.width;
canvas.height = image.height;
const pixels2DContext = createCanvasContext(canvas);
if (pixels2DContext != null) {
const height = image.height;
const width = image.width;
pixels2DContext.drawImage(image, 0, 0, width, height);
data = pixels2DContext.getImageData(0, 0, width, height).data;
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
return bufferToTensor(data, bufferToTensorOptions);
} else {
throw new Error('Can not access image data');
}
} else if (isString) {
return new Promise((resolve, reject) => {
const canvas = createCanvas();
const context = createCanvasContext(canvas);
if (!image || !context) {
return reject();
}
const newImage = new Image();
newImage.crossOrigin = 'Anonymous';
newImage.src = image;
newImage.onload = () => {
canvas.width = newImage.width;
canvas.height = newImage.height;
context.drawImage(newImage, 0, 0, canvas.width, canvas.height);
const img = context.getImageData(0, 0, canvas.width, canvas.height);
bufferToTensorOptions.height = canvas.height;
bufferToTensorOptions.width = canvas.width;
resolve(bufferToTensor(img.data, bufferToTensorOptions));
};
});
} else {
throw new Error('Input data provided is not supported - aborted tensor creation');
}
if (data !== undefined) {
return bufferToTensor(data, bufferToTensorOptions);
} else {
throw new Error('Input data provided is not supported - aborted tensor creation');
}
};
/**
* implementation of Tensor.fromTexture().
*/
export const tensorFromTexture = <T extends TensorInterface.TextureDataTypes>(
texture: TensorInterface.TextureType,
options: TensorFromTextureOptions<T>,
): Tensor => {
const { width, height, download, dispose } = options;
// Always assume RGBAF32. TODO: support different texture format
const dims = [1, height, width, 4];
return new Tensor({ location: 'texture', type: 'float32', texture, dims, download, dispose });
};
/**
* implementation of Tensor.fromGpuBuffer().
*/
export const tensorFromGpuBuffer = <T extends TensorInterface.GpuBufferDataTypes>(
gpuBuffer: TensorInterface.GpuBufferType,
options: TensorFromGpuBufferOptions<T>,
): Tensor => {
const { dataType, dims, download, dispose } = options;
return new Tensor({ location: 'gpu-buffer', type: dataType ?? 'float32', gpuBuffer, dims, download, dispose });
};
/**
* implementation of Tensor.fromPinnedBuffer().
*/
export const tensorFromPinnedBuffer = <T extends TensorInterface.CpuPinnedDataTypes>(
type: T,
buffer: TensorInterface.DataTypeMap[T],
dims?: readonly number[],
): Tensor => new Tensor({ location: 'cpu-pinned', type, data: buffer, dims: dims ?? [buffer.length] });