COMPMID-424 Implemented reference implementation and validation tests (NEON and CL) for Warp Perspective

Changed the behaviour in NEWarpKernel for border mode replicate and constant to stick with the VX specs.
When the new coords are out of the valid region, the output will be computed using the values from the border.
In the validation tests the validate will be called with tolerance_value 1 and tolerance_number 0.2%, due to some float arithmetic related mismatches.

Change-Id: Id4f9d0ef87178f8f8fd38ee17fee0e6f4beb85cd
Reviewed-on: http://mpd-gerrit.cambridge.arm.com/80283
Tested-by: Kaizen <jeremy.johnson+kaizengerrit@arm.com>
Reviewed-by: Moritz Pflanzer <moritz.pflanzer@arm.com>
Reviewed-by: Steven Niu <steven.niu@arm.com>
diff --git a/tests/validation/CL/WarpPerspective.cpp b/tests/validation/CL/WarpPerspective.cpp
new file mode 100644
index 0000000..260b22b
--- /dev/null
+++ b/tests/validation/CL/WarpPerspective.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 "AssetsLibrary.h"
+#include "CL/CLAccessor.h"
+#include "Globals.h"
+#include "PaddingCalculator.h"
+#include "TypePrinter.h"
+#include "Utils.h"
+#include "validation/Datasets.h"
+#include "validation/Helpers.h"
+#include "validation/Reference.h"
+#include "validation/Validation.h"
+#include "validation/ValidationUserConfiguration.h"
+
+#include "arm_compute/core/Helpers.h"
+#include "arm_compute/core/Types.h"
+#include "arm_compute/runtime/CL/functions/CLWarpPerspective.h"
+#include "arm_compute/runtime/Tensor.h"
+#include "arm_compute/runtime/TensorAllocator.h"
+
+#include "boost_wrapper.h"
+
+#include <random>
+#include <string>
+
+using namespace arm_compute;
+using namespace arm_compute::test;
+using namespace arm_compute::test::validation;
+
+namespace
+{
+/** Compute Warp Perspective function.
+ *
+ * @param[in] input                 Shape of the input and output tensors.
+ * @param[in] matrix                The perspective matrix. Must be 3x3 of type float.
+ * @param[in] policy                The interpolation type.
+ * @param[in] border_mode           Strategy to use for borders.
+ * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ *
+ * @return Computed output tensor.
+ */
+CLTensor compute_warp_perspective(const TensorShape &shape, const float *matrix, InterpolationPolicy policy,
+                                  BorderMode border_mode, uint8_t constant_border_value)
+{
+    // Create tensors
+    CLTensor src = create_tensor<CLTensor>(shape, DataType::U8);
+    CLTensor dst = create_tensor<CLTensor>(shape, DataType::U8);
+
+    // Create and configure function
+    CLWarpPerspective warp_perspective;
+    warp_perspective.configure(&src, &dst, matrix, policy, border_mode, constant_border_value);
+
+    // Allocate tensors
+    src.allocator()->allocate();
+    dst.allocator()->allocate();
+
+    BOOST_TEST(!src.info()->is_resizable());
+    BOOST_TEST(!dst.info()->is_resizable());
+
+    // Fill tensors
+    library->fill_tensor_uniform(CLAccessor(src), 0);
+
+    // Compute function
+    warp_perspective.run();
+
+    return dst;
+}
+} // namespace
+
+#ifndef DOXYGEN_SKIP_THIS
+BOOST_AUTO_TEST_SUITE(CL)
+BOOST_AUTO_TEST_SUITE(WarpPerspective)
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit") * boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(Configuration, (SmallShapes() + LargeShapes())
+                     * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR }) * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Create tensors
+    CLTensor src = create_tensor<CLTensor>(shape, DataType::U8);
+    CLTensor dst = create_tensor<CLTensor>(shape, DataType::U8);
+
+    BOOST_TEST(src.info()->is_resizable());
+    BOOST_TEST(dst.info()->is_resizable());
+
+    // Create and configure function
+    CLWarpPerspective warp_perspective;
+    warp_perspective.configure(&src, &dst, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate valid region
+    const ValidRegion valid_region = shape_to_valid_region(shape);
+
+    validate(src.info()->valid_region(), valid_region);
+    validate(dst.info()->valid_region(), valid_region);
+
+    // Validate padding
+    PaddingCalculator calculator(shape.x(), 4);
+    calculator.set_border_mode(border_mode);
+
+    const PaddingSize read_padding(1);
+    const PaddingSize write_padding = calculator.required_padding(PaddingCalculator::Option::EXCLUDE_BORDER);
+
+    validate(src.info()->padding(), read_padding);
+    validate(dst.info()->padding(), write_padding);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit"))
+BOOST_DATA_TEST_CASE(RunSmall, SmallShapes()
+                     * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR })
+                     * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    // Create the valid mask Tensor
+    RawTensor valid_mask(shape, DataType::U8);
+
+    // Create the matrix
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Compute function
+    CLTensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Compute reference
+    RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate output
+    validate(CLAccessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(RunLarge, LargeShapes()
+                     * boost::unit_test::data::make({ InterpolationPolicy::NEAREST_NEIGHBOR, InterpolationPolicy::BILINEAR }) * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    // Create the valid mask Tensor
+    RawTensor valid_mask(shape, DataType::U8);
+
+    // Create the matrix
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Compute function
+    CLTensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Compute reference
+    RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate output
+    validate(CLAccessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+#endif /* DOXYGEN_SKIP_THIS */
diff --git a/tests/validation/Helpers.h b/tests/validation/Helpers.h
index 8d70de6..09ffda8 100644
--- a/tests/validation/Helpers.h
+++ b/tests/validation/Helpers.h
@@ -225,6 +225,31 @@
     return out_shape;
 }
 
+/** Fill matrix random.
+ *
+ * @param[in,out] matrix Matrix
+ * @param[in]     cols   Columns (width) of matrix
+ * @param[in]     rows   Rows (height) of matrix
+ */
+template <std::size_t SIZE>
+inline void fill_warp_matrix(std::array<float, SIZE> &matrix, int cols, int rows)
+{
+    std::mt19937                          gen(user_config.seed.get());
+    std::uniform_real_distribution<float> dist(-1, 1);
+
+    for(int v = 0, r = 0; r < rows; ++r)
+    {
+        for(int c = 0; c < cols; ++c, ++v)
+        {
+            matrix[v] = dist(gen);
+        }
+    }
+    if(SIZE == 9)
+    {
+        matrix[(cols * rows) - 1] = 1;
+    }
+}
+
 /** Create a vector of random ROIs.
  *
  * @param[in] shape     The shape of the input tensor.
diff --git a/tests/validation/NEON/WarpPerspective.cpp b/tests/validation/NEON/WarpPerspective.cpp
new file mode 100644
index 0000000..2c102ea
--- /dev/null
+++ b/tests/validation/NEON/WarpPerspective.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "AssetsLibrary.h"
+#include "Globals.h"
+#include "NEON/Accessor.h"
+#include "PaddingCalculator.h"
+#include "TypePrinter.h"
+#include "Utils.h"
+#include "validation/Datasets.h"
+#include "validation/Helpers.h"
+#include "validation/Reference.h"
+#include "validation/Validation.h"
+#include "validation/ValidationUserConfiguration.h"
+
+#include "arm_compute/core/Helpers.h"
+#include "arm_compute/core/Types.h"
+#include "arm_compute/runtime/NEON/functions/NEWarpPerspective.h"
+#include "arm_compute/runtime/Tensor.h"
+#include "arm_compute/runtime/TensorAllocator.h"
+
+#include "boost_wrapper.h"
+
+#include <random>
+#include <string>
+
+using namespace arm_compute;
+using namespace arm_compute::test;
+using namespace arm_compute::test::validation;
+
+namespace
+{
+/** Compute Warp Perspective function.
+ *
+     * @param[in] input                 Shape of the input and output tensors.
+     * @param[in] matrix                The perspective matrix. Must be 3x3 of type float.
+     * @param[in] policy                The interpolation type.
+     * @param[in] border_mode           Strategy to use for borders.
+     * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ *
+ * @return Computed output tensor.
+ */
+Tensor compute_warp_perspective(const TensorShape &shape, const float *matrix, InterpolationPolicy policy,
+                                BorderMode border_mode, uint8_t constant_border_value)
+{
+    // Create tensors
+    Tensor src = create_tensor<Tensor>(shape, DataType::U8);
+    Tensor dst = create_tensor<Tensor>(shape, DataType::U8);
+
+    // Create and configure function
+    NEWarpPerspective warp_perspective;
+    warp_perspective.configure(&src, &dst, matrix, policy, border_mode, constant_border_value);
+
+    // Allocate tensors
+    src.allocator()->allocate();
+    dst.allocator()->allocate();
+
+    BOOST_TEST(!src.info()->is_resizable());
+    BOOST_TEST(!dst.info()->is_resizable());
+
+    // Fill tensors
+    library->fill_tensor_uniform(Accessor(src), 0);
+
+    // Compute function
+    warp_perspective.run();
+
+    return dst;
+}
+} // namespace
+
+#ifndef DOXYGEN_SKIP_THIS
+BOOST_AUTO_TEST_SUITE(NEON)
+BOOST_AUTO_TEST_SUITE(WarpPerspective)
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit") * boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(Configuration, (SmallShapes() + LargeShapes())
+                     * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR }) * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    // Create the matrix
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Create tensors
+    Tensor src = create_tensor<Tensor>(shape, DataType::U8);
+    Tensor dst = create_tensor<Tensor>(shape, DataType::U8);
+
+    BOOST_TEST(src.info()->is_resizable());
+    BOOST_TEST(dst.info()->is_resizable());
+
+    // Create and configure function
+    NEWarpPerspective warp_perspective;
+    warp_perspective.configure(&src, &dst, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate valid region
+    const ValidRegion valid_region = shape_to_valid_region(shape);
+
+    validate(src.info()->valid_region(), valid_region);
+    validate(dst.info()->valid_region(), valid_region);
+
+    // Validate padding
+    PaddingCalculator calculator(shape.x(), 1);
+    calculator.set_border_mode(border_mode);
+    calculator.set_border_size(1);
+
+    const PaddingSize read_padding(1);
+    const PaddingSize write_padding = calculator.required_padding();
+
+    validate(src.info()->padding(), read_padding);
+    validate(dst.info()->padding(), write_padding);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit"))
+BOOST_DATA_TEST_CASE(RunSmall, SmallShapes()
+                     * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR })
+                     * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    // Create the valid mask Tensor
+    RawTensor valid_mask(shape, DataType::U8);
+
+    // Create the matrix
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Compute function
+    Tensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Compute reference
+    RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate output
+    validate(Accessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+BOOST_TEST_DECORATOR(*boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(RunLarge, LargeShapes()
+                     * boost::unit_test::data::make({ InterpolationPolicy::NEAREST_NEIGHBOR, InterpolationPolicy::BILINEAR }) * BorderModes(),
+                     shape, policy, border_mode)
+{
+    uint8_t constant_border_value = 0;
+
+    // Generate a random constant value if border_mode is constant
+    if(border_mode == BorderMode::CONSTANT)
+    {
+        std::mt19937                           gen(user_config.seed.get());
+        std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+        constant_border_value = distribution_u8(gen);
+    }
+
+    // Create the valid mask Tensor
+    RawTensor valid_mask(shape, DataType::U8);
+
+    // Create the matrix
+    std::array<float, 9> matrix;
+    fill_warp_matrix<9>(matrix, 3, 3);
+
+    // Compute function
+    Tensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Compute reference
+    RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+    // Validate output
+    validate(Accessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+#endif /* DOXYGEN_SKIP_THIS */
diff --git a/tests/validation/Reference.cpp b/tests/validation/Reference.cpp
index 621158a..0fca661 100644
--- a/tests/validation/Reference.cpp
+++ b/tests/validation/Reference.cpp
@@ -390,6 +390,22 @@
     return ref_dst;
 }
 
+RawTensor Reference::compute_reference_warp_perspective(const TensorShape &shape, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode,
+                                                        uint8_t constant_border_value)
+{
+    // Create reference
+    RawTensor ref_src(shape, DataType::U8);
+    RawTensor ref_dst(shape, DataType::U8);
+
+    // Fill reference
+    library->fill_tensor_uniform(ref_src, 0);
+
+    // Compute reference
+    ReferenceCPP::warp_perspective(ref_src, ref_dst, valid_mask, matrix, policy, border_mode, constant_border_value);
+
+    return ref_dst;
+}
+
 RawTensor Reference::compute_reference_batch_normalization_layer(const TensorShape &shape0, const TensorShape &shape1, DataType dt, float epsilon, int fixed_point_position)
 {
     // Create reference
diff --git a/tests/validation/Reference.h b/tests/validation/Reference.h
index e00a5b9..13e43d3 100644
--- a/tests/validation/Reference.h
+++ b/tests/validation/Reference.h
@@ -266,6 +266,21 @@
      * @return Computed raw tensor.
      */
     static RawTensor compute_reference_threshold(const TensorShape &shape, uint8_t threshold, uint8_t false_value, uint8_t true_value, ThresholdType type, uint8_t upper);
+
+    /** Compute reference Warp Perspective.
+     *
+     * @param[in]  shape                 Shape of the input and output tensors.
+     * @param[out] valid_mask            Valid mask tensor.
+     * @param[in]  matrix                The perspective matrix. Must be 3x3 of type float.
+     * @param[in]  policy                The interpolation type.
+     * @param[in]  border_mode           Strategy to use for borders.
+     * @param[in]  constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+     *
+     * @return Computed raw tensor.
+     */
+    static RawTensor compute_reference_warp_perspective(const TensorShape &shape, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode,
+                                                        uint8_t constant_border_value);
+
     /** Compute reference batch normalization layer.
      *
      * @param[in] shape0               Shape of the input and output tensors.
diff --git a/tests/validation/ReferenceCPP.cpp b/tests/validation/ReferenceCPP.cpp
index 69100ad..069cc1d 100644
--- a/tests/validation/ReferenceCPP.cpp
+++ b/tests/validation/ReferenceCPP.cpp
@@ -252,6 +252,16 @@
     tensor_operations::threshold(s, d, threshold, false_value, true_value, type, upper);
 }
 
+// Warp perspective
+void ReferenceCPP::warp_perspective(const RawTensor &src, RawTensor &dst, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value)
+{
+    ARM_COMPUTE_ERROR_ON(src.data_type() != DataType::U8 || dst.data_type() != DataType::U8);
+    const Tensor<uint8_t> s(src.shape(), src.data_type(), src.fixed_point_position(), reinterpret_cast<const uint8_t *>(src.data()));
+    Tensor<uint8_t>       d(dst.shape(), dst.data_type(), dst.fixed_point_position(), reinterpret_cast<uint8_t *>(dst.data()));
+    Tensor<uint8_t>       vmask(valid_mask.shape(), valid_mask.data_type(), valid_mask.fixed_point_position(), reinterpret_cast<uint8_t *>(valid_mask.data()));
+    tensor_operations::warp_perspective(s, d, vmask, matrix, policy, border_mode, constant_border_value);
+}
+
 // Batch Normalization Layer
 void ReferenceCPP::batch_normalization_layer(const RawTensor &src, RawTensor &dst, const RawTensor &mean, const RawTensor &var, const RawTensor &beta, const RawTensor &gamma, float epsilon,
                                              int fixed_point_position)
diff --git a/tests/validation/ReferenceCPP.h b/tests/validation/ReferenceCPP.h
index 2118ea9..2d35fa9 100644
--- a/tests/validation/ReferenceCPP.h
+++ b/tests/validation/ReferenceCPP.h
@@ -228,6 +228,18 @@
      * @param[in]  upper       Upper threshold. Only used when the thresholding type is RANGE.
      */
     static void threshold(const RawTensor &src, RawTensor &dst, uint8_t threshold, uint8_t false_value, uint8_t true_value, ThresholdType type, uint8_t upper);
+    /** Warp perspective of@p src to @p dst
+     *
+     * @param[in]  src                   First tensor.
+     * @param[out] dst                   Result tensor.
+     * @param[out] valid_mask            Valid mask tensor.
+     * @param[in]  matrix                The perspective matrix. Must be 3x3 of type float.
+     * @param[in]  policy                The interpolation type.
+     * @param[in]  border_mode           Strategy to use for borders.
+     * @param[in]  constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+     */
+    static void warp_perspective(const RawTensor &src, RawTensor &dst, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value);
+
     /** Batch Normalization of @p src based on the information from @p norm_info.
      *
      * @param[in]  src                  Input tensor.
diff --git a/tests/validation/TensorOperations.h b/tests/validation/TensorOperations.h
index 317d229..3220d80 100644
--- a/tests/validation/TensorOperations.h
+++ b/tests/validation/TensorOperations.h
@@ -388,7 +388,7 @@
     }
 }
 
-// Accumulate weighted
+// Accumulate weighted total_size   = init_auto_padding(tensor_shape, num_channels, type);
 template <typename T>
 void accumulate_weighted(const Tensor<T> &in, Tensor<T> &out, float alpha)
 {
@@ -748,6 +748,132 @@
     }
 }
 
+template <typename T>
+T bilinear_policy(const Tensor<T> &in, Coordinates id, float xn, float yn, BorderMode border_mode, uint8_t constant_border_value)
+{
+    int idx = std::floor(xn);
+    int idy = std::floor(yn);
+
+    const float dx   = xn - idx;
+    const float dy   = yn - idy;
+    const float dx_1 = 1.0f - dx;
+    const float dy_1 = 1.0f - dy;
+
+    id.set(0, idx);
+    id.set(1, idy);
+    const T tl = tensor_elem_at(in, id, border_mode, constant_border_value);
+    id.set(0, idx + 1);
+    id.set(1, idy);
+    const T tr = tensor_elem_at(in, id, border_mode, constant_border_value);
+    id.set(0, idx);
+    id.set(1, idy + 1);
+    const T bl = tensor_elem_at(in, id, border_mode, constant_border_value);
+    id.set(0, idx + 1);
+    id.set(1, idy + 1);
+    const T br = tensor_elem_at(in, id, border_mode, constant_border_value);
+
+    return tl * (dx_1 * dy_1) + tr * (dx * dy_1) + bl * (dx_1 * dy) + br * (dx * dy);
+}
+
+bool valid_bilinear_policy(float xn, float yn, int width, int height, BorderMode border_mode)
+{
+    if(border_mode != BorderMode::UNDEFINED)
+    {
+        return true;
+    }
+    if((0 <= yn + 1) && (yn + 1 < height) && (0 <= xn + 1) && (xn + 1 < width))
+    {
+        return true;
+    }
+    return false;
+}
+
+// Warp Perspective
+template <typename T>
+void warp_perspective(const Tensor<T> &in, Tensor<T> &out, Tensor<T> &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value)
+{
+    // x0 = M00 * x + M01 * y + M02
+    // y0 = M10 * x + M11 * y + M12
+    // z0 = M20 * x + M21 * y + M22
+    // xn = x0 / z0
+    // yn = y0 / z0
+    const float M00 = matrix[0];
+    const float M10 = matrix[1];
+    const float M20 = matrix[2];
+    const float M01 = matrix[0 + 1 * 3];
+    const float M11 = matrix[1 + 1 * 3];
+    const float M21 = matrix[2 + 1 * 3];
+    const float M02 = matrix[0 + 2 * 3];
+    const float M12 = matrix[1 + 2 * 3];
+    const float M22 = matrix[2 + 2 * 3];
+
+    const int width  = in.shape().x();
+    const int height = in.shape().y();
+
+    for(int element_idx = 0; element_idx < in.num_elements(); ++element_idx)
+    {
+        valid_mask[element_idx] = 1;
+        Coordinates id          = index2coord(in.shape(), element_idx);
+        int         idx         = id.x();
+        int         idy         = id.y();
+        const float z0          = M20 * idx + M21 * idy + M22;
+
+        float x0 = (M00 * idx + M01 * idy + M02);
+        float y0 = (M10 * idx + M11 * idy + M12);
+
+        float xn = x0 / z0;
+        float yn = y0 / z0;
+        id.set(0, static_cast<int>(std::floor(xn)));
+        id.set(1, static_cast<int>(std::floor(yn)));
+        if((0 <= yn) && (yn < height) && (0 <= xn) && (xn < width))
+        {
+            switch(policy)
+            {
+                case InterpolationPolicy::NEAREST_NEIGHBOR:
+                    out[element_idx] = tensor_elem_at(in, id, border_mode, constant_border_value);
+                    break;
+                case InterpolationPolicy::BILINEAR:
+                    (valid_bilinear_policy(xn, yn, width, height, border_mode)) ? out[element_idx] = bilinear_policy(in, id, xn, yn, border_mode, constant_border_value) : valid_mask[element_idx] = 0;
+                    break;
+                case InterpolationPolicy::AREA:
+                default:
+                    ARM_COMPUTE_ERROR("Interpolation not supported");
+            }
+        }
+        else
+        {
+            if(border_mode == BorderMode::UNDEFINED)
+            {
+                valid_mask[element_idx] = 0;
+            }
+            else
+            {
+                switch(policy)
+                {
+                    case InterpolationPolicy::NEAREST_NEIGHBOR:
+                        if(border_mode == BorderMode::CONSTANT)
+                        {
+                            out[element_idx] = constant_border_value;
+                        }
+                        else if(border_mode == BorderMode::REPLICATE)
+                        {
+                            id.set(0, std::max(0, std::min(static_cast<int>(xn), width - 1)));
+                            id.set(1, std::max(0, std::min(static_cast<int>(yn), height - 1)));
+                            out[element_idx] = in[coord2index(in.shape(), id)];
+                        }
+                        break;
+                    case InterpolationPolicy::BILINEAR:
+                        out[element_idx] = bilinear_policy(in, id, xn, yn, border_mode, constant_border_value);
+                        break;
+                    case InterpolationPolicy::AREA:
+                    default:
+                        ARM_COMPUTE_ERROR("Interpolation not supported");
+                }
+            }
+        }
+    }
+}
+
 // Batch Normalization Layer for fixed point type
 template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type * = nullptr>
 void batch_normalization_layer(const Tensor<T> &in, Tensor<T> &out, const Tensor<T> &mean, const Tensor<T> &var, const Tensor<T> &beta, const Tensor<T> &gamma, float epsilon, int fixed_point_position)
diff --git a/tests/validation/Validation.cpp b/tests/validation/Validation.cpp
index eac4105..868bbaa 100644
--- a/tests/validation/Validation.cpp
+++ b/tests/validation/Validation.cpp
@@ -193,7 +193,6 @@
             BOOST_TEST_INFO("reference = " << std::setprecision(5) << ref);
             BOOST_TEST_INFO("target = " << std::setprecision(5) << target);
             BOOST_TEST_WARN(equal);
-
             ++num_mismatches;
         }
         ++num_elements;
@@ -264,6 +263,39 @@
                << "%) mismatched (maximum tolerated " << std::setprecision(2) << tolerance_number << "%)");
 }
 
+void validate(const IAccessor &tensor, const RawTensor &reference, const RawTensor &valid_mask, float tolerance_value, float tolerance_number, uint64_t wrap_range)
+{
+    int64_t num_mismatches = 0;
+    int64_t num_elements   = 0;
+
+    BOOST_TEST(tensor.element_size() == reference.element_size());
+    BOOST_TEST(tensor.format() == reference.format());
+    BOOST_TEST(tensor.data_type() == reference.data_type());
+    BOOST_TEST(tensor.num_channels() == reference.num_channels());
+    BOOST_TEST(compare_dimensions(tensor.shape(), reference.shape()));
+
+    const int    min_elements = std::min(tensor.num_elements(), reference.num_elements());
+    const int    min_channels = std::min(tensor.num_channels(), reference.num_channels());
+    const size_t channel_size = element_size_from_data_type(reference.data_type());
+
+    // Iterate over all elements within valid region, e.g. U8, S16, RGB888, ...
+    for(int element_idx = 0; element_idx < min_elements; ++element_idx)
+    {
+        const Coordinates id = index2coord(reference.shape(), element_idx);
+        if(valid_mask[element_idx] == 1)
+        {
+            check_single_element(id, tensor, reference, tolerance_value, wrap_range, min_channels, channel_size, num_mismatches, num_elements);
+        }
+    }
+
+    const int64_t absolute_tolerance_number = tolerance_number * num_elements;
+    const float   percent_mismatches        = static_cast<float>(num_mismatches) / num_elements * 100.f;
+
+    BOOST_TEST(num_mismatches <= absolute_tolerance_number,
+               num_mismatches << " values (" << std::setprecision(2) << percent_mismatches
+               << "%) mismatched (maximum tolerated " << std::setprecision(2) << tolerance_number << "%)");
+}
+
 void validate(const IAccessor &tensor, const void *reference_value)
 {
     BOOST_TEST_REQUIRE((reference_value != nullptr));
diff --git a/tests/validation/Validation.h b/tests/validation/Validation.h
index 66bb2be..43a90f3 100644
--- a/tests/validation/Validation.h
+++ b/tests/validation/Validation.h
@@ -103,6 +103,18 @@
  */
 void validate(const IAccessor &tensor, const RawTensor &reference, const ValidRegion &valid_region, float tolerance_value = 0.f, float tolerance_number = 0.f, uint64_t wrap_range = 0);
 
+/** Validate tensors with valid mask.
+ *
+ * - Dimensionality has to be the same.
+ * - All values have to match.
+ *
+ * @note: wrap_range allows cases where reference tensor rounds up to the wrapping point, causing it to wrap around to
+ * zero while the test tensor stays at wrapping point to pass. This may permit true erroneous cases (difference between
+ * reference tensor and test tensor is multiple of wrap_range), but such errors would be detected by
+ * other test cases.
+ */
+void validate(const IAccessor &tensor, const RawTensor &reference, const RawTensor &valid_mask, float tolerance_value = 0.f, float tolerance_number = 0.f, uint64_t wrap_range = 0);
+
 /** Validate tensors against constant value.
  *
  * - All values have to match.