1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 #include "tensorflow/core/framework/allocator.h" 17 #include "tensorflow/core/framework/fake_input.h" 18 #include "tensorflow/core/framework/node_def_builder.h" 19 #include "tensorflow/core/framework/op_kernel.h" 20 #include "tensorflow/core/framework/tensor.h" 21 #include "tensorflow/core/framework/tensor_testutil.h" 22 #include "tensorflow/core/framework/types.h" 23 #include "tensorflow/core/framework/types.pb.h" 24 #include "tensorflow/core/kernels/ops_testutil.h" 25 #include "tensorflow/core/kernels/ops_util.h" 26 #include "tensorflow/core/lib/core/status_test_util.h" 27 #include "tensorflow/core/lib/random/random.h" 28 #include "tensorflow/core/platform/test.h" 29 30 namespace tensorflow { 31 32 class ResizeBilinearOpTest : public OpsTestBase { 33 protected: 34 ResizeBilinearOpTest() { 35 TF_EXPECT_OK(NodeDefBuilder("resize_bilinear_op", "ResizeBilinear") 36 .Input(FakeInput(DT_FLOAT)) 37 .Input(FakeInput(DT_INT32)) 38 .Attr("align_corners", false) 39 .Finalize(node_def())); 40 TF_EXPECT_OK(InitOp()); 41 } 42 43 const Tensor* SetRandomImageInput(const TensorShape& shape) { 44 inputs_.clear(); 45 46 CHECK_EQ(shape.dims(), 4) << "All images must have 4 dimensions."; 47 bool is_ref = IsRefType(input_types_[inputs_.size()]); 48 Tensor* input = new Tensor(device_->GetAllocator(AllocatorAttributes()), 49 DataTypeToEnum<float>::v(), shape); 50 input->flat<float>().setRandom(); 51 tensors_.push_back(input); 52 if (is_ref) { 53 CHECK_EQ(RemoveRefType(input_types_[inputs_.size()]), 54 DataTypeToEnum<float>::v()); 55 inputs_.push_back({&lock_for_refs_, input}); 56 } else { 57 CHECK_EQ(input_types_[inputs_.size()], DataTypeToEnum<float>::v()); 58 inputs_.push_back({nullptr, input}); 59 } 60 return input; 61 } 62 63 // This is the straight forward unoptimized implementation of resize bilinear 64 // We use this to confirm that the optimized version is exactly identical. 65 void ResizeBilinearBaseline(TTypes<float, 4>::ConstTensor images, 66 TTypes<float, 4>::Tensor output) { 67 const int batch = images.dimension(0); 68 const int64 in_height = images.dimension(1); 69 const int64 in_width = images.dimension(2); 70 const int channels = images.dimension(3); 71 72 ASSERT_EQ(batch, output.dimension(0)); 73 ASSERT_EQ(channels, output.dimension(3)); 74 75 const int64 out_height = output.dimension(1); 76 const int64 out_width = output.dimension(2); 77 78 const float height_scale = in_height / static_cast<float>(out_height); 79 const float width_scale = in_width / static_cast<float>(out_width); 80 81 for (int b = 0; b < batch; ++b) { 82 for (int64 y = 0; y < out_height; ++y) { 83 const float in_y = y * height_scale; 84 const int64 top_y_index = static_cast<int64>(floorf(in_y)); 85 const int64 bottom_y_index = 86 std::min(static_cast<int64>(ceilf(in_y)), in_height - 1); 87 const float y_lerp = in_y - top_y_index; 88 for (int64 x = 0; x < out_width; ++x) { 89 const float in_x = x * width_scale; 90 const int64 left_x_index = static_cast<int64>(floorf(in_x)); 91 const int64 right_x_index = 92 std::min(static_cast<int64>(ceilf(in_x)), in_width - 1); 93 const float x_lerp = in_x - left_x_index; 94 for (int c = 0; c < channels; ++c) { 95 const float top_left = images(b, top_y_index, left_x_index, c); 96 const float top_right = images(b, top_y_index, right_x_index, c); 97 const float bottom_left = 98 images(b, bottom_y_index, left_x_index, c); 99 const float bottom_right = 100 images(b, bottom_y_index, right_x_index, c); 101 const float top = top_left + (top_right - top_left) * x_lerp; 102 const float bottom = 103 bottom_left + (bottom_right - bottom_left) * x_lerp; 104 output(b, y, x, c) = top + (bottom - top) * y_lerp; 105 } 106 } 107 } 108 } 109 } 110 111 void TestResize(int batch_size, int input_width, int input_height, 112 int channels, int output_width, int output_height) { 113 const TensorShape shape({batch_size, input_width, input_height, channels}); 114 const Tensor* input = SetRandomImageInput(shape); 115 AddInputFromArray<int32>(TensorShape({2}), {output_width, output_height}); 116 TF_ASSERT_OK(RunOpKernel()); 117 118 std::unique_ptr<Tensor> expected(new Tensor( 119 device_->GetAllocator(AllocatorAttributes()), 120 DataTypeToEnum<float>::v(), 121 TensorShape({batch_size, output_width, output_height, channels}))); 122 ResizeBilinearBaseline(input->tensor<float, 4>(), 123 expected->tensor<float, 4>()); 124 test::ExpectTensorEqual<float>(*expected, *GetOutput(0)); 125 } 126 127 void RunManyRandomTests(int channels) { 128 for (int batch_size : {1, 2, 5}) { 129 for (int in_w : {2, 4, 7, 20, 165}) { 130 for (int in_h : {1, 3, 5, 8, 100, 233}) { 131 for (int target_height : {1, 2, 3, 50, 113}) { 132 for (int target_width : {target_height, target_height / 2 + 1}) { 133 TestResize(batch_size, in_w, in_h, channels, target_width, 134 target_height); 135 } 136 } 137 } 138 } 139 } 140 } 141 }; 142 143 class ResizeBilinearOpAlignCornersTest : public OpsTestBase { 144 protected: 145 ResizeBilinearOpAlignCornersTest() { 146 TF_EXPECT_OK(NodeDefBuilder("resize_bilinear_op", "ResizeBilinear") 147 .Input(FakeInput(DT_FLOAT)) 148 .Input(FakeInput(DT_INT32)) 149 .Attr("align_corners", true) 150 .Finalize(node_def())); 151 TF_EXPECT_OK(InitOp()); 152 } 153 }; 154 155 TEST_F(ResizeBilinearOpTest, TestResizeRandomDataSeveralInputsSizes1Channel) { 156 RunManyRandomTests(1); 157 } 158 159 TEST_F(ResizeBilinearOpTest, TestResizeRandomDataSeveralInputsSizes3Channels) { 160 RunManyRandomTests(3); 161 } 162 163 TEST_F(ResizeBilinearOpTest, TestResizeRandomDataSeveralInputsSizes4Channels) { 164 RunManyRandomTests(4); 165 } 166 167 TEST_F(ResizeBilinearOpTest, TestBilinear2x2To1x1) { 168 // Input: 169 // 1, 2 170 // 3, 4 171 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 172 AddInputFromArray<int32>(TensorShape({2}), {1, 1}); 173 TF_ASSERT_OK(RunOpKernel()); 174 175 // When scaling down, we have to arbitrarily pick a pixel from the 176 // original input. In this case, we choose the top/left most pixel. 177 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 1, 1, 1})); 178 test::FillValues<float>(&expected, {1.0}); 179 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 180 } 181 182 TEST_F(ResizeBilinearOpTest, TestBilinearRandom2x2To1x1) { 183 const Tensor* input = SetRandomImageInput(TensorShape({1, 2, 2, 1})); 184 AddInputFromArray<int32>(TensorShape({2}), {1, 1}); 185 TF_ASSERT_OK(RunOpKernel()); 186 187 // When scaling down, we have to arbitrarily pick a pixel from the 188 // original input. In this case, we choose the top/left most pixel. 189 Tensor* output = GetOutput(0); 190 std::unique_ptr<Tensor> expected( 191 new Tensor(device_->GetAllocator(AllocatorAttributes()), 192 DataTypeToEnum<float>::v(), TensorShape({1, 1, 1, 1}))); 193 ResizeBilinearBaseline(input->tensor<float, 4>(), 194 expected->tensor<float, 4>()); 195 EXPECT_EQ(input->flat<float>()(0), output->flat<float>()(0)); 196 test::ExpectTensorEqual<float>(*expected, *output); 197 } 198 199 TEST_F(ResizeBilinearOpAlignCornersTest, TestBilinearAlignCorners2x2To1x1) { 200 // Input: 201 // 1, 2 202 // 3, 4 203 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 204 AddInputFromArray<int32>(TensorShape({2}), {1, 1}); 205 TF_ASSERT_OK(RunOpKernel()); 206 207 // When scaling down, we have to arbitrarily pick a pixel from the 208 // original input. In this case, we choose the top/left most pixel. 209 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 1, 1, 1})); 210 test::FillValues<float>(&expected, {1.0}); 211 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 212 } 213 214 TEST_F(ResizeBilinearOpTest, TestBilinear2x2To3x3) { 215 // Input: 216 // 1, 2 217 // 3, 4 218 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 219 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 220 TF_ASSERT_OK(RunOpKernel()); 221 222 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); 223 224 // clang-format off 225 test::FillValues<float>(&expected, 226 {1, 5.0f / 3, 2, 227 7.0f / 3, 3, 10.0f / 3, 228 3, 11.0f / 3, 4}); 229 230 // clang-format on 231 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 232 } 233 234 TEST_F(ResizeBilinearOpAlignCornersTest, TestBilinearAlignCorners2x2To3x3) { 235 // Input: 236 // 1, 2 237 // 3, 4 238 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 239 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 240 TF_ASSERT_OK(RunOpKernel()); 241 242 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); 243 244 // The corners exactly align with the original corners, and we bilinear 245 // interpolate the values in between. 246 247 // clang-format off 248 test::FillValues<float>(&expected, 249 {1, 1.5, 2, 250 2, 2.5, 3, 251 3, 3.5, 4}); 252 253 // clang-format on 254 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 255 } 256 257 TEST_F(ResizeBilinearOpTest, TestBilinear3x3To2x2) { 258 // Input: 259 // 1, 2, 3 260 // 4, 5, 6 261 // 7, 8, 9 262 AddInputFromArray<float>(TensorShape({1, 3, 3, 1}), 263 {1, 2, 3, 4, 5, 6, 7, 8, 9}); 264 AddInputFromArray<int32>(TensorShape({2}), {2, 2}); 265 TF_ASSERT_OK(RunOpKernel()); 266 267 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 2, 2, 1})); 268 269 // clang-format off 270 test::FillValues<float>(&expected, 271 {1, 2.5, 272 5.5, 7}); 273 274 // clang-format on 275 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 276 } 277 278 TEST_F(ResizeBilinearOpAlignCornersTest, TestBilinearAlignCorners3x3To2x2) { 279 // Input: 280 // 1, 2, 3 281 // 4, 5, 6 282 // 7, 8, 9 283 AddInputFromArray<float>(TensorShape({1, 3, 3, 1}), 284 {1, 2, 3, 4, 5, 6, 7, 8, 9}); 285 AddInputFromArray<int32>(TensorShape({2}), {2, 2}); 286 TF_ASSERT_OK(RunOpKernel()); 287 288 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 2, 2, 1})); 289 290 // clang-format off 291 test::FillValues<float>(&expected, 292 {1, 3, 293 7, 9}); 294 295 // clang-format on 296 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 297 } 298 299 TEST_F(ResizeBilinearOpTest, TestBilinear3x3To4x4) { 300 // Input: 301 // 1, 2, 3, 302 // 4, 5, 6, 303 // 7, 8, 9 304 AddInputFromArray<float>(TensorShape({1, 3, 3, 1}), 305 {1, 2, 3, 4, 5, 6, 7, 8, 9}); 306 AddInputFromArray<int32>(TensorShape({2}), {4, 4}); 307 TF_ASSERT_OK(RunOpKernel()); 308 309 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 4, 4, 1})); 310 // clang-format off 311 test::FillValues<float>(&expected, 312 {1, 1.75, 2.5, 3, 313 3.25, 4, 4.75, 5.25, 314 5.5, 6.25, 7, 7.5, 315 7, 7.75, 8.5, 9}); 316 317 // clang-format on 318 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 319 } 320 321 TEST_F(ResizeBilinearOpTest, TestBilinear4x4To3x3) { 322 // Input: 323 // 1, 2, 3, 4 324 // 5, 6, 7, 8 325 // 9, 10, 11, 12 326 // 13, 14, 15, 16 327 AddInputFromArray<float>( 328 TensorShape({1, 4, 4, 1}), 329 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); 330 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 331 TF_ASSERT_OK(RunOpKernel()); 332 333 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); 334 335 // clang-format off 336 test::FillValues<float>(&expected, 337 {1, 7.0f/3, 11.0f/3, 338 19.0f/3, 23.0f/3, 27.0f/3, 339 35.0f/3, 39.0f/3, 43.0f/3}); 340 341 // clang-format on 342 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 343 } 344 345 TEST_F(ResizeBilinearOpAlignCornersTest, TestBilinearAlignCorners4x4To3x3) { 346 // Input: 347 // 1, 2, 3, 4 348 // 5, 6, 7, 8 349 // 9, 10, 11, 12 350 // 13, 14, 15, 16 351 AddInputFromArray<float>( 352 TensorShape({1, 4, 4, 1}), 353 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); 354 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 355 TF_ASSERT_OK(RunOpKernel()); 356 357 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 1})); 358 359 // clang-format off 360 test::FillValues<float>(&expected, 361 { 1, 2.5, 4, 362 7, 8.5, 10, 363 13, 14.5, 16}); 364 365 // clang-format on 366 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 367 } 368 369 TEST_F(ResizeBilinearOpTest, TestBilinear2x2To3x3Batch2) { 370 // Input: 371 // 1, 2 372 // 3, 4 373 // 374 // repeated twice 375 AddInputFromArray<float>(TensorShape({2, 2, 2, 1}), {1, 2, 3, 4, 1, 2, 3, 4}); 376 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 377 TF_ASSERT_OK(RunOpKernel()); 378 379 Tensor expected(allocator(), DT_FLOAT, TensorShape({2, 3, 3, 1})); 380 // clang-format off 381 test::FillValues<float>(&expected, 382 {1, 5.0f/3, 2, 7.0f/3, 3, 10.0f/3, 3, 11.0f/3, 4, 383 1, 5.0f/3, 2, 7.0f/3, 3, 10.0f/3, 3, 11.0f/3, 4 384 }); 385 // clang-format on 386 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 387 } 388 389 TEST_F(ResizeBilinearOpTest, TestBilinear2x2x2To3x3x2) { 390 AddInputFromArray<float>(TensorShape({1, 2, 2, 2}), 391 {1, -1, 2, -2, 3, -3, 4, -4}); 392 AddInputFromArray<int32>(TensorShape({2}), {3, 3}); 393 TF_ASSERT_OK(RunOpKernel()); 394 395 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 3, 3, 2})); 396 // clang-format off 397 test::FillValues<float>(&expected, 398 { 399 1, -1, 400 5.0f/3, -5.0f/3, 401 2, -2, 402 7.0f/3, -7.0f/3, 403 3, -3, 404 10.0f/3, -10.0f/3, 405 3, -3, 406 11.0f/3, -11.0f/3, 407 4, -4 408 }); 409 // clang-format on 410 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 411 } 412 413 TEST_F(ResizeBilinearOpTest, TestBilinear2x2To4x4) { 414 // Input: 415 // 1, 2 416 // 3, 4 417 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 418 AddInputFromArray<int32>(TensorShape({2}), {4, 4}); 419 TF_ASSERT_OK(RunOpKernel()); 420 421 Tensor expected(allocator(), DT_FLOAT, TensorShape({1, 4, 4, 1})); 422 // clang-format off 423 test::FillValues<float>(&expected, 424 {1, 1.5, 2, 2, 425 2, 2.5, 3, 3, 426 3, 3.5, 4, 4, 427 3, 3.5, 4, 4}); 428 // clang-format on 429 test::ExpectTensorEqual<float>(expected, *GetOutput(0)); 430 } 431 432 // similar_size case 433 TEST_F(ResizeBilinearOpTest, Test1_1c) { TestResize(1, 183, 299, 1, 299, 299); } 434 TEST_F(ResizeBilinearOpTest, Test1_3c) { TestResize(1, 183, 299, 3, 299, 299); } 435 436 // Significantly smaller: scale_up case 437 TEST_F(ResizeBilinearOpTest, Test2_1c) { TestResize(1, 141, 186, 1, 299, 299); } 438 TEST_F(ResizeBilinearOpTest, Test2_3c) { TestResize(1, 141, 186, 3, 299, 299); } 439 440 // Significantly larger: scale_down case 441 TEST_F(ResizeBilinearOpTest, Test3_1c) { TestResize(1, 749, 603, 1, 299, 299); } 442 TEST_F(ResizeBilinearOpTest, Test3_3c) { TestResize(1, 749, 603, 3, 299, 299); } 443 444 // Exactly the same size 445 TEST_F(ResizeBilinearOpTest, Test4_1c) { TestResize(1, 299, 299, 1, 299, 299); } 446 TEST_F(ResizeBilinearOpTest, Test4_3c) { TestResize(1, 299, 299, 3, 299, 299); } 447 448 // Slightly smaller: similar_size case 449 TEST_F(ResizeBilinearOpTest, Test5_1c) { TestResize(1, 298, 297, 1, 299, 299); } 450 TEST_F(ResizeBilinearOpTest, Test5_3c) { TestResize(1, 298, 297, 3, 299, 299); } 451 452 // Slightly bigger: similar_size case 453 TEST_F(ResizeBilinearOpTest, Test6_1c) { TestResize(1, 304, 303, 1, 299, 299); } 454 TEST_F(ResizeBilinearOpTest, Test6_3c) { TestResize(1, 304, 303, 3, 299, 299); } 455 456 TEST_F(ResizeBilinearOpTest, TestInvalidOutputSize) { 457 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 458 AddInputFromArray<int32>(TensorShape({2}), {0, 0}); 459 Status s = RunOpKernel(); 460 EXPECT_TRUE( 461 StringPiece(s.ToString()) 462 .contains("Invalid argument: output dimensions must be positive")) 463 << s; 464 } 465 466 TEST_F(ResizeBilinearOpTest, TestInvalidInputShape) { 467 AddInputFromArray<float>(TensorShape({2, 2, 1}), {1, 2, 3, 4}); 468 AddInputFromArray<int32>(TensorShape({2}), {4, 4}); 469 Status s = RunOpKernel(); 470 EXPECT_TRUE(StringPiece(s.ToString()) 471 .contains("Invalid argument: input must be 4-dimensional")) 472 << s; 473 } 474 475 TEST_F(ResizeBilinearOpTest, TestInvalidSizeDim) { 476 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 477 AddInputFromArray<int32>(TensorShape({2, 1}), {4, 4}); 478 Status s = RunOpKernel(); 479 EXPECT_TRUE(StringPiece(s.ToString()) 480 .contains("Invalid argument: shape_t must be 1-dimensional")) 481 << s; 482 } 483 484 TEST_F(ResizeBilinearOpTest, TestInvalidSizeElements) { 485 AddInputFromArray<float>(TensorShape({1, 2, 2, 1}), {1, 2, 3, 4}); 486 AddInputFromArray<int32>(TensorShape({3}), {4, 4, 1}); 487 Status s = RunOpKernel(); 488 EXPECT_TRUE(StringPiece(s.ToString()) 489 .contains("Invalid argument: shape_t must have two elements")) 490 << s; 491 } 492 493 } // namespace tensorflow 494