Publishing R3
[platform/upstream/dldt.git] / model-optimizer / mo / front / caffe / loader.py
1 """
2  Copyright (c) 2018 Intel Corporation
3
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
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 """
16
17 import logging as log
18 import mmap
19 import os
20
21
22 import networkx as nx
23 import numpy as np
24 from google.protobuf import text_format
25 from google.protobuf.internal import api_implementation
26
27 from mo.front.caffe.proto import caffe_pb2
28 from mo.graph.graph import Node, unique_id
29 from mo.utils.error import Error
30 from mo.utils.utils import refer_to_faq_msg
31
32
33 def parse_mean(file_path: str, in_shape: np.ndarray, mean_file_offsets: [tuple, None]):
34     blob = caffe_pb2.BlobProto()
35     with open(file_path, 'rb') as file:
36         data = file.read()
37
38     if not data:
39         raise Error('Mean file "{}" is empty.' + refer_to_faq_msg(5),
40                     file_path)
41
42     try:
43         blob.ParseFromString(data)
44         data = np.array(blob.data)
45
46         if blob.HasField('channels') or blob.HasField('height') or blob.HasField('width'):
47             data = data.reshape(blob.channels, blob.height, blob.width)
48         else:
49             data = data.reshape(blob.shape.dim)
50         # crop mean image according to input size
51         if in_shape[2] > data.shape[1] or in_shape[3] > data.shape[2]:
52             raise Error(
53                 'Input image of shape {} is larger than mean image {} from file "{}". ' +
54                 refer_to_faq_msg(4),
55                 in_shape,
56                 data.shape,
57                 file_path
58             )
59
60         if mean_file_offsets is not None and len(mean_file_offsets) == 2:
61             offset_x = mean_file_offsets[0]
62             offset_y = mean_file_offsets[1]
63         else:
64             offset_x = int((data.shape[1] - in_shape[2]) / 2)
65             offset_y = int((data.shape[2] - in_shape[3]) / 2)
66
67         mean = []
68         for i in range(in_shape[1]):
69             data_channel = np.zeros(in_shape[2] * in_shape[3], dtype=np.float32)
70             for x in range(in_shape[2]):
71                 for y in range(in_shape[3]):
72                     data_channel[x * in_shape[3] + y] = data[i, x + offset_x, y + offset_y]
73             mean.append(data_channel)
74
75         return mean
76
77     except Exception as err:
78         raise Error(
79             'While processing mean file "{}": {}. Probably mean file has incorrect format. ' +
80             refer_to_faq_msg(6),
81             file_path,
82             str(err)) from err
83
84
85 def load_caffe_proto_model(proto_path: str, model_path: [str, None] = None):
86     # 1. python protobuf is used
87     if api_implementation._implementation_type == 'python':
88         message = 'Please expect that Model Optimizer conversion might be slow. ' \
89                   'You are currently using Python protobuf library implementation. \n'
90         try:
91             from google.protobuf.pyext import cpp_message
92             # Check os windows and env variable PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION
93             if os.name == 'nt' and os.environ.get('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', default='') != 'cpp':
94                 # 2. cpp implementaion is available but not used
95                 message += 'However, cpp implementation is available, you can boost ' \
96                            'model conversion by setting PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION env variable to cpp. \n' \
97                            'Run: set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp \n'
98         except ImportError:
99             # 3. cpp implementaion is not available
100             message += 'However you can use the C++ protobuf implementation that is supplied with the OpenVINO toolkit' \
101                        'or build protobuf library from sources. \n' \
102                        'Navigate to "install_prerequisites" folder and run: ' \
103                        'python -m easy_install protobuf-3.5.1-py($your_python_version)-win-amd64.egg \n' \
104                        'set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp'
105         print(message + '\n\n' + refer_to_faq_msg(80))
106     # Read proto layers
107     proto = caffe_pb2.NetParameter()
108     with open(proto_path, "r") as file:
109         text_format.Merge(str(file.read()), proto)
110
111     # Read model layer if exists
112     model = None
113     if model_path:
114         model = caffe_pb2.NetParameter()
115         with open(model_path, "rb") as infile:
116             map = mmap.mmap(infile.fileno(), 0, access=mmap.ACCESS_READ)
117             model.MergeFromString(map)
118
119     return proto, model
120
121
122 def get_layers(proto):
123     if len(proto.layer):
124         return proto.layer
125     elif len(proto.layers):
126         return proto.layers
127     else:
128         raise Error('Invalid proto file: there is neither "layer" nor "layers" top-level messages. ' +
129                     refer_to_faq_msg(7))
130
131
132 def caffe_pb_to_nx(proto, model):
133     """
134     Converts proto/model layers to a graph. Edges are restored by bottom/top attributes.
135     Graph nodes has two attributes: pb for prototxt definition and model_pb for caffemodel definition.
136
137     Parameters
138     ----------
139     proto : NetParameter
140        Protobuf message for NetParameter, representing .prototxt.
141     model : NetParameter
142        Protobuf message for NetParameter, representing .caffemodel.
143
144     Returns
145     ----------
146     nx.MultiDiGraph
147         built NX Directed graph.
148     """
149     graph = nx.MultiDiGraph()
150     # Blobs in prototxt model can be reused by inplace layer.
151     # This requires loading of pb layers in order and tracking the latest
152     # layer that writes a particular blob.
153     blob_producers = {}  # maps layer blob name to the layer name and port
154     proto_layers = get_layers(proto)
155     model_layers = None
156     if model:
157         model_layers = get_layers(model)
158
159     input_dims = []
160     input_names = []
161     if len(proto.input_dim) > 0 and len(list(proto.input)) > 1:
162         # example of proto input
163         # input: "data"
164         # input_dim: 1
165         # input_dim: 3
166         # input_dim: 500
167         # input_dim: 500
168         # input: "info"
169         # input_dim: 1
170         # input_dim: 3
171         raise Error('Old-style inputs (via "input_dims") are not supported. ' +
172                     'Please specify inputs via  "input_shape". ' +
173                     refer_to_faq_msg(8))
174     elif len(list(proto.input)) == 1 and len(list(proto.input_dim)):
175         # example of proto input
176         # input: "data"
177         # input_dim: 1
178         # input_dim: 3
179         # input_dim: 500
180         # input_dim: 500
181         input_dims = [np.array(list(proto.input_dim), dtype=np.int64)]
182         input_names = [proto.input[0]]
183
184     elif len(list(proto.input)) == 1 and len(list(proto.input_shape)):
185         # example of proto input
186         # input: "data"
187         # input_shape
188         # {
189         #     dim: 1
190         #     dim: 3
191         #     dim: 227
192         #     dim: 227
193         # }
194         input_dims = [np.array(proto.input_shape[0].dim, dtype=np.int64)]
195         input_names = [proto.input[0]]
196
197     elif len(proto.input_shape) > 0:
198         # example of proto input
199         # input: "data"
200         # input_shape
201         # {
202         #     dim: 1
203         #     dim: 3
204         #     dim: 600
205         #     dim: 1000
206         # }
207         # input: "im_info"
208         # input_shape
209         # {
210         #     dim: 1
211         #     dim: 3
212         # }
213         for i in range(len(proto.input_shape)):
214             input_dims.append(np.array(proto.input_shape[i].dim, dtype=np.int64))
215             input_names.append(proto.input[i])
216
217     for i in range(len(input_names)):
218         input_name = input_names[i]
219         input_dim = input_dims[i]
220         # Input is defined at the top level of proto instead of distinct Input layer
221         graph.add_node(input_name, pb=None, model_pb=None, type='GlobalInput', name=input_name, shape=input_dim,
222                        kind='op')
223         blob_producers[input_name] = (input_name, 0)
224
225     for i, layer in enumerate(proto_layers):
226
227         model_layer = None
228
229         if model_layers:
230             for ml in model_layers:
231                 if ml.name == layer.name:
232                     model_layer = ml
233                     break
234         if layer.type == 'Input':
235             if hasattr(layer, 'input_param'):
236                 input_param = layer.input_param
237             else:
238                 raise Error('Input layer has no input dims. ' +
239                             refer_to_faq_msg(8))
240             if hasattr(input_param, 'shape'):
241                 # example of proto input
242                 # layer
243                 # {
244                 #     name: "data"
245                 #     type: "Input"
246                 #     top: "data"
247                 #     input_param {shape: {dim: 1 dim: 3 dim: 600 dim: 1000}}
248                 # }
249                 #
250                 # layer
251                 # {
252                 #     name: "im_info"
253                 #     type: "Input"
254                 #     top: "im_info"
255                 #     input_param {shape: {dim: 1 dim: 3}}
256                 # }
257                 dims = map(int, list(filter(None, str(list(input_param.shape)[0]).split('dim:'))))
258                 input_dims.append(np.array(list(dims), dtype=np.int64))
259                 input_names.append(layer.name)
260
261         graph.add_node(layer.name, pb=layer, model_pb=model_layer, kind='op')
262
263         # connect inputs based on blob_producers dictionary
264         for dst_port, bottom in enumerate(layer.bottom):
265             src_layer = blob_producers[bottom][0]
266             src_port = blob_producers[bottom][1]
267             assert (graph.has_node(src_layer))
268             edge_attrs = {
269                 'out': src_port,
270                 'in': dst_port,
271                 'name': bottom,
272                 'fw_tensor_debug_info': [(src_layer, bottom)],  # debug anchor for a framework tensor name and port
273                 'in_attrs': ['in', 'name'],
274                 'out_attrs': ['out', 'name'],
275                 'data_attrs': ['fw_tensor_debug_info']
276             }
277             graph.add_edge(src_layer, layer.name, **edge_attrs)
278
279         # update blob producers dictionary by output ports
280         for src_port, top in enumerate(layer.top):
281             if top in blob_producers:
282                 log.debug("Detected reuse of blob {} by layer {}".format(top, layer.name))
283             blob_producers[top] = (layer.name, src_port)
284
285     # Find all nodes that do not have consumers.
286     # Add identity ops as a consumers for each output port for such nodes.
287     for node in list(graph.nodes()):
288         node = Node(graph, node)
289         if len(node.out_nodes()) == 0:
290             if not node.has_valid('pb') or not hasattr(node.pb, 'top'):
291                 continue
292             for port, top in enumerate(node.pb.top):
293                 new_id = unique_id(graph, 'TerminalIdentity_')
294                 graph.add_node(new_id, op='Identity', type='Identity', kind='op')
295                 edge_attrs = {
296                     'out': port,
297                     'in': 0,
298                     'name': top,
299                     'fw_tensor_debug_info': [(node.id, top)], # debug anchor for a framework tensor name and port
300                     'in_attrs': ['in', 'name'],
301                     'out_attrs': ['out', 'name'],
302                     'data_attrs': ['fw_tensor_debug_info']
303                 }
304                 graph.add_edge(node.id, new_id, **edge_attrs)
305
306     if len(input_names) <= 0:
307         raise Error('The topology contains no "input" layers. ' +
308                     refer_to_faq_msg(79))
309     return graph, {name: shape for (name, shape) in zip(input_names, input_dims)}