COMPMID-359: Implement NEON ROIPoolingLayer

Change-Id: Ibffa738d4016d7221968bd43a4e6e1dab85baee8
Reviewed-on: http://mpd-gerrit.cambridge.arm.com/78623
Reviewed-by: Moritz Pflanzer <moritz.pflanzer@arm.com>
Reviewed-by: Gian Marco Iodice <gianmarco.iodice@arm.com>
Tested-by: Kaizen <jeremy.johnson+kaizengerrit@arm.com>
diff --git a/tests/validation/Helpers.cpp b/tests/validation/Helpers.cpp
new file mode 100644
index 0000000..747260e
--- /dev/null
+++ b/tests/validation/Helpers.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "validation/Helpers.h"
+
+using namespace arm_compute::test;
+
+namespace arm_compute
+{
+namespace test
+{
+namespace validation
+{
+std::vector<ROI> generate_random_rois(const TensorShape &shape, const ROIPoolingLayerInfo &pool_info, unsigned int num_rois, std::random_device::result_type seed)
+{
+    ARM_COMPUTE_ERROR_ON((pool_info.pooled_width() < 4) || (pool_info.pooled_height() < 4));
+
+    std::vector<ROI> rois;
+    std::mt19937     gen(seed);
+    const int        pool_width  = pool_info.pooled_width();
+    const int        pool_height = pool_info.pooled_height();
+    const float      roi_scale   = pool_info.spatial_scale();
+
+    // Calculate distribution bounds
+    const auto scaled_width  = static_cast<int>((shape.x() / roi_scale) / pool_width);
+    const auto scaled_height = static_cast<int>((shape.y() / roi_scale) / pool_height);
+    const auto min_width     = static_cast<int>(pool_width / roi_scale);
+    const auto min_height    = static_cast<int>(pool_height / roi_scale);
+
+    // Create distributions
+    std::uniform_int_distribution<int> dist_batch(0, shape[3] - 1);
+    std::uniform_int_distribution<int> dist_x(0, scaled_width);
+    std::uniform_int_distribution<int> dist_y(0, scaled_height);
+    std::uniform_int_distribution<int> dist_w(min_width, std::max(min_width, (pool_width - 2) * scaled_width));
+    std::uniform_int_distribution<int> dist_h(min_height, std::max(min_height, (pool_height - 2) * scaled_height));
+
+    for(unsigned int r = 0; r < num_rois; ++r)
+    {
+        ROI roi{};
+        roi.batch_idx   = dist_batch(gen);
+        roi.rect.x      = dist_x(gen);
+        roi.rect.y      = dist_y(gen);
+        roi.rect.width  = dist_w(gen);
+        roi.rect.height = dist_h(gen);
+        rois.push_back(roi);
+    }
+
+    return rois;
+}
+} // namespace validation
+} // namespace test
+} // namespace arm_compute
diff --git a/tests/validation/Helpers.h b/tests/validation/Helpers.h
index c0c5865..d92699d 100644
--- a/tests/validation/Helpers.h
+++ b/tests/validation/Helpers.h
@@ -27,8 +27,12 @@
 #include "Types.h"
 #include "ValidationUserConfiguration.h"
 
+#include "arm_compute/core/Types.h"
+
+#include <random>
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 namespace arm_compute
 {
@@ -168,6 +172,16 @@
     }
 }
 
+/** Create a vector of random ROIs.
+ *
+ * @param[in] shape     The shape of the input tensor.
+ * @param[in] pool_info The ROI pooling information.
+ * @param[in] num_rois  The number of ROIs to be created.
+ * @param[in] seed      The random seed to be used.
+ *
+ * @return A vector that contains the requested number of random ROIs
+ */
+std::vector<ROI> generate_random_rois(const TensorShape &shape, const ROIPoolingLayerInfo &pool_info, unsigned int num_rois, std::random_device::result_type seed);
 } // namespace validation
 } // namespace test
 } // namespace arm_compute
