From 0fefa008c7d2e45c7ff8b7e6b5b56b469e4146eb Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Thu, 18 Jun 2020 12:50:21 -0400 Subject: [PATCH 1/2] modularize cropAndResize --- .../src/kernels/CropAndResize.ts | 37 +-- tfjs-core/src/kernel_names.ts | 9 + .../ops/{image_ops.ts => crop_and_resize.ts} | 17 +- tfjs-core/src/ops/crop_and_resize_test.ts | 314 ++++++++++++++++++ tfjs-core/src/ops/image_ops_test.ts | 295 ---------------- tfjs-core/src/ops/ops.ts | 2 +- tfjs-core/src/tests.ts | 1 + 7 files changed, 349 insertions(+), 326 deletions(-) rename tfjs-core/src/ops/{image_ops.ts => crop_and_resize.ts} (85%) create mode 100644 tfjs-core/src/ops/crop_and_resize_test.ts diff --git a/tfjs-backend-wasm/src/kernels/CropAndResize.ts b/tfjs-backend-wasm/src/kernels/CropAndResize.ts index a46f141fd2b..afcb209a84e 100644 --- a/tfjs-backend-wasm/src/kernels/CropAndResize.ts +++ b/tfjs-backend-wasm/src/kernels/CropAndResize.ts @@ -15,22 +15,11 @@ * ============================================================================= */ -import {NamedAttrMap, NamedTensorInfoMap, registerKernel, TensorInfo} from '@tensorflow/tfjs-core'; +import {CropAndResize, CropAndResizeAttrs, CropAndResizeInputs, NamedAttrMap, NamedTensorInfoMap, registerKernel, TensorInfo} from '@tensorflow/tfjs-core'; import {BackendWasm} from '../backend_wasm'; -import {cast} from './Cast'; - -interface CropAndResizeInputs extends NamedTensorInfoMap { - images: TensorInfo; - boxes: TensorInfo; - boxInd: TensorInfo; -} -interface CropAndResizeAttrs extends NamedAttrMap { - method: keyof InterpolationMethod; - extrapolationValue: number; - cropSize: [number, number]; -} +import {cast} from './Cast'; // Must match enum in CropAndResize.cc enum InterpolationMethod { @@ -60,23 +49,23 @@ function setup(backend: BackendWasm): void { function cropAndResize(args: { backend: BackendWasm, - inputs: CropAndResizeInputs, - attrs: CropAndResizeAttrs + inputs: NamedTensorInfoMap, + attrs: NamedAttrMap }): TensorInfo { const {backend, inputs, attrs} = args; - const {method, extrapolationValue, cropSize} = attrs; - const {images, boxes, boxInd} = inputs; + const {method, extrapolationValue, cropSize} = + attrs as {} as CropAndResizeAttrs; + const {image, boxes, boxInd} = inputs as CropAndResizeInputs; const numBoxes = boxes.shape[0]; const [cropHeight, cropWidth] = cropSize as [number, number]; - const outShape = [numBoxes, cropHeight, cropWidth, images.shape[3]]; + const outShape = [numBoxes, cropHeight, cropWidth, image.shape[3]]; - let imagesData = backend.dataIdMap.get(images.dataId); + let imagesData = backend.dataIdMap.get(image.dataId); let castedData; - if (images.dtype !== 'float32') { - castedData = - cast({backend, inputs: {x: images}, attrs: {dtype: 'float32'}}); + if (image.dtype !== 'float32') { + castedData = cast({backend, inputs: {x: image}, attrs: {dtype: 'float32'}}); imagesData = backend.dataIdMap.get(castedData.dataId); } @@ -87,7 +76,7 @@ function cropAndResize(args: { const out = backend.makeOutput(outShape, 'float32'); const outId = backend.dataIdMap.get(out.dataId).id; - const imagesShapeBytes = new Uint8Array(new Int32Array(images.shape).buffer); + const imagesShapeBytes = new Uint8Array(new Int32Array(image.shape).buffer); wasmCropAndResize( imagesId, boxesId, boxIndId, numBoxes, imagesShapeBytes, cropHeight, @@ -103,7 +92,7 @@ function cropAndResize(args: { } registerKernel({ - kernelName: 'CropAndResize', + kernelName: CropAndResize, backendName: 'wasm', setupFunc: setup, kernelFunc: cropAndResize diff --git a/tfjs-core/src/kernel_names.ts b/tfjs-core/src/kernel_names.ts index b8c5f3bd845..731833da54b 100644 --- a/tfjs-core/src/kernel_names.ts +++ b/tfjs-core/src/kernel_names.ts @@ -161,6 +161,15 @@ export interface CumsumAttrs { reverse: boolean; } +export const CropAndResize = 'CropAndResize'; +export type CropAndResizeInputs = + Pick; +export interface CropAndResizeAttrs { + cropSize: [number, number]; + method: 'bilinear'|'nearest'; + extrapolationValue: number; +} + export const DepthToSpace = 'DepthToSpace'; export type DepthToSpaceInputs = Pick; export interface DepthToSpaceAttrs { diff --git a/tfjs-core/src/ops/image_ops.ts b/tfjs-core/src/ops/crop_and_resize.ts similarity index 85% rename from tfjs-core/src/ops/image_ops.ts rename to tfjs-core/src/ops/crop_and_resize.ts index d018355df3e..99d127ff121 100644 --- a/tfjs-core/src/ops/image_ops.ts +++ b/tfjs-core/src/ops/crop_and_resize.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google Inc. All Rights Reserved. + * 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 @@ -16,7 +16,10 @@ */ import {ENGINE, ForwardFunc} from '../engine'; +import {CropAndResize, CropAndResizeAttrs, CropAndResizeInputs} from '../kernel_names'; +import {NamedAttrMap} from '../kernel_registry'; import {Tensor1D, Tensor2D, Tensor4D} from '../tensor'; +import {NamedTensorMap} from '../tensor_types'; import {convertToTensor} from '../tensor_util_env'; import {TensorLike} from '../types'; import * as util from '../util'; @@ -84,13 +87,15 @@ function cropAndResize_( method === 'bilinear' || method === 'nearest', () => `method must be bilinear or nearest, but was ${method}`); - const forward: ForwardFunc = (backend, save) => - backend.cropAndResize( - $image, $boxes, $boxInd, cropSize, method, extrapolationValue); + const forward: ForwardFunc = (backend) => backend.cropAndResize( + $image, $boxes, $boxInd, cropSize, method, extrapolationValue); + const inputs: + CropAndResizeInputs = {image: $image, boxes: $boxes, boxInd: $boxInd}; + const attrs: CropAndResizeAttrs = {method, extrapolationValue, cropSize}; const res = ENGINE.runKernelFunc( - forward, {images: $image, boxes: $boxes, boxInd: $boxInd}, null /* der */, - 'CropAndResize', {method, extrapolationValue, cropSize}); + forward, inputs as {} as NamedTensorMap, null /* grad */, CropAndResize, + attrs as {} as NamedAttrMap); return res; } diff --git a/tfjs-core/src/ops/crop_and_resize_test.ts b/tfjs-core/src/ops/crop_and_resize_test.ts new file mode 100644 index 00000000000..87b56164f2c --- /dev/null +++ b/tfjs-core/src/ops/crop_and_resize_test.ts @@ -0,0 +1,314 @@ +/** + * @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 * as tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectArraysClose} from '../test_util'; + +describeWithFlags('cropAndResize', ALL_ENVS, () => { + it('1x1-bilinear', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'bilinear', 0); + + expect(output.shape).toEqual([1, 1, 1, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [2.5]); + }); + + it('5x5-bilinear, no change in shape', async () => { + const image: tf.Tensor4D = tf.ones([1, 5, 5, 3]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [5, 5], 'bilinear', 0); + + expect(output.shape).toEqual([1, 5, 5, 3]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), await image.data()); + }); + + it('5x5-bilinear, just a crop, no resize', async () => { + const image: tf.Tensor4D = tf.ones([1, 6, 6, 3]); + const boxes: tf.Tensor2D = tf.tensor2d([0.5, 0.5, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([1, 3, 3, 3]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), await tf.ones([1, 3, 3, 3]).data()); + }); + + it('1x1-nearest', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'nearest', 0); + + expect(output.shape).toEqual([1, 1, 1, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [4.0]); + }); + it('1x1Flipped-bilinear', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'bilinear', 0); + + expect(output.shape).toEqual([1, 1, 1, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [2.5]); + }); + it('1x1Flipped-nearest', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'nearest', 0); + + expect(output.shape).toEqual([1, 1, 1, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [4.0]); + }); + it('3x3-bilinear', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [1, 1.5, 2, 2, 2.5, 3, 3, 3.5, 4]); + }); + it('3x3-nearest', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [1, 2, 2, 3, 4, 4, 3, 4, 4]); + }); + it('3x3Flipped-bilinear', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [4, 3.5, 3, 3, 2.5, 2, 2, 1.5, 1]); + }); + it('3x3Flipped-nearest', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [4, 4, 3, 4, 4, 3, 2, 2, 1]); + }); + it('3x3to2x2-bilinear', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); + const boxes: tf.Tensor2D = + tf.tensor2d([0, 0, 1, 1, 0, 0, 0.5, 0.5], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'bilinear', 0); + + expect(output.shape).toEqual([2, 2, 2, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [1, 3, 7, 9, 1, 2, 4, 5]); + }); + it('3x3to2x2-nearest', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); + const boxes: tf.Tensor2D = + tf.tensor2d([0, 0, 1, 1, 0, 0, 0.5, 0.5], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'nearest', 0); + + expect(output.shape).toEqual([2, 2, 2, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [1, 3, 7, 9, 1, 2, 4, 5]); + }); + it('3x3to2x2Flipped-bilinear', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); + const boxes: tf.Tensor2D = + tf.tensor2d([1, 1, 0, 0, 0.5, 0.5, 0, 0], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'bilinear', 0); + + expect(output.shape).toEqual([2, 2, 2, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [9, 7, 3, 1, 5, 4, 2, 1]); + }); + it('3x3to2x2Flipped-nearest', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); + const boxes: tf.Tensor2D = + tf.tensor2d([1, 1, 0, 0, 0.5, 0.5, 0, 0], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'nearest', 0); + + expect(output.shape).toEqual([2, 2, 2, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [9, 7, 3, 1, 5, 4, 2, 1]); + }); + it('3x3-BoxisRectangular', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0]); + }); + it('3x3-BoxisRectangular-nearest', async () => { + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), [1, 2, 0, 3, 4, 0, 3, 4, 0]); + }); + it('2x2to3x3-Extrapolated', async () => { + const val = -1; + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([-1, -1, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), [val, val, val, val, 1, 2, val, 3, 4]); + }); + it('2x2to3x3-Extrapolated-Float', async () => { + const val = -1.5; + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([-1, -1, 1, 1], [1, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); + + expect(output.shape).toEqual([1, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), [val, val, val, val, 1, 2, val, 3, 4]); + }); + it('2x2to3x3-NoCrop', async () => { + const val = -1.0; + const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([], [0, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); + + expect(output.shape).toEqual([0, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose(await output.data(), []); + }); + it('MultipleBoxes-DifferentBoxes', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1]); + const boxes: tf.Tensor2D = + tf.tensor2d([0, 0, 1, 1.5, 0, 0, 1.5, 1], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([2, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), + [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0, 5, 5.5, 6, 6.5, 7, 7.5, 0, 0, 0]); + }); + it('MultipleBoxes-DifferentBoxes-Nearest', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1]); + const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5, 0, 0, 2, 1], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); + + expect(output.shape).toEqual([2, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), + [1, 2, 0, 3, 4, 0, 3, 4, 0, 5, 6, 6, 7, 8, 8, 0, 0, 0]); + }); + it('int32 image returns float output', async () => { + const image: tf.Tensor4D = + tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1], 'int32'); + const boxes: tf.Tensor2D = + tf.tensor2d([0, 0, 1, 1.5, 0, 0, 1.5, 1], [2, 4]); + const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); + + const output = + tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); + + expect(output.shape).toEqual([2, 3, 3, 1]); + expect(output.dtype).toBe('float32'); + expectArraysClose( + await output.data(), + [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0, 5, 5.5, 6, 6.5, 7, 7.5, 0, 0, 0]); + }); +}); diff --git a/tfjs-core/src/ops/image_ops_test.ts b/tfjs-core/src/ops/image_ops_test.ts index dd8e1130876..53fc4da78d2 100644 --- a/tfjs-core/src/ops/image_ops_test.ts +++ b/tfjs-core/src/ops/image_ops_test.ts @@ -291,298 +291,3 @@ describeWithFlags('nonMaxSuppressionAsync', ALL_ENVS, () => { }); }); }); - -describeWithFlags('cropAndResize', ALL_ENVS, () => { - it('1x1-bilinear', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'bilinear', 0); - - expect(output.shape).toEqual([1, 1, 1, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [2.5]); - }); - - it('5x5-bilinear, no change in shape', async () => { - const image: tf.Tensor4D = tf.ones([1, 5, 5, 3]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [5, 5], 'bilinear', 0); - - expect(output.shape).toEqual([1, 5, 5, 3]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), await image.data()); - }); - - it('5x5-bilinear, just a crop, no resize', async () => { - const image: tf.Tensor4D = tf.ones([1, 6, 6, 3]); - const boxes: tf.Tensor2D = tf.tensor2d([0.5, 0.5, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([1, 3, 3, 3]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), await tf.ones([1, 3, 3, 3]).data()); - }); - - it('1x1-nearest', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'nearest', 0); - - expect(output.shape).toEqual([1, 1, 1, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [4.0]); - }); - it('1x1Flipped-bilinear', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'bilinear', 0); - - expect(output.shape).toEqual([1, 1, 1, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [2.5]); - }); - it('1x1Flipped-nearest', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [1, 1], 'nearest', 0); - - expect(output.shape).toEqual([1, 1, 1, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [4.0]); - }); - it('3x3-bilinear', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [1, 1.5, 2, 2, 2.5, 3, 3, 3.5, 4]); - }); - it('3x3-nearest', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [1, 2, 2, 3, 4, 4, 3, 4, 4]); - }); - it('3x3Flipped-bilinear', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [4, 3.5, 3, 3, 2.5, 2, 2, 1.5, 1]); - }); - it('3x3Flipped-nearest', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([1, 1, 0, 0], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [4, 4, 3, 4, 4, 3, 2, 2, 1]); - }); - it('3x3to2x2-bilinear', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); - const boxes: tf.Tensor2D = - tf.tensor2d([0, 0, 1, 1, 0, 0, 0.5, 0.5], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'bilinear', 0); - - expect(output.shape).toEqual([2, 2, 2, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [1, 3, 7, 9, 1, 2, 4, 5]); - }); - it('3x3to2x2-nearest', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); - const boxes: tf.Tensor2D = - tf.tensor2d([0, 0, 1, 1, 0, 0, 0.5, 0.5], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'nearest', 0); - - expect(output.shape).toEqual([2, 2, 2, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [1, 3, 7, 9, 1, 2, 4, 5]); - }); - it('3x3to2x2Flipped-bilinear', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); - const boxes: tf.Tensor2D = - tf.tensor2d([1, 1, 0, 0, 0.5, 0.5, 0, 0], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'bilinear', 0); - - expect(output.shape).toEqual([2, 2, 2, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [9, 7, 3, 1, 5, 4, 2, 1]); - }); - it('3x3to2x2Flipped-nearest', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 3, 3, 1]); - const boxes: tf.Tensor2D = - tf.tensor2d([1, 1, 0, 0, 0.5, 0.5, 0, 0], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [2, 2], 'nearest', 0); - - expect(output.shape).toEqual([2, 2, 2, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [9, 7, 3, 1, 5, 4, 2, 1]); - }); - it('3x3-BoxisRectangular', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0]); - }); - it('3x3-BoxisRectangular-nearest', async () => { - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), [1, 2, 0, 3, 4, 0, 3, 4, 0]); - }); - it('2x2to3x3-Extrapolated', async () => { - const val = -1; - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([-1, -1, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), [val, val, val, val, 1, 2, val, 3, 4]); - }); - it('2x2to3x3-Extrapolated-Float', async () => { - const val = -1.5; - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([-1, -1, 1, 1], [1, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); - - expect(output.shape).toEqual([1, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), [val, val, val, val, 1, 2, val, 3, 4]); - }); - it('2x2to3x3-NoCrop', async () => { - const val = -1.0; - const image: tf.Tensor4D = tf.tensor4d([1, 2, 3, 4], [1, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([], [0, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', val); - - expect(output.shape).toEqual([0, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose(await output.data(), []); - }); - it('MultipleBoxes-DifferentBoxes', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1]); - const boxes: tf.Tensor2D = - tf.tensor2d([0, 0, 1, 1.5, 0, 0, 1.5, 1], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([2, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), - [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0, 5, 5.5, 6, 6.5, 7, 7.5, 0, 0, 0]); - }); - it('MultipleBoxes-DifferentBoxes-Nearest', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1]); - const boxes: tf.Tensor2D = tf.tensor2d([0, 0, 1, 1.5, 0, 0, 2, 1], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'nearest', 0); - - expect(output.shape).toEqual([2, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), - [1, 2, 0, 3, 4, 0, 3, 4, 0, 5, 6, 6, 7, 8, 8, 0, 0, 0]); - }); - it('int32 image returns float output', async () => { - const image: tf.Tensor4D = - tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2, 1], 'int32'); - const boxes: tf.Tensor2D = - tf.tensor2d([0, 0, 1, 1.5, 0, 0, 1.5, 1], [2, 4]); - const boxInd: tf.Tensor1D = tf.tensor1d([0, 1], 'int32'); - - const output = - tf.image.cropAndResize(image, boxes, boxInd, [3, 3], 'bilinear', 0); - - expect(output.shape).toEqual([2, 3, 3, 1]); - expect(output.dtype).toBe('float32'); - expectArraysClose( - await output.data(), - [1, 1.75, 0, 2, 2.75, 0, 3, 3.75, 0, 5, 5.5, 6, 6.5, 7, 7.5, 0, 0, 0]); - }); -}); diff --git a/tfjs-core/src/ops/ops.ts b/tfjs-core/src/ops/ops.ts index ab66c92d783..a4a425f9f2d 100644 --- a/tfjs-core/src/ops/ops.ts +++ b/tfjs-core/src/ops/ops.ts @@ -135,7 +135,7 @@ import * as fused from './fused_ops'; import * as signal from './signal_ops'; // Image Ops namespace -import {cropAndResize} from './image_ops'; +import {cropAndResize} from './crop_and_resize'; import {nonMaxSuppression} from './non_max_suppression'; import {nonMaxSuppressionAsync} from './non_max_suppression_async'; import {nonMaxSuppressionWithScore} from './non_max_suppression_with_score'; diff --git a/tfjs-core/src/tests.ts b/tfjs-core/src/tests.ts index f144ae92357..3b45ea27b94 100644 --- a/tfjs-core/src/tests.ts +++ b/tfjs-core/src/tests.ts @@ -66,6 +66,7 @@ import './ops/conv2d_transpose_test'; import './ops/conv3d_test'; import './ops/conv3d_transpose_test'; import './ops/conv_util_test'; +import './ops/crop_and_resize_test'; import './ops/cumsum_test'; import './ops/depth_to_space_test'; import './ops/diag_test'; From 496d81198a6e1ec2803bc3d9105e2ecedfd236a8 Mon Sep 17 00:00:00 2001 From: Yannick Assogba Date: Thu, 18 Jun 2020 12:58:11 -0400 Subject: [PATCH 2/2] move non max suppresion tests to own files --- .../src/ops/non_max_suppression_async_test.ts | 84 +++++++++++++++++++ ...ps_test.ts => non_max_suppression_test.ts} | 67 +-------------- tfjs-core/src/tests.ts | 3 +- 3 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 tfjs-core/src/ops/non_max_suppression_async_test.ts rename tfjs-core/src/ops/{image_ops_test.ts => non_max_suppression_test.ts} (77%) diff --git a/tfjs-core/src/ops/non_max_suppression_async_test.ts b/tfjs-core/src/ops/non_max_suppression_async_test.ts new file mode 100644 index 00000000000..c5e453b4fe8 --- /dev/null +++ b/tfjs-core/src/ops/non_max_suppression_async_test.ts @@ -0,0 +1,84 @@ +/** + * @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 * as tf from '../index'; +import {ALL_ENVS, describeWithFlags} from '../jasmine_util'; +import {expectArraysClose, expectArraysEqual} from '../test_util'; + +describeWithFlags('nonMaxSuppressionAsync', ALL_ENVS, () => { + describe('NonMaxSuppressionAsync basic', () => { + it('select from three clusters', async () => { + const boxes = tf.tensor2d( + [ + 0, 0, 1, 1, 0, 0.1, 1, 1.1, 0, -0.1, 1, 0.9, + 0, 10, 1, 11, 0, 10.1, 1, 11.1, 0, 100, 1, 101 + ], + [6, 4]); + const scores = tf.tensor1d([0.9, 0.75, 0.6, 0.95, 0.5, 0.3]); + const maxOutputSize = 3; + const iouThreshold = 0.5; + const scoreThreshold = 0; + const indices = await tf.image.nonMaxSuppressionAsync( + boxes, scores, maxOutputSize, iouThreshold, scoreThreshold); + + expect(indices.shape).toEqual([3]); + expectArraysEqual(await indices.data(), [3, 0, 5]); + }); + + it('accepts a tensor-like object', async () => { + const boxes = [[0, 0, 1, 1], [0, 1, 1, 2]]; + const scores = [1, 2]; + const indices = await tf.image.nonMaxSuppressionAsync(boxes, scores, 10); + expect(indices.shape).toEqual([2]); + expect(indices.dtype).toEqual('int32'); + expectArraysEqual(await indices.data(), [1, 0]); + }); + }); + + describe('NonMaxSuppressionWithScoreAsync', () => { + it('select from three clusters with SoftNMS', async () => { + const boxes = tf.tensor2d( + [ + 0, 0, 1, 1, 0, 0.1, 1, 1.1, 0, -0.1, 1, 0.9, + 0, 10, 1, 11, 0, 10.1, 1, 11.1, 0, 100, 1, 101 + ], + [6, 4]); + const scores = tf.tensor1d([0.9, 0.75, 0.6, 0.95, 0.5, 0.3]); + const maxOutputSize = 6; + const iouThreshold = 1.0; + const scoreThreshold = 0; + const softNmsSigma = 0.5; + + const numTensorsBefore = tf.memory().numTensors; + + const {selectedIndices, selectedScores} = + await tf.image.nonMaxSuppressionWithScoreAsync( + boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, + softNmsSigma); + + const numTensorsAfter = tf.memory().numTensors; + + expectArraysEqual(await selectedIndices.data(), [3, 0, 1, 5, 4, 2]); + + expectArraysClose( + await selectedScores.data(), [0.95, 0.9, 0.384, 0.3, 0.256, 0.197]); + + // The number of tensors should increase by the number of tensors + // returned (i.e. selectedIndices and selectedScores). + expect(numTensorsAfter).toEqual(numTensorsBefore + 2); + }); + }); +}); diff --git a/tfjs-core/src/ops/image_ops_test.ts b/tfjs-core/src/ops/non_max_suppression_test.ts similarity index 77% rename from tfjs-core/src/ops/image_ops_test.ts rename to tfjs-core/src/ops/non_max_suppression_test.ts index 53fc4da78d2..e1c0ffb0b6f 100644 --- a/tfjs-core/src/ops/image_ops_test.ts +++ b/tfjs-core/src/ops/non_max_suppression_test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC. All Rights Reserved. + * 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 @@ -226,68 +226,3 @@ describeWithFlags('nonMaxSuppression', ALL_ENVS, () => { }); }); }); - -describeWithFlags('nonMaxSuppressionAsync', ALL_ENVS, () => { - describe('NonMaxSuppressionAsync basic', () => { - it('select from three clusters', async () => { - const boxes = tf.tensor2d( - [ - 0, 0, 1, 1, 0, 0.1, 1, 1.1, 0, -0.1, 1, 0.9, - 0, 10, 1, 11, 0, 10.1, 1, 11.1, 0, 100, 1, 101 - ], - [6, 4]); - const scores = tf.tensor1d([0.9, 0.75, 0.6, 0.95, 0.5, 0.3]); - const maxOutputSize = 3; - const iouThreshold = 0.5; - const scoreThreshold = 0; - const indices = await tf.image.nonMaxSuppressionAsync( - boxes, scores, maxOutputSize, iouThreshold, scoreThreshold); - - expect(indices.shape).toEqual([3]); - expectArraysEqual(await indices.data(), [3, 0, 5]); - }); - - it('accepts a tensor-like object', async () => { - const boxes = [[0, 0, 1, 1], [0, 1, 1, 2]]; - const scores = [1, 2]; - const indices = await tf.image.nonMaxSuppressionAsync(boxes, scores, 10); - expect(indices.shape).toEqual([2]); - expect(indices.dtype).toEqual('int32'); - expectArraysEqual(await indices.data(), [1, 0]); - }); - }); - - describe('NonMaxSuppressionWithScoreAsync', () => { - it('select from three clusters with SoftNMS', async () => { - const boxes = tf.tensor2d( - [ - 0, 0, 1, 1, 0, 0.1, 1, 1.1, 0, -0.1, 1, 0.9, - 0, 10, 1, 11, 0, 10.1, 1, 11.1, 0, 100, 1, 101 - ], - [6, 4]); - const scores = tf.tensor1d([0.9, 0.75, 0.6, 0.95, 0.5, 0.3]); - const maxOutputSize = 6; - const iouThreshold = 1.0; - const scoreThreshold = 0; - const softNmsSigma = 0.5; - - const numTensorsBefore = tf.memory().numTensors; - - const {selectedIndices, selectedScores} = - await tf.image.nonMaxSuppressionWithScoreAsync( - boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, - softNmsSigma); - - const numTensorsAfter = tf.memory().numTensors; - - expectArraysEqual(await selectedIndices.data(), [3, 0, 1, 5, 4, 2]); - - expectArraysClose( - await selectedScores.data(), [0.95, 0.9, 0.384, 0.3, 0.256, 0.197]); - - // The number of tensors should increase by the number of tensors - // returned (i.e. selectedIndices and selectedScores). - expect(numTensorsAfter).toEqual(numTensorsBefore + 2); - }); - }); -}); diff --git a/tfjs-core/src/tests.ts b/tfjs-core/src/tests.ts index 3b45ea27b94..7dd1b646e50 100644 --- a/tfjs-core/src/tests.ts +++ b/tfjs-core/src/tests.ts @@ -80,7 +80,6 @@ import './ops/gather_nd_test'; import './ops/gram_schmidt_test'; import './ops/greater_equal_test'; import './ops/greater_test'; -import './ops/image_ops_test'; import './ops/in_top_k_test'; import './ops/leaky_relu_test'; import './ops/less_equal_test'; @@ -98,6 +97,8 @@ import './ops/max_pool_test'; import './ops/max_pool_with_argmax_test'; import './ops/moving_average_test'; import './ops/multinomial_test'; +import './ops/non_max_suppression_async_test'; +import './ops/non_max_suppression_test'; import './ops/not_equal_test'; import './ops/one_hot_test'; import './ops/operation_test';