Support of unnamed input for MO Python API. (#16373)

* Support of unnamed input for MO Python API.

* Code correction, tests fix.

* Small fix.

* Added tests for unnamed input, code fixes.

* Small code correction.

* Removed code comment.

* Added tests, fixed bugs.

* Minor corrections, added comments.

* Code refactoring.

* Added defaults for InputCutInfo.

* Fixed error.

* Small fixes.

* Removed wrong change.

* Fixed error.

* Corrected input description.
This commit is contained in:
Anastasiia Pnevskaia
2023-04-14 19:37:46 +04:00
committed by GitHub
parent ae34720818
commit 24c9d95779
10 changed files with 492 additions and 263 deletions
@@ -43,6 +43,27 @@ class TestComplexParams(CommonMOConvertTest):
# save model to .pb and return path to the model
return save_to_pb(tf_net, tmp_dir)
def create_tf_model_no_concat(self, tmp_dir):
import tensorflow as tf
tf.compat.v1.reset_default_graph()
with tf.compat.v1.Session() as sess:
inp1 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input1')
inp2 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input2')
inp3 = tf.compat.v1.placeholder(tf.bool, [], 'Input3')
output2 = inp3
relu1 = tf.nn.sigmoid(inp1, name='Relu1')
relu2 = tf.nn.sigmoid(inp2, name='Relu2')
output = relu1 + relu2
tf.compat.v1.global_variables_initializer()
tf_net = sess.graph_def
# save model to .pb and return path to the model
return save_to_pb(tf_net, tmp_dir)
def create_tf_model_single_input_output(self, tmp_dir):
#
# Create Tensorflow model with single input/output
@@ -119,8 +140,8 @@ class TestComplexParams(CommonMOConvertTest):
[Dimension(), 3, Dimension(4, -1), Dimension(-1, 5)]],
'input':['Input1', 'Input2', 'Relu3']},
'params_ref': {'input_shape': "[?,1..3,4..,..5],[?,1..3,4,..5],[?,3,4..,..5]", 'input': 'Input1,Input2,Relu3'}},
{'params_test': {'input': [InputCutInfo("Relu1", Shape([3, 2]), Type(np.int32), None),
InputCutInfo("Relu2", PartialShape([Dimension(3, 10), Dimension(2, -1)]), np.int32, None),
{'params_test': {'input': [InputCutInfo("Relu1", Shape([3, 2]), Type(np.int32)),
InputCutInfo("Relu2", PartialShape([Dimension(3, 10), Dimension(2, -1)]), np.int32),
InputCutInfo("Relu3", [3, 2], Type(np.int32), [1, 2, 3, 4, 5, 6])]},
'params_ref': {'input': "Relu1[3 2]{i32},Relu2[3..10 2..]{i32},Relu3[3 2]{i32}->[1 2 3 4 5 6]"}},
{'params_test': {'input': [("Relu1", Shape([3, 2]), Type(np.int32)),
@@ -150,7 +171,14 @@ class TestComplexParams(CommonMOConvertTest):
'Input2': LayoutMap(source_layout="nc??", target_layout=Layout("n??c")),
'Input3': LayoutMap(source_layout="abcd", target_layout="acdb")}},
'params_ref': {'layout': "Input1(nchw->nhwc),Input2(nc??->n??c),Input3(abcd->acdb)"}},
{'params_test': {'input': [PartialShape([2, 3, 4]), [2, 3, 4], [Dimension(2), Dimension(3), Dimension(4)]]},
'params_ref': {'input_shape': "[2,3,4],[2,3,4],[2,3,4]", 'input': 'Input1,Input2,Input3'}},
{'params_test': {'input': [np.int32, Type(np.int32), np.int32]},
'params_ref': {'input': 'Input1{i32},Input2{i32},Input3{i32}'}},
{'params_test': {'input': [InputCutInfo(shape=[1], type=np.int32, value=[10]),
InputCutInfo(shape=[1], type=np.int32, value=[20]),
InputCutInfo(shape=[1], type=np.int32, value=[30])]},
'params_ref': {'input': 'Input1[1]{i32}->[10],Input2[1]{i32}->[20],Input3[1]{i32}->[30]'}}
]
@pytest.mark.parametrize("params", test_data)
@@ -165,6 +193,39 @@ class TestComplexParams(CommonMOConvertTest):
ref_params.update({'input_model': tf_net_path})
self._test(temp_dir, test_params, ref_params)
test_data = [
{'params_test': {'input_shape': [[Dimension(1), 2, 3], [Dimension(1), 2, 3]],
'freeze_placeholder_with_value': 'Input3->[1]'},
'params_ref': {'input_shape': '[1,2,3],[1,2,3]',
'freeze_placeholder_with_value': 'Input3->[1]'}},
{'params_test': {'input': [PartialShape([Dimension(-1), 5, 6]), [-1, 5, 6]],
'freeze_placeholder_with_value': 'Input3->[1]'},
'params_ref': {'input': 'Input1[?,5,6],Input2[?,5,6]',
'freeze_placeholder_with_value': 'Input3->[1]'}},
{'params_test': {'input': [np.float16, np.float16],
'input_shape': [[10, 20], [10, 20]],
'freeze_placeholder_with_value': 'Input3->[1]'},
'params_ref': {'input': 'Input1{f16},Input2{f16}',
'input_shape': "[10,20],[10,20]",
'freeze_placeholder_with_value': 'Input3->[1]'}},
]
@pytest.mark.parametrize("params", test_data)
@pytest.mark.nightly
def test_mo_convert_tf_model_no_concat(self, params, ie_device, precision, ir_version,
temp_dir, use_new_frontend, use_old_api):
tf_net_path = self.create_tf_model_no_concat(temp_dir)
test_params = params['params_test']
ref_params = params['params_ref']
test_params.update({'input_model': tf_net_path})
ref_params.update({'input_model': tf_net_path})
self._test(temp_dir, test_params, ref_params)
test_data = [
{'params_test': {'input_shape': PartialShape([2, 3, 4])},
'params_ref': {'input_shape': "[2,3,4]"}},
@@ -191,7 +252,29 @@ class TestComplexParams(CommonMOConvertTest):
{'params_test': {'layout': LayoutMap(source_layout=Layout("nchw"), target_layout="nhwc")},
'params_ref': {'layout': "nchw->nhwc"}},
{'params_test': {'layout': Layout("nchw")},
'params_ref': {'layout': "nchw"}}
'params_ref': {'layout': "nchw"}},
{'params_test': {'input': [3, 2]},
'params_ref': {'input': "Input[3 2]"}},
{'params_test': {'input': [Dimension(3,10), 2]},
'params_ref': {'input': "Input[3..10 2]"}},
{'params_test': {'input': (-1, 10)},
'params_ref': {'input': "Input[?,10]"}},
{'params_test': {'input': PartialShape([-1, 10])},
'params_ref': {'input': "Input[?,10]"}},
{'params_test': {'input': np.int32},
'params_ref': {'input': "Input{i32}"}},
{'params_test': {'input': InputCutInfo(shape=[1], type=np.int32, value=[10])},
'params_ref': {'input': "Input[1]{i32}->[10]"}},
{'params_test': {'input': (np.int32, [1, 2, 3])},
'params_ref': {'input': "Input[1,2,3]{i32}"}},
{'params_test': {'input_shape': [Dimension(3, 10), 10, -1]},
'params_ref': {'input_shape': '[3..10,10,?]'}},
{'params_test': {'input': [Dimension(3, 10), 10, -1]},
'params_ref': {'input': 'Input[3..10,10,?]'}},
{'params_test': {'input': PartialShape([1, 100, 100, 3]), 'mean_values': [0.5, 1.3, 0.67]},
'params_ref': {'input': "Input[1,100,100,3]", 'mean_values': "[0.5,1.3,0.67]"}},
{'params_test': {'input': [1, 100, 100, 3], 'scale_values': [0.5, 1.3, 0.67]},
'params_ref': {'input': "Input[1,100,100,3]", 'scale_values': "[0.5,1.3,0.67]"}},
]
@pytest.mark.parametrize("params", test_data)
+7 -7
View File
@@ -11,8 +11,8 @@ from openvino.tools.mo.convert_impl import _convert
from openvino.tools.mo.utils.cli_parser import get_all_cli_parser
from openvino.tools.mo.utils.logger import get_logger_state, restore_logger_state
InputCutInfo = namedtuple("InputInfo", ["name", "shape", "type", "value"])
LayoutMap = namedtuple("LayoutMap", ["source_layout", "target_layout"])
InputCutInfo = namedtuple("InputInfo", ["name", "shape", "type", "value"], defaults=[None, None, None, None])
LayoutMap = namedtuple("LayoutMap", ["source_layout", "target_layout"], defaults=[None, None])
def convert_model(
@@ -118,15 +118,15 @@ def convert_model(
:param input:
Input can be set by passing a list of InputCutInfo objects or by a list
of tuples. Each tuple should contain input name and optionally input
of tuples. Each tuple can contain optionally input name, input
type or input shape. Example: input=("op_name", PartialShape([-1,
3, 100, 100]), Type(np.float32)). Alternatively input can be set by
a string or list of strings of the following format. Quoted list of comma-separated
input nodes names with shapes, data types, and values for freezing.
The order of inputs in converted model is the same as order of specified
operation names. The shape and value are specified as comma-separated
lists. The data type of input node is specified in braces and can have
one of the values: f64 (float64), f32 (float32), f16 (float16), i64
If operation names are specified, the order of inputs in converted
model will be the same as order of specified operation names (applicable for TF2, ONNX, MxNet).
The shape and value are specified as comma-separated lists. The data type of input node is specified
in braces and can have one of the values: f64 (float64), f32 (float32), f16 (float16), i64
(int64), i32 (int32), u8 (uint8), boolean (bool). Data type is optional.
If it's not specified explicitly then there are two options: if input
node is a parameter, data type is taken from the original node dtype,
+102 -10
View File
@@ -23,6 +23,7 @@ from openvino.tools.mo.moc_frontend.pipeline import moc_pipeline
from openvino.tools.mo.moc_frontend.serialize import moc_emit_ir
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively
from openvino.tools.mo.middle.passes.convert_data_type import destination_type_to_np_data_type
from openvino.tools.mo.pipeline.common import prepare_emit_ir
from openvino.tools.mo.pipeline.unified import unified_pipeline
from openvino.tools.mo.utils import import_extensions
@@ -31,7 +32,8 @@ from openvino.tools.mo.utils.cli_parser import check_available_transforms, \
get_common_cli_options, get_freeze_placeholder_values, get_kaldi_cli_options, get_layout_values, \
get_mean_scale_dictionary, get_mxnet_cli_options, get_onnx_cli_options, \
get_placeholder_shapes, get_tf_cli_options, parse_transform, parse_tuple_pairs, \
get_model_name_from_args, depersonalize, get_mo_convert_params
get_model_name_from_args, depersonalize, get_mo_convert_params, input_to_input_cut_info, \
input_shape_to_input_cut_info, freeze_placeholder_to_input_cut_info
from openvino.tools.mo.utils.error import Error
from openvino.tools.mo.utils.version import VersionChecker
@@ -48,6 +50,7 @@ from openvino.tools.mo.moc_frontend.shape_utils import parse_input_shapes, get_s
# pylint: disable=no-name-in-module,import-error
from openvino.frontend import FrontEndManager, OpConversionFailure, ProgressReporterExtension, TelemetryExtension
from openvino.runtime import get_version as get_rt_version
from openvino.runtime import Type, PartialShape
def load_extensions(argv: argparse.Namespace, is_tf: bool, is_caffe: bool, is_mxnet: bool, is_kaldi: bool,
@@ -234,17 +237,22 @@ def arguments_post_parsing(argv: argparse.Namespace):
raise Error('Incorrect saved model tag was provided. Specify --saved_model_tags with no spaces in it')
argv.saved_model_tags = argv.saved_model_tags.split(',')
if hasattr(argv, 'is_python_api_used') and argv.is_python_api_used:
python_api_params_parsing(argv)
else:
argv.inputs_list, argv.placeholder_shapes, argv.placeholder_data_types = get_placeholder_shapes(
argv.input, argv.input_shape, argv.batch)
argv.freeze_placeholder_with_value, argv.input = get_freeze_placeholder_values(
argv.input,
argv.freeze_placeholder_with_value)
argv.unnamed_freeze_placeholder_with_value = {}
argv.output = argv.output.split(',') if argv.output else None
inputs_list, argv.placeholder_shapes, argv.placeholder_data_types = get_placeholder_shapes(
argv.input, argv.input_shape, argv.batch)
argv.inputs_list = inputs_list
argv.layout_values = get_layout_values(argv.layout, argv.source_layout, argv.target_layout)
mean_values = parse_tuple_pairs(argv.mean_values)
scale_values = parse_tuple_pairs(argv.scale_values)
mean_scale = get_mean_scale_dictionary(mean_values, scale_values, argv.input)
argv.mean_scale_values = mean_scale
argv.layout_values = get_layout_values(argv.layout, argv.source_layout, argv.target_layout)
if not os.path.exists(argv.output_dir):
try:
@@ -260,9 +268,6 @@ def arguments_post_parsing(argv: argparse.Namespace):
log.debug("Placeholder shapes : {}".format(argv.placeholder_shapes))
argv.freeze_placeholder_with_value, argv.input = get_freeze_placeholder_values(argv.input,
argv.freeze_placeholder_with_value)
load_extensions(argv, is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx)
return argv
@@ -692,6 +697,91 @@ def input_model_is_object(argv):
return True
def python_api_params_parsing(argv: argparse.Namespace):
"""
Parses params passed to convert_model and wraps resulting values into dictionaries or lists.
After working of this method following values are set in argv:
argv.input, argv.inputs_list - list of input names. Both values are used in some parts of MO.
Could be good to refactor it and use only one of these values.
argv.placeholder_shapes - dictionary where key is node name, value is PartialShape,
or list of PartialShape if node names were not set.
argv.placeholder_data_types - dictionary where key is node name, value is node np.type,
or list of np.types if node names were not set.
argv.freeze_placeholder_with_value - dictionary where key is node name, value is np.ndarray
argv.unnamed_freeze_placeholder_with_value - list with np.ndarray
:param argv: MO arguments
"""
# Parse input to list of InputCutInfo
inputs = input_to_input_cut_info(argv.input)
# Make list of input names
input_names_list = []
for inp in inputs:
if inp.name is not None:
input_names_list.append(inp.name)
if len(input_names_list) > 0:
assert len(input_names_list) == len(inputs), "--input parameter has unnamed inputs and named inputs. " \
"Please either set names for all inputs, " \
"or do not set names for all inputs."
argv.inputs_list = input_names_list
argv.input = ','.join(input_names_list)
# Parse input_shape param and update InputCutInfo list
input_shape_to_input_cut_info(argv.input_shape, inputs)
# Parse freeze_placeholder_with_value.
# values for freezing can be set both by named and unnamed approach if
# 'input' was used without names and 'freeze_placeholder_with_value' was used with names.
# So named and unnamed values are stored separately.
argv.freeze_placeholder_with_value, argv.unnamed_freeze_placeholder_with_value = \
freeze_placeholder_to_input_cut_info(argv.freeze_placeholder_with_value, inputs)
if len(input_names_list) > 0:
# Named inputs case
shape_dict = {}
data_type_dict = {}
for inp in inputs:
if inp.shape is not None:
# Wrap shape to PartialShape for uniformity of stored values
shape_dict[inp.name] = PartialShape(inp.shape)
else:
shape_dict[inp.name] = None
if inp.type is not None:
# Convert type to numpy type for uniformity of stored values
if isinstance(inp.type, str):
data_type_dict[inp.name] = destination_type_to_np_data_type(inp.type)
elif isinstance(inp.type, Type):
data_type_dict[inp.name] = inp.type.to_dtype().type
else:
data_type_dict[inp.name] = inp.type
argv.placeholder_shapes = shape_dict if shape_dict else None
argv.placeholder_data_types = data_type_dict if data_type_dict else {}
else:
# Unnamed inputs case
shape_list = []
data_type_list = []
for inp in inputs:
if inp.shape is not None:
# Wrap shape to PartialShape for uniformity of stored values
shape_list.append(PartialShape(inp.shape))
if inp.type is not None:
# Convert type to numpy type for uniformity of stored values
if isinstance(inp.type, str):
data_type_list.append(destination_type_to_np_data_type(inp.type))
elif isinstance(inp.type, Type):
data_type_list.append(inp.type.to_dtype().type)
else:
data_type_list.append(inp.type)
argv.placeholder_shapes = shape_list if shape_list else None
argv.placeholder_data_types = data_type_list if data_type_list else {}
def pack_params_to_args_namespace(args: dict, cli_parser: argparse.ArgumentParser):
if len(args) > 0:
args_string = params_to_string(**args)
@@ -711,8 +801,10 @@ def pack_params_to_args_namespace(args: dict, cli_parser: argparse.ArgumentParse
# so we need to set them in argv separately
if value is not None and getattr(argv, key, None) != value:
setattr(argv, key, value)
argv.is_python_api_used = True
else:
argv = cli_parser.parse_args()
argv.is_python_api_used = False
return argv
@@ -616,6 +616,8 @@ def input_user_data_repack(graph: Graph, input_user_shapes: [None, list, dict, n
if freeze_placeholder is None:
_freeze_placeholder = None
else:
if isinstance(freeze_placeholder, list):
raise Error('Unnamed inputs with values are not supported for legacy frontend. Please provide input names.')
for placeholder_name, value in freeze_placeholder.items():
placeholder_id, direction, port = get_node_id_with_ports(graph, placeholder_name)
if port is None and placeholder_id in placeholders_ids:
@@ -628,6 +630,10 @@ def input_user_data_repack(graph: Graph, input_user_shapes: [None, list, dict, n
{'direction': direction, 'port': port, 'name': placeholder_name, 'id': new_placeholder_id,
'value': value})
if isinstance(input_user_shapes, list):
if len(input_user_shapes) == 1 and isinstance(input_user_shapes[0], PartialShape):
input_user_shapes = input_user_shapes[0]
# input user shapes restructure
if input_user_shapes is None:
# None User did not provide neither --input nor --input_shape keys
@@ -252,6 +252,9 @@ def fe_input_user_data_repack(
"input_name": input_name
}
)
# case when single unnamed input shape and type was specified
if input_name in input_user_data_types:
_input_shapes[-1]['data_type'] = input_user_data_types[input_name]
_input_names.append(input_name)
break
else:
@@ -268,6 +271,9 @@ def fe_input_user_data_repack(
"input_name": input_name
}
)
# case when types were specified for unnamed inputs
if input_name in input_user_data_types:
_input_shapes[-1]['data_type'] = input_user_data_types[input_name]
# mark-up Place names we already put into the _input_names
# to avoid duplicates in updates by freeze_placeholder below
_input_names.append(input_name)
@@ -324,6 +330,99 @@ def fe_output_user_data_repack(input_model: InputModel, outputs: list, framework
return _outputs
def find_first_unused_input(model_inputs: list, freeze_placeholder: dict, param_dict: dict, param_name: str):
"""
Finds first input in model_inputs, which is not present in freeze_placeholder dictionary or param_dict.
:param model_inputs: list of model inputs
:param freeze_placeholder: dictionary where key is input name, value is input value for freezing.
:param param_dict: dictionary where key is input name, value is parameter value (shape or type).
:param param_name: name of parameter used in exception message.
:return: first input name, which is not present in freeze_placeholder dictionary or param_dict.
"""
for inp in model_inputs:
input_names = inp.get_names()
name_found = False
for input_name in input_names:
if input_name in freeze_placeholder or input_name in param_dict:
name_found = True
break
if name_found:
continue
return input_names[0]
raise Error("Could not set {}, as model does not have enough inputs.".format(param_name))
def convert_params_lists_to_dicts(input_model,
input_user_shapes: [list, dict],
input_user_data_types: [list, dict],
freeze_placeholder: dict,
unnamed_freeze_placeholders: list):
"""
Convert lists of unnamed params to dicts using input names from input_model.
:param input_model: openvino.runtime.InputModel
:param input_user_shapes: list of input shapes or dictionary where key is input name, value is input shape from user.
:param input_user_data_types: list of input types or dictionary where key is input name, value is input type from user.
:param freeze_placeholder: dictionary where key is input name, value is input value from user.
:param unnamed_freeze_placeholders: list of unnamed input values from user.
:return: (input_user_shapes_dict, input_user_data_types_dict, freeze_placeholder), where
input_user_shapes_dict - dictionary where key is input name, value is shape from user;
input_user_data_types_dict - dictionary where key is input name, value is type from user;
freeze_placeholder - dictionary where key is input name, value is input value from user;
"""
from openvino.runtime import PartialShape
model_inputs = input_model.get_inputs()
input_user_data_types_dict = {}
input_user_shapes_dict = {}
# input_user_shapes is list only if unnamed inputs were used
if isinstance(input_user_shapes, list):
# this cycle adds each unnamed shape to dictionary using name from model_inputs
for idx, shape in enumerate(input_user_shapes):
assert isinstance(shape, PartialShape), "Got incorrect format of input shapes {}.".format(type(shape))
inp_name = find_first_unused_input(model_inputs, freeze_placeholder, input_user_shapes_dict, "shape")
input_user_shapes_dict[inp_name] = shape
else:
input_user_shapes_dict = input_user_shapes
# input_user_data_types is list only if unnamed inputs were used
if isinstance(input_user_data_types, list):
from openvino.runtime import Type
if input_user_shapes_dict is None:
input_user_shapes_dict = {}
# this cycle adds each unnamed type to dictionary using name from model_inputs
for idx, node_type in enumerate(input_user_data_types):
assert isinstance(node_type, (type, Type)), "Got incorrect format of input types. " \
"Expected numpy type or openvino.runtime.Type, " \
"got {}.".format(type(node_type))
inp_name = find_first_unused_input(model_inputs, freeze_placeholder, input_user_data_types_dict, "type")
input_user_data_types_dict[inp_name] = node_type
# FE postprocessing expects input_user_shapes_dict to always have shapes for corresponding types.
# If shape is not set it is expected to have None shape in input_user_shapes_dict dictionary.
if inp_name not in input_user_shapes_dict:
input_user_shapes_dict[inp_name] = None
else:
input_user_data_types_dict = input_user_data_types
# unnamed_freeze_placeholders is always list, it is not empty only if unnamed inputs were used.
for value in unnamed_freeze_placeholders:
assert isinstance(value, list), "Got incorrect format of input values. " \
"Expected list, " \
"got {}.".format(type(value))
inp_name = find_first_unused_input(model_inputs, freeze_placeholder, {}, "input value")
freeze_placeholder[inp_name] = value
return input_user_shapes_dict, input_user_data_types_dict, freeze_placeholder
def fe_user_data_repack(
input_model: InputModel,
input_user_shapes: [None, list, dict, np.array],
@@ -16,7 +16,7 @@ from openvino.runtime.utils.types import get_element_type, \
get_numpy_ctype # pylint: disable=no-name-in-module,import-error
from openvino.tools.mo.middle.passes.infer import validate_batch_in_shape
from openvino.tools.mo.moc_frontend.analysis import json_model_analysis_dump
from openvino.tools.mo.moc_frontend.extractor import fe_user_data_repack
from openvino.tools.mo.moc_frontend.extractor import fe_user_data_repack, convert_params_lists_to_dicts
from openvino.tools.mo.utils.class_registration import get_enabled_and_disabled_transforms
from openvino.tools.mo.utils.error import Error
@@ -34,6 +34,10 @@ def moc_pipeline(argv: argparse.Namespace, moc_front_end: FrontEnd):
else:
input_model = moc_front_end.load(argv.input_model)
argv.placeholder_shapes, argv.placeholder_data_types, argv.freeze_placeholder_with_value = convert_params_lists_to_dicts(
input_model, argv.placeholder_shapes, argv.placeholder_data_types,
argv.freeze_placeholder_with_value, argv.unnamed_freeze_placeholder_with_value)
user_shapes, outputs, freeze_placeholder = fe_user_data_repack(
input_model, argv.placeholder_shapes, argv.placeholder_data_types,
argv.output, argv.freeze_placeholder_with_value, moc_front_end.get_name())
+179 -98
View File
@@ -21,7 +21,6 @@ from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type
import openvino
from openvino.tools.mo.front.extractor import split_node_in_port
from openvino.tools.mo.middle.passes.convert_data_type import destination_type_to_np_data_type
from openvino.tools.mo.middle.passes.convert_data_type import np_data_type_to_destination_type
from openvino.tools.mo.utils.error import Error
from openvino.tools.mo.utils.utils import refer_to_faq_msg, get_mo_root_dir
from openvino.tools.mo.utils.help import get_convert_model_help_specifics, get_to_string_methods_for_params
@@ -122,52 +121,6 @@ def is_shape_type(value):
return False
def shape_to_str(shape, separator):
if isinstance(shape, str):
return shape
if isinstance(shape, PartialShape):
return shape.to_string()
if isinstance(shape, Shape):
return PartialShape(shape).to_string()
if isinstance(shape, list) or isinstance(shape, tuple):
dims = []
for dim in shape:
if isinstance(dim, Dimension):
dims.append(dim.to_string())
elif isinstance(dim, int):
dims.append(str(dim))
else:
raise Exception("Incorrect type of dimension. Expected Dimension or int, got {}".format(type(dim)))
return "[" + separator.join(dims) + "]"
raise Exception("Incorrect shape type. Expected PartialShape, Shape, [Dimension, ...] or [int, ...], "
"got {}".format(type(shape)))
def input_shape_to_str(input_shape):
if input_shape is None or isinstance(input_shape, str):
return input_shape
if isinstance(input_shape, list):
if len(input_shape) > 0 and isinstance(input_shape[0], int) or isinstance(input_shape[0], Dimension):
# The case when shape is specified as list of int or Dimension
return shape_to_str(input_shape, ',')
# The case when list of shapes is specified
shapes = []
for shape in input_shape:
shapes.append(shape_to_str(shape, ','))
return ','.join(shapes)
return shape_to_str(input_shape, ',')
def type_to_str(type_obj):
if isinstance(type_obj, str):
return type_obj
if isinstance(type_obj, type):
return np_data_type_to_destination_type(type_obj)
if isinstance(type_obj, Type):
return type_obj.get_type_name()
raise Exception("Incorrect type. Expected Type or numpy type, got {}".format(type(type_obj)))
def value_to_str(value, separator):
if isinstance(value, np.ndarray):
values = []
@@ -186,22 +139,32 @@ def value_to_str(value, separator):
raise Exception("Incorrect value type. Expected np.ndarray or list, got {}".format(type(value)))
def single_input_to_str(input):
def single_input_to_input_cut_info(input: [str, tuple, list, PartialShape, Type, type]):
"""
Parses parameters of single input to InputCutInfo.
:param input: input cut parameters of single input
:return: InputCutInfo
"""
if isinstance(input, str):
return input
# Parse params from string
node_name, shape, value, data_type = parse_input_value(input)
return openvino.tools.mo.InputCutInfo(node_name,
PartialShape(shape) if shape is not None else None,
data_type,
value)
if isinstance(input, openvino.tools.mo.InputCutInfo):
if not isinstance(input.name, str):
raise Exception("Input name should be string, got {}".format(input.name))
input_str = input.name
assert input_str is not None, "Incorrect InputCutInfo. 'name' should be set."
if input.shape is not None:
input_str += shape_to_str(input.shape, " ")
if input.type is not None:
input_str += "{" + type_to_str(input.type) + "}"
if input.value is not None:
input_str += "->" + value_to_str(input.value, " ")
return input_str
if isinstance(input, tuple):
# Wrap input.shape to PartialShape if possible and wrap to InputCutInfo
return openvino.tools.mo.InputCutInfo(input.name,
PartialShape(input.shape) if input.shape is not None else None,
input.type,
input.value)
if isinstance(input, (tuple, list, PartialShape)):
# If input represents list with shape, wrap it to list. Single PartialShape also goes to this condition.
# Check of all dimensions will be in is_shape_type(val) method below
if len(input) > 0 and isinstance(input[0], (int, Dimension)):
input = [input]
# Check values of tuple or list and collect to InputCutInfo
name = None
inp_type = None
shape = None
@@ -210,38 +173,147 @@ def single_input_to_str(input):
if name is not None:
raise Exception("More than one input name provided: {}".format(input))
name = val
elif isinstance(val, type) or isinstance(val, Type):
elif isinstance(val, (type, Type)):
if inp_type is not None:
raise Exception("More than one input type provided: {}".format(input))
inp_type = type_to_str(val)
inp_type = val
elif is_shape_type(val):
if shape is not None:
raise Exception("More than one input shape provided: {}".format(input))
shape = shape_to_str(val, " ")
shape = PartialShape(val)
else:
raise Exception("Incorrect input parameters provided. Expected input name and "
"optionally input type or input shape. Got unknown object: {}".format(val))
if name is None:
raise Exception("Input name was not provided for following input {}.".format(input))
if shape is not None:
name += shape
if inp_type is not None:
name += "{" + inp_type + "}"
return name
raise Exception("Incorrect input parameters provided. Expected tuple with input name, "
"input type or input shape. Got unknown object: {}".format(val))
return openvino.tools.mo.InputCutInfo(name,
PartialShape(shape) if shape is not None else None,
inp_type,
None)
# Case when only type is set
if isinstance(input, (type, Type)):
return openvino.tools.mo.InputCutInfo(None, None, input, None)
# We don't expect here single unnamed value. If list of int is set it is considered as shape.
# Setting of value is expected only using InputCutInfo or string analog.
raise Exception("Unexpected object provided for input. Expected openvino.tools.mo.InputCutInfo "
"or tuple or str. Got {}".format(type(input)))
def input_to_str(input):
if input is None or isinstance(input, str):
return input
def input_to_input_cut_info(input: [str, tuple, list]):
"""
Parses 'input' to list of InputCutInfo.
:param input: input cut parameters passed by user
:return: list of InputCutInfo with input cut parameters
"""
if input is None:
return []
if isinstance(input, str):
inputs = []
# Split to list of string
for input_value in split_inputs(input):
# Parse string with parameters for single input
node_name, shape, value, data_type = parse_input_value(input_value)
inputs.append(openvino.tools.mo.InputCutInfo(node_name,
PartialShape(shape) if shape is not None else None,
data_type,
value))
return inputs
if isinstance(input, openvino.tools.mo.InputCutInfo):
# Wrap to list and return
return [input]
if isinstance(input, tuple):
# Case when input is single shape set in tuple
if len(input) > 0 and isinstance(input[0], (int, Dimension)):
input = [input]
# Case when input is set as tuple. Expected that it is always single input.
return [single_input_to_input_cut_info(input)]
if isinstance(input, list):
inputs_str = []
# Case when input is single shape set in list
if len(input) > 0 and isinstance(input[0], (int, Dimension)):
input = [input]
inputs = []
# Case when input is set as list. Expected that it is list of params for different inputs.
for inp in input:
inputs_str.append(single_input_to_str(inp))
return ','.join(inputs_str)
return single_input_to_str(input)
inputs.append(single_input_to_input_cut_info(inp))
return inputs
# Case when single type or value is set, or unknown object
return [single_input_to_input_cut_info(input)]
def input_shape_to_input_cut_info(input_shape: [str, Shape, PartialShape, list, tuple], inputs: list):
"""
Parses 'input_shape' to list of PartialShape and updates 'inputs'.
:param input_shape: input shapes passed by user
:param inputs: list of InputCutInfo with information from 'input' parameter
"""
if input_shape is None:
return
if isinstance(input_shape, str):
# Split input_shape to list of string
input_shape = split_shapes(input_shape)
if isinstance(input_shape, (Shape, PartialShape)):
# Whap single shape to list
input_shape = [input_shape]
if isinstance(input_shape, (list, tuple)):
# Check case when single shape is passed as list or tuple
if len(input_shape) > 0 and isinstance(input_shape[0], (int, Dimension)):
input_shape = [input_shape]
if len(inputs) > 0 and len(input_shape) > 0:
assert len(inputs) == len(input_shape), "Different numbers of inputs were specified in --input parameter " \
"and --input_shapes. --input has {} items, --input_shape has {} item.".format(len(inputs), len(input_shape))
# Update inputs with information from 'input_shape'
if len(inputs) > 0:
for idx, shape in enumerate(input_shape):
shape = PartialShape(shape)
assert inputs[idx].shape is None, "Shape was set in both --input and in --input_shape parameter." \
"Please use either --input or --input_shape for shape setting."
inputs[idx] = openvino.tools.mo.InputCutInfo(inputs[idx].name, shape, inputs[idx].type, inputs[idx].value)
else:
for shape in input_shape:
inputs.append(openvino.tools.mo.InputCutInfo(None, PartialShape(shape), None, None))
return
raise Exception("Unexpected object provided for input_shape. Expected PartialShape, Shape, tuple, list or str. "
"Got {}".format(type(input_shape)))
def freeze_placeholder_to_input_cut_info(argv_freeze_placeholder_with_value: str, inputs: list):
"""
Parses 'argv_freeze_placeholder_with_value' to dictionary and collects unnamed inputs from 'inputs' to list.
:param argv_freeze_placeholder_with_value: string set by user.
As it was planned to be deprecated no Python analogs were made.
:param inputs: list of InputCutInfo with information from 'input' parameter
:returns (placeholder_values, unnamed_placeholder_values), where
placeholder_values - dictionary where key is node name, value is node value,
unnamed_placeholder_values - list with unnamed node values
"""
# Parse argv_freeze_placeholder_with_value to dictionary with names and values
placeholder_values = parse_freeze_placeholder_values(argv_freeze_placeholder_with_value)
unnamed_placeholder_values = []
# Collect values for freezing from 'inputs'
if inputs is not None and len(inputs) > 0:
for input in inputs:
node_name = input.name
value = input.value
if value is None:
continue
# Check for value conflict
if node_name in placeholder_values and placeholder_values[node_name] != value:
raise Error("Overriding replacement value of the placeholder with name '{}': old value = {}, new value = {}"
".".format(node_name, placeholder_values[node_name], value))
if node_name is not None:
# Named input case, add to dictionary
placeholder_values[node_name] = value
else:
# Unnamed input case, add to list
unnamed_placeholder_values.append(value)
return placeholder_values, unnamed_placeholder_values
def mean_scale_value_to_str(value):
@@ -1329,6 +1401,30 @@ def get_layout_values(argv_layout: str = '', argv_source_layout: str = '', argv_
return res_list
def parse_freeze_placeholder_values(argv_freeze_placeholder_with_value: str):
"""
Parses parse_freeze_placeholder_values string.
:param argv_freeze_placeholder_with_value: string information on freezing placeholders
:return: dictionary where key is node name, value is node value.
"""
placeholder_values = {}
if argv_freeze_placeholder_with_value is not None:
for plh_with_value in argv_freeze_placeholder_with_value.split(','):
plh_with_value = plh_with_value.split('->')
if len(plh_with_value) != 2:
raise Error("Wrong replacement syntax. Use --freeze_placeholder_with_value "
"\"node1_name->value1,node2_name->value2\"")
node_name = plh_with_value[0]
value = plh_with_value[1]
if node_name in placeholder_values and placeholder_values[node_name] != value:
raise Error("Overriding replacement value of the placeholder with name '{}': old value = {}, new value = {}"
".".format(node_name, placeholder_values[node_name], value))
if '[' in value.strip(' '):
value = value.replace('[', '').replace(']', '').split(' ')
placeholder_values[node_name] = value
return placeholder_values
def get_freeze_placeholder_values(argv_input: str, argv_freeze_placeholder_with_value: str):
"""
Parses values for placeholder freezing and input node names
@@ -1347,24 +1443,9 @@ def get_freeze_placeholder_values(argv_input: str, argv_freeze_placeholder_with_
parsed placeholders with values for freezing
input nodes cleaned from shape info
"""
placeholder_values = {}
placeholder_values = parse_freeze_placeholder_values(argv_freeze_placeholder_with_value)
input_node_names = None
if argv_freeze_placeholder_with_value is not None:
for plh_with_value in argv_freeze_placeholder_with_value.split(','):
plh_with_value = plh_with_value.split('->')
if len(plh_with_value) != 2:
raise Error("Wrong replacement syntax. Use --freeze_placeholder_with_value "
"\"node1_name->value1,node2_name->value2\"")
node_name = plh_with_value[0]
value = plh_with_value[1]
if node_name in placeholder_values and placeholder_values[node_name] != value:
raise Error("Overriding replacement value of the placeholder with name '{}': old value = {}, new value = {}"
".".format(node_name, placeholder_values[node_name], value))
if '[' in value.strip(' '):
value = value.replace('[', '').replace(']', '').split(' ')
placeholder_values[node_name] = value
if argv_input is not None:
input_node_names = ''
# walkthrough all input values and save values for freezing
@@ -1608,7 +1689,7 @@ def get_tuple_values(argv_values: str or tuple, num_exp_values: int = 3, t=float
return mean_values_matches
def get_mean_scale_dictionary(mean_values, scale_values, argv_input: str):
def get_mean_scale_dictionary(mean_values, scale_values, argv_input: list):
"""
This function takes mean_values and scale_values, checks and processes them into convenient structure
@@ -1629,7 +1710,7 @@ def get_mean_scale_dictionary(mean_values, scale_values, argv_input: str):
res = {}
# collect input names
if argv_input:
inputs = [get_node_name_with_port_from_input_value(input_value) for input_value in split_inputs(argv_input)]
inputs = [get_node_name_with_port_from_input_value(input_value) for input_value in split_inputs(argv_input)]
else:
inputs = []
if type(mean_values) is dict:
+2 -4
View File
@@ -143,13 +143,11 @@ def get_convert_model_help_specifics():
# TODO: remove this when internal converting of params to string is removed
def get_to_string_methods_for_params():
from openvino.tools.mo.utils.cli_parser import path_to_str_or_object, input_shape_to_str, str_list_to_str, \
from openvino.tools.mo.utils.cli_parser import path_to_str_or_object, str_list_to_str, \
mean_scale_value_to_str, source_target_layout_to_str, layout_param_to_str, transform_param_to_str, \
extensions_to_str_or_extensions_class, batch_to_int, transformations_config_to_str, input_to_str
extensions_to_str_or_extensions_class, batch_to_int, transformations_config_to_str
return {
'input_model': path_to_str_or_object,
'input_shape': input_shape_to_str,
'input': input_to_str,
'output': str_list_to_str,
'mean_values': mean_scale_value_to_str,
'scale_values': mean_scale_value_to_str,
@@ -5,120 +5,12 @@ import numpy as np
from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type
from openvino.tools.mo import InputCutInfo, LayoutMap
from openvino.tools.mo.utils.cli_parser import input_to_str, mean_scale_value_to_str, \
transform_param_to_str, input_shape_to_str, str_list_to_str, source_target_layout_to_str, layout_param_to_str
from openvino.tools.mo.utils.cli_parser import mean_scale_value_to_str, \
transform_param_to_str, str_list_to_str, source_target_layout_to_str, layout_param_to_str
from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry
class TestConvertingConvertArgumentsToString(UnitTestWithMockedTelemetry):
def test_input_to_str(self):
inp1 = InputCutInfo(name="data:0", shape=None, type=None, value=None)
self.assertTrue(input_to_str(inp1) == "data:0")
inp2 = InputCutInfo("data:0", [1, 3, 100, 100], type=None, value=None)
self.assertTrue(input_to_str(inp2) == "data:0[1 3 100 100]")
inp3 = InputCutInfo("data:0", type=np.int32, value=None, shape=None)
self.assertTrue(input_to_str(inp3) == "data:0{i32}")
inp4 = InputCutInfo("data:0", value=[2, 4, 5], type=None, shape=None)
self.assertTrue(input_to_str(inp4) == "data:0->[2 4 5]")
inp5 = InputCutInfo("data:0", [1, 3, 100, 100], np.uint8, value=None)
self.assertTrue(input_to_str(inp5) == "data:0[1 3 100 100]{u8}")
inp6 = InputCutInfo("data:0", [2, 5, 7], value=[1, 2, 3, 4, 5], type=None)
self.assertTrue(input_to_str(inp6) == "data:0[2 5 7]->[1 2 3 4 5]")
inp7 = InputCutInfo("0:data1", type=np.float64, value=[1.6, 7.2, 5.66], shape=None)
self.assertTrue(input_to_str(inp7) == "0:data1{f64}->[1.6 7.2 5.66]")
inp8 = InputCutInfo("data2", [4, 5, 6], np.int64, [5, 4, 3, 2, 1])
self.assertTrue(input_to_str(inp8) == "data2[4 5 6]{i64}->[5 4 3 2 1]")
inp9 = InputCutInfo("data", [1], bool, True)
self.assertTrue(input_to_str(inp9) == "data[1]{boolean}->True")
inp = [inp6, inp7, inp8]
self.assertTrue(input_to_str(inp) == "data:0[2 5 7]->[1 2 3 4 5],"
"0:data1{f64}->[1.6 7.2 5.66],"
"data2[4 5 6]{i64}->[5 4 3 2 1]")
inp = ["data:0[2 5 7]->[1 2 3 4 5]", "0:data1{f64}->[1.6 7.2 5.66]", "data2[4 5 6]{i64}->[5 4 3 2 1]"]
self.assertTrue(input_to_str(inp) == "data:0[2 5 7]->[1 2 3 4 5],"
"0:data1{f64}->[1.6 7.2 5.66],"
"data2[4 5 6]{i64}->[5 4 3 2 1]")
inp9 = InputCutInfo("data1", PartialShape([Dimension(-1), Dimension(2, -1),
Dimension(-1, 10), 100, Dimension(2, 12)]), type=None, value=None)
self.assertTrue(input_to_str(inp9) == "data1[?,2..,..10,100,2..12]")
inp10 = InputCutInfo("data2", [Dimension(-1), Dimension(2, -1),
Dimension(-1, 10), 100, Dimension(2, 12)], np.uint8, value=None)
self.assertTrue(input_to_str(inp10) == "data2[? 2.. ..10 100 2..12]{u8}")
inp11 = InputCutInfo("data3", Shape([4, 5, 6]), np.int64, [5, 4, 3, 2, 1])
self.assertTrue(input_to_str(inp11) == "data3[4,5,6]{i64}->[5 4 3 2 1]")
inp12 = InputCutInfo("data4", PartialShape.dynamic(), type=None, value=None)
self.assertTrue(input_to_str(inp12) == "data4[...]")
inp = [inp9, inp10, inp11, inp12]
self.assertTrue(input_to_str(inp) == "data1[?,2..,..10,100,2..12],"
"data2[? 2.. ..10 100 2..12]{u8},"
"data3[4,5,6]{i64}->[5 4 3 2 1],"
"data4[...]")
inp1 = ("data:0")
self.assertTrue(input_to_str(inp1) == "data:0")
inp2 = ([1, 3, 100, 100], "data:0")
self.assertTrue(input_to_str(inp2) == "data:0[1 3 100 100]")
inp3 = ("data:0", np.int32)
self.assertTrue(input_to_str(inp3) == "data:0{i32}")
inp4 = (np.uint8, [1, 3, 100, 100], "data:0")
self.assertTrue(input_to_str(inp4) == "data:0[1 3 100 100]{u8}")
inp = [inp1, inp2, inp3, inp4]
self.assertTrue(input_to_str(inp) == "data:0,"
"data:0[1 3 100 100],"
"data:0{i32},"
"data:0[1 3 100 100]{u8}")
inp5 = ("data1", PartialShape([Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)]))
self.assertTrue(input_to_str(inp5) == "data1[?,2..,..10,100,2..12]")
inp6 = ("data2", [Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)], np.uint8)
self.assertTrue(input_to_str(inp6) == "data2[? 2.. ..10 100 2..12]{u8}")
inp7 = ("data3", Shape([4, 5, 6]), np.int64)
self.assertTrue(input_to_str(inp7) == "data3[4,5,6]{i64}")
inp8 = ("data4", PartialShape.dynamic())
self.assertTrue(input_to_str(inp8) == "data4[...]")
inp = [inp5, inp6, inp7, inp8]
self.assertTrue(input_to_str(inp) == "data1[?,2..,..10,100,2..12],"
"data2[? 2.. ..10 100 2..12]{u8},"
"data3[4,5,6]{i64},"
"data4[...]")
self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo(0.5, [1, 2, 3], None, None)})
self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", 0.5, None, None)})
self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], 0.5, None)})
self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], None, int)})
self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], None, int)})
self.assertRaises(Exception, input_to_str, **{"input": ([2, 3], Shape([1, 2]))})
self.assertRaises(Exception, input_to_str, **{"input": ("name", [int, 2, 3])})
self.assertRaises(Exception, input_to_str, **{"input": ("name", "name1", [2, 3])})
self.assertRaises(Exception, input_to_str, **{"input": ("name", [2, 3], Shape([1, 2]))})
self.assertRaises(Exception, input_to_str, **{"input": ("name", int, Type(float))})
self.assertRaises(Exception, input_to_str, **{"input": Exception})
self.assertRaises(Exception, input_to_str, **{"input": ("name", Exception)})
self.assertRaises(Exception, input_to_str, **{"input": ("name", Dimension(1))})
def test_mean_scale_value_to_str(self):
values = [0.5, 1.3, 0.67]
self.assertTrue(mean_scale_value_to_str(values) == "[0.5,1.3,0.67]")
@@ -164,32 +56,6 @@ class TestConvertingConvertArgumentsToString(UnitTestWithMockedTelemetry):
{('a', 'b'): False})})
self.assertRaises(Exception, transform_param_to_str, **{"value": Dimension(1)})
def test_input_shape_to_str(self):
input_shape1 = [1, 3, 100, 100]
self.assertTrue(input_shape_to_str(input_shape1) == "[1,3,100,100]")
input_shape2 = PartialShape([1, 3, 100, 100])
self.assertTrue(input_shape_to_str(input_shape2) == "[1,3,100,100]")
input_shape3 = PartialShape([Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)])
self.assertTrue(input_shape_to_str(input_shape3) == "[?,2..,..10,100,2..12]")
input_shape4 = PartialShape.dynamic()
self.assertTrue(input_shape_to_str(input_shape4) == "[...]")
input_shape5 = Shape([1, 2, 3, 4])
self.assertTrue(input_shape_to_str(input_shape5) == "[1,2,3,4]")
input_shape6 = [Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)]
self.assertTrue(input_shape_to_str(input_shape6) == "[?,2..,..10,100,2..12]")
input_shape = [input_shape1, input_shape2, input_shape3, input_shape4, input_shape5, input_shape6]
self.assertTrue(input_shape_to_str(input_shape) == "[1,3,100,100],[1,3,100,100],[?,2..,..10,100,2..12],"
"[...],[1,2,3,4],[?,2..,..10,100,2..12]")
self.assertRaises(Exception, input_shape_to_str, **{"input_shape": [int, 1]})
self.assertRaises(Exception, input_shape_to_str, **{"input_shape": Dimension(1)})
def test_str_list_to_str(self):
list_str = ["data1", "data2", "data3"]
self.assertTrue(str_list_to_str(list_str) == "data1,data2,data3")
@@ -1985,8 +1985,8 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
assert argv.reverse_input_channels == args['reverse_input_channels']
assert argv.scale == 0.5
assert argv.batch == 1
assert argv.input_shape == "[1,100,100,3],[2,3]"
assert argv.input == "name,a[1 2 3]{f32}->[5 6 7]"
assert argv.input_shape == [PartialShape([1,100,100,3]), [2,3]]
assert argv.input == ['name', InputCutInfo("a", [1,2,3], numpy.float32, [5, 6, 7])]
assert argv.output == "a,b,c"
assert argv.mean_values == "[0.5,0.3]"
assert argv.scale_values == "a[0.4],b[0.5,0.6]"
@@ -1995,7 +1995,7 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
assert argv.transform == "LowLatency2[use_const_initializer=False]"
for arg, value in vars(argv).items():
if arg not in args:
if arg not in args and arg != 'is_python_api_used':
assert value == cli_parser.get_default(arg)
def test_not_existing_dir(self):