IVGCVSW-6469 Add MirrorPad TfLiteParser and TfLiteDelegate Support

Signed-off-by: Matthew Sloyan <matthew.sloyan@arm.com>
Change-Id: Ia1c97adb401c5381341408ec1e4da287ef2d48fe
diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt
index 0178594..54ddd61 100644
--- a/delegate/CMakeLists.txt
+++ b/delegate/CMakeLists.txt
@@ -160,6 +160,7 @@
         src/test/LogicalTestHelper.hpp
         src/test/LstmTest.cpp
         src/test/LstmTestHelper.hpp
+        src/test/MirrorPadTest.cpp
         src/test/NormalizationTest.cpp
         src/test/NormalizationTestHelper.hpp
         src/test/PackTest.cpp
diff --git a/delegate/src/Pad.hpp b/delegate/src/Pad.hpp
index 431b8d3..78e0776 100644
--- a/delegate/src/Pad.hpp
+++ b/delegate/src/Pad.hpp
@@ -23,6 +23,7 @@
 
     switch(tfLitePadOperatorCode)
     {
+        case kTfLiteBuiltinMirrorPad:
         case kTfLiteBuiltinPad:
             TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 2, nodeIndex));
             break;
@@ -106,6 +107,47 @@
                 return kTfLiteError;
         }
     }
+    else if (tfLitePadOperatorCode == kTfLiteBuiltinMirrorPad)
+    {
+        TfLiteMirrorPaddingParams* options = reinterpret_cast<TfLiteMirrorPaddingParams*>(tfLiteNode->builtin_data);
+
+
+        if (options->mode == TfLiteMirrorPaddingMode::kTfLiteMirrorPaddingReflect)
+        {
+            descriptor.m_PaddingMode = armnn::PaddingMode::Reflect;
+        }
+        else if (options->mode == TfLiteMirrorPaddingMode::kTfLiteMirrorPaddingSymmetric)
+        {
+            descriptor.m_PaddingMode = armnn::PaddingMode::Symmetric;
+        }
+        else
+        {
+            TF_LITE_MAYBE_KERNEL_LOG(
+                tfLiteContext,
+                "TfLiteArmnnDelegate: PaddingMode must be either REFLECT or SYMMETRIC in operator #%d node #%d: ",
+                tfLitePadOperatorCode, nodeIndex);
+        }
+
+        // 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).
+        auto inputShape = inputTensorInfo.GetShape();
+        auto padList = descriptor.m_PadList;
+
+        const unsigned int isReflect =
+                static_cast<unsigned int>(descriptor.m_PaddingMode == armnn::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))
+            {
+                TF_LITE_MAYBE_KERNEL_LOG(
+                        tfLiteContext,
+                        "TfLiteArmnnDelegate: Padding values must be less (Reflect) or "
+                        "equal (Symmetric) to the dimension size in operator #%d node #%d: ",
+                        tfLitePadOperatorCode, nodeIndex);
+            }
+        }
+    }
 
     if (!delegateData.m_Network)
     {
diff --git a/delegate/src/armnn_delegate.cpp b/delegate/src/armnn_delegate.cpp
index e029e2c..ae25430 100644
--- a/delegate/src/armnn_delegate.cpp
+++ b/delegate/src/armnn_delegate.cpp
@@ -725,6 +725,12 @@
                                                   tfLiteNode,
                                                   nodeIndex,
                                                   kTfLiteBuiltinMinimum);
