tools/converter/source/optimizer/onnxextra/OnnxUpsample.cpp (170 lines of code) (raw):
//
// OnnxUpsample.cpp
// MNNConverter
//
// Created by MNN on 2019/10/24.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "MNN_generated.h"
#include "OnnxExtraManager.hpp"
namespace MNN {
namespace Express {
class OnnxUpSampleTransform : public OnnxExtraManager::Transform {
public:
virtual EXPRP onExecute(EXPRP expr) const override {
auto inputs = expr->inputs();
std::vector<float> scales;
int scalesSize = 1;
auto op = expr->get();
auto extraParam = op->main_as_Extra();
const int attrSize = extraParam->attr()->size();
std::string interpMode;
std::string coordMode = ""; // detect align_corner attribute
for (int i = 0; i < attrSize; ++i) {
auto attr = extraParam->attr()->GetAs<Attribute>(i);
const auto& key = attr->key()->str();
if (key == "mode") {
interpMode = attr->s()->str();
} else if ((inputs.size() == 1) && key == "scales") {
scalesSize = attr->list()->f()->size();
scales.resize(scalesSize);
memcpy(scales.data(), attr->list()->f()->data(), sizeof(float) * scalesSize);
} else if (key == "coordinate_transformation_mode") {
coordMode = attr->s()->str();
}
}
std::unique_ptr<OpT> mergeredUpsample(new OpT);
mergeredUpsample->name = expr->name();
mergeredUpsample->type = OpType_Interp;
mergeredUpsample->main.type = OpParameter_Interp;
std::unique_ptr<InterpT> interpParam(new InterpT);
const float* scaleDataPtr = scales.data();
if (inputs.size() == 2) {
auto scale = inputs[1];
scaleDataPtr = scale->readMap<float>();
auto scaleInfo = scale->getInfo();
if (!scaleDataPtr) {
mergeredUpsample->main.value = interpParam.release();
auto output = Variable::create(Expr::create(mergeredUpsample.get(), {inputs[0], inputs[1]}));
return output->expr().first;
}
// scale is constant node
scalesSize = scaleInfo->size;
}
interpParam->widthScale = 1.0f;
interpParam->heightScale = 1.0f;
if (scalesSize >= 2 && scalesSize <= 4) {
MNN_THROW_CHECK(scaleDataPtr[1] == 1.0f, "MNN NOT SUPPORT Upsamle along with channle");
if (scalesSize >= 3) {
interpParam->heightScale = scaleDataPtr[2];
}
if (scalesSize == 4){
interpParam->widthScale = scaleDataPtr[3];
}
} else {
MNN_ERROR("MNN Not support Upsample when scale size = %d\n", scalesSize);
}
interpParam->alignCorners = (coordMode == "align_corners");
// 1:near 2: bilinear 3: cubic
if (interpMode == "nearest") {
interpParam->resizeType = 1;
} else if (interpMode == "bilinear" || interpMode == "linear") {
interpParam->resizeType = 2;
} else if (interpMode == "cubic") {
interpParam->resizeType = 3;
} else {
MNN_ERROR("Unsupported Upsample mode! ==> %s\n", interpMode.c_str());
}
mergeredUpsample->main.value = interpParam.release();
auto newInput = inputs[0];
auto tempOutput = Variable::create(Expr::create(mergeredUpsample.get(), {newInput}));
tempOutput->setName(expr->name());
auto output = tempOutput;
return output->expr().first;
}
};
class OnnxReiszeTransform : public OnnxExtraManager::Transform {
public:
virtual EXPRP onExecute(EXPRP expr) const override {
auto inputs = expr->inputs();
// input, roi, scales, sizes
// for more information, please reference from https://github.com/onnx/onnx/blob/master/docs/Operators.md#Resize
MNN_THROW_CHECK((inputs.size() >= 2), "Onnx Resize should have 2/3/4 inputs!");
std::string resizeMode = "";
std::string coordMode = "half_pixel"; // detect align_corner attribute
std::string nearestMode = "round_prefer_floor";
auto op = expr->get();
auto extraParam = op->main_as_Extra();
const int attrSize = extraParam->attr()->size();
float cubicFactor = -0.75f;
for (int i = 0; i < attrSize; ++i) {
auto attr = extraParam->attr()->GetAs<Attribute>(i);
const auto& key = attr->key()->str();
if (key == "mode") {
resizeMode = attr->s()->str();
} else if (key == "coordinate_transformation_mode") {
coordMode = attr->s()->str();
} else if (key == "nearest_mode") {
nearestMode = attr->s()->str();
} else if (key == "cubic_coeff_a") {
cubicFactor = attr->f();
}
}
std::unique_ptr<OpT> mergeredResize(new OpT);
mergeredResize->type = OpType_Interp;
mergeredResize->main.type = OpParameter_Interp;
std::unique_ptr<InterpT> resizeParam(new InterpT);
// 1:near 2: bilinear 3: cubic
if (resizeMode == "nearest") {
if (nearestMode == "round_prefer_floor") {
resizeParam->resizeType = 4;
} else if (nearestMode == "floor") {
resizeParam->resizeType = 1;
} else {
MNN_ERROR("Don't support %s neareset mode, use round_prefer_floor instead\n", nearestMode.c_str());
resizeParam->resizeType = 4;
}
} else if (resizeMode == "bilinear" || resizeMode == "linear") {
resizeParam->resizeType = 2;
} else if (resizeMode == "cubic") {
resizeParam->resizeType = 3;
resizeParam->cubicCoeffA = cubicFactor;
} else {
MNN_ERROR("Unsupported Upsample mode! ==> %s, use bilinear instead\n", resizeMode.c_str());
resizeParam->resizeType = 2;
}
// For compability of old mnn
resizeParam->alignCorners = (coordMode == "align_corners");
resizeParam->halfPixelCenters = (coordMode == "half_pixel");
/*
coordinate_transformation_mode: string
This attribute describes how to transform the coordinate in the resized tensor to the coordinate in the original
tensor.
The coordinate of each dimension is transformed individually. Let's describe a case using axis x as an example.
Denote x_resized as the coordinate of axis x in the resized tensor, x_original as the coordinate of axis x in
the original tensor, length_original as the length of the original tensor in axis x, length_resized as the
length of the resized tensor in axis x, roi_x = (start_x, end_x) of the axis x in input "roi", scale =
length_resized / length_original,
if coordinate_transformation_mode is "half_pixel",
x_original = (x_resized + 0.5) / scale - 0.5,
if coordinate_transformation_mode is "pytorch_half_pixel",
x_original = length_resized > 1 ? (x_resized + 0.5) / scale - 0.5 : 0,
if coordinate_transformation_mode is "align_corners",
x_original = x_resized * (length_original - 1) / (length_resized - 1),
if coordinate_transformation_mode is "asymmetric",
x_original = x_resized / scale,
if coordinate_transformation_mode is "tf_half_pixel_for_nn",
x_original = (x_resized + 0.5) / scale,
if coordinate_transformation_mode is "tf_crop_and_resize",
x_original = length_resized > 1 ? start_x * (length_original - 1) + x_resized * (end_x - start_x) *
(length_original - 1) / (length_resized - 1) : 0.5 * (start_x + end_x) * (length_original - 1).
*/
#define SET_MODE(str, c) \
if (coordMode == str) \
resizeParam->ctm = MNN::CoordinateTransformationMode_##c
SET_MODE("align_corners", AlignCorners);
SET_MODE("half_pixel", HalfPixels);
SET_MODE("pytorch_half_pixel", PytorchHalfPixels);
SET_MODE("tf_half_pixel_for_nn", TensorflowHalfPixels);
SET_MODE("tf_crop_and_resize", TensorflowCropAndResize);
SET_MODE("asymmetric", Asymmetric);
#undef SET_MODE
VARP output;
if (inputs.size() == 2) {
mergeredResize->main.value = resizeParam.release();
auto output = Variable::create(Expr::create(mergeredResize.get(), {inputs[0], inputs[1]}));
output->setName(expr->name());
return output->expr().first;
}
if (inputs.size() == 3) {
auto scaleT = inputs[2];
// Compute shape dynamic
mergeredResize->main.value = resizeParam.release();
auto resizeExpr = Expr::create(mergeredResize.get(), {inputs[0], {inputs[2]}});
resizeExpr->setName(expr->name());
output = Variable::create(resizeExpr);
return output->expr().first;
}
if (inputs.size() == 4) {
auto sizes = inputs[3];
auto name = sizes->name();
mergeredResize->main.value = resizeParam.release();
auto resizeExpr = Expr::create(mergeredResize.get(), {inputs[0], inputs[3]});
resizeExpr->setName(expr->name());
output = Variable::create(resizeExpr);
return output->expr().first;
}
return output->expr().first;
}
};
static auto gRigister = []() {
OnnxExtraManager::get()->insert("Upsample",
std::shared_ptr<OnnxExtraManager::Transform>(new OnnxUpSampleTransform));
OnnxExtraManager::get()->insert("Resize", std::shared_ptr<OnnxExtraManager::Transform>(new OnnxReiszeTransform));
return true;
}();
} // namespace Express
} // namespace MNN