diff --git a/tests/validation/NEON/ROIPoolingLayer.cpp b/tests/validation/NEON/ROIPoolingLayer.cpp
new file mode 100644
index 0000000..20aaefd
--- /dev/null
+++ b/tests/validation/NEON/ROIPoolingLayer.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "NEON/Helper.h"
+#include "NEON/NEAccessor.h"
+#include "TypePrinter.h"
+#include "arm_compute/runtime/NEON/functions/NEROIPoolingLayer.h"
+#include "validation/Datasets.h"
+#include "validation/Helpers.h"
+#include "validation/Reference.h"
+#include "validation/Validation.h"
+#include "validation/ValidationUserConfiguration.h"
+
+#include <random>
+
+using namespace arm_compute;
+using namespace arm_compute::test;
+using namespace arm_compute::test::neon;
+using namespace arm_compute::test::validation;
+
+namespace
+{
+Tensor compute_roi_pooling_layer(const TensorShape &shape, DataType dt, const std::vector<ROI> &rois, ROIPoolingLayerInfo pool_info)
+{
+    TensorShape shape_dst;
+    shape_dst.set(0, pool_info.pooled_width());
+    shape_dst.set(1, pool_info.pooled_height());
+    shape_dst.set(2, shape.z());
+    shape_dst.set(3, rois.size());
+
+    // Create tensors
+    Tensor     src        = create_tensor(shape, dt);
+    Tensor     dst        = create_tensor(shape_dst, dt);
+    Array<ROI> rois_array = create_array(rois);
+
+    // Create and configure function
+    NEROIPoolingLayer roi_pool;
+    roi_pool.configure(&src, &rois_array, &dst, pool_info);
+
+    // Allocate tensors
+    src.allocator()->allocate();
+    dst.allocator()->allocate();
+
+    BOOST_TEST(!src.info()->is_resizable());
+    BOOST_TEST(!dst.info()->is_resizable());
+
+    // Fill tensors
+    std::uniform_real_distribution<> distribution(-1, 1);
+    library->fill(NEAccessor(src), distribution, 0);
+
+    // Compute function
+    roi_pool.run();
+
+    return dst;
+}
+} // namespace
+
+#ifndef DOXYGEN_SKIP_THIS
+BOOST_AUTO_TEST_SUITE(NEON)
+BOOST_AUTO_TEST_SUITE(ROIPoolingLayer)
+
+BOOST_AUTO_TEST_SUITE(Float)
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit"))
+BOOST_DATA_TEST_CASE(RunSmall, CNNFloatDataTypes() * boost::unit_test::data::make({ 10, 20, 40 }) * boost::unit_test::data::make({ 7, 9 }) * boost::unit_test::data::make({ 1.f / 8.f, 1.f / 16.f }),
+                     dt, num_rois, roi_pool_size, roi_scale)
+{
+    TensorShape         shape(50U, 47U, 2U, 3U);
+    ROIPoolingLayerInfo pool_info(roi_pool_size, roi_pool_size, roi_scale);
+
+    // Construct ROI vector
+    std::vector<ROI> rois = generate_random_rois(shape, pool_info, num_rois, user_config.seed);
+
+    // Compute function
+    Tensor dst = compute_roi_pooling_layer(shape, dt, rois, pool_info);
+
+    // Compute reference
+    RawTensor ref_dst = Reference::compute_reference_roi_pooling_layer(shape, dt, rois, pool_info);
+
+    // Validate output
+    validate(NEAccessor(dst), ref_dst);
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+#endif
diff --git a/tests/validation/Reference.cpp b/tests/validation/Reference.cpp
index 761bc20..2388fb6 100644
--- a/tests/validation/Reference.cpp
+++ b/tests/validation/Reference.cpp
@@ -30,6 +30,7 @@
 #include "validation/Helpers.h"
 
 #include <random>
+#include <vector>
 
 using namespace arm_compute::test;
 
@@ -620,6 +621,28 @@
     return ref_dst;
 }
 
