@aislamov/onnxruntime-web64
Version:
A Javascript library for running ONNX models on browsers
580 lines (543 loc) • 28.6 kB
text/typescript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {TensorView} from '../../tensor';
import {ShapeUtil} from '../../util';
import {AttributeWithCacheKey, createAttributeWithCacheKey} from '../attribute-with-cache-key';
import {ComputeContext, GpuDataType, ProgramInfo, ProgramInfoLoader, ProgramMetadata} from '../types';
import {IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
type CoordinateTransformMode = 'half_pixel'|'asymmetric'|'pytorch_half_pixel'|'tf_half_pixel_for_nn'|'align_corners'|
'tf_crop_and_resize'|'half_pixel_symmetric';
type KeepAspectRatioPolicy = 'stretch'|'not_smaller'|'not_larger';
type Mode = 'nearest'|'linear'|'cubic';
type NearestMode = 'round_prefer_floor'|'round_prefer_ceil'|'floor'|'ceil'|'simple';
export interface ResizeAttributes extends AttributeWithCacheKey {
antialias: number;
axes: number[];
coordinateTransformMode: CoordinateTransformMode;
cubicCoeffA: number;
excludeOutside: boolean;
extrapolationValue: number;
keepAspectRatioPolicy: KeepAspectRatioPolicy;
mode: Mode;
nearestMode: NearestMode;
}
const validateScales = (scales: number[], attributes: ResizeAttributes): void => {
scales.every((value) => value > 0 || (() => {
throw new Error('Resize requires scales input values to be positive');
}));
// Check scales dims based on mode: LINEAR, CUBIC
if (scales.length > 0) {
if (attributes.mode === 'linear') {
if (!(scales.length === 2 || (scales.length === 4 && scales[0] === 1 && scales[1] === 1) ||
(scales.length === 4 && scales[0] === 1 && scales[3] === 1))) {
throw new Error('Resize requires scales input size to be 2 or 4 for linear mode');
}
} else if (attributes.mode === 'cubic') {
if (!(scales.length === 2 || (scales.length === 4 && scales[0] === 1 && scales[1] === 1) ||
(scales.length === 4 && scales[0] === 1 && scales[3] === 1))) {
throw new Error('Resize requires scales input size to be 2 or 4 for cubic mode');
}
}
}
};
const updateScales = (scales: readonly number[], axes: readonly number[], rank: number): number[] => {
axes.every((value) => value >= 0 && value < rank || (() => {
throw new Error('Resize requires axes input values to be positive and less than rank');
}));
const newScales = new Array(rank).fill(1.0);
axes.forEach((value, index) => newScales[value] = scales[index]);
return newScales;
};
const validateInputs =
(inputs: readonly TensorView[], attributes: ResizeAttributes, opsetVersion: number, scales: number[],
sizes: number[], roi: number[]): void => {
const [roiInputIndex, scalesInputIndex, sizesInputIndex] =
(opsetVersion > 10) ? [1, 2, 3] : [-1, (inputs.length > 1) ? 1 : -1, -1];
const rank = inputs[0].dims.length;
if (roiInputIndex > 0 && inputs.length > roiInputIndex && inputs[roiInputIndex].dims.length > 0) {
inputs[roiInputIndex].getFloat32Array().forEach((value) => roi.push(value));
} else if (attributes.coordinateTransformMode === 'tf_crop_and_resize') {
throw new Error('Resize requires RoI input to be specified when coordinateTransformMode is tfCropAndResize');
}
if (scalesInputIndex > 0 && inputs.length > scalesInputIndex && inputs[scalesInputIndex].dims.length > 0) {
inputs[scalesInputIndex].getFloat32Array().forEach((value) => scales.push(value));
if (scales.length !== 0 &&
(scales.length !== rank && (opsetVersion >= 18 && scales.length !== attributes.axes.length))) {
throw new Error(
'Resize requires scales input size to be same as input rank or axes size for opset 18 and up');
}
validateScales(scales, attributes);
if (attributes.axes.length > 0) {
updateScales(scales, attributes.axes, rank).forEach((value, index) => scales[index] = value);
}
}
if (sizesInputIndex > 0 && inputs.length > sizesInputIndex) {
inputs[sizesInputIndex].getBigInt64Array().forEach((value) => sizes.push(Number(value)));
if (sizes.length !== rank || (opsetVersion >= 18 && sizes.length === attributes.axes.length)) {
throw new Error('Resize requires sizes input size to be same as input rank or axes size for opset 18 and up');
}
}
if (attributes.axes.length > 0) {
if (scales.length !== attributes.axes.length) {
throw new Error('Resize requires "scales" input size to be of axes rank when axes attributes is specified');
}
if (sizes.length !== attributes.axes.length) {
throw new Error(
'Resize requires "sizes" input size to be of rank axes rank when axes attributes is specified');
}
}
if (typeof scales !== 'undefined' && typeof sizes !== 'undefined' && scales.length > 0 && sizes.length > rank) {
throw new Error('Resize requires only of scales or sizes to be specified');
}
};
const getOriginalCoordinateFromResizedCoordinate = (coordinateTransferMode: CoordinateTransformMode, dType: string): string =>
`fn getOriginalCoordinateFromResizedCoordinate(xResized: ${dType}, xScale: ${dType}, lengthResized: ${dType},
lengthOriginal: ${dType}, roiStart: ${dType}, roiEnd: ${dType}) -> ${dType} { ` +
(() => {
switch (coordinateTransferMode) {
case 'asymmetric':
return 'return xResized / xScale;';
case 'pytorch_half_pixel':
return 'if (lengthResized > 1) { \
return (xResized + 0.5) / xScale - 0.5; \
} else { \
return 0.0; \
}';
case 'tf_half_pixel_for_nn':
return 'return (xResized + 0.5) / xScale;';
case 'align_corners':
return 'if (lengthResized == 1) { \
return 0.0; \
} else { \
return xResized * (lengthOriginal - 1) / (lengthResized - 1); \
}';
case 'tf_crop_and_resize':
return `if (lengthResized > 1) { \
return roiStart * (lengthOriginal - 1) + \
(xResized * (roiEnd - roiStart) * (lengthOriginal - 1)) / (lengthResized - 1); \
} else { \
return 0.5 * (roiStart + roiEnd) * ${dType}(lengthOriginal - 1); \
}`;
case 'half_pixel_symmetric':
return [
'const outputWidth = xScale * lengthResized;', 'const adjustment = lengthResized / outputWidth;',
'const center = lengthOriginal / 2;', 'const offset = center * (1 - adjustment);',
'return offset + ((xResized + 0.5) / xScale) - 0.5;'
].join('\n');
case 'half_pixel':
return 'return ((xResized + 0.5) / xScale) - 0.5;';
default:
throw new Error(`Coordinate transform mode ${coordinateTransferMode} is not supported`);
}
})() +
'}';
const getNearestPixelFromOriginal = (nearestMode: NearestMode, opsetVersion: number, dType: string): string =>
`fn getNearestPixelFromOriginal(xOriginal: ${dType}, isDownSample: bool) -> ${dType} {` + (() => {
switch (nearestMode) {
case 'round_prefer_ceil':
return 'if (fract(xOriginal) == 0.5) { \
return ceil(xOriginal); \
} else { \
return round(xOriginal); \
}';
case 'floor':
return 'return floor(xOriginal);';
case 'ceil':
return 'return ceil(xOriginal);';
case 'round_prefer_floor':
return 'if (fract(xOriginal) == 0.5) { \
return floor(xOriginal); \
} else { \
return round(xOriginal); \
}';
case 'simple':
default:
if (opsetVersion < 11) {
return 'if (isDownSample) \
{ \
return ceil(xOriginal); \
} else { \
return xOriginal; \
}';
}
throw new Error(`Nearest mode ${nearestMode} is not supported`);
}
})() +
'}';
const updateRoI = (roi: readonly number[], axes: readonly number[], rank: number): number[] => {
const roiTmp = new Array(rank).fill(0).concat(new Array(rank).fill(1));
const roiLocal = roi.length === 0 ? roiTmp : roi.slice();
if (axes.length > 0) {
axes.forEach((v, i) => {
roiTmp[v] = roiLocal[i];
roiTmp[i + rank] = roiLocal[axes.length + i];
});
return roiTmp;
}
return roiLocal;
};
const initOutputShape =
(inputShape: readonly number[], scales: readonly number[], sizes: readonly number[], axes: readonly number[]):
number[] => {
let outputShape: number[] = [];
if (sizes.length > 0) {
if (axes.length > 0) {
inputShape.forEach((v) => outputShape.push(v));
if (Math.max(...axes) > inputShape.length) {
throw new Error('axes is out of bound');
}
axes.forEach((v, i) => outputShape[v] = sizes[i]);
} else {
sizes.forEach((v) => outputShape.push(v));
}
} else {
if (scales.length === 0) {
throw new Error('Resize requires either scales or sizes.');
} else {
outputShape = inputShape.map((value, index) => Math.round(value * scales[index]));
}
}
return outputShape;
};
const adjustOutputShape =
(inputShape: readonly number[], outputShape: readonly number[], scales: number[], attributes: ResizeAttributes):
number[] => {
const scaleInPolicy = (() => {
switch (attributes.keepAspectRatioPolicy) {
case 'not_larger':
return attributes.axes.length > 0 ? Math.min(...attributes.axes.map(i => scales[i]), Number.MAX_VALUE) :
Math.min(...scales, Number.MAX_VALUE);
case 'not_smaller':
return attributes.axes.length > 0 ? Math.max(...attributes.axes.map(i => scales[i]), Number.MIN_VALUE) :
Math.max(...scales, Number.MIN_VALUE);
default:
throw new Error(`Keep aspect ratio policy ${attributes.keepAspectRatioPolicy} is not supported`);
}
})();
scales.fill(1.0, 0, scales.length);
const adjustedOutputShape = inputShape.slice();
if (attributes.axes.length > 0) {
attributes.axes.forEach((v) => scales[v] = scaleInPolicy);
attributes.axes.forEach((v) => adjustedOutputShape[v] = Math.round(inputShape[v] * scales[v]));
} else {
scales.fill(scaleInPolicy, 0, scales.length);
adjustedOutputShape.forEach((v, i) => adjustedOutputShape[i] = Math.round(v * scales[i]));
}
return adjustedOutputShape;
};
const calculateOriginalIndicesFromOutputIndices =
(output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[], scales: readonly number[],
roi: readonly number[]): string => `
fn calculateOriginalIndicesFromOutputIndices(outputIndices: ${output.type.indices}) -> array<${output.type.value}, ${outputShape.length}> {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
const outputShape = array<u32, ${outputShape.length}>(${outputShape.map(i => `${i}u`).join(',')});
const scales = array<${output.type.value}, ${scales.length}>(${scales.map(i => `${i}f`).join(',')});
const roi = array<${output.type.value}, ${roi.length}>(${roi.map(i => `${i}f`).join(',')});
var originalIndices: array<${output.type.value}, ${outputShape.length}>;
for (var i:u32 = 0; i < ${outputShape.length}; i++) {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : 'outputIndices[i]'};
if (scales[i] == 1.0) {
originalIndices[i] = ${output.type.value}(outputIndex);
} else {
originalIndices[i] = getOriginalCoordinateFromResizedCoordinate(${output.type.value}(outputIndex), scales[i],
${output.type.value}(outputShape[i]), ${output.type.value}(inputShape[i]), roi[i], roi[i + ${inputShape.length}]);
}
}
return originalIndices;
}`;
const calculateInputIndicesFromOutputIndices =
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[],
scales: readonly number[], roi: readonly number[], useExtrapolation: boolean): string => `
fn calculateInputIndicesFromOutputIndices(outputIndices: ${output.type.indices}) -> ${input.type.indices} {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
const outputShape = array<u32, ${outputShape.length}>(${outputShape.map(i => `${i}u`).join(',')});
const scales = array<${input.type.value}, ${scales.length}>(${scales.map(i => `${i}`).join(',')});
const roi = array<${input.type.value}, ${roi.length}>(${roi.map(i => `${i}`).join(',')});
var inputIndices: ${input.type.indices};
for (var i:u32 = 0; i < ${outputShape.length}; i++) {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : 'outputIndices[i]'};
var inputIndex: u32;
if (scales[i] == 1.0) {
inputIndex = outputIndex;
} else {
var original_idx = getOriginalCoordinateFromResizedCoordinate(${input.type.value}(outputIndex), scales[i],
${input.type.value}(outputShape[i]), ${input.type.value}(inputShape[i]), roi[i], roi[i + ${inputShape.length}]);
if (!${useExtrapolation} || (original_idx >= 0 && original_idx < ${input.type.value}(inputShape[i]))) {
if (original_idx < 0) {
inputIndex = 0;
} else if (original_idx > (${input.type.value}(inputShape[i]) - 1)) {
inputIndex = inputShape[i] - 1;
} else {
inputIndex = u32(getNearestPixelFromOriginal(original_idx, scales[i] < 1));
}
} else {
inputIndex = u32(original_idx);
}
}
${input.indicesSet('inputIndices', 'i', 'inputIndex')}
}
return inputIndices;
}`;
const checkInputIndices = (input: IndicesHelper, inputShape: readonly number[]): string => `
fn checkInputIndices(inputIndices: ${input.type.indices}) -> bool {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
for (var i:u32 = 0; i < ${inputShape.length}; i++) {
var inputIndex = ${inputShape.length === 1 ? 'inputIndices' : 'inputIndices[i]'};
if (inputIndex < 0 || inputIndex >= inputShape[i]) {
return false;
}
}
return true;
}`;
const bilinearInterpolation =
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[],
scales: readonly number[], useExtrapolation: boolean, extrapolationValue: number): string => {
const [batchIdx, heightIdx, widthIdx, channelIdx] =
inputShape.length === 2 ? [-1, 0, 1, -1] : (scales[1] === 1.0 ? [0, 2, 3, 1] : [0, 1, 2, 3]);
const dType = input.type.value;
return `
fn getInputValue(batch: u32, channel: u32, row: u32, col: u32) -> ${dType} {
var inputIndices: ${input.type.indices};
inputIndices[${heightIdx}] = max(0, min(row, ${inputShape[heightIdx]} - 1));
inputIndices[${widthIdx}] = max(0, min(col, ${inputShape[widthIdx]} - 1));
if (${inputShape.length} > 2) {
inputIndices[${channelIdx}] = channel;
inputIndices[${batchIdx}] = batch;
};
return input[${input.indicesToOffset('inputIndices')}];
}
fn bilinearInterpolation(outputIndices: ${output.type.indices}) -> ${dType} {
var originalIndices = calculateOriginalIndicesFromOutputIndices(outputIndices);
var row:${dType} = originalIndices[${heightIdx}];
var col:${dType} = originalIndices[${widthIdx}];
if (${useExtrapolation} && (row < 0 || row > (${inputShape[heightIdx]} - 1) || col < 0 || col > ${
inputShape[widthIdx]} - 1)) {
return ${extrapolationValue};
}
row = max(0, min(row, ${inputShape[heightIdx]} - 1));
col = max(0, min(col, ${inputShape[widthIdx]} - 1));
var row1: u32 = u32(row);
var col1: u32 = u32(col);
var row2: u32 = u32(row + 1);
var col2: u32 = u32(col + 1);
var channel: u32 = 0;
var batch: u32 = 0;
if (${inputShape.length > 2}) {
channel = u32(originalIndices[${channelIdx}]);
batch = u32(originalIndices[${batchIdx}]);
}
var x11: ${dType} = getInputValue(batch, channel, row1, col1);
var x12: ${dType} = getInputValue(batch, channel, row1, col2);
var x21: ${dType} = getInputValue(batch, channel, row2, col1);
var x22: ${dType} = getInputValue(batch, channel, row2, col2);
var dx1: ${dType} = row - ${dType}(row1);
var dx2: ${dType} = ${dType}(row2) - row;
var dy1 = col - ${dType}(col1);
var dy2 = ${dType}(col2) - col;
return (x11 * dx2 * dy2 + x12 * dx2 * dy1 + x21 * dx1 * dy2 + x22 * dx1 * dy1);
}`;
};
const bicubicInterpolation =
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[],
scales: readonly number[], roi: readonly number[], cubicCoeffA: number, useExtrapolation: boolean,
extrapolationValue: number, excludeOutside: boolean): string => {
const [heightIdx, widthIdx] = inputShape.length === 2 ? [0, 1] : (scales[1] === 1.0) ? [2, 3] : [1, 2];
const dType = input.type.value;
const createCubicInterpolationFunction = (idx: number): string => {
const direction = idx === heightIdx ? 'row' : 'col';
return `
fn ${direction}CubicInterpolation(inputIndices: ${input.type.indices}, outputIndices: ${
output.type.indices}) -> ${dType} {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : `outputIndices[${idx}]`};
var originalIdx: ${dType} = getOriginalCoordinateFromResizedCoordinate(${dType}(outputIndex), ${scales[idx]},
${dType}(${outputShape[idx]}), ${dType}(${inputShape[idx]}), ${roi[idx]}, ${roi[idx]} + ${inputShape.length});
var fractOriginalIdx: ${dType} = originalIdx - floor(originalIdx);
var coefs = getCubicInterpolationCoefs(fractOriginalIdx);
if (${useExtrapolation} && (originalIdx < 0 || originalIdx > (${inputShape[idx]} - 1))) {
return ${extrapolationValue};
}
var data: array<${dType}, 4> = array<${dType}, 4>(0.0, 0.0, 0.0, 0.0);
for (var i: i32 = -1; i < 3; i++) {
var ${direction}: ${dType} = originalIdx + ${dType}(i);
if (${direction} < 0 || ${direction} >= ${inputShape[idx]}) {
if (${excludeOutside}) {
coefs[i + 1] = 0.0;
continue;
} else if (${useExtrapolation}) {
return ${extrapolationValue};
} else {
${direction} = max(0, min(${direction}, ${inputShape[idx]} - 1));
}
}
var inputIndicesCopy: ${input.type.indices} = inputIndices;
inputIndicesCopy[${idx}] = u32(${direction});
data[i + 1] = ${idx === heightIdx ? `input[${input.indicesToOffset('inputIndicesCopy')}];` : `
rowCubicInterpolation(inputIndicesCopy, outputIndices);`}
}
return cubicInterpolation1D(data, coefs);
}`;
};
return `
${createCubicInterpolationFunction(heightIdx)};
${createCubicInterpolationFunction(widthIdx)};
fn getCubicInterpolationCoefs(s: ${dType}) -> array<${dType}, 4> {
var absS = abs(s);
var coeffs: array<${dType}, 4> = array<${dType}, 4>(0.0, 0.0, 0.0, 0.0);
var oneMinusAbsS: ${dType} = 1.0 - absS;
var twoMinusAbsS: ${dType} = 2.0 - absS;
var onePlusAbsS: ${dType} = 1.0 + absS;
coeffs[0] = ((${cubicCoeffA} * onePlusAbsS - 5 * ${cubicCoeffA}) * onePlusAbsS + 8 * ${
cubicCoeffA}) * onePlusAbsS - 4 * ${cubicCoeffA};
coeffs[1] = ((${cubicCoeffA} + 2) * absS - (${cubicCoeffA} + 3)) * absS * absS + 1;
coeffs[2] = ((${cubicCoeffA} + 2) * oneMinusAbsS - (${cubicCoeffA} + 3)) * oneMinusAbsS * oneMinusAbsS + 1;
coeffs[3] = ((${cubicCoeffA} * twoMinusAbsS - 5 * ${cubicCoeffA}) * twoMinusAbsS + 8 * ${
cubicCoeffA}) * twoMinusAbsS - 4 * ${cubicCoeffA};
return coeffs;
}
fn cubicInterpolation1D(x: array<${dType}, 4>, coefs: array<${dType}, 4>) -> ${dType} {
var coefsSum: ${dType} = coefs[0] + coefs[1] + coefs[2] + coefs[3];
return (x[0] * coefs[0] + x[1] * coefs[1]+ x[2] * coefs[2]+ x[3] * coefs[3]) / coefsSum;
}
fn bicubicInterpolation(outputIndices: ${output.type.indices}) -> ${dType} {
var inputIndices: ${input.type.indices} = outputIndices;
return colCubicInterpolation(inputIndices, outputIndices);
}
`;
};
const createResizeProgramInfo =
(metadata: ProgramMetadata, inputTensor: TensorView, attributes: ResizeAttributes, opsetVersion: number,
scalesInput: readonly number[], sizes: readonly number[], roiInput: readonly number[]): ProgramInfo => {
const inputShape = inputTensor.dims;
const roi = updateRoI(roiInput, attributes.axes, inputShape.length);
let outputShape = initOutputShape(inputShape, scalesInput, sizes, attributes.axes);
let scales = scalesInput.slice();
if (scalesInput.length === 0) {
scales = inputShape.map((value, index) => value === 0 ? 1.0 : outputShape[index] / value);
if (attributes.keepAspectRatioPolicy !== 'stretch') {
outputShape = adjustOutputShape(inputShape, outputShape, scales, attributes);
}
}
const output = outputVariable('output', inputTensor.dataType, outputShape);
const input = inputVariable('input', inputTensor.dataType, inputShape);
const outputSize = ShapeUtil.size(outputShape);
const noScale = inputShape.length === outputShape.length && inputShape.every((d, i) => d === outputShape[i]);
const useExtrapolation = attributes.coordinateTransformMode === 'tf_crop_and_resize';
const dataType = input.type.value;
const getShaderSource = (shaderHelper: ShaderHelper) => `
${getOriginalCoordinateFromResizedCoordinate(attributes.coordinateTransformMode, dataType)};
${(() => {
switch (attributes.mode) {
case 'nearest':
return `
${checkInputIndices(input, inputShape)};
${getNearestPixelFromOriginal(attributes.nearestMode, opsetVersion, dataType)};
${
calculateInputIndicesFromOutputIndices(
input, output, inputShape, outputShape, scales, roi, useExtrapolation)};
`;
case 'linear':
return `
${calculateOriginalIndicesFromOutputIndices(output, inputShape, outputShape, scales, roi)};
${
bilinearInterpolation(
input, output, inputShape, outputShape, scales, useExtrapolation, attributes.extrapolationValue)};
`;
case 'cubic':
return `
${
bicubicInterpolation(
input, output, inputShape, outputShape, scales, roi, attributes.cubicCoeffA, useExtrapolation,
attributes.extrapolationValue, attributes.excludeOutside)};
`;
default:
throw Error('Invalid resize mode');
}
})()};
${shaderHelper.declareVariables(input, output)}
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes(outputSize)}
if (${noScale}) {
output[global_idx] = input[global_idx];
} else {
let outputIndices = ${output.offsetToIndices('global_idx')};
var inputIndices: ${input.type.indices};
${(() => {
switch (attributes.mode) {
case 'nearest':
return `inputIndices = calculateInputIndicesFromOutputIndices(outputIndices);
if (checkInputIndices(inputIndices)) {
output[global_idx] = input[${input.indicesToOffset('inputIndices')}];
} else {
output[global_idx] = ${attributes.extrapolationValue};
}`;
case 'linear':
return 'output[global_idx] = bilinearInterpolation(outputIndices);';
case 'cubic':
return 'output[global_idx] = bicubicInterpolation(outputIndices);';
default:
throw Error(`Unsupported resize mode: ${attributes.mode}`);
}
})()};
}
}`;
return {
...metadata,
getShaderSource,
outputs: [{dims: outputShape, dataType: inputTensor.dataType, gpuDataType: GpuDataType.default}],
dispatchGroup: () => ({x: Math.ceil(outputSize / 64 /* workgroup size */)})
};
};
export const createResizeProgramInfoLoader =
(input: TensorView, attributes: ResizeAttributes, opsetVersion: number, scales: readonly number[],
sizes: readonly number[], roi: readonly number[]): ProgramInfoLoader => {
const metadata: ProgramMetadata = {
name: 'Resize',
inputTypes: [GpuDataType.default],
cacheHint: attributes.cacheKey + opsetVersion.toString() +
(scales.length > 0 ? '_scales_' + scales.toString() : '') +
(sizes.length > 0 ? '_sizes_' + sizes.toString() : ''),
};
return {
...metadata,
get: () => createResizeProgramInfo(metadata, input, attributes, opsetVersion, scales, sizes, roi)
};
};
const getOpsetVersionFromCustomDataBuffer = (context: ComputeContext): number => {
const customDataBuffer = context.customDataBuffer;
const customDataBuffer32 = new Uint32Array(customDataBuffer, customDataBuffer.byteOffset, 1);
const opsetVersion = customDataBuffer32[0];
return opsetVersion;
};
export const resize = (context: ComputeContext, attributes: ResizeAttributes): void => {
const scales: number[] = [];
const sizes: number[] = [];
const roi: number[] = [];
const opsetVersion = getOpsetVersionFromCustomDataBuffer(context);
validateInputs(context.inputs, attributes, opsetVersion, scales, sizes, roi);
context.compute(
createResizeProgramInfoLoader(context.inputs[0], attributes, opsetVersion, scales, sizes, roi), {inputs: [0]});
};
export const parseResizeAttributes = (attributes: Record<string, unknown>): ResizeAttributes => {
const antialias = attributes.antialias as number;
const axes = attributes.axes as number[];
const coordinateTransformMode: CoordinateTransformMode =
attributes.coordinateTransformMode as CoordinateTransformMode;
const cubicCoeffA = attributes.cubicCoeffA as number;
const excludeOutside = attributes.excludeOutside as number !== 0;
const extrapolationValue = attributes.extrapolationValue as number;
const keepAspectRatioPolicy: KeepAspectRatioPolicy = attributes.keepAspectRatioPolicy as KeepAspectRatioPolicy;
const mode: Mode = attributes.mode as Mode;
// If nearestMode is not specified, use simple mode.
const nearestMode: NearestMode = (attributes.nearestMode === '' ? 'simple' : attributes.nearestMode) as NearestMode;
return createAttributeWithCacheKey({
antialias,
axes,
coordinateTransformMode,
cubicCoeffA,
excludeOutside,
extrapolationValue,
keepAspectRatioPolicy,
mode,
nearestMode
});
};