[PyOV] OVDict class - new return value from inference (#16370)
This commit is contained in:
parent
8509d0dd82
commit
4561aa7109
@ -1 +1,2 @@
|
||||
numpy>=1.16.6
|
||||
singledispatchmethod; python_version<'3.8'
|
||||
|
@ -40,3 +40,4 @@ types-pkg_resources
|
||||
wheel>=0.38.1
|
||||
protobuf~=3.18.1
|
||||
numpy>=1.16.6,<=1.23.4
|
||||
singledispatchmethod; python_version<'3.8'
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Copyright (C) 2018-2023 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from functools import singledispatch
|
||||
from typing import Any, Iterable, Union, Dict, Optional
|
||||
from pathlib import Path
|
||||
|
||||
@ -16,6 +15,7 @@ from openvino._pyopenvino import ConstOutput
|
||||
from openvino._pyopenvino import Tensor
|
||||
|
||||
from openvino.runtime.utils.data_helpers import (
|
||||
OVDict,
|
||||
_InferRequestWrapper,
|
||||
_data_dispatch,
|
||||
tensor_from_file,
|
||||
@ -25,7 +25,7 @@ from openvino.runtime.utils.data_helpers import (
|
||||
class InferRequest(_InferRequestWrapper):
|
||||
"""InferRequest class represents infer request which can be run in asynchronous or synchronous manners."""
|
||||
|
||||
def infer(self, inputs: Any = None, shared_memory: bool = False) -> dict:
|
||||
def infer(self, inputs: Any = None, shared_memory: bool = False) -> OVDict:
|
||||
"""Infers specified input(s) in synchronous mode.
|
||||
|
||||
Blocks all methods of InferRequest while request is running.
|
||||
@ -68,14 +68,14 @@ class InferRequest(_InferRequestWrapper):
|
||||
|
||||
Default value: False
|
||||
:type shared_memory: bool, optional
|
||||
:return: Dictionary of results from output tensors with ports as keys.
|
||||
:rtype: Dict[openvino.runtime.ConstOutput, numpy.ndarray]
|
||||
:return: Dictionary of results from output tensors with port/int/str keys.
|
||||
:rtype: OVDict
|
||||
"""
|
||||
return super().infer(_data_dispatch(
|
||||
return OVDict(super().infer(_data_dispatch(
|
||||
self,
|
||||
inputs,
|
||||
is_shared=shared_memory,
|
||||
))
|
||||
)))
|
||||
|
||||
def start_async(
|
||||
self,
|
||||
@ -138,6 +138,15 @@ class InferRequest(_InferRequestWrapper):
|
||||
userdata,
|
||||
)
|
||||
|
||||
@property
|
||||
def results(self) -> OVDict:
|
||||
"""Gets all outputs tensors of this InferRequest.
|
||||
|
||||
:return: Dictionary of results from output tensors with ports as keys.
|
||||
:rtype: Dict[openvino.runtime.ConstOutput, numpy.array]
|
||||
"""
|
||||
return OVDict(super().results)
|
||||
|
||||
|
||||
class CompiledModel(CompiledModelBase):
|
||||
"""CompiledModel class.
|
||||
@ -161,7 +170,7 @@ class CompiledModel(CompiledModelBase):
|
||||
"""
|
||||
return InferRequest(super().create_infer_request())
|
||||
|
||||
def infer_new_request(self, inputs: Union[dict, list, tuple, Tensor, np.ndarray] = None) -> dict:
|
||||
def infer_new_request(self, inputs: Union[dict, list, tuple, Tensor, np.ndarray] = None) -> OVDict:
|
||||
"""Infers specified input(s) in synchronous mode.
|
||||
|
||||
Blocks all methods of CompiledModel while request is running.
|
||||
@ -187,8 +196,8 @@ class CompiledModel(CompiledModelBase):
|
||||
|
||||
:param inputs: Data to be set on input tensors.
|
||||
:type inputs: Union[Dict[keys, values], List[values], Tuple[values], Tensor, numpy.ndarray], optional
|
||||
:return: Dictionary of results from output tensors with ports as keys.
|
||||
:rtype: Dict[openvino.runtime.ConstOutput, numpy.array]
|
||||
:return: Dictionary of results from output tensors with port/int/str keys.
|
||||
:rtype: OVDict
|
||||
"""
|
||||
# It returns wrapped python InferReqeust and then call upon
|
||||
# overloaded functions of InferRequest class
|
||||
@ -196,7 +205,7 @@ class CompiledModel(CompiledModelBase):
|
||||
|
||||
def __call__(self,
|
||||
inputs: Union[dict, list, tuple, Tensor, np.ndarray] = None,
|
||||
shared_memory: bool = True) -> dict:
|
||||
shared_memory: bool = True) -> OVDict:
|
||||
"""Callable infer wrapper for CompiledModel.
|
||||
|
||||
Infers specified input(s) in synchronous mode.
|
||||
@ -248,8 +257,8 @@ class CompiledModel(CompiledModelBase):
|
||||
Default value: True
|
||||
:type shared_memory: bool, optional
|
||||
|
||||
:return: Dictionary of results from output tensors with ports as keys.
|
||||
:rtype: Dict[openvino.runtime.ConstOutput, numpy.ndarray]
|
||||
:return: Dictionary of results from output tensors with port/int/str as keys.
|
||||
:rtype: OVDict
|
||||
"""
|
||||
if self._infer_request is None:
|
||||
self._infer_request = self.create_infer_request()
|
||||
|
@ -5,3 +5,4 @@
|
||||
from openvino.runtime.utils.data_helpers.data_dispatcher import _data_dispatch
|
||||
from openvino.runtime.utils.data_helpers.wrappers import tensor_from_file
|
||||
from openvino.runtime.utils.data_helpers.wrappers import _InferRequestWrapper
|
||||
from openvino.runtime.utils.data_helpers.wrappers import OVDict
|
||||
|
@ -4,7 +4,17 @@
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openvino._pyopenvino import Tensor
|
||||
# TODO: remove this WA and refactor OVDict when Python3.8
|
||||
# becomes minimal supported version.
|
||||
try:
|
||||
from functools import singledispatchmethod
|
||||
except ImportError:
|
||||
from singledispatchmethod import singledispatchmethod # type: ignore[no-redef]
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Union, Dict, List, Iterator, KeysView, ItemsView, ValuesView
|
||||
|
||||
from openvino._pyopenvino import Tensor, ConstOutput
|
||||
from openvino._pyopenvino import InferRequest as InferRequestBase
|
||||
|
||||
|
||||
@ -20,3 +30,109 @@ class _InferRequestWrapper(InferRequestBase):
|
||||
# Private memeber to store newly created shared memory data
|
||||
self._inputs_data = None
|
||||
super().__init__(other)
|
||||
|
||||
|
||||
class OVDict(Mapping):
|
||||
"""Custom OpenVINO dictionary with inference results.
|
||||
|
||||
This class is a dict-like object. It provides possibility to
|
||||
address data tensors with three key types:
|
||||
|
||||
* `openvino.runtime.ConstOutput` - port of the output
|
||||
* `int` - index of the output
|
||||
* `str` - names of the output
|
||||
|
||||
This class follows `frozenset`/`tuple` concept of immutability.
|
||||
It is prohibited to assign new items or edit them.
|
||||
|
||||
To revert to the previous behavior use `to_dict` method which
|
||||
return shallow copy of underlaying dictionary.
|
||||
Note: It removes addressing feature! New dictionary keeps
|
||||
only `ConstOutput` keys.
|
||||
|
||||
If a tuple returns value is needed, use `to_tuple` method which
|
||||
converts values to the tuple.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Reverts to the previous behavior of the native dict
|
||||
result = request.infer(inputs).to_dict()
|
||||
# or alternatively:
|
||||
result = dict(request.infer(inputs))
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# To dispatch outputs of multi-ouput inference:
|
||||
out1, out2, out3, _ = request.infer(inputs).values()
|
||||
# or alternatively:
|
||||
out1, out2, out3, _ = request.infer(inputs).to_tuple()
|
||||
"""
|
||||
def __init__(self, _dict: Dict[ConstOutput, np.ndarray]) -> None:
|
||||
self._dict = _dict
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
return self._dict.__iter__()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._dict)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self._dict.__repr__()
|
||||
|
||||
def __get_key(self, index: int) -> ConstOutput:
|
||||
return list(self._dict.keys())[index]
|
||||
|
||||
@singledispatchmethod
|
||||
def __getitem_impl(self, key: Union[ConstOutput, int, str]) -> np.ndarray:
|
||||
raise TypeError("Unknown key type!")
|
||||
|
||||
@__getitem_impl.register
|
||||
def _(self, key: ConstOutput) -> np.ndarray:
|
||||
return self._dict[key]
|
||||
|
||||
@__getitem_impl.register
|
||||
def _(self, key: int) -> np.ndarray:
|
||||
try:
|
||||
return self._dict[self.__get_key(key)]
|
||||
except IndexError:
|
||||
raise KeyError(key)
|
||||
|
||||
@__getitem_impl.register
|
||||
def _(self, key: str) -> np.ndarray:
|
||||
try:
|
||||
return self._dict[self.__get_key(self.names().index(key))]
|
||||
except ValueError:
|
||||
raise KeyError(key)
|
||||
|
||||
def __getitem__(self, key: Union[ConstOutput, int, str]) -> np.ndarray:
|
||||
return self.__getitem_impl(key)
|
||||
|
||||
def keys(self) -> KeysView[ConstOutput]:
|
||||
return self._dict.keys()
|
||||
|
||||
def values(self) -> ValuesView[np.ndarray]:
|
||||
return self._dict.values()
|
||||
|
||||
def items(self) -> ItemsView[ConstOutput, np.ndarray]:
|
||||
return self._dict.items()
|
||||
|
||||
def names(self) -> List[str]:
|
||||
"""Return a name of every output key.
|
||||
|
||||
Throws RuntimeError if any of ConstOutput keys has no name.
|
||||
"""
|
||||
return [key.get_any_name() for key in self._dict.keys()]
|
||||
|
||||
def to_dict(self) -> Dict[ConstOutput, np.ndarray]:
|
||||
"""Return underlaying native dictionary.
|
||||
|
||||
Function performs shallow copy, thus any modifications to
|
||||
returned values may affect this class as well.
|
||||
"""
|
||||
return self._dict
|
||||
|
||||
def to_tuple(self) -> tuple:
|
||||
"""Convert values of this dictionary to a tuple."""
|
||||
return tuple(self._dict.values())
|
||||
|
@ -53,6 +53,27 @@ const std::map<std::string, ov::element::Type>& dtype_to_ov_type() {
|
||||
return dtype_to_ov_type_mapping;
|
||||
}
|
||||
|
||||
namespace containers {
|
||||
const TensorIndexMap cast_to_tensor_index_map(const py::dict& inputs) {
|
||||
TensorIndexMap result_map;
|
||||
for (auto&& input : inputs) {
|
||||
int idx;
|
||||
if (py::isinstance<py::int_>(input.first)) {
|
||||
idx = input.first.cast<int>();
|
||||
} else {
|
||||
throw py::type_error("incompatible function arguments!");
|
||||
}
|
||||
if (py::isinstance<ov::Tensor>(input.second)) {
|
||||
auto tensor = Common::cast_to_tensor(input.second);
|
||||
result_map[idx] = tensor;
|
||||
} else {
|
||||
throw ov::Exception("Unable to cast tensor " + std::to_string(idx) + "!");
|
||||
}
|
||||
}
|
||||
return result_map;
|
||||
}
|
||||
}; // namespace containers
|
||||
|
||||
namespace array_helpers {
|
||||
|
||||
bool is_contiguous(const py::array& array) {
|
||||
@ -110,6 +131,67 @@ py::array as_contiguous(py::array& array, ov::element::Type type) {
|
||||
}
|
||||
}
|
||||
|
||||
py::array array_from_tensor(ov::Tensor&& t) {
|
||||
switch (t.get_element_type()) {
|
||||
case ov::element::Type_t::f32: {
|
||||
return py::array_t<float>(t.get_shape(), t.data<float>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::f64: {
|
||||
return py::array_t<double>(t.get_shape(), t.data<double>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::bf16: {
|
||||
return py::array(py::dtype("float16"), t.get_shape(), t.data<ov::bfloat16>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::f16: {
|
||||
return py::array(py::dtype("float16"), t.get_shape(), t.data<ov::float16>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i8: {
|
||||
return py::array_t<int8_t>(t.get_shape(), t.data<int8_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i16: {
|
||||
return py::array_t<int16_t>(t.get_shape(), t.data<int16_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i32: {
|
||||
return py::array_t<int32_t>(t.get_shape(), t.data<int32_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i64: {
|
||||
return py::array_t<int64_t>(t.get_shape(), t.data<int64_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u8: {
|
||||
return py::array_t<uint8_t>(t.get_shape(), t.data<uint8_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u16: {
|
||||
return py::array_t<uint16_t>(t.get_shape(), t.data<uint16_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u32: {
|
||||
return py::array_t<uint32_t>(t.get_shape(), t.data<uint32_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u64: {
|
||||
return py::array_t<uint64_t>(t.get_shape(), t.data<uint64_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::boolean: {
|
||||
return py::array_t<bool>(t.get_shape(), t.data<bool>());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw ov::Exception("Numpy array cannot be created from given OV Tensor!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace array_helpers
|
||||
|
||||
template <>
|
||||
@ -226,38 +308,6 @@ const ov::Tensor& cast_to_tensor(const py::handle& tensor) {
|
||||
return tensor.cast<const ov::Tensor&>();
|
||||
}
|
||||
|
||||
const Containers::TensorNameMap cast_to_tensor_name_map(const py::dict& inputs) {
|
||||
Containers::TensorNameMap result_map;
|
||||
for (auto&& input : inputs) {
|
||||
std::string name;
|
||||
if (py::isinstance<py::str>(input.first)) {
|
||||
name = input.first.cast<std::string>();
|
||||
} else {
|
||||
throw py::type_error("incompatible function arguments!");
|
||||
}
|
||||
OPENVINO_ASSERT(py::isinstance<ov::Tensor>(input.second), "Unable to cast tensor ", name, "!");
|
||||
auto tensor = Common::cast_to_tensor(input.second);
|
||||
result_map[name] = tensor;
|
||||
}
|
||||
return result_map;
|
||||
}
|
||||
|
||||
const Containers::TensorIndexMap cast_to_tensor_index_map(const py::dict& inputs) {
|
||||
Containers::TensorIndexMap result_map;
|
||||
for (auto&& input : inputs) {
|
||||
int idx;
|
||||
if (py::isinstance<py::int_>(input.first)) {
|
||||
idx = input.first.cast<int>();
|
||||
} else {
|
||||
throw py::type_error("incompatible function arguments!");
|
||||
}
|
||||
OPENVINO_ASSERT(py::isinstance<ov::Tensor>(input.second), "Unable to cast tensor ", idx, "!");
|
||||
auto tensor = Common::cast_to_tensor(input.second);
|
||||
result_map[idx] = tensor;
|
||||
}
|
||||
return result_map;
|
||||
}
|
||||
|
||||
void set_request_tensors(ov::InferRequest& request, const py::dict& inputs) {
|
||||
if (!inputs.empty()) {
|
||||
for (auto&& input : inputs) {
|
||||
@ -293,67 +343,10 @@ uint32_t get_optimal_number_of_requests(const ov::CompiledModel& actual) {
|
||||
}
|
||||
}
|
||||
|
||||
py::dict outputs_to_dict(const std::vector<ov::Output<const ov::Node>>& outputs, ov::InferRequest& request) {
|
||||
py::dict outputs_to_dict(InferRequestWrapper& request) {
|
||||
py::dict res;
|
||||
for (const auto& out : outputs) {
|
||||
ov::Tensor t{request.get_tensor(out)};
|
||||
switch (t.get_element_type()) {
|
||||
case ov::element::Type_t::i8: {
|
||||
res[py::cast(out)] = py::array_t<int8_t>(t.get_shape(), t.data<int8_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i16: {
|
||||
res[py::cast(out)] = py::array_t<int16_t>(t.get_shape(), t.data<int16_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i32: {
|
||||
res[py::cast(out)] = py::array_t<int32_t>(t.get_shape(), t.data<int32_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::i64: {
|
||||
res[py::cast(out)] = py::array_t<int64_t>(t.get_shape(), t.data<int64_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u8: {
|
||||
res[py::cast(out)] = py::array_t<uint8_t>(t.get_shape(), t.data<uint8_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u16: {
|
||||
res[py::cast(out)] = py::array_t<uint16_t>(t.get_shape(), t.data<uint16_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u32: {
|
||||
res[py::cast(out)] = py::array_t<uint32_t>(t.get_shape(), t.data<uint32_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::u64: {
|
||||
res[py::cast(out)] = py::array_t<uint64_t>(t.get_shape(), t.data<uint64_t>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::bf16: {
|
||||
res[py::cast(out)] = py::array(py::dtype("float16"), t.get_shape(), t.data<ov::bfloat16>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::f16: {
|
||||
res[py::cast(out)] = py::array(py::dtype("float16"), t.get_shape(), t.data<ov::float16>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::f32: {
|
||||
res[py::cast(out)] = py::array_t<float>(t.get_shape(), t.data<float>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::f64: {
|
||||
res[py::cast(out)] = py::array_t<double>(t.get_shape(), t.data<double>());
|
||||
break;
|
||||
}
|
||||
case ov::element::Type_t::boolean: {
|
||||
res[py::cast(out)] = py::array_t<bool>(t.get_shape(), t.data<bool>());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& out : request.m_outputs) {
|
||||
res[py::cast(out)] = array_helpers::array_from_tensor(request.m_request.get_tensor(out));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -20,14 +20,20 @@
|
||||
#include "openvino/runtime/infer_request.hpp"
|
||||
#include "openvino/runtime/tensor.hpp"
|
||||
#include "openvino/pass/serialize.hpp"
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
#include "pyopenvino/graph/any.hpp"
|
||||
#include "pyopenvino/graph/ops/constant.hpp"
|
||||
#include "pyopenvino/core/infer_request.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace containers {
|
||||
using TensorIndexMap = std::map<size_t, ov::Tensor>;
|
||||
|
||||
const TensorIndexMap cast_to_tensor_index_map(const py::dict& inputs);
|
||||
}; // namespace containers
|
||||
|
||||
namespace values {
|
||||
|
||||
// Minimum amount of bits for common numpy types. Used to perform checks against OV types.
|
||||
@ -52,6 +58,8 @@ std::vector<size_t> get_strides(const py::array& array);
|
||||
|
||||
py::array as_contiguous(py::array& array, ov::element::Type type);
|
||||
|
||||
py::array array_from_tensor(ov::Tensor&& t);
|
||||
|
||||
}; // namespace array_helpers
|
||||
|
||||
template <typename T>
|
||||
@ -80,15 +88,11 @@ ov::PartialShape partial_shape_from_list(const py::list& shape);
|
||||
|
||||
const ov::Tensor& cast_to_tensor(const py::handle& tensor);
|
||||
|
||||
const Containers::TensorNameMap cast_to_tensor_name_map(const py::dict& inputs);
|
||||
|
||||
const Containers::TensorIndexMap cast_to_tensor_index_map(const py::dict& inputs);
|
||||
|
||||
void set_request_tensors(ov::InferRequest& request, const py::dict& inputs);
|
||||
|
||||
uint32_t get_optimal_number_of_requests(const ov::CompiledModel& actual);
|
||||
|
||||
py::dict outputs_to_dict(const std::vector<ov::Output<const ov::Node>>& outputs, ov::InferRequest& request);
|
||||
py::dict outputs_to_dict(InferRequestWrapper& request);
|
||||
|
||||
ov::pass::Serialize::Version convert_to_version(const std::string& version);
|
||||
|
||||
|
@ -9,13 +9,9 @@
|
||||
|
||||
#include "common.hpp"
|
||||
#include "pyopenvino/core/compiled_model.hpp"
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
#include "pyopenvino/core/infer_request.hpp"
|
||||
#include "pyopenvino/utils/utils.hpp"
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorIndexMap);
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorNameMap);
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
void regclass_CompiledModel(py::module m) {
|
||||
|
@ -1,23 +0,0 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorIndexMap);
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorNameMap);
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Containers {
|
||||
|
||||
void regclass_TensorIndexMap(py::module m) {
|
||||
py::bind_map<TensorIndexMap>(m, "TensorIndexMap");
|
||||
}
|
||||
|
||||
void regclass_TensorNameMap(py::module m) {
|
||||
py::bind_map<TensorNameMap>(m, "TensorNameMap");
|
||||
}
|
||||
} // namespace Containers
|
@ -1,23 +0,0 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <openvino/runtime/tensor.hpp>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Containers {
|
||||
using TensorIndexMap = std::map<size_t, ov::Tensor>;
|
||||
using TensorNameMap = std::map<std::string, ov::Tensor>;
|
||||
|
||||
void regclass_TensorIndexMap(py::module m);
|
||||
void regclass_TensorNameMap(py::module m);
|
||||
}
|
@ -11,12 +11,8 @@
|
||||
#include <string>
|
||||
|
||||
#include "pyopenvino/core/common.hpp"
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
#include "pyopenvino/utils/utils.hpp"
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorIndexMap);
|
||||
PYBIND11_MAKE_OPAQUE(Containers::TensorNameMap);
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
inline py::dict run_sync_infer(InferRequestWrapper& self) {
|
||||
@ -26,7 +22,7 @@ inline py::dict run_sync_infer(InferRequestWrapper& self) {
|
||||
self.m_request.infer();
|
||||
*self.m_end_time = Time::now();
|
||||
}
|
||||
return Common::outputs_to_dict(self.m_outputs, self.m_request);
|
||||
return Common::outputs_to_dict(self);
|
||||
}
|
||||
|
||||
void regclass_InferRequest(py::module m) {
|
||||
@ -103,7 +99,7 @@ void regclass_InferRequest(py::module m) {
|
||||
cls.def(
|
||||
"set_output_tensors",
|
||||
[](InferRequestWrapper& self, const py::dict& outputs) {
|
||||
auto outputs_map = Common::cast_to_tensor_index_map(outputs);
|
||||
auto outputs_map = Common::containers::cast_to_tensor_index_map(outputs);
|
||||
for (auto&& output : outputs_map) {
|
||||
self.m_request.set_output_tensor(output.first, output.second);
|
||||
}
|
||||
@ -120,7 +116,7 @@ void regclass_InferRequest(py::module m) {
|
||||
cls.def(
|
||||
"set_input_tensors",
|
||||
[](InferRequestWrapper& self, const py::dict& inputs) {
|
||||
auto inputs_map = Common::cast_to_tensor_index_map(inputs);
|
||||
auto inputs_map = Common::containers::cast_to_tensor_index_map(inputs);
|
||||
for (auto&& input : inputs_map) {
|
||||
self.m_request.set_input_tensor(input.first, input.second);
|
||||
}
|
||||
@ -719,7 +715,7 @@ void regclass_InferRequest(py::module m) {
|
||||
cls.def_property_readonly(
|
||||
"results",
|
||||
[](InferRequestWrapper& self) {
|
||||
return Common::outputs_to_dict(self.m_outputs, self.m_request);
|
||||
return Common::outputs_to_dict(self);
|
||||
},
|
||||
R"(
|
||||
Gets all outputs tensors of this InferRequest.
|
||||
|
@ -24,7 +24,6 @@
|
||||
#endif
|
||||
#include "pyopenvino/core/async_infer_queue.hpp"
|
||||
#include "pyopenvino/core/compiled_model.hpp"
|
||||
#include "pyopenvino/core/containers.hpp"
|
||||
#include "pyopenvino/core/core.hpp"
|
||||
#include "pyopenvino/core/extension.hpp"
|
||||
#include "pyopenvino/core/infer_request.hpp"
|
||||
@ -210,9 +209,6 @@ PYBIND11_MODULE(_pyopenvino, m) {
|
||||
|
||||
regclass_Core(m);
|
||||
regclass_Tensor(m);
|
||||
// Registering specific types of containers
|
||||
Containers::regclass_TensorIndexMap(m);
|
||||
Containers::regclass_TensorNameMap(m);
|
||||
|
||||
regclass_CompiledModel(m);
|
||||
regclass_InferRequest(m);
|
||||
|
249
src/bindings/python/tests/test_runtime/test_ovdict.py
Normal file
249
src/bindings/python/tests/test_runtime/test_ovdict.py
Normal file
@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2018-2023 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from collections.abc import Mapping
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import openvino.runtime.opset10 as ops
|
||||
from openvino.runtime import Core, ConstOutput, CompiledModel, InferRequest, Model
|
||||
from openvino.runtime.ie_api import OVDict
|
||||
|
||||
|
||||
def _get_ovdict(
|
||||
device,
|
||||
input_shape=None,
|
||||
data_type=np.float32,
|
||||
input_names=None,
|
||||
output_names=None,
|
||||
multi_output=False,
|
||||
direct_infer=False,
|
||||
split_num=5,
|
||||
):
|
||||
# Create model
|
||||
# If model is multi-output (multi_output=True), input_shape must match
|
||||
# requirements of split operation.
|
||||
# TODO OpenSource: refactor it to be more generic
|
||||
if input_shape is None:
|
||||
input_shape = [1, 20]
|
||||
if input_names is None:
|
||||
input_names = ["data_0"]
|
||||
if output_names is None:
|
||||
output_names = ["output_0"]
|
||||
if multi_output:
|
||||
assert isinstance(output_names, (list, tuple))
|
||||
assert len(output_names) > 1
|
||||
assert len(output_names) == split_num
|
||||
param = ops.parameter(input_shape, data_type, name=input_names[0])
|
||||
model = Model(
|
||||
ops.split(param, 1, split_num) if multi_output else ops.abs(param), [param],
|
||||
)
|
||||
# Manually name outputs
|
||||
for i in range(len(output_names)):
|
||||
model.output(i).tensor.names = {output_names[i]}
|
||||
# Compile model
|
||||
core = Core()
|
||||
compiled_model = core.compile_model(model, device)
|
||||
# Create test data
|
||||
input_data = np.random.random(input_shape).astype(data_type)
|
||||
# Two ways of infering
|
||||
if direct_infer:
|
||||
result = compiled_model(input_data)
|
||||
assert result is not None
|
||||
return result, compiled_model
|
||||
|
||||
request = compiled_model.create_infer_request()
|
||||
result = request.infer(input_data)
|
||||
assert result is not None
|
||||
return result, request
|
||||
|
||||
|
||||
def _check_keys(keys, outs):
|
||||
outs_iter = iter(outs)
|
||||
for key in keys:
|
||||
assert isinstance(key, ConstOutput)
|
||||
assert key == next(outs_iter)
|
||||
return True
|
||||
|
||||
|
||||
def _check_values(result):
|
||||
for value in result.values():
|
||||
assert isinstance(value, np.ndarray)
|
||||
return True
|
||||
|
||||
|
||||
def _check_items(result, outs, output_names):
|
||||
i = 0
|
||||
for key, value in result.items():
|
||||
assert isinstance(key, ConstOutput)
|
||||
assert isinstance(value, np.ndarray)
|
||||
# Check values
|
||||
assert np.equal(result[outs[i]], result[key]).all()
|
||||
assert np.equal(result[outs[i]], result[i]).all()
|
||||
assert np.equal(result[outs[i]], result[output_names[i]]).all()
|
||||
i += 1
|
||||
return True
|
||||
|
||||
|
||||
def _check_dict(result, obj, output_names=None):
|
||||
if output_names is None:
|
||||
output_names = ["output_0"]
|
||||
|
||||
outs = obj.model_outputs if isinstance(obj, InferRequest) else obj.outputs
|
||||
assert len(outs) == len(result)
|
||||
assert len(outs) == len(output_names)
|
||||
# Check for __iter__
|
||||
assert _check_keys(result, outs)
|
||||
# Check for keys function
|
||||
assert _check_keys(result.keys(), outs)
|
||||
assert _check_values(result)
|
||||
assert _check_items(result, outs, output_names)
|
||||
assert result.names() == output_names
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
def test_ovdict_assign(device, is_direct):
|
||||
result, _ = _get_ovdict(device, multi_output=False, direct_infer=is_direct)
|
||||
|
||||
with pytest.raises(TypeError) as e:
|
||||
result["some_name"] = 99
|
||||
assert "'OVDict' object does not support item assignment" in str(e.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
def test_ovdict_single_output_basic(device, is_direct):
|
||||
result, obj = _get_ovdict(device, multi_output=False, direct_infer=is_direct)
|
||||
|
||||
assert isinstance(result, OVDict)
|
||||
if isinstance(obj, (InferRequest, CompiledModel)):
|
||||
assert _check_dict(result, obj)
|
||||
else:
|
||||
raise TypeError("Unknown `obj` type!")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
def test_ovdict_single_output_noname(device, is_direct):
|
||||
result, obj = _get_ovdict(
|
||||
device,
|
||||
multi_output=False,
|
||||
direct_infer=is_direct,
|
||||
output_names=[],
|
||||
)
|
||||
|
||||
assert isinstance(result, OVDict)
|
||||
|
||||
outs = obj.model_outputs if isinstance(obj, InferRequest) else obj.outputs
|
||||
|
||||
assert isinstance(result[outs[0]], np.ndarray)
|
||||
assert isinstance(result[0], np.ndarray)
|
||||
|
||||
with pytest.raises(RuntimeError) as e0:
|
||||
_ = result["some_name"]
|
||||
assert "Attempt to get a name for a Tensor without names" in str(e0.value)
|
||||
|
||||
with pytest.raises(RuntimeError) as e1:
|
||||
_ = result.names()
|
||||
assert "Attempt to get a name for a Tensor without names" in str(e1.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
def test_ovdict_single_output_wrongname(device, is_direct):
|
||||
result, obj = _get_ovdict(
|
||||
device,
|
||||
multi_output=False,
|
||||
direct_infer=is_direct,
|
||||
output_names=["output_21"],
|
||||
)
|
||||
|
||||
assert isinstance(result, OVDict)
|
||||
|
||||
outs = obj.model_outputs if isinstance(obj, InferRequest) else obj.outputs
|
||||
|
||||
assert isinstance(result[outs[0]], np.ndarray)
|
||||
assert isinstance(result[0], np.ndarray)
|
||||
|
||||
with pytest.raises(KeyError) as e:
|
||||
_ = result["output_37"]
|
||||
assert "output_37" in str(e.value)
|
||||
|
||||
with pytest.raises(KeyError) as e:
|
||||
_ = result[6]
|
||||
assert "6" in str(e.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
@pytest.mark.parametrize("use_function", [True, False])
|
||||
def test_ovdict_single_output_dict(device, is_direct, use_function):
|
||||
result, obj = _get_ovdict(
|
||||
device,
|
||||
multi_output=False,
|
||||
direct_infer=is_direct,
|
||||
)
|
||||
|
||||
assert isinstance(result, OVDict)
|
||||
|
||||
outs = obj.model_outputs if isinstance(obj, InferRequest) else obj.outputs
|
||||
native_dict = result.to_dict() if use_function else dict(result)
|
||||
|
||||
assert issubclass(type(native_dict), dict)
|
||||
assert not isinstance(native_dict, OVDict)
|
||||
assert isinstance(native_dict[outs[0]], np.ndarray)
|
||||
|
||||
with pytest.raises(KeyError) as e:
|
||||
_ = native_dict["output_0"]
|
||||
assert "output_0" in str(e.value)
|
||||
|
||||
with pytest.raises(KeyError) as e:
|
||||
_ = native_dict[0]
|
||||
assert "0" in str(e.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
def test_ovdict_multi_output_basic(device, is_direct):
|
||||
output_names = ["output_0", "output_1", "output_2", "output_3", "output_4"]
|
||||
result, obj = _get_ovdict(
|
||||
device,
|
||||
multi_output=True,
|
||||
direct_infer=is_direct,
|
||||
output_names=output_names,
|
||||
)
|
||||
|
||||
assert isinstance(result, OVDict)
|
||||
if isinstance(obj, (InferRequest, CompiledModel)):
|
||||
assert _check_dict(result, obj, output_names)
|
||||
else:
|
||||
raise TypeError("Unknown `obj` type!")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_direct", [True, False])
|
||||
@pytest.mark.parametrize("use_function", [True, False])
|
||||
def test_ovdict_multi_output_tuple0(device, is_direct, use_function):
|
||||
output_names = ["output_0", "output_1"]
|
||||
result, obj = _get_ovdict(
|
||||
device,
|
||||
input_shape=(1, 10),
|
||||
multi_output=True,
|
||||
direct_infer=is_direct,
|
||||
split_num=2,
|
||||
output_names=output_names,
|
||||
)
|
||||
|
||||
out0, out1 = None, None
|
||||
if use_function:
|
||||
assert isinstance(result.to_tuple(), tuple)
|
||||
out0, out1 = result.to_tuple()
|
||||
else:
|
||||
out0, out1 = result.values()
|
||||
|
||||
assert out0 is not None
|
||||
assert out1 is not None
|
||||
assert isinstance(out0, np.ndarray)
|
||||
assert isinstance(out1, np.ndarray)
|
||||
|
||||
outs = obj.model_outputs if isinstance(obj, InferRequest) else obj.outputs
|
||||
|
||||
assert np.equal(result[outs[0]], out0).all()
|
||||
assert np.equal(result[outs[1]], out1).all()
|
Loading…
Reference in New Issue
Block a user