• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016-2020 Arm Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "arm_compute/runtime/NEON/functions/NEScale.h"
25 
26 #include "arm_compute/core/Coordinates.h"
27 #include "arm_compute/core/Error.h"
28 #include "arm_compute/core/Helpers.h"
29 #include "arm_compute/core/ITensor.h"
30 #include "arm_compute/core/PixelValue.h"
31 #include "arm_compute/core/TensorInfo.h"
32 #include "arm_compute/core/Window.h"
33 #include "arm_compute/runtime/NEON/NEScheduler.h"
34 #include "arm_compute/runtime/TensorAllocator.h"
35 #include "src/core/NEON/kernels/NEScaleKernel.h"
36 
37 #include "src/core/utils/ScaleUtils.h"
38 
39 #include "support/MemorySupport.h"
40 #include "support/Rounding.h"
41 
42 #include <cmath>
43 #include <cstddef>
44 #include <utility>
45 
46 namespace arm_compute
47 {
48 namespace
49 {
precompute_dx_dy_offsets(ITensor * dx,ITensor * dy,ITensor * offsets,float wr,float hr,SamplingPolicy sampling_policy,bool align_corners)50 void precompute_dx_dy_offsets(ITensor *dx, ITensor *dy, ITensor *offsets, float wr, float hr, SamplingPolicy sampling_policy, bool align_corners)
51 {
52     ARM_COMPUTE_ERROR_ON(nullptr == offsets);
53     ARM_COMPUTE_UNUSED(sampling_policy);
54     float sampling_offset = 0.0f;
55     if(sampling_policy == SamplingPolicy::CENTER)
56     {
57         sampling_offset = 0.5f;
58     }
59 
60     Window win;
61     win.set(Window::DimX, Window::Dimension(0, offsets->info()->dimension(0), 1));
62     win.set(Window::DimY, Window::Dimension(0, offsets->info()->dimension(1), 1));
63 
64     if(dx != nullptr && dy != nullptr)
65     {
66         // Pre-compute the offset and pixel's distance for BILINEAR interpolation
67         Iterator offsets_it(offsets, win);
68         Iterator dx_it(dx, win);
69         Iterator dy_it(dy, win);
70 
71         execute_window_loop(win, [&](const Coordinates & id)
72         {
73             const float in_x  = (id.x() + sampling_offset) * wr - sampling_offset;
74             const float in_y  = (id.y() + sampling_offset) * hr - sampling_offset;
75             const int   in_xi = std::floor(in_x);
76             const int   in_yi = std::floor(in_y);
77 
78             *reinterpret_cast<int32_t *>(offsets_it.ptr()) = in_xi;
79             *reinterpret_cast<float *>(dx_it.ptr())        = in_x - in_xi;
80             *reinterpret_cast<float *>(dy_it.ptr())        = in_y - in_yi;
81         },
82         offsets_it, dx_it, dy_it);
83     }
84     else
85     {
86         // Pre-compute the offset for NEAREST interpolation
87         Iterator offsets_it(offsets, win);
88 
89         execute_window_loop(win, [&](const Coordinates & id)
90         {
91             const float float_in_xi                        = (id.x() + sampling_offset) * wr;
92             const auto  in_xi                              = static_cast<size_t>(align_corners ? arm_compute::utils::rounding::round_half_away_from_zero(float_in_xi) : std::floor(float_in_xi));
93             *reinterpret_cast<int32_t *>(offsets_it.ptr()) = in_xi;
94         },
95         offsets_it);
96     }
97 }
98 } // namespace
99 
NEScale()100 NEScale::NEScale()
101     : _offsets(), _dx(), _dy()
102 {
103 }
104 
configure(ITensor * input,ITensor * output,const ScaleKernelInfo & info)105 void NEScale::configure(ITensor *input, ITensor *output, const ScaleKernelInfo &info)
106 {
107     ARM_COMPUTE_ERROR_ON_NULLPTR(input, output);
108     ARM_COMPUTE_ERROR_THROW_ON(NEScale::validate(input->info(), output->info(), info));
109 
110     const bool is_align_corners_used = info.align_corners && arm_compute::scale_utils::is_align_corners_allowed_sampling_policy(info.sampling_policy);
111 
112     // Get data layout and width/height indices
113     const DataLayout data_layout = input->info()->data_layout();
114     const int        idx_width   = get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH);
115     const int        idx_height  = get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT);
116 
117     // Get the tensor shape
118     TensorShape shape(output->info()->dimension(idx_width));
119     shape.set(1, output->info()->dimension(idx_height), false);
120 
121     // Compute the ratio between source width/height and destination width/height
122     const auto wr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_width), output->info()->dimension(idx_width), is_align_corners_used);
123     const auto hr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_height), output->info()->dimension(idx_height), is_align_corners_used);
124 
125     // Area interpolation behaves as Nearest Neighbour in case of up-sampling
126     const auto policy_to_use = (info.interpolation_policy == InterpolationPolicy::AREA && wr <= 1.f && hr <= 1.f) ? InterpolationPolicy::NEAREST_NEIGHBOR : info.interpolation_policy;
127 
128     auto scale_kernel = arm_compute::support::cpp14::make_unique<NEScaleKernel>();
129     switch(policy_to_use)
130     {
131         case InterpolationPolicy::NEAREST_NEIGHBOR:
132         {
133             TensorInfo tensor_info_offsets(shape, Format::S32);
134             _offsets.allocator()->init(tensor_info_offsets);
135 
136             scale_kernel->configure(input, nullptr, nullptr, &_offsets, output, info);
137 
138             // Allocate once the configure methods have been called
139             _offsets.allocator()->allocate();
140 
141             // Pre-compute offsets for nearest interpolation
142             precompute_dx_dy_offsets(nullptr, nullptr, &_offsets, wr, hr, info.sampling_policy, is_align_corners_used);
143             break;
144         }
145         case InterpolationPolicy::BILINEAR:
146         {
147             TensorInfo tensor_info_offsets(shape, Format::S32);
148             TensorInfo tensor_info_dxdy(shape, Format::F32);
149 
150             _offsets.allocator()->init(tensor_info_offsets);
151             _dx.allocator()->init(tensor_info_dxdy);
152             _dy.allocator()->init(tensor_info_dxdy);
153 
154             scale_kernel->configure(input, &_dx, &_dy, &_offsets, output, info);
155 
156             // Allocate once the configure methods have been called
157             _offsets.allocator()->allocate();
158             _dx.allocator()->allocate();
159             _dy.allocator()->allocate();
160 
161             // Pre-compute dx, dy and offsets for bilinear interpolation
162             precompute_dx_dy_offsets(&_dx, &_dy, &_offsets, wr, hr, info.sampling_policy, is_align_corners_used);
163             break;
164         }
165         case InterpolationPolicy::AREA:
166         {
167             scale_kernel->configure(input, nullptr, nullptr, nullptr, output, info);
168             break;
169         }
170         default:
171             ARM_COMPUTE_ERROR("Unsupported interpolation mode");
172     }
173     _kernel = std::move(scale_kernel);
174 }
175 
validate(const ITensorInfo * input,const ITensorInfo * output,const ScaleKernelInfo & info)176 Status NEScale::validate(const ITensorInfo *input, const ITensorInfo *output, const ScaleKernelInfo &info)
177 {
178     ARM_COMPUTE_RETURN_ERROR_ON_NULLPTR(input, output);
179     ARM_COMPUTE_RETURN_ERROR_ON(info.sampling_policy != SamplingPolicy::CENTER && info.sampling_policy != SamplingPolicy::TOP_LEFT);
180 
181     ITensorInfo *offsets = nullptr;
182     ITensorInfo *dx      = nullptr;
183     ITensorInfo *dy      = nullptr;
184 
185     // Get data layout and width/height indices
186     const DataLayout data_layout = input->data_layout();
187     const int        idx_width   = get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH);
188     const int        idx_height  = get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT);
189 
190     // Get the tensor shape of auxilary buffers
191     const TensorShape shape(output->dimension(idx_width), output->dimension(idx_height));
192 
193     TensorInfo tensor_info_offsets(shape, Format::S32);
194     TensorInfo tensor_info_dx(shape, Format::F32);
195     TensorInfo tensor_info_dy(shape, Format::F32);
196 
197     switch(info.interpolation_policy)
198     {
199         case InterpolationPolicy::NEAREST_NEIGHBOR:
200             offsets = &tensor_info_offsets;
201             break;
202         case InterpolationPolicy::BILINEAR:
203             offsets = &tensor_info_offsets;
204             dx      = &tensor_info_dx;
205             dy      = &tensor_info_dy;
206             break;
207         default:
208             break;
209     }
210 
211     ARM_COMPUTE_RETURN_ON_ERROR(NEScaleKernel::validate(input->clone().get(), dx, dy, offsets, output->clone().get(), info));
212     return Status{};
213 }
214 } // namespace arm_compute
215