[OV2.0] Model Optimizer: mean/scale/reverse_input_channels/layout for new frontends (#8751)

* Preprocessing API - base classes

Includes API definition for trivial mean/scale operations (which don't require layout)

Mean/scale with 'layout' support will be done under separate task together
 with Layout

Current test code coverage: 100%

* Python bindings for base preprocessing API

* remove pre_post_process directory from ngraph/core

* remove files from ngraph/python dir

* move pyngraph pre_post_process files from ngraph/python to runtime

* remove pre_post_process test from CMakeList

* move include to the header

* update include path for pre_post_process

* style fix

* bind InputTensorInfo::set_layout

* cleaned test_preprocess

* fix test expected output

* remove duplicate test

* update description of set_element_type

* fix style

* move preprocess from pyngraph to pyopenvino/graph

* update test_preprocess imports and remove unnecessary test

* remove duplicate import

* update custom method

* update test

* update test

* create decorator that changes Node into Output<Node>

* create function that cast Node to Output<Node>

* update test_preprocess to use decorator for custom function

* change _cast_to_output -> _from_node

* move frontend folder to pyopenvino

* rename includes and add compile options

* include frontend to pyopenvino

* move __init__.py

* move tests

* remove mock from tests_compatibility

* rename import module

* Fix code style cpp

* refactor a few lines

* style fix

* update few lines in mo

* add tests fro scale and mean with vector input

* style fix

* add docstring for custom_preprocess_function

* bind InputInfo network method

* style fix

* Add pyopenvino to dependencies

* bind OutputInfo

* fix description of preprocess submodule

* fix style

* update copyright year

* Fix mock

* update docstring

* bind OutputTensorInfo

* bind OutputNetworkInfo and InputNetworkInfo

* bind ColorFormat and ResizeAlgorithm

* clean imports

* fix typo

* add PostProcessSteps to init

* bind PreProcessSteps

* create additional tests

* Fix mo test

* remove module local

* fix code style

* update comment

* fix return type

* update docs

* fix code style

* change ngraph.Type to ov.Type

* fix typo

* move _from_node to node_output.hpp

* add read_model from buffer

* update imports

* add new line

* remove bad quotes

* update imports

* style fix

* add new line

* rename functin args

* remove Type import

* update tests

* style fix

* test clean

* remove blank line

* update PrePostProcessor init and build methods

* create test with model update tests with new PrePostProcessor init and build

* # Conflicts:
#	inference-engine/ie_bridges/python/src/openvino/offline_transformations/offline_transformations_api.pyx
#	inference-engine/ie_bridges/python/src/openvino/offline_transformations/offline_transformations_api_impl.cpp
#	inference-engine/ie_bridges/python/src/openvino/offline_transformations/offline_transformations_api_impl.hpp
#	inference-engine/ie_bridges/python/src/openvino/offline_transformations/offline_transformations_api_impl_defs.pxd
#	inference-engine/tests/ie_test_utils/common_test_utils/ngraph_test_utils.cpp
#	inference-engine/tests/ie_test_utils/common_test_utils/ngraph_test_utils.hpp
#	model-optimizer/mo/moc_frontend/serialize.py
#	thirdparty/gflags/gflags
#	thirdparty/gtest/gtest

* Stash

* move preprocess module from openvino.impl to openvino

* fix building

* fix code style

* try to move MO to use new api

* Intermediate commit

* try to move MO to use new api

* Test pybind11 custom holder for Preprocessing types (InputInfo and PreProcessingSteps)

* Initial code for source_target layout handling for preprocessing
Initial implementation of reverse input channels

* Use input's tensor names instead of friendly names

* Skeleton for guessing layouts and clearing it after preprocessing

* updated package_BOM.txt

* Use reference_wrapper for preprocess bindings

* Update tests

* Layout::find_permutation - support of dynamic layouts
Covered case for 'trivial convert' where no permutation is needed
It is needed for Model Optimizer for logic which will guess model's layout, like "?c??"

* Stash

* add bindings to I420_SINGLE_PLANE and I420_THREE_PLANES

* remove init from all classes except PrePostProcessor and add RGBX and BGRX to ColorFormat enum

* Guess layout so that existing mean/scale tests passed

* update test name

* Draft to guess layout for 'reverse_input_channels'

* More unit tests (error cases)

* pylint & flake8

* pylint - ignore import error

* Stash

* Moved preprocessing to 'back' folder

* More tests

* Update package_BOM

* Support layout_values with no names
Support layout set for 'outputs'
Tests

* Export more enum names from nrgaph

* Basic --layout parsing

* removed debug prints

* Further updates after rebase

* Update imports

* Removed part from 8829

* Fix imports in test code

* Minor cosmetics

* Don't guess 'C' if layout is already set by model
Expose 'Layout::empty' method

* Style fix

* Apply review comments
Restricted 'heuristics'

C++: Added 'fp16', 'fp64' support to mean/scale

* Applied review comments

* Added some dynamic test cases

* Move call of 'apply_preprocessing' to 'serialize.py'

* Unnecessary change

* Added more comments to code

Co-authored-by: pszmel <piotr.szmelczynski@intel.com>
Co-authored-by: Alexey Lebedev <alexey.lebedev@intel.com>
Co-authored-by: bszmelcz <bartosz.szmelczynski@intel.com>
Co-authored-by: Anastasia Kuporosova <anastasia.kuporosova@intel.com>
Co-authored-by: y <ilya.lavrenov@intel.com>
Co-authored-by: Vafin, Maxim <maxim.vafin@intel.com>
This commit is contained in:
Mikhail Nosov
2021-12-07 14:31:55 +03:00
committed by GitHub
co-authored by pszmel Alexey Lebedev bszmelcz Anastasia Kuporosova y Vafin, Maxim <maxim.vafin@intel.com>
parent 2c71cb4804
commit 6ef59ce3e4
9 changed files with 1061 additions and 6 deletions
@@ -807,6 +807,7 @@ mo/back/__init__.py
mo/back/ie_ir_ver_2/__init__.py
mo/back/ie_ir_ver_2/emitter.py
mo/back/offline_transformations.py
mo/back/preprocessing.py
mo/back/replacement.py
mo/front/__init__.py
mo/front/caffe/__init__.py
+401
View File
@@ -0,0 +1,401 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import argparse
import logging as log
from mo.utils.error import Error
from mo.utils.utils import refer_to_faq_msg
import numpy as np
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 Function, Layout, PartialShape, layout_helpers
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 = np.copy(mean_scale_val)
mean_scale_val = {}
for idx, node in enumerate(input_nodes):
names_list = list(node.get_tensor().get_names())
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())
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: Function, keys: list, 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
for name in 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: Function, 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: Function, 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: Function, 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:
if layout_item.get('target_layout'):
if check_suitable_for_reverse(Layout(layout_item['target_layout']), ov_input):
suitable_params.append(param_info)
elif 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 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\nAll inputs: {}\n'
'Suitable inputs {}'.format(len(all_params), len(suitable_params), all_params, suitable_params),
extra={'is_warning': True})
return suitable_params
def apply_preprocessing(ov_function: Function, 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 = 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 = argv.layout_values
if '' in layout_values:
if len(ov_function.inputs) > 1:
input_names = [list(ov_input.get_tensor().get_names())[0] for ov_input in ov_function.inputs]
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_values = {
list(ov_function.input().get_tensor().get_names())[0]: {
'source_layout': layout_values[''].get('source_layout'),
'target_layout': layout_values[''].get('target_layout')
}
}
check_keys_valid(ov_function=ov_function, keys=mean_scale_values.keys(), search_outputs=False)
check_keys_valid(ov_function=ov_function, keys=layout_values.keys(), 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).network().set_layout(Layout(layout_value['source_layout']))
else:
prep.output(node_name).network().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']))
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 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))
# 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()
@@ -5,6 +5,7 @@ import argparse
import os
from mo.pipeline.common import get_ir_version
from mo.back.ie_ir_ver_2.emitter import append_ir_info
from mo.back.preprocessing import apply_preprocessing
from mo.utils.cli_parser import get_meta_info, parse_transform
from openvino.runtime import Function # pylint: disable=no-name-in-module,import-error
@@ -13,6 +14,10 @@ from openvino.runtime import Function # pylint: disable=no-name-in-modul
def moc_emit_ir(ngraph_function: Function, argv: argparse.Namespace):
output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd()
# Apply preprocessing (mean/scale/reverse_channels/convert_layout/etc)
apply_preprocessing(ov_function=ngraph_function, argv=argv)
# Apply transformations
from mo.back.offline_transformations import apply_user_transformations, apply_moc_transformations
apply_user_transformations(ngraph_function, parse_transform(argv.transform))
apply_moc_transformations(ngraph_function)
@@ -0,0 +1,617 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import unittest
from argparse import Namespace
from mo.utils.error import Error
import numpy as np
try:
# pylint: disable=no-name-in-module,import-error
from mo.back.preprocessing import apply_preprocessing
# pylint: disable=no-name-in-module,import-error
import openvino.runtime.opset8 as ops
from openvino.runtime import Function, Layout, PartialShape
except Exception:
print("No OpenVINO API available,"
"ensure to set correct PYTHONPATH when running these tests")
raise
def create_function2(shape1=[2, 2], shape2=[2, 2], dtype1=np.float32, dtype2=np.float32):
input1 = ops.parameter(shape1, dtype=dtype1, name="input1")
input1.get_output_tensor(0).set_names({'input1', 'input1a'})
relu1 = ops.relu(input1)
res1 = ops.result(relu1, "res1")
res1.get_output_tensor(0).set_names({'res1', 'res1a'})
input2 = ops.parameter(shape2, dtype=dtype2, name="input2")
input2.get_output_tensor(0).set_names({'input2', 'input2a'})
relu2 = ops.relu(input2)
res2 = ops.result(relu2, "res2")
res2.get_output_tensor(0).set_names({'res2', 'res2a'})
function = Function(results=[res1, res2], parameters=[input1, input2], name="TestFunction")
return function
def create_function1(shape1=[2, 2]):
input1 = ops.parameter(shape1, dtype=np.float32, name="input1")
input1.get_output_tensor(0).set_names({'input1', 'input1a'})
relu1 = ops.relu(input1)
res1 = ops.result(relu1, "res1")
res1.get_output_tensor(0).set_names({'res1', 'res1a'})
function = Function(results=[res1], parameters=[input1], name="TestFunction")
return function
def process_function(ov_function: Function, argv: Namespace):
apply_preprocessing(ov_function=ov_function, argv=argv)
class TestPreprocessingMOC(unittest.TestCase):
def setUp(self):
pass
def check_scale_constant(self, node, expected, shape=None):
const_node = node.input(1).get_source_output().get_node()
self.assertEqual(const_node.get_type_name(), 'Constant')
if node.get_type_name() == 'Divide':
self.assertTrue(np.allclose(const_node.get_vector(), expected))
else:
self.assertTrue(np.allclose(const_node.get_vector(), 1. / expected))
if shape:
assert const_node.shape == PartialShape(shape)
def check_mean_constant(self, node, expected, shape=None):
const_node = node.input(1).get_source_output().get_node()
self.assertEqual(const_node.get_type_name(), 'Constant')
if node.get_type_name() == 'Subtract':
self.assertTrue(np.allclose(const_node.get_vector(), expected))
else:
self.assertTrue(np.allclose(const_node.get_vector(), -expected.toList()))
if shape:
self.assertEqual(const_node.shape, PartialShape(shape))
def test_scale_single_value(self):
argv = Namespace(mean_scale_values=None, scale=2.0)
function = create_function2()
process_function(ov_function=function, argv=argv)
for param in function.get_parameters():
op_node = list(param.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, [2.0])
def test_scale_single_value_fp64(self):
argv = Namespace(mean_scale_values=None, scale=2.0)
function = create_function2(dtype1=np.float64)
process_function(ov_function=function, argv=argv)
for ov_input in function.inputs:
op_node = list(ov_input.get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, [2.0])
def test_scale_single_value_fp16(self):
argv = Namespace(mean_scale_values=None, scale=2.0)
function = create_function2(dtype1=np.float16)
process_function(ov_function=function, argv=argv)
for ov_input in function.inputs:
op_node = list(ov_input.get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
def test_scale_vector(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([4.]), 'mean': None}}, scale=None)
function = create_function2()
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, [4.0], shape=None)
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
def test_scale_vector3(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([2., 4., 8.]), 'mean': None}}, scale=None)
function = create_function2(shape1=[1, 3, 224, 224])
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8.], shape=[1, 3, 1, 1])
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that guessed layout (?C??) is not appeared in input1
self.assertEqual(function.get_parameters()[0].layout, Layout())
def test_scale_vector4_layout(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([2., 4., 8., 9.]), 'mean': None}},
layout_values={'input1': {'source_layout': 'nhwc'}},
scale=None)
function = create_function2(shape1=[1, 3, 3, 4]) # Use layout to determine channels dim
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8., 9.], shape=[1, 1, 1, 4])
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that layout (NHWC) is appeared in input1
self.assertEqual(function.get_parameters()[0].layout, Layout('nhwc'))
def test_mean_single(self):
argv = Namespace(mean_scale_values={'input1': {'mean': np.array([4.]), 'scale': None}}, scale=None)
function = create_function2()
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, [4.0], shape=None)
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
def test_mean_single_fp64(self):
argv = Namespace(mean_scale_values={'input1': {'mean': np.array([4.]), 'scale': None}}, scale=None)
function = create_function2(dtype1=np.float64)
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, [4.0], shape=None)
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
def test_mean_single_fp16(self):
argv = Namespace(mean_scale_values={'input1': {'mean': np.array([4.]), 'scale': None}}, scale=None)
function = create_function2(dtype1=np.float16)
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
# Verify that input2 is not affected
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
def test_mean_vector3(self):
argv = Namespace(mean_scale_values={'input2': {'mean': np.array([2., 4., 8.]), 'scale': None}}, scale=None)
function = create_function2(shape2=[1, 3, 224, 224])
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[2., 4., 8.], shape=[1, 3, 1, 1])
# Verify that input1 is not affected
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that guessed layout (?C??) is not appeared in input2
self.assertEqual(function.get_parameters()[1].layout, Layout())
def test_mean_scale(self):
argv = Namespace(mean_scale_values={'input2a': {'mean': np.array([1., 2., 3.]),
'scale': np.array([2., 4., 8.])}},
scale=None)
function = create_function2(shape2=[1, 3, 224, 224])
process_function(ov_function=function, argv=argv)
# Verify that first is 'subtract mean', then 'scale'
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[1., 2., 3.], shape=[1, 3, 1, 1])
op_node = list(op_node.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8.], shape=[1, 3, 1, 1])
# Verify that input1 is not affected
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that guessed layout (?C??) is not appeared in input2
self.assertEqual(function.get_parameters()[1].layout, Layout())
def test_mean_scale_with_layout(self):
argv = Namespace(mean_scale_values={'input2a': {'mean': np.array([1., 2., 3., 4.]),
'scale': np.array([2., 4., 8., 9.])}},
scale=None)
function = create_function2(shape2=[1, 3, 3, 4])
function.get_parameters()[1].layout = Layout("NHWC")
process_function(ov_function=function, argv=argv)
# Verify that first is 'subtract mean', then 'scale'
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[1., 2., 3., 4.], shape=[1, 1, 1, 4])
op_node = list(op_node.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8., 9.], shape=[1, 1, 1, 4])
# Verify that input1 is not affected
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that layout presents in function after preprocessing
self.assertEqual(function.get_parameters()[1].layout, Layout("NHWC"))
def test_mean_scale_with_layout_dynamic(self):
argv = Namespace(mean_scale_values={'input2a': {'mean': np.array([1., 2., 3., 4.]),
'scale': np.array([2., 4., 8., 9.])}},
scale=None)
function = create_function2(shape2=[-1, -1, -1, -1])
function.get_parameters()[1].layout = Layout("NHWC")
process_function(ov_function=function, argv=argv)
# Verify that first is 'subtract mean', then 'scale'
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[1., 2., 3., 4.], shape=[1, 1, 1, 4])
op_node = list(op_node.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8., 9.], shape=[1, 1, 1, 4])
# Verify that input1 is not affected
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertEqual(op_node.get_type_name(), 'Relu')
# Verify that layout presents in function after preprocessing
self.assertEqual(function.get_parameters()[1].layout, Layout("NHWC"))
def test_no_param_name(self):
argv = Namespace(mean_scale_values=list(np.array([(np.array([1., 2., 3.]), np.array([2., 4., 6.])),
(np.array([7., 8., 9.]), None)],
dtype='object')), scale=None)
function = create_function2(shape1=[1, 3, 224, 224], shape2=[1, 224, 224, 3])
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[1., 2., 3.], shape=[1, 3, 1, 1])
op_node = list(op_node.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 6.], shape=[1, 3, 1, 1])
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[7., 8., 9.], shape=[1, 1, 1, 3])
# Verify that guessed layouts are not appeared in inputs
self.assertEqual(function.get_parameters()[0].layout, Layout())
self.assertEqual(function.get_parameters()[1].layout, Layout())
def test_no_param_name_single_value(self):
argv = Namespace(mean_scale_values=list(np.array([(np.array([1.]), None),
(np.array([2., 3., 4.]), np.array([5.]))],
dtype='object')), scale=None)
function = create_function2(shape1=[1, 3, 224, 224], shape2=[1, 224, 224, 3])
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[1.], shape=None)
op_node = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[2., 3., 4.], shape=[1, 1, 1, 3])
op_node = list(op_node.output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[5.], shape=None)
# Two inputs, but 'mean_scale_value' has only one array
def test_error_no_param_name_number_not_match(self):
argv = Namespace(mean_scale_values=[(np.array([2., 3.]), np.array([4.]))], scale=None)
function = create_function2(shape1=[1, 3, 224, 224], shape2=[1, 2, 224, 224])
with self.assertRaisesRegex(Error, '.*question.*61.*'):
process_function(ov_function=function, argv=argv)
def test_mean_scale_error_no_node_name_found(self):
argv = Namespace(mean_scale_values={'not_found': {'scale': np.array([1.]), 'mean': np.array([1.])}},
scale=None)
function = create_function2(shape1=[1, 3, 224, 224], shape2=[1, 2, 224, 224])
with self.assertRaisesRegex(Error, '.*question.*83.*'):
process_function(ov_function=function, argv=argv)
def test_layout_error_no_node_name_found(self):
argv = Namespace(layout_values={'not_found': {'source_layout': 'nhwc'}},
scale=None)
function = create_function2(shape1=[1, 3, 224, 224], shape2=[1, 2, 224, 224])
with self.assertRaisesRegex(Error, '.*question.*83.*'):
process_function(ov_function=function, argv=argv)
def test_error_dimension_mismatch(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3., 4.]), 'mean': None}},
scale=None)
function = create_function2(shape1=[1, 3, 224, 224])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_dimension_not_clear(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.]), 'mean': None}},
scale=None)
function = create_function2(shape1=[1, 3, 3, 3]) # Not clear to which 3 should scale be applied
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_dimension_mismatch_with_scale(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3., 4.]),
'mean': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[1, 3, 4, 224])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_guess_c_wrong_position_3d(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.]),
'mean': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[2, 3, 4])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_guess_c_wrong_position_4d(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.]),
'mean': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[1, 2, 3, 4])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_guess_c_wrong_position_5d(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.]),
'mean': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[1, 2, 3, 4, 5])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_guess_c_wrong_position_6d(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.]),
'mean': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[1, 2, 4, 5, 6, 3])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_2_names_to_same_input(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([1., 2., 3.])},
'input1a': {'scale': np.array([1., 2., 3.])}},
scale=None)
function = create_function2(shape1=[1, 3, 224, 224])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_error_2_names_to_same_input_single_value(self):
argv = Namespace(mean_scale_values={'input1': {'scale': np.array([2.])},
'input1a': {'scale': np.array([3.])}},
scale=None)
function = create_function2(shape1=[1, 3, 224, 224])
with self.assertRaises(Exception):
process_function(ov_function=function, argv=argv)
def test_reverse_input_channels(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 3, 224, 224])
process_function(ov_function=function,
argv=argv)
# Verify that some operations are inserted.
# In future, consider using mock PrePostProcessor to verify that 'reverse_channels' was called
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() != 'Relu')
# Verify that guessed layouts are not appeared in input1,input2
self.assertEqual(function.get_parameters()[0].layout, Layout())
self.assertEqual(function.get_parameters()[1].layout, Layout())
def test_reverse_input_channels_func_layout(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 3, 3, 3], shape2=[1, 3, 3, 3])
function.get_parameters()[0].layout = Layout("NCHW")
function.get_parameters()[1].layout = Layout("NHWC")
process_function(ov_function=function,
argv=argv)
# Verify that some operations are inserted.
# In future, consider using mock PrePostProcessor to verify that 'reverse_channels' was called
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() != 'Relu')
# Verify that guessed layouts are not appeared in input1,input2
self.assertEqual(function.get_parameters()[0].layout, Layout("NCHW"))
self.assertEqual(function.get_parameters()[1].layout, Layout("NHWC"))
def test_reverse_input_channels_layout(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None,
layout_values={'input1a': { 'source_layout': 'nhwc' },
'input2a': { 'source_layout': 'nchw' }
})
function = create_function2(shape1=[1, 224, 224, 4], shape2=[1, 4, 224, 224])
process_function(ov_function=function, argv=argv)
# In future, consider using mock PrePostProcessor to verify that 'reverse_channels' was not called
# Verify that reverse_channels are not applied.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_reverse_input_channels_3d(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None,
layout_values=None)
function = create_function2(shape1=[224, 224, 3], shape2=[3, 224, 224])
process_function(ov_function=function, argv=argv)
# Verify that reverse_channels are applied.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() != 'Relu')
def test_reverse_input_channels_6d(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None,
layout_values=None)
function = create_function2(shape1=[4, 4, 4, 4, 4, 3], shape2=[4, 3, 4, 4, 4, 4])
process_function(ov_function=function, argv=argv)
# Verify that reverse_channels are NOT applied.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_reverse_input_channels_dynamic(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None,
layout_values=None)
function = create_function2(shape1=[1, -1, 5, 5], shape2=[-1, -1, -1, -1])
process_function(ov_function=function, argv=argv)
# Verify that reverse_channels are NOT applied.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_reverse_input_channels_dynamic_layout(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None,
layout_values={'input1a': { 'source_layout': 'nchw' },
'input2a': { 'source_layout': 'nhwc' }
})
function = create_function2(shape1=[1, -1, 5, 5], shape2=[-1, -1, -1, -1])
process_function(ov_function=function, argv=argv)
# Verify that reverse_channels are applied.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() != 'Relu')
def test_reverse_input_channels_2_channels(self):
argv = Namespace(reverse_input_channels=True,
mean_scale_values=None,
scale=None)
function = create_function2(shape1=[1, 224, 224, 2], shape2=[1, 3, 224, 224])
process_function(ov_function=function, argv=argv)
# Verify that some operations are inserted to input2.
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() != 'Relu')
# Verify that guessed layouts are not appeared in input1,input2
self.assertEqual(function.get_parameters()[0].layout, Layout())
self.assertEqual(function.get_parameters()[1].layout, Layout())
# When input name for layout is empty for model with one input - it is applied to this input
def test_scale_vector3_layout_empty_input_name(self):
argv = Namespace(mean_scale_values=list(np.array([(None, np.array([2., 4., 8.]))],
dtype='object')),
layout_values={'': {'source_layout': 'nchw'}},
scale=None)
function = create_function1(shape1=[1, 3, 3, 3]) # Use layout to determine channels dim
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Divide' or op_node.get_type_name() == 'Multiply')
self.check_scale_constant(op_node, expected=[2., 4., 8.], shape=[1, 3, 1, 1])
# Verify that layout (nchw) is appeared in input1
self.assertEqual(function.get_parameters()[0].layout, Layout('nchw'))
def test_layout_output(self):
argv = Namespace(mean_scale_values=None,
layout_values={
'res1': {
'source_layout': 'nchw',
'target_layout': 'nhwc'
},
'res2a': {
'source_layout': 'ncdhw'
}
},
scale=None)
function = create_function2(shape1=[1, 3, 3, 3], shape2=[1, 3, 3, 3, 3])
process_function(ov_function=function, argv=argv)
op_node = function.get_results()[0].input(0).get_source_output().get_node()
self.assertEqual(op_node.get_type_name(), 'Transpose')
self.assertEqual(function.get_results()[0].layout, Layout('nhwc'))
self.assertEqual(function.get_results()[1].layout, Layout('ncdhw'))
def test_error_layout_empty_input_name_2_inputs(self):
argv = Namespace(mean_scale_values=None,
layout_values={'': {'source_layout': 'nchw'}},
scale=None)
function = create_function2(shape1=[1, 3, 3, 3])
# Verify user friendly error message contains number of inputs and their names
with self.assertRaisesRegex(Error, '.*2.*inputs.*input1.*input2.*'):
process_function(ov_function=function, argv=argv)
def test_reverse_channels_bad_layout(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 4, 224, 224])
function.get_parameters()[0].layout = Layout("NDHWC")
with self.assertRaisesRegex(Error, '.*input1.*'):
process_function(ov_function=function, argv=argv)
def test_guess_layout_reverse_channels_dont_apply_to_4(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 4, 224, 224])
process_function(ov_function=function, argv=argv)
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_error_guess_layout_reverse_channels_multi_3(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 3, 3, 224])
process_function(ov_function=function, argv=argv)
# Applied to only input1
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() != 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_no_guess_layout_reverse_channels_has_layout_no_c(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 3, 224, 224])
function.get_parameters()[0].layout = Layout("NHW?")
function.get_parameters()[1].layout = Layout("N?HW")
process_function(ov_function=function, argv=argv)
# Nothing has applied
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_guess_layout_reverse_channels_incorrect_pos(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[1, 4, 224, 224], shape2=[1, 224, 224, 2])
function.get_parameters()[0].layout = Layout("NCHW")
function.get_parameters()[1].layout = Layout("NHWC")
process_function(ov_function=function, argv=argv)
# Nothing has applied
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
def test_no_reverse_channels_even_with_layout(self):
argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)
function = create_function2(shape1=[3, 4, 224, 224], shape2=[1, 224, 3, 224])
process_function(ov_function=function, argv=argv)
# Nothing has applied
op_node0 = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node0.get_type_name() == 'Relu')
op_node1 = list(function.get_parameters()[1].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node1.get_type_name() == 'Relu')
@@ -43,6 +43,15 @@ def test_moc_extractor():
assert not status.returncode
def test_moc_preprocessing():
setup_env()
args = [sys.executable, '-m', 'pytest',
os.path.join(os.path.dirname(__file__), 'back/moc_preprocessing_test_actual.py'), '-s']
status = subprocess.run(args, env=os.environ)
assert not status.returncode
def test_main_test():
setup_env()
args = [sys.executable, '-m', 'pytest',
@@ -44,6 +44,7 @@ from openvino.runtime.impl import Function
from openvino.runtime.impl import Node
from openvino.runtime.impl import PartialShape
from openvino.runtime.impl import Layout
from openvino.runtime.impl import layout_helpers
from openvino.runtime.ie_api import Core
from openvino.runtime.ie_api import ExecutableNetwork
@@ -39,4 +39,5 @@ void regclass_graph_Layout(py::module m) {
layout.def("__str__", [](const ov::Layout& self) {
return self.to_string();
});
layout.def_property_readonly("empty", &ov::Layout::empty);
}
@@ -43,9 +43,15 @@ void PreStepsList::add_scale_impl(const std::vector<float>& values) {
if (values.size() == 1) {
shape = Shape{1};
} else {
shape = construct_mean_scale_shape(nodes[0].get_node_shared_ptr(), values.size(), context);
shape = construct_mean_scale_shape(nodes[0], values.size(), context);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
auto element_type = nodes[0].get_element_type();
OPENVINO_ASSERT(element_type.is_real(),
"Scale preprocessing can be applied to 'float' inputs. Consider using of "
"'convert_element_type' before scaling. Current type is: ",
element_type);
auto constant = op::v0::Constant::create(element_type, shape, values);
auto new_op = std::make_shared<op::v1::Divide>(nodes[0], constant);
return std::make_tuple(std::vector<Output<Node>>{new_op}, false);
@@ -66,7 +72,13 @@ void PreStepsList::add_mean_impl(const std::vector<float>& values) {
} else {
shape = construct_mean_scale_shape(nodes[0], values.size(), context);
}
auto constant = op::v0::Constant::create(element::f32, shape, values);
auto element_type = nodes[0].get_element_type();
OPENVINO_ASSERT(element_type.is_real(),
"Mean preprocessing can be applied to 'float' inputs. Consider using of 'convert_element_type' "
"before scaling. Current type is: ",
element_type);
auto constant = op::v0::Constant::create(element_type, shape, values);
auto new_op = std::make_shared<op::v1::Subtract>(nodes[0], constant);
return std::make_tuple(std::vector<Output<Node>>{new_op}, false);
+11 -3
View File
@@ -52,12 +52,20 @@ TEST(pre_post_process, simple_mean_scale) {
EXPECT_EQ(f->get_output_element_type(0), element::f32);
}
TEST(pre_post_process, simple_mean_scale_getters) {
auto f = create_simple_function(element::f32, Shape{1, 3, 2, 2});
TEST(pre_post_process, simple_mean_scale_getters_f16) {
auto f = create_simple_function(element::f16, Shape{1, 3, 2, 2});
auto p = PrePostProcessor(f);
p.input("tensor_input1").preprocess().mean(1).scale(2);
f = p.build();
EXPECT_EQ(f->get_output_element_type(0), element::f32);
EXPECT_EQ(f->get_output_element_type(0), element::f16);
}
TEST(pre_post_process, simple_mean_scale_getters_f64) {
auto f = create_simple_function(element::f64, Shape{1, 3, 2, 2});
auto p = PrePostProcessor(f);
p.input("tensor_input1").preprocess().mean(1).scale(2);
f = p.build();
EXPECT_EQ(f->get_output_element_type(0), element::f64);
}
TEST(pre_post_process, convert_element_type_and_scale) {