diff --git a/.github/workflows/mo.yml b/.github/workflows/mo.yml index 15f5c2ae92d..125db3b8507 100644 --- a/.github/workflows/mo.yml +++ b/.github/workflows/mo.yml @@ -52,6 +52,10 @@ jobs: pip install -r requirements_dev.txt working-directory: tools/mo - - name: Pylint + - name: Pylint-MO run: pylint -d C,R,W openvino/tools/mo working-directory: tools/mo + + - name: Pylint-OVC + run: pylint -d C,R,W openvino/tools/ovc + working-directory: tools/ovc \ No newline at end of file diff --git a/tools/mo/automation/package_BOM.txt b/tools/mo/automation/package_BOM.txt index d6e01875ac8..0780ce7eba8 100644 --- a/tools/mo/automation/package_BOM.txt +++ b/tools/mo/automation/package_BOM.txt @@ -41,10 +41,12 @@ openvino/tools/mo/back/MatMulNormalizer.py openvino/tools/mo/back/MaxPool.py openvino/tools/mo/back/names_uniqueness_check.py openvino/tools/mo/back/NormalizeToNormalizeL2.py +openvino/tools/mo/back/offline_transformations.py openvino/tools/mo/back/op_versioning.py openvino/tools/mo/back/OptimizeTransposeReshapeSequence.py openvino/tools/mo/back/PackBinaryWeights.py openvino/tools/mo/back/pass_separator.py +openvino/tools/mo/back/preprocessing.py openvino/tools/mo/back/priorbox_mutation.py openvino/tools/mo/back/ProposalMutation.py openvino/tools/mo/back/ReduceMerge.py @@ -829,6 +831,16 @@ openvino/tools/mo/mo_mxnet.py openvino/tools/mo/mo_onnx.py openvino/tools/mo/mo_paddle.py openvino/tools/mo/mo_tf.py +openvino/tools/mo/moc_frontend/__init__.py +openvino/tools/mo/moc_frontend/analysis.py +openvino/tools/mo/moc_frontend/check_config.py +openvino/tools/mo/moc_frontend/extractor.py +openvino/tools/mo/moc_frontend/layout_utils.py +openvino/tools/mo/moc_frontend/moc_emit_ir.py +openvino/tools/mo/moc_frontend/paddle_frontend_utils.py +openvino/tools/mo/moc_frontend/pipeline.py +openvino/tools/mo/moc_frontend/pytorch_frontend_utils.py +openvino/tools/mo/moc_frontend/shape_utils.py openvino/tools/mo/ops/__init__.py openvino/tools/mo/ops/activation.py openvino/tools/mo/ops/activation_ops.py @@ -1020,12 +1032,14 @@ openvino/tools/mo/utils/class_registration.py openvino/tools/mo/utils/cli_parser.py openvino/tools/mo/utils/custom_replacement_config.py openvino/tools/mo/utils/dsu.py +openvino/tools/mo/utils/environment_setup_utils.py openvino/tools/mo/utils/error.py openvino/tools/mo/utils/find_ie_version.py openvino/tools/mo/utils/find_inputs.py openvino/tools/mo/utils/get_ov_update_message.py openvino/tools/mo/utils/graph.py openvino/tools/mo/utils/guess_framework.py +openvino/tools/mo/utils/help.py openvino/tools/mo/utils/ie_version.py openvino/tools/mo/utils/import_extensions.py openvino/tools/mo/utils/ir_engine/__init__.py @@ -1085,6 +1099,7 @@ openvino/tools/mo/utils/shape.py openvino/tools/mo/utils/simple_proto_parser.py openvino/tools/mo/utils/str_to.py openvino/tools/mo/utils/summarize_graph.py +openvino/tools/mo/utils/telemetry_params.py openvino/tools/mo/utils/telemetry_stub.py openvino/tools/mo/utils/telemetry_utils.py openvino/tools/mo/utils/tensorboard_util.py diff --git a/tools/mo/openvino/tools/mo/back/CutMemory.py b/tools/mo/openvino/tools/mo/back/CutMemory.py index e0ca8e59569..3d2b0965f3a 100644 --- a/tools/mo/openvino/tools/mo/back/CutMemory.py +++ b/tools/mo/openvino/tools/mo/back/CutMemory.py @@ -6,7 +6,7 @@ from openvino.tools.mo.back.replacement import BackReplacementPattern from openvino.tools.mo.front.common.partial_infer.utils import mo_array from openvino.tools.mo.graph.graph import Graph from openvino.tools.mo.ops.crop import Crop -from openvino.tools.ovc.logger import log # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.logger import log class CutMemoryInput(BackReplacementPattern): diff --git a/tools/mo/openvino/tools/mo/back/offline_transformations.py b/tools/mo/openvino/tools/mo/back/offline_transformations.py new file mode 100644 index 00000000000..cb41e975f7a --- /dev/null +++ b/tools/mo/openvino/tools/mo/back/offline_transformations.py @@ -0,0 +1,128 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +from typing import List + +from openvino.tools.mo.utils.cli_parser import parse_transform +from openvino.tools.mo.utils.error import Error +from openvino.runtime import Model + + +def get_new_placeholder_name(node_id: str, is_out_port: bool = False, port: int = 0): + """ + Forms a name of new placeholder created by cutting a graph + :param node_id: a node name that is cut + :param is_out_port: it is True iff output port is cut + :param port: a port number + :return: a name of new placeholder created by cutting a graph + """ + port_type = '_out' if is_out_port else '' + return '{}/placeholder{}_port_{}'.format(node_id, port_type, port) + + +def create_params_with_custom_types(packed_user_shapes: [None, dict]): + """ + Compute a list of placeholder names for which an user specifies custom type + :param packed_user_shapes: packed data that contains input node names, + their port numbers, shapes and data types + :return: a list of placeholder names for which an user specifies custom type + Example of packed_user_shapes dictionary: + packed_user_shapes = + { + 'node_ID': + [ + {'shape': None, 'in': 0}, + {'shape': None, 'in': 1}, + ], + 'node_1_ID': + [ + {'shape': [1, 227, 227, 3], 'port': None, 'data_type': np.int32} + ], + 'node_2_ID': + [ + {'shape': None, 'out': 3} + ] + } + For which the function returns a list ['node_1_ID'] because this node only has custom data type + """ + if packed_user_shapes is None: + return [] + + params_with_custom_types = [] + for input_name in packed_user_shapes: + for desc in packed_user_shapes[input_name]: + p_name = input_name + if 'port' in desc and desc['port'] is None: # neither input nor output port specified + user_defined_type = desc.get('data_type', None) + else: # need to check the particular port the Parameter was created for + p_name = get_new_placeholder_name(input_name, 'out' in desc, + desc['out'] if 'out' in desc else desc['in']) + user_defined_type = desc.get('data_type', None) + if user_defined_type is not None: + params_with_custom_types.append(p_name) + return params_with_custom_types + +def get_available_transformations(): + try: + from openvino._offline_transformations import apply_low_latency_transformation # pylint: disable=import-error,no-name-in-module + from openvino._offline_transformations import apply_make_stateful_transformation # pylint: disable=import-error,no-name-in-module + from openvino._offline_transformations import apply_pruning_transformation # pylint: disable=import-error,no-name-in-module + return { + 'MakeStateful': apply_make_stateful_transformation, + 'LowLatency2': apply_low_latency_transformation, + 'Pruning': apply_pruning_transformation, + } + except Exception as e: + return {} + + +# net should be openvino.inference_engine.IENetwork type, but IE Engine is still optional dependency +def apply_user_transformations(func: object, transforms: list): + available_transformations = get_available_transformations() + + for name, args in transforms: + if name not in available_transformations.keys(): + raise Error("Transformation {} is not available.".format(name)) + + available_transformations[name](func, **args) + + +def apply_moc_transformations(func: object): + from openvino._offline_transformations import apply_moc_transformations # pylint: disable=import-error,no-name-in-module + apply_moc_transformations(func, cf=False, smart_reshape=True) + + +def apply_moc_legacy_transformations(func: object, params_with_custom_types: List[str]): + from openvino._offline_transformations import apply_moc_legacy_transformations # pylint: disable=import-error,no-name-in-module + apply_moc_legacy_transformations(func, params_with_custom_types) + + +def compress_model(func: object): + from openvino._offline_transformations import compress_model_transformation # pylint: disable=import-error,no-name-in-module + compress_model_transformation(func) + +def apply_fused_names_cleanup(func: object): + from openvino._offline_transformations import apply_fused_names_cleanup # pylint: disable=import-error,no-name-in-module + apply_fused_names_cleanup(func) + + +def apply_offline_transformations(func: Model, argv: argparse.Namespace): + from openvino.tools.mo.back.preprocessing import apply_preprocessing # pylint: disable=no-name-in-module,import-error + + # Apply preprocessing (mean/scale/reverse_channels/convert_layout/etc) + apply_preprocessing(ov_function=func, argv=argv) + + apply_moc_transformations(func) + + params_with_custom_types = create_params_with_custom_types(argv.packed_user_shapes) + apply_moc_legacy_transformations(func, params_with_custom_types) + apply_user_transformations(func, parse_transform(argv.transform)) + + if "compress_to_fp16" in argv and argv.compress_to_fp16: + compress_model(func) + + apply_fused_names_cleanup(func) + + return func + diff --git a/tools/mo/openvino/tools/mo/back/preprocessing.py b/tools/mo/openvino/tools/mo/back/preprocessing.py new file mode 100644 index 00000000000..cb42c6d1553 --- /dev/null +++ b/tools/mo/openvino/tools/mo/back/preprocessing.py @@ -0,0 +1,446 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import logging as log +from copy import copy + +from openvino.preprocess import PrePostProcessor # pylint: disable=no-name-in-module,import-error +# pylint: disable=no-name-in-module,import-error +from openvino.runtime import Model, Layout, PartialShape, layout_helpers + +from openvino.tools.mo.moc_frontend.layout_utils import update_layout_to_dict +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.utils.utils import refer_to_faq_msg + + +def update_mean_scale_to_dict(input_nodes: list, mean_scale_val, scale): + """ + Internal function. Updates mean/scale values from array to dictionary + :param: input_nodes Inputs of model + :param: mean_scale_val Parsed 'mean_scale_val' object from command line arguments + :param: scale Global scale factor for all inputs from scale command line arguments + """ + if not isinstance(mean_scale_val, dict): + if len(mean_scale_val) != len(input_nodes): + raise Error('Numbers of inputs and mean/scale values do not match. ' + refer_to_faq_msg(61)) + data = copy(mean_scale_val) + mean_scale_val = {} + for idx, node in enumerate(input_nodes): + names_list = list(node.get_tensor().get_names()) + names_list.sort() + if not names_list: + continue + node_name = names_list[0] + mean_scale_val.update( + { + node_name: { + 'mean': data[idx][0], + 'scale': data[idx][1] + } + } + ) + + if scale: + for node in input_nodes: + names_list = list(node.get_tensor().get_names()) + names_list.sort() + if not names_list: + continue + node_name = names_list[0] + old_val = mean_scale_val[node_name] if node_name in mean_scale_val else None + mean_scale_val.update( + { + node_name: { + 'mean': old_val['mean'] if old_val and 'mean' in old_val else None, + 'scale': scale + } + } + ) + return mean_scale_val + + +def check_keys_valid(ov_function: Model, dict_to_validate: dict, search_outputs: bool): + """ + Internal function: checks if keys from cmd line arguments correspond to ov_function's inputs/outputs + Throws if some key is not found + Throws if some different keys point to the same actual input/output + """ + nodes_used = {} + nodes = ov_function.inputs + if search_outputs: + nodes += ov_function.outputs + + # We need to replace all node names from dict to tensor names + rename_dict = {} + # Find names for replacing + for name in dict_to_validate.keys(): + for ov_node in nodes: + if name in ov_node.get_tensor().get_names(): + break + elif name == ov_node.get_node().get_friendly_name(): + assert len(ov_node.get_tensor().get_names()) > 0, 'Node must have at least one tensor name' + new_name = list(ov_node.get_tensor().get_names())[0] + rename_dict[name] = new_name + break + + # Replace found node names with tensor names + for name, new_name in rename_dict.items(): + assert name in dict_to_validate, 'Key {} is not in initial dict'.format(name) + assert new_name not in dict_to_validate, 'Key {} is already in initial dict'.format(new_name) + dict_to_validate[new_name] = dict_to_validate[name] + del dict_to_validate[name] + + # validate the dict + for name in dict_to_validate.keys(): + node_found = False + for ov_node in nodes: + if name in ov_node.get_tensor().get_names(): + if ov_node in nodes_used: + raise Error('Key for {} and {} point to same model input/output.' + .format(name, nodes_used[ov_node])) + nodes_used[ov_node] = name + node_found = True + break + + if not node_found: + if not search_outputs: + raise Error('Input with name {} wasn\'t found! {}'.format(name, refer_to_faq_msg(83))) + else: + raise Error('Input/Output with name {} wasn\'t found! {}'.format(name, refer_to_faq_msg(83))) + + +def update_layout_is_input_flag(ov_function: Model, layout_values: dict): + """ + Internal function: updates layout_values with flag whether each layout belongs to input or to output + """ + for name, layout_value in layout_values.items(): + layout_value['is_input'] = False + for ov_input in ov_function.inputs: + if name in ov_input.get_tensor().get_names(): + layout_value['is_input'] = True + break + return layout_values + + +def find_channels_dimension(shape: PartialShape, num_channels: int, name: str, layout_values): + """ + Internal function. Finds dimension index matching with expected channels number + Raises error if there is no candidates or number of candidates is > 1 + :param: shape Parameter's partial shape + :param: num_channels Number of channels to find in shape + :param: name Parameter's name, used for Error-handling purposes + :param: layout_values Existing source/target layout items specified by user + :return: updated layout items with guessed layouts + """ + if shape.rank.is_dynamic: + raise Error('Can\'t determine channels dimension for dynamic shape for parameter {}.' + .format(name)) + + dim_idx_found = -1 + for dim_idx in range(shape.rank.get_length()): + dim = shape.get_dimension(dim_idx) + if dim.is_static and dim.get_length() == num_channels: + if dim_idx_found >= 0: + raise Error('Can\'t determine channels dimension for {}. ' + 'Input shape is {}, needed channels {}. ' + 'Conflicting dimensions: {} and {}. Please specify layout manually.' + .format(name, shape, num_channels, dim_idx_found, dim_idx)) + dim_idx_found = dim_idx + if dim_idx_found < 0: + raise Error('Can\'t determine channels dimension for {}. ' + 'Input shape is {}, needed channels {}' + .format(name, shape, num_channels)) + + # Restrict guessed channels index to particular position depending on tensor shape(3d, 4d, 5d) + if shape.rank.get_length() == 3: + # CHW or HWC, possible channels index is 0 or 2 + if dim_idx_found != 0 and dim_idx_found != 2: + raise Error('Can\'t determine channels dimension for 3D input {} (CHW or HWC) with shape {}. ' + 'Please specify layout containing \'C\' channels manually.'.format(name, shape)) + elif shape.rank.get_length() == 4: + # NCHW or NHWC, possible channels index is 1 or 3 + if dim_idx_found != 1 and dim_idx_found != 3: + raise Error('Can\'t determine channels dimension for 4D input {} (NCHW or NHWC) with shape {}. ' + 'Please specify layout containing \'C\' channels manually.'.format(name, shape)) + elif shape.rank.get_length() == 5: + # NCDHW or NDHWC, possible channels index is 1 or 4 + if dim_idx_found != 1 and dim_idx_found != 4: + raise Error('Can\'t determine channels dimension for 5D input {} (NCDHW or NDHWC) with shape {}. ' + 'Please specify layout containing \'C\' channels manually.'.format(name, shape)) + else: + raise Error('Can\'t determine channels dimension for {}D input {} with shape {}.' + 'Please specify layout containing \'C\' channels manually.' + .format(shape.rank.get_length(), name, shape)) + + layout_str = "?" * shape.rank.get_length() + layout_str = layout_str[:dim_idx_found] + 'C' + layout_str[dim_idx_found + 1:] + layout_values[name] = { + 'source_layout': layout_str, + 'target_layout': None, + 'source_guessed': True, + 'is_input': True + } + return layout_values + + +def guess_source_layouts_by_mean_scale(ov_function: Model, layout_values, mean_scale_values: dict): + """ + Internal function. Try to guess source layout for input by its shape and/or framework + :param: ov_function Original model + :param: layout_values Existing source/target layout items specified by user + :param: mean_scale_values Dictionary with mean/scale values defined for each argument + :return: updated layout items with guessed layouts + """ + for ms_name, mean_scale in mean_scale_values.items(): + num_channels_mean = len(mean_scale['mean']) if mean_scale['mean'] is not None else 0 + num_channels_scale = len(mean_scale['scale']) if hasattr(mean_scale['scale'], '__len__') else 0 + + if num_channels_mean > 1 and \ + num_channels_scale > 1 and \ + num_channels_mean is not num_channels_scale: + raise Error('Mean/Scale values for {} have different sizes: {} {}' + .format(ms_name, num_channels_mean, num_channels_scale)) + + need_guess_channels = num_channels_mean > 1 or num_channels_scale > 1 + if not need_guess_channels: # Mean/scale is complex and needs 'channels' specified in layout + continue + + num_channels = num_channels_mean if num_channels_mean > 1 else num_channels_scale + + for i in range(0, len(ov_function.inputs)): + ov_input = ov_function.input(i) + + if not ov_function.get_parameters()[i].layout.empty: + continue + + if ms_name not in ov_input.get_tensor().get_names(): + continue + + layout_item = None + for name in ov_input.get_tensor().get_names(): + if name in layout_values: + layout_item = layout_values[name] + break + + if layout_item is not None: + # User specified some layout, skip guessing + continue + + # Guess layout is applicable only when number of channels is '3' + if num_channels != 3: + raise Error('Can\'t determine channels dimension for {}. ' + 'When number of mean/scale values is {} (not 3), ' + 'please specify layout for input manually'.format(ms_name, num_channels)) + + layout_values = find_channels_dimension(shape=ov_input.get_partial_shape(), + num_channels=num_channels, + name=ms_name, + layout_values=layout_values) + return layout_values + + +def check_suitable_for_reverse(layout: Layout, ov_input): + """ + Internal function. Checks if input with layout is suitable for reversing channels + :param: layout Existing source/target layout items specified by user + :param: ov_input Model's input + :return: True if reverse channels can be applied to input + """ + if not layout_helpers.has_channels(layout): + return False + if ov_input.get_partial_shape().rank.is_dynamic: + return False + + c_idx = layout_helpers.channels_idx(layout) + rank = ov_input.get_partial_shape().rank.get_length() + if c_idx < 0: + c_idx += rank + if c_idx >= rank: + raise Error('Layout {} for input {} is inconsistent with shape {}'.format( + layout, ov_input.get_tensor().get_any_name(), ov_input.get_partial_shape())) + c_num = ov_input.get_partial_shape()[c_idx] + return c_num.is_dynamic or c_num.get_length() == 3 + + +def guess_source_layouts_for_reverse_channels(ov_function: Model, layout_values): + """ + Internal function. Try to guess source layout for input by finding dimension with size=3 (RGB/BGR) + Additionally checks existing layouts and detects suitable inputs for reversing of input channels + :param: ov_function Original model + :param: layout_values Existing source/target layout items specified by user + :return: array with suitable parameters for reversing of input channels + """ + all_params = [] + suitable_params = [] + for i in range(0, len(ov_function.inputs)): + ov_input = ov_function.input(i) + param_info = [ov_input.get_tensor().get_any_name(), ov_input.get_partial_shape()] + all_params.append(param_info) + + if not ov_function.get_parameters()[i].layout.empty: + if check_suitable_for_reverse(ov_function.get_parameters()[i].layout, ov_input): + suitable_params.append(param_info) + continue + + layout_item = None + first_name = ov_input.get_tensor().get_any_name() + for name in ov_input.get_tensor().get_names(): + if name in layout_values: + layout_item = layout_values[name] + break + + if layout_item is not None: + # RIC transformation is applied before changing layout so only source_layout + # should be checked (even is target_layout is also provided) + if layout_item.get('source_layout'): + if check_suitable_for_reverse(Layout(layout_item['source_layout']), ov_input): + suitable_params.append(param_info) + continue + + try: + layout_values = find_channels_dimension(shape=ov_input.get_partial_shape(), + num_channels=3, + name=first_name, + layout_values=layout_values) + except Error as e: + log.debug('Reverse input channels guess did not succeed {}'.format(e)) + else: + layout = layout_values[first_name].get('source_layout') + if layout and check_suitable_for_reverse(Layout(layout), ov_input): + suitable_params.append(param_info) + + if not len(suitable_params): + raise Error('Network has {} inputs overall, but none of them are suitable for input channels reversing.\n' + 'Suitable for input channel reversing inputs are 4-dimensional with 3 channels (in case of dynamic ' + 'dimensions C channel must be provided in a layout for this input)\nAll inputs: {}'.format( + len(all_params), all_params)) + elif len(suitable_params) < len(all_params): + log.error('Network has {} inputs overall, but only {} of them are suitable for input channels reversing.\n' + 'Suitable for input channel reversing inputs are 4-dimensional with 3 channels (in case of dynamic ' + 'dimensions C channel must be provided in a layout for this input)\nAll inputs: {}\n' + 'Suitable inputs {}'.format(len(all_params), len(suitable_params), all_params, suitable_params), + extra={'is_warning': True}) + return suitable_params + + +def update_tensor_names_to_first_in_sorted_list(values_dict: dict, ov_function: Model): + if not isinstance(values_dict, dict): + return values_dict + updated_dict = {} + used_nodes = {} + for name, value in values_dict.items(): + input_found = False + for input in ov_function.inputs: + tensor_names = list(input.names) + tensor_names.sort() + if not (name in tensor_names or name == input.node.get_friendly_name()): + continue + if input in used_nodes: + raise Error("Tensor names {} and {} refer to the same node.".format(name, used_nodes[input])) + used_nodes.update({input: name}) + updated_dict[tensor_names[0]] = value + input_found = True + break + if not input_found: + raise Error('Input with name {} wasn\'t found! {}'.format(name, refer_to_faq_msg(83))) + + return updated_dict + + +def apply_preprocessing(ov_function: Model, argv: argparse.Namespace): + """ + Applies pre-processing of model inputs by adding appropriate operations + On return, 'ov_function' object will be updated + Expected 'argv.mean_scale_values' formats examples: + a) Dict: {'inputName': {'mean': [1., 2., 3.], 'scale': [2., 4., 8.]}} + b) List: list(np.array([(np.array([1., 2., 3.]), np.array([2., 4., 6.])), + (np.array([7., 8., 9.]), np.array([5., 6., 7.]))) + Expected 'argv.layout_values' format examples: + a) Specific layouts for inputs and outputs + { 'input1': { + 'source_layout': 'nchw', + 'target_layout': 'nhwc' + }, + 'output2': { + 'source_layout': 'nhwc' + } + } + b) Layout for single input: {'': {'source_layout': 'nchw'}} + :param: ov_function OV function for applying mean/scale pre-processing + :param: argv Parsed command line arguments + """ + prep = PrePostProcessor(ov_function) + + if 'mean_scale_values' in argv and argv.mean_scale_values: + mean_scale_values = argv.mean_scale_values + else: + mean_scale_values = {} + + # mean_scale_values stores mean/scale values from command line with names which were set by user. + # For models with single input scale or mean may be unnamed, so name is set by first tensor name from + # names list. This may lead to different naming of preprocessing params for a single node and lead to error. + # To make naming for mean/scale values unified, names provided by user are renamed here + # by the first tensor name from sorted names list. + mean_scale_values = update_tensor_names_to_first_in_sorted_list(mean_scale_values, ov_function) + mean_scale_values = update_mean_scale_to_dict(input_nodes=ov_function.inputs, + mean_scale_val=mean_scale_values, + scale=argv.scale) + # On return, mean_scale_values is a dictionary with input names as key and mean/scale pair as value + # {'inputName': {'mean': [1., 2., 3.], 'scale': [2.]}} + + layout_values = {} + if 'layout_values' in argv and argv.layout_values: + layout_values = update_layout_to_dict(ov_function.inputs, argv.layout_values, + lambda ov_input: ov_input.get_tensor().get_names()) + + check_keys_valid(ov_function=ov_function, dict_to_validate=mean_scale_values, search_outputs=False) + check_keys_valid(ov_function=ov_function, dict_to_validate=layout_values, search_outputs=True) + + layout_values = update_layout_is_input_flag(ov_function, layout_values) + layout_values = guess_source_layouts_by_mean_scale(ov_function, layout_values, mean_scale_values) + need_reverse = 'reverse_input_channels' in argv and argv.reverse_input_channels + suitable_params_ric = [] + if need_reverse: + suitable_params_ric = guess_source_layouts_for_reverse_channels(ov_function=ov_function, + layout_values=layout_values) + + for node_name, layout_value in layout_values.items(): + if layout_value.get('source_layout'): + if layout_value.get('is_input'): + prep.input(node_name).model().set_layout(Layout(layout_value['source_layout'])) + else: + prep.output(node_name).model().set_layout(Layout(layout_value['source_layout'])) + if layout_value.get('target_layout'): + if layout_value.get('is_input'): + prep.input(node_name).tensor().set_layout(Layout(layout_value['target_layout'])) + else: + prep.output(node_name).tensor().set_layout(Layout(layout_value['target_layout'])) + + # Apply reverse_input_channels + if need_reverse: + for name, _ in suitable_params_ric: + prep.input(name).preprocess().reverse_channels() + log.debug('reverse_input_channels pre-processing applied to {}'.format(name)) + + for node_name, node_mean_scale_values in mean_scale_values.items(): + # Apply mean first, then scale + if node_mean_scale_values['mean'] is not None: + prep.input(node_name).preprocess().mean(node_mean_scale_values['mean']) + if node_mean_scale_values['scale'] is not None: + prep.input(node_name).preprocess().scale(node_mean_scale_values['scale']) + log.debug('Mean/Scale pre-processing applied to {}'.format(node_name)) + + # Apply pre-processing builder to a function + ov_function = prep.build() + + # Remove guessed layout values from ov_function (these values shall not be serialized to IR + for node_name, layout_value in layout_values.items(): + if layout_value.get('source_guessed') and \ + not layout_value.get('target_layout'): + # search for parameter object + for idx, ov_input in enumerate(ov_function.inputs): + if node_name in ov_input.get_tensor().get_names(): + log.debug('Clearing guessed layout {} for {}' + .format(layout_value['source_layout'], node_name)) + ov_function.get_parameters()[idx].layout = Layout() diff --git a/tools/mo/openvino/tools/mo/convert.py b/tools/mo/openvino/tools/mo/convert.py index 06855965e40..7b2c92747b7 100644 --- a/tools/mo/openvino/tools/mo/convert.py +++ b/tools/mo/openvino/tools/mo/convert.py @@ -7,8 +7,8 @@ from typing import Any from openvino.runtime import PartialShape, Shape, Layout, Model from openvino.tools.mo.convert_impl import _convert from openvino.tools.ovc import InputCutInfo, LayoutMap # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.logger import get_logger_state, restore_logger_state # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.logger import get_logger_state, restore_logger_state # pylint: disable=no-name-in-module,import-error def convert_model( diff --git a/tools/mo/openvino/tools/mo/convert_impl.py b/tools/mo/openvino/tools/mo/convert_impl.py index 7d183992f5e..37042a28b10 100644 --- a/tools/mo/openvino/tools/mo/convert_impl.py +++ b/tools/mo/openvino/tools/mo/convert_impl.py @@ -18,10 +18,10 @@ except ImportError: import openvino.tools.mo.utils.telemetry_stub as tm from openvino.tools.mo.back.SpecialNodesFinalization import RemoveConstOps, CreateConstNodesReplacement, NormalizeTI -from openvino.tools.ovc.moc_frontend.check_config import legacy_transformations_config_used, \ +from openvino.tools.mo.moc_frontend.check_config import legacy_transformations_config_used, \ tensorflow_custom_operations_config_update_used, new_extensions_used # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.moc_frontend.pipeline import moc_pipeline # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.moc_frontend.moc_emit_ir import moc_emit_ir # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.pipeline import moc_pipeline # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.moc_emit_ir import moc_emit_ir # pylint: disable=no-name-in-module,import-error 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 @@ -30,7 +30,7 @@ from openvino.tools.mo.pipeline.unified import unified_pipeline from openvino.tools.mo.utils import import_extensions # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.cli_parser import check_available_transforms, \ +from openvino.tools.mo.utils.cli_parser import check_available_transforms, \ get_advanced_cli_options, get_available_front_ends, get_caffe_cli_options, \ 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, \ @@ -39,20 +39,20 @@ from openvino.tools.ovc.cli_parser import check_available_transforms, \ input_shape_to_input_cut_info, freeze_placeholder_to_input_cut_info from openvino.tools.mo.utils.error import Error, FrameworkError -from openvino.tools.ovc.get_ov_update_message import get_ov_update_message, get_ov_api20_message, \ +from openvino.tools.mo.utils.get_ov_update_message import get_ov_update_message, get_ov_api20_message, \ get_tf_fe_message, get_compression_message # pylint: disable=no-name-in-module,import-error from openvino.tools.mo.utils.get_ov_update_message import get_try_legacy_fe_message from openvino.tools.mo.utils.model_analysis import AnalysisResults from openvino.tools.mo.utils.version import VersionChecker from openvino.tools.mo.utils.guess_framework import deduce_legacy_frontend_by_namespace -from openvino.tools.ovc.logger import init_logger, progress_printer # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.logger import init_logger, progress_printer # pylint: disable=no-name-in-module,import-error from openvino.tools.mo.utils.utils import refer_to_faq_msg, check_values_equal from openvino.tools.mo.utils.telemetry_utils import send_params_info, send_framework_info, send_conversion_result, \ get_tid -from openvino.tools.ovc.moc_frontend.check_config import legacy_extensions_used # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.moc_frontend.pytorch_frontend_utils import get_pytorch_decoder, extract_input_info_from_example # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.moc_frontend.paddle_frontend_utils import paddle_frontend_converter # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.moc_frontend.shape_utils import parse_input_shapes # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.check_config import legacy_extensions_used # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.pytorch_frontend_utils import get_pytorch_decoder, extract_input_info_from_example # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.paddle_frontend_utils import paddle_frontend_converter # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.shape_utils import parse_input_shapes # pylint: disable=no-name-in-module,import-error # pylint: disable=no-name-in-module,import-error from openvino.frontend import FrontEndManager, OpConversionFailure, ProgressReporterExtension, TelemetryExtension @@ -493,7 +493,7 @@ def emit_ir(graph: Graph, argv: argparse.Namespace, non_default_params: dict): return_code = "not executed" if not (argv.framework == 'tf' and argv.tensorflow_custom_operations_config_update): try: - from openvino.tools.ovc.moc_frontend.offline_transformations import apply_offline_transformations # pylint: disable=no-name-in-module,import-error + from openvino.tools.mo.back.offline_transformations import apply_offline_transformations # pylint: disable=no-name-in-module,import-error func = apply_offline_transformations(func, argv) if "compress_to_fp16" in argv and argv.compress_to_fp16: # restore data_type cmd parameter diff --git a/tools/mo/openvino/tools/mo/front/tf/loader.py b/tools/mo/openvino/tools/mo/front/tf/loader.py index 4e0035a76ac..758e3f5bea3 100644 --- a/tools/mo/openvino/tools/mo/front/tf/loader.py +++ b/tools/mo/openvino/tools/mo/front/tf/loader.py @@ -11,7 +11,7 @@ from pathlib import Path from openvino.tools.mo.graph.graph import Node from openvino.tools.mo.utils.error import Error, FrameworkError from openvino.tools.mo.utils.utils import refer_to_faq_msg -from openvino.tools.ovc.environment_setup_utils import get_environment_setup # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.environment_setup_utils import get_environment_setup # pylint: disable=no-name-in-module,import-error # do not print INFO and WARNING messages from TensorFlow os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' diff --git a/tools/mo/openvino/tools/mo/main.py b/tools/mo/openvino/tools/mo/main.py index b6d3affa738..346d0138c37 100644 --- a/tools/mo/openvino/tools/mo/main.py +++ b/tools/mo/openvino/tools/mo/main.py @@ -34,5 +34,5 @@ def main(cli_parser: argparse.ArgumentParser, framework=None): if __name__ == "__main__": - from openvino.tools.ovc.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error + from openvino.tools.mo.utils.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error sys.exit(main(get_all_cli_parser(), None)) diff --git a/tools/mo/openvino/tools/mo/main_caffe.py b/tools/mo/openvino/tools/mo/main_caffe.py index a2a30426638..e8cae090166 100644 --- a/tools/mo/openvino/tools/mo/main_caffe.py +++ b/tools/mo/openvino/tools/mo/main_caffe.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_caffe_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_caffe_cli_parser # pylint: disable=no-name-in-module,import-error if __name__ == "__main__": from openvino.tools.mo.main import main diff --git a/tools/mo/openvino/tools/mo/main_kaldi.py b/tools/mo/openvino/tools/mo/main_kaldi.py index 2c29fa8cb08..6cc2ab8ffff 100644 --- a/tools/mo/openvino/tools/mo/main_kaldi.py +++ b/tools/mo/openvino/tools/mo/main_kaldi.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_kaldi_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_kaldi_cli_parser # pylint: disable=no-name-in-module,import-error if __name__ == "__main__": from openvino.tools.mo.main import main diff --git a/tools/mo/openvino/tools/mo/main_mxnet.py b/tools/mo/openvino/tools/mo/main_mxnet.py index 46c4d5f9a81..b5e040f57e7 100644 --- a/tools/mo/openvino/tools/mo/main_mxnet.py +++ b/tools/mo/openvino/tools/mo/main_mxnet.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_mxnet_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_mxnet_cli_parser # pylint: disable=no-name-in-module,import-error if __name__ == "__main__": from openvino.tools.mo.main import main diff --git a/tools/mo/openvino/tools/mo/main_onnx.py b/tools/mo/openvino/tools/mo/main_onnx.py index cb4ccc4a00b..c37266b7e26 100644 --- a/tools/mo/openvino/tools/mo/main_onnx.py +++ b/tools/mo/openvino/tools/mo/main_onnx.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_onnx_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_onnx_cli_parser # pylint: disable=no-name-in-module,import-error if __name__ == "__main__": from openvino.tools.mo.main import main diff --git a/tools/mo/openvino/tools/mo/main_paddle.py b/tools/mo/openvino/tools/mo/main_paddle.py index 6f5f393b4d3..984351f203c 100644 --- a/tools/mo/openvino/tools/mo/main_paddle.py +++ b/tools/mo/openvino/tools/mo/main_paddle.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_all_cli_parser # pylint: disable=no-name-in-module,import-error from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-module,import-error diff --git a/tools/mo/openvino/tools/mo/main_tf.py b/tools/mo/openvino/tools/mo/main_tf.py index e68c8bda516..e5d58b46003 100644 --- a/tools/mo/openvino/tools/mo/main_tf.py +++ b/tools/mo/openvino/tools/mo/main_tf.py @@ -3,7 +3,7 @@ import sys -from openvino.tools.ovc.cli_parser import get_tf_cli_parser # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import get_tf_cli_parser # pylint: disable=no-name-in-module,import-error if __name__ == "__main__": from openvino.tools.mo.main import main diff --git a/tools/mo/openvino/tools/mo/middle/passes/convert_data_type.py b/tools/mo/openvino/tools/mo/middle/passes/convert_data_type.py index d72878f5c80..38c9d8047c7 100644 --- a/tools/mo/openvino/tools/mo/middle/passes/convert_data_type.py +++ b/tools/mo/openvino/tools/mo/middle/passes/convert_data_type.py @@ -10,10 +10,88 @@ from openvino.tools.mo.graph.graph import Node, Graph from openvino.tools.mo.utils.error import Error from openvino.tools.mo.utils.utils import refer_to_faq_msg -# pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.convert_data_type import packed_U1, packed_U4, packed_I4, SUPPORTED_DATA_TYPES, \ - data_type_str_to_np, data_type_str_to_precision, data_type_str_to_destination_type, np_data_type_to_precision, \ - np_data_type_to_destination_type, destination_type_to_np_data_type, precision_to_destination_type +""" +Packed data of custom types are stored in numpy uint8 data type. +To distinguish true uint8 and custom data we introduce this class not to store, +but to have unique data type in SUPPORTED_DATA_TYPES map +""" + + +class packed_U1(np.generic): + pass + + +class packed_U4(np.generic): + pass + + +class packed_I4(np.generic): + pass + + +SUPPORTED_DATA_TYPES = { + 'float': (np.float32, 'FP32', 'f32'), + 'half': (np.float16, 'FP16', 'f16'), + 'FP32': (np.float32, 'FP32', 'f32'), + 'FP64': (np.float64, 'FP64', 'f64'), + 'FP16': (np.float16, 'FP16', 'f16'), + 'I32': (np.int32, 'I32', 'i32'), + 'I64': (np.int64, 'I64', 'i64'), + 'int8': (np.int8, 'I8', 'i8'), + 'int32': (np.int32, 'I32', 'i32'), + 'int64': (np.int64, 'I64', 'i64'), + 'bool': (bool, 'BOOL', 'boolean'), + 'uint8': (np.uint8, 'U8', 'u8'), + 'uint32': (np.uint32, 'U32', 'u32'), + 'uint64': (np.uint64, 'U64', 'u64'), + + # custom types + 'U1': (packed_U1, 'U1', 'u1'), + 'int4': (packed_I4, 'I4', 'i4'), + 'uint4': (packed_U4, 'U4', 'u4'), + 'I4': (packed_I4, 'I4', 'i4'), + 'U4': (packed_U4, 'U4', 'u4'), +} + + +def data_type_str_to_np(data_type_str: str): + return SUPPORTED_DATA_TYPES[data_type_str][0] if data_type_str in SUPPORTED_DATA_TYPES else None + + +def data_type_str_to_precision(data_type_str: str): + return SUPPORTED_DATA_TYPES[data_type_str][1] if data_type_str in SUPPORTED_DATA_TYPES else None + + +def data_type_str_to_destination_type(data_type_str: str): + return SUPPORTED_DATA_TYPES[data_type_str][2] if data_type_str in SUPPORTED_DATA_TYPES else None + + +def np_data_type_to_precision(np_data_type): + for np_t, precision, _ in SUPPORTED_DATA_TYPES.values(): + if np_t == np_data_type: + return precision + raise Error('Data type "{}" is not supported'.format(np_data_type)) + + +def np_data_type_to_destination_type(np_data_type): + for np_t, _, destination_type in SUPPORTED_DATA_TYPES.values(): + if np_t == np_data_type: + return destination_type + raise Error('Data type "{}" is not supported'.format(np_data_type)) + + +def destination_type_to_np_data_type(dst_type): + for np_t, _, destination_type in SUPPORTED_DATA_TYPES.values(): + if destination_type == dst_type: + return np_t + raise Error('Destination type "{}" is not supported'.format(dst_type)) + + +def precision_to_destination_type(data_type_str): + for _, precision, destination_type in SUPPORTED_DATA_TYPES.values(): + if precision == data_type_str: + return destination_type + raise Error('Data type "{}" is not supported'.format(data_type_str)) def convert_blob(blob: np.ndarray, dst_type: type): diff --git a/tools/mo/openvino/tools/mo/moc_frontend/__init__.py b/tools/mo/openvino/tools/mo/moc_frontend/__init__.py new file mode 100644 index 00000000000..cddd115d397 --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/tools/mo/openvino/tools/mo/moc_frontend/analysis.py b/tools/mo/openvino/tools/mo/moc_frontend/analysis.py new file mode 100644 index 00000000000..cc4d99ed794 --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/analysis.py @@ -0,0 +1,45 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import json +from openvino.runtime import PartialShape, Model, Type # pylint: disable=no-name-in-module,import-error +from openvino.runtime.utils.types import get_dtype + +def json_model_analysis_dump(framework_model: Model): + + def dump_partial_shape(shape: PartialShape): + if shape.rank.is_dynamic: + return 'None' + return [dim.get_length() if dim.is_static else 0 for dim in shape] + + def dump_element_type(ov_type: Type): + try: + return str(get_dtype(ov_type)) + except: + return 'None' + + json_dump = {} + json_dump['inputs'] = {} + for param in framework_model.get_parameters(): + param_name = param.get_friendly_name() + json_dump['inputs'][param_name] = {} + json_dump['inputs'][param_name]['shape'] = dump_partial_shape(param.get_partial_shape()) + json_dump['inputs'][param_name]['data_type'] = dump_element_type(param.get_element_type()) + json_dump['inputs'][param_name]['value'] = 'None' # not supported in 22.1 + + json_dump['intermediate'] = {} + #TODO: extend model analysis dump for operations with body graphs (If, Loop, and TensorIterator) + for op in filter(lambda node: node.type_info.name != "NullNode", framework_model.get_ordered_ops()): + for out_idx in range(op.get_output_size()): + output = op.output(out_idx) + tensor_name = output.get_any_name() + json_dump['intermediate'][tensor_name] = {} + json_dump['intermediate'][tensor_name]['shape'] = dump_partial_shape(output.get_partial_shape()) + json_dump['intermediate'][tensor_name]['data_type'] = dump_element_type(output.get_element_type()) + json_dump['intermediate'][tensor_name]['value'] = 'None' # not supported in 22.1 + + json_model_analysis_print(json_dump) + + +def json_model_analysis_print(json_dump:str): + print(json.dumps(json_dump)) diff --git a/tools/mo/openvino/tools/mo/moc_frontend/check_config.py b/tools/mo/openvino/tools/mo/moc_frontend/check_config.py new file mode 100644 index 00000000000..8f9f6d67b7e --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/check_config.py @@ -0,0 +1,102 @@ +# Copyright (C) 2022-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +from pathlib import Path + +from openvino.tools.mo.utils.error import Error +import os + + +def default_path(): + EXT_DIR_NAME = '.' + return os.path.abspath(os.getcwd().join(EXT_DIR_NAME)) + + +def any_extensions_used(argv: argparse.Namespace): + # Checks that extensions are provided. + # Allowed types are string containing path to legacy extension directory + # or path to new extension .so file, or classes inherited from BaseExtension. + if not hasattr(argv, 'extensions') or argv.extensions is None: + return False + + if isinstance(argv.extensions, list) and len(argv.extensions) > 0: + has_non_default_path = False + has_non_str_objects = False + for ext in argv.extensions: + if not isinstance(ext, str): + has_non_str_objects = True + continue + if len(ext) == 0 or ext == default_path(): + continue + has_non_default_path = True + + return has_non_default_path or has_non_str_objects + + raise Exception("Expected list of extensions, got {}.".format(type(argv.extensions))) + + +def legacy_extensions_used(argv: argparse.Namespace): + if any_extensions_used(argv): + extensions = argv.extensions + legacy_ext_counter = 0 + for extension in extensions: + if not isinstance(extension, str): + continue + if extension == default_path(): + continue + if not Path(extension).is_file(): + legacy_ext_counter += 1 + if legacy_ext_counter == len(extensions): + return True # provided only legacy extensions + elif legacy_ext_counter == 0: + return False # provided only new extensions + else: + raise Error('Using new and legacy extensions in the same time is forbidden') + return False + + +def new_extensions_used(argv: argparse.Namespace): + if any_extensions_used(argv): + extensions = argv.extensions + if not isinstance(extensions, list): + extensions = [extensions] + new_ext_counter = 0 + for extension in extensions: + if isinstance(extension, str): + path = Path(extension) + if path.is_file() and (path.suffix == '.so' or path.suffix == '.dll'): + new_ext_counter += 1 + else: + new_ext_counter += 1 + if new_ext_counter == len(extensions): + return True # provided only new extensions + elif new_ext_counter == 0: + return False # provided only legacy extensions + else: + raise Error('Using new and legacy extensions in the same time is forbidden') + return False + + +def get_transformations_config_path(argv: argparse.Namespace) -> Path: + if hasattr(argv, 'transformations_config') \ + and argv.transformations_config is not None and len(argv.transformations_config): + if isinstance(argv.transformations_config, str): + path = Path(argv.transformations_config) + if path.is_file(): + return path + return None + + +def legacy_transformations_config_used(argv: argparse.Namespace): + return get_transformations_config_path(argv) != None + + +def tensorflow_custom_operations_config_update_used(argv: argparse.Namespace): + return hasattr(argv, 'tensorflow_custom_operations_config_update') and \ + argv.tensorflow_custom_operations_config_update is not None + + +def input_freezig_used(argv): + return hasattr(argv, 'freeze_placeholder_with_value') and argv.freeze_placeholder_with_value is not None \ + and len(argv.freeze_placeholder_with_value) > 0 diff --git a/tools/mo/openvino/tools/mo/moc_frontend/extractor.py b/tools/mo/openvino/tools/mo/moc_frontend/extractor.py new file mode 100644 index 00000000000..ca11d6663dc --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/extractor.py @@ -0,0 +1,461 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import re +from enum import Enum + +import numpy as np +from openvino._pyopenvino import Place, PartialShape # pylint: disable=no-name-in-module,import-error + +from openvino.frontend import InputModel # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.error import Error + + +def raise_no_node(node_name: str): + raise Error('No node with name {}'.format(node_name)) + + +def raise_node_name_collision(node_name: str, found_nodes: list): + raise Error('Name collision was found, there are several nodes for mask "{}": {}. ' + 'If your intention was to specify port for node, please instead specify node names connected to ' + 'this port. If your intention was to specify the node name, please add port to the node ' + 'name'.format(node_name, found_nodes)) + + +class IOType(Enum): + Input = 1 + Output = 2 + + +def decode_name_with_port( + input_model: InputModel, node_name: str, framework="", io_type=IOType.Input +) -> Place or None: + """ + Decode name with optional port specification w/o traversing all the nodes in the graph + TODO: in future node_name can specify input/output port groups as well as indices (58562) + :param input_model: Input Model + :param node_name: user provided node name + :return: decoded place in the graph + """ + found_places = [] + found_place_names = [] + + def get_place_by_operation_name(input_model, name, framework, io_type): + node = input_model.get_place_by_operation_name(name) + if node and framework == "onnx": + if io_type == IOType.Input: + return ( + node.get_input_port(input_port_index=0) + .get_producing_port() + .get_target_tensor() + ) + else: + return node.get_output_port(output_port_index=0).get_target_tensor() + return node + + # find by tensor name + place = input_model.get_place_by_tensor_name(node_name) + if place: + found_place_names.append("Tensor:" + node_name) + found_places.append(place) + else: + # find by operation name + place = get_place_by_operation_name(input_model, node_name, framework, io_type) + name = node_name + if framework == "onnx" and io_type == IOType.Output: + name = "Tensor:" + name + + if place: + found_place_names.append(name) + found_places.append(place) + + def try_get_node(model, name, framework): + node = model.get_place_by_operation_name(name) + if node: + return node + if framework == "onnx": + tensor = model.get_place_by_tensor_name(name) + if tensor: + if tensor.is_input() or tensor.is_output(): + return tensor + return tensor.get_producing_operation() + return None + + def get_port(match, match_starts_with_name, input_model, framework): + if not match: + return None + + if match_starts_with_name: + name = match.group(1) + port_index = match.group(2) + else: + name = match.group(2) + port_index = match.group(1) + + node = try_get_node(input_model, name, framework) + if node: + # if regular expression has structure :, get node output port. + # Otherwise get node input port + if match_starts_with_name: + return node.get_output_port(output_port_index=int(port_index)) + else: + return node.get_input_port(input_port_index=int(port_index)) + else: + return None + + regexp_post = r"(.+):(\d+)" + match = re.search(regexp_post, node_name) + match_port = get_port( + match=match, + match_starts_with_name=True, + input_model=input_model, + framework=framework, + ) + + if match_port: + name = match.group(1) + if framework == "onnx": + found_place_names.append("Tensor:" + name) + found_places.append(match_port.get_target_tensor()) + else: + found_place_names.append(name) + found_places.append(match_port) + + regexp_pre = r"(\d+):(.+)" + match = re.search(regexp_pre, node_name) + match_port = get_port( + match=match, + match_starts_with_name=False, + input_model=input_model, + framework=framework, + ) + + if match_port: + name = match.group(2) + if framework == "onnx": + found_place_names.append("Tensor:" + name) + found_places.append(match_port.get_producing_port().get_target_tensor()) + else: + found_places.append(match_port) + found_place_names.append(name) + + if len(found_places) == 0: + raise_no_node(node_name) + + # Check that there is no collision, all found places shall point to same data + if not all([n.is_equal_data(found_places[0]) for n in found_places]): + raise_node_name_collision(node_name, found_place_names) + + # TODO: Add support for input/output group name and port index here (58562) + # For new frontends logic shall be extended to additionally support input and output group names + return found_places[0] + + +def fe_input_user_data_repack( + input_model: InputModel, + input_user_shapes: [None, list, dict, np.ndarray], + freeze_placeholder: dict, + framework: str, + input_user_data_types=None, +): + """ + Restructures user input cutting request. Splits ports out of node names. + Transforms node names to node ids. + :param input_model: current input model + :param input_user_shapes: data structure representing user input cutting request. It may be: + # None value if user did not provide neither "input" nor "input_shape" keys + # list instance which contains input layer names with or without ports if user provided + only "input" key + # dict instance which contains input layer names with or without ports as keys and shapes as + values if user provided both "input" and "input_shape" + # np.ndarray if user provided only "input_shape" key + :param freeze_placeholder: dictionary with placeholder names as keys and freezing value as values + :param input_user_data_types: dictionary with input nodes and its data types + :return: restructured input shapes and freeze placeholder shapes information + Example of input dictionary: + _input_shapes = + { + 'node_ID': + [ + {'shape': None, 'in': 0}, + {'shape': None, 'in': 1}, + ], + 'node_1_ID': + [ + {'shape': [1, 227, 227, 3], 'port': None, 'data_type': np.int32} + ], + 'node_2_ID': + [ + {'shape': None, 'out': 3} + ] + } + Example of freeze placeholder dictionary: + _freeze_placeholder = + { + 'phase_train' : False + } + """ + _input_shapes = [] + _input_names = [] + model_inputs = input_model.get_inputs() + + if isinstance(input_user_shapes, list) and len(input_user_shapes) > 1 and isinstance(input_user_shapes[0], + PartialShape): + for shape in input_user_shapes: + assert isinstance(shape, PartialShape), "Got incorrect format of input shapes." + assert len(model_inputs) == len(input_user_shapes) + for idx, model_input in enumerate(model_inputs): + _input_shapes.append({"node": model_input, "shape": input_user_shapes[idx]}) + elif isinstance(input_user_shapes, list) or isinstance(input_user_shapes, dict): + for input_name in input_user_shapes: + node = decode_name_with_port( + input_model, input_name, framework, IOType.Input + ) + if node is None: + raise Error( + "Cannot find location {} in the input model".format(input_name) + ) + shape = ( + None + if isinstance(input_user_shapes, list) + else input_user_shapes[input_name] + ) + if isinstance(input_user_data_types, dict) and input_user_data_types.get(input_name) is not None: + data_type = input_user_data_types[input_name] + _input_shapes.append( + { + "node": node, + "shape": shape, + "data_type": data_type, + "input_name": input_name, + } + ) + else: + _input_shapes.append( + { + "node": node, + "shape": shape, + "input_name": input_name + } + ) + _input_names.append(input_name) + elif isinstance(input_user_shapes, PartialShape): + # this branch covers the single use of `input_shape` without `input` option + # but it can be used along with `freeze_placeholder_with_value` option + # for example, input_shape [3] freeze_placeholder_with_value "is_training->False" + # means the model has two inputs: one is is_training to be frozen, the other to re-write the shape + # NOTE: the logic relies on parameters with the single name + frozen_names = freeze_placeholder.keys() + assert len(model_inputs) == len(frozen_names) + 1, \ + "Please check the conversion command-line. Total number of model inputs ({} detected) " \ + "must match to a number of input shapes along with frozen inputs ({} in total).".format( + len(model_inputs), + len(frozen_names) + 1) + for node in model_inputs: + assert len(node.get_names()) > 0, "Original model inputs must have tensor names." + input_name = node.get_names()[0] + if input_name not in frozen_names: + _input_shapes.append( + { + "node": node, + "shape": input_user_shapes, + "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: + # this case means that we use original inputs of the model + # and they should not be changed and their properties (shape and type) should not be over-written + # NOTE: the logic relies on parameters with the single name + assert input_user_shapes is None + for node in model_inputs: + assert len(node.get_names()) > 0, "Original model inputs must have tensor names." + input_name = node.get_names()[0] + _input_shapes.append( + { + "node": node, + "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) + + if freeze_placeholder: + # in case freezing via freeze_placeholder_with_value option, _input_shapes can miss some frozen places + for input_name in freeze_placeholder: + if input_name in _input_names: + continue + node = decode_name_with_port( + input_model, input_name, framework, IOType.Input + ) + _input_shapes.append( + { + "node": node, + "input_name": input_name + } + ) + return _input_shapes, freeze_placeholder + return _input_shapes, dict() + + +def fe_output_user_data_repack(input_model: InputModel, outputs: list, framework: str): + """ + + :param input_model: Input Model to operate on + :param outputs: list of node names provided by user + :return: dictionary with node IDs as keys and list of port dictionaries as values + Example of outputs dictionary: + _outputs = + { + 'node_ID': + [ + {'out': 0}, + {'out': 1}, + ], + 'node_1_ID': + [ + {'port': None} + ], + 'node_2_ID': + [ + {'in': 3} + ] + } + """ + _outputs = [] + if outputs is not None: + for output in outputs: + node = decode_name_with_port(input_model, output, framework, IOType.Output) + if node is None: + raise Error("Cannot find location {} in the graph".format(output)) + _outputs.append({"node": node}) + 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, np.dtype, 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], + input_user_data_types: dict, + outputs: list, + freeze_placeholder: dict, + framework: str, +): + """ + :param input_model: Input Model to operate on + :param input_user_shapes: data structure representing user input cutting request + :param input_user_data_types: dictionary with input nodes and its data types + :param outputs: list of node names to treat as outputs + :param freeze_placeholder: dictionary with placeholder names as keys and freezing value as values + :return: restructured input, output and freeze placeholder dictionaries or None values + """ + _input_shapes, _freeze_placeholder = fe_input_user_data_repack( + input_model, + input_user_shapes, + freeze_placeholder, + framework, + input_user_data_types=input_user_data_types, + ) + _outputs = fe_output_user_data_repack(input_model, outputs, framework) + + return _input_shapes, _outputs, _freeze_placeholder diff --git a/tools/mo/openvino/tools/mo/moc_frontend/layout_utils.py b/tools/mo/openvino/tools/mo/moc_frontend/layout_utils.py new file mode 100644 index 00000000000..f2018282a51 --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/layout_utils.py @@ -0,0 +1,73 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Callable + +from openvino.runtime import PartialShape # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.utils.utils import refer_to_faq_msg + + +def update_layout_to_dict(inputs: list, layout: [list, dict], get_names_func: Callable): + """ + The function prepares layout values in the dictionary with items of the format: + { node_name : {'source_layout': 'NHWC', 'target_layout': 'NCHW'} } + """ + if isinstance(layout, dict): + if '' in layout: + input_names = [list(get_names_func(cur_input))[0] for cur_input in inputs] + if len(input_names) > 1: + raise Error('Layout without name can be specified for models with only one input, ' + 'but provided model has {} inputs: \'{}\'. ' + 'Please specify explicitly input/output name for "layout" option' + .format(len(input_names), input_names)) + layout = { + input_names[0]: { + 'source_layout': layout[''].get('source_layout'), + 'target_layout': layout[''].get('target_layout') + } + } + return layout + if isinstance(layout, list): + if len(layout) != len(inputs): + raise Error('Numbers of inputs and layout values do not match. ' + refer_to_faq_msg(61)) + layout_dict = {} + for idx, cur_input in enumerate(inputs): + names_list = list(get_names_func(cur_input)) + assert len(names_list) > 0, "No names for input" + node_name = names_list[0] + layout_dict.update( + { + node_name: layout[idx] + } + ) + return layout_dict + raise Error("Unknown layout type. Expected dict, list. Got {}".format(type(layout))) + + +def get_dimension_index_by_label(input_shape: PartialShape, input_names: list, layout_dict: [dict], + dimension_label: str, default_dim: int): + """ + The function returns index of the dimension pointed in the layout + and a flag indicating if the index is chosen by default. + For example, the index for 'D' dimension in "NHWDC" layout is 3. + """ + if input_shape.rank.is_static and input_shape.rank.get_length() == 0: + # in case a scalar, batch dimension is not defined + return None, False + + # search for the corresponding layout + for name, layout_value in layout_dict.items(): + if name in input_names: + layout = layout_value.get('source_layout', None) + if layout is None: + return default_dim, True + from openvino.runtime import Layout # pylint: disable=no-name-in-module,import-error + layout_parsed = Layout(layout) + if layout_parsed.has_name(dimension_label): + return layout_parsed.get_index_by_name(dimension_label), False + else: + # if the layout is specified and the required dimension label is not found, the batch is unknown + return None, False + + return default_dim, True diff --git a/tools/mo/openvino/tools/mo/moc_frontend/moc_emit_ir.py b/tools/mo/openvino/tools/mo/moc_frontend/moc_emit_ir.py new file mode 100644 index 00000000000..9df5ff30cfc --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/moc_emit_ir.py @@ -0,0 +1,39 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse + +from openvino.runtime import Model # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.cli_parser import parse_transform +from openvino.tools.mo.back.preprocessing import apply_preprocessing + + +def moc_emit_ir(ngraph_function: Model, argv: argparse.Namespace): + + # Apply preprocessing (mean/scale/reverse_channels/convert_layout/etc) + apply_preprocessing(ov_function=ngraph_function, argv=argv) + + # Apply transformations + from openvino.tools.mo.back.offline_transformations import apply_user_transformations, apply_moc_transformations, \ + apply_moc_legacy_transformations, apply_fused_names_cleanup + + apply_moc_transformations(ngraph_function) + from openvino._offline_transformations import compress_quantize_weights_transformation # pylint: disable=no-name-in-module,import-error + compress_quantize_weights_transformation(ngraph_function) + + if argv.framework == "onnx": + # set OldApi map in IR to be executed via OV API 1.x and for parity with legacy MO + params_with_custom_types = [] if argv.placeholder_data_types is None \ + else list(argv.placeholder_data_types.keys()) + apply_moc_legacy_transformations(ngraph_function, params_with_custom_types) + + apply_user_transformations(ngraph_function, parse_transform(argv.transform)) + + if argv.compress_to_fp16: + from openvino.tools.mo.back.offline_transformations import compress_model + compress_model(ngraph_function) + + apply_fused_names_cleanup(ngraph_function) + + del argv.feManager + return ngraph_function diff --git a/tools/mo/openvino/tools/mo/moc_frontend/paddle_frontend_utils.py b/tools/mo/openvino/tools/mo/moc_frontend/paddle_frontend_utils.py new file mode 100644 index 00000000000..1e165d3289a --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/paddle_frontend_utils.py @@ -0,0 +1,83 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import tempfile + + +class paddle_frontend_converter: + def __init__(self, model, inputs=None, outputs=None): + self.model = model + self.inputs = inputs + self.outputs = outputs + self.tmp = None + self.model_name = None + self.pdmodel = None + self.pdiparams = None + self.pdiparams_info = None + self.is_generated = False + + def destroy(self): + # close tmp file + if isinstance(self.tmp, tempfile._TemporaryFileWrapper): + self.tmp.close() + + # remove the *.pdmodel + if os.path.exists(self.pdmodel): + os.remove(self.pdmodel) + + # remove the *.pdiparams + if os.path.exists(self.pdiparams): + os.remove(self.pdiparams) + + # remove the *.pdiparams.info + if os.path.exists(self.pdiparams_info): + os.remove(self.pdiparams_info) + + def convert_paddle_to_pdmodel(self): + ''' + There are three paddle model categories: + - High Level API: is a wrapper for dynamic or static model, use `self.save` to serialize + - Dynamic Model: use `paddle.jit.save` to serialize + - Static Model: use `paddle.static.save_inference_model` to serialize + ''' + try: + self.tmp = tempfile.NamedTemporaryFile(delete=True) + self.model_name = self.tmp.name + self.pdmodel = "{}.pdmodel".format(self.model_name) + self.pdiparams = "{}.pdiparams".format(self.model_name) + self.pdiparams_info = "{}.pdiparams.info".format(self.model_name) + + import paddle # pylint: disable=import-error + if isinstance(self.model, paddle.hapi.model.Model): + self.model.save(self.model_name, False) + else: + if self.inputs is None: + raise RuntimeError( + "Saving inference model needs 'inputs' before saving. Please specify 'example_input'" + ) + if isinstance(self.model, paddle.fluid.dygraph.layers.Layer): + with paddle.fluid.framework._dygraph_guard(None): + paddle.jit.save(self.model, self.model_name, input_spec=self.inputs, output_spec=self.outputs) + elif isinstance(self.model, paddle.fluid.executor.Executor): + if self.outputs is None: + raise RuntimeError( + "Model is static. Saving inference model needs 'outputs' before saving. Please specify 'example_output' for this model" + ) + paddle.static.save_inference_model(self.model_name, self.inputs, self.outputs, self.model) + else: + raise RuntimeError( + "Conversion just support paddle.hapi.model.Model, paddle.fluid.dygraph.layers.Layer and paddle.fluid.executor.Executor" + ) + + if not os.path.exists(self.pdmodel): + print("Failed generating paddle inference format model") + sys.exit(1) + + self.is_generated = True + return self.pdmodel + finally: + # close tmp file + if isinstance(self.tmp, tempfile._TemporaryFileWrapper): + self.tmp.close() \ No newline at end of file diff --git a/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py b/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py new file mode 100644 index 00000000000..51ff418612b --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py @@ -0,0 +1,319 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import logging as log +import sys +from copy import copy +from typing import List + +import numpy as np +import os + +from openvino.frontend import FrontEnd, InputModel, NotImplementedFailure, \ + Place # pylint: disable=no-name-in-module,import-error +from openvino.runtime import PartialShape, Type # pylint: disable=no-name-in-module,import-error +from openvino.runtime.utils.types import get_element_type, \ + get_numpy_ctype # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.moc_frontend.analysis import json_model_analysis_dump +from openvino.tools.mo.moc_frontend.extractor import fe_user_data_repack, convert_params_lists_to_dicts, fe_output_user_data_repack +from openvino.tools.mo.moc_frontend.layout_utils import update_layout_to_dict, get_dimension_index_by_label +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.utils.type_utils import np_map_cast +from openvino.tools.mo.front.common.partial_infer.utils import mo_array +from openvino.tools.mo.middle.passes.infer import validate_batch_in_shape + + +def get_enabled_and_disabled_transforms(): + """ + :return: tuple of lists with force enabled and disabled id of transformations. + """ + disabled_transforms = os.environ['MO_DISABLED_TRANSFORMS'] if 'MO_DISABLED_TRANSFORMS' in os.environ else '' + enabled_transforms = os.environ['MO_ENABLED_TRANSFORMS'] if 'MO_ENABLED_TRANSFORMS' in os.environ else '' + + assert isinstance(enabled_transforms, str) + assert isinstance(disabled_transforms, str) + + disabled_transforms = disabled_transforms.split(',') + enabled_transforms = enabled_transforms.split(',') + + return enabled_transforms, disabled_transforms + + +def moc_pipeline(argv: argparse.Namespace, moc_front_end: FrontEnd): + """ + Load input model and convert it to nGraph function + :param: argv: parsed command line arguments + :param: moc_front_end: Loaded Frontend for converting input model + :return: converted nGraph function ready for serialization + """ + input_checkpoint = getattr(argv, 'input_checkpoint', None) + if argv.input_model and input_checkpoint: + # frozen format with v1 checkpoints + input_model = moc_front_end.load([argv.input_model, argv.input_checkpoint]) + elif argv.input_model: + input_model = moc_front_end.load(argv.input_model) + elif argv.saved_model_dir: + if argv.saved_model_tags: + input_model = moc_front_end.load([argv.saved_model_dir, argv.saved_model_tags]) + else: + input_model = moc_front_end.load(argv.saved_model_dir) + elif argv.input_meta_graph: + input_model = moc_front_end.load(argv.input_meta_graph) + if argv.output: + # Simulate original behavior with freezing model + # While freezing we do a cutting of model, to keep similar behavior we + # need to simulate similar behavior with natively supported model + outputs = fe_output_user_data_repack(input_model, argv.output, moc_front_end.get_name()) + input_model.override_all_outputs([x['node'] for x in outputs]) + + 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()) + + def check_places_are_same(places_original: List[Place], places_new: List[Place]): + """ + Check if set of new places is same as original or not. + :param places_original: List[Place] Original model places + :param places_new: List[Place] New list of places + :return: True if new list of places is same as original + """ + return len(places_original) == len(places_new) and len( + [item for item in places_original if any( + [item.is_equal(item2['node']) for item2 in places_new])]) == len(places_original) + + def add_names_to_tensors(model: InputModel, places: List[Place]): + """ + Adds additional names to some model input tensors. This helper should be used + when a model modification is going to happen. + :param model The input model loaded by a given frontend + :param places An object containing Places and names that will be used for model modification + """ + for new_input in places: + if 'input_name' not in new_input: + continue + try: + model.add_name_for_tensor(new_input['node'], new_input['input_name']) + except NotImplementedFailure as e: + # some frontends might not implement this method + log.warn('Could not add an additional name to a tensor pointed to by \'{}\'. Details: {}'.format( + new_input['input_name'], str(e))) + + enabled_transforms, disabled_transforms = get_enabled_and_disabled_transforms() + if 'ANALYSIS_JSON_PRINT' in enabled_transforms: + # NOTE that model analysis is performed before applying user's settings (inputs's shapes etc.) + framework_model = moc_front_end.decode(input_model) + json_model_analysis_dump(framework_model) + # a model is not processed further in json analysis mode + sys.exit(0) + + model_inputs = input_model.get_inputs() + inputs_equal = True + if user_shapes: + inputs_equal = check_places_are_same(model_inputs, user_shapes) + + outputs_equal = True + if outputs: + outputs_equal = check_places_are_same(input_model.get_outputs(), outputs) + log.debug('Inputs are same: {}, outputs are same: {}'.format( + inputs_equal, outputs_equal)) + + def create_target_input_shapes(new_input_places): + if isinstance(new_input_places, list) and len(new_input_places) > 1 \ + and isinstance(new_input_places[0], tuple): + return new_input_places + new_input_place_names = [x.get_names()[0] for x in new_input_places] + shapes = [shape for shape in argv.placeholder_shapes.values()] + return dict(zip(new_input_place_names, shapes)) + + if not inputs_equal and not outputs_equal: + log.debug('Using extract subgraph') + new_input_places = [x['node'] for x in user_shapes] + new_output_places = [x['node'] for x in outputs] + add_names_to_tensors(input_model, user_shapes) + input_model.extract_subgraph(new_input_places, new_output_places) + # invalidation of existing Place objects could have happened in the operation above + if user_shapes: + placeholder_shapes = create_target_input_shapes(new_input_places) + new_output_places_name = [x.get_names()[0] for x in new_output_places] + + user_shapes, outputs, _ = fe_user_data_repack( + input_model, placeholder_shapes, argv.placeholder_data_types, + new_output_places_name, argv.freeze_placeholder_with_value, moc_front_end.get_name()) + elif not inputs_equal: + log.debug('Using override_all_inputs') + add_names_to_tensors(input_model, user_shapes) + new_input_places = [x['node'] for x in user_shapes] + input_model.override_all_inputs(new_input_places) + # invalidation of existing Place objects could have happened in the operation above + if user_shapes: + placeholder_shapes = create_target_input_shapes(new_input_places) + + user_shapes, outputs, _ = fe_user_data_repack( + input_model, placeholder_shapes, argv.placeholder_data_types, + argv.output, argv.freeze_placeholder_with_value, moc_front_end.get_name()) + elif not outputs_equal: + log.debug('Using override_all_outputs') + add_names_to_tensors(input_model, user_shapes) + new_output_places = [x['node'] for x in outputs] + input_model.override_all_outputs(new_output_places) + # invalidation of existing Place objects could have happened in the operation above + if user_shapes: + model_inputs = input_model.get_inputs() + + if user_shapes: + for user_shape in user_shapes: + if user_shape.get('shape') is not None: + input_model.set_partial_shape( + user_shape['node'], user_shape['shape']) + if user_shape.get('data_type') is not None: + data_type = get_element_type(user_shape['data_type']) + log.debug('Set data type: {}'.format(data_type)) + input_model.set_element_type(user_shape['node'], data_type) + + if freeze_placeholder: + for name, value in freeze_placeholder.items(): + node = None + # look for the certain place in user_shapes + for node_cur in user_shapes: + if node_cur.get('input_name') == name: + node = node_cur + break + if node is None: + raise Error("Please check correctness of the command-line. " + "Place (operation or tensor) with name {} is not found.".format(name)) + place = node.get('node') + + if node.get('data_type'): + dtype = node['data_type'] + ov_type = Type(dtype) + else: + # we need to detect type of Placeholder + try: + ov_type = input_model.get_element_type(place) + except NotImplementedFailure: + raise Error("Please specify type for value freezing {} node explicitly " + "because the frontend does not support automatic type detection.".format(name)) + # in case of cutting graph (or using custom inputs) and unspecified or dynamic type, + # the default type is fp32 + if ov_type == Type.undefined or ov_type == Type.dynamic: + ov_type = Type.f32 + dtype = get_numpy_ctype(ov_type) + + input_model.set_element_type(place, ov_type) + # prepare and cast value to dtype + if isinstance(value, list): + casted_list = list() + for v in mo_array(value): + casted_list.append(np_map_cast[dtype](v)) + value = mo_array(casted_list, dtype=dtype) + else: + value = np_map_cast[dtype](value) + value = np.array(value, dtype=dtype) + + ov_shape = input_model.get_partial_shape(place) + if node.get('shape'): + # set user defined shape + ov_shape = PartialShape(node['shape']) + input_model.set_partial_shape(place, ov_shape) + elif ov_shape.is_dynamic: + # in case of dynamic shape (dynamic rank or dynamic dimension) + # deduce it based on the value shape and set it + ov_shape = PartialShape(value.shape) + input_model.set_partial_shape(place, ov_shape) + + input_model.set_tensor_value(place, value) + + def shape_to_array(shape: PartialShape): + return [shape.get_dimension(i) for i in range(shape.rank.get_length())] + + # obtain layout for all inputs + layout_values = {} + if 'layout_values' in argv and argv.layout_values: + layout_values = update_layout_to_dict(model_inputs, argv.layout_values, + lambda input_place: input_place.get_names()) + + deferred_batch_names = [] + # set batch size for inputs with a static rank + # for all other inputs, set it after shape deduction is performed during model conversion + if argv.batch is not None and argv.batch > 0: + log.debug('Setting batch size to {}'.format(argv.batch)) + frozen_input_names = list(freeze_placeholder.keys()) if freeze_placeholder else [] + for place in model_inputs: + input_partial_shape = input_model.get_partial_shape(place) + input_names = place.get_names() + joined_name = ' '.join(place.get_names()) + assert len(input_names) > 0, "One input place has no names" + + # if this input is frozen, there is no need to set the batch + is_frozen_input = len([name for name in input_names if name in frozen_input_names]) > 0 + if is_frozen_input: + # skip the frozen input + continue + + if not input_partial_shape.rank.is_static: + # found input with dynamic rank, so have to repeat the batch setting after the model conversion + deferred_batch_names += input_names + continue + + batch_dim, is_default_index = get_dimension_index_by_label(input_partial_shape, + place.get_names(), layout_values, 'N', 0) + if batch_dim is None: + # skip because no batch dimension exists in the input + continue + + if is_default_index: + # if the batch index is chosen by default, we need to ensure that its size equals -1, 0 or 1 + validate_batch_in_shape(shape_to_array(input_partial_shape), joined_name) + + assert batch_dim < input_partial_shape.rank.get_length(), \ + "Incorrect layout is specified for {}:" \ + " index of the batch dimension is out of range.".format(input_names[0]) + + new_partial_shape = copy(input_partial_shape) + new_partial_shape[batch_dim] = argv.batch + + log.debug('Input: {}, Old shape: {}, New shape: {}'.format( + joined_name, input_partial_shape, new_partial_shape)) + input_model.set_partial_shape(place, new_partial_shape) + + ov_model = moc_front_end.convert(input_model) + + if argv.batch is not None and argv.batch > 0 and len(deferred_batch_names) > 0: + # Frontend convert method can include reverse infer functionality that can deduce undefined input shapes + # so try to repeat batch setting again + reshape_dict = {} + log.debug('Deferred batch setting to size {}'.format(argv.batch)) + is_batch_clarified = False + for model_input in ov_model.inputs: + input_name = model_input.any_name + input_partial_shape = model_input.get_partial_shape() + if input_name in deferred_batch_names and input_partial_shape.rank.is_static: + # update input shape with the specified batch for input that originally has dynamic rank + batch_dim, is_default_index = get_dimension_index_by_label(input_partial_shape, + model_input.get_names(), + layout_values, 'N', 0) + if batch_dim is None: + continue + + if is_default_index: + # if the batch index is chosen by default, we need to ensure that its size equals -1, 0 or 1 + validate_batch_in_shape(shape_to_array(input_partial_shape), input_name) + + assert batch_dim < input_partial_shape.rank.get_length(), \ + "Incorrect layout is specified for {}: " \ + "index of the batch dimension is out of range.".format(input_name) + input_partial_shape[batch_dim] = argv.batch + is_batch_clarified = True + + reshape_dict.update({input_name: input_partial_shape}) + + if is_batch_clarified: + # call reshape only if batch dimension for one of the input is clarified + ov_model.reshape(reshape_dict) + + return ov_model diff --git a/tools/mo/openvino/tools/mo/moc_frontend/pytorch_frontend_utils.py b/tools/mo/openvino/tools/mo/moc_frontend/pytorch_frontend_utils.py new file mode 100644 index 00000000000..e6ae7d4c6eb --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/pytorch_frontend_utils.py @@ -0,0 +1,211 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import logging as log + +import numpy as np +# pylint: disable=no-name-in-module,import-error +from openvino.runtime import Tensor, Type, PartialShape +from openvino.runtime.utils.types import get_element_type_str + +from openvino.tools.mo.utils.cli_parser import input_to_input_cut_info, input_shape_to_input_cut_info +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.moc_frontend.shape_utils import get_static_shape + + +def get_pytorch_decoder(model, input_shape, example_inputs, args): + try: + from openvino.frontend.pytorch.decoder import TorchScriptPythonDecoder + except Exception as e: + log.error("PyTorch frontend loading failed") + raise e + inputs = prepare_torch_inputs(example_inputs, input_shape, args.get("input"), allow_none=True) + decoder = TorchScriptPythonDecoder(model, example_input=inputs) + args['input_model'] = decoder + args["framework"] = "pytorch" + args["example_input"] = inputs + + return args + + +def update_list_or_dict(container, name, idx, value): + if isinstance(container, dict): + if name is None: + name = list(container)[idx] + container[name] = value + return + if idx == len(container): + container.append(value) + elif idx > len(container): + raise Error(f"Wrong {idx}") + else: + container[idx] = value + return + + +def get_value_from_list_or_dict(container, name, idx): + if isinstance(container, dict): + if name is None: + if idx < len(container): + name = list(container)[idx] + return None + return container.get(name) + if idx < len(container): + return container[idx] + return None + + +def extract_input_info_from_example(args, inputs): + try: + from openvino.frontend.pytorch.decoder import pt_to_ov_type_map # pylint: disable=no-name-in-module,import-error + except Exception as e: + log.error("PyTorch frontend loading failed") + raise e + example_inputs = args.example_input + data_types = args.placeholder_data_types or {} + input_shapes = args.placeholder_shapes or {} + is_dict_input = isinstance(example_inputs, dict) + list_inputs = list(example_inputs.values()) if is_dict_input else example_inputs + input_names = None if not is_dict_input else list(example_inputs) + if not isinstance(list_inputs, (list, tuple)): + list_inputs = [list_inputs] + if not data_types and input_names is None: + data_types = [] + if not input_shapes and input_names is None: + input_shapes = [] + if inputs: + for input_id, input_info in enumerate(inputs): + input_name = input_info.name + if is_dict_input and input_name in example_inputs: + example_input = example_inputs[input_name] + else: + example_input = list_inputs[input_id] + if is_dict_input and input_name is None: + input_name = input_names[input_id] + dtype = getattr(example_input, "dtype", type(example_input)) + example_dtype = pt_to_ov_type_map.get(str(dtype)) + user_dtype = get_value_from_list_or_dict(data_types, input_name, input_id) + if user_dtype is not None and example_dtype.to_dtype() != user_dtype: + raise Error(f"Defined input type {user_dtype} is not equal to provided example_input type {example_dtype.to_dtype()}") + + data_rank = getattr(example_input, "ndim", 0) + user_input_shape = get_value_from_list_or_dict(input_shapes, input_name, input_id) + if user_input_shape.rank.get_length() != data_rank: + raise Error( + f"Requested input shape {user_input_shape.rank.get_length()} rank" + f" is not equal to provided example_input rank {data_rank}") + + input_shape = user_input_shape if user_input_shape is not None else PartialShape([-1] * data_rank) + update_list_or_dict(data_types, input_name, input_id, example_dtype.to_dtype()) + update_list_or_dict(input_shapes, input_name, input_id, input_shape) + else: + for input_id, example_input in enumerate(list_inputs): + dtype = getattr(example_input, "dtype", type(example_input)) + ov_dtype = pt_to_ov_type_map.get(str(dtype)) + data_rank = getattr(example_input, "ndim", 0) + input_shape = PartialShape([-1] * data_rank) + input_name = input_names[input_id] if input_names else None + update_list_or_dict(input_shapes, input_name, input_id, input_shape) + update_list_or_dict(data_types, input_name, input_id, ov_dtype.to_dtype()) + + args.placeholder_data_types = data_types + args.placeholder_shapes = input_shapes + if not args.input and input_names: + args.input_list = input_names + args.input = ",".join(input_names) + +# pylint: disable=no-member +def to_torch_tensor(tensor): + import torch # pylint: disable=import-error + if isinstance(tensor, torch.Tensor): + return tensor + if isinstance(tensor, np.ndarray): + return torch.tensor(tensor) + if isinstance(tensor, Tensor): + return torch.tensor(tensor.data) + if isinstance(tensor, (float, int, bool)): + return tensor + if isinstance(tensor, tuple): + # TODO: Function to_torch_tensor should be renamed as it handles not only a tensor + return tuple(to_torch_tensor(x) for x in tensor) + else: + raise Error("Unexpected type of example_input. Supported types torch.Tensor, np.array or ov.Tensor. " + "Got {}".format(type(tensor))) + + +def get_torch_dtype(dtype): + import torch + ov_str_to_torch = { + "boolean": torch.bool, + "f16": torch.float16, + "f32": torch.float32, + "f64": torch.float64, + "i8": torch.int8, + "i16": torch.int16, + "i32": torch.int32, + "i64": torch.int64, + "u8": torch.uint8, + } + if dtype is None: + return torch.float + if isinstance(dtype, torch.dtype): + return dtype + if isinstance(dtype, (type, np.dtype)): + dtype = get_element_type_str(dtype) + if isinstance(dtype, Type): + dtype = dtype.get_type_name() + if isinstance(dtype, str): + str_dtype = ov_str_to_torch.get(dtype) + if str_dtype is None: + raise Error(f"Unexpected data type '{dtype}' for input") + return str_dtype + raise Error(f"Unexpected data type for input. Supported torch.dtype, numpy.dtype, ov.Type and str. Got {type(dtype)}") + + +def prepare_torch_inputs(example_inputs, input_shape, input_info=None, allow_none=False): + import torch + inputs = None + if example_inputs is not None: + inputs = example_inputs + if isinstance(inputs, list): + inputs = [to_torch_tensor(x) for x in inputs] + if len(inputs) == 1: + inputs = torch.unsqueeze(inputs[0], 0) + else: + inputs = inputs + elif isinstance(inputs, tuple): + inputs = [to_torch_tensor(x) for x in inputs] + inputs = tuple(inputs) + elif isinstance(inputs, dict): + for name, tensor in inputs.items(): + assert isinstance(name, str), "Expected dictionary where keys are input names of string type and" \ + " values are tensors. Got key of type {}".format(type(name)) + inputs[name] = to_torch_tensor(tensor) + else: + inputs = to_torch_tensor(inputs) + elif input_info is not None or input_shape is not None: + input_info = input_to_input_cut_info(input_info) or [] + input_shape_to_input_cut_info(input_shape, input_info) + inputs = [] + inputs_with_names = {} + for inp in input_info: + shape = inp.shape + if shape is None: + if not allow_none: + raise Error("Please provide input_shape or example_input for all inputs converting PyTorch model.") + inputs = None + break + dtype = get_torch_dtype(inp.type) + static_shape = get_static_shape(shape, dynamic_value=1) + input_tensor = torch.zeros(static_shape, dtype=dtype) # pylint: disable=no-member + if inp.name is not None: + inputs_with_names[inp.name] = input_tensor + inputs.append(input_tensor) + if isinstance(inputs, list): + inputs = tuple(inputs) + if inputs is not None and len(inputs) == len(inputs_with_names): + inputs = inputs_with_names + else: + if not allow_none: + raise Error("Please provide input_shape or example_input for converting PyTorch model.") + return inputs diff --git a/tools/mo/openvino/tools/mo/moc_frontend/shape_utils.py b/tools/mo/openvino/tools/mo/moc_frontend/shape_utils.py new file mode 100644 index 00000000000..2b2d336a1da --- /dev/null +++ b/tools/mo/openvino/tools/mo/moc_frontend/shape_utils.py @@ -0,0 +1,102 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +from openvino.runtime import PartialShape, Dimension +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.utils.cli_parser import get_placeholder_shapes, split_shapes + + +def get_static_shape(shape: [PartialShape, list, tuple], dynamic_value=None): + # Current function returns list with static dimensions with following logic. + # For dynamic dimensions return lower boundaries if they are set, otherwise + # return upper boundaries if they are set. If dimension is fully dynamic then raise error. + shape_list = [] + for idx, dim in enumerate(shape): + if isinstance(dim, int): + if dim == -1: + shape_list.append(dynamic_value) + continue + shape_list.append(dim) + elif isinstance(dim, np.int64): + if dim == np.int64(-1): + shape_list.append(dynamic_value) + continue + shape_list.append(dim) + elif isinstance(dim, tuple): + # tuple where (min_length, max_length), the format which uses MO cli parser + assert len(dim) == 2, "Unknown dimension type {}".format(dim) + if dim[0] > 0: + shape_list.append(dim[0]) + elif dim[1] < np.iinfo(np.int64).max: + shape_list.append(dim[1]) + else: + shape_list.append(dynamic_value) + continue + elif isinstance(dim, Dimension): + if dim.is_static or dim.get_min_length() > 0: + shape_list.append(dim.get_min_length()) + elif dim.get_max_length() != -1: + shape_list.append(dim.get_max_length()) + else: + shape_list.append(dynamic_value) + continue + else: + raise Error("Unknown dimension type {}".format(dim)) + + return tuple(shape_list) + + +def get_dynamic_dims(shape: [PartialShape, list, tuple]): + dynamic_dims = [] + for idx, dim in enumerate(shape): + if isinstance(dim, int): + if dim == -1: + dynamic_dims.append(idx) + if isinstance(dim, np.int64): + if dim == np.int64(-1): + dynamic_dims.append(idx) + elif isinstance(dim, tuple): + dynamic_dims.append(idx) + elif isinstance(dim, Dimension): + if dim.get_min_length() == 0 and dim.get_max_length() == -1: + dynamic_dims.append(idx) + + return dynamic_dims + + +def parse_input_shapes(argv): + input_shapes = None + if 'input_shape' in argv and argv['input_shape'] is not None: + shapes = argv['input_shape'] + if isinstance(shapes, str): + shapes = ["[{}]".format(x) for x in split_shapes(shapes)] + if isinstance(shapes, list) or isinstance(shapes, tuple): + input_shapes = [] + is_single_shape = False + for shape in shapes: + if isinstance(shape, str): + _, shape_tuple, _ = get_placeholder_shapes(argv_input=None, argv_input_shape=shape) + input_shapes.append(shape_tuple) + if is_single_shape: + raise Error("Incorrect format of shape.") + elif isinstance(shape, int) or isinstance(shape, np.int64) or isinstance(shape, Dimension): + is_single_shape = True + input_shapes.append(shape) + else: + input_shapes.append(shape) + if is_single_shape: + return [input_shapes] + else: + return input_shapes + elif isinstance(shapes, PartialShape): + return [shapes] + else: + try: + import torch + if isinstance(shapes, torch.Size): + return [shapes] + except ImportError: + raise Error("Unknown type of input shape {}.".format(type(shapes))) + + return input_shapes \ No newline at end of file diff --git a/tools/mo/openvino/tools/mo/utils/class_registration.py b/tools/mo/openvino/tools/mo/utils/class_registration.py index e95c6ab6e0c..30551e056c8 100644 --- a/tools/mo/openvino/tools/mo/utils/class_registration.py +++ b/tools/mo/openvino/tools/mo/utils/class_registration.py @@ -12,7 +12,7 @@ from openvino.tools.mo.graph.graph import Graph from openvino.tools.mo.middle.passes.eliminate import shape_inference from openvino.tools.mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively from openvino.tools.mo.utils.error import Error, InternalError, FrameworkError -from openvino.tools.ovc.logger import progress_bar # pylint: disable=no-name-in-module,import-error +from openvino.tools.mo.utils.logger import progress_bar # pylint: disable=no-name-in-module,import-error _registered_classes_dict = {} diff --git a/tools/mo/openvino/tools/mo/utils/cli_parser.py b/tools/mo/openvino/tools/mo/utils/cli_parser.py index a9692746c92..3ed44174531 100644 --- a/tools/mo/openvino/tools/mo/utils/cli_parser.py +++ b/tools/mo/openvino/tools/mo/utils/cli_parser.py @@ -1,4 +1,2042 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino.tools.ovc.cli_parser import get_all_cli_parser, get_layout_values # pylint: disable=no-name-in-module,import-error +import argparse +import ast +import logging as log +import os +import re +from collections import OrderedDict, namedtuple +from distutils.util import strtobool +from itertools import zip_longest +from pathlib import Path +from operator import xor +from typing import List, Union +import numbers +import inspect + +import numpy as np +from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type + +import openvino +from openvino.tools.mo.middle.passes.convert_data_type import destination_type_to_np_data_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 + + +def extension_path_to_str_or_extensions_class(extension): + if isinstance(extension, str): + return extension + elif isinstance(extension, Path): + return str(extension) + else: + # Return unknown object as is. + # The type of the object will be checked by frontend.add_extension() method + return extension + + +def transformations_config_to_str(value): + if value is None: + return value + return extension_path_to_str_or_extensions_class(value) + + +def extensions_to_str_or_extensions_class(extensions): + if extensions is None: + return None + extensions_list = [] + if isinstance(extensions, str): + extensions_list = extensions.split(',') + elif isinstance(extensions, list): + for ext in extensions: + ext = extension_path_to_str_or_extensions_class(ext) + extensions_list.append(ext) + else: + extensions_list = [extension_path_to_str_or_extensions_class(extensions)] + + for ext in extensions_list: + if isinstance(ext, str): + readable_file_or_dir(ext) + return extensions_list + + +def path_to_str(path): + if path is None: + return None + if isinstance(path, str): + return path + elif isinstance(path, Path): + return str(path) + else: + raise Exception("Incorrect type of {} expected str or Path, got {}".format(path, type(path))) + + +def path_to_str_or_object(value): + if value is None or isinstance(value, str): + return value + elif isinstance(value, Path): + return str(value) + else: + return value + + +def paths_to_str(paths): + if paths is None: + return None + if isinstance(paths, list): + paths_str = [] + for path in paths: + paths_str.append(path_to_str(path)) + return ','.join(paths_str) + else: + path_to_str(paths) + + +def str_list_to_str(values): + if values is None: + return None + if isinstance(values, str): + return values + elif isinstance(values, list): + for value in values: + if not isinstance(value, str): + raise Error("Incorrect argument. {} expected to string, got type {}.".format(value, type(value))) + return ','.join(values) + else: + raise Error("Incorrect argument. {} expected to string or list of strings, got type {}.".format(values, type(values))) + + +def is_shape_type(value): + if isinstance(value, PartialShape): + return True + if isinstance(value, Shape): + return True + if isinstance(value, list) or isinstance(value, tuple): + for dim in value: + if not (isinstance(dim, Dimension) or isinstance(dim, int)): + return False + return True + return False + + +def value_to_str(value, separator): + if isinstance(value, np.ndarray): + values = [] + for x in np.nditer(value): + values.append(str(x)) + return "[" + separator.join(values) + "]" + if isinstance(value, list): + values = [] + for x in value: + if not isinstance(x, numbers.Number): + raise Exception("Incorrect value type. Expected numeric value, got {}".format(type(x))) + values.append(str(x)) + return "[" + separator.join(values) + "]" + if isinstance(value, bool): + return "True" if value else "False" + raise Exception("Incorrect value type. Expected np.ndarray or list, got {}".format(type(value))) + + +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): + # Parse params from string + node_name, shape, value, data_type = parse_input_value(input) + return openvino.runtime.InputCutInfo(node_name, + PartialShape(shape) if shape is not None else None, + data_type, + value) + if isinstance(input, openvino.runtime.InputCutInfo): + # Wrap input.shape to PartialShape if possible and wrap to InputCutInfo + return openvino.runtime.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 + for val in input: + if isinstance(val, str): + if name is not None: + raise Exception("More than one input name provided: {}".format(input)) + name = val + elif isinstance(val, (type, Type)): + if inp_type is not None: + raise Exception("More than one input type provided: {}".format(input)) + inp_type = val + elif is_shape_type(val): + if shape is not None: + raise Exception("More than one input shape provided: {}".format(input)) + shape = PartialShape(val) + else: + raise Exception("Incorrect input parameters provided. Expected tuple with input name, " + "input type or input shape. Got unknown object: {}".format(val)) + return openvino.runtime.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.runtime.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.runtime.InputCutInfo " + "or tuple or str. Got {}".format(type(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.runtime.InputCutInfo(node_name, + PartialShape(shape) if shape is not None else None, + data_type, + value)) + return inputs + if isinstance(input, openvino.runtime.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): + # 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.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.runtime.InputCutInfo(inputs[idx].name, shape, inputs[idx].type, inputs[idx].value) + + else: + for shape in input_shape: + inputs.append(openvino.runtime.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): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + if isinstance(value, dict): + values_str = [] + for op_name, val in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + value_to_str(val, ",")) + return ",".join(values_str) + if isinstance(value, list) or isinstance(value, tuple): + list_of_lists = False + for val in value: + if isinstance(val, list) or isinstance(val, tuple): + list_of_lists = True + break + if list_of_lists: + values_str = [] + for val in value: + values_str.append(value_to_str(val, ",")) + return ",".join(values_str) + else: + return value_to_str(value, ",") + return value_to_str(value, ",") + + +def layout_to_str(layout): + if isinstance(layout, str): + return layout + if isinstance(layout, Layout): + return layout.to_string() + raise Exception("Incorrect layout type. Expected Layout or string or dictionary, " + "where key is operation name and value is layout or list of layouts, got {}".format(type(layout))) + + +def source_target_layout_to_str(value): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + if isinstance(value, dict): + values_str = [] + for op_name, layout in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + "(" + layout_to_str(layout) + ")") + return ",".join(values_str) + + return layout_to_str(value) + + +def layoutmap_to_str(value): + if isinstance(value, str): + return value + if isinstance(value, openvino.runtime.LayoutMap): + assert value.source_layout is not None, "Incorrect layout map. 'source_layout' should be set." + source_layout = layout_to_str(value.source_layout) + if value.target_layout is not None: + target_layout = layout_to_str(value.target_layout) + source_layout += "->" + target_layout + return source_layout + return layout_to_str(value) + + +def layout_param_to_str(value): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + + if isinstance(value, dict): + values_str = [] + for op_name, layout in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + "(" + layoutmap_to_str(layout) + ")") + return ",".join(values_str) + if isinstance(value, openvino.runtime.LayoutMap): + return layoutmap_to_str(value) + if isinstance(value, list) or isinstance(value, tuple): + values_str = [] + for layout in value: + values_str.append(layoutmap_to_str(layout)) + return ",".join(values_str) + + return layoutmap_to_str(value) + + +def batch_to_int(value): + if value is None or isinstance(value, int): + return value + if isinstance(value, Dimension): + if not value.is_static: + # TODO: Ticket 88676 + raise Exception("Dynamic batch for \"batch\" parameter is not supported.") + else: + return value.get_length() + raise Exception("Incorrect batch value. Expected int, got {}.".format(type(value))) + + +def transform_param_value_to_str(value): + # This function supports parsing of parameters of MakeStateful, LowLatency2, Pruning. + # If available transforms list is extended this method should be extended for new transforms. + if isinstance(value, str): + return value + if isinstance(value, bool): + return str(value) + if isinstance(value, dict): + # param_res_names dictionary for MakeStateful transform + values_str = [] + for input_name, output_name in value.items(): + assert isinstance(input_name, str), "Incorrect input name. " \ + "Expected string, got {}".format(type(input_name)) + assert isinstance(output_name, str), "Incorrect output name. " \ + "Expected string, got {}".format(type(output_name)) + values_str.append("\'{}\':\'{}\'".format(input_name, output_name)) + return "{" + ','.join(values_str) + "}" + raise Exception("Unknown parameter type.") + + +def transform_to_str(value): + from openvino.tools.mo.back.offline_transformations import get_available_transformations # pylint: disable=no-name-in-module,import-error + + if isinstance(value, str): + return value + + if isinstance(value, tuple): + assert 1 <= len(value) <= 2, "Incorrect definition of transformation in transform argument: " \ + "expected two elements in tuple, provided {}. " \ + "Supported transforms are: {}".format( + len(value), + list(get_available_transformations().keys())) + transform_name = value[0] + assert isinstance(transform_name, str), "Incorrect transform name type. " \ + "Expected string, got {}".format(type(transform_name)) + if len(value) == 2: + params = value[1] + assert isinstance(params, dict), "Incorrect transform params type. " \ + "Expected dictionary, got {}".format(type(params)) + params_str_list = [] + for param_name, val in params.items(): + assert isinstance(param_name, str), "Incorrect transform parameter name type. " \ + "Expected string, got {}".format(type(param_name)) + val_str = transform_param_value_to_str(val) + params_str_list.append(param_name + "=" + val_str) + transform_name += '[' + ','.join(params_str_list) + ']' + return transform_name + raise Exception("Incorrect transform type. Expected tuple with transform name and " + "dictionary with transform parameters. Got object of type {}".format(type(value))) + + +def transform_param_to_str(value): + if value is None or isinstance(value, str): + return value + if isinstance(value, list): + transforms_str = [] + for transform in value: + transforms_str.append(transform_to_str(transform)) + return ','.join(transforms_str) + return transform_to_str(value) + + +ParamDescription = namedtuple("ParamData", + ["description", "cli_tool_description", "to_string"]) + + +def get_mo_convert_params(): + mo_convert_docs = openvino.runtime.convert_model.__doc__ + mo_convert_params = {} + group = "Optional parameters:" + mo_convert_params[group] = {} + + mo_convert_docs = mo_convert_docs[:mo_convert_docs.find('Returns:')] + + while len(mo_convert_docs) > 0: + param_idx1 = mo_convert_docs.find(":param") + if param_idx1 == -1: + break + param_idx2 = mo_convert_docs.find(":", param_idx1+1) + param_name = mo_convert_docs[param_idx1+len(':param '):param_idx2] + + param_description_idx = mo_convert_docs.find(":param", param_idx2+1) + param_description = mo_convert_docs[param_idx2+1: param_description_idx] + + group_name_idx = param_description.rfind('\n\n') + group_name = '' + if group_name_idx != -1: + group_name = param_description[group_name_idx:].strip() + + param_description = param_description[:group_name_idx] + param_description = param_description.strip() + + mo_convert_params[group][param_name] = ParamDescription(param_description, "", None) + + mo_convert_docs = mo_convert_docs[param_description_idx:] + + if group_name != '': + mo_convert_params[group_name] = {} + group = group_name + + # TODO: remove this when internal converting of params to string is removed + params_converted_to_string = get_to_string_methods_for_params() + + params_with_paths = get_params_with_paths_list() + cli_tool_specific_descriptions = get_convert_model_help_specifics() + + for group_name, param_group in mo_convert_params.items(): + for param_name, d in param_group.items(): + to_str_method = None + if param_name in params_converted_to_string: + to_str_method = params_converted_to_string[param_name] + elif param_name in params_with_paths: + to_str_method = path_to_str + + cli_tool_description = None + if param_name in cli_tool_specific_descriptions: + cli_tool_description = cli_tool_specific_descriptions[param_name] + + desc = ParamDescription(d.description, + cli_tool_description, + to_str_method) + mo_convert_params[group_name][param_name] = desc + + return mo_convert_params + + +class DeprecatedStoreTrue(argparse.Action): + def __init__(self, nargs=0, **kw): + super().__init__(nargs=nargs, **kw) + + def __call__(self, parser, namespace, values, option_string=None): + dep_msg = "Use of deprecated cli option {} detected. Option use in the following releases will be fatal. ".format(option_string) + if 'fusing' in option_string: + dep_msg += 'Please use --finegrain_fusing cli option instead' + log.error(dep_msg, extra={'is_warning': True}) + setattr(namespace, self.dest, True) + + +class DeprecatedOptionCommon(argparse.Action): + def __call__(self, parser, args, values, option_string): + dep_msg = "Use of deprecated cli option {} detected. Option use in the following releases will be fatal. ".format(option_string) + log.error(dep_msg, extra={'is_warning': True}) + setattr(args, self.dest, values) + + +class IgnoredAction(argparse.Action): + def __init__(self, nargs=0, **kw): + super().__init__(nargs=nargs, **kw) + + def __call__(self, parser, namespace, values, option_string=None): + dep_msg = "Use of removed cli option '{}' detected. The option is ignored. ".format(option_string) + log.error(dep_msg, extra={'is_warning': True}) + setattr(namespace, self.dest, True) + + +def canonicalize_and_check_paths(values: Union[str, List[str]], param_name, + try_mo_root=False, check_existence=True) -> List[str]: + if values is not None: + list_of_values = list() + if isinstance(values, str): + if values != "": + list_of_values = values.split(',') + elif isinstance(values, list): + list_of_values = values + else: + raise Error('Unsupported type of command line parameter "{}" value'.format(param_name)) + + if not check_existence: + return [get_absolute_path(path) for path in list_of_values] + + for idx, val in enumerate(list_of_values): + list_of_values[idx] = val + + error_msg = 'The value for command line parameter "{}" must be existing file/directory, ' \ + 'but "{}" does not exist.'.format(param_name, val) + if os.path.exists(val): + continue + elif not try_mo_root or val == '': + raise Error(error_msg) + elif try_mo_root: + path_from_mo_root = get_mo_root_dir() + '/mo/' + val + list_of_values[idx] = path_from_mo_root + if not os.path.exists(path_from_mo_root): + raise Error(error_msg) + + return [get_absolute_path(path) for path in list_of_values] + + +class CanonicalizePathAction(argparse.Action): + """ + Expand user home directory paths and convert relative-paths to absolute. + """ + + def __call__(self, parser, namespace, values, option_string=None): + list_of_paths = canonicalize_and_check_paths(values, param_name=option_string, + try_mo_root=False, check_existence=False) + setattr(namespace, self.dest, ','.join(list_of_paths)) + + +class CanonicalizeTransformationPathCheckExistenceAction(argparse.Action): + """ + Convert relative to the current and relative to mo root paths to absolute + and check specified file or directory existence. + """ + + def __call__(self, parser, namespace, values, option_string=None): + list_of_paths = canonicalize_and_check_paths(values, param_name=option_string, + try_mo_root=True, check_existence=True) + setattr(namespace, self.dest, ','.join(list_of_paths)) + + +class CanonicalizePathCheckExistenceAction(argparse.Action): + """ + Expand user home directory paths and convert relative-paths to absolute and check specified file or directory + existence. + """ + + def __call__(self, parser, namespace, values, option_string=None): + list_of_paths = canonicalize_and_check_paths(values, param_name=option_string, + try_mo_root=False, check_existence=True) + setattr(namespace, self.dest, ','.join(list_of_paths)) + + +class CanonicalizeExtensionsPathCheckExistenceAction(argparse.Action): + """ + Expand user home directory paths and convert relative-paths to absolute and check specified file or directory + existence. + """ + + def __call__(self, parser, namespace, values, option_string=None): + list_of_paths = canonicalize_and_check_paths(values, param_name=option_string, + try_mo_root=False, check_existence=True) + # Extensions paths are needed to be stored as list + setattr(namespace, self.dest, list_of_paths) + + +class CanonicalizePathCheckExistenceIfNeededAction(CanonicalizePathCheckExistenceAction): + + def __call__(self, parser, namespace, values, option_string=None): + if values is not None: + if isinstance(values, str): + if values != "": + super().__call__(parser, namespace, values, option_string) + else: + setattr(namespace, self.dest, values) + + +class DeprecatedCanonicalizePathCheckExistenceAction(CanonicalizePathCheckExistenceAction): + def __call__(self, parser, namespace, values, option_string=None): + dep_msg = "Use of deprecated cli option {} detected. Option use in the following releases will be fatal. ".format( + option_string) + log.error(dep_msg, extra={'is_warning': True}) + super().__call__(parser, namespace, values, option_string) + + +def readable_file(path: str): + """ + Check that specified path is a readable file. + :param path: path to check + :return: path if the file is readable + """ + if not os.path.isfile(path): + raise Error('The "{}" is not existing file'.format(path)) + elif not os.access(path, os.R_OK): + raise Error('The "{}" is not readable'.format(path)) + else: + return path + + +def readable_file_or_dir(path: str): + """ + Check that specified path is a readable file or directory. + :param path: path to check + :return: path if the file/directory is readable + """ + if not os.path.isfile(path) and not os.path.isdir(path): + raise Error('The "{}" is not existing file or directory'.format(path)) + elif not os.access(path, os.R_OK): + raise Error('The "{}" is not readable'.format(path)) + else: + return path + + +def readable_dirs(paths: str): + """ + Checks that comma separated list of paths are readable directories. + :param paths: comma separated list of paths. + :return: comma separated list of paths. + """ + paths_list = [readable_dir(path) for path in paths.split(',')] + return ','.join(paths_list) + + +def readable_dirs_or_empty(paths: str): + """ + Checks that comma separated list of paths are readable directories of if it is empty. + :param paths: comma separated list of paths. + :return: comma separated list of paths. + """ + if paths: + return readable_dirs(paths) + return paths + + +def readable_dirs_or_files_or_empty(paths: str): + """ + Checks that comma separated list of paths are readable directories, files or a provided path is empty. + :param paths: comma separated list of paths. + :return: comma separated list of paths. + """ + if paths: + paths_list = [readable_file_or_dir(path) for path in paths.split(',')] + return ','.join(paths_list) + return paths + + +def readable_dir(path: str): + """ + Check that specified path is a readable directory. + :param path: path to check + :return: path if the directory is readable + """ + if not os.path.isdir(path): + raise Error('The "{}" is not existing directory'.format(path)) + elif not os.access(path, os.R_OK): + raise Error('The "{}" is not readable'.format(path)) + else: + return path + + +def writable_dir(path: str): + """ + Checks that specified directory is writable. The directory may not exist but it's parent or grandparent must exist. + :param path: path to check that it is writable. + :return: path if it is writable + """ + if path is None: + raise Error('The directory parameter is None') + if os.path.exists(path): + if os.path.isdir(path): + if os.access(path, os.W_OK): + return path + else: + raise Error('The directory "{}" is not writable'.format(path)) + else: + raise Error('The "{}" is not a directory'.format(path)) + else: + cur_path = path + while os.path.dirname(cur_path) != cur_path: + if os.path.exists(cur_path): + break + cur_path = os.path.dirname(cur_path) + if cur_path == '': + cur_path = os.path.curdir + if os.access(cur_path, os.W_OK): + return path + else: + raise Error('The directory "{}" is not writable'.format(cur_path)) + + +def add_args_by_description(args_group, params_description): + signature = inspect.signature(openvino.runtime.convert_model) + filepath_args = get_params_with_paths_list() + cli_tool_specific_descriptions = get_convert_model_help_specifics() + for param_name, param_description in params_description.items(): + if param_name == 'help': + continue + cli_param_name = "--"+param_name + if cli_param_name not in args_group._option_string_actions: + # Get parameter specifics + param_specifics = cli_tool_specific_descriptions[param_name] if param_name in \ + cli_tool_specific_descriptions else {} + help_text = param_specifics['description'] if 'description' in param_specifics \ + else param_description.description + action = param_specifics['action'] if 'action' in param_specifics else None + param_type = param_specifics['type'] if 'type' in param_specifics else None + param_alias = param_specifics['aliases'] if 'aliases' in param_specifics else {} + param_version = param_specifics['version'] if 'version' in param_specifics else None + param_choices = param_specifics['choices'] if 'choices' in param_specifics else None + + # Bool params common setting + if signature.parameters[param_name].annotation == bool and param_name != 'version': + args_group.add_argument( + cli_param_name, *param_alias, + type=check_bool if param_type is None else param_type, + nargs="?", + const=True, + help=help_text, + default=signature.parameters[param_name].default) + # File paths common setting + elif param_name in filepath_args: + action = action if action is not None else CanonicalizePathCheckExistenceAction + args_group.add_argument( + cli_param_name, *param_alias, + type=str if param_type is None else param_type, + action=action, + help=help_text, + default=signature.parameters[param_name].default) + # Other params + else: + additional_params = {} + if param_version is not None: + additional_params['version'] = param_version + if param_type is not None: + additional_params['type'] = param_type + if param_choices is not None: + additional_params['choices'] = param_choices + args_group.add_argument( + cli_param_name, *param_alias, + help=help_text, + default=signature.parameters[param_name].default, + action=action, + **additional_params + ) + + +def get_common_cli_parser(parser: argparse.ArgumentParser = None): + if not parser: + parser = argparse.ArgumentParser() + common_group = parser.add_argument_group('Framework-agnostic parameters') + mo_convert_params = get_mo_convert_params() + mo_convert_params_common = mo_convert_params['Framework-agnostic parameters:'] + + # Command line tool specific params + common_group.add_argument('--model_name', '-n', + help='Model_name parameter passed to the final create_ir transform. ' + + 'This parameter is used to name ' + + 'a network in a generated IR and output .xml/.bin files.') + common_group.add_argument('--output_dir', '-o', + help='Directory that stores the generated IR. ' + + 'By default, it is the directory from where the Model Conversion is launched.', + default=get_absolute_path('.'), + action=CanonicalizePathAction, + type=writable_dir) + + # Deprecated params + common_group.add_argument('--freeze_placeholder_with_value', + help='Replaces input layer with constant node with ' + 'provided value, for example: "node_name->True". ' + 'It will be DEPRECATED in future releases. ' + 'Use "input" option to specify a value for freezing.', + default=None) + common_group.add_argument('--static_shape', + help='Enables IR generation for fixed input shape (folding `ShapeOf` operations and ' + 'shape-calculating sub-graphs to `Constant`). Changing model input shape using ' + 'the OpenVINO Runtime API in runtime may fail for such an IR.', + action='store_true', default=False) + common_group.add_argument("--use_new_frontend", + help='Force the usage of new Frontend for model conversion into IR. ' + 'The new Frontend is C++ based and is available for ONNX* and PaddlePaddle* models. ' + 'Model Conversion API uses new Frontend for ONNX* and PaddlePaddle* by default that means ' + '`use_new_frontend` and `use_legacy_frontend` options are not specified.', + action='store_true', default=False) + common_group.add_argument("--use_legacy_frontend", + help='Force the usage of legacy Frontend for model conversion into IR. ' + 'The legacy Frontend is Python based and is available for TensorFlow*, ONNX*, MXNet*, ' + 'Caffe*, and Kaldi* models.', + action='store_true', default=False) + add_args_by_description(common_group, mo_convert_params_common) + return parser + + +def get_common_cli_options(model_name): + d = OrderedDict() + d['input_model'] = '- Path to the Input Model' + d['output_dir'] = ['- Path for generated IR', lambda x: x if x != '.' else os.getcwd()] + d['model_name'] = ['- IR output name', lambda x: x if x else model_name] + d['log_level'] = '- Log level' + d['batch'] = ['- Batch', lambda x: x if x else 'Not specified, inherited from the model'] + d['input'] = ['- Input layers', lambda x: x if x else 'Not specified, inherited from the model'] + d['output'] = ['- Output layers', lambda x: x if x else 'Not specified, inherited from the model'] + d['input_shape'] = ['- Input shapes', lambda x: x if x else 'Not specified, inherited from the model'] + d['source_layout'] = ['- Source layout', lambda x: x if x else 'Not specified'] + d['target_layout'] = ['- Target layout', lambda x: x if x else 'Not specified'] + d['layout'] = ['- Layout', lambda x: x if x else 'Not specified'] + d['mean_values'] = ['- Mean values', lambda x: x if x else 'Not specified'] + d['scale_values'] = ['- Scale values', lambda x: x if x else 'Not specified'] + d['scale'] = ['- Scale factor', lambda x: x if x else 'Not specified'] + d['transform'] = ['- User transformations', lambda x: x if x else 'Not specified'] + d['reverse_input_channels'] = '- Reverse input channels' + d['static_shape'] = '- Enable IR generation for fixed input shape' + d['transformations_config'] = '- Use the transformations config file' + return d + + +def get_advanced_cli_options(): + d = OrderedDict() + d['use_legacy_frontend'] = '- Force the usage of legacy Frontend for model conversion into IR' + d['use_new_frontend'] = '- Force the usage of new Frontend for model conversion into IR' + return d + + +def get_caffe_cli_options(): + d = { + 'input_proto': ['- Path to the Input prototxt', lambda x: x], + 'caffe_parser_path': ['- Path to Python Caffe* parser generated from caffe.proto', lambda x: x], + 'k': '- Path to CustomLayersMapping.xml', + } + + return OrderedDict(sorted(d.items(), key=lambda t: t[0])) + + +def get_tf_cli_options(): + d = { + 'input_model_is_text': '- Input model in text protobuf format', + 'tensorflow_custom_operations_config_update': '- Update the configuration file with input/output node names', + 'tensorflow_object_detection_api_pipeline_config': '- Use configuration file used to generate the model with ' + 'Object Detection API', + 'tensorflow_custom_layer_libraries': '- List of shared libraries with TensorFlow custom layers implementation', + 'tensorboard_logdir': '- Path to model dump for TensorBoard' + } + + return OrderedDict(sorted(d.items(), key=lambda t: t[0])) + + +def get_mxnet_cli_options(): + d = { + 'input_symbol': '- Deploy-ready symbol file', + 'nd_prefix_name': '- Prefix name for args.nd and argx.nd files', + 'pretrained_model_name': '- Pretrained model to be merged with the .nd files', + 'save_params_from_nd': '- Enable saving built parameters file from .nd files', + 'legacy_mxnet_model': '- Enable MXNet loader for models trained with MXNet version lower than 1.0.0', + } + + return OrderedDict(sorted(d.items(), key=lambda t: t[0])) + + +def get_kaldi_cli_options(): + d = { + 'counts': '- A file name with full path to the counts file or empty string if you want to use counts from model', + 'remove_output_softmax': '- Removes the SoftMax layer that is the output layer', + 'remove_memory': '- Removes the Memory layer and use additional inputs and outputs instead' + } + + return OrderedDict(sorted(d.items(), key=lambda t: t[0])) + + +def get_onnx_cli_options(): + d = { + } + + return OrderedDict(sorted(d.items(), key=lambda t: t[0])) + + +def get_params_with_paths_list(): + return ['input_model', 'output_dir', 'caffe_parser_path', 'extensions', 'k', 'output_dir', + 'input_checkpoint', 'input_meta_graph', 'input_proto', 'input_symbol', + 'pretrained_model_name', 'saved_model_dir', 'tensorboard_logdir', + 'tensorflow_custom_layer_libraries', 'tensorflow_custom_operations_config_update', + 'tensorflow_object_detection_api_pipeline_config', + 'transformations_config'] + + +def get_caffe_cli_parser(parser: argparse.ArgumentParser = None): + """ + Specifies cli arguments for Model Conversion for Caffe* + + Returns + ------- + ArgumentParser instance + """ + if not parser: + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + get_common_cli_parser(parser=parser) + + caffe_group = parser.add_argument_group('Caffe*-specific parameters') + mo_convert_params_caffe = get_mo_convert_params()['Caffe*-specific parameters:'] + add_args_by_description(caffe_group, mo_convert_params_caffe) + return parser + + +def get_tf_cli_parser(parser: argparse.ArgumentParser = None): + """ + Specifies cli arguments for Model Conversion for TF + + Returns + ------- + ArgumentParser instance + """ + if not parser: + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + get_common_cli_parser(parser=parser) + mo_convert_params_tf = get_mo_convert_params()['TensorFlow*-specific parameters:'] + + tf_group = parser.add_argument_group('TensorFlow*-specific parameters') + add_args_by_description(tf_group, mo_convert_params_tf) + return parser + + +def get_mxnet_cli_parser(parser: argparse.ArgumentParser = None): + """ + Specifies cli arguments for Model Conversion for MXNet* + + Returns + ------- + ArgumentParser instance + """ + if not parser: + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + get_common_cli_parser(parser=parser) + + mx_group = parser.add_argument_group('MXNet-specific parameters') + mo_convert_params_mxnet = get_mo_convert_params()['MXNet-specific parameters:'] + add_args_by_description(mx_group, mo_convert_params_mxnet) + + return parser + + +def get_kaldi_cli_parser(parser: argparse.ArgumentParser = None): + """ + Specifies cli arguments for Model Conversion for MXNet* + + Returns + ------- + ArgumentParser instance + """ + if not parser: + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + get_common_cli_parser(parser=parser) + + kaldi_group = parser.add_argument_group('Kaldi-specific parameters') + mo_convert_params_kaldi = get_mo_convert_params()['Kaldi-specific parameters:'] + add_args_by_description(kaldi_group, mo_convert_params_kaldi) + return parser + + +def get_onnx_cli_parser(parser: argparse.ArgumentParser = None): + """ + Specifies cli arguments for Model Conversion for ONNX + + Returns + ------- + ArgumentParser instance + """ + if not parser: + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + get_common_cli_parser(parser=parser) + + return parser + + +def get_all_cli_parser(): + """ + Specifies cli arguments for Model Conversion + + Returns + ------- + ArgumentParser instance + """ + parser = argparse.ArgumentParser(usage='%(prog)s [options]') + mo_convert_params_optional = get_mo_convert_params()['Optional parameters:'] + add_args_by_description(parser, mo_convert_params_optional) + + get_common_cli_parser(parser=parser) + get_tf_cli_parser(parser=parser) + get_caffe_cli_parser(parser=parser) + get_mxnet_cli_parser(parser=parser) + get_kaldi_cli_parser(parser=parser) + get_onnx_cli_parser(parser=parser) + + return parser + + +def remove_data_type_from_input_value(input_value: str): + """ + Removes the type specification from the input string. The type specification is a string enclosed with curly braces. + :param input_value: string passed as input to the "input" command line parameter + :return: string without type specification + """ + return re.sub(r'\{.*\}', '', input_value) + + +def get_data_type_from_input_value(input_value: str): + """ + Returns the numpy data type corresponding to the data type specified in the input value string + :param input_value: string passed as input to the "input" command line parameter + :return: the corresponding numpy data type and None if the data type is not specified in the input value + """ + data_type_match = re.match(r'.*\{(.*)\}.*', input_value) + return destination_type_to_np_data_type(data_type_match.group(1)) if data_type_match is not None else None + + +def remove_shape_from_input_value(input_value: str): + """ + Removes the shape specification from the input string. The shape specification is a string enclosed with square + brackets. + :param input_value: string passed as input to the "input" command line parameter + :return: string without shape specification + """ + assert '->' not in input_value, 'The function should not be called for input_value with constant value specified' + return re.sub(r'[(\[]([0-9\.?, -]*)[)\]]', '', input_value) + + +def get_shape_from_input_value(input_value: str): + """ + Returns PartialShape corresponding to the shape specified in the input value string + :param input_value: string passed as input to the "input" command line parameter + :return: the corresponding shape and None if the shape is not specified in the input value + """ + # remove the tensor value from the input_value first + input_value = input_value.split('->')[0] + + # parse shape + shape = re.findall(r'[(\[]([0-9\.\?, -]*)[)\]]', input_value) + if len(shape) == 0: + shape = None + elif len(shape) == 1 and shape[0] in ['', ' ']: + # this shape corresponds to scalar + shape = PartialShape([]) + elif len(shape) == 1: + dims = re.split(r', *| +', shape[0]) + dims = list(filter(None, dims)) + shape = PartialShape([Dimension(dim) for dim in dims]) + else: + raise Error("Wrong syntax to specify shape. Use \"input\" " + "\"node_name[shape]->value\"") + return shape + + +def get_node_name_with_port_from_input_value(input_value: str): + """ + Returns the node name (optionally with input/output port) from the input value + :param input_value: string passed as input to the "input" command line parameter + :return: the corresponding node name with input/output port + """ + return remove_shape_from_input_value(remove_data_type_from_input_value(input_value.split('->')[0])) + + +def get_value_from_input_value(input_value: str): + """ + Returns the value from the input value string + :param input_value: string passed as input to the "input" command line parameter + :return: the corresponding value or None if it is not specified + """ + parts = input_value.split('->') + value = None + if len(parts) == 2: + value = parts[1] + if value[0] == '[' and value[-1] != ']' or value[0] != '[' and value[-1] == ']': + raise Error("Wrong syntax to specify value. Use \"input\"=\"node_name[shape]->value\"") + if '[' in value.strip(' '): + value = value.replace('[', '').replace(']', '') + if ',' in value: + value = value.replace(' ', '') + value = value.split(',') + else: + value = value.split(' ') + if not isinstance(value, list): + value = ast.literal_eval(value) + elif len(parts) > 2: + raise Error("Wrong syntax to specify value. Use \"input\"=\"node_name[shape]->value\"") + return value + + +def partial_shape_prod(shape: [PartialShape, tuple]): + assert not (isinstance(shape, PartialShape) and shape.is_dynamic), \ + "Unable to calculate prod for dynamic shape {}.".format(shape) + + prod = 1 + for dim in shape: + prod *= dim.get_min_length() + return prod + + +def parse_input_value(input_value: str): + """ + Parses a value of the "input" command line parameter and gets a node name, shape and value. + The node name includes a port if it is specified. + Shape and value is equal to None if they are not specified. + Parameters + ---------- + input_value + string with a specified node name, shape, value and data_type. + E.g. 'node_name:0[4]{fp32}->[1.0 2.0 3.0 4.0]' + + Returns + ------- + Node name, shape, value, data type + E.g. 'node_name:0', '4', [1.0 2.0 3.0 4.0], np.float32 + """ + data_type = get_data_type_from_input_value(input_value) + node_name = get_node_name_with_port_from_input_value(input_value) + value = get_value_from_input_value(input_value) + shape = get_shape_from_input_value(input_value) + value_size = np.prod(len(value)) if isinstance(value, list) else 1 + + if value is not None and shape is not None: + for dim in shape: + if isinstance(dim, Dimension) and dim.is_dynamic: + raise Error("Cannot freeze input with dynamic shape: {}".format(shape)) + + if shape is not None and value is not None and partial_shape_prod(shape) != value_size: + raise Error("The shape '{}' of the input node '{}' does not correspond to the number of elements '{}' in the " + "value: {}".format(shape, node_name, value_size, value)) + return node_name, shape, value, data_type + + +def split_str_avoiding_square_brackets(s: str) -> list: + """ + Splits a string by comma, but skips commas inside square brackets. + :param s: string to split + :return: list of strings split by comma + """ + res = list() + skipping = 0 + last_idx = 0 + for i, c in enumerate(s): + if c == '[': + skipping += 1 + elif c == ']': + skipping -= 1 + elif c == ',' and skipping == 0: + res.append(s[last_idx:i]) + last_idx = i + 1 + res.append(s[last_idx:]) + return res + + +def split_layouts_by_arrow(s: str) -> tuple: + """ + Splits a layout string by first arrow (->). + :param s: string to split + :return: tuple containing source and target layouts + """ + arrow = s.find('->') + if arrow != -1: + source_layout = s[:arrow] + target_layout = s[arrow + 2:] + if source_layout == '': + source_layout = None + if target_layout == '': + target_layout = None + return source_layout, target_layout + else: + return s, None + + +def validate_layout(layout: str): + """ + Checks if layout is of valid format. + :param layout: string containing layout + :raises: if layout is incorrect + """ + error_msg = 'Invalid layout parsed: {}'.format(layout) + if layout: + incorrect_brackets = xor(layout[0] == '[', layout[-1] == ']') + if incorrect_brackets or layout[-1] == '-': + error_msg += ', did you forget quotes?' + else: + valid_layout_re = re.compile(r'\[?[^\[\]\(\)\-\s]*\]?') + if valid_layout_re.fullmatch(layout): + return + raise Error(error_msg) + + +def write_found_layout(name: str, found_layout: str, parsed: dict, dest: str = None): + """ + Writes found layout data to the 'parsed' dict. + :param name: name of the node to add layout + :param found_layout: string containing layout for the node + :param parsed: dict where result will be stored + :param dest: type of the command line: + * 'source' is "source_layout" + * 'target' is "target_layout" + * None is "layout" + """ + s_layout = None + t_layout = None + if name in parsed: + s_layout = parsed[name]['source_layout'] + t_layout = parsed[name]['target_layout'] + if dest == 'source': + s_layout = found_layout + elif dest == 'target': + t_layout = found_layout + else: + s_layout, t_layout = split_layouts_by_arrow(found_layout) + validate_layout(s_layout) + validate_layout(t_layout) + parsed[name] = {'source_layout': s_layout, 'target_layout': t_layout} + + +def write_found_layout_list(idx: int, found_layout: str, parsed: list, dest: str = None): + """ + Writes found layout data to the 'parsed' dict. + :param idx: idx of of the node to add layout + :param found_layout: string containing layout for the node + :param parsed: list where result will be stored + :param dest: type of the command line: + * 'source' is "source_layout" + * 'target' is "target_layout" + * None is "layout" + """ + s_layout = None + t_layout = None + if idx < len(parsed): + s_layout = parsed[idx]['source_layout'] + t_layout = parsed[idx]['target_layout'] + if dest == 'source': + s_layout = found_layout + elif dest == 'target': + t_layout = found_layout + else: + s_layout, t_layout = split_layouts_by_arrow(found_layout) + validate_layout(s_layout) + validate_layout(t_layout) + + if idx < len(parsed): + parsed[idx] = {'source_layout': s_layout, 'target_layout': t_layout} + else: + parsed.append({'source_layout': s_layout, 'target_layout': t_layout}) + + +def parse_layouts_by_destination(s: str, parsed: dict, parsed_list: list, dest: str = None) -> None: + """ + Parses layout command line to get all names and layouts from it. Adds all found data in the 'parsed' dict. + :param s: string to parse + :param parsed: dict where result will be stored + :param dest: type of the command line: + * 'source' is "source_layout" + * 'target' is "target_layout" + * None is "layout" + """ + list_s = split_str_avoiding_square_brackets(s) + if len(list_s) == 1 and (list_s[0][-1] not in ')]' or (list_s[0][0] == '[' and list_s[0][-1] == ']')): + # single layout case + write_found_layout('', list_s[0], parsed, dest) + else: + for idx, layout_str in enumerate(list_s): + # case for: "name1(nhwc->[n,c,h,w])" + p1 = re.compile(r'([^\[\]\(\)]*)\((\S+)\)') + m1 = p1.match(layout_str) + # case for: "name1[n,h,w,c]->[n,c,h,w]" + p2 = re.compile(r'([^\[\]\(\)]*)(\[\S*\])') + m2 = p2.match(layout_str) + if m1: + found_g = m1.groups() + elif m2: + found_g = m2.groups() + else: + # case for layout without name + write_found_layout_list(idx, layout_str, parsed_list, dest) + continue + if len(found_g[0]) > 0: + write_found_layout(found_g[0], found_g[1], parsed, dest) + else: + write_found_layout_list(idx, found_g[1], parsed_list, dest) + + +def get_layout_values(argv_layout: str = '', argv_source_layout: str = '', argv_target_layout: str = ''): + """ + Parses layout string. + :param argv_layout: string with a list of layouts passed as a "layout". + :param argv_source_layout: string with a list of layouts passed as a "source_layout". + :param argv_target_layout: string with a list of layouts passed as a "target_layout". + :return: dict with names and layouts associated + """ + if argv_layout and (argv_source_layout or argv_target_layout): + raise Error("\"layout\" is used as well as \"source_layout\" and/or \"target_layout\" which is not allowed, please " + "use one of them.") + res = {} + res_list = [] + if argv_layout: + parse_layouts_by_destination(argv_layout, res, res_list) + if argv_source_layout: + parse_layouts_by_destination(argv_source_layout, res, res_list, 'source') + if argv_target_layout: + parse_layouts_by_destination(argv_target_layout, res, res_list, 'target') + if len(res) > 0 and len(res_list) > 0: + raise Error("Some layout values are provided with names, and some without names. " + "Please provide ether all layouts with names or all layouts without names.") + if len(res) > 0: + return res + else: + 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 + + Parameters + ---------- + argv_input + string with a list of input layers: either an empty string, or strings separated with comma. + 'node_name1[shape1]->value1,node_name2[shape2]->value2,...' + argv_freeze_placeholder_with_value + string with a list of input shapes: either an empty string, or tuples separated with comma. + 'placeholder_name1->value1, placeholder_name2->value2,...' + + Returns + ------- + parsed placeholders with values for freezing + input nodes cleaned from shape info + """ + placeholder_values = parse_freeze_placeholder_values(argv_freeze_placeholder_with_value) + input_node_names = None + + if argv_input is not None: + input_node_names = '' + # walkthrough all input values and save values for freezing + for input_value in split_inputs(argv_input): + node_name, _, value, _ = parse_input_value(input_value) + input_node_names = input_node_names + ',' + node_name if input_node_names != '' else node_name + if value is None: # no value is specified for freezing + continue + 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)) + placeholder_values[node_name] = value + + return placeholder_values, input_node_names + + +def split_inputs(input_str): + brakets_count = 0 + inputs = [] + while input_str: + idx = 0 + for c in input_str: + if c == '[': + brakets_count += 1 + if c == ']': + brakets_count -= 1 + if c == ',': + if brakets_count != 0: + idx += 1 + continue + else: + break + idx += 1 + if idx >= len(input_str)-1: + inputs.append(input_str) + break + inputs.append(input_str[:idx]) + input_str = input_str[idx+1:] + return inputs + + + +def split_shapes(argv_input_shape: str): + range_reg = r'([0-9]*\.\.[0-9]*)' + first_digit_reg = r'([0-9 ]+|-1|\?|{})'.format(range_reg) + next_digits_reg = r'(,{})*'.format(first_digit_reg) + tuple_reg = r'((\({}{}\))|(\[{}{}\]))'.format(first_digit_reg, next_digits_reg, + first_digit_reg, next_digits_reg) + + full_reg = r'^{}(\s*,\s*{})*$|^$'.format(tuple_reg, tuple_reg) + if not re.match(full_reg, argv_input_shape): + raise Error('Input shape "{}" cannot be parsed. ' + refer_to_faq_msg(57), argv_input_shape) + return re.findall(r'[(\[]([0-9,\.\? -]+)[)\]]', argv_input_shape) + +def get_placeholder_shapes(argv_input: str, argv_input_shape: str, argv_batch=None): + """ + Parses input layers names and input shapes from the cli and returns the parsed object. + All shapes are specified only through one command line option either "input" or "input_shape". + + Parameters + ---------- + argv_input + string with a list of input layers: either an empty string, or strings separated with comma. + E.g. 'inp1,inp2', 'node_name1[shape1]->value1,node_name2[shape2]->value2' + argv_input_shape + string with a list of input shapes: either an empty string, or tuples separated with comma. + E.g. '[1,2],[3,4]'. + Only positive integers are accepted. + '?' marks dynamic dimension. + Partial shape is specified with ellipsis. E.g. '[1..10,2,3]' + argv_batch + integer that overrides batch size in input shape + + Returns + ------- + parsed shapes in form of {'name of input':tuple} if names of inputs are provided with shapes + parsed shapes in form of {'name of input':None} if names of inputs are provided without shapes + tuple if only one shape is provided and no input name + None if neither shape nor input were provided + """ + if argv_input_shape and argv_batch: + raise Error("Both \"input_shape\" and \"batch\" were provided. Please provide only one of them. " + + refer_to_faq_msg(56)) + + # attempt to extract shapes from "input" parameters + placeholder_shapes = dict() + placeholder_data_types = dict() + are_shapes_specified_through_input = False + inputs_list = list() + if argv_input: + for input_value in split_inputs(argv_input): + node_name, shape, _, data_type = parse_input_value(input_value) + placeholder_shapes[node_name] = shape + inputs_list.append(node_name) + if data_type is not None: + placeholder_data_types[node_name] = data_type + if shape is not None: + are_shapes_specified_through_input = True + + if argv_input_shape and are_shapes_specified_through_input: + raise Error("Shapes are specified using both \"input\" and \"input_shape\" command-line parameters, but only one " + "parameter is allowed.") + + if argv_batch and are_shapes_specified_through_input: + raise Error("Shapes are specified using both \"input\" and \"batch\" command-line parameters, but only one " + "parameter is allowed.") + + if are_shapes_specified_through_input: + return inputs_list, placeholder_shapes, placeholder_data_types + + shapes = list() + inputs = list() + inputs_list = list() + placeholder_shapes = None + + + if argv_input_shape: + shapes = split_shapes(argv_input_shape) + + if argv_input: + inputs = split_inputs(argv_input) + inputs = [remove_data_type_from_input_value(inp) for inp in inputs] + + # check number of shapes with no input provided + if argv_input_shape and not argv_input: + placeholder_shapes = [PartialShape(shape) for shape in shapes] + if len(placeholder_shapes) == 1: + placeholder_shapes = PartialShape(placeholder_shapes[0]) + # check if number of shapes does not match number of passed inputs + elif argv_input and (len(shapes) == len(inputs) or len(shapes) == 0): + # clean inputs from values for freezing + inputs_without_value = list(map(lambda x: x.split('->')[0], inputs)) + placeholder_shapes = dict(zip_longest(inputs_without_value, + map(lambda x: PartialShape(x) if x else None, shapes))) + for inp in inputs: + if '->' not in inp: + inputs_list.append(inp) + continue + shape = placeholder_shapes[inp.split('->')[0]] + inputs_list.append(inp.split('->')[0]) + + if shape is None: + continue + for dim in shape: + if isinstance(dim, Dimension) and not dim.is_static: + raise Error("Cannot freeze input with dynamic shape: {}".format(shape)) + + elif argv_input: + raise Error('Please provide each input layers with an input layer shape. ' + refer_to_faq_msg(58)) + + return inputs_list, placeholder_shapes, placeholder_data_types + + +def parse_tuple_pairs(argv_values: str): + """ + Gets mean/scale values from the given string parameter + Parameters + ---------- + argv_values + string with a specified input name and list of mean values: either an empty string, or a tuple + in a form [] or (). + E.g. 'data(1,2,3)' means 1 for the RED channel, 2 for the GREEN channel, 3 for the BLUE channel for the data + input layer, or tuple of values in a form [] or () if input is specified separately, e.g. (1,2,3),[4,5,6]. + + Returns + ------- + dictionary with input name and tuple of values or list of values if mean/scale value is specified with input, + e.g.: + "data(10,20,30),info(11,22,33)" -> { 'data': [10,20,30], 'info': [11,22,33] } + "(10,20,30),(11,22,33)" -> [mo_array(10,20,30), mo_array(11,22,33)] + """ + res = {} + if not argv_values: + return res + + matches = [m for m in re.finditer(r'[(\[]([0-9., -]+)[)\]]', argv_values, re.IGNORECASE)] + + error_msg = 'Mean/scale values should consist of name and values specified in round or square brackets ' \ + 'separated by comma, e.g. data(1,2,3),info[2,3,4],egg[255] or data(1,2,3). Or just plain set of ' \ + 'values without names: (1,2,3),(2,3,4) or [1,2,3],[2,3,4].' + refer_to_faq_msg(101) + if not matches: + raise Error(error_msg, argv_values) + + name_start_idx = 0 + name_was_present = False + for idx, match in enumerate(matches): + input_name = argv_values[name_start_idx:match.start(0)] + name_start_idx = match.end(0) + 1 + tuple_value = np.fromstring(match.groups()[0], dtype=float, sep=',') + + if idx != 0 and (name_was_present ^ bool(input_name)): + # if node name firstly was specified and then subsequently not or vice versa + # e.g. (255),input[127] or input(255),[127] + raise Error(error_msg, argv_values) + + name_was_present = True if input_name != "" else False + if name_was_present: + res[input_name] = tuple_value + else: + res[idx] = tuple_value + + if not name_was_present: + # return a list instead of a dictionary + res = sorted(res.values(), key=lambda v: v[0]) + return res + + +def get_tuple_values(argv_values: str or tuple, num_exp_values: int = 3, t=float or int): + """ + Gets mean values from the given string parameter + Args: + argv_values: string with list of mean values: either an empty string, or a tuple in a form [] or (). + E.g. '(1,2,3)' means 1 for the RED channel, 2 for the GREEN channel, 4 for the BLUE channel. + t: either float or int + num_exp_values: number of values in tuple + + Returns: + tuple of values + """ + + digit_reg = r'(-?[0-9. ]+)' if t == float else r'(-?[0-9 ]+)' + + assert num_exp_values > 1, 'Can not parse tuple of size 1' + content = r'{0}\s*,{1}\s*{0}'.format(digit_reg, (digit_reg + ',') * (num_exp_values - 2)) + tuple_reg = r'((\({0}\))|(\[{0}\]))'.format(content) + + if isinstance(argv_values, tuple) and not len(argv_values): + return argv_values + + if not len(argv_values) or not re.match(tuple_reg, argv_values): + raise Error('Values "{}" cannot be parsed. ' + + refer_to_faq_msg(59), argv_values) + + mean_values_matches = re.findall(r'[(\[]([0-9., -]+)[)\]]', argv_values) + + for mean in mean_values_matches: + if len(mean.split(',')) != num_exp_values: + raise Error('{} channels are expected for given values. ' + + refer_to_faq_msg(60), num_exp_values) + + return mean_values_matches + + +def split_node_in_port(node_id: str): + """Split node_id in form port:node to separate node and port, where port is converted to int""" + if isinstance(node_id, str): + separator = ':' + parts = node_id.split(separator) + if len(parts) > 1: + if parts[0].isdigit(): + node_name = separator.join(parts[1:]) + try: + port = int(parts[0]) + return node_name, port + except ValueError as err: + log.warning('Didn\'t recognize port:node format for "{}" because port is not an integer.'.format( + node_id)) + else: + node_name = separator.join(parts[:-1]) + try: + port = int(parts[-1]) + return node_name, port + except ValueError as err: + log.warning('Didn\'t recognize node:port format for "{}" because port is not an integer.'.format( + node_id)) + + return node_id, None + + +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 + + Parameters + ---------- + mean_values dictionary, contains input name and mean values passed py user (e.g. {data: np.array[102.4, 122.1, 113.9]}), + or list containing values (e.g. np.array[102.4, 122.1, 113.9]) + scale_values dictionary, contains input name and scale values passed py user (e.g. {data: np.array[102.4, 122.1, 113.9]}) + or list containing values (e.g. np.array[102.4, 122.1, 113.9]) + + Returns + ------- + The function returns a dictionary e.g. + mean = { 'data': np.array, 'info': np.array }, scale = { 'data': np.array, 'info': np.array }, input = "data, info" -> + { 'data': { 'mean': np.array, 'scale': np.array }, 'info': { 'mean': np.array, 'scale': np.array } } + + """ + 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)] + else: + inputs = [] + if type(mean_values) is dict: + inputs = list(mean_values.keys()) + if type(scale_values) is dict: + for name in scale_values.keys(): + if name not in inputs: + inputs.append(name) + + # create unified object containing both mean and scale for input + if type(mean_values) is dict and type(scale_values) is dict: + if not mean_values and not scale_values: + return res + + for inp_scale in scale_values.keys(): + if inp_scale not in inputs: + raise Error("Specified scale_values name '{}' do not match to any of inputs: {}. " + "Please set 'scale_values' that correspond to values from input.".format(inp_scale, inputs)) + + for inp_mean in mean_values.keys(): + if inp_mean not in inputs: + raise Error("Specified mean_values name '{}' do not match to any of inputs: {}. " + "Please set 'mean_values' that correspond to values from input.".format(inp_mean, inputs)) + + for inp in inputs: + inp, port = split_node_in_port(inp) + if inp in mean_values or inp in scale_values: + res.update( + { + inp: { + 'mean': + mean_values[inp] if inp in mean_values else None, + 'scale': + scale_values[inp] if inp in scale_values else None + } + } + ) + return res + + # user specified input and mean/scale separately - we should return dictionary + if inputs: + if mean_values and scale_values: + if len(inputs) != len(mean_values): + raise Error('Numbers of inputs and mean values do not match. ' + + refer_to_faq_msg(61)) + if len(inputs) != len(scale_values): + raise Error('Numbers of inputs and scale values do not match. ' + + refer_to_faq_msg(62)) + + data = list(zip(mean_values, scale_values)) + + for i in range(len(data)): + res.update( + { + inputs[i]: { + 'mean': + data[i][0], + 'scale': + data[i][1], + + } + } + ) + return res + # only mean value specified + if mean_values: + data = list(mean_values) + for i in range(len(data)): + res.update( + { + inputs[i]: { + 'mean': + data[i], + 'scale': + None + + } + } + ) + return res + + # only scale value specified + if scale_values: + data = list(scale_values) + for i in range(len(data)): + res.update( + { + inputs[i]: { + 'mean': + None, + 'scale': + data[i] + + } + } + ) + return res + # mean and/or scale are specified without inputs + return list(zip_longest(mean_values, scale_values)) + + +def get_model_name(path_input_model: str) -> str: + """ + Deduces model name by a given path to the input model + Args: + path_input_model: path to the input model + + Returns: + name of the output IR + """ + parsed_name, extension = os.path.splitext(os.path.basename(path_input_model)) + return 'model' if parsed_name.startswith('.') or len(parsed_name) == 0 else parsed_name + + +def get_model_name_from_args(argv: argparse.Namespace): + model_name = "" + if hasattr(argv, 'model_name'): + if argv.model_name: + model_name = argv.model_name + elif argv.input_model: + model_name = get_model_name(argv.input_model) + elif argv.saved_model_dir: + model_name = "saved_model" + elif argv.input_meta_graph: + model_name = get_model_name(argv.input_meta_graph) + elif argv.input_symbol: + model_name = get_model_name(argv.input_symbol) + argv.model_name = model_name + return model_name + + +def get_absolute_path(path_to_file: str) -> str: + """ + Deduces absolute path of the file by a given path to the file + Args: + path_to_file: path to the file + + Returns: + absolute path of the file + """ + file_path = os.path.expanduser(path_to_file) + if not os.path.isabs(file_path): + file_path = os.path.join(os.getcwd(), file_path) + return file_path + + +def isfloat(value): + try: + float(value) + return True + except ValueError: + return False + + +def isbool(value): + try: + strtobool(value) + return True + except ValueError: + return False + + +def isdict(value): + try: + evaluated = ast.literal_eval(value) + return isinstance(evaluated, dict) + except ValueError: + return False + + +def convert_string_to_real_type(value: str): + if isdict(value): + return ast.literal_eval(value) + + values = value.split(',') + for i in range(len(values)): + value = values[i] + if value.isdigit(): + values[i] = int(value) + elif isfloat(value): + values[i] = float(value) + elif isbool(value): + values[i] = strtobool(value) + + return values[0] if len(values) == 1 else values + + +def parse_transform(transform: str) -> list: + transforms = [] + + if len(transform) == 0: + return transforms + + all_transforms = re.findall(r"([a-zA-Z0-9]+)(\[([^\]]+)\])*(,|$)", transform) + + # Check that all characters were matched otherwise transform key value is invalid + key_len = len(transform) + for transform in all_transforms: + # In regexp we have 4 groups where 1st group - transformation_name, + # 2nd group - [args], + # 3rd group - args, <-- nested group + # 4th group - EOL + # And to check that regexp matched all string we decrease total length by the length of matched groups (1,2,4) + # In case if no arguments were given to transformation then 2nd and 3rd groups will be empty. + if len(transform) != 4: + raise Error("Unexpected transform key structure: {}".format(transform)) + key_len -= len(transform[0]) + len(transform[1]) + len(transform[3]) + + if key_len != 0: + raise Error("Unexpected transform key structure: {}".format(transform)) + + for transform in all_transforms: + name = transform[0] + args = transform[2] + + args_dict = {} + + if len(args) != 0: + for arg in args.split(';'): + m = re.match(r"^([_a-zA-Z]+)=(.+)$", arg) + if not m: + raise Error("Unrecognized attributes for transform key: {}".format(transform)) + + args_dict[m.group(1)] = convert_string_to_real_type(m.group(2)) + + transforms.append((name, args_dict)) + + return transforms + + +def check_available_transforms(transforms: list): + """ + This function check that transformations specified by user are available. + :param transforms: list of user specified transformations + :return: raises an Error if transformation is not available + """ + from openvino.tools.mo.back.offline_transformations import get_available_transformations # pylint: disable=no-name-in-module,import-error + available_transforms = get_available_transformations() + + missing_transformations = [] + for name, _ in transforms: + if name not in available_transforms.keys(): + missing_transformations.append(name) + + if len(missing_transformations) != 0: + raise Error('Following transformations ({}) are not available. ' + 'List with available transformations ({})'.format(','.join(missing_transformations), + ','.join(available_transforms.keys()))) + return True + + +def check_positive(value): + try: + int_value = int(value) + if int_value <= 0: + raise ValueError + except ValueError: + raise argparse.ArgumentTypeError("expected a positive integer value") + + return int_value + + +def check_bool(value): + if isinstance(value, bool): + return value + elif isinstance(value, str): + if value.lower() not in ['true', 'false']: + raise argparse.ArgumentTypeError("expected a True/False value") + return value.lower() == 'true' + else: + raise argparse.ArgumentTypeError("expected a bool or str type") + + +def depersonalize(value: str, key: str): + dir_keys = [ + 'output_dir', 'extensions', 'saved_model_dir', 'tensorboard_logdir', 'caffe_parser_path' + ] + if isinstance(value, list): + updated_value = [] + for elem in value: + updated_value.append(depersonalize(elem, key)) + return updated_value + + if not isinstance(value, str): + return value + res = [] + for path in value.split(','): + if os.path.isdir(path) and key in dir_keys: + res.append('DIR') + elif os.path.isfile(path): + res.append(os.path.join('DIR', os.path.split(path)[1])) + else: + res.append(path) + return ','.join(res) + +def get_available_front_ends(fem=None): + # Use this function as workaround to avoid IR frontend usage by MO + if fem is None: + return [] + available_moc_front_ends = fem.get_available_front_ends() + if 'ir' in available_moc_front_ends: + available_moc_front_ends.remove('ir') + + return available_moc_front_ends diff --git a/tools/mo/openvino/tools/mo/utils/environment_setup_utils.py b/tools/mo/openvino/tools/mo/utils/environment_setup_utils.py new file mode 100644 index 00000000000..6a0f4cf0df7 --- /dev/null +++ b/tools/mo/openvino/tools/mo/utils/environment_setup_utils.py @@ -0,0 +1,50 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys + +# do not print INFO and WARNING messages from TensorFlow +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + + +def get_imported_module_version(imported_module): + """ + Get imported module version + :return: version(str) or raise AttributeError exception + """ + version_attrs = ("__version__", "VERSION", "version") + installed_version = None + for attr in version_attrs: + installed_version = getattr(imported_module, attr, None) + if isinstance(installed_version, str): + return installed_version + else: + installed_version = None + + if installed_version is None: + raise AttributeError("{} module doesn't have version attribute".format(imported_module)) + else: + return installed_version + + +def get_environment_setup(framework): + """ + Get environment setup such as Python version, TensorFlow version + :param framework: framework name + :return: a dictionary of environment variables + """ + env_setup = dict() + python_version = "{}.{}.{}".format(sys.version_info.major, + sys.version_info.minor, + sys.version_info.micro) + env_setup['python_version'] = python_version + try: + if framework == 'tf': + exec("import tensorflow") + env_setup['tensorflow'] = get_imported_module_version(sys.modules["tensorflow"]) + exec("del tensorflow") + except (AttributeError, ImportError): + pass + env_setup['sys_platform'] = sys.platform + return env_setup diff --git a/tools/mo/openvino/tools/mo/utils/error.py b/tools/mo/openvino/tools/mo/utils/error.py index 1daf27e6f1f..102bf3cb27a 100644 --- a/tools/mo/openvino/tools/mo/utils/error.py +++ b/tools/mo/openvino/tools/mo/utils/error.py @@ -1,4 +1,54 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino.tools.ovc.error import Error, InternalError, FrameworkError, classify_error_type # pylint: disable=no-name-in-module,import-error +import re + + +class BasicError(Exception): + """ Base class for all exceptions in Model Conversion API + + It operates like Exception but when it is converted to str, + it formats string as args[0].format(*args[1:]), where + args are arguments provided when an exception instance is + created. + """ + + def __str__(self): + if len(self.args) <= 1: + return Exception.__str__(self) + return self.args[0].format(*self.args[1:]) # pylint: disable=unsubscriptable-object + + +class FrameworkError(BasicError): + """ User-friendly error: raised when the error on the framework side. """ + pass + + +class Error(BasicError): + """ User-friendly error: raised when the error on the user side. """ + pass + + +class InternalError(BasicError): + """ Not user-friendly error: user cannot fix it and it points to the bug inside MO. """ + pass + + +def classify_error_type(e): + patterns = [ + # Example: No module named 'openvino._offline_transformations.offline_transformations_api' + r"No module named \'\S+\'", + # Example: cannot import name 'IECore' from 'openvino.inference_engine' (unknown location) + r"cannot import name \'\S+\'", + ] + error_message = str(e) + for pattern in patterns: + m = re.search(pattern, error_message) + if m: + return m.group(0) + return "undefined" + + +def legacy_path_error(functionality_description): + raise Exception("{}Please try to install openvino-dev and use convert_model() " + "from openvino.tools.mo.".format(functionality_description)) diff --git a/tools/mo/openvino/tools/mo/utils/get_ov_update_message.py b/tools/mo/openvino/tools/mo/utils/get_ov_update_message.py index c1fb37e7723..9e4876f5888 100644 --- a/tools/mo/openvino/tools/mo/utils/get_ov_update_message.py +++ b/tools/mo/openvino/tools/mo/utils/get_ov_update_message.py @@ -1,6 +1,49 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import datetime + + +msg_fmt = 'Check for a new version of Intel(R) Distribution of OpenVINO(TM) toolkit here {0} ' \ + 'or on https://github.com/openvinotoolkit/openvino' + + +def get_ov_update_message(): + expected_update_date = datetime.date(year=2023, month=12, day=1) + current_date = datetime.date.today() + + link = 'https://software.intel.com/content/www/us/en/develop/tools/openvino-toolkit/download.html?cid=other&source=prod&campid=ww_2023_bu_IOTG_OpenVINO-2023-0&content=upg_all&medium=organic' + + return msg_fmt.format(link) if current_date >= expected_update_date else None + + +def get_ov_api20_message(): + link = "https://docs.openvino.ai/2023.0/openvino_2_0_transition_guide.html" + message = '[ INFO ] The model was converted to IR v11, the latest model format that corresponds to the source DL framework ' \ + 'input/output format. While IR v11 is backwards compatible with OpenVINO Inference Engine API v1.0, ' \ + 'please use API v2.0 (as of 2022.1) to take advantage of the latest improvements in IR v11.\n' \ + 'Find more information about API v2.0 and IR v11 at {}'.format(link) + + return message + + +def get_tf_fe_message(): + link = "https://docs.openvino.ai/2023.0/openvino_docs_MO_DG_TensorFlow_Frontend.html" + message = '[ INFO ] IR generated by new TensorFlow Frontend is compatible only with API v2.0. Please make sure to use API v2.0.\n' \ + 'Find more information about new TensorFlow Frontend at {}'.format(link) + + return message + + +def get_compression_message(): + link = "https://docs.openvino.ai/2023.0/openvino_docs_MO_DG_FP16_Compression.html" + message = '[ INFO ] Generated IR will be compressed to FP16. ' \ + 'If you get lower accuracy, please consider disabling compression ' \ + 'by removing argument "compress_to_fp16" or set it to false "compress_to_fp16=False".\n' \ + 'Find more information about compression to FP16 at {}'.format(link) + return message + + def get_try_legacy_fe_message(): message = '[ INFO ] You can also try to use legacy TensorFlow Frontend by using argument --use_legacy_frontend.\n' return message diff --git a/tools/mo/openvino/tools/mo/utils/help.py b/tools/mo/openvino/tools/mo/utils/help.py new file mode 100644 index 00000000000..4b9c3593f38 --- /dev/null +++ b/tools/mo/openvino/tools/mo/utils/help.py @@ -0,0 +1,161 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +def get_convert_model_help_specifics(): + from openvino.tools.mo.utils.cli_parser import CanonicalizeTransformationPathCheckExistenceAction, \ + CanonicalizePathCheckExistenceAction, CanonicalizeExtensionsPathCheckExistenceAction, \ + CanonicalizePathCheckExistenceIfNeededAction, readable_file_or_dir, readable_dirs_or_files_or_empty, \ + check_positive + from openvino.tools.mo.utils.version import VersionChecker + return { + 'input_model': + {'description': + 'Tensorflow*: a file with a pre-trained model ' + '(binary or text .pb file after freezing). ' + 'Caffe*: a model proto file with model weights.', 'action': CanonicalizePathCheckExistenceAction, + 'type': readable_file_or_dir, + 'aliases': {'-w', '-m'}}, + 'input_shape': + {'description': + 'Input shape(s) that should be fed to an input node(s) ' + 'of the model. Shape is defined as a comma-separated ' + 'list of integer numbers enclosed in parentheses or ' + 'square brackets, for example [1,3,227,227] or ' + '(1,227,227,3), where the order of dimensions depends ' + 'on the framework input layout of the model. For ' + 'example, [N,C,H,W] is used for ONNX* models and ' + '[N,H,W,C] for TensorFlow* models. The shape can ' + 'contain undefined dimensions (? or -1) and should fit ' + 'the dimensions defined in the input operation of the ' + 'graph. Boundaries of undefined dimension can be ' + 'specified with ellipsis, for example ' + '[1,1..10,128,128]. One boundary can be undefined, for ' + 'example [1,..100] or [1,3,1..,1..]. If there are ' + 'multiple inputs in the model, --input_shape should ' + 'contain definition of shape for each input separated ' + 'by a comma, for example: [1,3,227,227],[2,4] for a ' + 'model with two inputs with 4D and 2D shapes. ' + 'Alternatively, specify shapes with the --input option.'}, + 'input': + {'description': + '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 (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, if input node is not a parameter, ' + 'data type is set to f32. Example, to set `input_1` ' + 'with shape [1,100], and Parameter node `sequence_len` ' + 'with scalar input with value `150`, and boolean input ' + '`is_training` with `False` value use the following ' + 'format: \n ' + '\"input_1[1,100],sequence_len->150,is_training->False\". ' + 'Another example, use the following format to set input ' + 'port 0 of the node `node_name1` with the shape [3,4] ' + 'as an input node and freeze output port 1 of the node ' + '\"node_name2\" with the value [20,15] of the int32 type ' + 'and shape [2]: \n ' + '\"0:node_name1[3,4],node_name2:1[2]{i32}->[20,15]\".'}, + 'mean_values': + {'description': + 'Mean values to be used for the input image per ' + 'channel. Values to be provided in the (R,G,B) or ' + '[R,G,B] format. Can be defined for desired input of ' + 'the model, for example: "--mean_values ' + 'data[255,255,255],info[255,255,255]". The exact ' + 'meaning and order of channels depend on how the ' + 'original model was trained.'}, + 'scale_values': + {'description': + 'Scale values to be used for the input image per ' + 'channel. Values are provided in the (R,G,B) or [R,G,B] ' + 'format. Can be defined for desired input of the model, ' + 'for example: "--scale_values ' + 'data[255,255,255],info[255,255,255]". The exact ' + 'meaning and order of channels depend on how the ' + 'original model was trained. If both --mean_values and ' + '--scale_values are specified, the mean is subtracted ' + 'first and then scale is applied regardless of the ' + 'order of options in command line.'}, + 'source_layout': + {'description': + 'Layout of the input or output of the model in the ' + 'framework. Layout can be specified in the short form, ' + 'e.g. nhwc, or in complex form, e.g. \"[n,h,w,c]\". ' + 'Example for many names: \"in_name1([n,h,w,c]),in_name2(' + 'nc),out_name1(n),out_name2(nc)\". Layout can be ' + 'partially defined, \"?\" can be used to specify ' + 'undefined layout for one dimension, \"...\" can be used ' + 'to specify undefined layout for multiple dimensions, ' + 'for example \"?c??\", \"nc...\", \"n...c\", etc.'}, + 'transform': + {'description': + 'Apply additional transformations. Usage: \"--transform ' + 'transformation_name1[args],transformation_name2...\" ' + 'where [args] is key=value pairs separated by ' + 'semicolon. Examples: \"--transform LowLatency2\" or \"--' + 'transform Pruning" or "--transform ' + 'LowLatency2[use_const_initializer=False]" or "--' + 'transform "MakeStateful[param_res_names= {\'input_name_1\':' + '\'output_name_1\',\'input_name_2\':\'output_name_2\'}]\" \n' + 'Available transformations: "LowLatency2", "MakeStateful", "Pruning"'}, + 'extensions': + {'description': + 'Paths or a comma-separated list of paths to libraries ' + '(.so or .dll) with extensions. For the legacy MO path ' + '(if `--use_legacy_frontend` is used), a directory or a ' + 'comma-separated list of directories with extensions ' + 'are supported. To disable all extensions including ' + 'those that are placed at the default location, pass an empty string.', + 'action': CanonicalizeExtensionsPathCheckExistenceAction, + 'type': readable_dirs_or_files_or_empty}, + 'transformations_config': + {'description': + 'Use the configuration file with transformations ' + 'description. Transformations file can be specified as ' + 'relative path from the current directory, as absolute ' + 'path or as arelative path from the mo root directory.', + 'action': CanonicalizeTransformationPathCheckExistenceAction}, + 'counts': + {'action': CanonicalizePathCheckExistenceIfNeededAction}, + 'version': + {'action': 'version', + 'version': 'Version of Model Optimizer is: {}'.format(VersionChecker().get_ie_version())}, + 'scale': + {'type': float, + 'aliases': {'-s'}}, + 'batch': + {'type': check_positive, + 'aliases': {'-b'}}, + 'input_proto': + {'aliases': {'-d'}}, + 'log_level': + {'choices': ['CRITICAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']} + } + + +# 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, 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 + return { + 'input_model': path_to_str_or_object, + 'output': str_list_to_str, + 'mean_values': mean_scale_value_to_str, + 'scale_values': mean_scale_value_to_str, + 'source_layout': source_target_layout_to_str, + 'target_layout': source_target_layout_to_str, + 'layout': layout_param_to_str, + 'transform': transform_param_to_str, + 'extensions': extensions_to_str_or_extensions_class, + 'batch': batch_to_int, + 'transformations_config': transformations_config_to_str, + 'saved_model_tags': str_list_to_str + } diff --git a/tools/mo/openvino/tools/mo/utils/logger.py b/tools/mo/openvino/tools/mo/utils/logger.py index 82ae3f15bda..643d4d89191 100644 --- a/tools/mo/openvino/tools/mo/utils/logger.py +++ b/tools/mo/openvino/tools/mo/utils/logger.py @@ -1,6 +1,160 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import importlib.util +import logging as log +import os +import re +import sys +from argparse import Namespace +from copy import copy -from openvino.tools.ovc.logger import init_logger, LvlFormatter, TagFilter, get_logger_state, restore_logger_state, \ - progress_bar, progress_printer # pylint: disable=no-name-in-module,import-error \ No newline at end of file +# WA for abseil bug that affects logging while importing TF starting 1.14 version +# Link to original issue: https://github.com/abseil/abseil-py/issues/99 +if importlib.util.find_spec('absl') is not None: + import absl.logging + + log.root.removeHandler(absl.logging._absl_handler) + +handler_num = 0 + + +class LvlFormatter(log.Formatter): + format_dict = { + log.DEBUG: "[ %(asctime)s ] [ %(levelname)s ] [ %(module)s:%(lineno)d ] %(msg)s", + log.INFO: "[ %(levelname)s ] %(msg)s", + log.WARNING: "[ WARNING ] %(msg)s", + log.ERROR: "[ %(levelname)s ] %(msg)s", + log.CRITICAL: "[ %(levelname)s ] %(msg)s", + 'framework_error': "[ FRAMEWORK ERROR ] %(msg)s", + 'analysis_info': "[ ANALYSIS INFO ] %(msg)s" + } + + def __init__(self, lvl, fmt=None): + log.Formatter.__init__(self, fmt) + self.lvl = lvl + + def format(self, record: log.LogRecord): + if self.lvl == 'DEBUG': + self._style._fmt = self.format_dict[log.DEBUG] + else: + self._style._fmt = self.format_dict[record.levelno] + if 'is_warning' in record.__dict__.keys(): + self._style._fmt = self.format_dict[log.WARNING] + if 'framework_error' in record.__dict__.keys(): + self._style._fmt = self.format_dict['framework_error'] + if 'analysis_info' in record.__dict__.keys(): + self._style._fmt = self.format_dict['analysis_info'] + return log.Formatter.format(self, record) + + +class TagFilter(log.Filter): + def __init__(self, regex: str): + self.regex = regex + + def filter(self, record: log.LogRecord): + if record.__dict__['funcName'] == 'load_grammar': # for nx not to log into our logs + return False + if self.regex: + if 'tag' in record.__dict__.keys(): + tag = record.__dict__['tag'] + return re.findall(self.regex, tag) + else: + return False + return True # if regex wasn't set print all logs + + +def init_logger(lvl: str, silent: bool): + global handler_num + log_exp = os.environ.get('MO_LOG_PATTERN') + if silent: + lvl = 'ERROR' + fmt = LvlFormatter(lvl=lvl) + handler = log.StreamHandler() + handler.setFormatter(fmt) + logger = log.getLogger() + logger.setLevel(lvl) + logger.addFilter(TagFilter(regex=log_exp)) + if handler_num == 0 and len(logger.handlers) == 0: + logger.addHandler(handler) + handler_num += 1 + +def get_logger_state(): + logger = log.getLogger() + return logger.level, copy(logger.filters), copy(logger.handlers) + +def restore_logger_state(state: tuple): + level, filters, handlers = state + logger = log.getLogger() + logger.setLevel(level) + logger.filters = filters + logger.handlers = handlers + + +def progress_bar(function: callable): + """ + Decorator for model conversion pipeline progress display + Works in combination with function: mo.utils.class_registration.apply_transform + """ + + def wrapper(*args, **kwargs): + for arg in ['graph', 'curr_transform_num', 'num_transforms']: + msg = 'Progress bar decorator is enabled for Model Conversion API transformation applying cycle only. ' \ + 'Argument `{}` {}' + + assert arg in kwargs, msg.format(arg, 'is missing') + assert kwargs[arg] is not None, msg.format(arg, 'should not be None') + + if 'progress' in kwargs['graph'].graph['cmd_params'] and kwargs['graph'].graph['cmd_params'].progress: + bar_len = 20 + total_replacers_count = kwargs['num_transforms'] + + def progress(i): + return int((i + 1) / total_replacers_count * bar_len) + + def percent(i): + return (i + 1) / total_replacers_count * 100 + + end = '' if not kwargs['graph'].graph['cmd_params'].stream_output else '\n' + curr_i = kwargs['curr_transform_num'] + print('\rProgress: [{:{}}]{:>7.2f}% done'.format('.' * progress(curr_i), bar_len, percent(curr_i)), end=end) + + sys.stdout.flush() + + function(*args, **kwargs) + + return wrapper + +def progress_printer(argv: Namespace): + """ + A higher-order factory function returning a configurable callback displaying a progress bar + Depending on the configuration stored in 'argv' the progress bar can be one-line, multi-line, or silent. + """ + def _progress_bar(progress, total, completed, endline): + bar_len = 20 + + def dots(): + return '.' * int(progress * bar_len) + + print('\rProgress: [{:{}}]{:>7.2f}% done'.format(dots(), bar_len, progress*100), end=endline) + sys.stdout.flush() + + def no_progress_bar(progress, total, completed): + """ A 'dummy' progressbar which doesn't print anything """ + pass + + def oneline_progress_bar(progress, total, completed): + """ A callback that always prints the progress in the same line (mimics real GUI progress bar)""" + _progress_bar(progress, total, completed, '') + + def newline_progress_bar(progress, total, completed): + """ A callback that prints an updated progress bar in separate lines """ + _progress_bar(progress, total, completed, '\n') + + if "progress" in argv and argv.progress: + if "stream_output" in argv and argv.stream_output: + return newline_progress_bar + else: + return oneline_progress_bar + else: + return no_progress_bar diff --git a/tools/mo/openvino/tools/mo/utils/telemetry_params.py b/tools/mo/openvino/tools/mo/utils/telemetry_params.py new file mode 100644 index 00000000000..7894a63b9f7 --- /dev/null +++ b/tools/mo/openvino/tools/mo/utils/telemetry_params.py @@ -0,0 +1,6 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +telemetry_params = { + 'TID': "UA-17808594-29" +} diff --git a/tools/mo/openvino/tools/mo/utils/telemetry_stub.py b/tools/mo/openvino/tools/mo/utils/telemetry_stub.py index 0e1ba9a8341..7f4551d9036 100644 --- a/tools/mo/openvino/tools/mo/utils/telemetry_stub.py +++ b/tools/mo/openvino/tools/mo/utils/telemetry_stub.py @@ -1,4 +1,28 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino.tools.ovc.telemetry_stub import Telemetry # pylint: disable=no-name-in-module,import-error +class Telemetry(object): + """ + Stab file for the Telemetry class which is used when Telemetry class is not available. + """ + + def __init__(self, *arg, **kwargs): + pass + + def send_event(self, *arg, **kwargs): + pass + + def send_error(self, *arg, **kwargs): + pass + + def start_session(self, *arg, **kwargs): + pass + + def end_session(self, *arg, **kwargs): + pass + + def force_shutdown(self, *arg, **kwargs): + pass + + def send_stack_trace(self, *arg, **kwargs): + pass diff --git a/tools/mo/openvino/tools/mo/utils/telemetry_utils.py b/tools/mo/openvino/tools/mo/utils/telemetry_utils.py index 23e69782c50..a33c280ef4e 100644 --- a/tools/mo/openvino/tools/mo/utils/telemetry_utils.py +++ b/tools/mo/openvino/tools/mo/utils/telemetry_utils.py @@ -1,15 +1,18 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import argparse +import numbers from collections import Counter import numpy as np - -from openvino.tools.ovc.telemetry_utils import init_mo_telemetry, send_framework_info, get_tid, \ - send_conversion_result, arg_to_str, send_params_info # pylint: disable=no-name-in-module,import-error +from openvino.runtime import get_version as get_rt_version # pylint: disable=no-name-in-module,import-error from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, unmask_shape, int64_array 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.utils.cli_parser import get_params_with_paths_list +from openvino.tools.mo.utils.telemetry_params import telemetry_params +from openvino.tools.mo.utils.utils import check_values_equal try: import openvino_telemetry as tm @@ -17,6 +20,65 @@ except ImportError: import openvino.tools.mo.utils.telemetry_stub as tm +def init_mo_telemetry(): + _ = tm.Telemetry(tid=get_tid(), app_name='Model Conversion API', app_version=get_rt_version()) + + +def send_framework_info(framework: str): + """ + This function sends information about used framework. + :param framework: framework name. + """ + t = tm.Telemetry() + t.send_event('mo', 'framework', framework) + + +def get_tid(): + """ + This function returns the ID of the database to send telemetry. + """ + return telemetry_params['TID'] + + +def send_conversion_result(conversion_result: str, need_shutdown=False): + t = tm.Telemetry() + t.send_event('mo', 'conversion_result', conversion_result) + t.end_session('mo') + if need_shutdown: + t.force_shutdown(1.0) + + +def arg_to_str(arg): + # This method converts to string only known types, otherwise returns string with name of the type + from openvino.runtime import PartialShape, Shape, Type, Layout # pylint: disable=no-name-in-module,import-error + if isinstance(arg, (PartialShape, Shape, Type, Layout)): + return str(arg) + if isinstance(arg, (str, numbers.Number, bool)): + return str(arg) + return str(type(arg)) + + +def send_params_info(argv: argparse.Namespace, cli_parser: argparse.ArgumentParser): + """ + This function sends information about used command line parameters. + :param argv: command line parameters. + :param cli_parser: command line parameters parser. + """ + t = tm.Telemetry() + params_with_paths = get_params_with_paths_list() + for arg in vars(argv): + arg_value = getattr(argv, arg) + if not check_values_equal(arg_value, cli_parser.get_default(arg)): + if arg in params_with_paths: + # If command line argument value is a directory or a path to file it is not sent + # as it may contain confidential information. "1" value is used instead. + param_str = arg + ":" + str(1) + else: + param_str = arg + ":" + arg_to_str(arg_value) + + t.send_event('mo', 'cli_parameters', param_str) + + def send_op_names_info(framework: str, graph: Graph): """ This function sends information about operations in model. diff --git a/tools/mo/openvino/tools/mo/utils/utils.py b/tools/mo/openvino/tools/mo/utils/utils.py index fef2bf9c1cd..48e3cb7af86 100644 --- a/tools/mo/openvino/tools/mo/utils/utils.py +++ b/tools/mo/openvino/tools/mo/utils/utils.py @@ -10,7 +10,35 @@ from typing import Callable import numpy as np from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension -from openvino.tools.ovc.utils import refer_to_faq_msg, check_values_equal # pylint: disable=no-name-in-module,import-error + +try: + import openvino_telemetry as tm +except ImportError: + import openvino.tools.mo.utils.telemetry_stub as tm + + +def refer_to_faq_msg(question_num: int): + try: + t = tm.Telemetry() + t.send_event('mo', 'error_info', "faq:" + str(question_num)) + except Exception: + # Telemetry can be not initialized if it is used in MO IR Reader + pass + + return '\n For more information please refer to Model Conversion API FAQ, question #{0}. ' \ + '(https://docs.openvino.ai/2023.0/openvino_docs_MO_DG_prepare_model_Model_Optimizer_FAQ.html' \ + '?question={0}#question-{0})'.format(question_num) + + +def check_values_equal(val1, val2): + # This method is needed to check equality of values where some values can be None + if val1 is None and val2 is None: + return True + if val1 is None: + return False + if val2 is None: + return False + return val1 == val2 class NamedAttrsClass: diff --git a/tools/mo/openvino/tools/mo/utils/version.py b/tools/mo/openvino/tools/mo/utils/version.py index cd8bbcb9626..04530c8ac04 100644 --- a/tools/mo/openvino/tools/mo/utils/version.py +++ b/tools/mo/openvino/tools/mo/utils/version.py @@ -11,8 +11,37 @@ from openvino.runtime import get_version as get_ie_version from openvino.tools.mo.utils.error import Error from openvino.tools.mo.utils.find_ie_version import find_ie_version from openvino.tools.mo.utils.utils import get_mo_root_dir -from openvino.tools.ovc.version import extract_release_version, simplify_version, extract_hash_from_version, \ - SingletonMetaClass # pylint: disable=no-name-in-module,import-error + + +def extract_release_version(version: str): + patterns = [ + # captures release version set by CI for example: '2021.1.0-1028-55e4d5673a8' + r"^([0-9]+).([0-9]+)*", + # captures release version generated by MO from release branch, for example: 'custom_releases/2021/1_55e4d567' + r"_releases/([0-9]+)/([0-9]+)_*" + ] + + for pattern in patterns: + m = re.search(pattern, version) + if m and len(m.groups()) == 2: + return m.group(1), m.group(2) + return None, None + + +def simplify_version(version: str): + release_version = extract_release_version(version) + if release_version == (None, None): + return "custom" + return "{}.{}".format(*release_version) + + +def extract_hash_from_version(full_version: str): + res = re.findall(r'[-_]([a-f0-9]{7,40})', full_version) + if len(res) > 0: + return res[0] + else: + return None + def get_version_file_path(): @@ -59,6 +88,17 @@ def get_simplified_ie_version(env=dict(), version=None): return simplify_version(version) +class SingletonMetaClass(type): + def __init__(self, cls_name, super_classes, dic): + self.__single_instance = None + super().__init__(cls_name, super_classes, dic) + + def __call__(cls, *args, **kwargs): + if cls.__single_instance is None: + cls.__single_instance = super(SingletonMetaClass, cls).__call__(*args, **kwargs) + return cls.__single_instance + + class VersionChecker(metaclass=SingletonMetaClass): def __init__(self): self.runtime_checked = False diff --git a/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py b/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py index 99f7a5827ee..7c850478199 100644 --- a/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py +++ b/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py @@ -9,7 +9,7 @@ from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTele try: # pylint: disable=no-name-in-module,import-error - from openvino.tools.ovc.moc_frontend.preprocessing import apply_preprocessing + from openvino.tools.mo.back.preprocessing import apply_preprocessing # pylint: disable=no-name-in-module,import-error import openvino.runtime.opset8 as ops diff --git a/tools/mo/unit_tests/mo/moc_frontend/moc_extractor_test_actual.py b/tools/mo/unit_tests/mo/moc_frontend/moc_extractor_test_actual.py index e201baf6833..1534c962963 100644 --- a/tools/mo/unit_tests/mo/moc_frontend/moc_extractor_test_actual.py +++ b/tools/mo/unit_tests/mo/moc_frontend/moc_extractor_test_actual.py @@ -3,7 +3,7 @@ import unittest -from openvino.tools.ovc.moc_frontend.extractor import decode_name_with_port +from openvino.tools.mo.moc_frontend.extractor import decode_name_with_port from openvino.tools.mo.utils.error import Error import pytest diff --git a/tools/mo/unit_tests/mo/utils/args_to_string_test.py b/tools/mo/unit_tests/mo/utils/args_to_string_test.py new file mode 100644 index 00000000000..e9776b7c2ec --- /dev/null +++ b/tools/mo/unit_tests/mo/utils/args_to_string_test.py @@ -0,0 +1,108 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type + +from openvino.runtime import InputCutInfo, LayoutMap +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_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]") + + values = {"input": [0.5, 1.3, 0.67]} + self.assertTrue(mean_scale_value_to_str(values) == "input[0.5,1.3,0.67]") + + values = {"input1": [0.5, 1.3, 0.67], "input2": [4.2, 6.7, 3.15], "input3": [0.757, 4.6, 7.3]} + self.assertTrue(mean_scale_value_to_str(values) == + "input1[0.5,1.3,0.67],input2[4.2,6.7,3.15],input3[0.757,4.6,7.3]") + + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": {("a", "b"): [0.5, 1.3, 0.67]}}) + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": {"name": Dimension(1)}}) + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": Dimension(1)}) + + def test_transform_param_to_str(self): + transform = 'MakeStateful' + self.assertTrue(transform_param_to_str(transform) == "MakeStateful") + + transform1 = ('LowLatency2', {'use_const_initializer': False}) + self.assertTrue(transform_param_to_str(transform1) == + "LowLatency2[use_const_initializer=False]") + + transform2 = ('MakeStateful', {'param_res_names': { + 'input_name_1': 'output_name_1', 'input_name_2': 'output_name_2'}}) + self.assertTrue(transform_param_to_str(transform2) == + "MakeStateful[param_res_names={\'input_name_1\':\'output_name_1\'," + "\'input_name_2\':\'output_name_2\'}]") + + transform = [transform1, transform2] + + self.assertTrue(transform_param_to_str(transform) == "LowLatency2[use_const_initializer=False]," + "MakeStateful[param_res_names={" + "\'input_name_1\':\'output_name_1\'," + "\'input_name_2\':\'output_name_2\'}]") + + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', + {'use_const_initializer': False}, + "param")}) + self.assertRaises(Exception, transform_param_to_str, **{"value": (("a", "b"), {})}) + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', Dimension(1))}) + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', + {('a', 'b'): False})}) + self.assertRaises(Exception, transform_param_to_str, **{"value": 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") + + list_str = "data1" + self.assertTrue(str_list_to_str(list_str) == "data1") + + self.assertRaises(Exception, str_list_to_str, **{"values": [int, 1]}) + self.assertRaises(Exception, str_list_to_str, **{"values": Dimension(1)}) + + def test_source_target_layout_to_str(self): + layout = {"input1": Layout("nhwc"), "input2": Layout("n??"), "input3": "nchw"} + self.assertTrue(source_target_layout_to_str(layout) == "input1([N,H,W,C]),input2([N,?,?]),input3(nchw)") + + self.assertRaises(Exception, source_target_layout_to_str, **{"value": {"op": Dimension(1)}}) + self.assertRaises(Exception, source_target_layout_to_str, **{"value": {("a", "b"): Layout("nhwc")}}) + self.assertRaises(Exception, source_target_layout_to_str, **{"value": Dimension(1)}) + + def test_layout_param_to_str_to_str(self): + layout = {"input1": Layout("nhwc"), "input2": Layout("n??"), "input3": "nchw"} + self.assertTrue(layout_param_to_str(layout) == "input1([N,H,W,C]),input2([N,?,?]),input3(nchw)") + + layout_map1 = LayoutMap(source_layout=Layout("n??"), target_layout=None) + layout_map2 = LayoutMap(source_layout=Layout("nhwc"), target_layout=("nchw")) + layout_map3 = LayoutMap(source_layout="abc", target_layout="cab") + + layout = {"input1": layout_map1, "input2": layout_map2, "input3": layout_map3, "input4": Layout("nhwc"), + "input5": "n?"} + + self.assertTrue(layout_param_to_str(layout) == "input1([N,?,?]),input2([N,H,W,C]->nchw)," + "input3(abc->cab),input4([N,H,W,C]),input5(n?)") + + self.assertRaises(Exception, layout_param_to_str, **{"value": {"op": Dimension(1)}}) + self.assertRaises(Exception, layout_param_to_str, **{"value": {("a", "b"): Layout("nhwc")}}) + self.assertRaises(Exception, layout_param_to_str, **{"value": Dimension(1)}) + + layout = ["nhwc", "[n,c]"] + self.assertTrue(layout_param_to_str(layout) == "nhwc,[n,c]") + + layout = ["abc->cab", "..nc"] + self.assertTrue(layout_param_to_str(layout) == "abc->cab,..nc") + + layout_map1 = LayoutMap(source_layout=Layout("n??"), target_layout=None) + layout = [layout_map1, "..nc"] + self.assertTrue(layout_param_to_str(layout) == "[N,?,?],..nc") + + layout_map2 = LayoutMap(source_layout=Layout("nhwc"), target_layout=("nchw")) + layout_map3 = LayoutMap(source_layout="abc", target_layout="cab") + layout = [layout_map2, layout_map3] + self.assertTrue(layout_param_to_str(layout) == "[N,H,W,C]->nchw,abc->cab") diff --git a/tools/mo/unit_tests/mo/utils/cli_parser_test.py b/tools/mo/unit_tests/mo/utils/cli_parser_test.py new file mode 100644 index 00000000000..1b4b0892eec --- /dev/null +++ b/tools/mo/unit_tests/mo/utils/cli_parser_test.py @@ -0,0 +1,2070 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import numpy +import os +import shutil +import sys +import tempfile +import unittest +from unittest.mock import patch + +import numpy as np + +from openvino.tools.mo.utils.cli_parser import get_placeholder_shapes, get_tuple_values, get_mean_scale_dictionary, \ + get_model_name, \ + parse_tuple_pairs, check_positive, writable_dir, readable_dirs, \ + readable_file, get_freeze_placeholder_values, parse_transform, check_available_transforms, get_layout_values, get_all_cli_parser, \ + get_mo_convert_params +from openvino.tools.mo.convert_impl import pack_params_to_args_namespace +from openvino.tools.mo.utils.error import Error +from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry +from openvino.runtime import PartialShape, Dimension, Layout, InputCutInfo, LayoutMap + + +class TestingMeanScaleGetter(UnitTestWithMockedTelemetry): + def test_tuple_parser(self): + tuple_values = "data(1.1,22.22,333.333),info[2.2,33.33,444.444]" + result = parse_tuple_pairs(tuple_values) + exp_res = { + 'data': np.array([1.1, 22.22, 333.333]), + 'info': np.array([2.2, 33.33, 444.444]) + } + for el in exp_res.keys(): + assert np.array_equal(result[el], exp_res[el]) + + def test_tuple_parser_name_digits_only(self): + tuple_values = "0448(1.1,22.22,333.333),0449[2.2,33.33,444.444]" + result = parse_tuple_pairs(tuple_values) + exp_res = { + '0448': np.array([1.1, 22.22, 333.333]), + '0449': np.array([2.2, 33.33, 444.444]) + } + for el in exp_res.keys(): + assert np.array_equal(result[el], exp_res[el]) + + def test_tuple_parser_same_values(self): + tuple_values = "data(1.1,22.22,333.333),info[1.1,22.22,333.333]" + result = parse_tuple_pairs(tuple_values) + exp_res = { + 'data': np.array([1.1, 22.22, 333.333]), + 'info': np.array([1.1, 22.22, 333.333]) + } + for el in exp_res.keys(): + assert np.array_equal(result[el], exp_res[el]) + + def test_tuple_parser_no_inputs(self): + tuple_values = "(1.1,22.22,333.333),[2.2,33.33,444.444]" + result = parse_tuple_pairs(tuple_values) + exp_res = [np.array([1.1, 22.22, 333.333]), + np.array([2.2, 33.33, 444.444])] + for i in range(0, len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_tuple_parser_error_mixed_with_and_without_name(self): + tuple_values = "(1.1,22.22,333.333),data[2.2,33.33,444.444]" + self.assertRaises(Error, parse_tuple_pairs, tuple_values) + + def test_tuple_parser_error_mixed_with_and_without_name_1(self): + tuple_values = "data(1.1,22.22,333.333),[2.2,33.33,444.444]" + self.assertRaises(Error, parse_tuple_pairs, tuple_values) + + def test_tuple_parser_error_mixed_with_and_without_name_digits(self): + tuple_values = "(0.1,22.22,333.333),0448[2.2,33.33,444.444]" + self.assertRaises(Error, parse_tuple_pairs, tuple_values) + + def test_tuple_parser_error_mixed_with_and_without_name_digits_1(self): + tuple_values = "447(1.1,22.22,333.333),[2.2,33.33,444.444]" + self.assertRaises(Error, parse_tuple_pairs, tuple_values) + + def test_mean_scale_no_input(self): + mean_values = "data(1.1,22.22,333.333)" + scale_values = "info[1.1,22.22,333.333]" + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), None) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': None + }, + 'info': { + 'mean': None, + 'scale': np.array([1.1, 22.22, 333.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_mean_scale_no_input_diff_len(self): + mean_values = "data(1.1,22.22,333.333),info(2.1,33.22,333.333)" + scale_values = "info[1.1,22.22,333.333]" + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), None) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': None + }, + 'info': { + 'mean': np.array([2.1, 33.22, 333.333]), + 'scale': np.array([1.1, 22.22, 333.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_mean_only_input(self): + mean_values = "data(1.1,22.22,333.333)" + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(''), None) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': None + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_scale_only_input(self): + scale_values = "data(1.1,22.22,333.333)" + result = get_mean_scale_dictionary(parse_tuple_pairs(''), parse_tuple_pairs(scale_values), None) + exp_res = { + 'data': { + 'mean': None, + 'scale': np.array([1.1, 22.22, 333.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_scale_only_no_input(self): + scale_values = "(1.1,22.22,333.333)" + mean_values = "" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, None) + exp_res = [ + [ + None, + np.array([1.1, 22.22, 333.333]) + ] + ] + for i in range(len(exp_res)): + for j in range(len(exp_res[i])): + if type(exp_res[i][j]) is np.ndarray: + assert np.array_equal(exp_res[i][j], result[i][j]) + else: + self.assertEqual(exp_res[i][j], result[i][j]) + + def test_scale_only_with_input(self): + scale_values = "(1.1,22.22,333.333)" + mean_values = "" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, 'data') + exp_res = { + 'data': { + 'mean': None, + 'scale': np.array([1.1, 22.22, 333.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_2_scale_only_with_input(self): + scale_values = "(1.1,22.22,333.333),(1.2,22.33,333.444)" + mean_values = "" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, 'data,info') + exp_res = { + 'data': { + 'mean': None, + 'scale': np.array([1.1, 22.22, 333.333]) + }, + 'info': { + 'mean': None, + 'scale': np.array([1.2, 22.33, 333.444]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_2_mean_only_with_input(self): + scale_values = "" + mean_values = "(1.1,22.22,333.333),(1.2,22.33,333.444)" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, 'data,info') + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': None, + }, + 'info': { + 'mean': np.array([1.2, 22.33, 333.444]), + 'scale': None, + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_mean_only_with_input(self): + scale_values = "" + mean_values = "(1.1,22.22,333.333)" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, 'data') + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': None + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_mean_scale_diff_no_input(self): + scale_values = "(1.1,22.22,333.333),(1.1,22.22,333.333)" + mean_values = "(2.1,11.22,444.333)" + mean = parse_tuple_pairs(mean_values) + scale = parse_tuple_pairs(scale_values) + result = get_mean_scale_dictionary(mean, scale, None) + exp_res = [ + [ + np.array([2.1, 11.22, 444.333]), # mean + np.array([1.1, 22.22, 333.333]) # scale + ], + [ + None, # mean + np.array([1.1, 22.22, 333.333]) # scale + ] + ] + for i in range(len(exp_res)): + for j in range(len(exp_res[i])): + if type(exp_res[i][j]) is np.ndarray: + assert np.array_equal(exp_res[i][j], result[i][j]) + else: + self.assertEqual(exp_res[i][j], result[i][j]) + + def test_multi_mean_scale_no_input(self): + mean_values = "data(1.1,22.22,333.333),info(2.1,33.22,444.333)" + scale_values = "data[1.1,22.22,333.333],info[2.1,33.22,444.333]" + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), None) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': np.array([1.1, 22.22, 333.333]) + }, + 'info': { + 'mean': np.array([2.1, 33.22, 444.333]), + 'scale': np.array([2.1, 33.22, 444.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_multi_mean_scale_input(self): + mean_values = "data(1.1,22.22,333.333),info(2.1,33.22,444.333)" + scale_values = "data[1.1,22.22,333.333],info[2.1,33.22,444.333]" + input_names = 'data,info' + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), input_names) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': np.array([1.1, 22.22, 333.333]) + }, + 'info': { + 'mean': np.array([2.1, 33.22, 444.333]), + 'scale': np.array([2.1, 33.22, 444.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_multi_mean_scale_input_arrays(self): + mean_values = "(1.1,22.22,333.333),(2.1,33.22,444.333)" + scale_values = "[1.1,22.22,333.333],[2.1,33.22,444.333]" + input_names = 'data,info' + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), input_names) + exp_res = { + 'data': { + 'mean': np.array([1.1, 22.22, 333.333]), + 'scale': np.array([1.1, 22.22, 333.333]) + }, + 'info': { + 'mean': np.array([2.1, 33.22, 444.333]), + 'scale': np.array([2.1, 33.22, 444.333]) + } + } + for input in exp_res.keys(): + for key in exp_res[input].keys(): + if type(exp_res[input][key]) is np.ndarray: + assert np.array_equal(exp_res[input][key], result[input][key]) + else: + self.assertEqual(exp_res[input][key], result[input][key]) + + def test_multi_mean_scale_arrays_no_input(self): + mean_values = "(1.1,22.22,333.333),(2.1,33.22,444.333)" + scale_values = "[1.1,22.22,333.333],[2.1,33.22,444.333]" + result = get_mean_scale_dictionary(parse_tuple_pairs(mean_values), parse_tuple_pairs(scale_values), None) + exp_res = [ + [ + np.array([1.1, 22.22, 333.333]), # mean + np.array([1.1, 22.22, 333.333]) # scale + ], + [ + np.array([2.1, 33.22, 444.333]), # mean + np.array([2.1, 33.22, 444.333]) # scale + ] + ] + for i in range(0, len(exp_res)): + for j in range(0, len(exp_res[i])): + assert np.array_equal(exp_res[i][j], result[i][j]) + + def test_scale_do_not_match_input(self): + scale_values = parse_tuple_pairs("input_not_present(255),input2(255)") + mean_values = parse_tuple_pairs("input1(255),input2(255)") + self.assertRaises(Error, get_mean_scale_dictionary, mean_values, scale_values, "input1,input2") + + def test_mean_do_not_match_input(self): + scale_values = parse_tuple_pairs("input1(255),input2(255)") + mean_values = parse_tuple_pairs("input_not_present(255),input2(255)") + self.assertRaises(Error, get_mean_scale_dictionary, mean_values, scale_values, "input1,input2") + + def test_values_match_input_name(self): + # to be sure that we correctly processes complex names + res_values = parse_tuple_pairs("input255(255),input255.0(255.0),multi-dotted.input.3.(255,128,64)") + exp_res = {'input255': np.array([255.0]), + 'input255.0': np.array([255.0]), + 'multi-dotted.input.3.': np.array([255., 128., 64.])} + self.assertEqual(len(exp_res), len(res_values)) + for i, j in zip(exp_res, res_values): + self.assertEqual(i, j) + assert np.array_equal(exp_res[i], res_values[j]) + + def test_input_without_values(self): + self.assertRaises(Error, parse_tuple_pairs, "input1,input2") + + +class TestSingleTupleParsing(UnitTestWithMockedTelemetry): + def test_get_values_ideal(self): + values = "(1.11, 22.22, 333.333)" + result = get_tuple_values(values) + exp_res = ['1.11, 22.22, 333.333'] + self.assertEqual(exp_res, result) + + def test_get_values_ideal_spaces(self): + values = "(1 , 22 ,333)" + result = get_tuple_values(values) + exp_res = ['1 , 22 ,333'] + self.assertEqual(exp_res, result) + + def test_get_values_ideal_square(self): + values = "[1,22,333]" + result = get_tuple_values(values) + exp_res = ['1,22,333'] + self.assertEqual(exp_res, result) + + def test_get_values_ideal_square_spaces(self): + values = "[1 , 22 ,333]" + result = get_tuple_values(values) + exp_res = ['1 , 22 ,333'] + self.assertEqual(exp_res, result) + + def test_get_neg_values_ideal(self): + values = "(-1,-22,-333)" + result = get_tuple_values(values) + exp_res = ['-1,-22,-333'] + self.assertEqual(exp_res, result) + + def test_get_neg_values_minus(self): + values = "(-1,--22,-3-33)" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_unbalanced(self): + values = "(1,22,333]" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_unbalanced2(self): + values = "[1,22,333)" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_exactly_3(self): + values = "[1,22,333,22]" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_exactly_3_1(self): + values = "[1,22]" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_empty(self): + values = "" + self.assertRaises(Error, get_tuple_values, values) + + def test_get_values_empty_tuple(self): + values = () + result = get_tuple_values(values) + exp_res = () + self.assertEqual(exp_res, result) + + +class TestShapesParsing(UnitTestWithMockedTelemetry): + def test_get_shapes_several_inputs_several_shapes(self): + argv_input = "inp1,inp2" + input_shapes = "(1,22,333,123), (-1,45,7,1)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': np.array([1, 22, 333, 123]), 'inp2': np.array([-1, 45, 7, 1])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_shapes_several_inputs_several_shapes2(self): + # shapes specified using --input command line parameter and no values + argv_input = "inp1[1 22 333 123],inp2[-1 45 7 1]" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': np.array([1, 22, 333, 123]), 'inp2': np.array([-1, 45, 7, 1])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {} + input_node_names_ref = "inp1,inp2" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_and_freezing_with_scalar_and_without_shapes_in_input(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1,inp2->157" + input_list, result_shapes, _ = get_placeholder_shapes(argv_input, None) + ref_shapes = {'inp1': None, 'inp2': None} + self.assertEqual(list(ref_shapes.keys()), list(result_shapes.keys())) + self.assertEqual(input_list, ["inp1","inp2"]) + for i in ref_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_shapes[i]) + + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp2': 157} + + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + for i in placeholder_values_ref.keys(): + self.assertEqual(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_and_freezing_with_scalar(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1,inp2[]->157" + input_list, result_shapes, _ = get_placeholder_shapes(argv_input, None) + ref_shapes = {'inp1': None, 'inp2': ()} + self.assertEqual(list(ref_shapes.keys()), list(result_shapes.keys())) + for i in ref_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_shapes[i]) + self.assertEqual(input_list, ["inp1","inp2"]) + + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp2': 157} + + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + for i in placeholder_values_ref.keys(): + self.assertEqual(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_shapes3(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3[5]->[1.0 1.0 2.0 3.0 5.0]" + input_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), + 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(input_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_shapes3_comma_sep(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3[5]->[1.0, 1.0, 2.0, 3.0,5.0]" + input_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), + 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(input_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_shapes4(self): + # shapes specified using --input_shape and values for freezing using --input command line parameter + argv_input = "inp1->[1.0 2.0 3.0],inp2,inp3->[1.0 1.0 2.0 3.0 5.0]" + input_shapes = "(3,1), (3,2,3), (5)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), + 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + self.assertEqual(input_node_names_ref, input_node_names_res) + + def test_get_shapes_several_inputs_several_shapes5(self): + # some values for freezing specified using --freeze_placeholder_with_value + argv_input = "inp1->[1.0 2.0 3.0],inp2,inp3->[1.0 1.0 2.0 3.0 5.0]" + input_shapes = "(3,1), (3,2,3), (5)" + argv_freeze_placeholder_with_value = "inp2->[5.0 7.0 3.0],inp4->[100.0 200.0]" + + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, + argv_freeze_placeholder_with_value) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), + 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'], ), + 'inp2': np.array(['5.0', '7.0', '3.0']), 'inp4': np.array(['100.0', '200.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(sorted(list(placeholder_values_res.keys())), sorted(list(placeholder_values_ref.keys()))) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + self.assertEqual(input_node_names_ref, input_node_names_res) + + def test_get_shapes_several_inputs_several_shapes6(self): + # 0D value for freezing specified using --input command line parameter without shape + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3->False" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), 'inp2': PartialShape([3, 2, 3]), 'inp3': None} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': False} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_shapes7(self): + # 0D shape and value for freezing specified using --input command line parameter + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3[]->True" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array(False).shape} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': True} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_and_data_types1(self): + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3]{i32},inp3[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3': np.array([5])} + ref_result_data_types = {'inp2': np.int32, 'inp3': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1","inp2","inp3"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_with_input_ports(self): + argv_input = "1:inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3]{i32},0:inp3[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'1:inp1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), '0:inp3': np.array([5])} + ref_result_data_types = {'inp2': np.int32, '0:inp3': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["1:inp1","inp2","0:inp3"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_with_output_ports(self): + argv_input = "inp1:1[3 1]->[1.0 2.0 3.0],inp2[3 2 3]{i32},inp3:4[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3:4': np.array([5])} + ref_result_data_types = {'inp2': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1","inp2","inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_with_output_ports_comma_sep(self): + argv_input = "inp1:1[3,1]->[1.0,2.0 ,3.0],inp2[3,2, 3]{i32},inp3:4[5]{f32}->[1.0, 1.0,2.0, 3.0,5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': np.array([3, 1]), 'inp2': np.array([3, 2, 3]), 'inp3:4': np.array([5])} + ref_result_data_types = {'inp2': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1","inp2","inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_shape_only(self): + argv_input = "placeholder1[3 1],placeholder2,placeholder3" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'placeholder1': np.array([3, 1]), 'placeholder2': None, + 'placeholder3': None} + ref_result_data_types = {} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["placeholder1","placeholder2","placeholder3"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_shape_with_ports_only(self): + argv_input = "placeholder1:4[3 1],placeholder2,2:placeholder3" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'placeholder1:4': np.array([3, 1]), 'placeholder2': None, + '2:placeholder3': None} + ref_result_data_types = {} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["placeholder1:4","placeholder2","2:placeholder3"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_when_no_freeze_value(self): + argv_input = "placeholder1{i32}[3 1],placeholder2,placeholder3{i32}" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'placeholder1': np.array([3, 1]), 'placeholder2': None, + 'placeholder3': None} + ref_result_data_types = {'placeholder1': np.int32, 'placeholder3': np.int32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["placeholder1","placeholder2","placeholder3"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_wrong_data_types(self): + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3]{abracadabra},inp3[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_shapes_specified_using_both_params(self): + # shapes specified using both command line parameter --input and --input_shape + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3[5]->[1.0 1.0 2.0 3.0 5.0]" + input_shapes = "(3,1), (3,2,3), (5)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_shape_and_value_shape_mismatch(self): + # size of value tensor does not correspond to specified shape for the third node + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3 2 3],inp3[5 3]->[2.0 3.0 5.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, None) + + def test_wrong_data_for_input_cmd_param(self): + # test that wrongly formatted data specified in --input is handled properly + argv_input = "abc->[1.0" + self.assertRaises(Error, get_freeze_placeholder_values, argv_input, None) + argv_input = "def[2 2]->[1.0 2.0 3.0 4.0],abc->1.0 34]" + self.assertRaises(Error, get_freeze_placeholder_values, argv_input, None) + + def test_get_shapes_several_inputs_several_shapes_not_equal(self): + argv_input = "inp1,inp2,inp3" + input_shapes = "(1,22,333,123), (-1,45,7,1)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_several_shapes_one_input(self): + argv_input = "inp1" + input_shapes = "(1,22,333,123), (-1,45,7,1), (-1,456,7,1)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_one_shape(self): + argv_input = "inp1" + input_shapes = "(1,22,333,123)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': np.array([1, 22, 333, 123])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_shapes_no_input_no_shape(self): + argv_input = "" + input_shapes = "" + _, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = None + assert np.array_equal(result, exp_res) + + def test_get_shapes_no_input_one_shape(self): + argv_input = "" + input_shapes = "(12,4,1)" + _, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = np.array([12, 4, 1]) + assert np.array_equal(result, exp_res) + + def test_get_shapes_no_input_one_shape2(self): + argv_input = "" + input_shapes = "[12,4,1]" + _, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = np.array([12, 4, 1]) + assert np.array_equal(result, exp_res) + + + def test_get_shapes_one_input_no_shape(self): + argv_input = "inp1" + input_shapes = "" + input_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': None} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(input_list, ["inp1"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_shapes_one_input_wrong_shape8(self): + argv_input = "inp1" + input_shapes = "[2,4,1)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape9(self): + argv_input = "inp1" + input_shapes = "(2,4,1]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape10(self): + argv_input = "inp1" + input_shapes = "(2,,,4,1]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape2(self): + argv_input = "inp1" + input_shapes = "(2,4,1" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape3(self): + argv_input = "inp1" + input_shapes = "2,4,1" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape4(self): + argv_input = "inp1" + input_shapes = "2;4;1" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape5(self): + argv_input = "inp1" + input_shapes = "2, 4,1" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape6(self): + argv_input = "inp1" + input_shapes = "(2, 4,1),[4,6,8]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_wrong_shape7(self): + argv_input = "inp1" + input_shapes = "[2,4,1],(4,6,8)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_several_shapes(self): + argv_input = "inp1" + input_shapes = "(2,4,1),(4,6,8)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_first_neg_shape1(self): + argv_input = "inp1,inp2" + input_shapes = "(-1,4,1),(4,6,8)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': np.array([-1, 4, 1]), 'inp2': np.array([4, 6, 8])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_shapes_one_input_first_neg_shape_not_one(self): + argv_input = "inp1" + input_shapes = "(-12,4,1),(4,6,8)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_long_dimension_with_invalid_character(self): + # test for regular expression denial of service + argv_input = "inp1,inp2" + input_shapes = "(222222222222222222222222222222222222222222!,4,1),(4,6,8)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_one_input_any_neg_shape(self): + argv_input = "inp1, inp2" + input_shapes = "(12,4,1),(4,-6,8)" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_several_inputs_several_partial_shapes(self): + argv_input = "inp1,inp2" + input_shapes = "(1,..22,1..100,?), (-1,45..,7,1)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': PartialShape([1, Dimension(0, 22), Dimension(1, 100), -1]), 'inp2': PartialShape([-1, Dimension(45, -1), 7, 1])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + self.assertEqual(inputs_list, ["inp1","inp2"]) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_shapes_several_inputs_several_partial_shapes2(self): + # shapes specified using --input command line parameter and no values + argv_input = "inp1[1 ? 50..100 123],inp2[-1 45.. ..7 1]" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([1, -1, (50, 100), 123]), 'inp2': PartialShape([-1, Dimension(45,-1), Dimension(0, 7), 1])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {} + input_node_names_ref = "inp1,inp2" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes3(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3.. ..2 5..10 ? -1],inp3[5]->[1.0 1.0 2.0 3.0 5.0]" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': (3, 1), 'inp2': PartialShape([Dimension(3, -1), Dimension(0, 2), Dimension(5, 10), -1, -1]), 'inp3': (5,)} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes4(self): + # shapes specified using --input_shape and values for freezing using --input command line parameter + argv_input = "inp1->[1.0 2.0 3.0],inp2,inp3->[1.0 1.0 2.0 3.0 5.0]" + input_shapes = "(3,1), (3..,..2,5..10,?,-1), (5)" + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': (3, 1), 'inp2': PartialShape([Dimension(3, -1), Dimension(0, 2), Dimension(5, 10), -1, -1]), 'inp3': (5,)} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + self.assertEqual(input_node_names_ref, input_node_names_res) + + def test_get_shapes_several_inputs_several_partial_shapes5(self): + # some values for freezing specified using --freeze_placeholder_with_value + argv_input = "inp1->[1.0 2.0 3.0],inp2,inp3->[1.0 1.0 2.0 3.0 5.0]" + input_shapes = "(3,1), (3..,..2,5..10,?,-1), (5)" + argv_freeze_placeholder_with_value = "inp2->[5.0 7.0 3.0],inp4->[100.0 200.0]" + + inputs_list, result, _ = get_placeholder_shapes(argv_input, input_shapes) + exp_res = {'inp1': PartialShape([3, 1]), 'inp2': PartialShape([(3, np.iinfo(np.int64).max), (0, 2), (5, 10), -1, -1]), 'inp3': PartialShape([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, argv_freeze_placeholder_with_value) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'],), + 'inp2': np.array(['5.0', '7.0', '3.0']), 'inp4': np.array(['100.0', '200.0'])} + input_node_names_ref = "inp1,inp2,inp3" + self.assertEqual(sorted(list(placeholder_values_res.keys())), sorted(list(placeholder_values_ref.keys()))) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + self.assertEqual(input_node_names_ref, input_node_names_res) + + def test_get_shapes_several_inputs_several_partial_shapes6(self): + # 0D value for freezing specified using --input command line parameter without shape + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3.. ..2 5..10 ? -1],inp3->False" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), 'inp2': PartialShape([(3, np.iinfo(np.int64).max), (0, 2), (5, 10), -1, -1]), 'inp3': None} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': False} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes7(self): + # 0D shape and value for freezing specified using --input command line parameter + argv_input = "inp1[3 1]->[1.0 2.0 3.0],inp2[3.. ..2 5..10 ? -1],inp3[]->True" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), 'inp2': PartialShape([(3, np.iinfo(np.int64).max), (0, 2), (5, 10), -1, -1]), 'inp3': np.array(False).shape} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': True} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1","inp2","inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_and_data_types_partial_shape_with_input_port(self): + argv_input = "inp1:1[3 1]->[1.0 2.0 3.0],0:inp2[3.. ..2 5..10 ? -1]{i32},inp3:4[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': PartialShape([3, 1]), '0:inp2': PartialShape([Dimension(3, -1), Dimension(-1, 2), Dimension(5, 10), -1, -1]), 'inp3:4': np.array([5])} + ref_result_data_types = {'0:inp2': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1","0:inp2","inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_partial_shape_with_output_port(self): + argv_input = "inp1:1[3 1]->[1.0 2.0 3.0],inp2:3[3.. ..2 5..10 ? -1]{i32},inp3:4[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': PartialShape([3, 1]), 'inp2:3': PartialShape([Dimension(3, -1), Dimension(0, 2), Dimension(5, 10), -1, -1]), 'inp3:4': PartialShape([5])} + ref_result_data_types = {'inp2:3': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1","inp2:3","inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_partial_shapes_negative_case(self): + argv_input = "inp1" + input_shapes = "[6754fg..23ed]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_partial_shapes_freeze_dynamic_negative_case1(self): + argv_input = "inp1:1[3 1..10]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_partial_shapes_freeze_dynamic_negative_case2(self): + argv_input = "inp1:1[1 2 -1]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_partial_shapes_freeze_dynamic_negative_case3(self): + # some values for freezing specified using --freeze_placeholder_with_value + argv_input = "inp1->[1.0 2.0 3.0]" + input_shapes = "[3,1..10]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) + + def test_get_shapes_several_inputs_several_partial_shapes2_comma_separator(self): + # shapes specified using --input command line parameter and no values + argv_input = "inp1[1,?,50..100,123],inp2[-1,45..,..7,1]" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([1, -1, (50, 100), 123]), + 'inp2': PartialShape([-1, Dimension(45, -1), Dimension(0, 7), 1])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1", "inp2"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes3_comma_separator(self): + # shapes and value for freezing specified using --input command line parameter + argv_input = "inp1[3,1]->[1.0 2.0 3.0],inp2[3..,..2,5..10,?,-1],inp3[5]->[1.0 1.0 2.0 3.0 5.0]" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), 'inp2': PartialShape([Dimension(3, -1), Dimension(0, 2), Dimension(5, 10), -1, -1]), + 'inp3': PartialShape([5])} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), + 'inp3': np.array(['1.0', '1.0', '2.0', '3.0', '5.0'])} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1", "inp2", "inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes6_comma_separator(self): + # 0D value for freezing specified using --input command line parameter without shape + argv_input = "inp1[3, 1]->[1.0 2.0 3.0],inp2[3.., ..2, 5..10, ?,-1],inp3->False" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), + 'inp2': PartialShape([(3, np.iinfo(np.int64).max), (0, 2), (5, 10), -1, -1]), 'inp3': None} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': False} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1", "inp2", "inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_several_inputs_several_partial_shapes7_comma_separator(self): + # 0D shape and value for freezing specified using --input command line parameter + argv_input = "inp1[3,1]->[1.0 2.0 3.0],inp2[3.., ..2,5..10, ?,-1],inp3[]->True" + inputs_list, result, _ = get_placeholder_shapes(argv_input, None) + exp_res = {'inp1': PartialShape([3, 1]), + 'inp2': PartialShape([(3, np.iinfo(np.int64).max), (0, 2), (5, 10), -1, -1]), + 'inp3': np.array(False).shape} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + placeholder_values_res, input_node_names_res = get_freeze_placeholder_values(argv_input, None) + placeholder_values_ref = {'inp1': np.array(['1.0', '2.0', '3.0']), 'inp3': True} + self.assertEqual(list(placeholder_values_res.keys()), list(placeholder_values_ref.keys())) + self.assertEqual(inputs_list, ["inp1", "inp2", "inp3"]) + for i in placeholder_values_ref.keys(): + assert np.array_equal(placeholder_values_res[i], placeholder_values_ref[i]) + + def test_get_shapes_and_data_types_partial_shape_with_input_port_comma_separator(self): + argv_input = "inp1:1[3,1]->[1.0 2.0 3.0],0:inp2[ 3.. ,..2, 5..10, ?,-1]{i32},inp3:4[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': PartialShape([3, 1]), + '0:inp2': PartialShape([Dimension(3, -1), Dimension(-1, 2), Dimension(5, 10), -1, -1]), + 'inp3:4': np.array([5])} + ref_result_data_types = {'0:inp2': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1", "0:inp2", "inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_get_shapes_and_data_types_partial_shape_with_output_port_comma_separator(self): + argv_input = "inp1:1[3,1]->[1.0 2.0 3.0],inp2:3[3..,..2,5..10,?,-1]{i32},inp3:4[5]{f32}->[1.0 1.0 2.0 3.0 5.0]" + input_list, result_shapes, result_data_types = get_placeholder_shapes(argv_input, "") + ref_result_shapes = {'inp1:1': PartialShape([3, 1]), + 'inp2:3': PartialShape([Dimension(3, -1), Dimension(0, 2), Dimension(5, 10), -1, -1]), + 'inp3:4': PartialShape([5])} + ref_result_data_types = {'inp2:3': np.int32, 'inp3:4': np.float32} + self.assertEqual(list(ref_result_shapes.keys()), list(result_shapes.keys())) + for i in ref_result_shapes.keys(): + assert np.array_equal(result_shapes[i], ref_result_shapes[i]) + self.assertEqual(list(ref_result_data_types.keys()), list(result_data_types.keys())) + self.assertEqual(input_list, ["inp1:1", "inp2:3", "inp3:4"]) + for i in ref_result_data_types.keys(): + np.testing.assert_equal(result_data_types[i], ref_result_data_types[i]) + + def test_partial_shapes_freeze_dynamic_negative_case1_comma_separator(self): + argv_input = "inp1:1[3,1..10]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_partial_shapes_freeze_dynamic_negative_case2_comma_separator(self): + argv_input = "inp1:1[1,2,-1]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_partial_shapes_freeze_dynamic_negative_case3_comma_separator(self): + argv_input = "inp1:1[3,1..10]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + def test_partial_shapes_freeze_dynamic_negative_case4_comma_separator(self): + argv_input = "inp1:1[1, 2, -1]->[1.0 2.0 3.0]" + self.assertRaises(Error, get_placeholder_shapes, argv_input, "") + + +class TestModelNameParsing(unittest.TestCase): + def test_model_name_ideal(self): + model_name = '/home/models/mymodel.caffemodel' + res = get_model_name(model_name) + exp_res = 'mymodel' + self.assertEqual(exp_res, res) + + def test_model_name_no_name(self): + model_name = '/home/models/.caffemodel' + res = get_model_name(model_name) + exp_res = 'model' + self.assertEqual(exp_res, res) + + def test_model_name_no_ext(self): + model_name = '/home/models/caffemodel' + res = get_model_name(model_name) + exp_res = 'caffemodel' + self.assertEqual(exp_res, res) + + def test_model_name_no_name_no_path(self): + model_name = '.caffemodel' + res = get_model_name(model_name) + exp_res = 'model' + self.assertEqual(exp_res, res) + + @patch("openvino.tools.mo.utils.cli_parser.os") + def test_model_name_win(self, old_os): + old_os.path.basename.return_value = "caffemodel" + old_os.path.splitext.return_value = ("caffemodel", "") + model_name = r'\home\models\caffemodel' + res = get_model_name(model_name) + + exp_res = 'caffemodel' + self.assertEqual(exp_res, res) + + def test_model_name_dots(self): + model_name = r'/home/models/squeezenet_v1.1.caffemodel' + res = get_model_name(model_name) + exp_res = 'squeezenet_v1.1' + self.assertEqual(exp_res, res) + + +class PositiveChecker(unittest.TestCase): + def test_positive_checker_batch(self): + res = check_positive('1') + self.assertEqual(res, 1) + + def test_positive_checker_batch_negative(self): + self.assertRaises(argparse.ArgumentTypeError, check_positive, '-1') + + def test_positive_checker_batch_not_int(self): + self.assertRaises(argparse.ArgumentTypeError, check_positive, 'qwe') + + +class PathCheckerFunctions(unittest.TestCase): + READABLE_DIR = tempfile.gettempdir() + WRITABLE_DIR = os.path.join(tempfile.gettempdir(), 'writable_dir') + WRITABLE_NON_EXISTING_DIR = os.path.join(WRITABLE_DIR, 'non_existing_dir') + NOT_WRITABLE_DIR = os.path.join(tempfile.gettempdir(), 'not_writable_dir') + NOT_WRITABLE_SUB_DIR = os.path.join(tempfile.gettempdir(), 'another_not_writable_dir', 'not_existing_dir') + EXISTING_FILE = tempfile.NamedTemporaryFile(mode='r+', delete=False).name + NOT_EXISTING_FILE = '/abcd/efgh/ijkl' + + @classmethod + def setUpClass(cls): + if not os.path.exists(__class__.WRITABLE_DIR): + os.makedirs(__class__.WRITABLE_DIR) + if os.path.exists(__class__.WRITABLE_NON_EXISTING_DIR): + os.removedirs(__class__.WRITABLE_NON_EXISTING_DIR) + + if not os.path.exists(__class__.NOT_WRITABLE_DIR): + os.makedirs(__class__.NOT_WRITABLE_DIR) + os.chmod(__class__.NOT_WRITABLE_DIR, 0) + + if not os.path.exists(os.path.dirname(__class__.NOT_WRITABLE_SUB_DIR)): + os.makedirs(os.path.dirname(__class__.NOT_WRITABLE_SUB_DIR)) + os.chmod(os.path.dirname(__class__.NOT_WRITABLE_SUB_DIR), 0) + if os.path.exists(__class__.NOT_EXISTING_FILE): + os.remove(__class__.NOT_EXISTING_FILE) + + @classmethod + def tearDownClass(cls): + if os.path.exists(__class__.WRITABLE_DIR): + os.removedirs(__class__.WRITABLE_DIR) + if os.path.exists(__class__.NOT_WRITABLE_DIR): + shutil.rmtree(__class__.NOT_WRITABLE_DIR, ignore_errors=True) + if os.path.exists(os.path.dirname(__class__.NOT_WRITABLE_SUB_DIR)): + shutil.rmtree(os.path.dirname(__class__.NOT_WRITABLE_SUB_DIR), ignore_errors=True) + if os.path.exists(__class__.EXISTING_FILE): + os.remove(__class__.EXISTING_FILE) + + def test_single_writable_dir(self): + self.assertEqual(__class__.WRITABLE_DIR, writable_dir(__class__.WRITABLE_DIR)) + + @unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir") + def test_single_non_writable_dir(self): + with self.assertRaises(Error) as cm: + writable_dir(__class__.NOT_WRITABLE_DIR) + + @unittest.skipIf(sys.platform.startswith("win"), "chmod() on Windows do nor support not writable dir") + def test_single_non_writable_sub_dir(self): + with self.assertRaises(Error) as cm: + writable_dir(__class__.NOT_WRITABLE_SUB_DIR) + + def test_multiple_writable_dirs(self): + dirs_str = ','.join([__class__.WRITABLE_DIR, __class__.WRITABLE_NON_EXISTING_DIR]) + self.assertEqual(dirs_str, writable_dir(dirs_str)) + + def test_single_writable_non_existing_dir(self): + self.assertEqual(__class__.WRITABLE_NON_EXISTING_DIR, writable_dir(__class__.WRITABLE_NON_EXISTING_DIR)) + + def test_readable_dirs(self): + dirs_str = ','.join([__class__.WRITABLE_DIR, __class__.READABLE_DIR]) + self.assertEqual(dirs_str, readable_dirs(dirs_str)) + + def test_not_readable_dirs(self): + dirs_str = ','.join([__class__.WRITABLE_DIR, __class__.WRITABLE_NON_EXISTING_DIR]) + with self.assertRaises(Error) as cm: + readable_dirs(dirs_str) + + def test_readable_file(self): + self.assertEqual(__class__.EXISTING_FILE, readable_file(__class__.EXISTING_FILE)) + + def test_non_readable_file(self): + with self.assertRaises(Error) as cm: + readable_file(__class__.NOT_EXISTING_FILE) + + +class TransformChecker(unittest.TestCase): + def test_empty(self): + self.assertEqual(parse_transform(""), []) + + def test_single_pass(self): + self.assertEqual(parse_transform("LowLatency2"), [("LowLatency2", {})]) + + def test_single_pass_with_args(self): + self.assertEqual(parse_transform("LowLatency2[use_const_initializer=True]"), + [("LowLatency2", {"use_const_initializer": True})]) + + def test_single_pass_with_multiple_args(self): + self.assertEqual(parse_transform("LowLatency2[use_const_initializer=True;dummy_attr=3.14]"), + [("LowLatency2", {"use_const_initializer": True, "dummy_attr": 3.14})]) + + def test_multiple_passes_with_args(self): + self.assertEqual(parse_transform("LowLatency2[use_const_initializer=True],DummyPass[type=ReLU]"), + [("LowLatency2", {"use_const_initializer": True}), + ("DummyPass", {"type": "ReLU"})]) + + def test_multiple_passes_with_args2(self): + self.assertEqual(parse_transform("LowLatency2[use_const_initializer=True,False],DummyPass1," + "DummyPass2[types=ReLU,PReLU;values=1,2,3]"), + [("LowLatency2", {"use_const_initializer": [True, False]}), + ("DummyPass1", {}), + ("DummyPass2", {"types": ["ReLU", "PReLU"], "values": [1, 2, 3]})]) + + def test_multiple_passes_no_args(self): + self.assertEqual(parse_transform("DummyPass,LowLatency22"), + [("DummyPass", {}), ("LowLatency22", {})]) + + def test_single_pass_neg(self): + self.assertRaises(Error, parse_transform, "LowLatency2!") + + def test_multiple_passes_neg(self): + self.assertRaises(Error, parse_transform, "LowLatency2;DummyPass") + + def test_single_pass_with_args_neg1(self): + self.assertRaises(Error, parse_transform, "LowLatency2[=2]") + + def test_single_pass_with_args_neg2(self): + self.assertRaises(Error, parse_transform, "LowLatency2[key=]") + + def test_single_pass_with_args_neg3(self): + self.assertRaises(Error, parse_transform, "LowLatency2[]") + + def test_single_pass_with_args_neg4(self): + self.assertRaises(Error, parse_transform, "LowLatency2[key=value;]") + + def test_single_pass_with_args_neg5(self): + self.assertRaises(Error, parse_transform, "LowLatency2[value]") + + def test_single_pass_with_args_neg6(self): + self.assertRaises(Error, parse_transform, "LowLatency2[key=value") + + @patch("openvino.tools.mo.back.offline_transformations.get_available_transformations") + def test_check_low_latency_is_available(self, available_transformations): + available_transformations.return_value = {"LowLatency2": None} + try: + check_available_transforms([("LowLatency2", "")]) + except Error as e: + self.assertTrue(False, "Exception \"{}\" is unexpected".format(e)) + + @patch("openvino.tools.mo.back.offline_transformations.get_available_transformations") + def test_check_dummy_pass_is_available(self, available_transformations): + available_transformations.return_value = {"LowLatency2": None} + self.assertRaises(Error, check_available_transforms, [("DummyPass", "")]) + + +class TestLayoutParsing(unittest.TestCase): + def test_get_layout_1(self): + argv_layout = "name1([n,h,w,c]),name2([n,h,w,c]->[n,c,h,w])" + result = get_layout_values(argv_layout) + exp_res = {'name1': {'source_layout': '[n,h,w,c]', 'target_layout': None}, + 'name2': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_2(self): + argv_layout = "name1(nhwc),name2(nhwc->nchw)" + result = get_layout_values(argv_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': 'nhwc', 'target_layout': 'nchw'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_3(self): + argv_layout = "name1(n...c),name2(n...c->nc...)" + result = get_layout_values(argv_layout) + exp_res = {'name1': {'source_layout': 'n...c', 'target_layout': None}, + 'name2': {'source_layout': 'n...c', 'target_layout': 'nc...'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_4(self): + argv_layout = "nhwc" + result = get_layout_values(argv_layout) + exp_res = {'': {'source_layout': 'nhwc', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_5(self): + argv_layout = "[n,h,w,c]" + result = get_layout_values(argv_layout) + exp_res = {'': {'source_layout': '[n,h,w,c]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_6(self): + argv_layout = "nhwc->nchw" + result = get_layout_values(argv_layout) + exp_res = {'': {'source_layout': 'nhwc', 'target_layout': 'nchw'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_7(self): + argv_layout = "[n,h,w,c]->[n,c,h,w]" + result = get_layout_values(argv_layout) + exp_res = {'': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_8(self): + argv_layout = "name1-0(n...c),name2-0(n...c->nc...)" + result = get_layout_values(argv_layout) + exp_res = {'name1-0': {'source_layout': 'n...c', 'target_layout': None}, + 'name2-0': {'source_layout': 'n...c', 'target_layout': 'nc...'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_scalar(self): + argv_layout = "name1(nhwc),name2([])" + result = get_layout_values(argv_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': '[]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_1(self): + argv_source_layout = "[n,h,w,c]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'': {'source_layout': '[n,h,w,c]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_2(self): + argv_source_layout = "nhwc" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'': {'source_layout': 'nhwc', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_3(self): + argv_source_layout = "name1(nhwc),name2(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': 'nchw', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_4(self): + argv_source_layout = "name1([n,h,w,c]),name2([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'name1': {'source_layout': '[n,h,w,c]', 'target_layout': None}, + 'name2': {'source_layout': '[n,c,h,w]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_5(self): + argv_source_layout = "name1(nhwc),name2([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': '[n,c,h,w]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_6(self): + argv_source_layout = "name1(nhwc),name2[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': '[n,c,h,w]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_scalar(self): + argv_source_layout = "name1(nhwc),name2([])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': None}, + 'name2': {'source_layout': '[]', 'target_layout': None}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_1(self): + argv_target_layout = "[n,h,w,c]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'': {'source_layout': None, 'target_layout': '[n,h,w,c]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_2(self): + argv_target_layout = "nhwc" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'': {'source_layout': None, 'target_layout': 'nhwc'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_3(self): + argv_target_layout = "name1(nhwc),name2(nchw)" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': None, 'target_layout': 'nhwc'}, + 'name2': {'source_layout': None, 'target_layout': 'nchw'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_4(self): + argv_target_layout = "name1([n,h,w,c]),name2([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': None, 'target_layout': '[n,h,w,c]'}, + 'name2': {'source_layout': None, 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_5(self): + argv_target_layout = "name1(nhwc),name2([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': None, 'target_layout': 'nhwc'}, + 'name2': {'source_layout': None, 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_6(self): + argv_target_layout = "name1(nhwc),name2[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': None, 'target_layout': 'nhwc'}, + 'name2': {'source_layout': None, 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_scalar(self): + argv_target_layout = "name1(nhwc),name2[]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': None, 'target_layout': 'nhwc'}, + 'name2': {'source_layout': None, 'target_layout': '[]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_1(self): + argv_source_layout = "[n,h,w,c]" + argv_target_layout = "[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_2(self): + argv_source_layout = "nhwc" + argv_target_layout = "nchw" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'': {'source_layout': 'nhwc', 'target_layout': 'nchw'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_3(self): + argv_source_layout = "name1(nhwc),name2(nhwc)" + argv_target_layout = "name1(nchw),name2(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': 'nchw'}, + 'name2': {'source_layout': 'nhwc', 'target_layout': 'nchw'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_4(self): + argv_source_layout = "name1([n,h,w,c]),name2([n,h,w,c])" + argv_target_layout = "name1([n,c,h,w]),name2([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}, + 'name2': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_5(self): + argv_source_layout = "name1(nhwc),name2[n,h,w,c]" + argv_target_layout = "name1(nchw),name2[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': 'nchw'}, + 'name2': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_6(self): + argv_source_layout = "name1.0:a/b(nhwc),name2\\d\\[n,h,w,c]" + argv_target_layout = "name1.0:a/b(nchw),name2\\d\\[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1.0:a/b': {'source_layout': 'nhwc', 'target_layout': 'nchw'}, + 'name2\\d\\': {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_7(self): + argv_source_layout = "name1-0[n,h,w,c],name2-1(?c??)" + argv_target_layout = "name1-0(nchw),name2-1[?,?,?,c]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1-0': {'source_layout': '[n,h,w,c]', 'target_layout': 'nchw'}, + 'name2-1': {'source_layout': '?c??', 'target_layout': '[?,?,?,c]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_scalar(self): + argv_source_layout = "name1(nhwc),name2[]" + argv_target_layout = "name1(nchw),name2[]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = {'name1': {'source_layout': 'nhwc', 'target_layout': 'nchw'}, + 'name2': {'source_layout': '[]', 'target_layout': '[]'}} + self.assertEqual(list(exp_res.keys()), list(result.keys())) + for i in exp_res.keys(): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_raises_if_layout_and_source_layout_provided(self): + argv_layout = "nhwc" + argv_source_layout = "nhwc" + with self.assertRaises(Error): + get_layout_values(argv_layout=argv_layout, argv_source_layout=argv_source_layout) + + def test_get_layout_raises_if_layout_and_target_layout_provided(self): + argv_layout = "nhwc->nchw" + argv_target_layout = "nchw" + with self.assertRaises(Error): + get_layout_values(argv_layout=argv_layout, argv_target_layout=argv_target_layout) + + def test_get_layout_raises_if_layout_with_source_and_target_layout_provided(self): + argv_layout = "nhwc->nchw" + argv_source_layout = "nhwc" + argv_target_layout = "nchw" + with self.assertRaises(Error): + get_layout_values(argv_layout=argv_layout, argv_source_layout=argv_source_layout, + argv_target_layout=argv_target_layout) + + def test_get_layout_raises_incorrect_format(self): + argv_layout = "name[n,h,w,c]->nchw" + with self.assertRaises(Error): + res = get_layout_values(argv_layout=argv_layout) + print(res) + + +class TestLayoutParsingEmptyNames(unittest.TestCase): + def test_get_layout_1(self): + argv_layout = "([n,h,w,c]),([n,h,w,c]->[n,c,h,w])" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_2(self): + argv_layout = "(nhwc),(nhwc->nchw)" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_3(self): + argv_layout = "(n...c),(n...c->nc...)" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'n...c', 'target_layout': None}, + {'source_layout': 'n...c', 'target_layout': 'nc...'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_scalar(self): + argv_layout = "(nhwc),([])" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_3(self): + argv_source_layout = "(nhwc),(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nchw', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_4(self): + argv_source_layout = "([n,h,w,c]),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_5(self): + argv_source_layout = "(nhwc),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_6(self): + argv_source_layout = "(nhwc),[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_scalar(self): + argv_source_layout = "(nhwc),([])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_3(self): + argv_target_layout = "(nhwc),(nchw)" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_4(self): + argv_target_layout = "([n,h,w,c]),([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': '[n,h,w,c]'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_5(self): + argv_target_layout = "(nhwc),([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_6(self): + argv_target_layout = "(nhwc),[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_scalar(self): + argv_target_layout = "(nhwc),[]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_3(self): + argv_source_layout = "(nhwc),(nhwc)" + argv_target_layout = "(nchw),(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_4(self): + argv_source_layout = "([n,h,w,c]),([n,h,w,c])" + argv_target_layout = "([n,c,h,w]),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_5(self): + argv_source_layout = "(nhwc),[n,h,w,c]" + argv_target_layout = "(nchw),[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_scalar(self): + argv_source_layout = "(nhwc),[]" + argv_target_layout = "(nchw),[]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[]', 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + +class TestLayoutParsingEmptyNamesNoBrackets(unittest.TestCase): + def test_get_layout_1(self): + argv_layout = "[n,h,w,c],[n,h,w,c]->[n,c,h,w]" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_2(self): + argv_layout = "nhwc,nhwc->nchw" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_3(self): + argv_layout = "n...c,n...c->nc..." + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'n...c', 'target_layout': None}, + {'source_layout': 'n...c', 'target_layout': 'nc...'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_scalar(self): + argv_layout = "nhwc,[]" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_3(self): + argv_source_layout = "nhwc,nchw" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nchw', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_4(self): + argv_source_layout = "[n,h,w,c],[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_5(self): + argv_source_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_6(self): + argv_source_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_scalar(self): + argv_source_layout = "nhwc,[]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_3(self): + argv_target_layout = "nhwc,nchw" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_4(self): + argv_target_layout = "[n,h,w,c],[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': '[n,h,w,c]'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_5(self): + argv_target_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_6(self): + argv_target_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_scalar(self): + argv_target_layout = "nhwc,[]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_3(self): + argv_source_layout = "nhwc,nhwc" + argv_target_layout = "nchw,nchw" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_4(self): + argv_source_layout = "[n,h,w,c],[n,h,w,c]" + argv_target_layout = "[n,c,h,w],[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_5(self): + argv_source_layout = "nhwc,[n,h,w,c]" + argv_target_layout = "nchw,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_scalar(self): + argv_source_layout = "nhwc,[]" + argv_target_layout = "nchw,[]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[]', 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def wrong_case_1(self): + argv_source_layout = "[n,h,w,c]),[n,h,w,c]" + argv_target_layout = "[n,c,h,w],[n,c,h,w]" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout)) + + def wrong_case_2(self): + argv_source_layout = "[nchv" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout)) + + def wrong_case_3(self): + argv_source_layout = "nchv->" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout)) + +class TestPackParamsToArgsNamespace(unittest.TestCase): + def test_mo_convert_params(self): + from openvino.frontend import ConversionExtension + args = {'input_model': os.path.dirname(__file__), + 'input_shape': [PartialShape([1,100,100,3]), [2,3]], + 'extensions': ConversionExtension("Ext", lambda x: x), + 'reverse_input_channels': True, + 'scale': 0.5, + 'input': ['name', InputCutInfo("a", [1,2,3], numpy.float32, [5, 6, 7])], + 'batch': 1, + 'output': ["a", "b", "c"], + 'mean_values': [0.5, 0.3], + 'scale_values': {"a": np.array([0.4]), "b": [0.5, 0.6]}, + 'source_layout': Layout("nchw"), + 'layout': {"a": LayoutMap("nchw","nhwc"), "b": "nc"}, + 'transform': ('LowLatency2', {'use_const_initializer': False})} + + cli_parser = get_all_cli_parser() + argv = pack_params_to_args_namespace(args, cli_parser) + + assert argv.input_model == args['input_model'] + assert argv.extensions == [args['extensions']] + assert argv.reverse_input_channels == args['reverse_input_channels'] + assert argv.scale == 0.5 + assert argv.batch == 1 + 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]" + assert argv.source_layout == "[N,C,H,W]" + assert argv.layout == "a(nchw->nhwc),b(nc)" + assert argv.transform == "LowLatency2[use_const_initializer=False]" + + 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_not_existing_dir(self): + args = {"input_model": "abc"} + cli_parser = get_all_cli_parser() + + with self.assertRaisesRegex(Error, "The \"abc\" is not existing file or directory"): + pack_params_to_args_namespace(args, cli_parser) + + def test_unknown_params(self): + args = {"input_model": os.path.dirname(__file__), + "a": "b"} + cli_parser = get_all_cli_parser() + + with self.assertRaisesRegex(Error, "Unrecognized argument: a"): + pack_params_to_args_namespace(args, cli_parser) + + +class TestConvertModelParamsParsing(unittest.TestCase): + def test_mo_convert_params_parsing(self): + ref_params = { + 'Optional parameters:': {'help', 'framework'}, + 'Framework-agnostic parameters:': {'input_model', 'input_shape', 'scale', 'reverse_input_channels', + 'log_level', 'input', 'output', 'mean_values', 'scale_values', 'source_layout', + 'target_layout', 'layout', 'compress_to_fp16', 'transform', 'extensions', + 'batch', 'silent', 'version', 'progress', 'stream_output', + 'transformations_config', 'example_input'}, + 'Caffe*-specific parameters:': {'input_proto', 'caffe_parser_path', 'k', 'disable_omitting_optional', + 'enable_flattening_nested_params'}, + 'TensorFlow*-specific parameters:': {'input_model_is_text', 'input_checkpoint', 'input_meta_graph', + 'saved_model_dir', 'saved_model_tags', + 'tensorflow_custom_operations_config_update', + 'tensorflow_object_detection_api_pipeline_config', + 'tensorboard_logdir', 'tensorflow_custom_layer_libraries'}, + 'MXNet-specific parameters:': {'input_symbol', 'nd_prefix_name', 'pretrained_model_name', 'save_params_from_nd', + 'legacy_mxnet_model', 'enable_ssd_gluoncv'}, + 'Kaldi-specific parameters:': {'counts', 'remove_output_softmax', 'remove_memory'}, + 'PaddlePaddle-specific parameters:': {'example_output'}, + } + + params = get_mo_convert_params() + for group_name in ref_params: + assert group_name in params + assert params[group_name].keys() == ref_params[group_name] + + cli_parser = get_all_cli_parser() + for group_name, params in ref_params.items(): + for param_name in params: + param_name = '--' + param_name + if group_name == 'PaddlePaddle-specific parameters:': + assert param_name not in cli_parser._option_string_actions + else: + assert param_name in cli_parser._option_string_actions + diff --git a/tools/mo/unit_tests/mo/utils/convert_impl_tmp_irs_cleanup_test_actual.py b/tools/mo/unit_tests/mo/utils/convert_impl_tmp_irs_cleanup_test_actual.py index f083cfa0ead..aec3e6d7fb3 100644 --- a/tools/mo/unit_tests/mo/utils/convert_impl_tmp_irs_cleanup_test_actual.py +++ b/tools/mo/unit_tests/mo/utils/convert_impl_tmp_irs_cleanup_test_actual.py @@ -22,7 +22,7 @@ class TestConvertImplTmpIrsCleanup(unittest.TestCase): return False def test_tmp_irs_cleanup_convert_impl_1(self): - with patch("openvino.tools.ovc.moc_frontend.offline_transformations.apply_offline_transformations") as emit_ir_func: + with patch("openvino.tools.mo.back.offline_transformations.apply_offline_transformations") as emit_ir_func: emit_ir_func.side_effect = Error('offline transformations step has failed') params = {'input_model': self.test_model_file, 'input_model_is_text': True, 'input': 'x[3],y[1 3]', diff --git a/tools/mo/unit_tests/mo/utils/error_test.py b/tools/mo/unit_tests/mo/utils/error_test.py new file mode 100644 index 00000000000..9ac2ffc2fa1 --- /dev/null +++ b/tools/mo/unit_tests/mo/utils/error_test.py @@ -0,0 +1,24 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import unittest + +from openvino.tools.mo.utils.error import classify_error_type + + +class TestingErrorClassifier(unittest.TestCase): + def test_no_module(self): + message = "No module named 'openvino._offline_transformations.offline_transformations_api'" + self.assertEqual(classify_error_type(message), message) + + def test_no_module_neg(self): + message = "No module 'openvino'" + self.assertEqual(classify_error_type(message), "undefined") + + def test_cannot_import_name(self): + message = "cannot import name 'IECore' from 'openvino.inference_engine' (unknown location)" + self.assertEqual(classify_error_type(message), "cannot import name 'IECore'") + + def test_cannot_import_name_neg(self): + message = "import name 'IECore' from 'openvino.inference_engine' (unknown location)" + self.assertEqual(classify_error_type(message), "undefined") diff --git a/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py b/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py index c01a815b42c..aa1f690f4a0 100644 --- a/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py +++ b/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py @@ -11,7 +11,7 @@ import openvino.runtime.opset10 as opset10 from openvino.runtime import Model, serialize, Core, PartialShape, Dimension from openvino.tools.mo.utils.ir_reader.restore_graph import restore_graph_from_ir, save_restored_graph -from openvino.tools.ovc.logger import init_logger +from openvino.tools.mo.utils.logger import init_logger # required to be in global area to run MO IR Reader init_logger('ERROR', False) diff --git a/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py b/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py index 9878aa5a588..5848e9be82b 100644 --- a/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py +++ b/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py @@ -112,7 +112,7 @@ class TestMoFallback(unittest.TestCase): os.remove(name) - @patch('openvino.tools.ovc.moc_frontend.analysis.json_model_analysis_print') + @patch('openvino.tools.mo.moc_frontend.analysis.json_model_analysis_print') def test_model(self, json_print): args = base_args_config() args.input_model = "test_model.onnx" @@ -132,7 +132,7 @@ class TestMoFallback(unittest.TestCase): "add_out": {"shape": "None", "data_type": "None", "value": "None"}}') - @patch('openvino.tools.ovc.moc_frontend.analysis.json_model_analysis_print') + @patch('openvino.tools.mo.moc_frontend.analysis.json_model_analysis_print') def test_model_with_dyn_shapes(self, json_print): args = base_args_config() args.input_model = "test_model_2.onnx" @@ -156,7 +156,7 @@ class TestMoFallback(unittest.TestCase): "add_out": {"shape": "None", "data_type": "None", "value": "None"}}') - @patch('openvino.tools.ovc.moc_frontend.analysis.json_model_analysis_print') + @patch('openvino.tools.mo.moc_frontend.analysis.json_model_analysis_print') def test_multi_outputs_model(self, json_print): args = base_args_config() args.input_model = "test_model_3.onnx" diff --git a/tools/mo/unit_tests/moc_tf_fe/check_info_messages_test.py b/tools/mo/unit_tests/moc_tf_fe/check_info_messages_test.py index cf93a78e20f..554740094d0 100644 --- a/tools/mo/unit_tests/moc_tf_fe/check_info_messages_test.py +++ b/tools/mo/unit_tests/moc_tf_fe/check_info_messages_test.py @@ -9,7 +9,8 @@ from contextlib import redirect_stdout from unittest.mock import patch from openvino.tools.mo.main import main -from openvino.tools.ovc.get_ov_update_message import get_tf_fe_message +from openvino.tools.mo.utils.get_ov_update_message import get_tf_fe_message, get_compression_message, \ + get_try_legacy_fe_message def arg_parse_helper(input_model, @@ -55,6 +56,36 @@ def arg_parse_helper(input_model, ) +class TestInfoMessagesTFFE(unittest.TestCase): + @patch('argparse.ArgumentParser.parse_args', + return_value=arg_parse_helper(input_model="model_int32.pbtxt", + use_legacy_frontend=False, use_new_frontend=True, + framework=None, input_model_is_text=True)) + def test_api20_only(self, mock_argparse): + f = io.StringIO() + with redirect_stdout(f): + main(argparse.ArgumentParser()) + std_out = f.getvalue() + tf_fe_message_found = get_tf_fe_message() in std_out + assert tf_fe_message_found + + @patch('openvino.tools.mo.convert_impl.driver', side_effect=Exception('MESSAGE')) + def run_fail_tf_fe(self, mock_driver): + from openvino.tools.mo import convert_model + path = os.path.dirname(__file__) + convert_model(os.path.join(path, "test_models", "model_int32.pbtxt"), silent=False) + + def test_suggest_legacy_fe(self): + f = io.StringIO() + with redirect_stdout(f): + try: + self.run_fail_tf_fe() + except: + pass + std_out = f.getvalue() + assert get_try_legacy_fe_message() in std_out + + class TestInfoMessagesTFFEWithFallback(unittest.TestCase): @patch('argparse.ArgumentParser.parse_args', return_value=arg_parse_helper(input_model="model_switch_merge.pbtxt", @@ -69,3 +100,17 @@ class TestInfoMessagesTFFEWithFallback(unittest.TestCase): tf_fe_message_found = get_tf_fe_message() in std_out assert not tf_fe_message_found, 'TF FE Info message is found for the fallback case' + +class TestInfoMessagesCompressFP16(unittest.TestCase): + @patch('argparse.ArgumentParser.parse_args', + return_value=arg_parse_helper(input_model="model_int32.pbtxt", + use_legacy_frontend=False, use_new_frontend=True, + compress_to_fp16=True, + framework=None, input_model_is_text=True)) + def test_compress_to_fp16(self, mock_argparse): + f = io.StringIO() + with redirect_stdout(f): + main(argparse.ArgumentParser()) + std_out = f.getvalue() + fp16_compression_message_found = get_compression_message() in std_out + assert fp16_compression_message_found diff --git a/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py b/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py index f697c9ffbc6..4ceb2fc3c15 100644 --- a/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py +++ b/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py @@ -40,10 +40,273 @@ class TestMoFreezePlaceholderTFFE(unittest.TestCase): assert values.dtype == dtype assert np.allclose(values, expected) + + @generate( + *[ + ( + "in1[1 4]->[1.0 2.0 3.0 4.0],in2[1 4]{f32}->[1.0 2.0 3.0 4.0]", + {}, + np.array([2.0, 4.0, 6.0, 8.0]), + np.float32, + ), + ( + "in2{f32}->[0.0 0.0 0.0 0.0]", + {"in1": np.array([[1.0, 2.0], [3.0, 4.0]])}, + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.float32, + ), + ( + "in2->[1.0 15.0 15.5 1.0]", + {"in1": np.array([[2.0, 4.0], [12.0, 8.0]])}, + np.array([[3.0, 19.0], [27.5, 9.0]]), + np.float32, + ), + ( + "in1[1 4]{i32}->[1 2 3 4],in2[1 4]{i32}->[1 2 3 4]", + {}, + np.array([2.0, 4.0, 6.0, 8.0]), + np.int32, + ), + ], + ) + def test_fp32(self, input_freezing_value, inputs, expected, + dtype): + self.basic("model_fp32.pbtxt", input_freezing_value, inputs, dtype, expected) + + @generate( + *[ + ( + "in1[1 4]->[1 2 3 4],in2[1 4]{i32}->[1 2 3 4]", + {}, + np.array([1, 4, 9, 16]), + np.int32, + ), + ( + "in2->[2 5 6 7 3 2]", + {"in1": np.array([[2, 4, 1], [1, 2, 8]])}, + np.array([[4, 20, 6], [7, 6, 16]]), + np.int32, + ), + ], + ) + def test_int32(self, input_freezing_value, inputs, expected, + dtype=None): + self.basic("model_int32.pbtxt", input_freezing_value, inputs, dtype, expected) + + @generate( + *[ + ( + "in1[2]->[True False],in2[2]->[True True]", + {}, + np.array([True, False], dtype=bool), + bool, + ), + ( + "in2[2,3]->[True,True,False,True,True,False]", + {"in1": np.array([[False, True, True], [False, True, True]], dtype=bool)}, + np.array([[False, True, False], [False, True, False]], dtype=bool), + bool, + ), + ( + "in2[]->True", + {"in1": np.array([[False, True, True], [False, True, True]], dtype=bool)}, + np.array([[False, True, True], [False, True, True]], dtype=bool), + bool, + ), + ], + ) + def test_bool(self, input_freezing_value, inputs, expected, + dtype=None): + self.basic("model_bool.pbtxt", input_freezing_value, inputs, dtype, expected) + + @generate( + *[ + ( + "in1[3]->[1 2 3],in2[3]->[4 5 6],cond->False", + {}, + np.array([4, 5, 6], dtype=np.float32), + np.float32, + None + ), + ( + None, + {"in1": np.array([2.0, 4.0, 6.0], dtype=np.float32), + "in2": np.array([1.0, 3.0, 5.0], dtype=np.float32)}, + np.array([2, 4, 6], dtype=np.float32), + np.float32, + "cond->False", + None, + True # fill a bug to investigate why compilation of this model is hang on + ), + # case: input_shape + freeze_placeholder_with_value + ( + None, + {"in2": np.array([1.0, 3.0, 5.0], dtype=np.float32)}, + np.array([2, 4, 6], dtype=np.float32), + np.float32, + "in1->[2.0 4.0 6.0],cond->True", + "[3]", + False + ), + ], + ) + def test_bool2(self, input_freezing_value, inputs, expected, + dtype=None, freeze_placeholder_with_value=None, input_shape=None, only_conversion=False): + self.basic("model_bool2.pbtxt", input_freezing_value, inputs, dtype, expected, freeze_placeholder_with_value, + input_shape, only_conversion) + + @generate( + *[ + ( + "add:0[3],z", + {"add:0": np.array([4, 5, 6], dtype=np.float32), "z": np.array([1, 2, 3], dtype=np.float32)}, + np.array([4, 10, 18], dtype=np.float32), + np.float32, + None + ), + ( + "add:0{i32}[3],z{i32}", + {"add:0": np.array([4, 5, 6], dtype=np.int32), "z": np.array([1, 2, 3], dtype=np.int32)}, + np.array([4, 10, 18], dtype=np.int32), + np.int32, + None + ), + ], + ) + def test_cutting_fp32(self, input_freezing_value, inputs, expected, + dtype=None, freeze_placeholder_with_value=None, input_shape=None, only_conversion=False): + self.basic("model_three_inputs.pbtxt", input_freezing_value, inputs, dtype, expected, + freeze_placeholder_with_value, + input_shape, only_conversion, True) + + @generate( + *[ + ( + "x[1,4],y[4]", + {"x": np.array([[3, 2, 1, 5]], dtype=np.int32), "y": np.array([0, -1, -7, 8], dtype=np.int32)}, + np.array([[3, 1, -6, 13]], dtype=np.int32), + np.int32, + None + ), + ( + "x,y", + {"x": np.array([[-3, 20, 1]], dtype=np.int32), "y": np.array([[10, -11, -17]], dtype=np.int32)}, + np.array([[7, 9, -16]], dtype=np.int32), + np.int32, + None + ), + ( + "x", + {"x": np.array([[-3, 20, 1]], dtype=np.int32)}, + np.array([[-2, 22, 4], [1, 25, 7]], dtype=np.int32), + np.int32, + None + ), + ], + ) + def test_placeholder_with_default(self, inputs, inputs_data, expected, + dtype=None, freeze_placeholder_with_value=None, input_shape=None, + only_conversion=False): + self.basic("placeholder_with_default.pbtxt", inputs, inputs_data, dtype, expected, + freeze_placeholder_with_value, + input_shape, only_conversion, True) + + @generate( + *[ + ( + "x[4],y->2.0", + {"x": np.array([3, 2, 1, 5], dtype=np.float32)}, + np.array([6, 4, 2, 10], dtype=np.float32), + np.float32, + None + ), + ( + "x[1],y->[2.0,3.0]", + {"x": np.array([3], dtype=np.float32)}, + np.array([6, 9], dtype=np.float32), + np.float32, + None + ), + ], + ) + def test_freeze_placeholder_with_unknown_rank(self, inputs, inputs_data, expected, + dtype=None, freeze_placeholder_with_value=None, input_shape=None, + only_conversion=False): + self.basic("mul_with_unknown_rank_y.pbtxt", inputs, inputs_data, dtype, expected, + freeze_placeholder_with_value, + input_shape, only_conversion, True) + def test_conversion_failure_fallback_default(self): self.basic("ctc_model_based.pbtxt", None, None, None, None, None, None, True, True, False, False) + def test_conversion_failure_fallback_use_new_frontend(self): + with self.assertRaisesRegex(Exception, + "\[TensorFlow Frontend\] Internal error, no translator found for operation\(s\)\: " + "Enter\, Exit\, LoopCond\, Merge\, NextIteration\, Switch\, TensorArrayGatherV3\, " + "TensorArraySizeV3\, TensorArrayV3"): + self.basic("ctc_model_based.pbtxt", None, None, None, None, + None, None, True, True, True, False) + + @unittest.skip("88349: Fix auto-pruning in legacy FE") + def test_conversion_model_oneshot_iterator_use_legacy_frontend(self): + self.basic("model_oneshot_iterator.pbtxt", None, None, None, None, + None, None, True, True, False, True) + + def test_conversion_model_oneshot_iterator_default(self): + self.basic("model_oneshot_iterator.pbtxt", None, None, None, None, + None, None, True, True, False, False) + + @generate( + *[ + ( + "in2{f32}->[0.0 0.0 0.0 0.0]", + {"in1": np.array([[1.0, 2.0], [3.0, 4.0]])}, + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.float32, + ), + ( + "in2->[1.0 15.0 15.5 1.0]", + {"in1": np.array([[2.0, 4.0], [12.0, 8.0]])}, + np.array([[3.0, 19.0], [27.5, 9.0]]), + np.float32, + ), + ], + ) + @unittest.skip("109220: Use generating script for this test model instead of Git LFS") + def test_conversion_model_with_non_standard_extension(self, input_freezing_value, inputs, expected, + dtype): + self.basic("model_fp32.frozen", input_freezing_value, inputs, dtype, expected, only_conversion=False, + input_model_is_text=False, use_new_frontend=True, + use_legacy_frontend=False) + + @unittest.skip("109220: Make TF FE to return the error") + def test_conversion_dir_model(self): + with self.assertRaisesRegex(Exception, + "Internal error or inconsistent input model: the frontend supports " + "only frozen binary protobuf format."): + self.basic(".", None, None, None, None, + only_conversion=True, input_model_is_text=False, use_new_frontend=True, + use_legacy_frontend=False) + + @generate( + *[ + ( + {"x": np.array([1, 2], dtype=np.int32), "y": np.array([4], dtype=np.int32)}, + np.array([-3, -2], dtype=np.int32), + np.int32, + ), + ( + {"x": np.array([20, 25], dtype=np.int32), "y": np.array([10], dtype=np.int32)}, + np.array([30, 35], dtype=np.int32), + np.int32, + ) + ], + ) + def test_conversion_pbtxt_model_with_inference(self, inputs, expected, dtype): + self.basic("model_with_if.pbtxt", None, inputs, dtype, expected, only_conversion=False, + input_model_is_text=False, use_new_frontend=True, use_legacy_frontend=False) + @generate( *[ # legacy frontend @@ -61,6 +324,21 @@ class TestMoFreezePlaceholderTFFE(unittest.TestCase): np.array([0, 0], dtype=np.int32), np.int32, False, True, ), + # new frontend + ( + "model_add_with_undefined_constant.pbtxt", + "x[2,3]", + {"x": np.array([[12, 13, 10], [11, 14, 16]], dtype=np.float32)}, + np.array([[12, 13, 10], [11, 14, 16]], dtype=np.float32), + np.float32, True, False, + ), + ( + "model_mul_with_undefined_constant.pbtxt", + "x[2]", + {"x": np.array([11, -12], dtype=np.int32)}, + np.array([0, 0], dtype=np.int32), + np.int32, True, False, + ), ], ) def test_conversion_model_with_undefined_constant(self, model_name, argv_input, inputs, expected, dtype, diff --git a/tools/mo/unit_tests/moc_tf_fe/conversion_incorrect_models_test.py b/tools/mo/unit_tests/moc_tf_fe/conversion_incorrect_models_test.py index e77b29eb87a..71299aea909 100644 --- a/tools/mo/unit_tests/moc_tf_fe/conversion_incorrect_models_test.py +++ b/tools/mo/unit_tests/moc_tf_fe/conversion_incorrect_models_test.py @@ -3,13 +3,42 @@ import tempfile import unittest - +import os from generator import generator, generate from openvino.tools.mo.convert import convert_model @generator class TestMoFreezePlaceholderTFFE(unittest.TestCase): + @generate( + *[ + # the default frontend + ( + False, False, None + ), + ( + False, False, "tf" + ), + # new frontend + ( + True, False, None + ), + ( + True, False, "tf" + ), + ], + ) + def test_conversion_fake_pb_model(self, use_new_frontend, use_legacy_frontend, framework): + with self.assertRaisesRegex(Exception, + "Internal error or inconsistent input model: the frontend supports frozen formats" + " \(.pb and .pbtxt\), SavedModel and MetaGraph \(.meta\), and v1 checkpoints."): + path = os.path.dirname(__file__) + input_model = os.path.join(path, "test_models", "fake.pb") + + convert_model(input_model, + use_new_frontend=use_new_frontend, use_legacy_frontend=use_legacy_frontend, + framework=framework) + @generate( *[ # the default frontend diff --git a/tools/mo/unit_tests/moc_tf_fe/conversion_with_checkpoint_v1_test.py b/tools/mo/unit_tests/moc_tf_fe/conversion_with_checkpoint_v1_test.py new file mode 100644 index 00000000000..4b7a37495cb --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/conversion_with_checkpoint_v1_test.py @@ -0,0 +1,42 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tempfile +import unittest + +import numpy as np + +from unit_tests.moc_tf_fe.utils import basic_check + + +class TestBasicConversion(unittest.TestCase): + def prepare_checkpoint_v1(self): + # quite old TensorFlow version can produce checkpoint v1 file + # so have hard coded bytestream corresponding to checkpoint v1 content + # this is a checkpoint v1 for Variable global_step with value = 14108582 of int64 type + buffer_checkpoint = [ + 0x00, 0x00, 0x1B, 0x0A, 0x19, 0x0A, 0x13, 0x0A, 0x0B, 0x67, 0x6C, 0x6F, + 0x62, 0x61, 0x6C, 0x5F, 0x73, 0x74, 0x65, 0x70, 0x12, 0x00, 0x18, 0x09, + 0x22, 0x00, 0x12, 0x02, 0x08, 0x01, 0x00, 0x0F, 0x19, 0x00, 0x67, 0x6C, + 0x6F, 0x62, 0x61, 0x6C, 0x5F, 0x73, 0x74, 0x65, 0x70, 0x00, 0x01, 0x00, + 0x12, 0x17, 0x0A, 0x0B, 0x67, 0x6C, 0x6F, 0x62, 0x61, 0x6C, 0x5F, 0x73, + 0x74, 0x65, 0x70, 0x12, 0x00, 0x1A, 0x06, 0x52, 0x04, 0xA6, 0x8F, 0xDD, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x62, 0x29, + 0x33, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, + 0xF2, 0xA1, 0xB0, 0x00, 0x01, 0x02, 0x01, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x13, 0xD9, 0x46, 0x56, 0x08, + 0x63, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x57, 0xFB, 0x80, 0x8B, 0x24, 0x75, 0x47, 0xDB] + return buffer_checkpoint + + def test_basic_checkpoint_v1(self): + ckpt_file = tempfile.NamedTemporaryFile(delete=False) + checkpoint_byte_stream = self.prepare_checkpoint_v1() + ckpt_file.write(bytes(checkpoint_byte_stream)) + ckpt_file.close() + basic_check(input_model="model_with_variable_v1.pbtxt", argv_input=None, + input_data={'input1': np.array([[1]], dtype=np.int64)}, + expected_dtype=np.int64, expected_value=np.array([[14108583]], dtype=np.int64), + use_new_frontend=True, use_legacy_frontend=False, input_checkpoint=ckpt_file.name) diff --git a/tools/mo/unit_tests/moc_tf_fe/conversion_with_layout_test.py b/tools/mo/unit_tests/moc_tf_fe/conversion_with_layout_test.py new file mode 100644 index 00000000000..0a3264e7fcf --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/conversion_with_layout_test.py @@ -0,0 +1,97 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import unittest + +import numpy as np +from generator import generator, generate + +import openvino.runtime.opset11 as opset11 +from openvino.runtime import Model +from openvino.runtime import PartialShape, Dimension +from openvino.tools.mo.convert import convert_model +from openvino.tools.mo.utils.error import Error + + +@generator +class TestConversionWithBatchAndLayout(unittest.TestCase): + def basic_check(self, model_name: str, batch: int, layout: str, refs_shapes: dict): + path = os.path.dirname(__file__) + input_model = os.path.join(path, "test_models", model_name) + ov_model = convert_model(input_model, batch=batch, layout=layout) + + for ov_input in ov_model.inputs: + input_name = ov_input.any_name + assert input_name in refs_shapes, "No reference input shape is found for {}".format(input_name) + input_shape = ov_input.get_partial_shape() + ref_shape = refs_shapes[input_name] + assert input_shape == ref_shape, "Incorrect shape for {} input:" \ + " expected shape - {}, actual shape - {}".format(input_name, ref_shape, + input_shape) + + @unittest.skip("Fix importing of openvino.test_utils in Jenkins") + def test_basic_model_no_layout(self): + from openvino.test_utils import compare_functions + path = os.path.dirname(__file__) + input_model = os.path.join(path, "test_models", "model_fp32.pbtxt") + ov_model = convert_model(input_model) + + # compare with the reference graph + param1 = opset11.parameter([2, 2], name="in1", dtype=np.float32) + param2 = opset11.parameter([2, 2], name="in2", dtype=np.float32) + add = opset11.add(param1, param2, name="add") + ref_model = Model(add, [param1, param2]) + flag, msg = compare_functions(ov_model, ref_model, compare_tensor_names=False) + assert flag, msg + + @generate( + *[ + ( + "model_fp32.pbtxt", 5, "in1(cn),in2(cn)", + {"in1": PartialShape([2, 5]), "in2": PartialShape([2, 5])}, + ), + ( + "model_fp32.pbtxt", 9, "in1(nc),in2(nc)", + {"in1": PartialShape([9, 2]), "in2": PartialShape([9, 2])}, + ), + ( + "model_fp32.pbtxt", 7, "in1(?c),in2(?c)", + {"in1": PartialShape([2, 2]), "in2": PartialShape([2, 2])}, + ), + ], + ) + def test_basic_model_with_layout(self, model_name: str, batch: int, layout: str, refs_shapes: dict): + self.basic_check(model_name, batch, layout, refs_shapes) + + @generate( + *[ + ( + "model_with_convolution_dynamic_rank.pbtxt", 7, "x(n???),kernel(????)", + {"x": PartialShape([7, Dimension.dynamic(), Dimension.dynamic(), Dimension.dynamic()]), + "kernel": PartialShape([2, 2, 3, 1])}, + ), + ( + "model_with_convolution_dynamic_rank.pbtxt", 3, "x(???n),kernel(??n?)", + {"x": PartialShape([Dimension.dynamic(), Dimension.dynamic(), Dimension.dynamic(), 3]), + "kernel": PartialShape([2, 2, 3, 1])}, + ), + ], + ) + def test_model_with_convolution_dynamic_rank(self, model_name: str, batch: int, layout: str, refs_shapes: dict): + self.basic_check(model_name, batch, layout, refs_shapes) + + @generate( + *[ + ( + "model_fp32.pbtxt", 17, "", + {}, + ), + ], + ) + def test_model_expected_failure(self, model_name: str, batch: int, layout: str, refs_shapes: dict): + # try to override batch size by default index (without specifying layout) + with self.assertRaisesRegex(Error, + "When you use -b \(--batch\) option, Model Optimizer applies its value to the first " + "element of the shape if it is equal to -1, 0 or 1\."): + self.basic_check(model_name, batch, layout, refs_shapes) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/fake.pb b/tools/mo/unit_tests/moc_tf_fe/test_models/fake.pb new file mode 100644 index 00000000000..ae05864994a --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/fake.pb @@ -0,0 +1,2 @@ +dcfsdcdsdcs +cscscsc \ No newline at end of file diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/future_op.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/future_op.pbtxt new file mode 100644 index 00000000000..d5fdcce4a90 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/future_op.pbtxt @@ -0,0 +1,58 @@ +node { + name: "in1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "in2" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "future_op" + op: "FutureOp" + input: "in1" + input: "in2" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_add_with_undefined_constant.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_add_with_undefined_constant.py new file mode 100644 index 00000000000..04de808dbff --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_add_with_undefined_constant.py @@ -0,0 +1,13 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +# Create the graph and model +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [2, 3], 'x') + const = tf.constant(value=[], dtype=tf.float32, shape=[3], name='Const') + tf.add(x, const, name="add") + tf.global_variables_initializer() + tf.io.write_graph(sess.graph, './', 'model_add_with_undefined_constant.pbtxt', as_text=True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.pbtxt new file mode 100644 index 00000000000..3078a20607e --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.pbtxt @@ -0,0 +1,52 @@ +node { + name: "in1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_BOOL + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "in2" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_BOOL + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "LogicalAnd" + op: "LogicalAnd" + input: "in1" + input: "in2" +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.py new file mode 100644 index 00000000000..3b1504298e1 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool.py @@ -0,0 +1,15 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.bool, [2, 3], 'in1') + y = tf.placeholder(tf.bool, [2, 3], 'in2') + tf.math.logical_and(x, y) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_bool.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.pbtxt new file mode 100644 index 00000000000..b0070f8f9bf --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.pbtxt @@ -0,0 +1,70 @@ +node { + name: "in1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 3 + } + } + } + } +} +node { + name: "in2" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 3 + } + } + } + } +} +node { + name: "cond" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_BOOL + } + } + attr { + key: "shape" + value { + shape { + } + } + } +} +node { + name: "Select" + op: "Select" + input: "cond" + input: "in1" + input: "in2" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.py new file mode 100644 index 00000000000..e75c56dfa4f --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_bool2.py @@ -0,0 +1,16 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [3], 'in1') + y = tf.placeholder(tf.float32, [3], 'in2') + cond = tf.placeholder(tf.bool, [], 'cond') + tf.where(cond, x, y) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_bool2.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.frozen b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.frozen new file mode 100644 index 00000000000..3343e4106f8 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.frozen @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a33c91148b5e72ca03608c7d2ee18229ee4b610344dadd6896efeb6ac7b93e0 +size 141 diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.pbtxt new file mode 100644 index 00000000000..57d5a6008dc --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.pbtxt @@ -0,0 +1,58 @@ +node { + name: "in1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 2 + } + } + } + } +} +node { + name: "in2" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 2 + } + } + } + } +} +node { + name: "add" + op: "AddV2" + input: "in1" + input: "in2" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.py new file mode 100644 index 00000000000..d10871514df --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_fp32.py @@ -0,0 +1,15 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [2, 2], 'in1') + y = tf.placeholder(tf.float32, [2, 2], 'in2') + tf.add(x, y, name="add") + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_fp32.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.pbtxt new file mode 100644 index 00000000000..acfcb5f5441 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.pbtxt @@ -0,0 +1,58 @@ +node { + name: "in1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "in2" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "add" + op: "Mul" + input: "in1" + input: "in2" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.py new file mode 100644 index 00000000000..21f68a2fa29 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_int32.py @@ -0,0 +1,15 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.int32, [2, 3], 'in1') + y = tf.placeholder(tf.int32, [2, 3], 'in2') + tf.multiply(x, y, name="add") + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_int32.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_mul_with_undefined_constant.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_mul_with_undefined_constant.py new file mode 100644 index 00000000000..8c0c6dd814d --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_mul_with_undefined_constant.py @@ -0,0 +1,13 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +# Create the graph and model +with tf.Session() as sess: + x = tf.placeholder(tf.int32, [2], 'x') + const = tf.constant(value=[], dtype=tf.int32, shape=[], name='Const') + tf.multiply(x, const, name="mul") + tf.global_variables_initializer() + tf.io.write_graph(sess.graph, './', 'model_mul_with_undefined_constant.pbtxt', as_text=True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_oneshot_iterator.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_oneshot_iterator.pbtxt new file mode 100644 index 00000000000..68278cb12f0 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_oneshot_iterator.pbtxt @@ -0,0 +1,129 @@ +node { + name: "OneShotIterator" + op: "OneShotIterator" + attr { + key: "container" + value { + s: "" + } + } + attr { + key: "dataset_factory" + value { + func { + name: "_make_dataset_Ap6cSkjEDjc" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + } + } + } + attr { + key: "shared_name" + value { + s: "" + } + } +} +node { + name: "IteratorGetNext" + op: "IteratorGetNext" + input: "OneShotIterator" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + } + } + } +} +node { + name: "Const_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 1 + } + dim { + size: 1 + } + dim { + size: 3 + } + } + tensor_content: "\232Y\367B\\\217\350B\\\017\317B" + } + } + } +} +node { + name: "sub" + op: "Sub" + input: "IteratorGetNext" + input: "Const_2" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.pbtxt new file mode 100644 index 00000000000..c5603bb633f --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.pbtxt @@ -0,0 +1,134 @@ +node { + name: "x" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "y" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "is_training" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_BOOL + } + } + attr { + key: "shape" + value { + shape { + } + } + } +} +node { + name: "Switch" + op: "Switch" + input: "x" + input: "is_training" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "Relu" + op: "Relu" + input: "Switch" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "Sigmoid" + op: "Sigmoid" + input: "Switch:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "Merge" + op: "Merge" + input: "Relu" + input: "Sigmoid" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "AddV2" + op: "AddV2" + input: "Merge" + input: "y" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "init" + op: "NoOp" +} +versions { + producer: 808 +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.py new file mode 100644 index 00000000000..663bbefef6c --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_switch_merge.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [2, 3], 'x') + y = tf.placeholder(tf.float32, [2, 3], 'y') + is_training = tf.placeholder(tf.bool, [], 'is_training') + switch = tf.raw_ops.Switch(data=x, pred=is_training) + relu = tf.raw_ops.Relu(features=switch[0]) + sigmoid = tf.raw_ops.Sigmoid(x=switch[1]) + merge = tf.raw_ops.Merge(inputs=[relu, sigmoid]) + tf.raw_ops.AddV2(x=merge[0], y=y) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_switch_merge.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.pbtxt new file mode 100644 index 00000000000..86399c2ffbe --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.pbtxt @@ -0,0 +1,84 @@ +node { + name: "x" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 3 + } + } + } + } +} +node { + name: "y" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 3 + } + } + } + } +} +node { + name: "z" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 3 + } + } + } + } +} +node { + name: "add" + op: "AddV2" + input: "x" + input: "y" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "multiply" + op: "Mul" + input: "add" + input: "z" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.py new file mode 100644 index 00000000000..4b03d99fc24 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_three_inputs.py @@ -0,0 +1,17 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [3], 'x') + y = tf.placeholder(tf.float32, [3], 'y') + z = tf.placeholder(tf.float32, [3], 'z') + add = tf.add(x, y, name="add") + tf.multiply(add, z, name="multiply") + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_three_inputs.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.pbtxt new file mode 100644 index 00000000000..366e3bf6ef7 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.pbtxt @@ -0,0 +1,124 @@ +node { + name: "x" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + unknown_rank: true + } + } + } +} +node { + name: "kernel" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + dim { + size: 2 + } + dim { + size: 3 + } + dim { + size: 1 + } + } + } + } +} +node { + name: "Conv2D" + op: "Conv2D" + input: "x" + input: "kernel" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "data_format" + value { + s: "NHWC" + } + } + attr { + key: "dilations" + value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + } + } + } + attr { + key: "explicit_paddings" + value { + list { + } + } + } + attr { + key: "padding" + value { + s: "SAME" + } + } + attr { + key: "strides" + value { + list { + i: 1 + i: 1 + i: 1 + i: 1 + } + } + } + attr { + key: "use_cudnn_on_gpu" + value { + b: true + } + } +} +node { + name: "Relu" + op: "Relu" + input: "Conv2D" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} +node { + name: "init" + op: "NoOp" +} +versions { + producer: 808 +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.py new file mode 100644 index 00000000000..f4666d45f94 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_convolution_dynamic_rank.py @@ -0,0 +1,18 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, None, 'x') + filter = tf.placeholder(tf.float32, [2, 2, 3, 1], 'kernel') + + conv2d = tf.raw_ops.Conv2D(input=x, filter=filter, strides=[1, 1, 1, 1], padding='SAME', + dilations=None) + relu = tf.raw_ops.Relu(features=conv2d) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_with_convolution_dynamic_rank.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.pbtxt new file mode 100644 index 00000000000..735ef4a32c8 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.pbtxt @@ -0,0 +1,320 @@ +node { + name: "x" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 2 + } + } + } + } +} +node { + name: "y" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 1 + } + } + } + } +} +node { + name: "Const" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 10 + } + } + } +} +node { + name: "Greater" + op: "Greater" + input: "x" + input: "Const" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} +node { + name: "Const_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } +} +node { + name: "All" + op: "All" + input: "Greater" + input: "Const_1" + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "keep_dims" + value { + b: false + } + } +} +node { + name: "If" + op: "If" + input: "All" + input: "x" + input: "y" + attr { + key: "Tcond" + value { + type: DT_BOOL + } + } + attr { + key: "Tin" + value { + list { + type: DT_INT32 + type: DT_INT32 + } + } + } + attr { + key: "Tout" + value { + list { + type: DT_INT32 + } + } + } + attr { + key: "else_branch" + value { + func { + name: "else_branch_func_Fw4jHLGozIk" + } + } + } + attr { + key: "output_shapes" + value { + list { + } + } + } + attr { + key: "then_branch" + value { + func { + name: "then_branch_func_mdn8Hcdd6RQ" + } + } + } +} +node { + name: "init" + op: "NoOp" +} +library { + function { + signature { + name: "then_branch_func_mdn8Hcdd6RQ" + input_arg { + name: "x" + type: DT_INT32 + } + input_arg { + name: "y" + type: DT_INT32 + } + output_arg { + name: "add" + type: DT_INT32 + } + } + node_def { + name: "add_0" + op: "AddV2" + input: "x" + input: "y" + attr { + key: "T" + value { + type: DT_INT32 + } + } + experimental_debug_info { + original_node_names: "add" + } + } + ret { + key: "add" + value: "add_0:z:0" + } + attr { + key: "_disable_call_shape_inference" + value { + b: true + } + } + arg_attr { + value { + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + } + } + arg_attr { + key: 1 + value { + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + } + } + } + function { + signature { + name: "else_branch_func_Fw4jHLGozIk" + input_arg { + name: "x" + type: DT_INT32 + } + input_arg { + name: "y" + type: DT_INT32 + } + output_arg { + name: "sub" + type: DT_INT32 + } + } + node_def { + name: "sub_0" + op: "Sub" + input: "x" + input: "y" + attr { + key: "T" + value { + type: DT_INT32 + } + } + experimental_debug_info { + original_node_names: "sub" + } + } + ret { + key: "sub" + value: "sub_0:z:0" + } + attr { + key: "_disable_call_shape_inference" + value { + b: true + } + } + arg_attr { + value { + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + } + } + arg_attr { + key: 1 + value { + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + } + } + } +} +versions { + producer: 808 + min_consumer: 12 +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.py b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.py new file mode 100644 index 00000000000..4a68b1810ea --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_if.py @@ -0,0 +1,32 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf +from tensorflow.python.framework import function + +tf.reset_default_graph() + + +@function.Defun(tf.int32, tf.int32) +def then_branch_func(x, y): + return x + y + + +@function.Defun(tf.int32, tf.int32) +def else_branch_func(x, y): + return x - y + + +with tf.Session() as sess: + x = tf.placeholder(tf.int32, [2], 'x') + y = tf.placeholder(tf.int32, [1], 'y') + const_cond = tf.constant(10, dtype=tf.int32) + greater = tf.raw_ops.Greater(x=x, y=const_cond) + axis = tf.constant([0], dtype=tf.int32) + cond = tf.raw_ops.All(input=greater, axis=axis) + if_op = tf.raw_ops.If(cond=cond, input=[x, y], Tout=[tf.int32], then_branch=then_branch_func, + else_branch=else_branch_func) + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'model_with_if.pbtxt', as_text=True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_variable_v1.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_variable_v1.pbtxt new file mode 100644 index 00000000000..4bd69c1afbe --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/model_with_variable_v1.pbtxt @@ -0,0 +1,53 @@ +node { + name: "input1" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 1 + } + dim { + size: 1 + } + } + } + } +} +node { + name: "global_step" + op: "Variable" + device: "/device:CPU:0" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "shape" + value { + shape { + } + } + } +} +node { + name: "add" + op: "Add" + input: "input1" + input: "global_step" + attr { + key: "T" + value { + type: DT_FLOAT + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/mul_with_unknown_rank_y.py b/tools/mo/unit_tests/moc_tf_fe/test_models/mul_with_unknown_rank_y.py new file mode 100644 index 00000000000..6497b556407 --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/mul_with_unknown_rank_y.py @@ -0,0 +1,15 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.float32, [3], 'x') + keep_prob = tf.placeholder(tf.float32, None, 'y') + tf.multiply(x, keep_prob) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'mul_with_unknown_rank_y.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.pbtxt b/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.pbtxt new file mode 100644 index 00000000000..c8d0deb4d5d --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.pbtxt @@ -0,0 +1,86 @@ +node { + name: "x" + op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: -1 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "Const" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 2 + } + dim { + size: 3 + } + } + tensor_content: "\001\000\000\000\002\000\000\000\003\000\000\000\004\000\000\000\005\000\000\000\006\000\000\000" + } + } + } +} +node { + name: "y" + op: "PlaceholderWithDefault" + input: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "shape" + value { + shape { + dim { + size: -1 + } + dim { + size: 3 + } + } + } + } +} +node { + name: "Add" + op: "AddV2" + input: "x" + input: "y" + attr { + key: "T" + value { + type: DT_INT32 + } + } +} diff --git a/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.py b/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.py new file mode 100644 index 00000000000..6ce4b49ba1d --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/test_models/placeholder_with_default.py @@ -0,0 +1,16 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import tensorflow.compat.v1 as tf + +tf.reset_default_graph() +with tf.Session() as sess: + x = tf.placeholder(tf.int32, [None, 3], 'x') + y = tf.placeholder_with_default(tf.constant([[1, 2, 3], [4, 5, 6]], dtype=tf.int32), + [None, 3], 'y') + tf.add(x, y) + + tf.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, './', 'placeholder_with_default.pbtxt', True) diff --git a/tools/mo/unit_tests/moc_tf_fe/utils.py b/tools/mo/unit_tests/moc_tf_fe/utils.py new file mode 100644 index 00000000000..4e73b1d3aae --- /dev/null +++ b/tools/mo/unit_tests/moc_tf_fe/utils.py @@ -0,0 +1,38 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os + +import numpy as np + +from openvino.runtime import Core +from openvino.tools.mo.convert import convert_model + + +def basic_check(input_model, argv_input, input_data, expected_dtype, expected_value, freeze_placeholder_with_value=None, + input_shape=None, only_conversion=False, input_model_is_text=True, use_new_frontend=True, + use_legacy_frontend=False, extensions=None, input_checkpoint=None): + path = os.path.dirname(__file__) + input_model = os.path.join(path, "test_models", input_model) + + ov_model = convert_model(input_model, input=argv_input, + freeze_placeholder_with_value=freeze_placeholder_with_value, + input_shape=input_shape, input_model_is_text=input_model_is_text, + use_new_frontend=use_new_frontend, use_legacy_frontend=use_legacy_frontend, + framework="tf", extensions=extensions, input_checkpoint=input_checkpoint) + + if only_conversion: + return ov_model + + ie = Core() + exec_net = ie.compile_model(ov_model, "CPU") + req = exec_net.create_infer_request() + results = req.infer(input_data) + values = list(results.values())[0] + if expected_dtype is not None: + assert values.dtype == expected_dtype + assert np.allclose(values, + expected_value), "Expected and actual values are different." \ + " Expected value: {}, actual value: {}".format(expected_value, values) + + return ov_model diff --git a/tools/ovc/.pylintdict b/tools/ovc/.pylintdict new file mode 100644 index 00000000000..637082afa1d --- /dev/null +++ b/tools/ovc/.pylintdict @@ -0,0 +1,68 @@ +attrs +arg +args +bfs +bool +caffe +caffemodel +ceil +chw +cli +cls +co-location +concat +config +const +conv +dfs +dict +dsu +eltwise +enum +env +eq +fallback +fc +fcn +fw +hardcoded +http +https +hwc +indices +inplace +inv +io +len +lrn +mul +n-ary +networkx +ndarray +nx +np +numpy +org +oi +params +pb +pbs +priorbox +priorboxes +proto +protobuf +protobufs +prototxt +regex +reorgyolo +regionyolo +replacers +resnet +rcnn +split +splitv +ssd +usr +undead +xml +www diff --git a/tools/ovc/.pylintrc b/tools/ovc/.pylintrc new file mode 100644 index 00000000000..0ef60c8749f --- /dev/null +++ b/tools/ovc/.pylintrc @@ -0,0 +1,410 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook='import os; import sys; sys.path.append(os.path.abspath(os.path.curdir))' + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=proto, tests, docs, automation + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns=.env3/*, python3.5, .*_test.py + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +# jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. This option is deprecated +# and it will be removed in Pylint 2.0. +optimize-ast=no + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=locally-disabled,too-few-public-methods,no-self-use,too-many-ancestors, + missing-docstring,old-style-class,consider-iterating-dictionary,consider-using-enumerate, + superfluous-parens,no-else-return,duplicate-code,wrong-import-order, + too-many-locals,logging-not-lazy,unnecessary-lambda,super-on-old-class,ungrouped-imports,too-many-format-args, + protected-access,too-many-statements,too-many-branches,too-many-return-statements,too-many-public-methods, + super-init-not-called,singleton-comparison,pointless-string-statement,broad-except, invalid-name + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". This option is deprecated +# and it will be removed in Pylint 2.0. +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=8 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=120 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict=en_US + +# List of comma separated words that should not be checked. +spelling-ignore-words=TF, MO, IR, IE, OVC + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file=.pylintdict + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=flask_sqlalchemy,app.extensions.flask_sqlalchemy,distutils,openvino,torch,paddle + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=fget,query,begin,add,merge,delete,commit,rollback + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_,log,api,a,c,d,e,ei,f,hp,id,l,l2,ml,mn,n,N,op,p,pb,pb,ph,q,rt,s,s1,s2,si,u,v,wp,x,y + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct argument names +argument-rgx=([a-z_][a-z0-9_]{2,40}$)|(fileName)|(pl)|(t) + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct function names +function-rgx=([a-z_][a-z0-9_]{2,40}$) + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=5 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=10 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=flask_restplus_patched + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/tools/ovc/openvino/__init__.py b/tools/ovc/openvino/__init__.py new file mode 100644 index 00000000000..1155c58b3b1 --- /dev/null +++ b/tools/ovc/openvino/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file diff --git a/tools/ovc/openvino/tools/__init__.py b/tools/ovc/openvino/tools/__init__.py new file mode 100644 index 00000000000..1155c58b3b1 --- /dev/null +++ b/tools/ovc/openvino/tools/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file diff --git a/tools/ovc/openvino/tools/ovc/__init__.py b/tools/ovc/openvino/tools/ovc/__init__.py index 5d5f94ac686..9292269a0a5 100644 --- a/tools/ovc/openvino/tools/ovc/__init__.py +++ b/tools/ovc/openvino/tools/ovc/__init__.py @@ -3,6 +3,7 @@ from openvino.tools.ovc.convert import convert_model, InputCutInfo, LayoutMap +# pylint: disable=no-name-in-module,import-error,no-member try: import openvino.runtime openvino.runtime.convert_model = convert_model diff --git a/tools/ovc/openvino/tools/ovc/cli_parser.py b/tools/ovc/openvino/tools/ovc/cli_parser.py index 86d0281b135..4326c523b8d 100644 --- a/tools/ovc/openvino/tools/ovc/cli_parser.py +++ b/tools/ovc/openvino/tools/ovc/cli_parser.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse import ast import logging as log @@ -19,7 +16,7 @@ import numbers import inspect import numpy as np -from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type +from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type # pylint: disable=no-name-in-module,import-error import openvino from openvino.tools.ovc.convert_data_type import destination_type_to_np_data_type @@ -150,12 +147,14 @@ def single_input_to_input_cut_info(input: [str, tuple, list, PartialShape, Type, if isinstance(input, str): # Parse params from string node_name, shape, value, data_type = parse_input_value(input) + # pylint: disable=no-member return openvino.runtime.InputCutInfo(node_name, PartialShape(shape) if shape is not None else None, data_type, value) - if isinstance(input, openvino.runtime.InputCutInfo): + if isinstance(input, openvino.runtime.InputCutInfo): # pylint: disable=no-member # Wrap input.shape to PartialShape if possible and wrap to InputCutInfo + # pylint: disable=no-member return openvino.runtime.InputCutInfo(input.name, PartialShape(input.shape) if input.shape is not None else None, input.type, @@ -186,13 +185,14 @@ def single_input_to_input_cut_info(input: [str, tuple, list, PartialShape, Type, else: raise Exception("Incorrect input parameters provided. Expected tuple with input name, " "input type or input shape. Got unknown object: {}".format(val)) + # pylint: disable=no-member return openvino.runtime.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.runtime.InputCutInfo(None, None, input, None) + return openvino.runtime.InputCutInfo(None, None, input, None) # pylint: disable=no-member # 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. @@ -216,11 +216,13 @@ def input_to_input_cut_info(input: [str, tuple, list]): # Parse string with parameters for single input node_name, shape, value, data_type = parse_input_value(input_value) + # pylint: disable=no-member inputs.append(openvino.runtime.InputCutInfo(node_name, PartialShape(shape) if shape is not None else None, data_type, value)) return inputs + # pylint: disable=no-member if isinstance(input, openvino.runtime.InputCutInfo): # Wrap to list and return return [input] @@ -272,10 +274,12 @@ def input_shape_to_input_cut_info(input_shape: [str, Shape, PartialShape, list, 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." + # pylint: disable=no-member inputs[idx] = openvino.runtime.InputCutInfo(inputs[idx].name, shape, inputs[idx].type, inputs[idx].value) else: for shape in input_shape: + # pylint: disable=no-member inputs.append(openvino.runtime.InputCutInfo(None, PartialShape(shape), None, None)) return @@ -378,7 +382,7 @@ def source_target_layout_to_str(value): def layoutmap_to_str(value): if isinstance(value, str): return value - if isinstance(value, openvino.runtime.LayoutMap): + if isinstance(value, openvino.runtime.LayoutMap): # pylint: disable=no-member assert value.source_layout is not None, "Incorrect layout map. 'source_layout' should be set." source_layout = layout_to_str(value.source_layout) if value.target_layout is not None: @@ -403,7 +407,7 @@ def layout_param_to_str(value): raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) values_str.append(op_name + "(" + layoutmap_to_str(layout) + ")") return ",".join(values_str) - if isinstance(value, openvino.runtime.LayoutMap): + if isinstance(value, openvino.runtime.LayoutMap): # pylint: disable=no-member return layoutmap_to_str(value) if isinstance(value, list) or isinstance(value, tuple): values_str = [] @@ -493,7 +497,7 @@ ParamDescription = namedtuple("ParamData", def get_mo_convert_params(): - mo_convert_docs = openvino.runtime.convert_model.__doc__ + mo_convert_docs = openvino.runtime.convert_model.__doc__ # pylint: disable=no-member mo_convert_params = {} group = "Optional parameters:" mo_convert_params[group] = {} @@ -787,7 +791,7 @@ def writable_dir(path: str): def add_args_by_description(args_group, params_description): - signature = inspect.signature(openvino.runtime.convert_model) + signature = inspect.signature(openvino.runtime.convert_model) # pylint: disable=no-member filepath_args = get_params_with_paths_list() cli_tool_specific_descriptions = get_convert_model_help_specifics() for param_name, param_description in params_description.items(): diff --git a/tools/ovc/openvino/tools/ovc/convert.py b/tools/ovc/openvino/tools/ovc/convert.py index 6d46d006e8a..6ef08c86d21 100644 --- a/tools/ovc/openvino/tools/ovc/convert.py +++ b/tools/ovc/openvino/tools/ovc/convert.py @@ -1,15 +1,12 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import os import pathlib from collections import namedtuple from typing import Any -from openvino.runtime import PartialShape, Shape, Layout, Model +from openvino.runtime import PartialShape, Shape, Layout, Model # pylint: disable=no-name-in-module,import-error from openvino.tools.ovc.convert_impl import _convert from openvino.tools.ovc.logger import get_logger_state, restore_logger_state diff --git a/tools/ovc/openvino/tools/ovc/convert_data_type.py b/tools/ovc/openvino/tools/ovc/convert_data_type.py index d34787c115b..8d5fc7784c7 100644 --- a/tools/ovc/openvino/tools/ovc/convert_data_type.py +++ b/tools/ovc/openvino/tools/ovc/convert_data_type.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import numpy as np from openvino.tools.ovc.error import Error diff --git a/tools/ovc/openvino/tools/ovc/convert_impl.py b/tools/ovc/openvino/tools/ovc/convert_impl.py index 2f8a63a2d53..46b898042b0 100644 --- a/tools/ovc/openvino/tools/ovc/convert_impl.py +++ b/tools/ovc/openvino/tools/ovc/convert_impl.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse import datetime import logging as log @@ -434,7 +431,7 @@ def driver(argv: argparse.Namespace, non_default_params: dict): graph, ngraph_function = prepare_ir(argv) legacy_path = False if graph is not None: - legacy_path_error() + legacy_path_error("") else: res_ngraph_function = moc_emit_ir(ngraph_function, argv) diff --git a/tools/ovc/openvino/tools/ovc/environment_setup_utils.py b/tools/ovc/openvino/tools/ovc/environment_setup_utils.py index 0406341587e..6a0f4cf0df7 100644 --- a/tools/ovc/openvino/tools/ovc/environment_setup_utils.py +++ b/tools/ovc/openvino/tools/ovc/environment_setup_utils.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import os import sys diff --git a/tools/ovc/openvino/tools/ovc/error.py b/tools/ovc/openvino/tools/ovc/error.py index f0af0545333..a58eb2202d4 100644 --- a/tools/ovc/openvino/tools/ovc/error.py +++ b/tools/ovc/openvino/tools/ovc/error.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import re class BasicError(Exception): diff --git a/tools/ovc/openvino/tools/ovc/get_ov_update_message.py b/tools/ovc/openvino/tools/ovc/get_ov_update_message.py index 899e93d3a0f..afb1ba9b482 100644 --- a/tools/ovc/openvino/tools/ovc/get_ov_update_message.py +++ b/tools/ovc/openvino/tools/ovc/get_ov_update_message.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import datetime msg_fmt = 'Check for a new version of Intel(R) Distribution of OpenVINO(TM) toolkit here {0} ' \ diff --git a/tools/ovc/openvino/tools/ovc/help.py b/tools/ovc/openvino/tools/ovc/help.py index 06b7b4aa264..3f65be41c0f 100644 --- a/tools/ovc/openvino/tools/ovc/help.py +++ b/tools/ovc/openvino/tools/ovc/help.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - def get_convert_model_help_specifics(): from openvino.tools.ovc.cli_parser import CanonicalizeTransformationPathCheckExistenceAction, \ CanonicalizePathCheckExistenceAction, CanonicalizeExtensionsPathCheckExistenceAction, \ diff --git a/tools/ovc/openvino/tools/ovc/logger.py b/tools/ovc/openvino/tools/ovc/logger.py index 8f914f1e32d..643d4d89191 100644 --- a/tools/ovc/openvino/tools/ovc/logger.py +++ b/tools/ovc/openvino/tools/ovc/logger.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import importlib.util import logging as log import os diff --git a/tools/ovc/openvino/tools/ovc/main.py b/tools/ovc/openvino/tools/ovc/main.py index c61f8b3b419..c345c05fd03 100644 --- a/tools/ovc/openvino/tools/ovc/main.py +++ b/tools/ovc/openvino/tools/ovc/main.py @@ -1,7 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -import argparse import os import sys @@ -10,7 +9,7 @@ try: except ImportError: import openvino.tools.ovc.telemetry_stub as tm from openvino.tools.ovc.convert_impl import _convert -from openvino.tools.ovc.version import VersionChecker +from openvino.tools.ovc.utils import get_ir_version # pylint: disable=no-name-in-module,import-error from openvino.runtime import serialize @@ -28,7 +27,7 @@ def main(): serialize(ngraph_function, model_path.encode('utf-8'), model_path.replace('.xml', '.bin').encode('utf-8')) - print('[ SUCCESS ] Generated IR version {} model.'.format(VersionChecker().get_ie_simplified_version())) + print('[ SUCCESS ] Generated IR version {} model.'.format(get_ir_version())) print('[ SUCCESS ] XML file: {}'.format(model_path)) print('[ SUCCESS ] BIN file: {}'.format(model_path.replace('.xml', '.bin'))) return 0 diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/__init__.py b/tools/ovc/openvino/tools/ovc/moc_frontend/__init__.py index f66616f1411..cddd115d397 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/__init__.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/__init__.py @@ -1,5 +1,2 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 - -# flake8: noqa -# mypy: ignore-errors diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/analysis.py b/tools/ovc/openvino/tools/ovc/moc_frontend/analysis.py index eed0d9a73b1..3d0bba92847 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/analysis.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/analysis.py @@ -1,12 +1,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import json from openvino.runtime import PartialShape, Model, Type # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.types import get_dtype +from openvino.runtime.utils.types import get_dtype # pylint: disable=no-name-in-module,import-error def json_model_analysis_dump(framework_model: Model): diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/check_config.py b/tools/ovc/openvino/tools/ovc/moc_frontend/check_config.py index 23b67ef815f..1905d768547 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/check_config.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/check_config.py @@ -1,9 +1,6 @@ # Copyright (C) 2022-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse from pathlib import Path diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/extractor.py b/tools/ovc/openvino/tools/ovc/moc_frontend/extractor.py index 9c2ff255b9c..81056b5d534 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/extractor.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/extractor.py @@ -1,14 +1,11 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import re from enum import Enum import numpy as np -from openvino._pyopenvino import Place, PartialShape +from openvino._pyopenvino import Place, PartialShape # pylint: disable=no-name-in-module,import-error from openvino.frontend import InputModel # pylint: disable=no-name-in-module,import-error from openvino.tools.ovc.error import Error @@ -386,7 +383,7 @@ def convert_params_lists_to_dicts(input_model, 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 + from openvino.runtime import PartialShape # pylint: disable=no-name-in-module,import-error model_inputs = input_model.get_inputs() input_user_data_types_dict = {} input_user_shapes_dict = {} @@ -405,7 +402,7 @@ def convert_params_lists_to_dicts(input_model, # input_user_data_types is list only if unnamed inputs were used if isinstance(input_user_data_types, list): - from openvino.runtime import Type + from openvino.runtime import Type # pylint: disable=no-name-in-module,import-error if input_user_shapes_dict is None: input_user_shapes_dict = {} diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/layout_utils.py b/tools/ovc/openvino/tools/ovc/moc_frontend/layout_utils.py index eb75ba02333..2b34307d377 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/layout_utils.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/layout_utils.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - from typing import Callable from openvino.runtime import PartialShape # pylint: disable=no-name-in-module,import-error diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/moc_emit_ir.py b/tools/ovc/openvino/tools/ovc/moc_frontend/moc_emit_ir.py index 8c00e758754..c716c5141ca 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/moc_emit_ir.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/moc_emit_ir.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse from openvino.runtime import Model # pylint: disable=no-name-in-module,import-error @@ -21,7 +18,7 @@ def moc_emit_ir(ngraph_function: Model, argv: argparse.Namespace): apply_moc_legacy_transformations, apply_fused_names_cleanup apply_moc_transformations(ngraph_function) - from openvino._offline_transformations import compress_quantize_weights_transformation + from openvino._offline_transformations import compress_quantize_weights_transformation # pylint: disable=no-name-in-module,import-error compress_quantize_weights_transformation(ngraph_function) if argv.framework == "onnx": diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/offline_transformations.py b/tools/ovc/openvino/tools/ovc/moc_frontend/offline_transformations.py index 366e01d4290..16749584bf9 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/offline_transformations.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/offline_transformations.py @@ -1,15 +1,12 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse from typing import List from openvino.tools.ovc.cli_parser import parse_transform from openvino.tools.ovc.error import Error -from openvino.runtime import Model +from openvino.runtime import Model # pylint: disable=no-name-in-module,import-error def get_new_placeholder_name(node_id: str, is_out_port: bool = False, port: int = 0): diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/paddle_frontend_utils.py b/tools/ovc/openvino/tools/ovc/moc_frontend/paddle_frontend_utils.py index c87d924e308..85ed861bccd 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/paddle_frontend_utils.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/paddle_frontend_utils.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import os import sys import tempfile @@ -52,7 +49,7 @@ class paddle_frontend_converter: self.pdiparams = "{}.pdiparams".format(self.model_name) self.pdiparams_info = "{}.pdiparams.info".format(self.model_name) - import paddle + import paddle # pylint: disable=import-error if isinstance(self.model, paddle.hapi.model.Model): self.model.save(self.model_name, False) else: diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/pipeline.py b/tools/ovc/openvino/tools/ovc/moc_frontend/pipeline.py index 453ba440297..4a7498e0fe9 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/pipeline.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/pipeline.py @@ -1,11 +1,7 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse -import io import logging as log import sys from copy import copy @@ -17,7 +13,7 @@ import os from openvino.frontend import FrontEnd, InputModel, NotImplementedFailure, \ Place # pylint: disable=no-name-in-module,import-error from openvino.runtime import PartialShape, Type # pylint: disable=no-name-in-module,import-error -from openvino.tools.ovc.types import get_element_type, \ +from openvino.runtime.utils.types import get_element_type, \ get_numpy_ctype # pylint: disable=no-name-in-module,import-error from openvino.tools.ovc.moc_frontend.analysis import json_model_analysis_dump from openvino.tools.ovc.moc_frontend.extractor import fe_user_data_repack, convert_params_lists_to_dicts, fe_output_user_data_repack diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/preprocessing.py b/tools/ovc/openvino/tools/ovc/moc_frontend/preprocessing.py index 03eb97c28f3..c42c5725d86 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/preprocessing.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/preprocessing.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse import logging as log from copy import copy diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/pytorch_frontend_utils.py b/tools/ovc/openvino/tools/ovc/moc_frontend/pytorch_frontend_utils.py index fd7c3ee7384..700cb454164 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/pytorch_frontend_utils.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/pytorch_frontend_utils.py @@ -1,17 +1,16 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import logging as log -import numpy as np -from openvino.tools.ovc.moc_frontend.shape_utils import get_static_shape -from openvino.tools.ovc.error import Error -from openvino.runtime import Tensor, Type, PartialShape -from openvino.tools.ovc.types import get_element_type_str -from openvino.tools.ovc.cli_parser import input_to_input_cut_info, input_shape_to_input_cut_info +import numpy as np +# pylint: disable=no-name-in-module,import-error +from openvino.runtime import Tensor, Type, PartialShape +from openvino.runtime.utils.types import get_element_type_str + +from openvino.tools.ovc.cli_parser import input_to_input_cut_info, input_shape_to_input_cut_info +from openvino.tools.ovc.error import Error +from openvino.tools.ovc.moc_frontend.shape_utils import get_static_shape def get_pytorch_decoder(model, input_shape, example_inputs, args): @@ -58,7 +57,7 @@ def get_value_from_list_or_dict(container, name, idx): def extract_input_info_from_example(args, inputs): try: - from openvino.frontend.pytorch.decoder import pt_to_ov_type_map + from openvino.frontend.pytorch.decoder import pt_to_ov_type_map # pylint: disable=no-name-in-module,import-error except Exception as e: log.error("PyTorch frontend loading failed") raise e @@ -115,9 +114,9 @@ def extract_input_info_from_example(args, inputs): args.input_list = input_names args.input = ",".join(input_names) - +# pylint: disable=no-member def to_torch_tensor(tensor): - import torch + import torch # pylint: disable=import-error if isinstance(tensor, torch.Tensor): return tensor if isinstance(tensor, np.ndarray): @@ -198,7 +197,7 @@ def prepare_torch_inputs(example_inputs, input_shape, input_info=None, allow_non break dtype = get_torch_dtype(inp.type) static_shape = get_static_shape(shape, dynamic_value=1) - input_tensor = torch.zeros(static_shape, dtype=dtype) + input_tensor = torch.zeros(static_shape, dtype=dtype) # pylint: disable=no-member if inp.name is not None: inputs_with_names[inp.name] = input_tensor inputs.append(input_tensor) diff --git a/tools/ovc/openvino/tools/ovc/moc_frontend/shape_utils.py b/tools/ovc/openvino/tools/ovc/moc_frontend/shape_utils.py index 9a7bb8db067..6fe29135ba3 100644 --- a/tools/ovc/openvino/tools/ovc/moc_frontend/shape_utils.py +++ b/tools/ovc/openvino/tools/ovc/moc_frontend/shape_utils.py @@ -1,11 +1,8 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import numpy as np -from openvino.runtime import PartialShape, Dimension +from openvino.runtime import PartialShape, Dimension # pylint: disable=no-name-in-module,import-error from openvino.tools.ovc.error import Error from openvino.tools.ovc.cli_parser import get_placeholder_shapes, split_shapes @@ -97,7 +94,7 @@ def parse_input_shapes(argv): else: try: import torch - if isinstance(shapes, torch.Size): + if isinstance(shapes, torch.Size): # pylint: disable=no-member return [shapes] except ImportError: raise Error("Unknown type of input shape {}.".format(type(shapes))) diff --git a/tools/ovc/openvino/tools/ovc/ovc.py b/tools/ovc/openvino/tools/ovc/ovc.py index 87b32da80e3..c28e15a5b56 100755 --- a/tools/ovc/openvino/tools/ovc/ovc.py +++ b/tools/ovc/openvino/tools/ovc/ovc.py @@ -1,8 +1,8 @@ -#!/usr/bin/env python3 - # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +#!/usr/bin/env python3 + import sys if __name__ == "__main__": diff --git a/tools/ovc/openvino/tools/ovc/telemetry_params.py b/tools/ovc/openvino/tools/ovc/telemetry_params.py index 09eee1491ba..7894a63b9f7 100644 --- a/tools/ovc/openvino/tools/ovc/telemetry_params.py +++ b/tools/ovc/openvino/tools/ovc/telemetry_params.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - telemetry_params = { 'TID': "UA-17808594-29" } diff --git a/tools/ovc/openvino/tools/ovc/telemetry_stub.py b/tools/ovc/openvino/tools/ovc/telemetry_stub.py index 0fccfa3beda..7f4551d9036 100644 --- a/tools/ovc/openvino/tools/ovc/telemetry_stub.py +++ b/tools/ovc/openvino/tools/ovc/telemetry_stub.py @@ -1,9 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - class Telemetry(object): """ Stab file for the Telemetry class which is used when Telemetry class is not available. diff --git a/tools/ovc/openvino/tools/ovc/telemetry_utils.py b/tools/ovc/openvino/tools/ovc/telemetry_utils.py index b88685c4c8a..dd39e2f273d 100644 --- a/tools/ovc/openvino/tools/ovc/telemetry_utils.py +++ b/tools/ovc/openvino/tools/ovc/telemetry_utils.py @@ -1,13 +1,10 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import argparse import numbers -from openvino.runtime import get_version as get_rt_version +from openvino.runtime import get_version as get_rt_version # pylint: disable=no-name-in-module,import-error from openvino.tools.ovc.cli_parser import get_params_with_paths_list from openvino.tools.ovc.telemetry_params import telemetry_params from openvino.tools.ovc.utils import check_values_equal @@ -48,7 +45,7 @@ def send_conversion_result(conversion_result: str, need_shutdown=False): def arg_to_str(arg): # This method converts to string only known types, otherwise returns string with name of the type - from openvino.runtime import PartialShape, Shape, Type, Layout + from openvino.runtime import PartialShape, Shape, Type, Layout # pylint: disable=no-name-in-module,import-error if isinstance(arg, (PartialShape, Shape, Type, Layout)): return str(arg) if isinstance(arg, (str, numbers.Number, bool)): diff --git a/tools/ovc/openvino/tools/ovc/types.py b/tools/ovc/openvino/tools/ovc/types.py deleted file mode 100644 index ec3e3436339..00000000000 --- a/tools/ovc/openvino/tools/ovc/types.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (C) 2018-2023 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -"""Functions related to converting between Python and numpy types and openvino types.""" - -import logging -from typing import List, Union - -import numpy as np - -from openvino.runtime.exceptions import OVTypeError -from openvino.runtime import Node, Shape, Output, Type -from openvino.runtime.op import Constant - -log = logging.getLogger(__name__) - -TensorShape = List[int] -NumericData = Union[int, float, np.ndarray] -NumericType = Union[type, np.dtype] -ScalarData = Union[int, float] -NodeInput = Union[Node, NumericData] - -openvino_to_numpy_types_map = [ - (Type.boolean, bool), - (Type.f16, np.float16), - (Type.f32, np.float32), - (Type.f64, np.float64), - (Type.i8, np.int8), - (Type.i16, np.int16), - (Type.i32, np.int32), - (Type.i64, np.int64), - (Type.u8, np.uint8), - (Type.u16, np.uint16), - (Type.u32, np.uint32), - (Type.u64, np.uint64), - (Type.bf16, np.uint16), -] - -openvino_to_numpy_types_str_map = [ - ("boolean", bool), - ("f16", np.float16), - ("f32", np.float32), - ("f64", np.float64), - ("i8", np.int8), - ("i16", np.int16), - ("i32", np.int32), - ("i64", np.int64), - ("u8", np.uint8), - ("u16", np.uint16), - ("u32", np.uint32), - ("u64", np.uint64), -] - - -def get_element_type(data_type: NumericType) -> Type: - """Return an ngraph element type for a Python type or numpy.dtype.""" - if data_type is int: - log.warning("Converting int type of undefined bitwidth to 32-bit ngraph integer.") - return Type.i32 - - if data_type is float: - log.warning("Converting float type of undefined bitwidth to 32-bit ngraph float.") - return Type.f32 - - ov_type = next( - (ov_type for (ov_type, np_type) in openvino_to_numpy_types_map if np_type == data_type), - None, - ) - if ov_type: - return ov_type - - raise OVTypeError("Unidentified data type %s", data_type) - - -def get_element_type_str(data_type: NumericType) -> str: - """Return an ngraph element type string representation for a Python type or numpy dtype.""" - if data_type is int: - log.warning("Converting int type of undefined bitwidth to 32-bit ngraph integer.") - return "i32" - - if data_type is float: - log.warning("Converting float type of undefined bitwidth to 32-bit ngraph float.") - return "f32" - - ov_type = next( - (ov_type for (ov_type, np_type) in openvino_to_numpy_types_str_map if np_type == data_type), - None, - ) - if ov_type: - return ov_type - - raise OVTypeError("Unidentified data type %s", data_type) - - -def get_dtype(openvino_type: Type) -> np.dtype: - """Return a numpy.dtype for an openvino element type.""" - np_type = next( - (np_type for (ov_type, np_type) in openvino_to_numpy_types_map if ov_type == openvino_type), - None, - ) - - if np_type: - return np.dtype(np_type) - - raise OVTypeError("Unidentified data type %s", openvino_type) - - -def get_numpy_ctype(openvino_type: Type) -> type: - """Return numpy ctype for an openvino element type.""" - np_type = next( - (np_type for (ov_type, np_type) in openvino_to_numpy_types_map if ov_type == openvino_type), - None, - ) - - if np_type: - return np_type - - raise OVTypeError("Unidentified data type %s", openvino_type) - - -def get_ndarray(data: NumericData) -> np.ndarray: - """Wrap data into a numpy ndarray.""" - if type(data) == np.ndarray: - return data # type: ignore - return np.array(data) - - -def get_shape(data: NumericData) -> TensorShape: - """Return a shape of NumericData.""" - if type(data) == np.ndarray: - return data.shape # type: ignore - elif type(data) == list: - return [len(data)] # type: ignore - return [] - - -def make_constant_node(value: NumericData, dtype: Union[NumericType, Type] = None) -> Constant: - """Return an openvino Constant node with the specified value.""" - ndarray = get_ndarray(value) - if dtype is not None: - element_type = get_element_type(dtype) if isinstance(dtype, (type, np.dtype)) else dtype - else: - element_type = get_element_type(ndarray.dtype) - - return Constant(element_type, Shape(ndarray.shape), ndarray.flatten().tolist()) - - -def as_node(input_value: NodeInput) -> Node: - """Return input values as nodes. Scalars will be converted to Constant nodes.""" - if issubclass(type(input_value), Node): - return input_value - if issubclass(type(input_value), Output): - return input_value - return make_constant_node(input_value) - - -def as_nodes(*input_values: NodeInput) -> List[Node]: - """Return input values as nodes. Scalars will be converted to Constant nodes.""" - return [as_node(input_value) for input_value in input_values] diff --git a/tools/ovc/openvino/tools/ovc/utils.py b/tools/ovc/openvino/tools/ovc/utils.py index c2b23c78691..19551e157a3 100644 --- a/tools/ovc/openvino/tools/ovc/utils.py +++ b/tools/ovc/openvino/tools/ovc/utils.py @@ -1,10 +1,6 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - - import os import re from typing import Iterable, Union @@ -137,3 +133,10 @@ def guess_framework_by_ext(input_model_path: str) -> int: return 'kaldi' elif re.match(r'^.*\.onnx$', input_model_path): return 'onnx' + +def get_ir_version(): + """ + Default IR version. + :return: the IR version + """ + return 11 \ No newline at end of file diff --git a/tools/ovc/openvino/tools/ovc/version.py b/tools/ovc/openvino/tools/ovc/version.py index 67fc31b82ec..ab2a5740518 100644 --- a/tools/ovc/openvino/tools/ovc/version.py +++ b/tools/ovc/openvino/tools/ovc/version.py @@ -1,12 +1,9 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# flake8: noqa -# mypy: ignore-errors - import re -from openvino.runtime import get_version as get_ie_version +from openvino.runtime import get_version as get_ie_version # pylint: disable=no-name-in-module,import-error def extract_release_version(version: str): @@ -32,7 +29,7 @@ def simplify_version(version: str): def get_simplified_ie_version(version=None): - from openvino.runtime import get_version + from openvino.runtime import get_version # pylint: disable=no-name-in-module,import-error if version is None: version = get_version() diff --git a/tools/ovc/unit_tests/ovc/package_BOM.txt b/tools/ovc/unit_tests/ovc/package_BOM.txt index 93885c3f9e0..5c8c73a2ac8 100644 --- a/tools/ovc/unit_tests/ovc/package_BOM.txt +++ b/tools/ovc/unit_tests/ovc/package_BOM.txt @@ -26,6 +26,5 @@ openvino/tools/ovc/ovc.py openvino/tools/ovc/telemetry_params.py openvino/tools/ovc/telemetry_stub.py openvino/tools/ovc/telemetry_utils.py -openvino/tools/ovc/types.py openvino/tools/ovc/utils.py openvino/tools/ovc/version.py \ No newline at end of file