[OVC] Fix output parsing (#19425)
* fix parsing output * use always coma backspace to separate outputs ', ' * update docstring * call parser only for ovc cli tool * docstring correction * separate docs for cli tool and convert_model; other minor changes * drop redundant arg from cli_parser_test.py * more solid test cases added * remove redundant argv.framework from cli_parser_test.py * shape correction in concat * Apply suggestions from code review fix: coma -> comma Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com> * apply review suggestions * remove extra ')' --------- Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
This commit is contained in:
parent
2cf8f2bc1f
commit
b8226db465
|
|
@ -1,13 +1,14 @@
|
|||
# Copyright (C) 2018-2023 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import numpy as np
|
||||
import openvino.runtime as ov
|
||||
import os
|
||||
import pytest
|
||||
import tempfile
|
||||
import unittest
|
||||
from openvino.runtime import Model, Layout, PartialShape, Shape, layout_helpers, Type, Dimension
|
||||
|
||||
import numpy as np
|
||||
import openvino.runtime as ov
|
||||
import pytest
|
||||
from openvino.runtime import PartialShape, Type, Dimension
|
||||
|
||||
from common.mo_convert_test_class import CommonMOConvertTest
|
||||
from common.tf_layer_test_class import save_to_pb
|
||||
|
|
@ -158,6 +159,124 @@ class TestComplexParams(CommonMOConvertTest):
|
|||
ref_params.update({'input_model': tf_net_path})
|
||||
self._test(temp_dir, test_params, ref_params)
|
||||
|
||||
@staticmethod
|
||||
def create_onnx_model_with_comma_in_names(temp_dir):
|
||||
import onnx
|
||||
from onnx import helper
|
||||
from onnx import TensorProto
|
||||
|
||||
input_1 = helper.make_tensor_value_info('input_1', TensorProto.FLOAT, [1, 3, 2, 2])
|
||||
input_2 = helper.make_tensor_value_info('input_2', TensorProto.FLOAT, [1, 3, 2, 2])
|
||||
output = helper.make_tensor_value_info('relu_1,relu_2', TensorProto.FLOAT, [1, 3, 4, 2])
|
||||
|
||||
node_def_1 = onnx.helper.make_node(
|
||||
'Relu',
|
||||
inputs=['input_1'],
|
||||
outputs=['Relu_1_data'],
|
||||
name='relu_1'
|
||||
)
|
||||
node_def_2 = onnx.helper.make_node(
|
||||
'Relu',
|
||||
inputs=['input_2'],
|
||||
outputs=['Relu_2_data'],
|
||||
name='relu_2'
|
||||
)
|
||||
node_def_3 = onnx.helper.make_node(
|
||||
'Concat',
|
||||
inputs=['Relu_1_data', 'Relu_2_data'],
|
||||
outputs=['relu_1,relu_2'],
|
||||
axis=3,
|
||||
)
|
||||
|
||||
graph_def = helper.make_graph(
|
||||
[node_def_1, node_def_2, node_def_3],
|
||||
'test_model',
|
||||
[input_1, input_2],
|
||||
[output],
|
||||
)
|
||||
onnx_net = helper.make_model(graph_def, producer_name='test_model')
|
||||
model_path = temp_dir + '/test_model.onnx'
|
||||
onnx.save(onnx_net, model_path)
|
||||
return model_path
|
||||
|
||||
@staticmethod
|
||||
def create_ref_graph_with_comma_in_names():
|
||||
from openvino.runtime.opset12 import relu, concat
|
||||
from openvino.runtime.op import Parameter
|
||||
import openvino as ov
|
||||
|
||||
parameter1 = Parameter(ov.Type.f32, ov.Shape([1, 3, 2, 2]))
|
||||
parameter2 = Parameter(ov.Type.f32, ov.Shape([1, 3, 2, 2]))
|
||||
relu_1 = relu(parameter1)
|
||||
relu_2 = relu(parameter2)
|
||||
|
||||
output = concat([relu_1, relu_2], 3)
|
||||
return ov.Model([output], [parameter1, parameter2])
|
||||
|
||||
@staticmethod
|
||||
def create_onnx_model_with_several_outputs(temp_dir):
|
||||
import onnx
|
||||
from onnx import helper
|
||||
from onnx import TensorProto
|
||||
|
||||
shape = [1, 3, 2, 2]
|
||||
|
||||
input_1 = helper.make_tensor_value_info('input_1', TensorProto.FLOAT, shape)
|
||||
input_2 = helper.make_tensor_value_info('input_2', TensorProto.FLOAT, shape)
|
||||
concat_output = helper.make_tensor_value_info('concat', TensorProto.FLOAT, shape)
|
||||
relu_output = helper.make_tensor_value_info('Relu_1_data', TensorProto.FLOAT, shape)
|
||||
|
||||
node_def_1 = onnx.helper.make_node(
|
||||
'Relu',
|
||||
inputs=['input_1'],
|
||||
outputs=['Relu_1_data'],
|
||||
name='relu_1'
|
||||
)
|
||||
node_def_2 = onnx.helper.make_node(
|
||||
'Relu',
|
||||
inputs=['input_2'],
|
||||
outputs=['Relu_2_data'],
|
||||
name='relu_2'
|
||||
)
|
||||
node_def_3 = onnx.helper.make_node(
|
||||
'Concat',
|
||||
inputs=['Relu_1_data', 'Relu_2_data'],
|
||||
outputs=['concat'],
|
||||
axis=3,
|
||||
)
|
||||
|
||||
graph_def = helper.make_graph(
|
||||
[node_def_1, node_def_2, node_def_3],
|
||||
'test_model',
|
||||
[input_1, input_2],
|
||||
[relu_output, concat_output],
|
||||
)
|
||||
onnx_net = helper.make_model(graph_def, producer_name='test_model')
|
||||
model_path = temp_dir + '/test_model.onnx'
|
||||
onnx.save(onnx_net, model_path)
|
||||
return model_path
|
||||
|
||||
@pytest.mark.nightly
|
||||
@pytest.mark.precommit
|
||||
def test_ovc_convert_model_with_comma_in_names(self, ie_device, precision, ir_version,
|
||||
temp_dir, use_new_frontend, use_old_api):
|
||||
onnx_net_path = self.create_onnx_model_with_comma_in_names(temp_dir)
|
||||
ref_model = self.create_ref_graph_with_comma_in_names()
|
||||
test_params = {'input_model': onnx_net_path, 'output': 'relu_1,relu_2'}
|
||||
|
||||
self._test_by_ref_graph(temp_dir, test_params, ref_model, compare_tensor_names=False)
|
||||
|
||||
@pytest.mark.nightly
|
||||
@pytest.mark.precommit
|
||||
def test_ovc_convert_model_with_several_output(self, ie_device, precision, ir_version,
|
||||
temp_dir, use_new_frontend, use_old_api):
|
||||
onnx_net_path = self.create_onnx_model_with_several_outputs(temp_dir)
|
||||
convert_model_params = {'input_model': onnx_net_path, 'output': ['Relu_1_data', 'concat']}
|
||||
cli_tool_params = {'input_model': onnx_net_path, 'output': 'Relu_1_data,concat'}
|
||||
|
||||
self._test(temp_dir, convert_model_params, cli_tool_params)
|
||||
|
||||
|
||||
class NegativeCases(unittest.TestCase):
|
||||
test_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
|
|
|||
|
|
@ -68,11 +68,12 @@ def convert_model(
|
|||
If data type is not specified explicitly data type is taken from the original node data type.
|
||||
|
||||
:param output:
|
||||
The name of the output operation of the model or list of names. For TensorFlow*,
|
||||
do not add :0 to this name. The order of outputs in converted model is the
|
||||
same as order of specified operation names. For PaddlePaddle model represented
|
||||
as a Python object, you can specify outputs as a PaddlePaddle Python objects or
|
||||
a list of such objects.
|
||||
One or more comma-separated model outputs to be preserved in the converted model. Other outputs are removed.
|
||||
If `output` parameter is not specified then all outputs from the original model are preserved.
|
||||
Do not add :0 to the names for TensorFlow. The order of outputs in the converted model is the same as
|
||||
the order of specified names. Example: output="out_1", or output=["out_1", "out_2"].
|
||||
For PaddlePaddle model represented as a Python object, you can specify outputs as a PaddlePaddle Python
|
||||
objects or a list of such objects.
|
||||
:param example_input:
|
||||
Sample of model input in original framework.
|
||||
For PyTorch it can be torch.Tensor.
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import argparse
|
|||
import datetime
|
||||
import logging as log
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Callable
|
||||
|
||||
try:
|
||||
import openvino_telemetry as tm
|
||||
|
|
@ -39,6 +39,7 @@ from openvino.tools.ovc.moc_frontend.paddle_frontend_utils import paddle_fronten
|
|||
from openvino.frontend import FrontEndManager, OpConversionFailure, TelemetryExtension
|
||||
from openvino.runtime import get_version as get_rt_version
|
||||
from openvino.runtime import Type, PartialShape
|
||||
import re
|
||||
|
||||
try:
|
||||
from openvino.frontend.tensorflow.utils import create_tf_graph_iterator, type_supported_by_tf_fe, \
|
||||
|
|
@ -76,20 +77,43 @@ def print_argv(argv: argparse.Namespace):
|
|||
print('\n'.join(lines), flush=True)
|
||||
|
||||
|
||||
def check_iterable(iterable: Iterable, func: Callable):
|
||||
for element in iterable:
|
||||
if not func(element):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def arguments_post_parsing(argv: argparse.Namespace):
|
||||
# TODO: This function looks similar to another one. Check for code duplicates.
|
||||
log.debug("Model Conversion API started")
|
||||
if not argv.is_python_api_used:
|
||||
log.debug('Output model name would be {}{{.xml, .bin}}'.format(argv.output_model))
|
||||
|
||||
if argv.verbose:
|
||||
if is_verbose(argv):
|
||||
print_argv(argv)
|
||||
|
||||
params_parsing(argv)
|
||||
argv.output = argv.output.split(',') if isinstance(argv.output, (str, pathlib.Path)) else argv.output
|
||||
|
||||
log.debug("Placeholder shapes : {}".format(argv.placeholder_shapes))
|
||||
|
||||
if not hasattr(argv, 'output') or argv.output is None:
|
||||
return argv
|
||||
|
||||
if argv.is_python_api_used:
|
||||
error_msg = f"output '{argv.output}' is incorrect, it should be string or a list/tuple of strings"
|
||||
assert isinstance(argv.output, (str, list, tuple)), error_msg
|
||||
if isinstance(argv.output, list):
|
||||
assert check_iterable(argv.output, lambda x: isinstance(x, str)), error_msg
|
||||
else:
|
||||
argv.output = [argv.output]
|
||||
else:
|
||||
assert isinstance(argv.output, str)
|
||||
|
||||
error_msg = f"output '{argv.output}' is incorrect, output names should not be empty or contain spaces"
|
||||
processed_output = re.split(r'\s*,\s*', argv.output.strip())
|
||||
assert check_iterable(processed_output, lambda x: x.find(' ') == -1), error_msg
|
||||
assert check_iterable(processed_output, lambda x: len(x) > 0), error_msg
|
||||
argv.output = processed_output
|
||||
return argv
|
||||
|
||||
|
||||
|
|
@ -342,7 +366,7 @@ def params_parsing(argv: argparse.Namespace):
|
|||
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 {}
|
||||
if argv.framework == "pytorch" and getattr(argv, "example_input", None) is not None:
|
||||
if hasattr(argv, "framework") and argv.framework == "pytorch" and getattr(argv, "example_input", None) is not None:
|
||||
extract_input_info_from_example(argv, inputs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@ def get_convert_model_help_specifics():
|
|||
'If the dimension is set as an integer (like 100 in [1,100]), such a dimension is not supposed '
|
||||
'to be changed later, during a model conversion it is treated as a static value. '
|
||||
'Example with unnamed inputs: \"[1,100],[1,?]\".'},
|
||||
'output':
|
||||
{'description':
|
||||
'One or more comma-separated model outputs to be preserved in the converted model. '
|
||||
'Other outputs are removed. If `output` parameter is not specified then all outputs from '
|
||||
'the original model are preserved. '
|
||||
'Do not add :0 to the names for TensorFlow. The order of outputs in the converted model is the '
|
||||
'same as the order of specified names. '
|
||||
'Example: ovc model.onnx output=out_1,out_2'},
|
||||
'extension':
|
||||
{'description':
|
||||
'Paths or a comma-separated list of paths to libraries '
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import numpy as np
|
|||
|
||||
from openvino.tools.ovc.cli_parser import input_to_input_cut_info, check_positive, writable_dir, \
|
||||
readable_file_or_object, get_all_cli_parser, get_mo_convert_params
|
||||
from openvino.tools.ovc.convert_impl import pack_params_to_args_namespace
|
||||
from openvino.tools.ovc.convert_impl import pack_params_to_args_namespace, arguments_post_parsing, args_to_argv
|
||||
from openvino.tools.ovc.error import Error
|
||||
from unit_tests.ovc.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry
|
||||
from openvino.runtime import PartialShape, Dimension, Layout
|
||||
|
|
@ -366,7 +366,7 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
|
|||
from openvino.frontend import ConversionExtension
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'extension': ConversionExtension("Ext", lambda x: x),
|
||||
'input': ['name', ("a", [1,2,3], ov.Type.f32)],
|
||||
'input': ['name', ("a", [1, 2, 3], ov.Type.f32)],
|
||||
'output': ["a", "b", "c"]}
|
||||
|
||||
cli_parser = get_all_cli_parser()
|
||||
|
|
@ -375,12 +375,120 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
|
|||
assert argv.input_model == args['input_model']
|
||||
assert argv.extension == args['extension']
|
||||
assert argv.input == ['name', ("a", [1,2,3], ov.Type.f32)]
|
||||
assert argv.output == ["a","b","c"]
|
||||
assert argv.output == ["a", "b", "c"]
|
||||
|
||||
for arg, value in vars(argv).items():
|
||||
if arg not in args and arg != 'is_python_api_used':
|
||||
assert value == cli_parser.get_default(arg)
|
||||
|
||||
def test_output_post_parsing_1(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a,b,c"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
argv = arguments_post_parsing(argv)
|
||||
assert argv.output == ["a", "b", "c"]
|
||||
|
||||
def test_output_post_parsing_2(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a, b, c"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
argv = arguments_post_parsing(argv)
|
||||
assert argv.output == ["a", "b", "c"]
|
||||
|
||||
def test_output_post_parsing_3(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a,b, c"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
argv = arguments_post_parsing(argv)
|
||||
assert argv.output == ["a", "b", "c"]
|
||||
|
||||
def test_output_post_parsing_4(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a , b , c"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
argv = arguments_post_parsing(argv)
|
||||
assert argv.output == ["a", "b", "c"]
|
||||
|
||||
def test_output_post_parsing_5(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a,b"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = True
|
||||
argv = arguments_post_parsing(argv)
|
||||
assert argv.output == ["a,b"] # post parsing should decorate single string into a list
|
||||
|
||||
def test_output_post_parsing_6(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': ["first na me", "second name"]}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = True
|
||||
argv = arguments_post_parsing(argv)
|
||||
# when used in python api should be able to handle names with spaces
|
||||
assert argv.output == ["first na me", "second name"]
|
||||
|
||||
def test_raises_output_post_parsing_1(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': ["a,b, c", 23]}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = True
|
||||
self.assertRaises(AssertionError, arguments_post_parsing, argv)
|
||||
|
||||
def test_raises_output_post_parsing_2(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "na me, full_name"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
with self.assertRaisesRegex(AssertionError, ".*output names should not be empty or contain spaces"):
|
||||
arguments_post_parsing(argv)
|
||||
|
||||
def test_raises_output_post_parsing_3(self):
|
||||
args = {'input_model': os.path.dirname(__file__),
|
||||
'input': "input_1[1,2,3]",
|
||||
'output_model': os.getcwd() + "model.xml",
|
||||
'output': "a,,b,c"}
|
||||
|
||||
argv = args_to_argv(**args)
|
||||
|
||||
argv.is_python_api_used = False
|
||||
with self.assertRaisesRegex(AssertionError, ".*output names should not be empty or contain spaces"):
|
||||
arguments_post_parsing(argv)
|
||||
|
||||
def test_not_existing_dir(self):
|
||||
args = {"input_model": "abc"}
|
||||
cli_parser = get_all_cli_parser()
|
||||
|
|
|
|||
Loading…
Reference in New Issue