IVGCVSW-6469 Add MirrorPad FrontEnd and Ref Support

 * Added PaddingMode enum to PaddingDescriptor to enable Symmetric and
   Reflect padding.
 * Added Symmetric and Reflect Ref implementation.
 * Added Serializer & Deserializer support.
 * Added unit tests.

Signed-off-by: Matthew Sloyan <matthew.sloyan@arm.com>
Change-Id: I4bed907b31742b32ccefe5e8ca39a6f1e5bd9dee
diff --git a/src/backends/reference/backend.mk b/src/backends/reference/backend.mk
index f8169a6..7049279 100644
--- a/src/backends/reference/backend.mk
+++ b/src/backends/reference/backend.mk
@@ -41,6 +41,7 @@
         workloads/Lstm.cpp \
         workloads/LstmUtils.cpp \
         workloads/Concatenate.cpp \
+        workloads/MirrorPad.cpp \
         workloads/Pad.cpp \
         workloads/Pooling2d.cpp \
         workloads/PreluImpl.cpp \
diff --git a/src/backends/reference/test/RefLayerTests.cpp b/src/backends/reference/test/RefLayerTests.cpp
index cb31b37..5993270 100644
--- a/src/backends/reference/test/RefLayerTests.cpp
+++ b/src/backends/reference/test/RefLayerTests.cpp
@@ -1415,7 +1415,7 @@
 ARMNN_AUTO_TEST_CASE_WITH_THF(LogSoftmaxFloat16_3, LogSoftmaxTest3<DataType::Float16>)
 ARMNN_AUTO_TEST_CASE_WITH_THF(LogSoftmaxFloat16_4, LogSoftmaxTest4<DataType::Float16>)
 
-// Pad
+// Pad - Constant
 ARMNN_AUTO_TEST_CASE_WITH_THF(PadBFloat162d, PadBFloat162dTest)
 ARMNN_AUTO_TEST_CASE_WITH_THF(PadBFloat162dCustomPadding, PadBFloat162dCustomPaddingTest)
 ARMNN_AUTO_TEST_CASE_WITH_THF(PadBFloat163d, PadBFloat163dTest)
@@ -1445,6 +1445,31 @@
 ARMNN_AUTO_TEST_CASE_WITH_THF(PadQAsymmS8, PadQAsymmTestCommon<DataType::QAsymmS8>, -2.0f, 3, 0.0f)
 ARMNN_AUTO_TEST_CASE_WITH_THF(PadQAsymmS8CustomPadding, PadQAsymmTestCommon<DataType::QAsymmS8>, -2.0f, 3, 2.0f)
 
+// Pad - Symmetric & Reflect
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric2dFloat32, PadSymmetric2dFloat32Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect2dFloat32, PadReflect2dFloat32Test)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric3dFloat32, PadSymmetric3dFloat32Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect3dFloat32, PadReflect3dFloat32Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric3dUint8, PadSymmetric3dUint8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect3dUint8, PadReflect3dUint8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric3dInt8, PadSymmetric3dInt8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect3dInt8, PadReflect3dInt8Test)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric4dFloat32, PadSymmetric4dFloat32Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect4dFloat32, PadReflect4dFloat32Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric4dBFloat16, PadSymmetric4dBFloat16Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect4dBFloat16, PadReflect4dBFloat16Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric4dUint8, PadSymmetric4dUint8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect4dUint8, PadReflect4dUint8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric4dInt8, PadSymmetric4dInt8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect4dInt8, PadReflect4dInt8Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetric4dInt16, PadSymmetric4dInt16Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflect4dInt16, PadReflect4dInt16Test)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadSymmetricFloat16, PadSymmetricFloat16Test)
+ARMNN_AUTO_TEST_CASE_WITH_THF(PadReflectFloat16, PadReflectFloat16Test)
+
 // Constant
 ARMNN_AUTO_TEST_CASE_WITH_THF(Constant, ConstantTest)
 ARMNN_AUTO_TEST_CASE_WITH_THF(ConstantUint8, ConstantUint8CustomQuantizationScaleAndOffsetTest)
diff --git a/src/backends/reference/workloads/CMakeLists.txt b/src/backends/reference/workloads/CMakeLists.txt
index 5727291..f212522 100644
--- a/src/backends/reference/workloads/CMakeLists.txt
+++ b/src/backends/reference/workloads/CMakeLists.txt
@@ -52,6 +52,8 @@
     Concatenate.hpp
     Concatenate.cpp
     Minimum.hpp
+    MirrorPad.cpp
+    MirrorPad.hpp
     Pad.cpp
     Pad.hpp
     Pooling2d.cpp
