xref: /aosp_15_r20/external/XNNPACK/test/resize-bilinear-operator-tester.h (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
1 // Copyright 2019 Google LLC
2 //
3 // This source code is licensed under the BSD-style license found in the
4 // LICENSE file in the root directory of this source tree.
5 
6 #pragma once
7 
8 #include <gtest/gtest.h>
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cassert>
13 #include <cstddef>
14 #include <cstdlib>
15 #include <functional>
16 #include <random>
17 #include <vector>
18 
19 #include <fp16.h>
20 
21 #include <xnnpack.h>
22 
23 
24 class ResizeBilinearOperatorTester {
25  public:
input_size(size_t input_height,size_t input_width)26   inline ResizeBilinearOperatorTester& input_size(size_t input_height, size_t input_width) {
27     assert(input_height >= 1);
28     assert(input_width >= 1);
29     this->input_height_ = input_height;
30     this->input_width_ = input_width;
31     return *this;
32   }
33 
input_height(size_t input_height)34   inline ResizeBilinearOperatorTester& input_height(size_t input_height) {
35     assert(input_height >= 1);
36     this->input_height_ = input_height;
37     return *this;
38   }
39 
input_height()40   inline size_t input_height() const {
41     return this->input_height_;
42   }
43 
input_width(size_t input_width)44   inline ResizeBilinearOperatorTester& input_width(size_t input_width) {
45     assert(input_width >= 1);
46     this->input_width_ = input_width;
47     return *this;
48   }
49 
input_width()50   inline size_t input_width() const {
51     return this->input_width_;
52   }
53 
output_size(size_t output_height,size_t output_width)54   inline ResizeBilinearOperatorTester& output_size(size_t output_height, size_t output_width) {
55     assert(output_height >= 1);
56     assert(output_width >= 1);
57     this->output_height_ = output_height;
58     this->output_width_ = output_width;
59     return *this;
60   }
61 
output_height(size_t output_height)62   inline ResizeBilinearOperatorTester& output_height(size_t output_height) {
63     assert(output_height >= 1);
64     this->output_height_ = output_height;
65     return *this;
66   }
67 
output_height()68   inline size_t output_height() const {
69     return this->output_height_;
70   }
71 
output_width(size_t output_width)72   inline ResizeBilinearOperatorTester& output_width(size_t output_width) {
73     assert(output_width >= 1);
74     this->output_width_ = output_width;
75     return *this;
76   }
77 
output_width()78   inline size_t output_width() const {
79     return this->output_width_;
80   }
81 
height_scale()82   inline float height_scale() const {
83     if (align_corners() && output_height() > 1) {
84       return float(input_height() - 1) / float(output_height() - 1);
85     } else {
86       return float(input_height()) / float(output_height());
87     }
88   }
89 
width_scale()90   inline float width_scale() const {
91     if (align_corners() && output_width() > 1) {
92       return float(input_width() - 1) / float(output_width() - 1);
93     } else {
94       return float(input_width()) / float(output_width());
95     }
96   }
97 
channels(size_t channels)98   inline ResizeBilinearOperatorTester& channels(size_t channels) {
99     assert(channels != 0);
100     this->channels_ = channels;
101     return *this;
102   }
103 
channels()104   inline size_t channels() const {
105     return this->channels_;
106   }
107 
batch_size(size_t batch_size)108   inline ResizeBilinearOperatorTester& batch_size(size_t batch_size) {
109     assert(batch_size != 0);
110     this->batch_size_ = batch_size;
111     return *this;
112   }
113 
batch_size()114   inline size_t batch_size() const {
115     return this->batch_size_;
116   }
117 
input_pixel_stride(size_t input_pixel_stride)118   inline ResizeBilinearOperatorTester& input_pixel_stride(size_t input_pixel_stride) {
119     assert(input_pixel_stride != 0);
120     this->input_pixel_stride_ = input_pixel_stride;
121     return *this;
122   }
123 
input_pixel_stride()124   inline size_t input_pixel_stride() const {
125     if (this->input_pixel_stride_ == 0) {
126       return channels();
127     } else {
128       assert(this->input_pixel_stride_ >= channels());
129       return this->input_pixel_stride_;
130     }
131   }
132 
output_pixel_stride(size_t output_pixel_stride)133   inline ResizeBilinearOperatorTester& output_pixel_stride(size_t output_pixel_stride) {
134     assert(output_pixel_stride != 0);
135     this->output_pixel_stride_ = output_pixel_stride;
136     return *this;
137   }
138 
output_pixel_stride()139   inline size_t output_pixel_stride() const {
140     if (this->output_pixel_stride_ == 0) {
141       return channels();
142     } else {
143       assert(this->output_pixel_stride_ >= channels());
144       return this->output_pixel_stride_;
145     }
146   }
147 
next_input_size(uint32_t next_input_height,uint32_t next_input_width)148   inline ResizeBilinearOperatorTester& next_input_size(uint32_t next_input_height, uint32_t next_input_width) {
149     assert(next_input_height >= 1);
150     assert(next_input_width >= 1);
151     this->next_input_height_ = next_input_height;
152     this->next_input_width_ = next_input_width;
153     return *this;
154   }
155 
next_input_height(uint32_t next_input_height)156   inline ResizeBilinearOperatorTester& next_input_height(uint32_t next_input_height) {
157     assert(next_input_height >= 1);
158     this->next_input_height_ = next_input_height;
159     return *this;
160   }
161 
next_input_height()162   inline uint32_t next_input_height() const {
163     if (this->next_input_height_ == 0) {
164       return input_height();
165     } else {
166       return this->next_input_height_;
167     }
168   }
169 
next_input_width(uint32_t next_input_width)170   inline ResizeBilinearOperatorTester& next_input_width(uint32_t next_input_width) {
171     assert(next_input_width >= 1);
172     this->next_input_width_ = next_input_width;
173     return *this;
174   }
175 
next_input_width()176   inline uint32_t next_input_width() const {
177     if (this->next_input_width_ == 0) {
178       return input_width();
179     } else {
180       return this->next_input_width_;
181     }
182   }
183 
next_batch_size(size_t next_batch_size)184   inline ResizeBilinearOperatorTester& next_batch_size(size_t next_batch_size) {
185     assert(next_batch_size >= 1);
186     this->next_batch_size_ = next_batch_size;
187     return *this;
188   }
189 
next_batch_size()190   inline size_t next_batch_size() const {
191     if (this->next_batch_size_ == 0) {
192       return batch_size();
193     } else {
194       return this->next_batch_size_;
195     }
196   }
197 
align_corners(bool align_corners)198   inline ResizeBilinearOperatorTester& align_corners(bool align_corners) {
199     this->align_corners_ = align_corners;
200     return *this;
201   }
202 
align_corners()203   inline bool align_corners() const {
204     return this->align_corners_;
205   }
206 
tf_legacy_mode(bool tf_legacy_mode)207   inline ResizeBilinearOperatorTester& tf_legacy_mode(bool tf_legacy_mode) {
208     this->tf_legacy_mode_ = tf_legacy_mode;
209     return *this;
210   }
211 
tf_legacy_mode()212   inline bool tf_legacy_mode() const {
213     return this->tf_legacy_mode_;
214   }
215 
iterations(size_t iterations)216   inline ResizeBilinearOperatorTester& iterations(size_t iterations) {
217     this->iterations_ = iterations;
218     return *this;
219   }
220 
iterations()221   inline size_t iterations() const {
222     return this->iterations_;
223   }
224 
TestNHWCxF16()225   void TestNHWCxF16() const {
226     if (align_corners()) {
227       ASSERT_FALSE(tf_legacy_mode());
228     }
229 
230     std::random_device random_device;
231     auto rng = std::mt19937(random_device());
232     std::uniform_real_distribution<float> f32dist;
233 
234     std::vector<uint16_t> input(XNN_EXTRA_BYTES / sizeof(uint16_t) +
235       (batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels());
236     std::vector<uint16_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels());
237     std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels());
238     for (size_t iteration = 0; iteration < iterations(); iteration++) {
239       std::generate(input.begin(), input.end(), [&]() { return fp16_ieee_from_fp32_value(f32dist(rng)); });
240       std::fill(output.begin(), output.end(), UINT16_C(0x7E00) /* NaN */);
241 
242       // Compute reference results.
243       const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f;
244       for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) {
245         for (size_t output_y = 0; output_y < output_height(); output_y++) {
246           const float input_y = (float(output_y) + offset) * height_scale() - offset;
247           const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0);
248           const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1);
249           const float y_alpha = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(input_y - std::floor(input_y)));
250           for (size_t output_x = 0; output_x < output_width(); output_x++) {
251             const float input_x = (float(output_x) + offset) * width_scale() - offset;
252             const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0);
253             const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1);
254             const float x_alpha = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(input_x - std::floor(input_x)));
255             for (size_t c = 0; c < channels(); c++) {
256               output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] =
257                 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c]) * (1.0f - y_alpha) * (1.0f - x_alpha) +
258                 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c]) * (1.0f - y_alpha) * x_alpha +
259                 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c]) * y_alpha * (1.0f - x_alpha) +
260                 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c]) * y_alpha * x_alpha;
261             }
262           }
263         }
264       }
265 
266       // Create, setup, run, and destroy Resize Bilinear operator.
267       ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */));
268       xnn_operator_t resize_bilinear_op = nullptr;
269 
270       const xnn_status status = xnn_create_resize_bilinear2d_nhwc_f16(
271           channels(), input_pixel_stride(), output_pixel_stride(),
272           (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0),
273           &resize_bilinear_op);
274       if (status == xnn_status_unsupported_hardware) {
275         GTEST_SKIP();
276       }
277       ASSERT_EQ(xnn_status_success, status);
278       ASSERT_NE(nullptr, resize_bilinear_op);
279 
280       // Smart pointer to automatically delete resize_bilinear_op.
281       std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator);
282 
283       ASSERT_EQ(xnn_status_success,
284         xnn_setup_resize_bilinear2d_nhwc_f16(
285           resize_bilinear_op,
286           batch_size(), input_height(), input_width(),
287           output_height(), output_width(),
288           input.data(), output.data(),
289           nullptr /* thread pool */));
290 
291       ASSERT_EQ(xnn_status_success,
292         xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */));
293 
294       // Verify results.
295       for (size_t i = 0; i < batch_size(); i++) {
296         for (size_t y = 0; y < output_height(); y++) {
297           for (size_t x = 0; x < output_width(); x++) {
298             for (size_t c = 0; c < channels(); c++) {
299               ASSERT_NEAR(
300                   fp16_ieee_to_fp32_value(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c]),
301                   output_ref[((i * output_height() + y) * output_width() + x) * channels() + c],
302                   std::max(1.0e-4f, std::abs(output_ref[((i * output_height() + y) * output_width() + x) * channels() + c]) * 1.0e-2f)) <<
303                 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c;
304             }
305           }
306         }
307       }
308     }
309   }
310 
TestNHWCxF32()311   void TestNHWCxF32() const {
312     if (align_corners()) {
313       ASSERT_FALSE(tf_legacy_mode());
314     }
315 
316     std::random_device random_device;
317     auto rng = std::mt19937(random_device());
318     std::uniform_real_distribution<float> f32dist;
319 
320     std::vector<float> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(float));
321     std::vector<float> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels());
322     std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels());
323     for (size_t iteration = 0; iteration < iterations(); iteration++) {
324       std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
325       std::fill(output.begin(), output.end(), std::nanf(""));
326 
327       // Compute reference results.
328       const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f;
329       for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) {
330         for (size_t output_y = 0; output_y < output_height(); output_y++) {
331           const float input_y = (float(output_y) + offset) * height_scale() - offset;
332           const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0);
333           const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1);
334           const float y_alpha = input_y - std::floor(input_y);
335           for (size_t output_x = 0; output_x < output_width(); output_x++) {
336             const float input_x = (float(output_x) + offset) * width_scale() - offset;
337             const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0);
338             const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1);
339             const float x_alpha = input_x - std::floor(input_x);
340             for (size_t c = 0; c < channels(); c++) {
341               output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] =
342                 input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c] * (1.0f - y_alpha) * (1.0f - x_alpha) +
343                 input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c] * (1.0f - y_alpha) * x_alpha +
344                 input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c] * y_alpha * (1.0f - x_alpha) +
345                 input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c] * y_alpha * x_alpha;
346             }
347           }
348         }
349       }
350 
351       // Create, setup, run, and destroy Resize Bilinear operator.
352       ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */));
353       xnn_operator_t resize_bilinear_op = nullptr;
354 
355       ASSERT_EQ(xnn_status_success,
356         xnn_create_resize_bilinear2d_nhwc_f32(
357           channels(), input_pixel_stride(), output_pixel_stride(),
358           (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0),
359           &resize_bilinear_op));
360       ASSERT_NE(nullptr, resize_bilinear_op);
361 
362       // Smart pointer to automatically delete resize_bilinear_op.
363       std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator);
364 
365       ASSERT_EQ(xnn_status_success,
366         xnn_setup_resize_bilinear2d_nhwc_f32(
367           resize_bilinear_op,
368           batch_size(), input_height(), input_width(),
369           output_height(), output_width(),
370           input.data(), output.data(),
371           nullptr /* thread pool */));
372 
373       ASSERT_EQ(xnn_status_success,
374         xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */));
375 
376       // Verify results.
377       for (size_t i = 0; i < batch_size(); i++) {
378         for (size_t y = 0; y < output_height(); y++) {
379           for (size_t x = 0; x < output_width(); x++) {
380             for (size_t c = 0; c < channels(); c++) {
381               ASSERT_NEAR(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c],
382                   output_ref[((i * output_height() + y) * output_width() + x) * channels() + c],
383                   std::abs(output_ref[((i * output_height() + y) * output_width() + x) * channels() + c]) * 1.0e-5f) <<
384                 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c;
385             }
386           }
387         }
388       }
389     }
390   }
391 
TestNHWCxS8()392   void TestNHWCxS8() const {
393     if (align_corners()) {
394       ASSERT_FALSE(tf_legacy_mode());
395     }
396 
397     std::random_device random_device;
398     auto rng = std::mt19937(random_device());
399     std::uniform_int_distribution<int32_t> i8dist(
400       std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
401 
402     std::vector<int8_t> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(int8_t));
403     std::vector<int8_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels());
404     std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels());
405     for (size_t iteration = 0; iteration < iterations(); iteration++) {
406       std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); });
407       std::fill(output.begin(), output.end(), INT8_C(0xA5));
408 
409       // Compute reference results.
410       const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f;
411       for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) {
412         for (size_t output_y = 0; output_y < output_height(); output_y++) {
413           const float input_y = (float(output_y) + offset) * height_scale() - offset;
414           const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0);
415           const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1);
416           const float y_alpha = input_y - std::floor(input_y);
417           for (size_t output_x = 0; output_x < output_width(); output_x++) {
418             const float input_x = (float(output_x) + offset) * width_scale() - offset;
419             const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0);
420             const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1);
421             const float x_alpha = input_x - std::floor(input_x);
422             for (size_t c = 0; c < channels(); c++) {
423               output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] =
424                 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c])) * (1.0f - y_alpha) * (1.0f - x_alpha) +
425                 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c])) * (1.0f - y_alpha) * x_alpha +
426                 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c])) * y_alpha * (1.0f - x_alpha) +
427                 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c])) * y_alpha * x_alpha;
428             }
429           }
430         }
431       }
432 
433       // Create, setup, run, and destroy Resize Bilinear operator.
434       ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */));
435       xnn_operator_t resize_bilinear_op = nullptr;
436 
437       ASSERT_EQ(xnn_status_success,
438         xnn_create_resize_bilinear2d_nhwc_s8(
439           channels(), input_pixel_stride(), output_pixel_stride(),
440           (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0),
441           &resize_bilinear_op));
442       ASSERT_NE(nullptr, resize_bilinear_op);
443 
444       // Smart pointer to automatically delete resize_bilinear_op.
445       std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator);
446 
447       ASSERT_EQ(xnn_status_success,
448         xnn_setup_resize_bilinear2d_nhwc_s8(
449           resize_bilinear_op,
450           batch_size(), input_height(), input_width(),
451           output_height(), output_width(),
452           input.data(), output.data(),
453           nullptr /* thread pool */));
454 
455       ASSERT_EQ(xnn_status_success,
456         xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */));
457 
458       // Verify results.
459       for (size_t i = 0; i < batch_size(); i++) {
460         for (size_t y = 0; y < output_height(); y++) {
461           for (size_t x = 0; x < output_width(); x++) {
462             for (size_t c = 0; c < channels(); c++) {
463               ASSERT_NEAR(
464                   float(int32_t(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c])),
465                   output_ref[((i * output_height() + y) * output_width() + x) * channels() + c],
466                   0.6f) <<
467                 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c;
468             }
469           }
470         }
471       }
472     }
473   }
474 
TestNHWCxU8()475   void TestNHWCxU8() const {
476     if (align_corners()) {
477       ASSERT_FALSE(tf_legacy_mode());
478     }
479 
480     std::random_device random_device;
481     auto rng = std::mt19937(random_device());
482     std::uniform_int_distribution<int32_t> u8dist(
483       std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
484 
485     std::vector<uint8_t> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(uint8_t));
486     std::vector<uint8_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels());
487     std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels());
488     for (size_t iteration = 0; iteration < iterations(); iteration++) {
489       std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); });
490       std::fill(output.begin(), output.end(), UINT8_C(0xA5));
491 
492       // Compute reference results.
493       const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f;
494       for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) {
495         for (size_t output_y = 0; output_y < output_height(); output_y++) {
496           const float input_y = (float(output_y) + offset) * height_scale() - offset;
497           const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0);
498           const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1);
499           const float y_alpha = input_y - std::floor(input_y);
500           for (size_t output_x = 0; output_x < output_width(); output_x++) {
501             const float input_x = (float(output_x) + offset) * width_scale() - offset;
502             const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0);
503             const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1);
504             const float x_alpha = input_x - std::floor(input_x);
505             for (size_t c = 0; c < channels(); c++) {
506               output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] =
507                 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c])) * (1.0f - y_alpha) * (1.0f - x_alpha) +
508                 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c])) * (1.0f - y_alpha) * x_alpha +
509                 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c])) * y_alpha * (1.0f - x_alpha) +
510                 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c])) * y_alpha * x_alpha;
511             }
512           }
513         }
514       }
515 
516       // Create, setup, run, and destroy Resize Bilinear operator.
517       ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */));
518       xnn_operator_t resize_bilinear_op = nullptr;
519 
520       ASSERT_EQ(xnn_status_success,
521         xnn_create_resize_bilinear2d_nhwc_u8(
522           channels(), input_pixel_stride(), output_pixel_stride(),
523           (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0),
524           &resize_bilinear_op));
525       ASSERT_NE(nullptr, resize_bilinear_op);
526 
527       // Smart pointer to automatically delete resize_bilinear_op.
528       std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator);
529 
530       ASSERT_EQ(xnn_status_success,
531         xnn_setup_resize_bilinear2d_nhwc_u8(
532           resize_bilinear_op,
533           batch_size(), input_height(), input_width(),
534           output_height(), output_width(),
535           input.data(), output.data(),
536           nullptr /* thread pool */));
537 
538       ASSERT_EQ(xnn_status_success,
539         xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */));
540 
541       // Verify results.
542       for (size_t i = 0; i < batch_size(); i++) {
543         for (size_t y = 0; y < output_height(); y++) {
544           for (size_t x = 0; x < output_width(); x++) {
545             for (size_t c = 0; c < channels(); c++) {
546               ASSERT_NEAR(
547                   float(int32_t(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c])),
548                   output_ref[((i * output_height() + y) * output_width() + x) * channels() + c],
549                   0.6f) <<
550                 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c;
551             }
552           }
553         }
554       }
555     }
556   }
557 
TestNCHWxF32()558   void TestNCHWxF32() const {
559     if (align_corners()) {
560       ASSERT_FALSE(tf_legacy_mode());
561     }
562 
563     std::random_device random_device;
564     auto rng = std::mt19937(random_device());
565     std::uniform_real_distribution<float> f32dist;
566 
567     std::vector<float> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(float));
568     std::vector<float> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels());
569     std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels());
570     for (size_t iteration = 0; iteration < iterations(); iteration++) {
571       std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); });
572       std::fill(output.begin(), output.end(), std::nanf(""));
573 
574       // Compute reference results.
575       const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f;
576       const int64_t input_num_pixels = input_height() * input_width();
577       const int64_t input_num_elements = input_num_pixels * input_pixel_stride();
578       const int64_t output_num_pixels = output_height() * output_width();
579       const int64_t output_num_elements = output_num_pixels * channels();
580       for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) {
581         for (size_t output_y = 0; output_y < output_height(); output_y++) {
582           const float input_y = (float(output_y) + offset) * height_scale() - offset;
583           const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0);
584           const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1);
585           const float y_alpha = input_y - std::floor(input_y);
586           for (size_t output_x = 0; output_x < output_width(); output_x++) {
587             const float input_x = (float(output_x) + offset) * width_scale() - offset;
588             const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0);
589             const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1);
590             const float x_alpha = input_x - std::floor(input_x);
591             for (size_t c = 0; c < channels(); c++) {
592               output_ref[batch_index * output_num_elements + c * output_num_pixels + output_y * output_width() + output_x] =
593                 input[batch_index * input_num_elements + c * input_num_pixels + input_y_top * input_width() + input_x_left] * (1.0f - y_alpha) * (1.0f - x_alpha) +
594                 input[batch_index * input_num_elements + c * input_num_pixels + input_y_top * input_width() + input_x_right] * (1.0f - y_alpha) * x_alpha +
595                 input[batch_index * input_num_elements + c * input_num_pixels + input_y_bottom * input_width() + input_x_left] * y_alpha * (1.0f - x_alpha) +
596                 input[batch_index * input_num_elements + c * input_num_pixels + input_y_bottom * input_width() + input_x_right] * y_alpha * x_alpha;
597             }
598           }
599         }
600       }
601 
602       // Create, setup, run, and destroy Resize Bilinear operator.
603       ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */));
604       xnn_operator_t resize_bilinear_op = nullptr;
605 
606       ASSERT_EQ(xnn_status_success,
607         xnn_create_resize_bilinear2d_nchw_f32(
608           channels(), input_pixel_stride(), output_pixel_stride(),
609           (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0),
610           &resize_bilinear_op));
611       ASSERT_NE(nullptr, resize_bilinear_op);
612 
613       // Smart pointer to automatically delete resize_bilinear_op.
614       std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator);
615 
616       ASSERT_EQ(xnn_status_success,
617         xnn_setup_resize_bilinear2d_nchw_f32(
618           resize_bilinear_op,
619           batch_size(), input_height(), input_width(),
620           output_height(), output_width(),
621           input.data(), output.data(),
622           nullptr /* thread pool */));
623 
624       ASSERT_EQ(xnn_status_success,
625         xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */));
626 
627       // Verify results.
628       for (size_t i = 0; i < batch_size(); i++) {
629         for (size_t y = 0; y < output_height(); y++) {
630           for (size_t x = 0; x < output_width(); x++) {
631             for (size_t c = 0; c < channels(); c++) {
632               ASSERT_NEAR(output[i * output_num_elements +  c * output_num_pixels + y * output_width() + x],
633                   output_ref[i * output_num_elements +  c * output_num_pixels + y * output_width() + x],
634                   1.0e-6f) <<
635                 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c;
636             }
637           }
638         }
639       }
640     }
641   }
642 
643  private:
644   size_t input_height_{1};
645   size_t input_width_{1};
646   size_t output_height_{1};
647   size_t output_width_{1};
648   size_t channels_{1};
649   size_t batch_size_{1};
650   size_t input_pixel_stride_{0};
651   size_t output_pixel_stride_{0};
652   size_t next_input_height_{0};
653   size_t next_input_width_{0};
654   size_t next_batch_size_{0};
655   bool align_corners_{false};
656   bool tf_legacy_mode_{false};
657   size_t iterations_{1};
658 };
659