+        case kTfLiteBuiltinMirrorPad:
+            return VisitPadOperator(delegateData,
+                                    tfLiteContext,
+                                    tfLiteNode,
+                                    nodeIndex,
+                                    kTfLiteBuiltinMirrorPad);
         case kTfLiteBuiltinMul:
             return VisitElementwiseBinaryOperator(delegateData,
                                                   tfLiteContext,
diff --git a/delegate/src/test/MirrorPadTest.cpp b/delegate/src/test/MirrorPadTest.cpp
new file mode 100644
index 0000000..ca66181
--- /dev/null
+++ b/delegate/src/test/MirrorPadTest.cpp
@@ -0,0 +1,341 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "PadTestHelper.hpp"
+
+#include <armnn_delegate.hpp>
+
+#include <flatbuffers/flatbuffers.h>
+#include <tensorflow/lite/schema/schema_generated.h>
+
+#include <doctest/doctest.h>
+
+namespace armnnDelegate
+{
+
+void MirrorPadSymmetric2dTest(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 3, 3 };
+    std::vector<int32_t> outputShape { 7, 7 };
+    std::vector<int32_t> paddingShape { 2, 2 };
+
+    std::vector<float> inputValues =
+    {
+        1.0f, 2.0f, 3.0f,
+        4.0f, 5.0f, 6.0f,
+        7.0f, 8.0f, 9.0f
+    };
+
+    std::vector<float> expectedOutputValues =
+    {
+        5.0f, 4.0f, 4.0f, 5.0f, 6.0f, 6.0f, 5.0f,
+        2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 3.0f, 2.0f,
+        2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 3.0f, 2.0f,
+        5.0f, 4.0f, 4.0f, 5.0f, 6.0f, 6.0f, 5.0f,
+        8.0f, 7.0f, 7.0f, 8.0f, 9.0f, 9.0f, 8.0f,
+        8.0f, 7.0f, 7.0f, 8.0f, 9.0f, 9.0f, 8.0f,
+        5.0f, 4.0f, 4.0f, 5.0f, 6.0f, 6.0f, 5.0f
+    };
+
+    std::vector<int32_t> paddingDim = { 2, 2, 2, 2 };
+
+    PadTest<float>(tflite::BuiltinOperator_MIRROR_PAD,
+                   ::tflite::TensorType_FLOAT32,
+                   backends,
+                   inputShape,
+                   paddingShape,
+                   outputShape,
+                   inputValues,
+                   paddingDim,
+                   expectedOutputValues,
+                   0,    // Padding value - Not used in these tests.
+                   1.0f, // Scale
+                   0,    // Offset
+                   tflite::MirrorPadMode_SYMMETRIC);
+}
+
+void MirrorPadReflect2dTest(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 3, 3 };
+    std::vector<int32_t> outputShape { 7, 7 };
+    std::vector<int32_t> paddingShape { 2, 2 };
+
+    std::vector<float> inputValues =
+    {
+        1.0f, 2.0f, 3.0f,
+        4.0f, 5.0f, 6.0f,
+        7.0f, 8.0f, 9.0f
+    };
+
+    std::vector<float> expectedOutputValues =
+    {
+        9.0f, 8.0f, 7.0f, 8.0f, 9.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 4.0f, 5.0f, 6.0f, 5.0f, 4.0f,
+        3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f,
+        6.0f, 5.0f, 4.0f, 5.0f, 6.0f, 5.0f, 4.0f,
+        9.0f, 8.0f, 7.0f, 8.0f, 9.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 4.0f, 5.0f, 6.0f, 5.0f, 4.0f,
+        3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f
+    };
+
+    std::vector<int32_t> paddingDim = { 2, 2, 2, 2 };
+
+    PadTest<float>(tflite::BuiltinOperator_MIRROR_PAD,
+                   ::tflite::TensorType_FLOAT32,
+                   backends,
+                   inputShape,
+                   paddingShape,
+                   outputShape,
+                   inputValues,
+                   paddingDim,
+                   expectedOutputValues,
+                   0,    // Padding value - Not used in these tests.
+                   1.0f, // Scale
+                   0,    // Offset
+                   tflite::MirrorPadMode_REFLECT);
+}
+
+void MirrorPadSymmetric3dTest(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 2, 2, 2 };
+    std::vector<int32_t> outputShape { 4, 4, 4 };
+    std::vector<int32_t> paddingShape { 3, 2 };
+
+    std::vector<float> inputValues =
+    {
+        // Channel 0, Height (2) x Width (2)
+        1.0f, 2.0f,
+        3.0f, 4.0f,
+
+        // Channel 1, Height (2) x Width (2)
+        5.0f, 6.0f,
+        7.0f, 8.0f
+    };
+
+    std::vector<float> expectedOutputValues =
+    {
+        1.0f, 1.0f, 2.0f, 2.0f,
+        1.0f, 1.0f, 2.0f, 2.0f,
+        3.0f, 3.0f, 4.0f, 4.0f,
+        3.0f, 3.0f, 4.0f, 4.0f,
+
+        1.0f, 1.0f, 2.0f, 2.0f,
+        1.0f, 1.0f, 2.0f, 2.0f,
+        3.0f, 3.0f, 4.0f, 4.0f,
+        3.0f, 3.0f, 4.0f, 4.0f,
+
+        5.0f, 5.0f, 6.0f, 6.0f,
+        5.0f, 5.0f, 6.0f, 6.0f,
+        7.0f, 7.0f, 8.0f, 8.0f,
+        7.0f, 7.0f, 8.0f, 8.0f,
+
+        5.0f, 5.0f, 6.0f, 6.0f,
+        5.0f, 5.0f, 6.0f, 6.0f,
+        7.0f, 7.0f, 8.0f, 8.0f,
+        7.0f, 7.0f, 8.0f, 8.0f
+    };
+
+    std::vector<int32_t> paddingDim = { 1, 1, 1, 1, 1, 1 };
+
+    PadTest<float>(tflite::BuiltinOperator_MIRROR_PAD,
+                   ::tflite::TensorType_FLOAT32,
+                   backends,
+                   inputShape,
+                   paddingShape,
+                   outputShape,
+                   inputValues,
+                   paddingDim,
+                   expectedOutputValues,
+                   0,    // Padding value - Not used in these tests.
+                   1.0f, // Scale
+                   0,    // Offset
+                   tflite::MirrorPadMode_SYMMETRIC);
+}
+
+void MirrorPadReflect3dTest(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 2, 2, 2 };
+    std::vector<int32_t> outputShape { 4, 4, 4 };
+    std::vector<int32_t> paddingShape { 3, 2 };
+
+    std::vector<float> inputValues =
+    {
+        // Channel 0, Height (2) x Width (2)
+        1.0f, 2.0f,
+        3.0f, 4.0f,
+
+        // Channel 1, Height (2) x Width (2)
+        5.0f, 6.0f,
+        7.0f, 8.0f
+    };
+
+    std::vector<float> expectedOutputValues =
+    {
+        8.0f, 7.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 6.0f, 5.0f,
+        8.0f, 7.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 6.0f, 5.0f,
+
+        4.0f, 3.0f, 4.0f, 3.0f,
+        2.0f, 1.0f, 2.0f, 1.0f,
+        4.0f, 3.0f, 4.0f, 3.0f,
+        2.0f, 1.0f, 2.0f, 1.0f,
+
+        8.0f, 7.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 6.0f, 5.0f,
+        8.0f, 7.0f, 8.0f, 7.0f,
+        6.0f, 5.0f, 6.0f, 5.0f,
+
+        4.0f, 3.0f, 4.0f, 3.0f,
+        2.0f, 1.0f, 2.0f, 1.0f,
+        4.0f, 3.0f, 4.0f, 3.0f,
+        2.0f, 1.0f, 2.0f, 1.0f
+    };
+
+    std::vector<int32_t> paddingDim = { 1, 1, 1, 1, 1, 1 };
+
+    PadTest<float>(tflite::BuiltinOperator_MIRROR_PAD,
+                   ::tflite::TensorType_FLOAT32,
+                   backends,
+                   inputShape,
+                   paddingShape,
+                   outputShape,
+                   inputValues,
+                   paddingDim,
+                   expectedOutputValues,
+                   0,    // Padding value - Not used in these tests.
+                   1.0f, // Scale
+                   0,    // Offset
+                   tflite::MirrorPadMode_REFLECT);
+}
+
+void MirrorPadSymmetricUint8Test(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 3, 3 };
+    std::vector<int32_t> outputShape { 5, 7 };
+    std::vector<int32_t> paddingShape { 2, 2 };
+
+    std::vector<uint8_t> inputValues =
+    {
+        1, 2, 3,
+        4, 5, 6,
+        7, 8, 9
+    };
+
+    std::vector<uint8_t> expectedOutputValues =
+    {
+        2, 1, 1, 2, 3, 3, 2,
+        2, 1, 1, 2, 3, 3, 2,
+        5, 4, 4, 5, 6, 6, 5,
+        8, 7, 7, 8, 9, 9, 8,
+        8, 7, 7, 8, 9, 9, 8,
+    };
+
+    std::vector<int32_t> paddingDim = { 1, 1, 2, 2 };
+
+    PadTest<uint8_t>(tflite::BuiltinOperator_MIRROR_PAD,
+                     ::tflite::TensorType_UINT8,
+                     backends,
+                     inputShape,
+                     paddingShape,
+                     outputShape,
+                     inputValues,
+                     paddingDim,
+                     expectedOutputValues,
+                     0,    // Padding value - Not used in these tests.
+                     1.0f, // Scale
+                     1,    // Offset
+                     tflite::MirrorPadMode_SYMMETRIC);
+}
+
+void MirrorPadReflectInt8Test(std::vector<armnn::BackendId>& backends)
+{
+    // Set input data
+    std::vector<int32_t> inputShape { 3, 3 };
+    std::vector<int32_t> outputShape { 7, 5 };
+    std::vector<int32_t> paddingShape { 2, 2 };
+
+    std::vector<int8_t> inputValues =
+    {
+        1, 2, 3,
+        4, 5, 6,
+        7, 8, 9
+    };
+
+    std::vector<int8_t> expectedOutputValues =
+    {
+        8, 7, 8, 9, 8,
+        5, 4, 5, 6, 5,
+        2, 1, 2, 3, 2,
+        5, 4, 5, 6, 5,
+        8, 7, 8, 9, 8,
+        5, 4, 5, 6, 5,
+        2, 1, 2, 3, 2
+    };
+
+    std::vector<int32_t> paddingDim = { 2, 2, 1, 1 };
+
+    PadTest<int8_t>(tflite::BuiltinOperator_MIRROR_PAD,
+                    ::tflite::TensorType_INT8,
+                    backends,
+                    inputShape,
+                    paddingShape,
+                    outputShape,
+                    inputValues,
+                    paddingDim,
+                    expectedOutputValues,
+                    0,    // Padding value - Not used in these tests.
+                    1.0f, // Scale
+                    1,    // Offset
+                    tflite::MirrorPadMode_REFLECT);
+}
+
+TEST_SUITE("MirrorPad_CpuRefTests")
+{
+
+TEST_CASE ("MirrorPadSymmetric2d_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadSymmetric2dTest(backends);
+}
+
+TEST_CASE ("MirrorPadReflect2d_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadReflect2dTest(backends);
+}
+
+TEST_CASE ("MirrorPadSymmetric3d_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadSymmetric3dTest(backends);
+}
+
+TEST_CASE ("MirrorPadReflect3d_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadReflect3dTest(backends);
+}
+
+TEST_CASE ("MirrorPadSymmetricUint8_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadSymmetricUint8Test(backends);
+}
+
+TEST_CASE ("MirrorPadSymmetricInt8_CpuRef_Test")
+{
+    std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef };
+    MirrorPadReflectInt8Test(backends);
+}
+
+} // TEST_SUITE("MirrorPad_CpuRefTests")
+
+} // namespace armnnDelegate
\ No newline at end of file
diff --git a/delegate/src/test/PadTestHelper.hpp b/delegate/src/test/PadTestHelper.hpp
index 3d6e649..5b9a1bc 100644
--- a/delegate/src/test/PadTestHelper.hpp
+++ b/delegate/src/test/PadTestHelper.hpp
@@ -25,6 +25,7 @@
 std::vector<char> CreatePadTfLiteModel(
     tflite::BuiltinOperator padOperatorCode,
     tflite::TensorType tensorType,
+    tflite::MirrorPadMode paddingMode,
     const std::vector<int32_t>& inputTensorShape,
     const std::vector<int32_t>& paddingTensorShape,
     const std::vector<int32_t>& outputTensorShape,
@@ -87,7 +88,14 @@
         operatorInputs = {{ 0, 1 }};
         subgraphInputs = {{ 0, 1 }};
         operatorBuiltinOptions = CreatePadOptions(flatBufferBuilder).Union();
+    }
+    else if(padOperatorCode == tflite::BuiltinOperator_MIRROR_PAD)
+    {
+        operatorInputs = {{ 0, 1 }};
+        subgraphInputs = {{ 0, 1 }};
 
+        operatorBuiltinOptionsType = BuiltinOptions_MirrorPadOptions;
+        operatorBuiltinOptions = CreateMirrorPadOptions(flatBufferBuilder, paddingMode).Union();
     }
     else if (padOperatorCode == tflite::BuiltinOperator_PADV2)
     {
@@ -116,7 +124,7 @@
 
     // create operator
     const std::vector<int32_t> operatorOutputs{ 2 };
-    flatbuffers::Offset <Operator> redefineOperator =
+    flatbuffers::Offset <Operator> paddingOperator =
         CreateOperator(flatBufferBuilder,
                        0,
                        flatBufferBuilder.CreateVector<int32_t>(operatorInputs.data(), operatorInputs.size()),
@@ -130,7 +138,7 @@
                        flatBufferBuilder.CreateVector(tensors.data(), tensors.size()),
                        flatBufferBuilder.CreateVector<int32_t>(subgraphInputs.data(), subgraphInputs.size()),
                        flatBufferBuilder.CreateVector<int32_t>(subgraphOutputs.data(), subgraphOutputs.size()),
-                       flatBufferBuilder.CreateVector(&redefineOperator, 1));
+                       flatBufferBuilder.CreateVector(&paddingOperator, 1));
 
     flatbuffers::Offset <flatbuffers::String> modelDescription =
         flatBufferBuilder.CreateString("ArmnnDelegate: Pad Operator Model");
@@ -163,11 +171,13 @@
              std::vector<T>& expectedOutputValues,
              T paddingValue,
              float quantScale = 1.0f,
-             int quantOffset  = 0)
+             int quantOffset  = 0,
+             tflite::MirrorPadMode paddingMode = tflite::MirrorPadMode_SYMMETRIC)
 {
     using namespace tflite;
     std::vector<char> modelBuffer = CreatePadTfLiteModel<T>(padOperatorCode,
                                                             tensorType,
+                                                            paddingMode,
                                                             inputShape,
                                                             paddingShape,
                                                             outputShape,