[OV2.0] Leftovers for Preprocessing/input/output (#7893)

* Pre-process:
- Implicit conversions for element type and layout
- 'convert_element_type' with default argument to network
- Convert_element_type - don't add ops if dst and src types are same
- Convert_layout - don't add ops if dst and src layouts are same
- Custom step - use Output<Node> instead of shared_ptr<Node>
- Support of addressing input by tensor name

Post-process:
- Avoid duplication of tensor names after post-processing

* Fixed IE tests
This commit is contained in:
Mikhail Nosov 2021-10-08 12:58:29 +03:00 committed by GitHub
parent d8c6591249
commit 2592856200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 477 additions and 325 deletions

View File

@ -181,9 +181,9 @@ static RefPreprocessParams custom_preprocessing() {
res.function = []() {
auto f = create_simple_function(element::i32, Shape{1, 3, 1, 1});
f = PrePostProcessor()
.input(InputInfo().preprocess(PreProcessSteps().custom([](const std::shared_ptr<Node>& node) {
.input(InputInfo().preprocess(PreProcessSteps().custom([](const Output<Node>& node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
})))
.build(f);
@ -221,9 +221,9 @@ static RefPreprocessParams test_lvalue() {
preprocessSteps.scale(2.f);
preprocessSteps.mean({1.f, 2.f, 3.f});
preprocessSteps.scale({2.f, 3.f, 4.f});
preprocessSteps.custom([](const std::shared_ptr<Node> &node) {
preprocessSteps.custom([](const Output<Node> &node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
});
auto &same = preprocessSteps.convert_element_type(element::i8);
@ -243,13 +243,22 @@ static RefPreprocessParams test_2_inputs_basic() {
RefPreprocessParams res("test_2_inputs_basic");
res.function = []() {
auto f = create_2inputs(element::f32, Shape{1, 3, 1, 1});
{ f = PrePostProcessor().input(InputInfo(1).preprocess(PreProcessSteps().mean(1.f).scale(2.0f))).build(f); }
f = PrePostProcessor().input(InputInfo(0)
.preprocess(
PreProcessSteps()
.mean(1.f)))
.input(
InputInfo("tensor_input2")
.preprocess(PreProcessSteps()
.mean(1.f)
.scale(2.0f)))
.build(f);
return f;
};
res.inputs.emplace_back(Shape{1, 3, 1, 1}, element::f32, std::vector<float>{3., 5., 7.});
res.inputs.emplace_back(Shape{1, 3, 1, 1}, element::f32, std::vector<float>{3., 5., 7.});
res.expected.emplace_back(Shape{1, 3, 1, 1}, element::f32, std::vector<float>{3., 5., 7.});
res.expected.emplace_back(Shape{1, 3, 1, 1}, element::f32, std::vector<float>{2., 4., 6.});
res.expected.emplace_back(Shape{1, 3, 1, 1}, element::f32, std::vector<float>{1., 2., 3.});
return res;
}

View File

@ -152,17 +152,17 @@ TEST_F(RTInfoDeserialization, NodeV10) {
param->set_friendly_name("in1");
param->get_output_tensor(0).set_names({"input_tensor"});
// TODO: avoid relying on internal pre-processing implementation (exact operation and exact names)
auto convert_param = std::make_shared<opset8::Convert>(param, ngraph::element::f16);
convert_param->get_output_tensor(0).set_names({"input_tensor/convert_element_type"});
auto round = std::make_shared<opset8::Round>(convert_param,
ngraph::opset8::Round::RoundMode::HALF_TO_EVEN);
round->set_friendly_name("Round");
// TODO: why it has this name?
round->get_output_tensor(0).set_names({"output_tensor"});
round->get_output_tensor(0).set_names({"output_tensor/post_convert_element_type"});
auto convert_result = std::make_shared<opset8::Convert>(round, type);
convert_result->set_friendly_name("Round/convert_element_type");
convert_result->set_friendly_name("Round/post_convert_element_type");
convert_result->get_output_tensor(0).set_names({"output_tensor"});
auto result = std::make_shared<opset8::Result>(convert_result);
@ -310,11 +310,11 @@ TEST_F(RTInfoDeserialization, InputAndOutputV10) {
auto sum = std::make_shared<opset8::Add>(param, param);
sum->set_friendly_name("sum");
// TODO: why it has this name?
sum->get_output_tensor(0).set_names({"output_tensor"});
// TODO: avoid relying on internal post-processing implementation (exact operation and exact names)
sum->get_output_tensor(0).set_names({"output_tensor/post_convert_element_type"});
auto convert_result = std::make_shared<opset8::Convert>(sum, ngraph::element::i32);
convert_result->set_friendly_name("sum/convert_element_type");
convert_result->set_friendly_name("sum/post_convert_element_type");
convert_result->get_output_tensor(0).set_names({"output_tensor"});
auto result = std::make_shared<opset8::Result>(convert_result);
@ -471,6 +471,7 @@ TEST_F(RTInfoDeserialization, NodeV11) {
param->set_friendly_name("in1");
param->get_output_tensor(0).set_names({"input_tensor"});
// TODO: avoid relying on internal pre-processing implementation (exact operations and exact names)
auto convert_param = std::make_shared<opset8::Convert>(param, ngraph::element::f32);
convert_param->set_friendly_name("in1/convert_element_type");
convert_param->get_output_tensor(0).set_names({"input_tensor/convert_element_type"});
@ -484,18 +485,17 @@ TEST_F(RTInfoDeserialization, NodeV11) {
auto round = std::make_shared<opset8::Round>(transpose_param,
ngraph::opset8::Round::RoundMode::HALF_TO_EVEN);
round->set_friendly_name("Round");
// TODO: why it has this name?
round->get_output_tensor(0).set_names({"output_tensor"});
round->get_output_tensor(0).set_names({"output_tensor/post_convert_layout"});
round->get_rt_info()[VariantWrapper<ngraph::FusedNames>::get_type_info_static()] =
std::make_shared<VariantWrapper<ngraph::FusedNames>>(ngraph::FusedNames("Round1,Round2"));
auto constant_result = std::make_shared<opset8::Constant>(ngraph::element::i64, ngraph::Shape{4},
std::vector<int64_t>{0, 3, 1, 2});
auto transpose_result = std::make_shared<opset8::Transpose>(round, constant_result);
transpose_result->set_friendly_name("Round/convert_layout");
transpose_result->set_friendly_name("Round/post_convert_layout");
auto convert_result = std::make_shared<opset8::Convert>(transpose_result, type);
convert_result->set_friendly_name("Round/convert_layout/convert_element_type");
convert_result->set_friendly_name("Round/post_convert_layout/post_convert_element_type");
convert_result->get_output_tensor(0).set_names({"output_tensor"});
auto result = std::make_shared<opset8::Result>(convert_result);

View File

@ -130,9 +130,9 @@ inline std::shared_ptr<Function> custom_preprocessing() {
using namespace ov::preprocess;
auto function = create_preprocess_1input(element::i32, Shape{3, 4, 10, 20});
function = PrePostProcessor()
.input(InputInfo().preprocess(PreProcessSteps().custom([](const std::shared_ptr<Node>& node) {
.input(InputInfo().preprocess(PreProcessSteps().custom([](const Output<Node>& node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
})))
.build(function);
@ -164,9 +164,9 @@ inline std::shared_ptr<Function> lvalues_multiple_ops() {
preprocessSteps.scale(2.f);
preprocessSteps.mean({1.1f, 2.2f, 3.3f});
preprocessSteps.scale({2.f, 3.f, 4.f});
preprocessSteps.custom([](const std::shared_ptr<Node>& node) {
preprocessSteps.custom([](const Output<Node>& node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
});
auto& same = preprocessSteps.convert_element_type(element::u8);

View File

@ -31,10 +31,15 @@ public:
/// \brief Empty constructor. Should be used only if network will have only one input
InputInfo();
/// \brief Information about info for particular input index of model
/// \brief Constructor for particular input index of model
///
/// \param input_index Index to address specified input parameter of model
InputInfo(size_t input_index);
explicit InputInfo(size_t input_index);
/// \brief Constructor for particular output of model addressed by it's input name
///
/// \param input_tensor_name Name of input tensor name
explicit InputInfo(const std::string& input_tensor_name);
/// \brief Default move constructor
InputInfo(InputInfo&&) noexcept;

View File

@ -34,12 +34,12 @@ public:
/// \brief Constructor for particular output index of model
///
/// \param output_index Index to address specified output parameter of model
OutputInfo(size_t output_index);
explicit OutputInfo(size_t output_index);
/// \brief Constructor for particular output of model addressed by it's output name
///
/// \param output_tensor_name Name of output tensor name
OutputInfo(const std::string& output_tensor_name);
explicit OutputInfo(const std::string& output_tensor_name);
/// \brief Default move constructor
OutputInfo(OutputInfo&&) noexcept;

View File

@ -48,14 +48,14 @@ public:
/// \param type Desired type of input.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
PreProcessSteps& convert_element_type(const ov::element::Type& type) &;
PreProcessSteps& convert_element_type(const ov::element::Type& type = {}) &;
/// \brief Add convert element type preprocess operation - Rvalue version
///
/// \param type Desired type of input.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
PreProcessSteps&& convert_element_type(const ov::element::Type& type) &&;
PreProcessSteps&& convert_element_type(const ov::element::Type& type = {}) &&;
/// \brief Converts color format for user's input tensor. Requires source color format to be specified by
/// InputTensorInfo::set_color_format.
@ -141,10 +141,10 @@ public:
/// produces one output node. For more advanced cases, client's code can use transformation passes over ov::Function
/// directly
///
/// \param node Input node for custom preprocessing operation
/// \param node Input node for custom preprocessing operation (output of previous preprocessing operation)
///
/// \return New node after applying custom preprocessing operation
using CustomPreprocessOp = std::function<std::shared_ptr<ov::Node>(const std::shared_ptr<ov::Node>& node)>;
using CustomPreprocessOp = std::function<Output<Node>(const Output<Node>& node)>;
/// \brief Add custom preprocess operation - Lvalue version
/// Client application can specify callback function for custom action

View File

@ -243,6 +243,9 @@ namespace layout {
std::vector<int64_t> find_permutation(const Layout& src_layout, const Rank& rank, const Layout& dst) {
// Basic implementation so far, can support partially-specified layouts later (shape rank will be needed for dynamic
// layouts)
if (src_layout == dst) {
return {}; // No permutation is needed
}
OPENVINO_ASSERT(!src_layout.m_dynamic && !dst.m_dynamic, "Conversion is not supported for dynamic layouts");
OPENVINO_ASSERT(src_layout.m_left_size == src_layout.m_left_size,
"Conversion is not supported for layouts with different sizes");

View File

@ -155,11 +155,16 @@ class OutputNetworkInfo::OutputNetworkInfoImpl : public NetworkInfoImpl {};
struct InputInfo::InputInfoImpl {
InputInfoImpl() = default;
explicit InputInfoImpl(size_t idx) : m_has_index(true), m_index(idx) {}
explicit InputInfoImpl(std::string name) : m_has_name(true), m_name(std::move(name)) {}
bool has_index() const {
return m_has_index;
}
bool has_name() const {
return m_has_name;
}
void create_tensor_data(const element::Type& type, const Layout& layout) {
auto data = std::unique_ptr<InputTensorInfo::InputTensorInfoImpl>(new InputTensorInfo::InputTensorInfoImpl());
data->set_layout(layout);
@ -169,6 +174,8 @@ struct InputInfo::InputInfoImpl {
bool m_has_index = false;
size_t m_index = 0;
bool m_has_name = false;
std::string m_name;
std::unique_ptr<InputTensorInfo::InputTensorInfoImpl> m_tensor_data;
std::unique_ptr<PreProcessSteps::PreProcessStepsImpl> m_preprocess;
std::unique_ptr<InputNetworkInfo::InputNetworkInfoImpl> m_network_data;
@ -206,6 +213,8 @@ struct OutputInfo::OutputInfoImpl {
//-------------- InputInfo ------------------
InputInfo::InputInfo() : m_impl(std::unique_ptr<InputInfoImpl>(new InputInfoImpl)) {}
InputInfo::InputInfo(size_t input_index) : m_impl(std::unique_ptr<InputInfoImpl>(new InputInfoImpl(input_index))) {}
InputInfo::InputInfo(const std::string& input_tensor_name)
: m_impl(std::unique_ptr<InputInfoImpl>(new InputInfoImpl(input_tensor_name))) {}
InputInfo::InputInfo(InputInfo&&) noexcept = default;
InputInfo& InputInfo::operator=(InputInfo&&) noexcept = default;
InputInfo::~InputInfo() = default;
@ -318,18 +327,16 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
bool tensor_data_updated = false;
for (const auto& input : m_impl->in_contexts) {
std::shared_ptr<op::v0::Parameter> param;
Output<Node> node;
OPENVINO_ASSERT(input, "Internal error: Invalid preprocessing input, please report a problem");
if (input->has_index()) {
param = function->get_parameters().at(input->m_index);
node = function->input(input->m_index);
} else if (input->has_name()) {
node = function->input(input->m_name);
} else {
// Default case
OPENVINO_ASSERT(function->get_parameters().size() == 1,
std::string("Preprocessing info expects having 1 input, however function has ") +
std::to_string(function->get_parameters().size()) +
" inputs. Please use ov::preprocess::InputInfo constructor specifying "
"particular input instead of default one");
param = function->get_parameters().front();
node = function->input();
}
param = std::dynamic_pointer_cast<op::v0::Parameter>(node.get_node_shared_ptr());
// Set parameter layout from 'network' information
if (input->m_network_data && input->m_network_data->is_layout_set() && param->get_layout().empty()) {
param->set_layout(input->m_network_data->get_layout());
@ -362,11 +369,13 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
// Find transpose between network and tensor layouts and update tensor shape
auto net_to_tensor =
layout::find_permutation(param->get_layout(), net_shape.rank(), input->m_tensor_data->get_layout());
std::vector<ov::Dimension> dims(new_param_shape.size());
std::transform(net_to_tensor.begin(), net_to_tensor.end(), dims.begin(), [&](int64_t v) {
return new_param_shape[v];
});
new_param_shape = PartialShape(dims);
if (!net_to_tensor.empty()) {
std::vector<ov::Dimension> dims(new_param_shape.size());
std::transform(net_to_tensor.begin(), net_to_tensor.end(), dims.begin(), [&](int64_t v) {
return new_param_shape[v];
});
new_param_shape = PartialShape(dims);
}
}
if (input->m_tensor_data->is_spatial_shape_set()) {
auto height_idx = get_and_check_height_idx(input->m_tensor_data->get_layout(), new_param_shape);
@ -382,7 +391,7 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
}
}
std::vector<std::shared_ptr<ov::Node>> nodes;
std::vector<Output<Node>> nodes;
std::vector<std::shared_ptr<op::v0::Parameter>> new_params;
// Create separate parameter for each plane. Shape and friendly name is based on color format
@ -401,20 +410,21 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
plane_param->set_layout(input->m_tensor_data->get_layout());
}
new_params.push_back(plane_param);
nodes.push_back(plane_param);
nodes.emplace_back(plane_param);
}
PreprocessingContext context(input->m_tensor_data->get_layout());
context.color_format() = input->m_tensor_data->get_color_format();
context.target_layout() = param->get_layout();
context.network_shape() = param->get_partial_shape();
context.target_element_type() = param->get_element_type();
// 2. Apply preprocessing
if (input->m_preprocess) {
for (const auto& action : input->m_preprocess->actions()) {
auto node = std::get<0>(action)(nodes, function, context);
nodes = {node};
tensor_data_updated |= std::get<1>(action);
auto action_result = action(nodes, function, context);
nodes = std::get<0>(action_result);
tensor_data_updated |= std::get<1>(action_result);
}
}
@ -428,13 +438,27 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
"to convert current color format '",
color_format_name(context.color_format()),
"'to RGB/BGR");
// Implicit: Convert element type + layout to user's tensor implicitly
PreStepsList implicit_steps;
implicit_steps.add_convert_impl(param->get_element_type());
if (!context.target_layout().empty()) {
implicit_steps.add_convert_layout_impl(context.target_layout());
}
for (const auto& action : implicit_steps.actions()) {
auto action_result = action(nodes, function, context);
nodes = std::get<0>(action_result);
}
auto node = nodes[0];
// Check final type
OPENVINO_ASSERT(node->get_element_type() == param->get_element_type(),
std::string("Element type after preprocessing {") + node->get_element_type().c_type_string() +
std::string("} doesn't match with network element type {") +
param->get_element_type().c_type_string() +
"}. Please add 'convert_element_type' explicitly");
// Check final shape
OPENVINO_ASSERT(node.get_partial_shape().refines(param->get_partial_shape()),
"Resulting shape '",
node.get_partial_shape(),
"' after preprocessing is not aligned with original parameter's shape: ",
param->get_partial_shape());
// Replace parameter
for (auto consumer : consumers) {
@ -462,6 +486,7 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
} else {
node = function->output();
}
auto start_out_node_names = node.get_tensor().get_names();
result = std::dynamic_pointer_cast<op::v0::Result>(node.get_node_shared_ptr());
// Set result layout from 'network' information
if (output->m_network_data && output->m_network_data->is_layout_set() && result->get_layout().empty()) {
@ -506,7 +531,8 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
if (!context.layout().empty()) {
new_result->set_layout(context.layout());
}
new_result->get_input_tensor(0).set_names(result->get_input_tensor(0).get_names());
node.get_tensor().set_names(start_out_node_names);
function->add_results({new_result});
function->remove_result(result);
}
@ -694,31 +720,27 @@ PreProcessSteps&& PreProcessSteps::convert_color(const ov::preprocess::ColorForm
PreProcessSteps& PreProcessSteps::custom(const CustomPreprocessOp& preprocess_cb) & {
// 'true' indicates that custom preprocessing step will trigger validate_and_infer_types
m_impl->actions().emplace_back(std::make_tuple(
[preprocess_cb](const std::vector<std::shared_ptr<ov::Node>>& nodes,
const std::shared_ptr<ov::Function>&,
PreprocessingContext&) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply custom preprocessing step for multi-plane input. Suggesting to convert "
"current image to RGB/BGR color format using 'convert_color'");
return {preprocess_cb(nodes[0])};
},
true));
m_impl->actions().emplace_back([preprocess_cb](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<ov::Function>&,
PreprocessingContext&) {
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply custom preprocessing step for multi-plane input. Suggesting to convert "
"current image to RGB/BGR color format using 'convert_color'");
return std::make_tuple(std::vector<Output<Node>>{preprocess_cb(nodes[0])}, true);
});
return *this;
}
PreProcessSteps&& PreProcessSteps::custom(const CustomPreprocessOp& preprocess_cb) && {
// 'true' indicates that custom preprocessing step will trigger validate_and_infer_types
m_impl->actions().emplace_back(std::make_tuple(
[preprocess_cb](const std::vector<std::shared_ptr<ov::Node>>& nodes,
const std::shared_ptr<ov::Function>&,
PreprocessingContext&) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply custom preprocessing step for multi-plane input. Suggesting to convert "
"current image to RGB/BGR color format using 'convert_color'");
return {preprocess_cb(nodes[0])};
},
true));
m_impl->actions().emplace_back([preprocess_cb](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<ov::Function>&,
PreprocessingContext&) {
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply custom preprocessing step for multi-plane input. Suggesting to convert "
"current image to RGB/BGR color format using 'convert_color'");
return std::make_tuple(std::vector<Output<Node>>{preprocess_cb(nodes[0])}, true);
});
return std::move(*this);
}

View File

@ -14,11 +14,10 @@
namespace ov {
namespace preprocess {
static Shape construct_mean_scale_shape(const std::shared_ptr<Node>& node,
static Shape construct_mean_scale_shape(const Output<Node>& node,
size_t values_size,
const PreprocessingContext& context) {
// TODO: support also Mean/Scale image case
auto node_shape = node->get_output_partial_shape(0);
auto node_shape = node.get_partial_shape();
auto node_rank = node_shape.rank();
auto channels_index = get_and_check_channels_idx(context.layout(), node_shape);
std::vector<std::size_t> v(node_rank.get_length(), 1);
@ -32,218 +31,216 @@ static Shape construct_mean_scale_shape(const std::shared_ptr<Node>& node,
return {v};
}
void PreProcessSteps::PreProcessStepsImpl::add_scale_impl(const std::vector<float>& values) {
m_actions.emplace_back(std::make_tuple(
[values](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't apply scale preprocessing for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply scale preprocessing for multi-plane input. Suggesting to convert current "
"image to RGB/BGR color format using 'convert_color'");
Shape shape;
if (values.size() == 1) {
shape = Shape{1};
void PreStepsList::add_scale_impl(const std::vector<float>& values) {
m_actions.emplace_back([values](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) -> std::tuple<std::vector<Output<Node>>, bool> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't apply scale preprocessing for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply scale preprocessing for multi-plane input. Suggesting to convert current "
"image to RGB/BGR color format using 'convert_color'");
Shape shape;
if (values.size() == 1) {
shape = Shape{1};
} else {
shape = construct_mean_scale_shape(nodes[0].get_node_shared_ptr(), values.size(), context);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
inherit_friendly_names(function, nodes[0].get_node_shared_ptr(), constant, "/scale/Divide_Factor");
auto new_op = std::make_shared<op::v1::Divide>(nodes[0], constant);
inherit_friendly_names(function, nodes[0].get_node_shared_ptr(), new_op, "/scale/Divide");
return std::make_tuple(std::vector<Output<Node>>{new_op}, false);
});
}
void PreStepsList::add_mean_impl(const std::vector<float>& values) {
m_actions.emplace_back([values](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't apply mean preprocessing for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply scale preprocessing for multi-plane input. Suggesting to convert current "
"image to RGB/BGR color format using 'convert_color'");
Shape shape;
if (values.size() == 1) {
shape = Shape{1};
} else {
shape = construct_mean_scale_shape(nodes[0], values.size(), context);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
inherit_friendly_names(function, nodes[0], constant, "/mean/Mean_Const");
auto new_op = std::make_shared<op::v1::Subtract>(nodes[0], constant);
inherit_friendly_names(function, nodes[0], new_op, "/mean/Subtract");
return std::make_tuple(std::vector<Output<Node>>{new_op}, false);
});
}
void PreStepsList::add_convert_impl(const element::Type& type) {
m_actions.emplace_back([type](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<Function>& function,
PreprocessingContext& ctxt) {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't set element type for empty input.");
std::vector<Output<Node>> res;
element::Type t = type;
if (t == element::Type{}) {
t = ctxt.target_element_type();
}
bool convert_added = false;
for (const auto& node : nodes) {
OPENVINO_ASSERT(node.get_element_type().is_static(),
"Can't insert 'convert_element_type' for dynamic source tensor type.");
if (t != node.get_element_type()) {
auto convert = std::make_shared<op::v0::Convert>(node, t);
inherit_friendly_names(function, node, convert, "/convert_element_type");
res.emplace_back(convert);
convert_added = true;
} else {
shape = construct_mean_scale_shape(nodes[0], values.size(), context);
res.emplace_back(node);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
inherit_friendly_names(function, nodes[0], constant, "/scale/Divide_Factor");
auto new_op = std::make_shared<op::v1::Divide>(nodes[0], constant);
inherit_friendly_names(function, nodes[0], new_op, "/scale/Divide");
return {new_op};
},
false));
}
return std::make_tuple(res, convert_added);
});
}
void PreProcessSteps::PreProcessStepsImpl::add_mean_impl(const std::vector<float>& values) {
m_actions.emplace_back(std::make_tuple(
[values](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't apply mean preprocessing for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't apply scale preprocessing for multi-plane input. Suggesting to convert current "
"image to RGB/BGR color format using 'convert_color'");
Shape shape;
if (values.size() == 1) {
shape = Shape{1};
} else {
shape = construct_mean_scale_shape(nodes[0], values.size(), context);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
inherit_friendly_names(function, nodes[0], constant, "/mean/Mean_Const");
auto new_op = std::make_shared<op::v1::Subtract>(nodes[0], constant);
inherit_friendly_names(function, nodes[0], new_op, "/mean/Subtract");
return {new_op};
},
false));
}
void PreProcessSteps::PreProcessStepsImpl::add_convert_impl(const ov::element::Type& type) {
m_actions.emplace_back(std::make_tuple(
[type](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext&) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't set element type for empty input.");
std::vector<std::shared_ptr<ov::Node>> res;
for (const auto& node : nodes) {
OPENVINO_ASSERT(node->get_element_type().is_static(),
"Can't insert 'convert_element_type' for dynamic source tensor type.");
if (node->get_element_type() != type) {
auto convert = std::make_shared<op::v0::Convert>(node, type);
inherit_friendly_names(function, node, convert, "/convert_element_type");
res.emplace_back(convert);
} else {
res.emplace_back(node);
}
}
return res;
},
true));
}
void PreProcessSteps::PreProcessStepsImpl::add_resize_impl(ResizeAlgorithm alg, int dst_height, int dst_width) {
void PreStepsList::add_resize_impl(ResizeAlgorithm alg, int dst_height, int dst_width) {
using InterpolateMode = op::v4::Interpolate::InterpolateMode;
m_actions.emplace_back(std::make_tuple(
[alg, dst_width, dst_height](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& ctxt) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't add resize for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't resize multi-plane input. Suggesting to convert current image to "
"RGB/BGR color format using 'PreProcessSteps::convert_color'");
auto to_mode = [](ResizeAlgorithm alg) -> InterpolateMode {
switch (alg) {
case ResizeAlgorithm::RESIZE_NEAREST:
return InterpolateMode::NEAREST;
case ResizeAlgorithm::RESIZE_CUBIC:
return InterpolateMode::CUBIC;
case ResizeAlgorithm::RESIZE_LINEAR:
default:
return InterpolateMode::LINEAR;
}
};
auto node = nodes.front();
auto layout = ctxt.layout();
OPENVINO_ASSERT(ov::layout::has_height(layout) && ov::layout::has_width(layout),
"Can't add resize for layout without W/H specified. Use 'set_layout' API to define layout "
"of image data, like `NCHW`");
auto node_rank = node->get_output_partial_shape(0).rank();
OPENVINO_ASSERT(node_rank.is_static(), "Resize operation is not supported for fully dynamic shape");
auto height_idx = static_cast<int64_t>(get_and_check_height_idx(layout, node->get_output_partial_shape(0)));
auto width_idx = static_cast<int64_t>(get_and_check_width_idx(layout, node->get_output_partial_shape(0)));
if (dst_height < 0 || dst_width < 0) {
OPENVINO_ASSERT(ctxt.network_shape().rank().is_static(),
"Resize is not fully specified while target network shape is dynamic");
m_actions.emplace_back([alg, dst_width, dst_height](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<Function>& function,
PreprocessingContext& ctxt) {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't add resize for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't resize multi-plane input. Suggesting to convert current image to "
"RGB/BGR color format using 'PreProcessSteps::convert_color'");
auto to_mode = [](ResizeAlgorithm alg) -> InterpolateMode {
switch (alg) {
case ResizeAlgorithm::RESIZE_NEAREST:
return InterpolateMode::NEAREST;
case ResizeAlgorithm::RESIZE_CUBIC:
return InterpolateMode::CUBIC;
case ResizeAlgorithm::RESIZE_LINEAR:
default:
return InterpolateMode::LINEAR;
}
int new_image_width = dst_width < 0 ? static_cast<int>(ctxt.get_network_width_for_resize()) : dst_width;
int new_image_height = dst_height < 0 ? static_cast<int>(ctxt.get_network_height_for_resize()) : dst_height;
};
auto node = nodes.front();
auto layout = ctxt.layout();
OPENVINO_ASSERT(ov::layout::has_height(layout) && ov::layout::has_width(layout),
"Can't add resize for layout without W/H specified. Use 'set_layout' API to define layout "
"of image data, like `NCHW`");
auto node_rank = node.get_partial_shape().rank();
OPENVINO_ASSERT(node_rank.is_static(), "Resize operation is not supported for fully dynamic shape");
auto target_spatial_shape =
op::v0::Constant::create<int64_t>(element::i64, Shape{2}, {new_image_height, new_image_width});
auto scales = op::v0::Constant::create<float>(element::f32, Shape{2}, {1, 1});
// In future consider replacing this to set of new OV operations like `getDimByName(node, "H")`
// This is to allow specifying layout on 'evaluation' stage
auto axes = op::v0::Constant::create<int64_t>(element::i64, Shape{2}, {height_idx, width_idx});
auto height_idx = static_cast<int64_t>(get_and_check_height_idx(layout, node.get_partial_shape()));
auto width_idx = static_cast<int64_t>(get_and_check_width_idx(layout, node.get_partial_shape()));
if (dst_height < 0 || dst_width < 0) {
OPENVINO_ASSERT(ctxt.network_shape().rank().is_static(),
"Resize is not fully specified while target network shape is dynamic");
}
int new_image_width = dst_width < 0 ? static_cast<int>(ctxt.get_network_width_for_resize()) : dst_width;
int new_image_height = dst_height < 0 ? static_cast<int>(ctxt.get_network_height_for_resize()) : dst_height;
op::v4::Interpolate::InterpolateAttrs attrs(to_mode(alg),
op::v4::Interpolate::ShapeCalcMode::SIZES,
{0, 0},
{0, 0});
auto target_spatial_shape =
op::v0::Constant::create<int64_t>(element::i64, Shape{2}, {new_image_height, new_image_width});
auto scales = op::v0::Constant::create<float>(element::f32, Shape{2}, {1, 1});
// In future consider replacing this to set of new OV operations like `getDimByName(node, "H")`
// This is to allow specifying layout on 'evaluation' stage
auto axes = op::v0::Constant::create<int64_t>(element::i64, Shape{2}, {height_idx, width_idx});
auto interp = std::make_shared<op::v4::Interpolate>(node, target_spatial_shape, scales, axes, attrs);
inherit_friendly_names(function, nodes[0], interp, "/resize");
return {interp};
},
true));
op::v4::Interpolate::InterpolateAttrs attrs(to_mode(alg),
op::v4::Interpolate::ShapeCalcMode::SIZES,
{0, 0},
{0, 0});
auto interp = std::make_shared<op::v4::Interpolate>(node, target_spatial_shape, scales, axes, attrs);
inherit_friendly_names(function, nodes[0], interp, "/resize");
return std::make_tuple(std::vector<Output<Node>>{interp}, true);
});
}
void PreProcessSteps::PreProcessStepsImpl::add_convert_layout_impl(const Layout& layout) {
m_actions.emplace_back(std::make_tuple(
[layout](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) -> std::vector<std::shared_ptr<ov::Node>> {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't convert layout for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't convert layout for multi-plane input. Suggesting to convert current image to "
"RGB/BGR color format using 'convert_color'");
Layout dst_layout = layout.empty() ? context.target_layout() : layout;
auto permutation =
layout::find_permutation(context.layout(), nodes[0]->get_output_partial_shape(0).rank(), dst_layout);
auto perm_constant =
op::v0::Constant::create<int64_t>(element::i64, Shape{permutation.size()}, permutation);
auto transpose = std::make_shared<op::v1::Transpose>(nodes[0], perm_constant);
inherit_friendly_names(function, nodes[0], transpose, "/convert_layout");
context.layout() = dst_layout; // Update context's current layout
return {transpose};
},
true));
void PreStepsList::add_convert_layout_impl(const Layout& layout) {
m_actions.emplace_back([layout](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<Function>& function,
PreprocessingContext& context) {
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't convert layout for empty input.");
OPENVINO_ASSERT(nodes.size() == 1,
"Can't convert layout for multi-plane input. Suggesting to convert current image to "
"RGB/BGR color format using 'convert_color'");
Layout dst_layout = layout.empty() ? context.target_layout() : layout;
auto permutation = layout::find_permutation(context.layout(), nodes[0].get_partial_shape().rank(), dst_layout);
if (permutation.empty()) {
// No transpose is needed, just update layout
if (!layout.empty()) {
context.layout() = layout;
}
return std::make_tuple(nodes, false);
}
auto perm_constant = op::v0::Constant::create<int64_t>(element::i64, Shape{permutation.size()}, permutation);
auto transpose = std::make_shared<op::v1::Transpose>(nodes[0], perm_constant);
inherit_friendly_names(function, nodes[0], transpose, "/convert_layout");
context.layout() = dst_layout; // Update context's current layout
return std::make_tuple(std::vector<Output<Node>>{transpose}, true);
});
}
void PreProcessSteps::PreProcessStepsImpl::add_convert_color_impl(const ColorFormat& dst_format) {
m_actions.emplace_back(std::make_tuple(
[&, dst_format](const std::vector<std::shared_ptr<Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context) -> std::vector<std::shared_ptr<ov::Node>> {
if (context.color_format() == dst_format) {
return nodes;
void PreStepsList::add_convert_color_impl(const ColorFormat& dst_format) {
m_actions.emplace_back([&, dst_format](const std::vector<Output<Node>>& nodes,
const std::shared_ptr<Function>& function,
PreprocessingContext& context) {
if (context.color_format() == dst_format) {
return std::make_tuple(nodes, false);
}
if (context.color_format() == ColorFormat::NV12_SINGLE_PLANE) {
OPENVINO_ASSERT(nodes.size() == 1, "Internal error: single plane NV12 image can't have multiple inputs");
std::shared_ptr<Node> convert;
switch (dst_format) {
case ColorFormat::RGB:
convert = std::make_shared<op::v8::NV12toRGB>(nodes[0]);
break;
case ColorFormat::BGR:
convert = std::make_shared<op::v8::NV12toBGR>(nodes[0]);
break;
default:
OPENVINO_ASSERT(false,
"Unsupported conversion from NV12 to '",
color_format_name(dst_format),
"' format:");
}
if (context.color_format() == ColorFormat::NV12_SINGLE_PLANE) {
OPENVINO_ASSERT(nodes.size() == 1,
"Internal error: single plane NV12 image can't have multiple inputs");
std::shared_ptr<Node> convert;
switch (dst_format) {
case ColorFormat::RGB:
convert = std::make_shared<op::v8::NV12toRGB>(nodes[0]);
break;
case ColorFormat::BGR:
convert = std::make_shared<op::v8::NV12toBGR>(nodes[0]);
break;
default:
OPENVINO_ASSERT(false,
"Unsupported conversion from NV12 to '",
color_format_name(dst_format),
"' format:");
}
inherit_friendly_names(function, nodes[0], convert, "/convert_color_nv12_single");
context.color_format() = dst_format;
return {convert};
} else if (context.color_format() == ColorFormat::NV12_TWO_PLANES) {
OPENVINO_ASSERT(nodes.size() == 2, "Internal error: two-plane NV12 image must have exactly two inputs");
std::shared_ptr<Node> convert;
switch (dst_format) {
case ColorFormat::RGB:
convert = std::make_shared<op::v8::NV12toRGB>(nodes[0], nodes[1]);
break;
case ColorFormat::BGR:
convert = std::make_shared<op::v8::NV12toBGR>(nodes[0], nodes[1]);
break;
default:
OPENVINO_ASSERT(false,
"Unsupported conversion from NV12 to '",
color_format_name(dst_format),
"' format:");
}
inherit_friendly_names(function, nodes[0], convert, "/convert_color_nv12_two_planes");
context.color_format() = dst_format;
return {convert};
inherit_friendly_names(function, nodes[0], convert, "/convert_color_nv12_single");
context.color_format() = dst_format;
return std::make_tuple(std::vector<Output<Node>>{convert}, true);
} else if (context.color_format() == ColorFormat::NV12_TWO_PLANES) {
OPENVINO_ASSERT(nodes.size() == 2, "Internal error: two-plane NV12 image must have exactly two inputs");
std::shared_ptr<Node> convert;
switch (dst_format) {
case ColorFormat::RGB:
convert = std::make_shared<op::v8::NV12toRGB>(nodes[0], nodes[1]);
break;
case ColorFormat::BGR:
convert = std::make_shared<op::v8::NV12toBGR>(nodes[0], nodes[1]);
break;
default:
OPENVINO_ASSERT(false,
"Unsupported conversion from NV12 to '",
color_format_name(dst_format),
"' format:");
}
OPENVINO_ASSERT(false,
"Source color format '",
color_format_name(context.color_format()),
"' is not convertible to any other");
},
true));
inherit_friendly_names(function, nodes[0], convert, "/convert_color_nv12_two_planes");
context.color_format() = dst_format;
return std::make_tuple(std::vector<Output<Node>>{convert}, true);
}
OPENVINO_ASSERT(false,
"Source color format '",
color_format_name(context.color_format()),
"' is not convertible to any other");
});
}
//------------- Post processing ------
void PostStepsList::add_convert_impl(const ov::element::Type& type) {
m_actions.emplace_back([type](const ov::Output<Node>& node, PostprocessingContext& ctxt) {
ov::element::Type t = type;
void PostStepsList::add_convert_impl(const element::Type& type) {
m_actions.emplace_back([type](const Output<Node>& node, PostprocessingContext& ctxt) {
element::Type t = type;
if (t == element::Type{}) {
t = ctxt.target_element_type();
}
@ -254,23 +251,27 @@ void PostStepsList::add_convert_impl(const ov::element::Type& type) {
!t.is_dynamic() && t != element::undefined,
"Can't convert to dynamic/unknown element type, consider using of InputTensorInfo::set_element_type");
auto convert = std::make_shared<op::v0::Convert>(node, t);
convert->set_friendly_name(node.get_node()->get_friendly_name() + "/convert_element_type");
return std::make_tuple(ov::Output<ov::Node>(convert), true);
inherit_friendly_names_postprocess(convert, node, "/post_convert_element_type");
return std::make_tuple(Output<Node>(convert), true);
});
}
void PostStepsList::add_convert_layout_impl(const Layout& layout) {
m_actions.emplace_back([layout](const ov::Output<Node>& node, PostprocessingContext& context) {
m_actions.emplace_back([layout](const Output<Node>& node, PostprocessingContext& context) {
Layout dst_layout = layout.empty() ? context.target_layout() : layout;
if (dst_layout == context.layout()) {
auto permutation = layout::find_permutation(context.layout(), node.get_partial_shape().rank(), dst_layout);
if (permutation.empty()) {
// No transpose is needed, just update layout
if (!layout.empty()) {
context.layout() = layout;
}
return std::make_tuple(node, false);
}
auto permutation = layout::find_permutation(context.layout(), node.get_partial_shape().rank(), dst_layout);
auto perm_constant = op::v0::Constant::create<int64_t>(element::i64, Shape{permutation.size()}, permutation);
auto transpose = std::make_shared<op::v1::Transpose>(node, perm_constant);
transpose->set_friendly_name(node.get_node()->get_friendly_name() + "/convert_layout");
inherit_friendly_names_postprocess(transpose, node, "/post_convert_layout");
context.layout() = dst_layout; // Update context's current layout
return std::make_tuple(ov::Output<ov::Node>(transpose), true);
return std::make_tuple(Output<Node>(transpose), true);
});
}

View File

@ -60,15 +60,13 @@ inline size_t get_and_check_channels_idx(const Layout& layout, const PartialShap
}
inline void inherit_friendly_names(const std::shared_ptr<ov::Function>& function,
const std::shared_ptr<ov::Node>& src_node,
const std::shared_ptr<ov::Node>& dst_node,
const Output<ov::Node>& src_node,
const Output<ov::Node>& dst_node,
const std::string& suffix,
bool search_for_available_name = true) {
OPENVINO_ASSERT(src_node->get_output_size() == 1 && dst_node->get_output_size() == 1,
"Internal error. Preprocessing steps must contain nodes with one output");
dst_node->set_friendly_name(src_node->get_friendly_name() + suffix);
dst_node.get_node_shared_ptr()->set_friendly_name(src_node.get_node_shared_ptr()->get_friendly_name() + suffix);
std::unordered_set<std::string> new_names;
for (const auto& tensor_name : src_node->output(0).get_tensor().get_names()) {
for (const auto& tensor_name : src_node.get_tensor().get_names()) {
auto new_tensor_name = tensor_name + suffix;
if (!suffix.empty()) {
// Verify that new names are unique for a function
@ -82,7 +80,21 @@ inline void inherit_friendly_names(const std::shared_ptr<ov::Function>& function
}
new_names.emplace(new_tensor_name);
}
dst_node->output(0).get_tensor().set_names(new_names);
dst_node.get_tensor().set_names(new_names);
}
// TODO: add uniqueness check like for preprocessing (or remove from pre-processing)
inline void inherit_friendly_names_postprocess(const Output<ov::Node>& inserted_output,
const Output<ov::Node>& previous_output,
const std::string& suffix) {
inserted_output.get_node_shared_ptr()->set_friendly_name(
previous_output.get_node_shared_ptr()->get_friendly_name() + suffix);
std::unordered_set<std::string> new_names; // New name for previous node
for (const auto& tensor_name : previous_output.get_tensor().get_names()) {
auto new_tensor_name = tensor_name + suffix;
new_names.emplace(new_tensor_name);
}
previous_output.get_tensor().set_names(new_names);
}
/// \brief Context passed to each pre/post-processing operation.
@ -167,12 +179,12 @@ private:
};
using InternalPreprocessOp =
std::function<std::vector<std::shared_ptr<ov::Node>>(const std::vector<std::shared_ptr<ov::Node>>& nodes,
const std::shared_ptr<ov::Function>& function,
PreprocessingContext& context)>;
std::function<std::tuple<std::vector<Output<Node>>, bool>(const std::vector<Output<Node>>& nodes,
const std::shared_ptr<Function>& function,
PreprocessingContext& context)>;
/// \brief PreProcessStepsImpl - internal data structure
class PreProcessSteps::PreProcessStepsImpl {
class PreStepsList {
public:
void add_scale_impl(const std::vector<float>& values);
void add_mean_impl(const std::vector<float>& values);
@ -181,17 +193,19 @@ public:
void add_convert_layout_impl(const Layout& layout);
void add_convert_color_impl(const ColorFormat& dst_format);
const std::list<std::tuple<InternalPreprocessOp, bool>>& actions() const {
const std::list<InternalPreprocessOp>& actions() const {
return m_actions;
}
std::list<std::tuple<InternalPreprocessOp, bool>>& actions() {
std::list<InternalPreprocessOp>& actions() {
return m_actions;
}
private:
std::list<std::tuple<InternalPreprocessOp, bool>> m_actions;
std::list<InternalPreprocessOp> m_actions;
};
class PreProcessSteps::PreProcessStepsImpl : public PreStepsList {};
//------ Post process -----
class PostprocessingContext : public PrePostProcessingContextBase {
public:

View File

@ -20,6 +20,7 @@ static std::shared_ptr<Function> create_simple_function(element::Type type, cons
data1->get_output_tensor(0).set_names({"tensor_input1"});
auto op = std::make_shared<op::v0::Relu>(data1);
op->set_friendly_name("Relu");
op->get_output_tensor(0).set_names({"tensor_Relu"});
auto res = std::make_shared<op::v0::Result>(op);
res->set_friendly_name("Result1");
res->get_output_tensor(0).set_names({"tensor_output1"});
@ -66,6 +67,49 @@ TEST(pre_post_process, convert_element_type_and_scale) {
EXPECT_EQ(f->get_output_element_type(0), element::i8);
}
TEST(pre_post_process, convert_element_type_implicit) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
f = PrePostProcessor().input(InputInfo().tensor(InputTensorInfo().set_element_type(element::f32))).build(f);
EXPECT_EQ(f->get_parameters().front()->get_element_type(), element::f32);
EXPECT_EQ(f->get_results().front()->get_element_type(), element::i32);
}
TEST(pre_post_process, convert_element_type_same) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
auto old_size = f->get_ops().size();
f = PrePostProcessor()
.input(InputInfo("tensor_input1")
.tensor(InputTensorInfo().set_element_type(element::i32))
.preprocess(PreProcessSteps().convert_element_type(element::i32)))
.build(f);
EXPECT_EQ(f->get_parameters().front()->get_element_type(), element::i32);
EXPECT_EQ(old_size, f->get_ops().size());
}
TEST(pre_post_process, convert_element_type_default) {
auto f = create_simple_function(element::f32, Shape{1, 3, 224, 224});
auto type_custom1 = element::Type();
auto type_custom2 = element::Type();
f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_element_type(element::i32))
.preprocess(PreProcessSteps()
.custom([&type_custom1](const Output<Node>& node) {
type_custom1 = node.get_element_type();
return node;
})
.convert_element_type()
.custom([&type_custom2](const Output<Node>& node) {
type_custom2 = node.get_element_type();
return node;
})))
.build(f);
EXPECT_EQ(type_custom1, element::i32);
EXPECT_EQ(type_custom2, element::f32);
EXPECT_EQ(f->get_parameters().front()->get_element_type(), element::i32);
EXPECT_EQ(f->get_results().front()->get_element_type(), element::f32);
}
TEST(pre_post_process, empty_preprocess) {
auto f = create_simple_function(element::i8, Shape{1, 3, 2, 2});
f = PrePostProcessor().input(InputInfo().tensor(InputTensorInfo().set_element_type(element::i8))).build(f);
@ -73,6 +117,14 @@ TEST(pre_post_process, empty_preprocess) {
EXPECT_EQ(f->get_output_element_type(0), element::i8);
}
TEST(pre_post_process, preprocess_assert_input_without_index) {
auto f = create_2inputs(element::f32, Shape{1, 3, 2, 2});
auto inp = InputInfo();
EXPECT_ANY_THROW(f = PrePostProcessor().input(std::move(inp)).build(f));
inp = InputInfo("some_non_existing_name");
EXPECT_ANY_THROW(f = PrePostProcessor().input(std::move(inp)).build(f));
}
TEST(pre_post_process, convert_element_type_from_unknown) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
ASSERT_THROW(
@ -83,29 +135,19 @@ TEST(pre_post_process, convert_element_type_from_unknown) {
ov::AssertFailure);
}
TEST(pre_post_process, convert_element_type_no_match) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
ASSERT_THROW(f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_element_type(element::i32))
.preprocess(PreProcessSteps().convert_element_type(element::f32).scale(2.0f)))
.build(f),
ov::AssertFailure);
}
TEST(pre_post_process, scale_not_float) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
auto f = create_simple_function(element::f32, Shape{1, 3, 224, 224});
ASSERT_THROW(
f = PrePostProcessor()
.input(InputInfo().preprocess(PreProcessSteps().convert_element_type(element::f32).scale(2.0f)))
.input(InputInfo().preprocess(PreProcessSteps().convert_element_type(element::i32).scale(2.0f)))
.build(f),
ov::AssertFailure);
}
TEST(pre_post_process, mean_not_float) {
auto f = create_simple_function(element::i32, Shape{1, 3, 224, 224});
auto f = create_simple_function(element::f32, Shape{1, 3, 224, 224});
ASSERT_THROW(f = PrePostProcessor()
.input(InputInfo().preprocess(PreProcessSteps().convert_element_type(element::f32).mean(2.0f)))
.input(InputInfo().preprocess(PreProcessSteps().convert_element_type(element::i32).mean(2.0f)))
.build(f),
ov::AssertFailure);
}
@ -232,7 +274,7 @@ TEST(pre_post_process, convert_color_nv12_bgr_2_planes_el_type) {
PreProcessSteps().convert_element_type(element::u8).convert_color(ColorFormat::BGR)))
.build(f));
EXPECT_EQ(f->get_parameters().size(), 2);
ASSERT_EQ(f->get_parameters().size(), 2);
EXPECT_EQ(f->get_parameters()[0]->get_element_type(), element::f32);
EXPECT_EQ(f->get_parameters()[1]->get_element_type(), element::f32);
}
@ -333,18 +375,28 @@ TEST(pre_post_process, convert_color_duplicate_internal_subnames_mean) {
.input(InputInfo().preprocess(
PreProcessSteps().convert_element_type(element::u8).convert_element_type(element::f32)))
.build(f));
EXPECT_NO_THROW(f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_layout("NHWC"))
.preprocess(PreProcessSteps().convert_layout("NCHW")))
.build(f));
EXPECT_NO_THROW(
f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_layout("NHWC").set_spatial_static_shape(480, 640))
.preprocess(PreProcessSteps().resize(ResizeAlgorithm::RESIZE_LINEAR)))
.build(f));
}
f = create_simple_function(element::f32, PartialShape{1, 2, 2, 3});
for (int i = 0; i < 10; i++) {
(f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_layout("NHWC"))
.preprocess(PreProcessSteps().convert_layout("NCHW"))
.network(InputNetworkInfo().set_layout("NHWC")))
.build(f));
}
f = create_simple_function(element::f32, PartialShape{1, 2, 2, 3});
auto p = PreProcessSteps();
for (int i = 10; i < 20; i++) {
p.resize(ResizeAlgorithm::RESIZE_LINEAR, i, i);
}
p.resize(ResizeAlgorithm::RESIZE_LINEAR);
EXPECT_NO_THROW(f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_spatial_static_shape(480, 640))
.preprocess(std::move(p))
.network(InputNetworkInfo().set_layout("NHWC")))
.build(f));
}
TEST(pre_post_process, unsupported_network_color_format) {
@ -385,9 +437,9 @@ TEST(pre_post_process, unsupported_network_color_format) {
TEST(pre_post_process, custom_preprocessing) {
auto f = create_simple_function(element::i32, Shape{1, 3, 1, 1});
f = PrePostProcessor()
.input(InputInfo().preprocess(PreProcessSteps().custom([](const std::shared_ptr<Node>& node) {
.input(InputInfo().preprocess(PreProcessSteps().custom([](const Output<Node>& node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
})))
.build(f);
@ -420,9 +472,9 @@ TEST(pre_post_process, test_lvalue) {
preprocessSteps.scale(2.f);
preprocessSteps.mean({1.f, 2.f, 3.f});
preprocessSteps.scale({2.f, 3.f, 4.f});
preprocessSteps.custom([](const std::shared_ptr<Node>& node) {
preprocessSteps.custom([](const Output<Node>& node) {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node->get_friendly_name() + "/abs");
abs->set_friendly_name(node.get_node_shared_ptr()->get_friendly_name() + "/abs");
return abs;
});
auto& same = preprocessSteps.convert_element_type(element::i8);
@ -603,20 +655,67 @@ TEST(pre_post_process, resize_no_tensor_width) {
ov::AssertFailure);
}
TEST(pre_post_process, preprocess_convert_layout_implicit) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.input(
InputInfo().tensor(InputTensorInfo().set_layout("NHWC")).network(InputNetworkInfo().set_layout("NCHW")))
.build(f);
EXPECT_EQ(f->get_parameters()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_parameters()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, preprocess_convert_layout_default) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_layout("NHWC"))
.preprocess(PreProcessSteps().convert_layout())
.network(InputNetworkInfo().set_layout("NCHW")))
.build(f);
EXPECT_EQ(f->get_parameters()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_parameters()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, preprocess_convert_layout_same) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
auto size_old = f->get_ordered_ops().size();
f = PrePostProcessor()
.input(InputInfo()
.tensor(InputTensorInfo().set_layout("NCHW"))
.preprocess(PreProcessSteps().convert_layout("NCHW"))
.network(InputNetworkInfo().set_layout("NCHW")))
.build(f);
EXPECT_EQ(f->get_parameters()[0]->get_layout(), "NCHW");
EXPECT_EQ(f->get_parameters()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 3, 2, 2}));
// Verify that redundant ops were not added
EXPECT_EQ(size_old, f->get_ordered_ops().size());
}
// --- PostProcess - set/convert element type ---
TEST(pre_post_process, postprocess_convert_element_type_explicit) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
auto old_names = f->output().get_tensor().get_names();
f = PrePostProcessor()
.output(OutputInfo().postprocess(PostProcessSteps().convert_element_type(element::u8)))
.build(f);
EXPECT_EQ(f->get_results().size(), 1);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::u8);
EXPECT_EQ(f->output().get_tensor().get_names(), old_names);
EXPECT_EQ(old_names.count("tensor_output1"), 1);
auto ops = f->get_ordered_ops();
auto res_count = std::count_if(ops.begin(), ops.end(), [](std::shared_ptr<ov::Node> n) {
return std::dynamic_pointer_cast<ov::op::v0::Result>(n) != nullptr;
});
EXPECT_EQ(res_count, 1);
auto names_count = std::count_if(ops.begin(), ops.end(), [](std::shared_ptr<ov::Node> n) {
return n->output(0).get_tensor().get_names().count("tensor_output1") > 0;
});
EXPECT_EQ(names_count, 2); // last node + result referencing to it
}
TEST(pre_post_process, postprocess_convert_element_type_default) {
@ -822,10 +921,9 @@ TEST(pre_post_process, exception_safety) {
.preprocess(PreProcessSteps().convert_element_type(element::f32)))
.input(InputInfo(1) // This one is not
.tensor(InputTensorInfo().set_color_format(ColorFormat::NV12_TWO_PLANES))
.preprocess(PreProcessSteps().custom(
[](const std::shared_ptr<Node>& node) -> std::shared_ptr<Node> {
throw ngraph::ngraph_error("test error");
})))
.preprocess(PreProcessSteps().custom([](const Output<Node>& node) -> Output<Node> {
throw ngraph::ngraph_error("test error");
})))
.build(f),
ov::AssertFailure);
EXPECT_EQ(f->get_parameters().size(), 2);