PrePostProcessor.output() - first implementation of post-processing (#7866)

* PrePostProcessor.output() - first implementation of post-processing
Supported convert_layout, convert_element_type and custom operations

* Fix review comments

* Added test for pre and post processing together
Fix clang-format

* Move 'validate_and_infer_types' before post-processing
This commit is contained in:
Mikhail Nosov
2021-10-06 21:17:10 +03:00
committed by GitHub
parent a4788989f2
commit e20cefb620
12 changed files with 1039 additions and 38 deletions
@@ -62,8 +62,12 @@ static std::shared_ptr<Function> create_simple_function(element::Type type, cons
auto data1 = std::make_shared<op::v0::Parameter>(type, shape);
data1->set_friendly_name("input1");
data1->get_output_tensor(0).set_names({"tensor_input1"});
auto res = std::make_shared<op::v0::Result>(data1);
res->set_friendly_name("Result");
auto c = op::v0::Constant::create(type, {1}, {0});
auto op = std::make_shared<op::v1::Add>(data1, c);
op->set_friendly_name("Add0");
auto res = std::make_shared<op::v0::Result>(op);
res->set_friendly_name("Result1");
res->get_output_tensor(0).set_names({"tensor_output1"});
return std::make_shared<ov::Function>(ResultVector{res}, ParameterVector{data1});
}
@@ -71,13 +75,21 @@ static std::shared_ptr<Function> create_2inputs(element::Type type, const Partia
auto data1 = std::make_shared<op::v0::Parameter>(type, shape);
data1->set_friendly_name("input1");
data1->get_output_tensor(0).set_names({"tensor_input1"});
auto c1 = op::v0::Constant::create(type, {1}, {0});
auto op1 = std::make_shared<op::v1::Add>(data1, c1);
op1->set_friendly_name("Add01");
auto data2 = std::make_shared<op::v0::Parameter>(type, shape);
data2->get_output_tensor(0).set_names({"tensor_input2"});
data2->set_friendly_name("input2");
data1->get_output_tensor(0).set_names({"tensor_input2"});
auto res1 = std::make_shared<op::v0::Result>(data1);
auto c2 = op::v0::Constant::create(type, {1}, {0});
auto op2 = std::make_shared<op::v1::Add>(data2, c2);
op2->set_friendly_name("Add02");
auto res1 = std::make_shared<op::v0::Result>(op1);
res1->set_friendly_name("Result1");
auto res2 = std::make_shared<op::v0::Result>(data2);
res1->get_output_tensor(0).set_names({"tensor_output1"});
auto res2 = std::make_shared<op::v0::Result>(op2);
res2->set_friendly_name("Result2");
res2->get_output_tensor(0).set_names({"tensor_output2"});
return std::make_shared<ov::Function>(ResultVector{res1, res2}, ParameterVector{data1, data2});
}
@@ -676,6 +688,55 @@ static RefPreprocessParams element_type_before_convert_color_nv12() {
return res;
}
static RefPreprocessParams postprocess_2_inputs_basic() {
RefPreprocessParams res("postprocess_2_inputs_basic");
res.function = []() {
auto f = create_2inputs(element::f32, Shape{1, 3, 1, 2});
f = PrePostProcessor()
.output(OutputInfo("tensor_output1")
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout())
.tensor(OutputTensorInfo().set_layout("NHWC")))
.output(OutputInfo("tensor_output2")
.postprocess(PostProcessSteps().convert_element_type())
.tensor(OutputTensorInfo().set_element_type(element::u8)))
.build(f);
return f;
};
res.inputs.emplace_back(Shape{1, 3, 1, 2}, element::f32, std::vector<float>{1.1, 2.1, 3.1, 4.1, 5.1, 6.1});
res.inputs.emplace_back(Shape{1, 3, 1, 2}, element::f32, std::vector<float>{1.1, 2.1, 3.1, 4.1, 5.1, 6.1});
res.expected.emplace_back(Shape{1, 1, 2, 3}, element::f32, std::vector<float>{1.1, 3.1, 5.1, 2.1, 4.1, 6.1});
res.expected.emplace_back(Shape{1, 3, 1, 2}, element::u8, std::vector<uint8_t>{1, 2, 3, 4, 5, 6});
return res;
}
static RefPreprocessParams pre_and_post_processing() {
RefPreprocessParams res("pre_and_post_processing");
res.function = []() {
auto f = create_2inputs(element::f32, Shape{1, 3, 1, 2});
f = PrePostProcessor()
.input(InputInfo(0)
.tensor(InputTensorInfo().set_element_type(element::u8))
.preprocess(PreProcessSteps().convert_element_type(element::f32).mean(1.f)))
.input(InputInfo(1)
.preprocess(PreProcessSteps().scale(2.f)))
.output(OutputInfo("tensor_output1")
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout())
.tensor(OutputTensorInfo().set_layout("NHWC")))
.output(OutputInfo("tensor_output2")
.postprocess(PostProcessSteps().convert_element_type())
.tensor(OutputTensorInfo().set_element_type(element::u8)))
.build(f);
return f;
};
res.inputs.emplace_back(Shape{1, 3, 1, 2}, element::u8, std::vector<uint8_t>{1, 2, 3, 4, 5, 6});
res.inputs.emplace_back(Shape{1, 3, 1, 2}, element::f32, std::vector<float>{2.2, 4.2, 6.2, 2.4, 4.4, 6.4});
res.expected.emplace_back(Shape{1, 1, 2, 3}, element::f32, std::vector<float>{0, 2, 4, 1, 3, 5});
res.expected.emplace_back(Shape{1, 3, 1, 2}, element::u8, std::vector<uint8_t>{1, 2, 3, 1, 2, 3});
return res;
}
std::vector<RefPreprocessParams> allPreprocessTests() {
return std::vector<RefPreprocessParams> {
simple_mean_scale(),
@@ -702,6 +763,8 @@ std::vector<RefPreprocessParams> allPreprocessTests() {
convert_color_nv12_single_plane(),
convert_color_nv12_layout_resize(),
element_type_before_convert_color_nv12(),
postprocess_2_inputs_basic(),
pre_and_post_processing()
};
}
@@ -0,0 +1,98 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/core/core_visibility.hpp"
#include "openvino/core/preprocess/output_network_info.hpp"
#include "openvino/core/preprocess/output_tensor_info.hpp"
#include "openvino/core/preprocess/postprocess_steps.hpp"
namespace ov {
namespace preprocess {
/// \brief Class holding postprocessing information for one output
/// From postprocessing pipeline perspective, each output can be represented as:
/// - Network's output info, (OutputInfo::network)
/// - Postprocessing steps applied to user's input (OutputInfo::postprocess)
/// - User's desired output parameter information, which is a final one after preprocessing (OutputInfo::tensor)
///
/// API has Builder-like style to allow chaining calls in client's code, like
/// \code{.cpp}
/// auto proc = PrePostProcessor().output(InputInfo().network(...).preprocess(...).tensor(...);
/// \endcode
class OPENVINO_API OutputInfo final {
class OutputInfoImpl;
std::unique_ptr<OutputInfoImpl> m_impl;
friend class PrePostProcessor;
public:
/// \brief Empty constructor. Should be used only if network has exactly one output
OutputInfo();
/// \brief Constructor for particular output index of model
///
/// \param output_index Index to address specified output parameter of model
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);
/// \brief Default move constructor
OutputInfo(OutputInfo&&) noexcept;
/// \brief Default move assignment operator
OutputInfo& operator=(OutputInfo&&) noexcept;
/// \brief Default destructor
~OutputInfo();
/// \brief Set network's tensor information for output - Lvalue version
///
/// \param builder Output network tensor information.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo& network(OutputNetworkInfo&& builder) &;
/// \brief Set network's tensor information for output - Rvalue version
///
/// \param builder Output network tensor information.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo&& network(OutputNetworkInfo&& builder) &&;
/// \brief Set postprocessing operations for output - Lvalue version
///
/// \param builder Postprocessing operations.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo& postprocess(PostProcessSteps&& builder) &;
/// \brief Set postprocessing operations for output - Rvalue version
///
/// \param builder Postprocessing operations.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo&& postprocess(PostProcessSteps&& builder) &&;
/// \brief Set final output tensor information for output after postprocessing - Lvalue version
///
/// \param builder Output tensor information.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo& tensor(OutputTensorInfo&& builder) &;
/// \brief Set final output tensor information for output after postprocessing - Rvalue version
///
/// \param builder Output tensor information.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
OutputInfo&& tensor(OutputTensorInfo&& builder) &&;
};
} // namespace preprocess
} // namespace ov
@@ -0,0 +1,65 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/core/core_visibility.hpp"
#include "openvino/core/layout.hpp"
namespace ov {
namespace preprocess {
/// \brief Information about network's output tensor. If all information is already included to loaded network, this
/// info may not be needed. However it can be set to specify additional information about network, like 'layout'.
///
/// Example of usage of network 'layout':
/// Support network has output parameter with shape {1, 3, 224, 224} and `NHWC` layout. User may need to transpose
/// output picture to interleaved format {1, 224, 224, 3}. This can be done with the following code
///
/// \code{.cpp}
/// <network has output parameter with shape {1, 3, 224, 224}>
/// auto proc =
/// PrePostProcessor()
/// .output(OutputInfo()
/// .network(OutputNetworkInfo().set_layout("NCHW")
/// .preprocess(PostProcessSteps().convert_layout("NHWC")))
/// );
/// \endcode
class OPENVINO_API OutputNetworkInfo final {
class OutputNetworkInfoImpl;
std::unique_ptr<OutputNetworkInfoImpl> m_impl;
friend class OutputInfo;
public:
/// \brief Default empty constructor
OutputNetworkInfo();
/// \brief Default move constructor
OutputNetworkInfo(OutputNetworkInfo&&) noexcept;
/// \brief Default move assignment
OutputNetworkInfo& operator=(OutputNetworkInfo&&) noexcept;
/// \brief Default destructor
~OutputNetworkInfo();
/// \brief Set layout for network's output tensor
/// This version allows chaining for Lvalue objects
///
/// \param layout Layout for network's output tensor.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
OutputNetworkInfo& set_layout(const ov::Layout& layout) &;
/// \brief Set layout for network's output tensor
/// This version allows chaining for Rvalue objects
///
/// \param layout Layout for network's output tensor.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
OutputNetworkInfo&& set_layout(const ov::Layout& layout) &&;
};
} // namespace preprocess
} // namespace ov
@@ -0,0 +1,79 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/core/core_visibility.hpp"
#include "openvino/core/layout.hpp"
#include "openvino/core/type/element_type.hpp"
namespace ov {
namespace preprocess {
/// \brief Information about user's desired output tensor. By default, it will be initialized to same data
/// (type/shape/etc) as network's output parameter. User application can override particular parameters (like
/// 'element_type') according to application's data and specify appropriate conversions in post-processing steps
///
/// \code{.cpp}
/// auto proc =
/// PrePostProcessor()
/// .output(OutputInfo()
/// .postprocess(<add steps + conversion to user's output element type>)
/// .tensor(OutputTensorInfo()
/// .set_element_type(ov::element::u8))
/// );
/// \endcode
class OPENVINO_API OutputTensorInfo final {
class OutputTensorInfoImpl;
std::unique_ptr<OutputTensorInfoImpl> m_impl;
friend class OutputInfo;
public:
/// \brief Default empty constructor
OutputTensorInfo();
/// \brief Default move constructor
OutputTensorInfo(OutputTensorInfo&&) noexcept;
/// \brief Default move assignment
OutputTensorInfo& operator=(OutputTensorInfo&&) noexcept;
/// \brief Default destructor
~OutputTensorInfo();
/// \brief Set element type for user's desired output tensor.
/// This version allows chaining for Lvalue objects.
///
/// \param type Element type for user's output tensor.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner.
OutputTensorInfo& set_element_type(const ov::element::Type& type) &;
/// \brief Set element type for user's desired output tensor.
/// This version allows chaining for Rvalue objects.
///
/// \param type Element type for user's output tensor.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner.
OutputTensorInfo&& set_element_type(const ov::element::Type& type) &&;
/// \brief Set layout for user's output tensor.
/// This version allows chaining for Lvalue objects
///
/// \param layout Layout for user's output tensor.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
OutputTensorInfo& set_layout(const ov::Layout& layout) &;
/// \brief Set layout for user's output tensor.
/// This version allows chaining for Rvalue objects
///
/// \param layout Layout for user's output tensor.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
OutputTensorInfo&& set_layout(const ov::Layout& layout) &&;
};
} // namespace preprocess
} // namespace ov
@@ -0,0 +1,116 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/core/core_visibility.hpp"
#include "openvino/core/layout.hpp"
#include "openvino/core/type/element_type.hpp"
namespace ov {
class Node;
namespace preprocess {
/// \brief Postprocessing steps. Each step typically intends adding of some operation to output parameter
/// User application can specify sequence of postprocessing steps in a builder-like manner
/// \code{.cpp}
/// auto proc = PrePostProcessor()
/// .output(OutputInfo()
/// .postprocess(PostProcessSteps()
/// .convert_element_type(element::u8)))
/// );
/// \endcode
class OPENVINO_API PostProcessSteps final {
class PostProcessStepsImpl;
std::unique_ptr<PostProcessStepsImpl> m_impl;
friend class OutputInfo;
public:
/// \brief Default empty constructor
PostProcessSteps();
/// \brief Default move constructor
PostProcessSteps(PostProcessSteps&&) noexcept;
/// \brief Default move assignment operator
PostProcessSteps& operator=(PostProcessSteps&&) noexcept;
/// \brief Default destructor
~PostProcessSteps();
/// \brief Add convert element type post-process operation - Lvalue version
///
/// \param type Desired type of output. If not specified, type will be obtained from 'tensor' output information
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
PostProcessSteps& convert_element_type(const ov::element::Type& type = {}) &;
/// \brief Add convert element type post-process operation - Rvalue version
///
/// \param type Desired type of output. If not specified, type will be obtained from 'tensor' output information
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
PostProcessSteps&& convert_element_type(const ov::element::Type& type = {}) &&;
/// \brief Add 'convert layout' operation to specified layout - Lvalue version.
///
/// \details Adds appropriate 'transpose' operation between network layout and user's desired layout.
/// Current implementation requires source and destination layout to have same number of dimensions
///
/// \example Example: when network data has output in 'NCHW' layout ([1, 3, 224, 224]) but user needs
/// interleaved output image ('NHWC', [1, 224, 224, 3]). Post-processing may look like this:
///
/// \code{.cpp} auto proc =
/// PrePostProcessor()
/// .output(OutputInfo()
/// .network(OutputTensorInfo().set_layout("NCHW")) // Network output is NCHW
/// .postprocess(PostProcessSteps()
/// .convert_layout("NHWC")) // User needs output as NHWC
/// );
/// \endcode
///
/// \param dst_layout New layout after conversion. If not specified - destination layout is obtained from
/// appropriate tensor output properties.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner.
PostProcessSteps& convert_layout(const Layout& dst_layout = {}) &;
/// \brief Add convert_layout operation to network dimensions - Rvalue version.
///
/// \param dst_layout New layout after conversion. If not specified - destination layout is obtained from
/// appropriate tensor output properties.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner.
PostProcessSteps&& convert_layout(const Layout& dst_layout = {}) &&;
/// \brief Signature for custom postprocessing operation. Custom postprocessing operation takes one output node and
/// produces one output node. For more advanced cases, client's code can use transformation passes over ov::Function
/// directly
///
/// \param node Output node for custom post-processing operation
///
/// \return New node after applying custom post-processing operation
using CustomPostprocessOp = std::function<ov::Output<ov::Node>(const ov::Output<ov::Node>& node)>;
/// \brief Add custom post-process operation - Lvalue version
/// Client application can specify callback function for custom action
///
/// \param postprocess_cb Client's custom postprocess operation.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
PostProcessSteps& custom(const CustomPostprocessOp& postprocess_cb) &;
/// \brief Add custom post-process operation - Rvalue version
/// Client application can specify callback function for custom action
///
/// \param postprocess_cb Client's custom postprocess operation.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
PostProcessSteps&& custom(const CustomPostprocessOp& postprocess_cb) &&;
};
} // namespace preprocess
} // namespace ov
@@ -6,6 +6,7 @@
#include "openvino/core/core_visibility.hpp"
#include "openvino/core/preprocess/input_info.hpp"
#include "openvino/core/preprocess/output_info.hpp"
namespace ov {
@@ -42,22 +43,34 @@ public:
/// \brief Default destructor
~PrePostProcessor();
/// \brief Adds pre-processing information and steps to input of model. This method can be used only if ov::Function
/// passed on `build` has only one input
/// \brief Adds pre-processing information and steps to input of model.
///
/// \param builder Pre-processing data for input tensor of model.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
PrePostProcessor& input(InputInfo&& builder) &;
/// \brief Adds pre-processing information and steps to input of model - Rvalue version. This method can be used
/// only if ov::Function passed on `build` has only one input.
/// \brief Adds pre-processing information and steps to input of model - Rvalue version.
///
/// \param builder Pre-processing data for input tensor of model.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
PrePostProcessor&& input(InputInfo&& builder) &&;
/// \brief Adds post-processing information and steps to output of model.
///
/// \param builder Post-processing data for output tensor of model.
///
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner
PrePostProcessor& output(OutputInfo&& builder) &;
/// \brief Adds pre-processing information and steps to input of model - Rvalue version.
///
/// \param builder Post-processing data for output tensor of model.
///
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner
PrePostProcessor&& output(OutputInfo&& builder) &&;
/// \brief Adds pre/post-processing operations to existing function
///
/// \param function Existing function representing loaded model
@@ -4,6 +4,7 @@
#pragma once
#include "openvino/core/layout.hpp"
#include "openvino/op/op.hpp"
namespace ov {
@@ -39,6 +40,12 @@ public:
bool evaluate(const HostTensorVector& outputs, const HostTensorVector& inputs) const override;
bool has_evaluate() const override;
bool constant_fold(OutputVector& output_values, const OutputVector& inputs_values) override;
/// \brief Returns current layout, or empty Layout if it is not set
Layout get_layout() const;
/// \brief Sets layout runtime information to tensor
void set_layout(const Layout& layout);
};
} // namespace v0
} // namespace op
+14
View File
@@ -67,6 +67,20 @@ bool op::Result::constant_fold(OutputVector& output_values, const OutputVector&
return false;
}
ov::Layout op::Result::get_layout() const {
auto it = get_output_tensor(0).get_rt_info().find("LAYOUT");
if (it == get_output_tensor(0).get_rt_info().end()) {
return {};
}
auto layout = std::dynamic_pointer_cast<VariantWrapper<ov::Layout>>(it->second);
OPENVINO_ASSERT(layout, "'LAYOUT' runtime info for node is invalid, use set_layout API");
return layout->get();
}
void op::Result::set_layout(const ov::Layout& layout) {
get_output_tensor(0).get_rt_info()["LAYOUT"] = std::make_shared<VariantWrapper<ov::Layout>>(layout);
}
BWDCMP_RTTI_DEFINITION(ov::AttributeAdapter<ResultVector>);
ov::AttributeAdapter<ResultVector>::AttributeAdapter(ResultVector& ref) : m_ref(ref) {}
+253 -6
View File
@@ -13,10 +13,9 @@
namespace ov {
namespace preprocess {
/// \brief InputTensorInfoImpl - internal data structure
class InputTensorInfo::InputTensorInfoImpl {
class TensorInfoImplBase {
public:
InputTensorInfoImpl() = default;
TensorInfoImplBase() = default;
void set_element_type(const element::Type& type) {
m_type = type;
@@ -40,6 +39,19 @@ public:
return m_layout;
}
protected:
element::Type m_type = element::dynamic;
bool m_type_set = false;
Layout m_layout = Layout();
bool m_layout_set = false;
};
/// \brief InputTensorInfoImpl - internal data structure
class InputTensorInfo::InputTensorInfoImpl : public TensorInfoImplBase {
public:
InputTensorInfoImpl() = default;
bool is_spatial_shape_set() const {
return m_spatial_shape_set;
}
@@ -112,10 +124,12 @@ private:
bool m_spatial_shape_set = false;
};
class OutputTensorInfo::OutputTensorInfoImpl : public TensorInfoImplBase {};
/// \brief InputNetworkInfoImpl - internal data structure
class InputNetworkInfo::InputNetworkInfoImpl {
class NetworkInfoImpl {
public:
InputNetworkInfoImpl() = default;
NetworkInfoImpl() = default;
void set_layout(const Layout& layout) {
m_layout = layout;
@@ -133,6 +147,10 @@ private:
bool m_layout_set = false;
};
class InputNetworkInfo::InputNetworkInfoImpl : public NetworkInfoImpl {};
class OutputNetworkInfo::OutputNetworkInfoImpl : public NetworkInfoImpl {};
/// \brief InputInfoImpl - internal data structure
struct InputInfo::InputInfoImpl {
InputInfoImpl() = default;
@@ -157,6 +175,34 @@ struct InputInfo::InputInfoImpl {
std::shared_ptr<op::v0::Parameter> m_resolved_param;
};
/// \brief OutputInfoImpl - internal data structure
struct OutputInfo::OutputInfoImpl {
OutputInfoImpl() = default;
explicit OutputInfoImpl(size_t idx) : m_has_index(true), m_index(idx) {}
explicit OutputInfoImpl(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() {
m_tensor_data =
std::unique_ptr<OutputTensorInfo::OutputTensorInfoImpl>(new OutputTensorInfo::OutputTensorInfoImpl());
}
bool m_has_index = false;
size_t m_index = 0;
bool m_has_name = false;
std::string m_name;
std::unique_ptr<OutputTensorInfo::OutputTensorInfoImpl> m_tensor_data;
std::unique_ptr<PostProcessSteps::PostProcessStepsImpl> m_postprocess;
std::unique_ptr<OutputNetworkInfo::OutputNetworkInfoImpl> m_network_data;
};
//-------------- 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))) {}
@@ -194,10 +240,52 @@ InputInfo&& InputInfo::network(InputNetworkInfo&& builder) && {
return std::move(*this);
}
//-------------- OutputInfo ------------------
OutputInfo::OutputInfo() : m_impl(std::unique_ptr<OutputInfoImpl>(new OutputInfoImpl)) {}
OutputInfo::OutputInfo(size_t output_index)
: m_impl(std::unique_ptr<OutputInfoImpl>(new OutputInfoImpl(output_index))) {}
OutputInfo::OutputInfo(const std::string& output_tensor_name)
: m_impl(std::unique_ptr<OutputInfoImpl>(new OutputInfoImpl(output_tensor_name))) {}
OutputInfo::OutputInfo(OutputInfo&&) noexcept = default;
OutputInfo& OutputInfo::operator=(OutputInfo&&) noexcept = default;
OutputInfo::~OutputInfo() = default;
OutputInfo& OutputInfo::tensor(OutputTensorInfo&& builder) & {
m_impl->m_tensor_data = std::move(builder.m_impl);
return *this;
}
OutputInfo&& OutputInfo::tensor(OutputTensorInfo&& builder) && {
m_impl->m_tensor_data = std::move(builder.m_impl);
return std::move(*this);
}
OutputInfo&& OutputInfo::postprocess(PostProcessSteps&& builder) && {
m_impl->m_postprocess = std::move(builder.m_impl);
return std::move(*this);
}
OutputInfo& OutputInfo::postprocess(PostProcessSteps&& builder) & {
m_impl->m_postprocess = std::move(builder.m_impl);
return *this;
}
OutputInfo& OutputInfo::network(OutputNetworkInfo&& builder) & {
m_impl->m_network_data = std::move(builder.m_impl);
return *this;
}
OutputInfo&& OutputInfo::network(OutputNetworkInfo&& builder) && {
m_impl->m_network_data = std::move(builder.m_impl);
return std::move(*this);
}
// ------------------------ PrePostProcessor --------------------
struct PrePostProcessor::PrePostProcessorImpl {
public:
std::list<std::unique_ptr<InputInfo::InputInfoImpl>> in_contexts;
std::list<std::unique_ptr<OutputInfo::OutputInfoImpl>> out_contexts;
};
PrePostProcessor::PrePostProcessor() : m_impl(std::unique_ptr<PrePostProcessorImpl>(new PrePostProcessorImpl())) {}
@@ -215,6 +303,16 @@ PrePostProcessor&& PrePostProcessor::input(InputInfo&& builder) && {
return std::move(*this);
}
PrePostProcessor& PrePostProcessor::output(OutputInfo&& builder) & {
m_impl->out_contexts.push_back(std::move(builder.m_impl));
return *this;
}
PrePostProcessor&& PrePostProcessor::output(OutputInfo&& builder) && {
m_impl->out_contexts.push_back(std::move(builder.m_impl));
return std::move(*this);
}
std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function>& function) {
FunctionGuard guard(function);
bool tensor_data_updated = false;
@@ -308,7 +406,7 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
PreprocessingContext context(input->m_tensor_data->get_layout());
context.color_format() = input->m_tensor_data->get_color_format();
context.network_layout() = param->get_layout();
context.target_layout() = param->get_layout();
context.network_shape() = param->get_partial_shape();
// 2. Apply preprocessing
@@ -346,9 +444,73 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
// remove old parameter
function->remove_parameter(param);
}
// Validate nodes after preprocessing if needed (no need to repeat it after post-processing)
if (tensor_data_updated) {
function->validate_nodes_and_infer_types();
}
// Post processing
for (const auto& output : m_impl->out_contexts) {
std::shared_ptr<op::v0::Result> result;
Output<Node> node;
OPENVINO_ASSERT(output, "Internal error: Invalid postprocessing output, please report a problem");
if (output->has_index()) {
node = function->output(output->m_index);
} else if (output->has_name()) {
node = function->output(output->m_name);
} else {
node = function->output();
}
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()) {
result->set_layout(output->m_network_data->get_layout());
}
auto parent = result->get_input_source_output(0);
if (!output->m_tensor_data) {
output->create_tensor_data();
}
PostprocessingContext context(result->get_layout());
if (output->m_tensor_data->is_layout_set()) {
context.target_layout() = output->m_tensor_data->get_layout();
}
if (output->m_tensor_data->is_element_type_set()) {
context.target_element_type() = output->m_tensor_data->get_element_type();
}
// Apply post-processing
node = result->get_input_source_output(0);
if (output->m_postprocess) {
for (const auto& action : output->m_postprocess->actions()) {
auto action_result = action({node}, context);
node = std::get<0>(action_result);
}
}
// Implicit: Convert element type + layout to user's tensor implicitly
PostStepsList implicit_steps;
if (node.get_element_type() != output->m_tensor_data->get_element_type() &&
output->m_tensor_data->is_element_type_set() && node.get_element_type() != element::dynamic) {
implicit_steps.add_convert_impl(output->m_tensor_data->get_element_type());
}
if (!context.target_layout().empty() && context.target_layout() != context.layout()) {
implicit_steps.add_convert_layout_impl(context.target_layout());
}
for (const auto& action : implicit_steps.actions()) {
auto action_result = action({node}, context);
node = std::get<0>(action_result);
}
// Create result
auto new_result = std::make_shared<ov::op::v0::Result>(node);
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());
function->add_results({new_result});
function->remove_result(result);
}
guard.reset();
return function;
}
@@ -560,5 +722,90 @@ PreProcessSteps&& PreProcessSteps::custom(const CustomPreprocessOp& preprocess_c
return std::move(*this);
}
// --------------------- OutputTensorInfo ------------------
OutputTensorInfo::OutputTensorInfo() : m_impl(std::unique_ptr<OutputTensorInfoImpl>(new OutputTensorInfoImpl())) {}
OutputTensorInfo::OutputTensorInfo(OutputTensorInfo&&) noexcept = default;
OutputTensorInfo& OutputTensorInfo::operator=(OutputTensorInfo&&) noexcept = default;
OutputTensorInfo::~OutputTensorInfo() = default;
OutputTensorInfo& OutputTensorInfo::set_element_type(const element::Type& type) & {
m_impl->set_element_type(type);
return *this;
}
OutputTensorInfo&& OutputTensorInfo::set_element_type(const element::Type& type) && {
m_impl->set_element_type(type);
return std::move(*this);
}
OutputTensorInfo& OutputTensorInfo::set_layout(const Layout& layout) & {
m_impl->set_layout(layout);
return *this;
}
OutputTensorInfo&& OutputTensorInfo::set_layout(const Layout& layout) && {
m_impl->set_layout(layout);
return std::move(*this);
}
// --------------------- OutputNetworkInfo ------------------
OutputNetworkInfo::OutputNetworkInfo() : m_impl(std::unique_ptr<OutputNetworkInfoImpl>(new OutputNetworkInfoImpl())) {}
OutputNetworkInfo::OutputNetworkInfo(OutputNetworkInfo&&) noexcept = default;
OutputNetworkInfo& OutputNetworkInfo::operator=(OutputNetworkInfo&&) noexcept = default;
OutputNetworkInfo::~OutputNetworkInfo() = default;
OutputNetworkInfo& OutputNetworkInfo::set_layout(const Layout& layout) & {
m_impl->set_layout(layout);
return *this;
}
OutputNetworkInfo&& OutputNetworkInfo::set_layout(const Layout& layout) && {
m_impl->set_layout(layout);
return std::move(*this);
}
// --------------------- PostProcessSteps ------------------
PostProcessSteps::PostProcessSteps() : m_impl(std::unique_ptr<PostProcessStepsImpl>(new PostProcessStepsImpl())) {}
PostProcessSteps::PostProcessSteps(PostProcessSteps&&) noexcept = default;
PostProcessSteps& PostProcessSteps::operator=(PostProcessSteps&&) noexcept = default;
PostProcessSteps::~PostProcessSteps() = default;
PostProcessSteps& PostProcessSteps::convert_element_type(const element::Type& type) & {
m_impl->add_convert_impl(type);
return *this;
}
PostProcessSteps&& PostProcessSteps::convert_element_type(const element::Type& type) && {
m_impl->add_convert_impl(type);
return std::move(*this);
}
PostProcessSteps& PostProcessSteps::convert_layout(const Layout& dst_layout) & {
m_impl->add_convert_layout_impl(dst_layout);
return *this;
}
PostProcessSteps&& PostProcessSteps::convert_layout(const Layout& dst_layout) && {
m_impl->add_convert_layout_impl(dst_layout);
return std::move(*this);
}
PostProcessSteps& PostProcessSteps::custom(const CustomPostprocessOp& postprocess_cb) & {
// 'true' indicates that custom postprocessing step will trigger validate_and_infer_types
m_impl->actions().emplace_back([postprocess_cb](const Output<ov::Node>& node, PostprocessingContext&) {
return std::make_tuple(postprocess_cb(node), true);
});
return *this;
}
PostProcessSteps&& PostProcessSteps::custom(const CustomPostprocessOp& postprocess_cb) && {
// 'true' indicates that custom postprocessing step will trigger validate_and_infer_types
m_impl->actions().emplace_back([postprocess_cb](const Output<ov::Node>& node, PostprocessingContext&) {
return std::make_tuple(postprocess_cb(node), true);
});
return std::move(*this);
}
} // namespace preprocess
} // namespace ov
@@ -166,10 +166,8 @@ void PreProcessSteps::PreProcessStepsImpl::add_convert_layout_impl(const Layout&
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'. Current format is '",
color_format_name(context.color_format()),
"'");
Layout dst_layout = layout.empty() ? context.network_layout() : layout;
"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 =
@@ -238,5 +236,39 @@ void PreProcessSteps::PreProcessStepsImpl::add_convert_color_impl(const ColorFor
true));
}
//------------- 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;
if (t == element::Type{}) {
t = ctxt.target_element_type();
}
if (t == node.get_node()->get_element_type()) {
return std::make_tuple(node, false);
}
OPENVINO_ASSERT(
!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);
});
}
void PostStepsList::add_convert_layout_impl(const Layout& layout) {
m_actions.emplace_back([layout](const ov::Output<Node>& node, PostprocessingContext& context) {
Layout dst_layout = layout.empty() ? context.target_layout() : layout;
if (dst_layout == context.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");
context.layout() = dst_layout; // Update context's current layout
return std::make_tuple(ov::Output<ov::Node>(transpose), true);
});
}
} // namespace preprocess
} // namespace ov
@@ -10,6 +10,7 @@
#include "openvino/core/node.hpp"
#include "openvino/core/partial_shape.hpp"
#include "openvino/core/preprocess/color_format.hpp"
#include "openvino/core/preprocess/postprocess_steps.hpp"
#include "openvino/core/preprocess/preprocess_steps.hpp"
#include "tensor_name_util.hpp"
@@ -84,11 +85,11 @@ inline void inherit_friendly_names(const std::shared_ptr<ov::Function>& function
dst_node->output(0).get_tensor().set_names(new_names);
}
/// \brief Preprocessing context passed to each preprocessing operation.
/// \brief Context passed to each pre/post-processing operation.
/// This is internal structure which is not shared to custom operations yet.
class PreprocessingContext {
class PrePostProcessingContextBase {
public:
explicit PreprocessingContext(Layout layout) : m_layout(std::move(layout)) {}
explicit PrePostProcessingContextBase(Layout layout) : m_layout(std::move(layout)) {}
const Layout& layout() const {
return m_layout;
@@ -98,6 +99,37 @@ public:
return m_layout;
}
// Final layout. Needed if user specified convert_layout without arguments
// For preprocessing it is parameter's network layout
// For post-processing it is result's tensor layout
const Layout& target_layout() const {
return m_target_layout;
}
Layout& target_layout() {
return m_target_layout;
}
element::Type target_element_type() const {
return m_target_element_type;
}
element::Type& target_element_type() {
return m_target_element_type;
}
protected:
Layout m_layout;
Layout m_target_layout;
element::Type m_target_element_type;
};
/// \brief Preprocessing context passed to each preprocessing operation.
/// This is internal structure which is not shared to custom operations yet.
class PreprocessingContext : public PrePostProcessingContextBase {
public:
explicit PreprocessingContext(const Layout& layout) : PrePostProcessingContextBase(layout) {}
const PartialShape& network_shape() const {
return m_network_shape;
}
@@ -106,23 +138,15 @@ public:
return m_network_shape;
}
const Layout& network_layout() const {
return m_network_layout;
}
Layout& network_layout() {
return m_network_layout;
}
size_t get_network_height_for_resize() const {
auto network_height_idx = get_and_check_height_idx(network_layout(), network_shape());
auto network_height_idx = get_and_check_height_idx(target_layout(), network_shape());
OPENVINO_ASSERT(network_shape()[network_height_idx].is_static(),
"Dynamic resize: Network height dimension shall be static");
return network_shape()[network_height_idx].get_length();
}
size_t get_network_width_for_resize() const {
auto network_width_idx = get_and_check_width_idx(network_layout(), network_shape());
auto network_width_idx = get_and_check_width_idx(target_layout(), network_shape());
OPENVINO_ASSERT(network_shape()[network_width_idx].is_static(),
"Dynamic resize: Network width dimension shall be static");
return network_shape()[network_width_idx].get_length();
@@ -137,7 +161,6 @@ public:
}
private:
Layout m_layout;
PartialShape m_network_shape;
Layout m_network_layout;
ColorFormat m_color_format = ColorFormat::UNDEFINED;
@@ -169,5 +192,33 @@ private:
std::list<std::tuple<InternalPreprocessOp, bool>> m_actions;
};
//------ Post process -----
class PostprocessingContext : public PrePostProcessingContextBase {
public:
explicit PostprocessingContext(const Layout& layout) : PrePostProcessingContextBase(layout) {}
};
using InternalPostprocessOp = std::function<std::tuple<ov::Output<ov::Node>, bool>(const ov::Output<ov::Node>& node,
PostprocessingContext& context)>;
/// \brief PostProcessStepsImpl - internal data structure
class PostStepsList {
public:
void add_convert_impl(const element::Type& type);
void add_convert_layout_impl(const Layout& layout);
const std::list<InternalPostprocessOp>& actions() const {
return m_actions;
}
std::list<InternalPostprocessOp>& actions() {
return m_actions;
}
private:
std::list<InternalPostprocessOp> m_actions;
};
class PostProcessSteps::PostProcessStepsImpl : public PostStepsList {};
} // namespace preprocess
} // namespace ov
+221 -5
View File
@@ -18,8 +18,11 @@ static std::shared_ptr<Function> create_simple_function(element::Type type, cons
auto data1 = std::make_shared<op::v0::Parameter>(type, shape);
data1->set_friendly_name("input1");
data1->get_output_tensor(0).set_names({"tensor_input1"});
auto res = std::make_shared<op::v0::Result>(data1);
res->set_friendly_name("Result");
auto op = std::make_shared<op::v0::Relu>(data1);
op->set_friendly_name("Relu");
auto res = std::make_shared<op::v0::Result>(op);
res->set_friendly_name("Result1");
res->get_output_tensor(0).set_names({"tensor_output1"});
return std::make_shared<Function>(ResultVector{res}, ParameterVector{data1});
}
@@ -27,13 +30,19 @@ static std::shared_ptr<Function> create_2inputs(element::Type type, const Partia
auto data1 = std::make_shared<op::v0::Parameter>(type, shape);
data1->set_friendly_name("input1");
data1->get_output_tensor(0).set_names({"tensor_input1"});
auto op1 = std::make_shared<op::v0::Relu>(data1);
op1->set_friendly_name("Relu1");
auto data2 = std::make_shared<op::v0::Parameter>(type, shape);
data2->set_friendly_name("input2");
data1->get_output_tensor(0).set_names({"tensor_input2"});
auto res1 = std::make_shared<op::v0::Result>(data1);
data2->get_output_tensor(0).set_names({"tensor_input2"});
auto op2 = std::make_shared<op::v0::Relu>(data2);
op2->set_friendly_name("Relu2");
auto res1 = std::make_shared<op::v0::Result>(op1);
res1->set_friendly_name("Result1");
auto res2 = std::make_shared<op::v0::Result>(data2);
res1->get_output_tensor(0).set_names({"tensor_output1"});
auto res2 = std::make_shared<op::v0::Result>(op2);
res2->set_friendly_name("Result2");
res2->get_output_tensor(0).set_names({"tensor_output2"});
return std::make_shared<Function>(ResultVector{res1, res2}, ParameterVector{data1, data2});
}
@@ -594,6 +603,213 @@ TEST(pre_post_process, resize_no_tensor_width) {
ov::AssertFailure);
}
// --- 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});
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);
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);
}
TEST(pre_post_process, postprocess_convert_element_type_default) {
auto f = create_2inputs(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.output(OutputInfo(1)
.postprocess(PostProcessSteps().convert_element_type())
.tensor(OutputTensorInfo().set_element_type(element::u8)))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::f32);
EXPECT_EQ(f->get_results()[1]->get_element_type(), element::u8);
}
TEST(pre_post_process, postprocess_convert_element_type_same) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
auto size_old = f->get_ordered_ops().size();
f = PrePostProcessor()
.output(OutputInfo("tensor_output1")
.postprocess(PostProcessSteps().convert_element_type(element::f32))
.tensor(OutputTensorInfo().set_element_type(element::f32)))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::f32);
// Verify that redundant ops were not added
EXPECT_EQ(size_old, f->get_ordered_ops().size());
}
TEST(pre_post_process, postprocess_convert_element_type_default_error) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
EXPECT_THROW(
f = PrePostProcessor().output(OutputInfo().postprocess(PostProcessSteps().convert_element_type())).build(f),
ov::AssertFailure);
}
TEST(pre_post_process, postprocess_convert_element_type_implicit) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor().output(OutputInfo().tensor(OutputTensorInfo().set_element_type(element::u8))).build(f);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::u8);
}
// --- PostProcess - set/convert layout ---
TEST(pre_post_process, postprocess_set_layout_network) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor().output(OutputInfo().network(OutputNetworkInfo().set_layout("NCHW"))).build(f);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NCHW");
}
TEST(pre_post_process, postprocess_set_layout_tensor) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
// no layout is specified for network, no way to implicitly convert it to user's layout
EXPECT_THROW(f = PrePostProcessor().output(OutputInfo().tensor(OutputTensorInfo().set_layout("NHWC"))).build(f),
ov::AssertFailure);
}
TEST(pre_post_process, postprocess_convert_layout_implicit) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.output(OutputInfo()
.network(OutputNetworkInfo().set_layout("NCHW"))
.tensor(OutputTensorInfo().set_layout("NHWC")))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_results()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, postprocess_convert_layout_explicit_no_target) {
auto f = create_2inputs(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.output(OutputInfo(1)
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout("NHWC")))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 3, 2, 2}));
EXPECT_EQ(f->get_results()[1]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, postprocess_convert_layout_default) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.output(OutputInfo()
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout())
.tensor(OutputTensorInfo().set_layout("NHWC")))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_results()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, postprocess_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()
.output(OutputInfo()
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout("NCHW"))
.tensor(OutputTensorInfo().set_layout("NCHW")))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NCHW");
EXPECT_EQ(f->get_results()[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());
}
TEST(pre_post_process, postprocess_convert_layout_default_error) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
EXPECT_THROW(f = PrePostProcessor()
.output(OutputInfo()
.network(OutputNetworkInfo().set_layout("NCHW"))
.postprocess(PostProcessSteps().convert_layout()))
.build(f),
ov::AssertFailure);
}
// Postprocessing - other
TEST(pre_post_process, postprocess_custom_step) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
std::string name;
f = PrePostProcessor()
.output(OutputInfo().postprocess(
PostProcessSteps().custom([&name](const ov::Output<Node>& node) -> ov::Output<Node> {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node.get_node()->get_friendly_name() + "/abs");
name = node.get_node()->get_friendly_name() + "/abs";
return abs;
})))
.build(f);
EXPECT_FALSE(name.empty());
EXPECT_EQ(f->get_results()[0]->get_input_source_output(0).get_node()->get_friendly_name(), name);
}
TEST(pre_post_process, postprocess_implicit_convert_element_type_and_layout) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
f = PrePostProcessor()
.output(OutputInfo()
.network(OutputNetworkInfo().set_layout("NCHW"))
.tensor(OutputTensorInfo().set_layout("NHWC").set_element_type(element::u8)))
.build(f);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::u8);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_results()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
}
TEST(pre_post_process, postprocess_assert_output_without_index) {
auto f = create_2inputs(element::f32, Shape{1, 3, 2, 2});
auto out = OutputInfo();
EXPECT_ANY_THROW(f = PrePostProcessor().output(std::move(out)).build(f));
out = OutputInfo("some_non_existing_name");
EXPECT_ANY_THROW(f = PrePostProcessor().output(std::move(out)).build(f));
}
TEST(pre_post_process, postprocess_lvalues_1) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
bool custom_called = false;
auto netInfo = OutputNetworkInfo();
netInfo.set_layout("NCHW");
auto steps = PostProcessSteps();
steps.convert_layout();
steps.convert_element_type();
steps.custom([&custom_called](const ov::Output<Node>& node) -> ov::Output<Node> {
auto abs = std::make_shared<op::v0::Abs>(node);
abs->set_friendly_name(node.get_node()->get_friendly_name() + "/abs");
custom_called = true;
return abs;
});
auto tensorInfo = OutputTensorInfo();
tensorInfo.set_layout("NHWC");
tensorInfo.set_element_type(element::u8);
auto outputInfo = OutputInfo("tensor_output1");
outputInfo.network(std::move(netInfo));
outputInfo.postprocess(std::move(steps));
outputInfo.tensor(std::move(tensorInfo));
auto p = PrePostProcessor();
p.output(std::move(outputInfo));
f = p.build(f);
EXPECT_EQ(f->get_results().size(), 1);
EXPECT_EQ(f->output().get_tensor().get_names().count("tensor_output1"), 1);
EXPECT_EQ(f->get_results()[0]->get_element_type(), element::u8);
EXPECT_EQ(f->get_results()[0]->get_layout(), "NHWC");
EXPECT_EQ(f->get_results()[0]->get_output_tensor(0).get_partial_shape(), (PartialShape{1, 2, 2, 3}));
EXPECT_TRUE(custom_called);
}
TEST(pre_post_process, exception_safety) {
auto f = create_2inputs(element::f32, Shape{1, 3, 224, 224});
auto name0 = f->get_parameters()[0]->get_friendly_name();