337 lines
13 KiB
Python
337 lines
13 KiB
Python
# Copyright (C) 2018-2021 Intel Corporation
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import importlib
|
|
import logging as log
|
|
import os
|
|
import sys
|
|
|
|
import mmap
|
|
import numpy as np
|
|
from google.protobuf import text_format
|
|
from google.protobuf.internal import api_implementation
|
|
|
|
from mo.front.extractor import add_outputs_identity
|
|
from mo.graph.graph import Graph
|
|
from mo.utils.error import Error, FrameworkError
|
|
from mo.utils.utils import refer_to_faq_msg
|
|
|
|
|
|
def import_caffe_pb2(caffe_parser_path: str):
|
|
# import caffe_pb2
|
|
sys.path.insert(0, caffe_parser_path)
|
|
caffe_pb2 = importlib.import_module("caffe_pb2")
|
|
sys.path.pop(0)
|
|
|
|
return caffe_pb2
|
|
|
|
|
|
def parse_mean(file_path: str, in_shape: np.ndarray, mean_file_offsets: [tuple, None], caffe_pb2):
|
|
blob = caffe_pb2.BlobProto()
|
|
with open(file_path, 'rb') as file:
|
|
data = file.read()
|
|
|
|
if not data:
|
|
raise Error('Mean file "{}" is empty.' + refer_to_faq_msg(5),
|
|
file_path)
|
|
|
|
try:
|
|
blob.ParseFromString(data)
|
|
data = np.array(blob.data) # pylint: disable=no-member
|
|
|
|
if blob.HasField('channels') or blob.HasField('height') or blob.HasField('width'):
|
|
data = data.reshape(blob.channels, blob.height, blob.width) # pylint: disable=no-member
|
|
else:
|
|
data = data.reshape(blob.shape.dim) # pylint: disable=no-member
|
|
# crop mean image according to input size
|
|
if in_shape[2] > data.shape[1] or in_shape[3] > data.shape[2]:
|
|
raise Error(
|
|
'Input image of shape {} is larger than mean image {} from file "{}". ' +
|
|
refer_to_faq_msg(4),
|
|
in_shape,
|
|
data.shape,
|
|
file_path
|
|
)
|
|
|
|
if mean_file_offsets is not None and len(mean_file_offsets) == 2:
|
|
offset_x = mean_file_offsets[0]
|
|
offset_y = mean_file_offsets[1]
|
|
else:
|
|
offset_x = int((data.shape[1] - in_shape[2]) / 2)
|
|
offset_y = int((data.shape[2] - in_shape[3]) / 2)
|
|
|
|
mean = []
|
|
for i in range(in_shape[1]):
|
|
data_channel = np.zeros(in_shape[2] * in_shape[3], dtype=np.float32)
|
|
for x in range(in_shape[2]):
|
|
for y in range(in_shape[3]):
|
|
data_channel[x * in_shape[3] + y] = data[i, x + offset_x, y + offset_y]
|
|
mean.append(data_channel)
|
|
|
|
return mean
|
|
|
|
except Exception as err:
|
|
raise Error(
|
|
'While processing mean file "{}": {}. Probably mean file has incorrect format. ' +
|
|
refer_to_faq_msg(6),
|
|
file_path,
|
|
str(err)) from err
|
|
|
|
|
|
def load_caffe_proto_model(caffe_pb2, proto_path: str, model_path: [str, None] = None):
|
|
# 1. python protobuf is used
|
|
if api_implementation._implementation_type == 'python':
|
|
message = 'Please expect that Model Optimizer conversion might be slow. ' \
|
|
'You are currently using Python protobuf library implementation. \n'
|
|
try:
|
|
from google.protobuf.pyext import cpp_message
|
|
# Check os windows and env variable PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION
|
|
if os.name == 'nt' and os.environ.get('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', default='') != 'cpp':
|
|
# 2. cpp implementation is available but not used
|
|
message += 'However, cpp implementation is available, you can boost ' \
|
|
'model conversion by setting PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION env variable to cpp. \n' \
|
|
'Run: set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp \n'
|
|
except ImportError:
|
|
# 3. cpp implementation is not available
|
|
message += 'However you can use the C++ protobuf implementation that is supplied with the OpenVINO toolkit' \
|
|
'or build protobuf library from sources. \n' \
|
|
'Navigate to "install_prerequisites" folder and run: ' \
|
|
'python -m easy_install protobuf-3.5.1-py($your_python_version)-win-amd64.egg \n' \
|
|
'set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp'
|
|
print(message + '\n\n' + refer_to_faq_msg(80))
|
|
|
|
# Read proto layers
|
|
try:
|
|
proto = caffe_pb2.NetParameter()
|
|
with open(proto_path, "r") as file:
|
|
text_format.Merge(str(file.read()), proto)
|
|
except Exception as e:
|
|
log.error('Exception message: {}\n\n'.format(e) +
|
|
' Possible reasons:\n' +
|
|
' 1. {} does not exist\n'.format(proto_path) +
|
|
' 2. {} does not have a valid structure, for example, it was downloaded as html\n'.format(
|
|
proto_path) +
|
|
' 3. {} contains custom layers or attributes that are not supported\n'.format(proto_path) +
|
|
' in Model Optimizer by default.\n\n' +
|
|
' After you made sure that {} has a valid structure and still see this issue, then\n'.format(
|
|
proto_path) +
|
|
' you need to generate a python parser for caffe.proto that was used when the model\n' +
|
|
' was created.\n' +
|
|
' Run "python3 generate_caffe_pb2.py --input_proto ${PATH_TO_CAFFE}/src/caffe/proto/caffe.proto"' +
|
|
refer_to_faq_msg(1) + '\n\n', extra={'framework_error': True})
|
|
raise FrameworkError('Model Optimizer is not able to parse {}'.format(proto_path)) from e
|
|
|
|
# Read model layer if exists
|
|
model = None
|
|
try:
|
|
if model_path:
|
|
model = caffe_pb2.NetParameter()
|
|
with open(model_path, "rb") as infile:
|
|
map = mmap.mmap(infile.fileno(), 0, access=mmap.ACCESS_READ)
|
|
model.MergeFromString(map)
|
|
except Exception as e:
|
|
third_point = ''
|
|
if api_implementation._implementation_type == 'python':
|
|
third_point = ' 3. Python protobuf implementation was used. Some models can\'t be converted ' + \
|
|
' in this configuration. Please, use Python version with existing cpp implementation of ' + \
|
|
'protobuf library or build it by yourself\n' + refer_to_faq_msg(103)
|
|
log.error('Exception message: {}\n\n'.format(e) +
|
|
' Possible reasons:\n' +
|
|
' 1. {} does not exist\n'.format(model_path) +
|
|
' 2. {} does not have a valid structure\n'.format(model_path) + third_point,
|
|
extra={'framework_error': True})
|
|
raise FrameworkError('Model Optimizer is not able to parse {}'.format(model_path)) from e
|
|
|
|
return proto, model
|
|
|
|
|
|
def get_layers(proto):
|
|
if len(proto.layer):
|
|
return proto.layer
|
|
elif len(proto.layers):
|
|
return proto.layers
|
|
else:
|
|
raise Error('Invalid proto file: there is neither "layer" nor "layers" top-level messages. ' +
|
|
refer_to_faq_msg(7))
|
|
|
|
|
|
def caffe_pb_to_nx(graph, proto, model):
|
|
"""
|
|
Converts proto/model layers to a graph. Edges are restored by bottom/top attributes.
|
|
Graph nodes has two attributes: pb for prototxt definition and model_pb for caffemodel definition.
|
|
|
|
Parameters
|
|
----------
|
|
proto : NetParameter
|
|
Protobuf message for NetParameter, representing .prototxt.
|
|
model : NetParameter
|
|
Protobuf message for NetParameter, representing .caffemodel.
|
|
|
|
Returns
|
|
----------
|
|
Graph
|
|
built NX Directed graph.
|
|
"""
|
|
# Blobs in prototxt model can be reused by inplace layer.
|
|
# This requires loading of pb layers in order and tracking the latest
|
|
# layer that writes a particular blob.
|
|
blob_producers = {} # maps layer blob name to node id in graph, port and layer name
|
|
proto_layers = get_layers(proto)
|
|
model_layers = None
|
|
if model:
|
|
model_layers = get_layers(model)
|
|
|
|
input_dims = []
|
|
input_names = []
|
|
if len(proto.input_dim) > 0 and len(list(proto.input)) > 1:
|
|
# example of proto input
|
|
# input: "data"
|
|
# input_dim: 1
|
|
# input_dim: 3
|
|
# input_dim: 500
|
|
# input_dim: 500
|
|
# input: "info"
|
|
# input_dim: 1
|
|
# input_dim: 3
|
|
raise Error('Old-style inputs (via "input_dims") are not supported. ' +
|
|
'Please specify inputs via "input_shape". ' +
|
|
refer_to_faq_msg(8))
|
|
elif len(list(proto.input)) == 1 and len(list(proto.input_dim)):
|
|
# example of proto input
|
|
# input: "data"
|
|
# input_dim: 1
|
|
# input_dim: 3
|
|
# input_dim: 500
|
|
# input_dim: 500
|
|
input_dims = [np.array(list(proto.input_dim), dtype=np.int64)]
|
|
input_names = [proto.input[0]]
|
|
|
|
elif len(list(proto.input)) == 1 and len(list(proto.input_shape)):
|
|
# example of proto input
|
|
# input: "data"
|
|
# input_shape
|
|
# {
|
|
# dim: 1
|
|
# dim: 3
|
|
# dim: 227
|
|
# dim: 227
|
|
# }
|
|
input_dims = [np.array(proto.input_shape[0].dim, dtype=np.int64)]
|
|
input_names = [proto.input[0]]
|
|
|
|
elif len(proto.input_shape) > 0:
|
|
# example of proto input
|
|
# input: "data"
|
|
# input_shape
|
|
# {
|
|
# dim: 1
|
|
# dim: 3
|
|
# dim: 600
|
|
# dim: 1000
|
|
# }
|
|
# input: "im_info"
|
|
# input_shape
|
|
# {
|
|
# dim: 1
|
|
# dim: 3
|
|
# }
|
|
for i in range(len(proto.input_shape)):
|
|
input_dims.append(np.array(proto.input_shape[i].dim, dtype=np.int64))
|
|
input_names.append(proto.input[i])
|
|
|
|
for i in range(len(input_names)):
|
|
input_name = input_names[i]
|
|
input_dim = input_dims[i]
|
|
# Input is defined at the top level of proto instead of distinct Input layer
|
|
graph.add_node(input_name, pb=None, model_pb=None, type='GlobalInput', name=input_name, shape=input_dim,
|
|
kind='op')
|
|
blob_producers[input_name] = (input_name, 0, input_name)
|
|
|
|
used_blobs = set()
|
|
for i, layer in enumerate(proto_layers):
|
|
|
|
model_layer = None
|
|
|
|
if model_layers:
|
|
for ml in model_layers:
|
|
if ml.name == layer.name:
|
|
model_layer = ml
|
|
break
|
|
if layer.type == 'Input':
|
|
if hasattr(layer, 'input_param'):
|
|
input_param = layer.input_param
|
|
else:
|
|
raise Error('Input layer has no input dims. ' +
|
|
refer_to_faq_msg(8))
|
|
if hasattr(input_param, 'shape'):
|
|
"""
|
|
example of proto input
|
|
layer
|
|
{
|
|
name: "data"
|
|
type: "Input"
|
|
top: "data"
|
|
input_param {shape: {dim: 1 dim: 3 dim: 600 dim: 1000}}
|
|
}
|
|
|
|
layer
|
|
{
|
|
name: "im_info"
|
|
type: "Input"
|
|
top: "im_info"
|
|
input_param {shape: {dim: 1 dim: 3}}
|
|
}
|
|
"""
|
|
dims = map(int, list(filter(None, str(list(input_param.shape)[0]).split('dim:'))))
|
|
input_dims.append(np.array(list(dims), dtype=np.int64))
|
|
input_names.append(layer.name)
|
|
|
|
node_id = graph.unique_id(layer.name)
|
|
graph.add_node(node_id, pb=layer, model_pb=model_layer, kind='op', type='Parameter')
|
|
if hasattr(graph, 'op_names_statistic') and hasattr(layer, 'type'):
|
|
graph.op_names_statistic[layer.type] += 1
|
|
|
|
# connect inputs based on blob_producers dictionary
|
|
for dst_port, bottom in enumerate(layer.bottom):
|
|
add_edge_caffe(graph, bottom, node_id, blob_producers, dst_port)
|
|
used_blobs.add(bottom)
|
|
|
|
# update blob producers dictionary by output ports
|
|
for src_port, top in enumerate(layer.top):
|
|
if top in blob_producers:
|
|
log.debug("Detected reuse of blob {} by layer {}".format(top, node_id))
|
|
blob_producers[top] = (node_id, src_port, layer.name)
|
|
|
|
# Tensor names information corresponding to a node is stored on outgoing edges.
|
|
# As output nodes do not have outgoing edges, fake outputs are required. In the following code
|
|
# for each output Identity node is added, and tensor name for the output is kept
|
|
# on (output, fake output) edge. After Result nodes adding transformation fake outputs
|
|
# are deleted from graph.
|
|
all_blobs = set(blob_producers.keys())
|
|
add_outputs_identity(graph, all_blobs - used_blobs, add_edge_caffe,
|
|
{'blob_producers': blob_producers, 'dst_port': 0})
|
|
|
|
if len(input_names) <= 0:
|
|
raise Error('The topology contains no "input" layers. ' +
|
|
refer_to_faq_msg(79))
|
|
return {fake_node_name: shape for (fake_node_name, shape) in zip(input_names, input_dims)}
|
|
|
|
|
|
def add_edge_caffe(graph: Graph, bottom: str, dst_layer: str, blob_producers: dict, dst_port: int):
|
|
"""
|
|
Creates an edge and adds it to the graph.
|
|
"""
|
|
src_layer = blob_producers[bottom][0]
|
|
src_port = blob_producers[bottom][1]
|
|
edge_attrs = {
|
|
'out': src_port,
|
|
'in': dst_port,
|
|
'name': bottom,
|
|
# debug anchor for a framework name and tensor name
|
|
'fw_tensor_debug_info': [(blob_producers[bottom][2], bottom)],
|
|
'in_attrs': ['in', 'name'],
|
|
'out_attrs': ['out', 'name'],
|
|
'data_attrs': ['fw_tensor_debug_info']
|
|
}
|
|
graph.add_edge(src_layer, dst_layer, **edge_attrs)
|