+RawTensor Reference::compute_reference_roi_pooling_layer(const TensorShape &shape, DataType dt, const std::vector<ROI> &rois, const ROIPoolingLayerInfo &pool_info)
+{
+    TensorShape shape_dst;
+    shape_dst.set(0, pool_info.pooled_width());
+    shape_dst.set(1, pool_info.pooled_height());
+    shape_dst.set(2, shape.z());
+    shape_dst.set(3, rois.size());
+
+    // Create reference
+    RawTensor ref_src = library->get(shape, dt);
+    RawTensor ref_dst = library->get(shape_dst, dt);
+
+    // Fill reference
+    std::uniform_real_distribution<> distribution(-1, 1);
+    library->fill(ref_src, distribution, 0.0);
+
+    // Compute reference
+    ReferenceCPP::roi_pooling_layer(ref_src, ref_dst, rois, pool_info);
+
+    return ref_dst;
+}
+
 RawTensor Reference::compute_reference_softmax_layer(const TensorShape &shape, DataType dt, int fixed_point_position)
 {
     // Create reference
diff --git a/tests/validation/Reference.h b/tests/validation/Reference.h
index 41d6a60..1794ae6 100644
--- a/tests/validation/Reference.h
+++ b/tests/validation/Reference.h
@@ -331,6 +331,14 @@
       * @return Computed raw tensor.
       */
     static RawTensor compute_reference_pooling_layer(const TensorShape &shape_in, const TensorShape &shape_out, DataType dt, PoolingLayerInfo pool_info, int fixed_point_position = 0);
+    /** Compute reference roi pooling layer.
+     *
+     * @param[in] shape     Shape of the input tensor.
+     * @param[in] dt        Data type of input and output tensors.
+     * @param[in] rois      Region of interest vector.
+     * @param[in] pool_info ROI Pooling Layer information.
+     */
+    static RawTensor compute_reference_roi_pooling_layer(const TensorShape &shape, DataType dt, const std::vector<ROI> &rois, const ROIPoolingLayerInfo &pool_info);
     /** Compute reference softmax layer.
      *
      * @param[in] shape                Shape of the input and output tensors.
diff --git a/tests/validation/ReferenceCPP.cpp b/tests/validation/ReferenceCPP.cpp
index 7c50f50..c89b737 100644
--- a/tests/validation/ReferenceCPP.cpp
+++ b/tests/validation/ReferenceCPP.cpp
@@ -318,6 +318,14 @@
     boost::apply_visitor(tensor_visitors::pooling_layer_visitor(s, pool_info, fixed_point_position), d);
 }
 
+// ROI Pooling Layer
+void ReferenceCPP::roi_pooling_layer(const RawTensor &src, RawTensor &dst, const std::vector<ROI> &rois, const ROIPoolingLayerInfo &pool_info)
+{
+    const TensorVariant s = TensorFactory::get_tensor(src);
+    TensorVariant       d = TensorFactory::get_tensor(dst);
+    boost::apply_visitor(tensor_visitors::roi_pooling_layer_visitor(s, rois, pool_info), d);
+}
+
 // Softmax Layer
 void ReferenceCPP::softmax_layer(const RawTensor &src, RawTensor &dst)
 {
diff --git a/tests/validation/ReferenceCPP.h b/tests/validation/ReferenceCPP.h
index 8ee8f31..9debdee 100644
--- a/tests/validation/ReferenceCPP.h
+++ b/tests/validation/ReferenceCPP.h
@@ -276,7 +276,7 @@
      * @param[in]  norm_info Normalization Layer information.
      */
     static void normalization_layer(const RawTensor &src, RawTensor &dst, NormalizationLayerInfo norm_info);
-    /** Pooling layer of @p src based on the information from @p norm_info.
+    /** Pooling layer of @p src based on the information from @p pool_info.
      *
      * @param[in]  src                  Input tensor.
      * @param[out] dst                  Result tensor.
@@ -284,6 +284,14 @@
      * @param[in]  fixed_point_position Fixed point position. (Optional)
      */
     static void pooling_layer(const RawTensor &src, RawTensor &dst, PoolingLayerInfo pool_info, int fixed_point_position = 0);
+    /** ROI Pooling layer of @p src based on the information from @p pool_info and @p rois.
+     *
+     * @param[in]  src       Input tensor.
+     * @param[out] dst       Result tensor.
+     * @param[in]  rois      Region of Interest points.
+     * @param[in]  pool_info ROI Pooling Layer information.
+     */
+    static void roi_pooling_layer(const RawTensor &src, RawTensor &dst, const std::vector<ROI> &rois, const ROIPoolingLayerInfo &pool_info);
     /** Softmax Layer of @p src.
      *
      * @param[in]  src Input tensor.
diff --git a/tests/validation/TensorOperations.h b/tests/validation/TensorOperations.h
index 4f77656..5499e58 100644
--- a/tests/validation/TensorOperations.h
+++ b/tests/validation/TensorOperations.h
@@ -1438,6 +1438,78 @@
     }
 }
 
+// Pooling layer
+template <typename T>
+void roi_pooling_layer(const Tensor<T> &in, Tensor<T> &out, const std::vector<ROI> &rois, const ROIPoolingLayerInfo &pool_info)
+{
+    const int   num_rois   = rois.size();
+    const int   width_in   = in.shape().x();
+    const int   height_in  = in.shape().y();
+    const int   fms        = in.shape().z();
+    const int   volume_in  = width_in * height_in * fms;
+    const int   pool_w     = pool_info.pooled_width();
+    const int   pool_h     = pool_info.pooled_height();
+    const int   volume_out = pool_w * pool_h * fms;
+    const float roi_scale  = pool_info.spatial_scale();
+
+    // Iterate through all rois
+    for(int roi_idx = 0; roi_idx < num_rois; ++roi_idx)
+    {
+        // Get dimensions of current ROI
+        const ROI &roi = rois[roi_idx];
+
+        int batch_id    = roi.batch_idx;
+        int roi_start_x = support::cpp11::round(roi.rect.x * roi_scale);
+        int roi_start_y = support::cpp11::round(roi.rect.y * roi_scale);
+        int roi_width   = std::max(support::cpp11::round(roi.rect.width * roi_scale), 1.f);
+        int roi_height  = std::max(support::cpp11::round(roi.rect.height * roi_scale), 1.f);
+
+        // Determine pooling regions
+        float pool_region_size_x = static_cast<float>(roi_width) / pool_w;
+        float pool_region_size_y = static_cast<float>(roi_height) / pool_h;
+
+        // Iterate through all channel
+        for(int fm = 0; fm < fms; ++fm)
+        {
+            // Calculate each output pixel
+            for(int py = 0; py < pool_h; ++py)
+            {
+                for(int px = 0; px < pool_w; ++px)
+                {
+                    int region_start_x = static_cast<int>(std::floor(px * pool_region_size_x));
+                    int region_end_x   = static_cast<int>(std::ceil((px + 1) * pool_region_size_x));
+                    int region_start_y = static_cast<int>(std::floor(py * pool_region_size_y));
+                    int region_end_y   = static_cast<int>(std::ceil((py + 1) * pool_region_size_y));
+
+                    region_start_x = std::min(std::max(region_start_x + roi_start_x, 0), width_in);
+                    region_end_x   = std::min(std::max(region_end_x + roi_start_x, 0), width_in);
+                    region_start_y = std::min(std::max(region_start_y + roi_start_y, 0), height_in);
+                    region_end_y   = std::min(std::max(region_end_y + roi_start_y, 0), height_in);
+
+                    // Iterate through each pixel in the pooling region
+                    if((region_end_x <= region_start_x) || (region_end_y <= region_start_y))
+                    {
+                        out[roi_idx * volume_out + fm * pool_w * pool_h + py * pool_w + px] = 0;
+                    }
+                    else
+                    {
+                        T curr_max = std::numeric_limits<T>::lowest();
+                        for(int j = region_start_y; j < region_end_y; ++j)
+                        {
+                            for(int i = region_start_x; i < region_end_x; ++i)
+                            {
+                                const auto val = in[batch_id * volume_in + fm * width_in * height_in + j * width_in + i];
+                                curr_max       = std::max(val, curr_max);
+                            }
+                        }
+                        out[roi_idx * volume_out + fm * pool_w * pool_h + py * pool_w + px] = curr_max;
+                    }
+                }
+            }
+        }
+    }
+}
+
 // Softmax Layer
 template <typename T, typename std::enable_if<is_floating_point<T>::value, int>::type * = nullptr>
 void softmax_layer(const Tensor<T> &in, Tensor<T> &out)
diff --git a/tests/validation/TensorVisitors.h b/tests/validation/TensorVisitors.h
index a274140..1b58a16 100644
--- a/tests/validation/TensorVisitors.h
+++ b/tests/validation/TensorVisitors.h
@@ -316,6 +316,29 @@
     PoolingLayerInfo     _pool_info;
     int                  _fixed_point_position;
 };
+
+// ROI Pooling layer
+struct roi_pooling_layer_visitor : public boost::static_visitor<>
+{
+public:
+    explicit roi_pooling_layer_visitor(const TensorVariant &in, const std::vector<ROI> &rois, ROIPoolingLayerInfo pool_info)
+        : _in(in), _rois(rois), _pool_info(pool_info)
+    {
+    }
+
+    template <typename T>
+    void operator()(Tensor<T> &out) const
+    {
+        const Tensor<T> &in = boost::get<Tensor<T>>(_in);
+        tensor_operations::roi_pooling_layer(in, out, _rois, _pool_info);
+    }
+
+private:
+    const TensorVariant    &_in;
+    const std::vector<ROI> &_rois;
+    ROIPoolingLayerInfo     _pool_info;
+};
+
 // Softmax Layer visitor
 struct softmax_layer_visitor : public boost::static_visitor<>
 {