blob: 7388fed147667040c1234d973d73f5510c30921b [file] [log] [blame]
//
// 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