4
import { AttributeWithCacheKey, createAttributeWithCacheKey } from '../../../attribute-with-cache-key';
5
import { Graph } from '../../../graph';
6
import { OperatorImplementation, OperatorInitialization } from '../../../operators';
7
import { Tensor } from '../../../tensor';
8
import { getGlsl } from '../glsl-source';
9
import { WebGLInferenceHandler } from '../inference-handler';
10
import { ProgramInfo, TextureType } from '../types';
12
export interface UpsampleAttributes extends AttributeWithCacheKey {
13
readonly opset: number;
14
readonly isResize: boolean;
15
readonly mode: string;
16
readonly scales: number[];
17
readonly extrapolationValue: number;
18
readonly coordinateTransformMode: string;
19
readonly useExtrapolation: boolean;
20
readonly needRoiInput: boolean;
21
readonly nearestMode: string;
22
readonly cubicCoefficientA: number;
23
readonly excludeOutside: boolean;
24
readonly useNearest2xOptimization: boolean;
25
readonly roiInputIdx: number;
26
readonly scalesInputIdx: number;
27
readonly sizesInputIdx: number;
30
const upsampleProgramMetadata = {
33
inputTypes: [TextureType.unpacked],
36
export const upsample: OperatorImplementation<UpsampleAttributes> = (
37
inferenceHandler: WebGLInferenceHandler,
39
attributes: UpsampleAttributes,
41
validateInputs(inputs, attributes);
42
const output = inferenceHandler.run(
44
...upsampleProgramMetadata,
45
cacheHint: attributes.cacheKey,
46
get: () => createUpsampleProgramInfo(inferenceHandler, inputs, attributes),
53
export const parseUpsampleAttributesV7: OperatorInitialization<UpsampleAttributes> = (
55
): UpsampleAttributes => parseUpsampleAttributes(node, 7);
57
export const parseUpsampleAttributesV9: OperatorInitialization<UpsampleAttributes> = (
59
): UpsampleAttributes => parseUpsampleAttributes(node, 9);
61
export const parseUpsampleAttributes = (node: Graph.Node, opset: number): UpsampleAttributes => {
62
const isResize = opset >= 10;
65
const mode = node.attributes.getString('mode', 'nearest');
66
if (mode !== 'nearest' && mode !== 'linear' && (opset < 11 || mode !== 'cubic')) {
67
throw new Error(`unrecognized mode: ${mode}`);
70
let scales: number[] = [];
72
scales = node.attributes.getFloats('scales');
73
scalesValidation(scales, mode, isResize);
76
const extrapolationValue = node.attributes.getFloat('extrapolation_value', 0.0);
78
const coordinateTransformMode =
79
opset > 10 ? node.attributes.getString('coordinate_transformation_mode', 'half_pixel') : 'asymmetric';
84
'tf_half_pixel_for_nn',
88
].indexOf(coordinateTransformMode) === -1
90
throw new Error(`coordinate_transform_mode '${coordinateTransformMode}' is not supported`);
92
const needRoiInput = coordinateTransformMode === 'tf_crop_and_resize';
93
const useExtrapolation = needRoiInput;
96
mode === 'nearest' && opset >= 11 ? node.attributes.getString('nearest_mode', 'round_prefer_floor') : '';
97
if (['round_prefer_floor', 'round_prefer_ceil', 'floor', 'ceil', ''].indexOf(nearestMode) === -1) {
98
throw new Error(`nearest_mode '${nearestMode}' is not supported`);
101
const cubicCoefficientA = node.attributes.getFloat('cubic_coeff_a', -0.75);
102
const excludeOutside = node.attributes.getInt('exclude_outside', 0) !== 0;
103
if (excludeOutside && mode !== 'cubic') {
104
throw new Error('exclude_outside can be set to 1 only when mode is CUBIC.');
107
const useNearest2xOptimization =
108
opset < 11 ? true : mode === 'nearest' && coordinateTransformMode === 'asymmetric' && nearestMode === 'floor';
111
let scalesInputIdx = 0;
112
let sizesInputIdx = 0;
116
if (node.inputs.length > 2) {
124
} else if (opset === 9) {
128
return createAttributeWithCacheKey({
134
coordinateTransformMode,
140
useNearest2xOptimization,
147
const createUpsampleProgramInfo = (
148
inferenceHandler: WebGLInferenceHandler,
150
attributes: UpsampleAttributes,
152
const glsl = getGlsl(inferenceHandler.session.backend.glContext.version);
153
const [inputWidth, inputHeight] = inferenceHandler.calculateTextureWidthAndHeight(
155
TextureType.unpacked,
158
const outputShape = inputs[0].dims.map((dim, i) => Math.floor(dim * attributes.scales[i]));
159
const [outputWidth, outputHeight] = inferenceHandler.calculateTextureWidthAndHeight(
161
TextureType.unpacked,
163
const dim = outputShape.length;
165
const outputPitches = new Array<number>(dim);
166
const inputPitches = new Array<number>(dim);
167
let precalculatedPitches = `
168
int output_pitches[${dim}];
169
int input_pitches[${dim}];
171
for (let d = dim - 1; d >= 0; d--) {
172
outputPitches[d] = d === dim - 1 ? 1 : outputPitches[d + 1] * outputShape[d + 1];
173
inputPitches[d] = d === dim - 1 ? 1 : inputPitches[d + 1] * inputs[0].dims[d + 1];
175
precalculatedPitches += `
176
output_pitches[${d}] = ${outputPitches[d]};
177
input_pitches[${d}] = ${inputPitches[d]};
180
const getInputFloatFunction = `
181
float getInputFloat(int index) {
182
vec2 coords = offsetToCoords(index, ${inputWidth}, ${inputHeight});
183
float value = getColorAsFloat(${glsl.texture2D}(X, coords));
189
attributes.mode === 'nearest'
192
${getInputFloatFunction}
193
float process(int indices[${dim}]) {
195
int output_index = coordsToOffset(TexCoords, ${outputWidth}, ${outputHeight});
197
${precalculatedPitches}
200
for (int dim = 0; dim < ${dim}; ++dim) {
201
d = output_index / output_pitches[dim];
202
m = output_index - d * output_pitches[dim];
205
if (scales[dim] != 1 && d > 0) {
206
int d2 = d / scales[dim];
207
m = d - d2 * scales[dim];
210
input_index += input_pitches[dim] * d;
213
return getInputFloat(input_index);
218
${getInputFloatFunction}
219
float process(int indices[4]) {
221
int output_index = coordsToOffset(TexCoords, ${outputWidth}, ${outputHeight});
223
${precalculatedPitches}
226
int index_of_dim0, index_of_dim1, index_of_dim2, index_of_dim3;
227
index_of_dim0 = output_index / output_pitches[0];
228
m = output_index - index_of_dim0 * output_pitches[0];
229
index_of_dim1 = m / output_pitches[1];
230
m = m - index_of_dim1 * output_pitches[1];
231
index_of_dim2 = m / output_pitches[2];
232
m = m - index_of_dim2 * output_pitches[2];
235
int index_of_input_dim2, index_of_input_dim3, x_offset, y_offset;
236
index_of_input_dim2 = index_of_dim2 / scales[2];
237
y_offset = index_of_dim2 - index_of_input_dim2 * scales[2];
238
index_of_input_dim3 = index_of_dim3 / scales[3];
239
x_offset = index_of_dim3 - index_of_input_dim3 * scales[3];
241
input_index = index_of_dim0 * input_pitches[0] +
242
index_of_dim1 * input_pitches[1] +
243
index_of_input_dim2 * input_pitches[2] +
246
float x00 = getInputFloat(input_index);
249
bool end_of_dim2 = false;
250
if (index_of_input_dim2 == (${inputs[0].dims[2]} - 1)) {
251
// It's the end in dimension 2
255
x01 = getInputFloat(input_index + input_pitches[2]);
258
if (index_of_input_dim3 == (input_pitches[2] - 1)) {
259
// It's the end in dimension 3
264
x10 = getInputFloat(input_index + 1);
265
x11 = end_of_dim2 ? x10 : getInputFloat(input_index + input_pitches[2] + 1);
268
float y0 = x00 + float(y_offset) * (x01 - x00) / float(scales[2]);
269
float y1 = x10 + float(y_offset) * (x11 - x10) / float(scales[2]);
270
return y0 + float(x_offset) * (y1 - y0) / float(scales[3]);
274
${getInputFloatFunction}
275
float process(int indices[2]) {
277
int output_index = coordsToOffset(TexCoords, ${outputWidth}, ${outputHeight});
279
${precalculatedPitches}
282
int index_of_dim0, index_of_dim1;
283
index_of_dim0 = output_index / output_pitches[0];
284
m = output_index - index_of_dim0 * output_pitches[0];
287
int index_of_input_dim0, index_of_input_dim1, x_offset, y_offset;
288
index_of_input_dim0 = index_of_dim0 / scales[0];
289
y_offset = index_of_dim0 - index_of_input_dim0 * scales[0];
290
index_of_input_dim1 = index_of_dim1 / scales[1];
291
x_offset = index_of_dim1 - index_of_input_dim1 * scales[1];
293
input_index = index_of_input_dim0 * input_pitches[0] + index_of_input_dim1;
295
float x00 = getInputFloat(input_index);
298
bool end_of_dim0 = false;
299
if (index_of_input_dim0 == (${inputs[0].dims[0]} - 1)) {
300
// It's the end in dimension 0
304
x01 = getInputFloat(input_index + input_pitches[0]);
307
if (index_of_input_dim1 == (input_pitches[0] - 1)) {
308
// It's the end in dimension 1
313
x10 = getInputFloat(input_index + 1);
314
x11 = end_of_dim0 ? x10 : getInputFloat(input_index + input_pitches[0] + 1);
317
float y0 = x00 + float(y_offset) * (x01 - x00) / float(scales[0]);
318
float y1 = x10 + float(y_offset) * (x11 - x10) / float(scales[0]);
319
return y0 + float(x_offset) * (y1 - y0) / float(scales[1]);
322
...upsampleProgramMetadata,
323
output: { dims: outputShape, type: inputs[0].type, textureType: TextureType.unpacked },
329
arrayLength: attributes.scales.length,
330
data: attributes.scales.map((x) => Math.ceil(x)),
336
export const validateInputs = (inputs: Tensor[], attribute: UpsampleAttributes): void => {
339
(attribute.opset < 9 && inputs.length !== 1) ||
340
(attribute.opset >= 9 && attribute.opset < 11 && inputs.length !== 2) ||
341
(attribute.opset >= 11 && inputs.length < 2)
343
throw new Error('invalid inputs.');
346
if (attribute.scales.length > 0 && inputs[0].dims.length !== attribute.scales.length) {
347
throw new Error('Invalid input shape.');
350
if (inputs[0].type === 'string') {
351
throw new Error('Invalid input tensor types.');
355
export const scalesValidation = (scales: number[], mode: string, isResize: boolean): void => {
357
for (const scale of scales) {
359
throw new Error('Scale value should be greater than or equal to 1.');
363
for (const scale of scales) {
365
throw new Error('Scale value should be greater than 0.');
369
if (mode === 'linear' || mode === 'cubic') {
370
if (scales.length !== 2 && (scales.length !== 4 || scales[0] !== 1 || scales[1] !== 1)) {
371
throw new Error(`'Linear' mode and 'Cubic' mode only support 2-D inputs ('Bilinear', 'Bicubic') \
372
or 4-D inputs with the corresponding outermost 2 scale values being 1 \
373
in the ${isResize ? 'Resize' : 'Upsample'} opeartor.`);