2 Copyright (c) 2018-2019 Intel Corporation
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
22 from google.protobuf import text_format
23 from google.protobuf.internal import api_implementation
25 from mo.front.caffe.proto import caffe_pb2
26 from mo.graph.graph import Node, Graph
27 from mo.utils.error import Error, FrameworkError
28 from mo.utils.utils import refer_to_faq_msg
31 def parse_mean(file_path: str, in_shape: np.ndarray, mean_file_offsets: [tuple, None]):
32 blob = caffe_pb2.BlobProto()
33 with open(file_path, 'rb') as file:
37 raise Error('Mean file "{}" is empty.' + refer_to_faq_msg(5),
41 blob.ParseFromString(data)
42 data = np.array(blob.data) # pylint: disable=no-member
44 if blob.HasField('channels') or blob.HasField('height') or blob.HasField('width'):
45 data = data.reshape(blob.channels, blob.height, blob.width) # pylint: disable=no-member
47 data = data.reshape(blob.shape.dim) # pylint: disable=no-member
48 # crop mean image according to input size
49 if in_shape[2] > data.shape[1] or in_shape[3] > data.shape[2]:
51 'Input image of shape {} is larger than mean image {} from file "{}". ' +
58 if mean_file_offsets is not None and len(mean_file_offsets) == 2:
59 offset_x = mean_file_offsets[0]
60 offset_y = mean_file_offsets[1]
62 offset_x = int((data.shape[1] - in_shape[2]) / 2)
63 offset_y = int((data.shape[2] - in_shape[3]) / 2)
66 for i in range(in_shape[1]):
67 data_channel = np.zeros(in_shape[2] * in_shape[3], dtype=np.float32)
68 for x in range(in_shape[2]):
69 for y in range(in_shape[3]):
70 data_channel[x * in_shape[3] + y] = data[i, x + offset_x, y + offset_y]
71 mean.append(data_channel)
75 except Exception as err:
77 'While processing mean file "{}": {}. Probably mean file has incorrect format. ' +
83 def load_caffe_proto_model(proto_path: str, model_path: [str, None] = None):
84 # 1. python protobuf is used
85 if api_implementation._implementation_type == 'python':
86 message = 'Please expect that Model Optimizer conversion might be slow. ' \
87 'You are currently using Python protobuf library implementation. \n'
89 from google.protobuf.pyext import cpp_message
90 # Check os windows and env variable PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION
91 if os.name == 'nt' and os.environ.get('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', default='') != 'cpp':
92 # 2. cpp implementaion is available but not used
93 message += 'However, cpp implementation is available, you can boost ' \
94 'model conversion by setting PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION env variable to cpp. \n' \
95 'Run: set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp \n'
97 # 3. cpp implementaion is not available
98 message += 'However you can use the C++ protobuf implementation that is supplied with the OpenVINO toolkit' \
99 'or build protobuf library from sources. \n' \
100 'Navigate to "install_prerequisites" folder and run: ' \
101 'python -m easy_install protobuf-3.5.1-py($your_python_version)-win-amd64.egg \n' \
102 'set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp'
103 print(message + '\n\n' + refer_to_faq_msg(80))
107 proto = caffe_pb2.NetParameter()
108 with open(proto_path, "r") as file:
109 text_format.Merge(str(file.read()), proto)
110 except Exception as e:
111 log.error('Exception message: {}\n\n'.format(e) +
112 ' Possible reasons:\n' +
113 ' 1. {} does not exist\n'.format(proto_path) +
114 ' 2. {} does not have a valid structure, for example, it was downloaded as html\n'.format(proto_path) +
115 ' 3. {} contains custom layers or attributes that are not supported\n'.format(proto_path) +
116 ' in Model Optimizer by default.\n\n' +
117 ' After you made sure that {} has a valid structure and still see this issue, then\n'.format(proto_path) +
118 ' you need to generate a python parser for caffe.proto that was used when the model\n' +
120 ' Run "python3 generate_caffe_pb2.py --input_proto ${PATH_TO_CAFFE}/src/caffe/proto/caffe.proto"' +
121 refer_to_faq_msg(1) + '\n\n', extra={'framework_error': True})
122 raise FrameworkError('Model Optimizer is not able to parse {}'.format(proto_path)) from e
124 # Read model layer if exists
128 model = caffe_pb2.NetParameter()
129 with open(model_path, "rb") as infile:
130 map = mmap.mmap(infile.fileno(), 0, access=mmap.ACCESS_READ)
131 model.MergeFromString(map)
132 except Exception as e:
133 log.error('Exception message: {}\n\n'.format(e) +
134 ' Possible reasons:\n' +
135 ' 1. {} does not exist\n'.format(model_path) +
136 ' 2. {} does not have a valid structure\n'.format(model_path), extra={'framework_error': True})
137 raise FrameworkError('Model Optimizer is not able to parse {}'.format(model_path)) from e
142 def get_layers(proto):
145 elif len(proto.layers):
148 raise Error('Invalid proto file: there is neither "layer" nor "layers" top-level messages. ' +
152 def caffe_pb_to_nx(proto, model):
154 Converts proto/model layers to a graph. Edges are restored by bottom/top attributes.
155 Graph nodes has two attributes: pb for prototxt definition and model_pb for caffemodel definition.
160 Protobuf message for NetParameter, representing .prototxt.
162 Protobuf message for NetParameter, representing .caffemodel.
167 built NX Directed graph.
170 # Blobs in prototxt model can be reused by inplace layer.
171 # This requires loading of pb layers in order and tracking the latest
172 # layer that writes a particular blob.
173 blob_producers = {} # maps layer blob name to the layer name and port
174 proto_layers = get_layers(proto)
177 model_layers = get_layers(model)
181 if len(proto.input_dim) > 0 and len(list(proto.input)) > 1:
182 # example of proto input
191 raise Error('Old-style inputs (via "input_dims") are not supported. ' +
192 'Please specify inputs via "input_shape". ' +
194 elif len(list(proto.input)) == 1 and len(list(proto.input_dim)):
195 # example of proto input
201 input_dims = [np.array(list(proto.input_dim), dtype=np.int64)]
202 input_names = [proto.input[0]]
204 elif len(list(proto.input)) == 1 and len(list(proto.input_shape)):
205 # example of proto input
214 input_dims = [np.array(proto.input_shape[0].dim, dtype=np.int64)]
215 input_names = [proto.input[0]]
217 elif len(proto.input_shape) > 0:
218 # example of proto input
233 for i in range(len(proto.input_shape)):
234 input_dims.append(np.array(proto.input_shape[i].dim, dtype=np.int64))
235 input_names.append(proto.input[i])
237 for i in range(len(input_names)):
238 input_name = input_names[i]
239 input_dim = input_dims[i]
240 # Input is defined at the top level of proto instead of distinct Input layer
241 graph.add_node(input_name, pb=None, model_pb=None, type='GlobalInput', name=input_name, shape=input_dim,
243 blob_producers[input_name] = (input_name, 0)
245 for i, layer in enumerate(proto_layers):
250 for ml in model_layers:
251 if ml.name == layer.name:
254 if layer.type == 'Input':
255 if hasattr(layer, 'input_param'):
256 input_param = layer.input_param
258 raise Error('Input layer has no input dims. ' +
260 if hasattr(input_param, 'shape'):
262 example of proto input
268 input_param {shape: {dim: 1 dim: 3 dim: 600 dim: 1000}}
276 input_param {shape: {dim: 1 dim: 3}}
279 dims = map(int, list(filter(None, str(list(input_param.shape)[0]).split('dim:'))))
280 input_dims.append(np.array(list(dims), dtype=np.int64))
281 input_names.append(layer.name)
283 layer.name = graph.unique_id(layer.name)
284 graph.add_node(layer.name, pb=layer, model_pb=model_layer, kind='op')
286 # connect inputs based on blob_producers dictionary
287 for dst_port, bottom in enumerate(layer.bottom):
288 src_layer = blob_producers[bottom][0]
289 src_port = blob_producers[bottom][1]
290 assert (graph.has_node(src_layer))
295 'fw_tensor_debug_info': [(src_layer, bottom)], # debug anchor for a framework tensor name and port
296 'in_attrs': ['in', 'name'],
297 'out_attrs': ['out', 'name'],
298 'data_attrs': ['fw_tensor_debug_info']
300 graph.add_edge(src_layer, layer.name, **edge_attrs)
302 # update blob producers dictionary by output ports
303 for src_port, top in enumerate(layer.top):
304 if top in blob_producers:
305 log.debug("Detected reuse of blob {} by layer {}".format(top, layer.name))
306 blob_producers[top] = (layer.name, src_port)
308 if len(input_names) <= 0:
309 raise Error('The topology contains no "input" layers. ' +
310 refer_to_faq_msg(79))
311 return graph, {name: shape for (name, shape) in zip(input_names, input_dims)}