blob: 8c098984031957db505492aa5805b164c76c4998 [file] [log] [blame]
Anthony Barbier6ff3b192017-09-04 18:44:23 +01001/*
Michalis Spyroua4f378d2019-04-26 14:54:54 +01002 * Copyright (c) 2016-2019 ARM Limited.
Anthony Barbier6ff3b192017-09-04 18:44:23 +01003 *
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/core/NEON/kernels/NEMagnitudePhaseKernel.h"
25
26#include "arm_compute/core/Error.h"
27#include "arm_compute/core/Helpers.h"
28#include "arm_compute/core/IAccessWindow.h"
29#include "arm_compute/core/ITensor.h"
30#include "arm_compute/core/Validate.h"
31
32#include <arm_neon.h>
33#include <cstdint>
34
35using namespace arm_compute;
36
37namespace arm_compute
38{
39class Coordinates;
40} // namespace arm_compute
41
42namespace
43{
44// Defines for computing atan2
45constexpr float SCALE_FACTOR = 0.7111111111111111f;
46constexpr float PI = 3.141592653589793f;
47constexpr float SCALE_180 = 180.0f / PI;
48constexpr float SCALE_360 = SCALE_180 * SCALE_FACTOR;
49constexpr float PI_4 = 0.7853981633974483f;
50constexpr float COEFF1 = 0.0663f;
51constexpr float COEFF2 = 0.2447f;
52} // namespace
53
Anthony Barbier6ff3b192017-09-04 18:44:23 +010054namespace
55{
56inline float32x4_t inv(float32x4_t x)
57{
58 float32x4_t result = vrecpeq_f32(x);
59 result = vmulq_f32(vrecpsq_f32(x, result), result);
60 return result;
61}
62
63inline float32x4_t atan2_0_360(float32x4_t gx, float32x4_t gy)
64{
65 const float32x4_t zero = vdupq_n_f32(0.0f);
66 const float32x4_t epsilon = vdupq_n_f32(1e-9f);
67 const float32x4_t piover4 = vdupq_n_f32(PI_4);
68 const float32x4_t coeff1 = vdupq_n_f32(COEFF1);
69 const float32x4_t coeff2 = vdupq_n_f32(COEFF2);
70 const float32x4_t ninety = vdupq_n_f32(90.0f * SCALE_FACTOR);
71 const float32x4_t oneeighty = vdupq_n_f32(180.0f * SCALE_FACTOR);
72 const float32x4_t threesixty = vdupq_n_f32(360.0f * SCALE_FACTOR);
73 const float32x4_t scale = vdupq_n_f32(SCALE_360);
74
75 float32x4_t abs_gx = vabsq_f32(gx);
76 float32x4_t abs_gy = vabsq_f32(gy);
77 float32x4_t tmin = vminq_f32(abs_gx, abs_gy);
78 float32x4_t tmax = vmaxq_f32(abs_gx, abs_gy);
79 float32x4_t z = vmulq_f32(tmin, inv(vaddq_f32(tmax, epsilon)));
80 float32x4_t absz = vabsq_f32(z);
81 float32x4_t term = vmulq_f32(z, vsubq_f32(vdupq_n_f32(1.0f), absz));
82
83 /* Compute y = pi/4 * x - x*(abs(x)-1)*(0.2447+0.0663 * abs(x) */
84 float32x4_t result = vaddq_f32(coeff2, vmulq_f32(absz, coeff1));
85 result = vmulq_f32(result, term);
86 result = vmlaq_f32(result, piover4, z);
87
88 /* Radians to degrees conversion with applied a scale factor in order to have the result [0, 255] */
89 result = vmulq_f32(result, scale);
90
91 /* If z > 1, result = 90 - result */
92 result = vbslq_f32(vcgeq_f32(abs_gx, abs_gy), result, vsubq_f32(ninety, result));
93
94 /* Choose correct quadrant */
95 result = vbslq_f32(vcltq_f32(gx, zero), vsubq_f32(oneeighty, result), result);
96 result = vbslq_f32(vcltq_f32(gy, zero), vsubq_f32(threesixty, result), result);
97
98 return result;
99}
100
101inline float32x4_t atan2_0_180(float32x4_t gx, float32x4_t gy)
102{
103 const float32x4_t zero = vdupq_n_f32(0.0f);
104 const float32x4_t epsilon = vdupq_n_f32(1e-9f); // epsilon used to avoiding division by 0
105 const float32x4_t piover4 = vdupq_n_f32(PI_4);
106 const float32x4_t coeff1 = vdupq_n_f32(COEFF1);
107 const float32x4_t coeff2 = vdupq_n_f32(COEFF2);
108 const float32x4_t ninety = vdupq_n_f32(90.0f);
109 const float32x4_t oneeighty = vdupq_n_f32(180.0f);
110 const float32x4_t threesixty = vdupq_n_f32(360.0f);
111 const float32x4_t scale = vdupq_n_f32(SCALE_180);
112
113 float32x4_t abs_gx = vabsq_f32(gx);
114 float32x4_t abs_gy = vabsq_f32(gy);
115 float32x4_t tmin = vminq_f32(abs_gx, abs_gy);
116 float32x4_t tmax = vmaxq_f32(abs_gx, abs_gy);
117 float32x4_t z = vmulq_f32(tmin, inv(vaddq_f32(tmax, epsilon)));
118 float32x4_t absz = vabsq_f32(z);
119
120 /* Compute y = pi/4 * z - z*(abs(z)-1)*(0.2447+0.0663 * abs(z) */
121 float32x4_t term = vmulq_f32(z, vsubq_f32(vdupq_n_f32(1.0f), absz));
122 float32x4_t result = vaddq_f32(coeff2, vmulq_f32(absz, coeff1));
123 result = vmulq_f32(result, term);
124 result = vmlaq_f32(result, piover4, z);
125
126 /* Radians to degrees conversion */
127 result = vmulq_f32(result, scale);
128
129 /* If z > 1, result = 90 - result */
130 result = vbslq_f32(vcgeq_f32(abs_gx, abs_gy), result, vsubq_f32(ninety, result));
131
132 /* Choose correct quadrant */
133 result = vbslq_f32(vcltq_f32(gx, zero), vsubq_f32(oneeighty, result), result);
134 result = vbslq_f32(vcltq_f32(gy, zero), vsubq_f32(threesixty, result), result);
135 result = vbslq_f32(vcgtq_f32(result, oneeighty), vsubq_f32(result, oneeighty), result);
136
137 return result;
138}
139
140inline float32x4_t invsqrtv(float32x4_t x)
141{
142 float32x4_t sqrt_reciprocal = vrsqrteq_f32(x);
143
144 sqrt_reciprocal = vmulq_f32(vrsqrtsq_f32(vmulq_f32(x, sqrt_reciprocal), sqrt_reciprocal),
145 sqrt_reciprocal);
146 sqrt_reciprocal = vmulq_f32(vrsqrtsq_f32(vmulq_f32(x, sqrt_reciprocal), sqrt_reciprocal),
147 sqrt_reciprocal);
148
149 return sqrt_reciprocal;
150}
151
152inline float32x4_t sqrtv(float32x4_t x)
153{
154 float32x4_t res = vdupq_n_f32(0.5f);
155 return vmlaq_f32(res, x, invsqrtv(x));
156}
157
158inline int16x8_t magnitude_l2(int16x8_t input1, int16x8_t input2)
159{
160 const int32x4x2_t square_x =
161 {
162 {
163 vmull_s16(vget_low_s16(input1), vget_low_s16(input1)),
164 vmull_s16(vget_high_s16(input1), vget_high_s16(input1))
165 }
166 };
167
168 const int32x4x2_t square_y =
169 {
170 {
171 vmull_s16(vget_low_s16(input2), vget_low_s16(input2)),
172 vmull_s16(vget_high_s16(input2), vget_high_s16(input2))
173 }
174 };
175
176 const uint32x4x2_t sum =
177 {
178 {
179 vaddq_u32(vreinterpretq_u32_s32(square_x.val[0]), vreinterpretq_u32_s32(square_y.val[0])),
180 vaddq_u32(vreinterpretq_u32_s32(square_x.val[1]), vreinterpretq_u32_s32(square_y.val[1]))
181 }
182 };
183
184 const float32x4x2_t res =
185 {
186 {
187 sqrtv(vcvtq_f32_u32(sum.val[0])),
188 sqrtv(vcvtq_f32_u32(sum.val[1]))
189 }
190 };
191
192 return vcombine_s16(vqmovn_s32(vcvtq_s32_f32(res.val[0])),
193 vqmovn_s32(vcvtq_s32_f32(res.val[1])));
194}
195
196inline int16x8_t magnitude_l1(int16x8_t input1, int16x8_t input2)
197{
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100198 /* Saturating add */
John Richardson3c5f9492017-10-04 15:27:37 +0100199 return vqaddq_s16(vqabsq_s16(input1), vqabsq_s16(input2));
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100200}
201
202inline uint8x8_t phase_signed(int16x8_t input1, int16x8_t input2)
203{
204 const float32x4_t zeropointfive = vdupq_n_f32(0.5f);
205
206 float32x4_t inputx_f32_high = vcvtq_f32_s32(vmovl_s16(vget_high_s16(input1)));
207 float32x4_t inputx_f32_low = vcvtq_f32_s32(vmovl_s16(vget_low_s16(input1)));
208 float32x4_t inputy_f32_high = vcvtq_f32_s32(vmovl_s16(vget_high_s16(input2)));
209 float32x4_t inputy_f32_low = vcvtq_f32_s32(vmovl_s16(vget_low_s16(input2)));
210
211 /* Compute fast atan2 */
212 float32x4_t angle_high = atan2_0_360(inputx_f32_high, inputy_f32_high);
213 float32x4_t angle_low = atan2_0_360(inputx_f32_low, inputy_f32_low);
214
215 angle_high = vaddq_f32(angle_high, zeropointfive);
216 angle_low = vaddq_f32(angle_low, zeropointfive);
217
218 return vmovn_u16(vcombine_u16(vqmovun_s32(vcvtq_s32_f32(angle_low)),
219 vqmovun_s32(vcvtq_s32_f32(angle_high))));
220}
221
222inline uint8x8_t phase_unsigned(int16x8_t input1, int16x8_t input2)
223{
224 const float32x4_t zeropointfive = vdupq_n_f32(0.5f);
225
226 float32x4_t inputx_f32_high = vcvtq_f32_s32(vmovl_s16(vget_high_s16(input1)));
227 float32x4_t inputx_f32_low = vcvtq_f32_s32(vmovl_s16(vget_low_s16(input1)));
228 float32x4_t inputy_f32_high = vcvtq_f32_s32(vmovl_s16(vget_high_s16(input2)));
229 float32x4_t inputy_f32_low = vcvtq_f32_s32(vmovl_s16(vget_low_s16(input2)));
230
231 /* Compute fast atan2 */
232 float32x4_t angle_high = atan2_0_180(inputx_f32_high, inputy_f32_high);
233 float32x4_t angle_low = atan2_0_180(inputx_f32_low, inputy_f32_low);
234
235 angle_high = vaddq_f32(angle_high, zeropointfive);
236 angle_low = vaddq_f32(angle_low, zeropointfive);
237
238 return vmovn_u16(vcombine_u16(vqmovun_s32(vcvtq_s32_f32(angle_low)),
239 vqmovun_s32(vcvtq_s32_f32(angle_high))));
240}
241} // namespace
242
243template <MagnitudeType mag_type, PhaseType phase_type>
244NEMagnitudePhaseKernel<mag_type, phase_type>::NEMagnitudePhaseKernel()
245 : _func(nullptr), _gx(nullptr), _gy(nullptr), _magnitude(nullptr), _phase(nullptr)
246{
247}
248
249template <MagnitudeType mag_type, PhaseType phase_type>
250void NEMagnitudePhaseKernel<mag_type, phase_type>::configure(const ITensor *gx, const ITensor *gy, ITensor *magnitude, ITensor *phase)
251{
252 ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(gx, 1, DataType::S16);
253 ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(gy, 1, DataType::S16);
254 ARM_COMPUTE_ERROR_ON((nullptr == magnitude) && (nullptr == phase));
255
256 const bool run_mag = magnitude != nullptr;
257 const bool run_phase = phase != nullptr;
258
259 if(run_mag)
260 {
261 ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(magnitude, 1, DataType::S16);
262 }
263
264 if(run_phase)
265 {
266 ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(phase, 1, DataType::U8);
267 }
268
269 _gx = gx;
270 _gy = gy;
271 _magnitude = magnitude;
272 _phase = phase;
273
274 if(run_mag && run_phase)
275 {
276 /* Run magnitude and phase */
277 _func = &NEMagnitudePhaseKernel<mag_type, phase_type>::magnitude_phase;
278 }
279 else
280 {
281 if(run_mag)
282 {
283 /* Run magnitude */
284 _func = &NEMagnitudePhaseKernel<mag_type, phase_type>::magnitude;
285 }
286 else if(run_phase)
287 {
288 /* Run phase */
289 _func = &NEMagnitudePhaseKernel<mag_type, phase_type>::phase;
290 }
291 else
292 {
293 ARM_COMPUTE_ERROR("At least one output must be NOT NULL");
294 }
295 }
296
297 constexpr unsigned int num_elems_processed_per_iteration = 16;
298
299 // Configure kernel window
300 Window win = calculate_max_window(*gx->info(), Steps(num_elems_processed_per_iteration));
301 AccessWindowHorizontal magnitude_access(magnitude == nullptr ? nullptr : magnitude->info(), 0, num_elems_processed_per_iteration);
302 AccessWindowHorizontal phase_access(phase == nullptr ? nullptr : phase->info(), 0, num_elems_processed_per_iteration);
303
304 update_window_and_padding(win,
305 AccessWindowHorizontal(gx->info(), 0, num_elems_processed_per_iteration),
306 AccessWindowHorizontal(gy->info(), 0, num_elems_processed_per_iteration),
307 magnitude_access,
308 phase_access);
309
310 ValidRegion valid_region = intersect_valid_regions(gx->info()->valid_region(),
311 gy->info()->valid_region());
312
313 magnitude_access.set_valid_region(win, valid_region);
314 phase_access.set_valid_region(win, valid_region);
315
316 INEKernel::configure(win);
317}
318
319template <MagnitudeType mag_type, PhaseType phase_type>
320void NEMagnitudePhaseKernel<mag_type, phase_type>::magnitude(const Window &window)
321{
322 Iterator gx(_gx, window);
323 Iterator gy(_gy, window);
324 Iterator magnitude(_magnitude, window);
325
Michalis Spyroua4f378d2019-04-26 14:54:54 +0100326 execute_window_loop(window, [&](const Coordinates &)
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100327 {
328 const int16x8x2_t input1 =
329 {
330 {
331 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr())),
332 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr()) + 8)
333 }
334 };
335
336 const int16x8x2_t input2 =
337 {
338 {
339 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr())),
340 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr()) + 8)
341 }
342 };
343
344 /* Compute magnitude */
345 int16x8x2_t mag{ {} };
346
347 if(MagnitudeType::L2NORM == mag_type)
348 {
349 mag.val[0] = magnitude_l2(input1.val[0], input2.val[0]);
350 mag.val[1] = magnitude_l2(input1.val[1], input2.val[1]);
351 }
352 else
353 {
354 mag.val[0] = magnitude_l1(input1.val[0], input2.val[0]);
355 mag.val[1] = magnitude_l1(input1.val[1], input2.val[1]);
356 }
357
358 /* Store magnitude */
359 vst1q_s16(reinterpret_cast<int16_t *>(magnitude.ptr()), mag.val[0]);
360 vst1q_s16(reinterpret_cast<int16_t *>(magnitude.ptr()) + 8, mag.val[1]);
361 },
362 gx, gy, magnitude);
363}
364
365template <MagnitudeType mag_type, PhaseType phase_type>
366void NEMagnitudePhaseKernel<mag_type, phase_type>::phase(const Window &window)
367{
368 Iterator gx(_gx, window);
369 Iterator gy(_gy, window);
370 Iterator phase(_phase, window);
371
Michalis Spyroua4f378d2019-04-26 14:54:54 +0100372 execute_window_loop(window, [&](const Coordinates &)
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100373 {
374 const int16x8x2_t input1 =
375 {
376 {
377 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr())),
378 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr()) + 8)
379 }
380 };
381
382 const int16x8x2_t input2 =
383 {
384 {
385 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr())),
386 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr()) + 8)
387 }
388 };
389
390 /* Compute phase */
391 uint8x8x2_t vphase{ {} };
392
393 if(PhaseType::SIGNED == phase_type)
394 {
395 vphase.val[0] = phase_signed(input1.val[0], input2.val[0]);
396 vphase.val[1] = phase_signed(input1.val[1], input2.val[1]);
397 }
398 else
399 {
400 vphase.val[0] = phase_unsigned(input1.val[0], input2.val[0]);
401 vphase.val[1] = phase_unsigned(input1.val[1], input2.val[1]);
402 }
403
404 /* Store phase */
405 vst1q_u8(phase.ptr(), vcombine_u8(vphase.val[0], vphase.val[1]));
406 },
407 gx, gy, phase);
408}
409
410template <MagnitudeType mag_type, PhaseType phase_type>
411void NEMagnitudePhaseKernel<mag_type, phase_type>::magnitude_phase(const Window &window)
412{
413 Iterator gx(_gx, window);
414 Iterator gy(_gy, window);
415 Iterator magnitude(_magnitude, window);
416 Iterator phase(_phase, window);
417
Michalis Spyroua4f378d2019-04-26 14:54:54 +0100418 execute_window_loop(window, [&](const Coordinates &)
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100419 {
420 const int16x8x2_t input1 =
421 {
422 {
423 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr())),
424 vld1q_s16(reinterpret_cast<int16_t *>(gx.ptr()) + 8)
425 }
426 };
427
428 const int16x8x2_t input2 =
429 {
430 {
431 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr())),
432 vld1q_s16(reinterpret_cast<int16_t *>(gy.ptr()) + 8)
433 }
434 };
435
436 /* Compute magnitude */
437 int16x8x2_t mag{ {} };
438
439 if(MagnitudeType::L2NORM == mag_type)
440 {
441 mag.val[0] = magnitude_l2(input1.val[0], input2.val[0]);
442 mag.val[1] = magnitude_l2(input1.val[1], input2.val[1]);
443 }
444 else
445 {
446 mag.val[0] = magnitude_l1(input1.val[0], input2.val[0]);
447 mag.val[1] = magnitude_l1(input1.val[1], input2.val[1]);
448 }
449
450 /* Store magnitude */
451 vst1q_s16(reinterpret_cast<int16_t *>(magnitude.ptr()), mag.val[0]);
452 vst1q_s16(reinterpret_cast<int16_t *>(magnitude.ptr()) + 8, mag.val[1]);
453
454 /* Compute phase */
455 uint8x8x2_t vphase{ {} };
456
457 if(PhaseType::SIGNED == phase_type)
458 {
459 vphase.val[0] = phase_signed(input1.val[0], input2.val[0]);
460 vphase.val[1] = phase_signed(input1.val[1], input2.val[1]);
461 }
462 else
463 {
464 vphase.val[0] = phase_unsigned(input1.val[0], input2.val[0]);
465 vphase.val[1] = phase_unsigned(input1.val[1], input2.val[1]);
466 }
467
468 /* Store phase */
469 vst1q_u8(phase.ptr(), vcombine_u8(vphase.val[0], vphase.val[1]));
470 },
471 gx, gy, magnitude, phase);
472}
473
474template <MagnitudeType mag_type, PhaseType phase_type>
Moritz Pflanzerc186b572017-09-07 09:48:04 +0100475void NEMagnitudePhaseKernel<mag_type, phase_type>::run(const Window &window, const ThreadInfo &info)
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100476{
Moritz Pflanzerc186b572017-09-07 09:48:04 +0100477 ARM_COMPUTE_UNUSED(info);
Anthony Barbier6ff3b192017-09-04 18:44:23 +0100478 ARM_COMPUTE_ERROR_ON_UNCONFIGURED_KERNEL(this);
479 ARM_COMPUTE_ERROR_ON_INVALID_SUBWINDOW(INEKernel::window(), window);
480 ARM_COMPUTE_ERROR_ON(_func == nullptr);
481
482 (this->*_func)(window);
483}
484
485template class arm_compute::NEMagnitudePhaseKernel<MagnitudeType::L1NORM, PhaseType::SIGNED>;
486template class arm_compute::NEMagnitudePhaseKernel<MagnitudeType::L2NORM, PhaseType::SIGNED>;
487template class arm_compute::NEMagnitudePhaseKernel<MagnitudeType::L1NORM, PhaseType::UNSIGNED>;
488template class arm_compute::NEMagnitudePhaseKernel<MagnitudeType::L2NORM, PhaseType::UNSIGNED>;