Home | History | Annotate | Download | only in kernels
      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