// // 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 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 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& 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(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(inputInfo, inputHandle->Map()); auto outData = MakeEncoder(outputInfo, outputHandle->Map()); Decoder& input = *inputData; Encoder& 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 coord = IndexToCoord(outputShape, idx); std::vector dimensions; std::vector 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 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