diff --git a/src/backends/reference/workloads/MirrorPad.cpp b/src/backends/reference/workloads/MirrorPad.cpp
new file mode 100644
index 0000000..7388fed
--- /dev/null
+++ b/src/backends/reference/workloads/MirrorPad.cpp
@@ -0,0 +1,199 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "MirrorPad.hpp"
+
+#include "BaseIterator.hpp"
+#include "Decoders.hpp"
+#include "Encoders.hpp"
+
+namespace
+{
+
+// Convert a linear index into n-dimensional coordinates.
+// E.g. index = 2 returns [0, 0, 2].
+inline std::vector<unsigned int> IndexToCoord(const armnn::TensorShape& shape, unsigned int index)
+{
+    unsigned int numOfElements = shape.GetNumElements();
+
+    ARMNN_ASSERT_MSG(index <= numOfElements, "Index has to be in [0, num_elements]");
+    ARMNN_ASSERT_MSG(numOfElements != 0, "Cannot create coordinate from empty shape");
+
+    std::vector<unsigned int> coord(shape.GetNumDimensions());
+    for(unsigned int i = 0; i < shape.GetNumDimensions(); ++i)
+    {
+        numOfElements /= shape[i];
+        coord[i] = index / numOfElements;
+        index %= numOfElements;
+    }
+
+    return coord;
+}
+
+// Returns the index of a given coordinate.
+// E.g. [0, 0, 2] returns 2.
+inline unsigned int CoordToIndex(const armnn::TensorShape& shape, const std::vector<unsigned int>& coord)
+{
+    ARMNN_ASSERT_MSG(shape.GetNumDimensions() != 0, "Cannot get index from empty shape");
+    ARMNN_ASSERT_MSG(coord.size() != 0, "Cannot get index of empty coordinate");
+
+    unsigned int index    = 0;
+    unsigned int dimSize  = 1;
+
+    for (unsigned int i = shape.GetNumDimensions(); i > 0; --i)
+    {
+        index += coord[i - 1] * dimSize;
+        dimSize *= shape[i - 1];
+    }
+
+    return index;
+}
+
+} // anonymous namespace
+
+namespace armnn
+{
+
+void MirrorPad(const TensorInfo& inputInfo,
+               const TensorInfo& outputInfo,
+               const ITensorHandle* inputHandle,
+               ITensorHandle* outputHandle,
+               const PadQueueDescriptor& data)
+{
+    auto padList  = data.m_Parameters.m_PadList;
+    PaddingMode paddingMode = data.m_Parameters.m_PaddingMode;
+
+    TensorShape outputShape = outputInfo.GetShape();
+    TensorShape inputShape  = inputInfo.GetShape();
+
+    unsigned int numOutputElements = outputInfo.GetNumElements();
+    unsigned int numInputDimensions = inputShape.GetNumDimensions();
+    assert(numInputDimensions == outputShape.GetNumDimensions());
+
+    // If padding mode is Reflect then both paddings must be no greater than inputShape(i) - 1.
+    // If padding mode is Symmetric then both paddings must be no greater than inputShape(i).
+    const unsigned int isReflect = static_cast<unsigned int>(paddingMode == PaddingMode::Reflect);
+    for(unsigned int i = 0; i < padList.size(); ++i)
+    {
+        if(padList.at(i).first > (inputShape[i] - isReflect) ||
+           padList.at(i).second > (inputShape[i] - isReflect))
+        {
+            throw armnn::InvalidArgumentException("Paddings must be less (Reflect) or "
+                                                  "equal (Symmetric) to the dimension size.");
+        }
+    }
+
+    auto inputData = MakeDecoder<float>(inputInfo, inputHandle->Map());
+    auto outData   = MakeEncoder<float>(outputInfo, outputHandle->Map());
+
+    Decoder<float>& input  = *inputData;
+    Encoder<float>& output = *outData;
+
+    for(unsigned int idx = 0; idx < numOutputElements; ++idx)
+    {
+        // Get the coordinates of the current index in vector form. E.g inx 1 = [0, 0, 0, 1 ]
+        const std::vector<unsigned int> coord = IndexToCoord(outputShape, idx);
+
+        std::vector<unsigned int> dimensions;
+        std::vector<unsigned int> coords;
+
+        for(unsigned int i = 0; i < numInputDimensions; ++i)
+        {
+            dimensions.emplace_back(i);
+            coords.emplace_back(coord[i]);
+        }
+
+        auto isInPadding = [&](unsigned int i)
+        {
+            return (coords[i] < padList[i].first || coords[i] > inputShape[i] + padList[i].first - 1);
+        };
+
+        auto getReflectIndex = [&](unsigned int i) -> unsigned int
+        {
+            if(isInPadding(i))
+            {
+                if(coords[i] < padList[i].first)
+                {
+                    return padList[i].first - coords[i];
+                }
+                else
+                {
+                    return 2 * inputShape[i] + padList[i].first - 2 - coords[i];
+                }
+            }
+            return coords[i] - padList[i].first;
+        };
+
+        auto getSymmetricIndex = [&](unsigned int i) -> unsigned int
+        {
+            if(isInPadding(i))
+            {
+                if(coords[i] < padList[i].first)
+                {
+                    return padList[i].first - coords[i] - 1;
+                }
+                else
+                {
+                    return 2 * inputShape[i] + padList[i].first - 1 - coords[i];
+                }
+            }
+            return coords[i] - padList[i].first;
+        };
+
+        // Location of the value in the input tensor to use in the output.
+        std::vector<unsigned int> coordOfInput;
+
+        // any_of works as a loop here to check if any of the dimensions are in the padding.
+        // If dimensions is in the padding area, then create the coordinates of the location in the
+        // input tensor to use in the output.
+        // E.g.
+        // Input tensor = [ 1, 2, 3 ], Rank = 1.
+        // Output tensor = [ 2, 1, 2, 3, 1 ] if Reflect or [ 1, 1, 2, 3, 3 ] if Symmetric with a padding of (1, 1).
+        // So it will either return [ 1 ] or [ 0 ] which is used to set the first value in the output tensor and so on.
+        if(std::any_of(dimensions.begin(), dimensions.end(), isInPadding))
+        {
+            switch(paddingMode)
+            {
+                case PaddingMode::Reflect:
+                {
+                    for(unsigned int i = 0; i < numInputDimensions; ++i)
+                    {
+                        coordOfInput.emplace_back(getReflectIndex(i));
+                    }
+                    break;
+                }
+                case PaddingMode::Symmetric:
+                {
+                    for(unsigned int i = 0; i < numInputDimensions; ++i)
+                    {
+                        coordOfInput.emplace_back(getSymmetricIndex(i));
+                    }
+                    break;
+                }
+                default:
+                    throw InvalidArgumentException("Padding mode not supported.");
+                    break;
+            }
+        }
+        else
+        {
+            for(unsigned int i = 0; i < numInputDimensions; ++i)
+            {
+                coordOfInput.emplace_back(coord[i] - padList[i].first);
+            }
+        }
+
+        // Set output value using the coordinate of the input value to use.
+        const unsigned int indexOfInput = CoordToIndex(inputShape, coordOfInput);
+
+        input[indexOfInput];
+        auto inputValue = input.Get();
+
+        output[idx];
+        output.Set(inputValue);
+    }
+}
+
+} //namespace armnn
\ No newline at end of file
diff --git a/src/backends/reference/workloads/MirrorPad.hpp b/src/backends/reference/workloads/MirrorPad.hpp
new file mode 100644
index 0000000..3deaf1d
--- /dev/null
+++ b/src/backends/reference/workloads/MirrorPad.hpp
@@ -0,0 +1,22 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "armnn/Tensor.hpp"
+
+#include <backendsCommon/Workload.hpp>
+#include <backendsCommon/WorkloadData.hpp>
+
+namespace armnn
+{
+
+void MirrorPad(const TensorInfo& inputInfo,
+               const TensorInfo& outputInfo,
+               const ITensorHandle* inputHandle,
+               ITensorHandle* outputHandle,
+               const PadQueueDescriptor& data);
+
+} //namespace armnn
diff --git a/src/backends/reference/workloads/RefPadWorkload.cpp b/src/backends/reference/workloads/RefPadWorkload.cpp
index f15306d..fd0728c 100644
--- a/src/backends/reference/workloads/RefPadWorkload.cpp
+++ b/src/backends/reference/workloads/RefPadWorkload.cpp
@@ -5,6 +5,7 @@
 
 #include "RefPadWorkload.hpp"
 
+#include "MirrorPad.hpp"
 #include "Pad.hpp"
 #include "Profiling.hpp"
 #include "RefWorkloadUtils.hpp"
@@ -29,11 +30,19 @@
     const TensorInfo& inputInfo  = GetTensorInfo(inputs[0]);
     const TensorInfo& outputInfo = GetTensorInfo(outputs[0]);
 
-    armnn::Pad(inputInfo,
-               outputInfo,
-               inputs[0],
-               outputs[0],
-               m_Data);
+    PaddingMode paddingMode = m_Data.m_Parameters.m_PaddingMode;
+    if (paddingMode == PaddingMode::Constant)
+    {
+        armnn::Pad(inputInfo, outputInfo, inputs[0], outputs[0], m_Data);
+    }
+    else if(paddingMode == PaddingMode::Reflect || paddingMode == PaddingMode::Symmetric)
+    {
+        armnn::MirrorPad(inputInfo, outputInfo, inputs[0], outputs[0], m_Data);
+    }
+    else
+    {
+        throw InvalidArgumentException("Padding mode not supported.");
+    }
 }
 
 } //namespace armnn
\ No newline at end of file