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:
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user