diff --git a/tfjs-backend-wasm/src/kernels/ResizeBilinear.ts b/tfjs-backend-wasm/src/kernels/ResizeBilinear.ts index e2057874c36..99ed1d608b5 100644 --- a/tfjs-backend-wasm/src/kernels/ResizeBilinear.ts +++ b/tfjs-backend-wasm/src/kernels/ResizeBilinear.ts @@ -15,22 +15,12 @@ * ============================================================================= */ -import {NamedAttrMap, NamedTensorInfoMap, registerKernel, TensorInfo, util} from '@tensorflow/tfjs-core'; +import {NamedAttrMap, NamedTensorInfoMap, registerKernel, ResizeBilinear, ResizeBilinearAttrs, ResizeBilinearInputs, TensorInfo, util} from '@tensorflow/tfjs-core'; import {BackendWasm} from '../backend_wasm'; import {cast} from './Cast'; -interface ResizeBilinearInputs extends NamedTensorInfoMap { - x: TensorInfo; -} - -interface ResizeBilinearAttrs extends NamedAttrMap { - newWidth: number; - newHeight: number; - alignCorners: boolean; -} - let wasmResizeBilinear: ( xId: number, batch: number, oldHeight: number, oldWidth: number, numChannels: number, newHeight: number, newWidth: number, @@ -52,26 +42,29 @@ function setup(backend: BackendWasm): void { function resizeBilinear(args: { backend: BackendWasm, - inputs: ResizeBilinearInputs, - attrs: ResizeBilinearAttrs + inputs: NamedTensorInfoMap, + attrs: NamedAttrMap }): TensorInfo { const {backend, inputs, attrs} = args; - const {x} = inputs; - const {alignCorners, newHeight, newWidth} = attrs; - const [batch, oldHeight, oldWidth, numChannels] = x.shape; + const {images} = inputs as ResizeBilinearInputs; + const {alignCorners, size} = attrs as {} as ResizeBilinearAttrs; + const [newHeight, newWidth] = size; + + const [batch, oldHeight, oldWidth, numChannels] = images.shape; const outShape = [batch, newHeight, newWidth, numChannels]; - let xData = backend.dataIdMap.get(x.dataId); + let xData = backend.dataIdMap.get(images.dataId); let castedData; if (xData.dtype !== 'float32') { - castedData = cast({backend, inputs: {x}, attrs: {dtype: 'float32'}}); + castedData = + cast({backend, inputs: {x: images}, attrs: {dtype: 'float32'}}); xData = backend.dataIdMap.get(castedData.dataId); } const xId = xData.id; const out = backend.makeOutput(outShape, 'float32'); - if (util.sizeFromShape(x.shape) === 0) { + if (util.sizeFromShape(images.shape) === 0) { return out; } const outId = backend.dataIdMap.get(out.dataId).id; @@ -88,7 +81,7 @@ function resizeBilinear(args: { } registerKernel({ - kernelName: 'ResizeBilinear', + kernelName: ResizeBilinear, backendName: 'wasm', setupFunc: setup, kernelFunc: resizeBilinear diff --git a/tfjs-core/src/gradients/ResizeBilinear_grad.ts b/tfjs-core/src/gradients/ResizeBilinear_grad.ts new file mode 100644 index 00000000000..6fe41ffb11e --- /dev/null +++ b/tfjs-core/src/gradients/ResizeBilinear_grad.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ +import {ENGINE, ForwardFunc} from '../engine'; +import {ResizeBilinear, ResizeBilinearAttrs, ResizeBilinearGrad, ResizeBilinearGradInputs} from '../kernel_names'; +import {GradConfig} from '../kernel_registry'; +import {NamedAttrMap} from '../kernel_registry'; +import {Tensor, Tensor4D} from '../tensor'; +import {NamedTensorMap} from '../tensor_types'; + +export const resizeBilinearGradConfig: GradConfig = { + kernelName: ResizeBilinear, + inputsToSave: ['images'], + gradFunc: (dy: Tensor4D, saved: Tensor[], attrs: NamedAttrMap) => { + const [images] = saved; + + const backPropKernelFunc: ForwardFunc = (backend) => { + const {alignCorners} = attrs as {} as ResizeBilinearAttrs; + return backend.resizeBilinearBackprop( + dy, images as Tensor4D, alignCorners); + }; + + const inputs: ResizeBilinearGradInputs = {images}; + const imagesDer = () => ENGINE.runKernelFunc( + backPropKernelFunc, inputs as {} as NamedTensorMap, null /* gradient */, + ResizeBilinearGrad, attrs); + + return {images: imagesDer}; + } +}; diff --git a/tfjs-core/src/gradients/ResizeNearestNeighbor_grad.ts b/tfjs-core/src/gradients/ResizeNearestNeighbor_grad.ts new file mode 100644 index 00000000000..bb642dadbfd --- /dev/null +++ b/tfjs-core/src/gradients/ResizeNearestNeighbor_grad.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ +import {ENGINE, ForwardFunc} from '../engine'; +import {ResizeNearestNeighbor, ResizeNearestNeighborAttrs, ResizeNearestNeighborGrad, ResizeNearestNeighborGradInputs} from '../kernel_names'; +import {GradConfig} from '../kernel_registry'; +import {NamedAttrMap} from '../kernel_registry'; +import {Tensor, Tensor4D} from '../tensor'; +import {NamedTensorMap} from '../tensor_types'; + +export const resizeNearestNeighborGradConfig: GradConfig = { + kernelName: ResizeNearestNeighbor, + inputsToSave: ['images'], + gradFunc: (dy: Tensor4D, saved: Tensor[], attrs: NamedAttrMap) => { + const [images] = saved; + + const backPropKernelFunc: ForwardFunc = (backend) => { + const {alignCorners} = attrs as {} as ResizeNearestNeighborAttrs; + return backend.resizeNearestNeighborBackprop( + dy, images as Tensor4D, alignCorners); + }; + + const inputs: ResizeNearestNeighborGradInputs = {images}; + const imagesDer = () => ENGINE.runKernelFunc( + backPropKernelFunc, inputs as {} as NamedTensorMap, null /* gradient */, + ResizeNearestNeighborGrad, attrs); + + return {images: imagesDer}; + } +}; diff --git a/tfjs-core/src/kernel_names.ts b/tfjs-core/src/kernel_names.ts index 4e9a822453f..5e95ecc3465 100644 --- a/tfjs-core/src/kernel_names.ts +++ b/tfjs-core/src/kernel_names.ts @@ -381,6 +381,27 @@ export type RealInputs = Pick; export const Relu = 'Relu'; export type ReluInputs = Pick; +export const ResizeNearestNeighbor = 'ResizeNearestNeighbor'; +export type ResizeNearestNeighborInputs = Pick; +export interface ResizeNearestNeighborAttrs { + alignCorners: boolean; + size: [number, number]; +} + +export const ResizeNearestNeighborGrad = 'ResizeNearestNeighborGrad'; +export type ResizeNearestNeighborGradInputs = + Pick; + +export const ResizeBilinear = 'ResizeBilinear'; +export type ResizeBilinearInputs = Pick; +export interface ResizeBilinearAttrs { + alignCorners: boolean; + size: [number, number]; +} + +export const ResizeBilinearGrad = 'ResizeBilinearGrad'; +export type ResizeBilinearGradInputs = Pick; + export const Relu6 = 'Relu6'; export type Relu6Inputs = Pick; diff --git a/tfjs-core/src/ops/image_ops.ts b/tfjs-core/src/ops/image_ops.ts index ba034e33526..f22220898a0 100644 --- a/tfjs-core/src/ops/image_ops.ts +++ b/tfjs-core/src/ops/image_ops.ts @@ -17,7 +17,7 @@ import {nonMaxSuppressionV3, nonMaxSuppressionV5} from '../backends/non_max_suppression_impl'; import {ENGINE, ForwardFunc} from '../engine'; -import {Tensor, Tensor1D, Tensor2D, Tensor3D, Tensor4D} from '../tensor'; +import {Tensor, Tensor1D, Tensor2D, Tensor4D} from '../tensor'; import {NamedTensorMap} from '../tensor_types'; import {convertToTensor} from '../tensor_util_env'; import {TensorLike} from '../types'; @@ -27,125 +27,6 @@ import {nonMaxSuppSanityCheck} from './nonmax_util'; import {op} from './operation'; export {nonMaxSuppression} from './non_max_suppression'; -/** - * Bilinear resize a batch of 3D images to a new shape. - * - * @param images The images, of rank 4 or rank 3, of shape - * `[batch, height, width, inChannels]`. If rank 3, batch of 1 is assumed. - * @param size The new shape `[newHeight, newWidth]` to resize the - * images to. Each channel is resized individually. - * @param alignCorners Defaults to False. If true, rescale - * input by `(new_height - 1) / (height - 1)`, which exactly aligns the 4 - * corners of images and resized images. If false, rescale by - * `new_height / height`. Treat similarly the width dimension. - */ -/** @doc {heading: 'Operations', subheading: 'Images', namespace: 'image'} */ -function resizeBilinear_( - images: T|TensorLike, size: [number, number], alignCorners = false): T { - const $images = convertToTensor(images, 'images', 'resizeBilinear'); - util.assert( - $images.rank === 3 || $images.rank === 4, - () => `Error in resizeBilinear: x must be rank 3 or 4, but got ` + - `rank ${$images.rank}.`); - util.assert( - size.length === 2, - () => `Error in resizeBilinear: new shape must 2D, but got shape ` + - `${size}.`); - - let batchImages = $images as Tensor4D; - let reshapedTo4D = false; - if ($images.rank === 3) { - reshapedTo4D = true; - batchImages = - $images.as4D(1, $images.shape[0], $images.shape[1], $images.shape[2]); - } - - const [newHeight, newWidth] = size; - const forward: ForwardFunc = (backend, save) => { - save([batchImages]); - return backend.resizeBilinear( - batchImages, newHeight, newWidth, alignCorners); - }; - - const backward = (dy: Tensor4D, saved: Tensor[]) => { - return { - x: () => ENGINE.runKernelFunc( - backend => backend.resizeBilinearBackprop( - dy, saved[0] as Tensor4D, alignCorners), - {}) - }; - }; - - const res = ENGINE.runKernelFunc( - forward, {x: batchImages}, backward, 'ResizeBilinear', - {alignCorners, newHeight, newWidth}); - if (reshapedTo4D) { - return res.as3D(res.shape[1], res.shape[2], res.shape[3]) as T; - } - return res as T; -} - -/** - * NearestNeighbor resize a batch of 3D images to a new shape. - * - * @param images The images, of rank 4 or rank 3, of shape - * `[batch, height, width, inChannels]`. If rank 3, batch of 1 is assumed. - * @param size The new shape `[newHeight, newWidth]` to resize the - * images to. Each channel is resized individually. - * @param alignCorners Defaults to False. If true, rescale - * input by `(new_height - 1) / (height - 1)`, which exactly aligns the 4 - * corners of images and resized images. If false, rescale by - * `new_height / height`. Treat similarly the width dimension. - */ -/** @doc {heading: 'Operations', subheading: 'Images', namespace: 'image'} */ -function resizeNearestNeighbor_( - images: T|TensorLike, size: [number, number], alignCorners = false): T { - const $images = convertToTensor(images, 'images', 'resizeNearestNeighbor'); - util.assert( - $images.rank === 3 || $images.rank === 4, - () => `Error in resizeNearestNeighbor: x must be rank 3 or 4, but got ` + - `rank ${$images.rank}.`); - util.assert( - size.length === 2, - () => - `Error in resizeNearestNeighbor: new shape must 2D, but got shape ` + - `${size}.`); - util.assert( - $images.dtype === 'float32' || $images.dtype === 'int32', - () => '`images` must have `int32` or `float32` as dtype'); - - let batchImages = $images as Tensor4D; - let reshapedTo4D = false; - if ($images.rank === 3) { - reshapedTo4D = true; - batchImages = - $images.as4D(1, $images.shape[0], $images.shape[1], $images.shape[2]); - } - const [newHeight, newWidth] = size; - - const forward: ForwardFunc = (backend, save) => { - save([batchImages]); - return backend.resizeNearestNeighbor( - batchImages, newHeight, newWidth, alignCorners); - }; - - const backward = (dy: Tensor4D, saved: Tensor[]) => { - return { - batchImages: () => ENGINE.runKernelFunc( - backend => backend.resizeNearestNeighborBackprop( - dy, saved[0] as Tensor4D, alignCorners), - {}) - }; - }; - - const res = ENGINE.runKernelFunc(forward, {batchImages}, backward); - - if (reshapedTo4D) { - return res.as3D(res.shape[1], res.shape[2], res.shape[3]) as T; - } - return res as T; -} - /** * Performs non maximum suppression of bounding boxes based on * iou (intersection over union). @@ -350,8 +231,6 @@ function cropAndResize_( return res; } -export const resizeBilinear = op({resizeBilinear_}); -export const resizeNearestNeighbor = op({resizeNearestNeighbor_}); export const nonMaxSuppressionAsync = nonMaxSuppressionAsync_; export const nonMaxSuppressionWithScore = op({nonMaxSuppressionWithScore_}); export const nonMaxSuppressionWithScoreAsync = nonMaxSuppressionWithScoreAsync_; diff --git a/tfjs-core/src/ops/ops.ts b/tfjs-core/src/ops/ops.ts index 2747d48f9a8..9797cd5e548 100644 --- a/tfjs-core/src/ops/ops.ts +++ b/tfjs-core/src/ops/ops.ts @@ -127,9 +127,21 @@ export {op} from './operation'; // Second level exports. import * as losses from './loss_ops'; import * as linalg from './linalg_ops'; -import * as image from './image_ops'; import * as spectral from './spectral_ops'; import * as fused from './fused_ops'; import * as signal from './signal_ops'; +import {cropAndResize, nonMaxSuppression, nonMaxSuppressionAsync, nonMaxSuppressionWithScore, nonMaxSuppressionWithScoreAsync} from './image_ops'; +import {resizeBilinear} from './resize_bilinear'; +import {resizeNearestNeighbor} from './resize_nearest_neighbor'; +const image = { + resizeNearestNeighbor, + resizeBilinear, + cropAndResize, + nonMaxSuppression, + nonMaxSuppressionAsync, + nonMaxSuppressionWithScore, + nonMaxSuppressionWithScoreAsync +}; + export {image, linalg, losses, spectral, fused, signal}; diff --git a/tfjs-core/src/ops/resize_bilinear.ts b/tfjs-core/src/ops/resize_bilinear.ts new file mode 100644 index 00000000000..cecb1e9c242 --- /dev/null +++ b/tfjs-core/src/ops/resize_bilinear.ts @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ + +import {ENGINE, ForwardFunc} from '../engine'; +import {ResizeBilinear, ResizeBilinearAttrs, ResizeBilinearInputs} from '../kernel_names'; +import {NamedAttrMap} from '../kernel_registry'; +import {Tensor3D, Tensor4D} from '../tensor'; +import {NamedTensorMap} from '../tensor_types'; +import {convertToTensor} from '../tensor_util_env'; +import {TensorLike} from '../types'; +import * as util from '../util'; + +import {op} from './operation'; + +/** + * Bilinear resize a batch of 3D images to a new shape. + * + * @param images The images, of rank 4 or rank 3, of shape + * `[batch, height, width, inChannels]`. If rank 3, batch of 1 is assumed. + * @param size The new shape `[newHeight, newWidth]` to resize the + * images to. Each channel is resized individually. + * @param alignCorners Defaults to False. If true, rescale + * input by `(new_height - 1) / (height - 1)`, which exactly aligns the 4 + * corners of images and resized images. If false, rescale by + * `new_height / height`. Treat similarly the width dimension. + */ +/** @doc {heading: 'Operations', subheading: 'Images', namespace: 'image'} */ +function resizeBilinear_( + images: T|TensorLike, size: [number, number], alignCorners = false): T { + const $images = convertToTensor(images, 'images', 'resizeBilinear'); + + util.assert( + $images.rank === 3 || $images.rank === 4, + () => `Error in resizeBilinear: x must be rank 3 or 4, but got ` + + `rank ${$images.rank}.`); + util.assert( + size.length === 2, + () => `Error in resizeBilinear: new shape must 2D, but got shape ` + + `${size}.`); + + let batchImages = $images as Tensor4D; + let reshapedTo4D = false; + if ($images.rank === 3) { + reshapedTo4D = true; + batchImages = + $images.as4D(1, $images.shape[0], $images.shape[1], $images.shape[2]); + } + + const [newHeight, newWidth] = size; + const forward: ForwardFunc = (backend, save) => { + save([batchImages]); + return backend.resizeBilinear( + batchImages, newHeight, newWidth, alignCorners); + }; + + const inputs: ResizeBilinearInputs = {images: batchImages}; + const attrs: ResizeBilinearAttrs = {alignCorners, size}; + + const res = ENGINE.runKernelFunc( + forward, inputs as {} as NamedTensorMap, null /* gradient */, + ResizeBilinear, attrs as {} as NamedAttrMap); + + if (reshapedTo4D) { + return res.as3D(res.shape[1], res.shape[2], res.shape[3]) as T; + } + return res as T; +} + +export const resizeBilinear = op({resizeBilinear_}); diff --git a/tfjs-core/src/ops/resize_bilinear_test.ts b/tfjs-core/src/ops/resize_bilinear_test.ts index bd6f378d5a0..9d804b0aa7c 100644 --- a/tfjs-core/src/ops/resize_bilinear_test.ts +++ b/tfjs-core/src/ops/resize_bilinear_test.ts @@ -57,20 +57,21 @@ describeWithFlags('resizeBilinear', ALL_ENVS, () => { const output = input.resizeBilinear([4, 3], false); expectArraysClose(await output.data(), [ - 1.5632453, 2.13817763, 1.44398415, 1.07632685, 0.59306782, -0.36970866, - 1.59388208, 1.98745549, 1.2917161, 1.54812956, 1.30613375, 1.15276587, + 1.5632453, 2.13817763, 1.44398415, 1.07632685, 0.59306782, -0.36970866, + 1.59388208, 1.98745549, 1.2917161, 1.54812956, 1.30613375, 1.15276587, 1.62451875, 1.83673334, 1.13944793, 2.01993227, 2.01919961, 2.67524052, - 1.62451875, 1.83673334, 1.13944793, 2.01993227, 2.01919961, 2.67524052]); + 1.62451875, 1.83673334, 1.13944793, 2.01993227, 2.01919961, 2.67524052 + ]); }); it('works for ints', async () => { const input = tf.tensor3d([1, 2, 3, 4, 5], [1, 5, 1], 'int32'); - const output = input.resizeBilinear([1, 10]); + const output = input.resizeBilinear([1, 10], false); expect(output.shape).toEqual([1, 10, 1]); expect(output.dtype).toBe('float32'); expectArraysClose( - await output.data(), [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5]); + await output.data(), [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5]); }); it('matches tensorflow w/ random numbers alignCorners=false', async () => { diff --git a/tfjs-core/src/ops/resize_nearest_neighbor.ts b/tfjs-core/src/ops/resize_nearest_neighbor.ts new file mode 100644 index 00000000000..eb0ef5260b6 --- /dev/null +++ b/tfjs-core/src/ops/resize_nearest_neighbor.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ + +import {ENGINE, ForwardFunc} from '../engine'; +import {ResizeNearestNeighbor, ResizeNearestNeighborAttrs, ResizeNearestNeighborInputs} from '../kernel_names'; +import {NamedAttrMap} from '../kernel_registry'; +import {Tensor3D, Tensor4D} from '../tensor'; +import {NamedTensorMap} from '../tensor_types'; +import {convertToTensor} from '../tensor_util_env'; +import {TensorLike} from '../types'; +import * as util from '../util'; + +import {op} from './operation'; + +/** + * NearestNeighbor resize a batch of 3D images to a new shape. + * + * @param images The images, of rank 4 or rank 3, of shape + * `[batch, height, width, inChannels]`. If rank 3, batch of 1 is assumed. + * @param size The new shape `[newHeight, newWidth]` to resize the + * images to. Each channel is resized individually. + * @param alignCorners Defaults to False. If true, rescale + * input by `(new_height - 1) / (height - 1)`, which exactly aligns the 4 + * corners of images and resized images. If false, rescale by + * `new_height / height`. Treat similarly the width dimension. + */ +/** @doc {heading: 'Operations', subheading: 'Images', namespace: 'image'} */ +function resizeNearestNeighbor_( + images: T|TensorLike, size: [number, number], alignCorners = false): T { + const $images = convertToTensor(images, 'images', 'resizeNearestNeighbor'); + + util.assert( + $images.rank === 3 || $images.rank === 4, + () => `Error in resizeNearestNeighbor: x must be rank 3 or 4, but got ` + + `rank ${$images.rank}.`); + util.assert( + size.length === 2, + () => + `Error in resizeNearestNeighbor: new shape must 2D, but got shape ` + + `${size}.`); + util.assert( + $images.dtype === 'float32' || $images.dtype === 'int32', + () => '`images` must have `int32` or `float32` as dtype'); + + let batchImages = $images as Tensor4D; + let reshapedTo4D = false; + if ($images.rank === 3) { + reshapedTo4D = true; + batchImages = + $images.as4D(1, $images.shape[0], $images.shape[1], $images.shape[2]); + } + const [newHeight, newWidth] = size; + + const inputs: ResizeNearestNeighborInputs = {images: batchImages}; + const attrs: ResizeNearestNeighborAttrs = {alignCorners, size}; + + const forward: ForwardFunc = (backend, save) => { + save([batchImages]); + return backend.resizeNearestNeighbor( + batchImages, newHeight, newWidth, alignCorners); + }; + + const res = ENGINE.runKernelFunc( + forward, inputs as {} as NamedTensorMap, null /* gradient */, + ResizeNearestNeighbor, attrs as {} as NamedAttrMap); + + if (reshapedTo4D) { + return res.as3D(res.shape[1], res.shape[2], res.shape[3]) as T; + } + return res as T; +} + +export const resizeNearestNeighbor = op({resizeNearestNeighbor_}); diff --git a/tfjs-core/src/public/chained_ops/register_all_chained_ops.ts b/tfjs-core/src/public/chained_ops/register_all_chained_ops.ts index c201ebfd9e4..5b1a5ee6b6a 100644 --- a/tfjs-core/src/public/chained_ops/register_all_chained_ops.ts +++ b/tfjs-core/src/public/chained_ops/register_all_chained_ops.ts @@ -54,6 +54,8 @@ import './pool'; import './pow'; import './prelu'; import './relu'; +import './resize_bilinear'; +import './resize_nearest_neighbor'; import './relu6'; import './selu'; import './separable_conv2d'; diff --git a/tfjs-core/src/public/chained_ops/register_all_chained_ops_test.ts b/tfjs-core/src/public/chained_ops/register_all_chained_ops_test.ts index 07f15d6bcd1..df4e211c6b5 100644 --- a/tfjs-core/src/public/chained_ops/register_all_chained_ops_test.ts +++ b/tfjs-core/src/public/chained_ops/register_all_chained_ops_test.ts @@ -64,6 +64,8 @@ const CHAINED_OPS = [ 'pow', 'prelu', 'relu', + 'resizeBilinear', + 'resizeNearestNeighbor', 'relu6', 'selu', 'separableConv2d', diff --git a/tfjs-core/src/public/chained_ops/resize_bilinear.ts b/tfjs-core/src/public/chained_ops/resize_bilinear.ts new file mode 100644 index 00000000000..e70c15f8a61 --- /dev/null +++ b/tfjs-core/src/public/chained_ops/resize_bilinear.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2020 Google LLC. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ +import {resizeBilinear} from '../../ops/resize_bilinear'; +import {Tensor, Tensor3D, Tensor4D} from '../../tensor'; +import {Rank} from '../../types'; + +declare module '../../tensor' { + interface Tensor { + resizeBilinear( + newShape2D: [number, number], alignCorners?: boolean): T; + } +} + +Tensor.prototype.resizeBilinear = function( + this: T, newShape2D: [number, number], alignCorners?: boolean): T { + this.throwIfDisposed(); + return resizeBilinear(this, newShape2D, alignCorners); +}; diff --git a/tfjs-core/src/public/chained_ops/resize_nearest_neighbor.ts b/tfjs-core/src/public/chained_ops/resize_nearest_neighbor.ts new file mode 100644 index 00000000000..77a6fc791f9 --- /dev/null +++ b/tfjs-core/src/public/chained_ops/resize_nearest_neighbor.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2020 Google LLC. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================================= + */ +import {resizeNearestNeighbor} from '../../ops/resize_nearest_neighbor'; +import {Tensor, Tensor3D, Tensor4D} from '../../tensor'; +import {Rank} from '../../types'; + +declare module '../../tensor' { + interface Tensor { + resizeNearestNeighbor( + newShape2D: [number, number], alignCorners?: boolean): T; + } +} + +Tensor.prototype.resizeNearestNeighbor = function( + this: T, newShape2D: [number, number], alignCorners?: boolean): T { + this.throwIfDisposed(); + return resizeNearestNeighbor(this, newShape2D, alignCorners); +}; diff --git a/tfjs-core/src/register_all_gradients.ts b/tfjs-core/src/register_all_gradients.ts index 0e32c60a3b9..a7566d3a171 100644 --- a/tfjs-core/src/register_all_gradients.ts +++ b/tfjs-core/src/register_all_gradients.ts @@ -48,6 +48,8 @@ import {powGradConfig} from './gradients/Pow_grad'; import {preluGradConfig} from './gradients/Prelu_grad'; import {relu6GradConfig} from './gradients/Relu6_grad'; import {reluGradConfig} from './gradients/Relu_grad'; +import {resizeBilinearGradConfig} from './gradients/ResizeBilinear_grad'; +import {resizeNearestNeighborGradConfig} from './gradients/ResizeNearestNeighbor_grad'; import {seluGradConfig} from './gradients/Selu_grad'; import {spaceToBatchNDGradConfig} from './gradients/SpaceToBatchND_grad'; import {splitVGradConfig} from './gradients/SplitV_grad'; @@ -99,6 +101,8 @@ const gradConfigs: GradConfig[] = [ powGradConfig, preluGradConfig, reluGradConfig, + resizeBilinearGradConfig, + resizeNearestNeighborGradConfig, relu6GradConfig, seluGradConfig, spaceToBatchNDGradConfig, diff --git a/tfjs-core/src/tensor.ts b/tfjs-core/src/tensor.ts index 16bf5dbeb6f..91ec3c58cdd 100644 --- a/tfjs-core/src/tensor.ts +++ b/tfjs-core/src/tensor.ts @@ -260,12 +260,6 @@ export interface OpHandler { step(x: T, alpha: number): T; softmax(logits: T, dim: number): T; logSoftmax(logits: T, axis: number): T; - image: { - resizeBilinear( - images: T, size: [number, number], alignCorners: boolean): T; - resizeNearestNeighbor( - images: T, size: [number, number], alignCorners: boolean): T; - }; unsortedSegmentSum( x: T, segmentIds: Tensor1D|TensorLike1D, numSegments: number): T; topk(x: T, k: number, sorted: boolean): @@ -1033,21 +1027,6 @@ export class Tensor { this.throwIfDisposed(); return opHandler.logSoftmax(this, axis); } - - // Image ops. - resizeBilinear( - this: T, newShape2D: [number, number], alignCorners = false): T { - (this as Tensor).throwIfDisposed(); - return opHandler.image.resizeBilinear(this, newShape2D, alignCorners); - } - - resizeNearestNeighbor( - this: T, newShape2D: [number, number], alignCorners = false): T { - (this as Tensor).throwIfDisposed(); - return opHandler.image.resizeNearestNeighbor( - this, newShape2D, alignCorners); - } - // Pooling. variable(trainable = true, name?: string, dtype?: DataType): Variable { this.throwIfDisposed();