1 /* Copyright 2017 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/common_runtime/kernel_benchmark_testlib.h" 17 #include "tensorflow/core/framework/fake_input.h" 18 #include "tensorflow/core/framework/node_def_builder.h" 19 #include "tensorflow/core/framework/tensor.h" 20 #include "tensorflow/core/kernels/ops_testutil.h" 21 #include "tensorflow/core/platform/test.h" 22 #include "tensorflow/core/platform/test_benchmark.h" 23 24 namespace tensorflow { 25 26 class ResizeAreaOpTest : public OpsTestBase { 27 protected: 28 ResizeAreaOpTest() { 29 TF_EXPECT_OK(NodeDefBuilder("resize_area_op", "ResizeArea") 30 .Input(FakeInput(DT_FLOAT)) 31 .Input(FakeInput(DT_INT32)) 32 .Attr("align_corners", false) 33 .Finalize(node_def())); 34 TF_EXPECT_OK(InitOp()); 35 } 36 37 const Tensor* SetRandomImageInput(const TensorShape& shape) { 38 inputs_.clear(); 39 40 CHECK_EQ(shape.dims(), 4) << "All images must have 4 dimensions."; 41 bool is_ref = IsRefType(input_types_[inputs_.size()]); 42 Tensor* input = new Tensor(device_->GetAllocator(AllocatorAttributes()), 43 DataTypeToEnum<float>::v(), shape); 44 input->flat<float>().setRandom(); 45 tensors_.push_back(input); 46 if (is_ref) { 47 CHECK_EQ(RemoveRefType(input_types_[inputs_.size()]), 48 DataTypeToEnum<float>::v()); 49 inputs_.push_back({&lock_for_refs_, input}); 50 } else { 51 CHECK_EQ(input_types_[inputs_.size()], DataTypeToEnum<float>::v()); 52 inputs_.push_back({nullptr, input}); 53 } 54 return input; 55 } 56 57 private: 58 // This is the unoptimized implementation of ResizeArea. 59 // We use this to confirm that the optimized version is exactly identical. 60 void ResizeAreaBaseline(TTypes<float, 4>::ConstTensor input_data, 61 TTypes<float, 4>::Tensor output_data) { 62 const int batch_size = input_data.dimension(0); 63 const int64 in_height = input_data.dimension(1); 64 const int64 in_width = input_data.dimension(2); 65 const int channels = input_data.dimension(3); 66 67 ASSERT_EQ(batch_size, output_data.dimension(0)); 68 ASSERT_EQ(channels, output_data.dimension(3)); 69 70 const int64 out_height = output_data.dimension(1); 71 const int64 out_width = output_data.dimension(2); 72 73 const float height_scale = in_height / static_cast<float>(out_height); 74 const float width_scale = in_width / static_cast<float>(out_width); 75 76 // A temporary tensor for computing the sum. 77 Tensor sum_tensor(DT_FLOAT, TensorShape({channels})); 78 typename TTypes<float, 1>::Tensor sum_data = sum_tensor.vec<float>(); 79 80 // When using this algorithm for downsizing, the target pixel value is the 81 // weighted average of all the source pixels. The weight is determined by 82 // the contribution percentage of the source pixel. 83 // 84 // Let "scale" be "target_image_size/source_image_size". If 1/n of the 85 // source pixel contributes to the target pixel, then the weight is (1/n * 86 // scale); if the complete source pixel contributes to the target pixel, 87 // then the weight is scale. 88 // 89 // To visualize the implementation, use one dimension as an example: 90 // Resize in[4] to out[3]. 91 // scale = 3/4 = 0.75 92 // out[0]: in[0] and 1/3 of in[1] 93 // out[1]: 2/3 of in[1] and 2/3 of in[2] 94 // out[2]: 1/3 of in[2] and in[1] 95 // Hence, the output pixel values are: 96 // out[0] = (in[0] * 1.0 + in[1] * 1/3) * scale 97 // out[1] = (in[1] * 2/3 + in[2] * 2/3 * scale 98 // out[2] = (in[3] * 1/3 + in[3] * 1.0) * scale 99 float scale = 1.0 / (height_scale * width_scale); 100 for (int64 b = 0; b < batch_size; ++b) { 101 for (int64 y = 0; y < out_height; ++y) { 102 const float in_y = y * height_scale; 103 const float in_y1 = (y + 1) * height_scale; 104 // The start and end height indices of all the cells that could 105 // contribute to the target cell. 106 int64 y_start = floor(in_y); 107 int64 y_end = ceil(in_y1); 108 109 for (int64 x = 0; x < out_width; ++x) { 110 const float in_x = x * width_scale; 111 const float in_x1 = (x + 1) * width_scale; 112 // The start and end width indices of all the cells that could 113 // contribute to the target cell. 114 int64 x_start = floor(in_x); 115 int64 x_end = ceil(in_x1); 116 117 sum_data.setConstant(0.0); 118 for (int64 i = y_start; i < y_end; ++i) { 119 float scale_y = i < in_y 120 ? (i + 1 > in_y1 ? height_scale : i + 1 - in_y) 121 : (i + 1 > in_y1 ? in_y1 - i : 1.0); 122 for (int64 j = x_start; j < x_end; ++j) { 123 float scale_x = j < in_x 124 ? (j + 1 > in_x1 ? width_scale : j + 1 - in_x) 125 : (j + 1 > in_x1 ? in_x1 - j : 1.0); 126 for (int64 c = 0; c < channels; ++c) { 127 #define BOUND(val, limit) std::min(((limit)-1ll), (std::max(0ll, (val)))) 128 sum_data(c) += 129 static_cast<float>(input_data(b, BOUND(i, in_height), 130 BOUND(j, in_width), c)) * 131 scale_y * scale_x * scale; 132 #undef BOUND 133 } 134 } 135 } 136 for (int64 c = 0; c < channels; ++c) { 137 output_data(b, y, x, c) = sum_data(c); 138 } 139 } 140 } 141 } 142 } 143 144 protected: 145 void RunRandomTest(int in_height, int in_width, int target_height, 146 int target_width, int channels) { 147 const Tensor* input = 148 SetRandomImageInput(TensorShape({1, in_height, in_width, channels})); 149 AddInputFromArray<int32>(TensorShape({2}), {target_height, target_width}); 150 151 TF_ASSERT_OK(RunOpKernel()); 152 std::unique_ptr<Tensor> expected( 153 new Tensor(device_->GetAllocator(AllocatorAttributes()), 154 DataTypeToEnum<float>::v(), 155 TensorShape({1, target_height, target_width, channels}))); 156 ResizeAreaBaseline(input->tensor<float, 4>(), expected->tensor<float, 4>()); 157 test::ExpectTensorNear<float>(*expected, *GetOutput(0), 0.00001); 158 } 159 160 void RunManyRandomTests(int channels) { 161 for (int in_w : {2, 4, 7, 20, 165}) { 162 for (int in_h : {1, 3, 5, 8, 100, 233}) { 163 for (int target_height : {1, 2, 3, 50, 113}) { 164 for (int target_width : {target_height, target_height / 2 + 1}) { 165 RunRandomTest(in_h, in_w, target_height, target_width, channels); 166 } 167 } 168 } 169 } 170 } 171 }; 172 173 TEST_F(ResizeAreaOpTest, TestAreaRandom141x186) { 174 RunRandomTest(141, 186, 299, 299, 3 /* channels */); 175 } 176 177 TEST_F(ResizeAreaOpTest, TestAreaRandom183x229) { 178 RunRandomTest(183, 229, 299, 299, 3 /* channels */); 179 } 180 181 TEST_F(ResizeAreaOpTest, TestAreaRandom749x603) { 182 RunRandomTest(749, 603, 299, 299, 3 /* channels */); 183 } 184 185 TEST_F(ResizeAreaOpTest, TestAreaRandomDataSeveralInputsSizes1Channel) { 186 RunManyRandomTests(1); 187 } 188 189 TEST_F(ResizeAreaOpTest, TestAreaRandomDataSeveralInputsSizes3Channels) { 190 RunManyRandomTests(3); 191 } 192 193 TEST_F(ResizeAreaOpTest, TestAreaRandomDataSeveralInputsSizes4Channels) { 194 RunManyRandomTests(4); 195 } 196 197 } // namespace tensorflow 198