From 543584d5772ffbc0eeebab9454ec4690dd975aec Mon Sep 17 00:00:00 2001 From: Anastasia Kuporosova Date: Fri, 19 Nov 2021 20:19:19 +0300 Subject: [PATCH] [Python API] add_outputs api (#8626) * [Python API] add_outputs api * add tests and missed api for tests * fix codestyle * add documentation * fix code style * fix building * add tensorDescriptor tests * add get_any_name and more checks * descriptor tensor name --- .../src/openvino/descriptor/__init__.py | 4 + .../pyopenvino/graph/descriptors/tensor.cpp | 135 +++++++++++++++++ .../pyopenvino/graph/descriptors/tensor.hpp | 11 ++ .../python/src/pyopenvino/graph/function.cpp | 38 ++++- .../python/src/pyopenvino/graph/node.cpp | 17 +++ .../src/pyopenvino/graph/node_output.hpp | 10 ++ .../python/src/pyopenvino/pyopenvino.cpp | 2 + .../test_inference_engine/test_function.py | 142 ++++++++++++++++++ .../tests/test_ngraph/test_descriptor.py | 17 +++ 9 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 runtime/bindings/python/src/openvino/descriptor/__init__.py create mode 100644 runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.cpp create mode 100644 runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.hpp create mode 100644 runtime/bindings/python/tests/test_inference_engine/test_function.py create mode 100644 runtime/bindings/python/tests/test_ngraph/test_descriptor.py diff --git a/runtime/bindings/python/src/openvino/descriptor/__init__.py b/runtime/bindings/python/src/openvino/descriptor/__init__.py new file mode 100644 index 00000000000..bb222ef384b --- /dev/null +++ b/runtime/bindings/python/src/openvino/descriptor/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from openvino.pyopenvino import DescriptorTensor as Tensor diff --git a/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.cpp b/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.cpp new file mode 100644 index 00000000000..547b9d8d3c6 --- /dev/null +++ b/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "pyopenvino/graph/descriptors/tensor.hpp" + +#include +#include + +#include + +#include "openvino/core/descriptor/tensor.hpp" + +namespace py = pybind11; + +using PyRTMap = std::map>; + +PYBIND11_MAKE_OPAQUE(PyRTMap); + +void regclass_graph_descriptor_Tensor(py::module m) { + py::class_> tensor(m, "DescriptorTensor"); + + tensor.doc() = "openvino.descriptor.Tensor wraps ov::descriptor::Tensor"; + + tensor.def(py::init(), + py::arg("element_type"), + py::arg("partial_shape"), + py::arg("name")); + + tensor.def("get_shape", + &ov::descriptor::Tensor::get_shape, + R"( + Returns the shape description. + + Returns + ---------- + get_shape : Shape + The shape description. + )"); + + tensor.def("get_rt_info", + (PyRTMap & (ov::descriptor::Tensor::*)()) & ov::descriptor::Tensor::get_rt_info, + py::return_value_policy::reference_internal, + R"( + Returns PyRTMap which is a dictionary of user defined runtime info. + + Returns + ---------- + get_rt_info : PyRTMap + A dictionary of user defined data. + )"); + + tensor.def("size", + &ov::descriptor::Tensor::size, + R"( + Returns the size description + + Returns + ---------- + size : size_t + The size description. + )"); + + tensor.def("get_partial_shape", + &ov::descriptor::Tensor::get_partial_shape, + R"( + Returns the partial shape description + + Returns + ---------- + get_partial_shape : PartialShape + PartialShape description. + )"); + + tensor.def("get_element_type", + &ov::descriptor::Tensor::get_element_type, + R"( + Returns the element type description + + Returns + ---------- + get_element_type : Type + Type description + )"); + + tensor.def("get_names", + &ov::descriptor::Tensor::get_names, + R"( + Returns names + + Returns + ---------- + get_names : set + Set of names + )"); + + tensor.def("set_names", + &ov::descriptor::Tensor::set_names, + py::arg("names"), + R"( + Set names for tensor + + Parameters + ---------- + names : set + Set of names + )"); + + tensor.def("get_any_name", + &ov::descriptor::Tensor::get_any_name, + R"( + Returns any of set name + + Returns + ---------- + get_any_name : string + Any name + )"); + + tensor.def_property_readonly("shape", &ov::descriptor::Tensor::get_shape); + + tensor.def_property_readonly("rt_info", + (PyRTMap & (ov::descriptor::Tensor::*)()) & ov::descriptor::Tensor::get_rt_info, + py::return_value_policy::reference_internal); + + tensor.def_property_readonly("size", &ov::descriptor::Tensor::size); + + tensor.def_property_readonly("partial_shape", &ov::descriptor::Tensor::get_partial_shape); + + tensor.def_property_readonly("element_type", &ov::descriptor::Tensor::get_element_type); + + tensor.def_property_readonly("any_name", &ov::descriptor::Tensor::get_any_name); + + tensor.def_property("names", &ov::descriptor::Tensor::get_names, &ov::descriptor::Tensor::set_names); +} diff --git a/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.hpp b/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.hpp new file mode 100644 index 00000000000..bc84742fb90 --- /dev/null +++ b/runtime/bindings/python/src/pyopenvino/graph/descriptors/tensor.hpp @@ -0,0 +1,11 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +namespace py = pybind11; + +void regclass_graph_descriptor_Tensor(py::module m); diff --git a/runtime/bindings/python/src/pyopenvino/graph/function.cpp b/runtime/bindings/python/src/pyopenvino/graph/function.cpp index 27e3d18b0ec..c064ddbdd3c 100644 --- a/runtime/bindings/python/src/pyopenvino/graph/function.cpp +++ b/runtime/bindings/python/src/pyopenvino/graph/function.cpp @@ -14,7 +14,7 @@ namespace py = pybind11; -static const char* CAPSULE_NAME = "ngraph_function"; +static const char* CAPSULE_NAME = "openvino_function"; void set_tensor_names(const ov::ParameterVector& parameters) { for (const auto& param : parameters) { @@ -352,6 +352,42 @@ void regclass_graph_Function(py::module m) { (ov::Output(ov::Function::*)(const std::string&) const) & ov::Function::output, py::arg("tensor_name")); + function.def( + "add_outputs", + [](ov::Function& self, py::handle& outputs) { + int i = 0; + py::list _outputs; + if (!py::isinstance(outputs)) { + if (py::isinstance(outputs)) { + _outputs.append(outputs.cast()); + } else if (py::isinstance(outputs)) { + _outputs.append(outputs.cast()); + } else if (py::isinstance>(outputs)) { + _outputs.append(outputs.cast>()); + } else { + throw py::type_error("Incorrect type of a value to add as output."); + } + } else { + _outputs = outputs.cast(); + } + + for (py::handle output : _outputs) { + if (py::isinstance(_outputs[i])) { + self.add_output(output.cast()); + } else if (py::isinstance(output)) { + py::tuple output_tuple = output.cast(); + self.add_output(output_tuple[0].cast(), output_tuple[1].cast()); + } else if (py::isinstance>(_outputs[i])) { + self.add_output(output.cast>()); + } else { + throw py::type_error("Incorrect type of a value to add as output at index " + std::to_string(i) + + "."); + } + i++; + } + }, + py::arg("outputs")); + function.def("__repr__", [](const ov::Function& self) { std::string class_name = py::cast(self).get_type().attr("__name__").cast(); std::stringstream shapes_ss; diff --git a/runtime/bindings/python/src/pyopenvino/graph/node.cpp b/runtime/bindings/python/src/pyopenvino/graph/node.cpp index 242fd76c1e0..ce20f8147fc 100644 --- a/runtime/bindings/python/src/pyopenvino/graph/node.cpp +++ b/runtime/bindings/python/src/pyopenvino/graph/node.cpp @@ -149,6 +149,23 @@ void regclass_graph_Node(py::module m) { get_output_partial_shape : PartialShape PartialShape of the output i )"); + node.def("get_output_tensor", + &ov::Node::get_output_tensor, + py::arg("i"), + py::return_value_policy::reference_internal, + R"( + Returns the tensor for output i + + Parameters + ---------- + i : int + Index of the output. + + Returns + ---------- + get_output_tensor : descriptor.Tensor + Tensor of the output i + )"); node.def("get_type_name", &ov::Node::get_type_name, R"( diff --git a/runtime/bindings/python/src/pyopenvino/graph/node_output.hpp b/runtime/bindings/python/src/pyopenvino/graph/node_output.hpp index a88722ebc18..27a67e677f3 100644 --- a/runtime/bindings/python/src/pyopenvino/graph/node_output.hpp +++ b/runtime/bindings/python/src/pyopenvino/graph/node_output.hpp @@ -81,4 +81,14 @@ void regclass_graph_Output(py::module m, std::string typestring) get_target_inputs : Set[Input] Set of Inputs. )"); + output.def("get_tensor", + &ov::Output::get_tensor, + py::return_value_policy::reference_internal, + R"( + A reference to the tensor descriptor for this output. + Returns + ---------- + get_tensor : descriptor.Tensor + Tensor of the output. + )"); } diff --git a/runtime/bindings/python/src/pyopenvino/pyopenvino.cpp b/runtime/bindings/python/src/pyopenvino/pyopenvino.cpp index d1c28537a85..6e15c7a4117 100644 --- a/runtime/bindings/python/src/pyopenvino/pyopenvino.cpp +++ b/runtime/bindings/python/src/pyopenvino/pyopenvino.cpp @@ -30,6 +30,7 @@ #include "pyopenvino/core/tensor.hpp" #include "pyopenvino/core/variable_state.hpp" #include "pyopenvino/core/version.hpp" +#include "pyopenvino/graph/descriptors/tensor.hpp" #include "pyopenvino/graph/dimension.hpp" #include "pyopenvino/graph/layout.hpp" #include "pyopenvino/graph/layout_helpers.hpp" @@ -74,6 +75,7 @@ PYBIND11_MODULE(pyopenvino, m) { regclass_graph_AxisSet(m); regclass_graph_AxisVector(m); regclass_graph_Coordinate(m); + regclass_graph_descriptor_Tensor(m); py::module m_op = m.def_submodule("op", "Package ngraph.impl.op that wraps ov::op"); // TODO(!) regclass_graph_op_Constant(m_op); regclass_graph_op_Parameter(m_op); diff --git a/runtime/bindings/python/tests/test_inference_engine/test_function.py b/runtime/bindings/python/tests/test_inference_engine/test_function.py new file mode 100644 index 00000000000..7db18b1fdb0 --- /dev/null +++ b/runtime/bindings/python/tests/test_inference_engine/test_function.py @@ -0,0 +1,142 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import pytest + +import openvino.opset8 as ops + +from openvino import Function +from openvino.descriptor import Tensor +from openvino.impl import PartialShape + + +def test_function_add_outputs_tensor_name(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + assert "relu_t1" in relu1.get_output_tensor(0).names + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + function.add_outputs("relu_t1") + assert len(function.get_results()) == 2 + assert isinstance(function.outputs[1].get_tensor(), Tensor) + assert "relu_t1" in function.outputs[1].get_tensor().names + + +def test_function_add_outputs_op_name(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + function.add_outputs(("relu1", 0)) + assert len(function.get_results()) == 2 + + +def test_function_add_output_port(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + function.add_outputs(relu1.output(0)) + assert len(function.get_results()) == 2 + + +def test_function_add_output_incorrect_tensor_name(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + with pytest.raises(RuntimeError) as e: + function.add_outputs("relu_t") + assert "Tensor name relu_t was not found." in str(e.value) + + +def test_function_add_output_incorrect_idx(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + with pytest.raises(RuntimeError) as e: + function.add_outputs(("relu1", 10)) + assert "Cannot add output to port 10 operation relu1 has only 1 outputs." in str(e.value) + + +def test_function_add_output_incorrect_name(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + with pytest.raises(RuntimeError) as e: + function.add_outputs(("relu_1", 0)) + assert "Port 0 for operation with name relu_1 was not found." in str(e.value) + + +def test_add_outputs_several_tensors(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + relu2.get_output_tensor(0).set_names({"relu_t2"}) + relu3 = ops.relu(relu2, name="relu3") + function = Function(relu3, [param], "TestFunction") + assert len(function.get_results()) == 1 + function.add_outputs(["relu_t1", "relu_t2"]) + assert len(function.get_results()) == 3 + + +def test_add_outputs_several_ports(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + relu2.get_output_tensor(0).set_names({"relu_t2"}) + relu3 = ops.relu(relu2, name="relu3") + function = Function(relu3, [param], "TestFunction") + assert len(function.get_results()) == 1 + function.add_outputs([("relu1", 0), ("relu2", 0)]) + assert len(function.get_results()) == 3 + + +def test_add_outputs_incorrect_value(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + relu2 = ops.relu(relu1, name="relu2") + function = Function(relu2, [param], "TestFunction") + assert len(function.get_results()) == 1 + with pytest.raises(TypeError) as e: + function.add_outputs(0) + assert "Incorrect type of a value to add as output." in str(e.value) + + +def test_add_outputs_incorrect_outputs_list(): + input_shape = PartialShape([1]) + param = ops.parameter(input_shape, dtype=np.float32, name="data") + relu1 = ops.relu(param, name="relu1") + relu1.get_output_tensor(0).set_names({"relu_t1"}) + function = Function(relu1, [param], "TestFunction") + assert len(function.get_results()) == 1 + with pytest.raises(TypeError) as e: + function.add_outputs([0, 0]) + assert "Incorrect type of a value to add as output at index 0" in str(e.value) diff --git a/runtime/bindings/python/tests/test_ngraph/test_descriptor.py b/runtime/bindings/python/tests/test_ngraph/test_descriptor.py new file mode 100644 index 00000000000..0d61ff7205a --- /dev/null +++ b/runtime/bindings/python/tests/test_ngraph/test_descriptor.py @@ -0,0 +1,17 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from openvino.descriptor import Tensor +from openvino.impl import Type, PartialShape + + +def test_tensor_descriptor_api(): + td = Tensor(Type.f32, PartialShape([1, 1, 1, 1]), "tensor_name") + td.names = {"tensor_name"} + assert "tensor_name" in td.names + assert isinstance(td, Tensor) + assert td.element_type == Type.f32 + assert td.partial_shape == PartialShape([1, 1, 1, 1]) + assert repr(td.shape) == "" + assert td.size == 4 + assert td.any_name == "tensor_name"