Reduced usage of batch in python samples (#3104)
[platform/upstream/dldt.git] / inference-engine / ie_bridges / python / sample / ngraph_function_creation_sample / ngraph_function_creation_sample.py
1 #!/usr/bin/env python
2 """
3  Copyright (C) 2018-2020 Intel Corporation
4
5  Licensed under the Apache License, Version 2.0 (the "License");
6  you may not use this file except in compliance with the License.
7  You may obtain a copy of the License at
8
9       http://www.apache.org/licenses/LICENSE-2.0
10
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16 """
17
18 import sys
19 import os
20 from argparse import ArgumentParser, SUPPRESS
21 import cv2
22 import numpy as np
23 import logging as log
24 from openvino.inference_engine import IECore, IENetwork
25 import ngraph
26 from ngraph.impl import Function
27 from functools import reduce
28 import struct as st
29
30
31 def build_argparser() -> ArgumentParser:
32     parser = ArgumentParser(add_help=False)
33     args = parser.add_argument_group('Options')
34     args.add_argument('-h', '--help', action='help', default=SUPPRESS, help='Show this help message and exit.')
35     args.add_argument('-i', '--input', help='Required. Path to a folder with images or path to an image files',
36                       required=True, type=str, nargs="+")
37     args.add_argument('-m', '--model', help='Required. Path to file where weights for the network are located')
38     args.add_argument('-d', '--device',
39                       help='Optional. Specify the target device to infer on; CPU, GPU, FPGA, HDDL, MYRIAD or HETERO: '
40                            'is acceptable. The sample will look for a suitable plugin for device specified. Default '
41                            'value is CPU',
42                       default='CPU', type=str)
43     args.add_argument('--labels', help='Optional. Path to a labels mapping file', default=None, type=str)
44     args.add_argument('-nt', '--number_top', help='Optional. Number of top results', default=1, type=int)
45
46     return parser
47
48
49 def list_input_images(input_dirs: list):
50     images = []
51     for input_dir in input_dirs:
52         if os.path.isdir(input_dir):
53             for root, directories, filenames in os.walk(input_dir):
54                 for filename in filenames:
55                     images.append(os.path.join(root, filename))
56         elif os.path.isfile(input_dir):
57             images.append(input_dir)
58
59     return images
60
61
62 def read_image(image_path: np):
63     # try to read image in usual image formats
64     image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
65
66     # try to open image as ubyte
67     if image is None:
68         with open(image_path, 'rb') as f:
69             image_file = open(image_path, 'rb')
70             image_file.seek(0)
71             st.unpack('>4B', image_file.read(4))    # need to skip  4 bytes
72             nimg = st.unpack('>I', image_file.read(4))[0]  # number of images
73             nrow = st.unpack('>I', image_file.read(4))[0]  # number of rows
74             ncolumn = st.unpack('>I', image_file.read(4))[0]  # number of column
75             nbytes = nimg * nrow * ncolumn * 1  # each pixel data is 1 byte
76
77             assert nimg == 1, log.error('Sample supports ubyte files with 1 image inside')
78
79             image = np.asarray(st.unpack('>' + 'B' * nbytes, image_file.read(nbytes))).reshape(
80                 (nrow, ncolumn))
81
82     return image
83
84
85 def shape_and_length(shape: list):
86     length = reduce(lambda x, y: x*y, shape)
87     return shape, length
88
89
90 def create_ngraph_function(args) -> Function:
91     weights = np.fromfile(args.model, dtype=np.float32)
92     weights_offset = 0
93     padding_begin = [0, 0]
94     padding_end = [0, 0]
95
96     # input
97     input_shape = [64, 1, 28, 28]
98     param_node = ngraph.parameter(input_shape, np.float32, 'Parameter')
99
100     # convolution 1
101     conv_1_kernel_shape, conv_1_kernel_length = shape_and_length([20, 1, 5, 5])
102     conv_1_kernel = ngraph.constant(weights[0:conv_1_kernel_length].reshape(conv_1_kernel_shape))
103     weights_offset += conv_1_kernel_length
104     conv_1_node = ngraph.convolution(param_node, conv_1_kernel, [1, 1], padding_begin, padding_end, [1, 1])
105
106     # add 1
107     add_1_kernel_shape, add_1_kernel_length = shape_and_length([1, 20, 1, 1])
108     add_1_kernel = ngraph.constant(
109         weights[weights_offset:weights_offset + add_1_kernel_length].reshape(add_1_kernel_shape)
110     )
111     weights_offset += add_1_kernel_length
112     add_1_node = ngraph.add(conv_1_node, add_1_kernel)
113
114     # maxpool 1
115     maxpool_1_node = ngraph.max_pool(add_1_node, [2, 2], padding_begin, padding_end, [2, 2], 'ceil', None)
116
117     # convolution 2
118     conv_2_kernel_shape, conv_2_kernel_length = shape_and_length([50, 20, 5, 5])
119     conv_2_kernel = ngraph.constant(
120         weights[weights_offset:weights_offset + conv_2_kernel_length].reshape(conv_2_kernel_shape)
121     )
122     weights_offset += conv_2_kernel_length
123     conv_2_node = ngraph.convolution(maxpool_1_node, conv_2_kernel, [1, 1], padding_begin, padding_end, [1, 1])
124
125     # add 2
126     add_2_kernel_shape, add_2_kernel_length = shape_and_length([1, 50, 1, 1])
127     add_2_kernel = ngraph.constant(
128         weights[weights_offset:weights_offset + add_2_kernel_length].reshape(add_2_kernel_shape)
129     )
130     weights_offset += add_2_kernel_length
131     add_2_node = ngraph.add(conv_2_node, add_2_kernel)
132
133     # maxpool 2
134     maxpool_2_node = ngraph.max_pool(add_2_node, [2, 2], padding_begin, padding_end, [2, 2], 'ceil', None)
135
136     # reshape 1
137     reshape_1_dims, reshape_1_length = shape_and_length([2])
138     # workaround to get int64 weights from float32 ndarray w/o unnecessary copying
139     dtype_weights = np.frombuffer(
140         weights[weights_offset:weights_offset + 2*reshape_1_length], dtype=np.int64
141     )
142     reshape_1_kernel = ngraph.constant(dtype_weights)
143     weights_offset += 2*reshape_1_length
144     reshape_1_node = ngraph.reshape(maxpool_2_node, reshape_1_kernel, True)
145
146     # matmul 1
147     matmul_1_kernel_shape, matmul_1_kernel_length = shape_and_length([500, 800])
148     matmul_1_kernel = ngraph.constant(
149         weights[weights_offset:weights_offset + matmul_1_kernel_length].reshape(matmul_1_kernel_shape)
150     )
151     weights_offset += matmul_1_kernel_length
152     matmul_1_node = ngraph.matmul(reshape_1_node, matmul_1_kernel, False, True)
153
154     # add 3
155     add_3_kernel_shape, add_3_kernel_length = shape_and_length([1, 500])
156     add_3_kernel = ngraph.constant(
157         weights[weights_offset:weights_offset + add_3_kernel_length].reshape(add_3_kernel_shape)
158     )
159     weights_offset += add_3_kernel_length
160     add_3_node = ngraph.add(matmul_1_node, add_3_kernel)
161
162     # ReLU
163     relu_node = ngraph.relu(add_3_node)
164
165     # reshape 2
166     reshape_2_kernel = ngraph.constant(dtype_weights)
167     reshape_2_node = ngraph.reshape(relu_node, reshape_2_kernel, True)
168
169     # matmul 2
170     matmul_2_kernel_shape, matmul_2_kernel_length = shape_and_length([10, 500])
171     matmul_2_kernel = ngraph.constant(
172         weights[weights_offset:weights_offset + matmul_2_kernel_length].reshape(matmul_2_kernel_shape)
173     )
174     weights_offset += matmul_2_kernel_length
175     matmul_2_node = ngraph.matmul(reshape_2_node, matmul_2_kernel, False, True)
176
177     # add 4
178     add_4_kernel_shape, add_4_kernel_length = shape_and_length([1, 10])
179     add_4_kernel = ngraph.constant(
180         weights[weights_offset:weights_offset + add_4_kernel_length].reshape(add_4_kernel_shape)
181     )
182     weights_offset += add_4_kernel_length
183     add_4_node = ngraph.add(matmul_2_node, add_4_kernel)
184
185     # softmax
186     softmax_axis = 1
187     softmax_node = ngraph.softmax(add_4_node, softmax_axis)
188
189     # result
190     result_node = ngraph.result(softmax_node)
191
192     # nGraph function
193     function = Function(result_node, [param_node], 'lenet')
194
195     return function
196
197
198 def main():
199     log.basicConfig(format='[ %(levelname)s ] %(message)s', level=log.INFO, stream=sys.stdout)
200     args = build_argparser().parse_args()
201
202     input_images = list_input_images(args.input)
203
204     # Loading network using ngraph function
205     ngraph_function = create_ngraph_function(args)
206     net = IENetwork(Function.to_capsule(ngraph_function))
207
208     assert len(net.input_info.keys()) == 1, "Sample supports only single input topologies"
209     assert len(net.outputs) == 1, "Sample supports only single output topologies"
210
211     log.info("Preparing input blobs")
212     input_blob = next(iter(net.input_info))
213     out_blob = next(iter(net.outputs))
214     net.batch_size = len(input_images)
215
216     # Read and pre-process input images
217     n, c, h, w = net.input_info[input_blob].input_data.shape
218     images = np.ndarray(shape=(n, c, h, w))
219     for i in range(n):
220         image = read_image(input_images[i])
221         assert image is not None, log.error("Can't open an image {}".format(input_images[i]))
222         assert len(image.shape) == 2, log.error('Sample supports images with 1 channel only')
223         if image.shape[:] != (w, h):
224             log.warning("Image {} is resized from {} to {}".format(input_images[i], image.shape[:], (w, h)))
225             image = cv2.resize(image, (w, h))
226         images[i] = image
227     log.info("Batch size is {}".format(n))
228
229     log.info("Creating Inference Engine")
230     ie = IECore()
231
232     log.info('Loading model to the device')
233     exec_net = ie.load_network(network=net, device_name=args.device.upper())
234
235     # Start sync inference
236     log.info('Creating infer request and starting inference')
237     res = exec_net.infer(inputs={input_blob: images})
238
239     # Processing results
240     log.info("Processing output blob")
241     res = res[out_blob]
242     log.info("Top {} results: ".format(args.number_top))
243
244     # Read labels file if it is provided as argument
245     labels_map = None
246     if args.labels:
247         with open(args.labels, 'r') as f:
248             labels_map = [x.split(sep=' ', maxsplit=1)[-1].strip() for x in f]
249
250     classid_str = "classid"
251     probability_str = "probability"
252     for i, probs in enumerate(res):
253         probs = np.squeeze(probs)
254         top_ind = np.argsort(probs)[-args.number_top:][::-1]
255         print("Image {}\n".format(input_images[i]))
256         print(classid_str, probability_str)
257         print("{} {}".format('-' * len(classid_str), '-' * len(probability_str)))
258         for class_id in top_ind:
259             det_label = labels_map[class_id] if labels_map else "{}".format(class_id)
260             label_length = len(det_label)
261             space_num_before = (len(classid_str) - label_length) // 2
262             space_num_after = len(classid_str) - (space_num_before + label_length) + 2
263             space_num_before_prob = (len(probability_str) - len(str(probs[class_id]))) // 2
264             print("{}{}{}{}{:.7f}".format(' ' * space_num_before, det_label,
265                                           ' ' * space_num_after, ' ' * space_num_before_prob,
266                                           probs[class_id]))
267         print("\n")
268
269     log.info('This sample is an API example, for any performance measurements '
270              'please use the dedicated benchmark_app tool')
271
272
273 if __name__ == '__main__':
274     sys.exit(main() or 0)