/* Copyright 2015 The TensorFlow Authors. 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. ==============================================================================*/ #include "tensorflow/core/framework/allocator.h" #include "tensorflow/core/framework/fake_input.h" #include "tensorflow/core/framework/node_def_builder.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_testutil.h" #include "tensorflow/core/framework/types.h" #include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/kernels/ops_testutil.h" #include "tensorflow/core/kernels/ops_util.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { class NonMaxSuppressionOpTest : public OpsTestBase { protected: void MakeOp(float iou_threshold) { TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppression") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Attr("iou_threshold", iou_threshold) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } }; TEST_F(NonMaxSuppressionOpTest, TestSelectFromThreeClusters) { MakeOp(.5); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectFromThreeClustersFlippedCoordinates) { MakeOp(.5); AddInputFromArray(TensorShape({6, 4}), {1, 1, 0, 0, 0, 0.1f, 1, 1.1f, 0, .9f, 1, -0.1f, 0, 10, 1, 11, 1, 10.1f, 0, 11.1f, 1, 101, 0, 100}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectAtMostTwoBoxesFromThreeClusters) { MakeOp(.5); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {2}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectWithNegativeScores) { MakeOp(.5); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray( TensorShape({6}), {.9f - 10.0f, .75f - 10.0f, .6f - 10.0f, .95f - 10.0f, .5f - 10.0f, .3f - 10.0f}); AddInputFromArray(TensorShape({}), {6}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectAtMostThirtyBoxesFromThreeClusters) { MakeOp(.5); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {30}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectSingleBox) { MakeOp(.5); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestSelectFromTenIdenticalBoxes) { MakeOp(.5); int num_boxes = 10; std::vector corners(num_boxes * 4); std::vector scores(num_boxes); for (int i = 0; i < num_boxes; ++i) { corners[i * 4 + 0] = 0; corners[i * 4 + 1] = 0; corners[i * 4 + 2] = 1; corners[i * 4 + 3] = 1; scores[i] = .9; } AddInputFromArray(TensorShape({num_boxes, 4}), corners); AddInputFromArray(TensorShape({num_boxes}), scores); AddInputFromArray(TensorShape({}), {3}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionOpTest, TestInconsistentBoxAndScoreShapes) { MakeOp(.5); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({5}), {.9f, .75f, .6f, .95f, .5f}); AddInputFromArray(TensorShape({}), {30}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "scores has incompatible shape")) << s; } TEST_F(NonMaxSuppressionOpTest, TestInvalidIOUThreshold) { MakeOp(1.2); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "iou_threshold must be in [0, 1]")) << s; } TEST_F(NonMaxSuppressionOpTest, TestEmptyInput) { MakeOp(.5); AddInputFromArray(TensorShape({0, 4}), {}); AddInputFromArray(TensorShape({0}), {}); AddInputFromArray(TensorShape({}), {30}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({0})); test::FillValues(&expected, {}); test::ExpectTensorEqual(expected, *GetOutput(0)); } // // NonMaxSuppressionV2Op Tests // class NonMaxSuppressionV2OpTest : public OpsTestBase { protected: void MakeOp() { TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppressionV2") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Input(FakeInput(DT_FLOAT)) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } }; TEST_F(NonMaxSuppressionV2OpTest, TestSelectFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestSelectFromThreeClustersFlippedCoordinates) { MakeOp(); AddInputFromArray(TensorShape({6, 4}), {1, 1, 0, 0, 0, 0.1f, 1, 1.1f, 0, .9f, 1, -0.1f, 0, 10, 1, 11, 1, 10.1f, 0, 11.1f, 1, 101, 0, 100}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestSelectAtMostTwoBoxesFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {2}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestSelectAtMostThirtyBoxesFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestSelectSingleBox) { MakeOp(); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestSelectFromTenIdenticalBoxes) { MakeOp(); int num_boxes = 10; std::vector corners(num_boxes * 4); std::vector scores(num_boxes); for (int i = 0; i < num_boxes; ++i) { corners[i * 4 + 0] = 0; corners[i * 4 + 1] = 0; corners[i * 4 + 2] = 1; corners[i * 4 + 3] = 1; scores[i] = .9; } AddInputFromArray(TensorShape({num_boxes, 4}), corners); AddInputFromArray(TensorShape({num_boxes}), scores); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV2OpTest, TestInconsistentBoxAndScoreShapes) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({5}), {.9f, .75f, .6f, .95f, .5f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "scores has incompatible shape")) << s; } TEST_F(NonMaxSuppressionV2OpTest, TestInvalidIOUThreshold) { MakeOp(); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {1.2f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "iou_threshold must be in [0, 1]")) << s; } TEST_F(NonMaxSuppressionV2OpTest, TestEmptyInput) { MakeOp(); AddInputFromArray(TensorShape({0, 4}), {}); AddInputFromArray(TensorShape({0}), {}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({0})); test::FillValues(&expected, {}); test::ExpectTensorEqual(expected, *GetOutput(0)); } // // NonMaxSuppressionV3Op Tests // class NonMaxSuppressionV3OpTest : public OpsTestBase { protected: void MakeOp() { TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppressionV3") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } }; TEST_F(NonMaxSuppressionV3OpTest, TestSelectFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectFromThreeClustersWithScoreThreshold) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {0.5f}); AddInputFromArray(TensorShape({}), {0.4f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectFromThreeClustersWithScoreThresholdZeroScores) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.1, 0, 0, .3, .2, -5.0}); // If we ask for more boxes than we actually expect to get back; // should still only get 2 boxes back. AddInputFromArray(TensorShape({}), {6}); AddInputFromArray(TensorShape({}), {0.5f}); AddInputFromArray(TensorShape({}), {-3.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectFromThreeClustersFlippedCoordinates) { MakeOp(); AddInputFromArray(TensorShape({6, 4}), {1, 1, 0, 0, 0, 0.1f, 1, 1.1f, 0, .9f, 1, -0.1f, 0, 10, 1, 11, 1, 10.1f, 0, 11.1f, 1, 101, 0, 100}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectAtMostTwoBoxesFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {2}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectAtMostThirtyBoxesFromThreeClusters) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectSingleBox) { MakeOp(); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestSelectFromTenIdenticalBoxes) { MakeOp(); int num_boxes = 10; std::vector corners(num_boxes * 4); std::vector scores(num_boxes); for (int i = 0; i < num_boxes; ++i) { corners[i * 4 + 0] = 0; corners[i * 4 + 1] = 0; corners[i * 4 + 2] = 1; corners[i * 4 + 3] = 1; scores[i] = .9; } AddInputFromArray(TensorShape({num_boxes, 4}), corners); AddInputFromArray(TensorShape({num_boxes}), scores); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionV3OpTest, TestInconsistentBoxAndScoreShapes) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({5}), {.9f, .75f, .6f, .95f, .5f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "scores has incompatible shape")) << s; } TEST_F(NonMaxSuppressionV3OpTest, TestInvalidIOUThreshold) { MakeOp(); AddInputFromArray(TensorShape({1, 4}), {0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {1.2f}); AddInputFromArray(TensorShape({}), {0.0f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "iou_threshold must be in [0, 1]")) << s; } TEST_F(NonMaxSuppressionV3OpTest, TestEmptyInput) { MakeOp(); AddInputFromArray(TensorShape({0, 4}), {}); AddInputFromArray(TensorShape({0}), {}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({0})); test::FillValues(&expected, {}); test::ExpectTensorEqual(expected, *GetOutput(0)); } // // NonMaxSuppressionV4Op Tests // class NonMaxSuppressionV4OpTest : public OpsTestBase { protected: void MakeOp() { TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppressionV4") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Attr("pad_to_max_output_size", true) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } }; TEST_F(NonMaxSuppressionV4OpTest, TestSelectFromThreeClustersPadFive) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {5}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); const auto expected_indices = test::AsTensor({3, 0, 5, 0, 0}); test::ExpectTensorEqual(expected_indices, *GetOutput(0)); Tensor expected_num_valid = test::AsScalar(3); test::ExpectTensorEqual(expected_num_valid, *GetOutput(1)); } TEST_F(NonMaxSuppressionV4OpTest, TestSelectFromThreeClustersPadFiveScoreThr) { MakeOp(); AddInputFromArray( TensorShape({6, 4}), {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {6}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.4f}); TF_ASSERT_OK(RunOpKernel()); const auto expected_indices = test::AsTensor({3, 0, 0, 0, 0, 0}); test::ExpectTensorEqual(expected_indices, *GetOutput(0)); Tensor expected_num_valid = test::AsScalar(2); test::ExpectTensorEqual(expected_num_valid, *GetOutput(1)); } // // NonMaxSuppressionWithOverlapsOp Tests // class NonMaxSuppressionWithOverlapsOpTest : public OpsTestBase { protected: void MakeOp() { TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppressionWithOverlaps") .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_INT32)) .Input(FakeInput(DT_FLOAT)) .Input(FakeInput(DT_FLOAT)) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); } void AddIoUInput(const std::vector& boxes) { ASSERT_EQ((boxes.size() % 4), 0); size_t num_boxes = boxes.size() / 4; std::vector iou_overlaps(num_boxes * num_boxes); // compute the pairwise IoU overlaps auto corner_access = [&boxes](size_t box_idx, size_t corner_idx) { return boxes[box_idx * 4 + corner_idx]; }; for (size_t i = 0; i < num_boxes; ++i) { for (size_t j = 0; j < num_boxes; ++j) { const float ymin_i = std::min(corner_access(i, 0), corner_access(i, 2)); const float xmin_i = std::min(corner_access(i, 1), corner_access(i, 3)); const float ymax_i = std::max(corner_access(i, 0), corner_access(i, 2)); const float xmax_i = std::max(corner_access(i, 1), corner_access(i, 3)); const float ymin_j = std::min(corner_access(j, 0), corner_access(j, 2)); const float xmin_j = std::min(corner_access(j, 1), corner_access(j, 3)); const float ymax_j = std::max(corner_access(j, 0), corner_access(j, 2)); const float xmax_j = std::max(corner_access(j, 1), corner_access(j, 3)); const float area_i = (ymax_i - ymin_i) * (xmax_i - xmin_i); const float area_j = (ymax_j - ymin_j) * (xmax_j - xmin_j); float iou; if (area_i <= 0 || area_j <= 0) { iou = 0.0; } else { const float intersection_ymin = std::max(ymin_i, ymin_j); const float intersection_xmin = std::max(xmin_i, xmin_j); const float intersection_ymax = std::min(ymax_i, ymax_j); const float intersection_xmax = std::min(xmax_i, xmax_j); const float intersection_area = std::max(intersection_ymax - intersection_ymin, 0.0) * std::max(intersection_xmax - intersection_xmin, 0.0); iou = intersection_area / (area_i + area_j - intersection_area); } iou_overlaps[i * num_boxes + j] = iou; } } AddInputFromArray(TensorShape({static_cast(num_boxes), static_cast(num_boxes)}), iou_overlaps); } }; TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectFromThreeClusters) { MakeOp(); AddIoUInput({0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectFromThreeClustersFlippedCoordinates) { MakeOp(); AddIoUInput({1, 1, 0, 0, 0, 0.1f, 1, 1.1f, 0, .9f, 1, -0.1f, 0, 10, 1, 11, 1, 10.1f, 0, 11.1f, 1, 101, 0, 100}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectAtMostTwoBoxesFromThreeClusters) { MakeOp(); AddIoUInput({0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {2}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({2})); test::FillValues(&expected, {3, 0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectAtMostThirtyBoxesFromThreeClusters) { MakeOp(); AddIoUInput({0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({3})); test::FillValues(&expected, {3, 0, 5}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectSingleBox) { MakeOp(); AddIoUInput({0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestSelectFromTenIdenticalBoxes) { MakeOp(); int num_boxes = 10; std::vector corners(num_boxes * 4); std::vector scores(num_boxes); for (int i = 0; i < num_boxes; ++i) { corners[i * 4 + 0] = 0; corners[i * 4 + 1] = 0; corners[i * 4 + 2] = 1; corners[i * 4 + 3] = 1; scores[i] = .9; } AddIoUInput(corners); AddInputFromArray(TensorShape({num_boxes}), scores); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({1})); test::FillValues(&expected, {0}); test::ExpectTensorEqual(expected, *GetOutput(0)); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestInconsistentBoxAndScoreShapes) { MakeOp(); AddIoUInput({0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f, 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101}); AddInputFromArray(TensorShape({5}), {.9f, .75f, .6f, .95f, .5f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE( str_util::StrContains(s.ToString(), "scores has incompatible shape")) << s; } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestInvalidOverlapsShape) { MakeOp(); AddInputFromArray(TensorShape({2, 3}), {0, 0, 0, 0, 0, 0}); AddInputFromArray(TensorShape({2}), {0.5f, 0.5f}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {0.f}); AddInputFromArray(TensorShape({}), {0.0f}); Status s = RunOpKernel(); ASSERT_FALSE(s.ok()); EXPECT_TRUE(str_util::StrContains(s.ToString(), "overlaps must be square")) << s; } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestThresholdGreaterOne) { MakeOp(); AddIoUInput({0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {1.2f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestThresholdSmallerZero) { MakeOp(); AddIoUInput({0, 0, 1, 1}); AddInputFromArray(TensorShape({1}), {.9f}); AddInputFromArray(TensorShape({}), {3}); AddInputFromArray(TensorShape({}), {-0.2f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); } TEST_F(NonMaxSuppressionWithOverlapsOpTest, TestEmptyInput) { MakeOp(); AddIoUInput({}); AddInputFromArray(TensorShape({0}), {}); AddInputFromArray(TensorShape({}), {30}); AddInputFromArray(TensorShape({}), {.5f}); AddInputFromArray(TensorShape({}), {0.0f}); TF_ASSERT_OK(RunOpKernel()); Tensor expected(allocator(), DT_INT32, TensorShape({0})); test::FillValues(&expected, {}); test::ExpectTensorEqual(expected, *GetOutput(0)); } } // namespace tensorflow