Update AlexNet example with accessors
[platform/upstream/armcl.git] / utils / Utils.h
1 /*
2  * Copyright (c) 2016, 2017 ARM Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #ifndef __UTILS_UTILS_H__
25 #define __UTILS_UTILS_H__
26
27 #include "arm_compute/core/Helpers.h"
28 #include "arm_compute/core/ITensor.h"
29 #include "arm_compute/core/Types.h"
30 #include "arm_compute/core/Validate.h"
31 #include "arm_compute/core/Window.h"
32 #include "arm_compute/runtime/Tensor.h"
33 #include "support/ToolchainSupport.h"
34
35 #ifdef ARM_COMPUTE_CL
36 #include "arm_compute/core/CL/OpenCL.h"
37 #include "arm_compute/runtime/CL/CLTensor.h"
38 #endif /* ARM_COMPUTE_CL */
39
40 #include <cstdlib>
41 #include <cstring>
42 #include <fstream>
43 #include <iostream>
44
45 namespace arm_compute
46 {
47 namespace utils
48 {
49 /** Signature of an example to run
50  *
51  * @param[in] argc Number of command line arguments
52  * @param[in] argv Command line arguments
53  */
54 using example = void(int argc, const char **argv);
55
56 /** Run an example and handle the potential exceptions it throws
57  *
58  * @param[in] argc Number of command line arguments
59  * @param[in] argv Command line arguments
60  * @param[in] func Pointer to the function containing the code to run
61  */
62 int run_example(int argc, const char **argv, example &func);
63
64 /** Draw a RGB rectangular window for the detected object
65  *
66  * @param[in, out] tensor Input tensor where the rectangle will be drawn on. Format supported: RGB888
67  * @param[in]      rect   Geometry of the rectangular window
68  * @param[in]      r      Red colour to use
69  * @param[in]      g      Green colour to use
70  * @param[in]      b      Blue colour to use
71  */
72 void draw_detection_rectangle(arm_compute::ITensor *tensor, const arm_compute::DetectionWindow &rect, uint8_t r, uint8_t g, uint8_t b);
73
74 /** Parse the ppm header from an input file stream. At the end of the execution,
75  *  the file position pointer will be located at the first pixel stored in the ppm file
76  *
77  * @param[in] fs Input file stream to parse
78  *
79  * @return The width, height and max value stored in the header of the PPM file
80  */
81 std::tuple<unsigned int, unsigned int, int> parse_ppm_header(std::ifstream &fs);
82
83 /** Maps a tensor if needed
84  *
85  * @param[in] tensor   Tensor to be mapped
86  * @param[in] blocking Specified if map is blocking or not
87  */
88 template <typename T>
89 inline void map(T &tensor, bool blocking)
90 {
91     ARM_COMPUTE_UNUSED(tensor);
92     ARM_COMPUTE_UNUSED(blocking);
93 }
94
95 /** Unmaps a tensor if needed
96  *
97  * @param tensor  Tensor to be unmapped
98  */
99 template <typename T>
100 inline void unmap(T &tensor)
101 {
102     ARM_COMPUTE_UNUSED(tensor);
103 }
104
105 #ifdef ARM_COMPUTE_CL
106 /** Maps a tensor if needed
107  *
108  * @param[in] tensor   Tensor to be mapped
109  * @param[in] blocking Specified if map is blocking or not
110  */
111 inline void map(CLTensor &tensor, bool blocking)
112 {
113     tensor.map(blocking);
114 }
115
116 /** Unmaps a tensor if needed
117  *
118  * @param tensor  Tensor to be unmapped
119  */
120 inline void unmap(CLTensor &tensor)
121 {
122     tensor.unmap();
123 }
124 #endif /* ARM_COMPUTE_CL */
125
126 /** Class to load the content of a PPM file into an Image
127  */
128 class PPMLoader
129 {
130 public:
131     PPMLoader()
132         : _fs(), _width(0), _height(0)
133     {
134     }
135     /** Open a PPM file and reads its metadata (Width, height)
136      *
137      * @param[in] ppm_filename File to open
138      */
139     void open(const std::string &ppm_filename)
140     {
141         ARM_COMPUTE_ERROR_ON(is_open());
142         try
143         {
144             _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
145             _fs.open(ppm_filename, std::ios::in | std::ios::binary);
146
147             unsigned int max_val = 0;
148             std::tie(_width, _height, max_val) = parse_ppm_header(_fs);
149
150             ARM_COMPUTE_ERROR_ON_MSG(max_val >= 256, "2 bytes per colour channel not supported in file %s", ppm_filename.c_str());
151         }
152         catch(const std::ifstream::failure &e)
153         {
154             ARM_COMPUTE_ERROR("Accessing %s: %s", ppm_filename.c_str(), e.what());
155         }
156     }
157     /** Return true if a PPM file is currently open
158          */
159     bool is_open()
160     {
161         return _fs.is_open();
162     }
163
164     /** Initialise an image's metadata with the dimensions of the PPM file currently open
165      *
166      * @param[out] image  Image to initialise
167      * @param[in]  format Format to use for the image (Must be RGB888 or U8)
168      */
169     template <typename T>
170     void init_image(T &image, arm_compute::Format format)
171     {
172         ARM_COMPUTE_ERROR_ON(!is_open());
173         ARM_COMPUTE_ERROR_ON(format != arm_compute::Format::RGB888 && format != arm_compute::Format::U8);
174
175         // Use the size of the input PPM image
176         arm_compute::TensorInfo image_info(_width, _height, format);
177         image.allocator()->init(image_info);
178     }
179
180     /** Fill an image with the content of the currently open PPM file.
181      *
182      * @note If the image is a CLImage, the function maps and unmaps the image
183      *
184      * @param[in,out] image Image to fill (Must be allocated, and of matching dimensions with the opened PPM).
185      */
186     template <typename T>
187     void fill_image(T &image)
188     {
189         ARM_COMPUTE_ERROR_ON(!is_open());
190         ARM_COMPUTE_ERROR_ON(image.info()->dimension(0) != _width || image.info()->dimension(1) != _height);
191         ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&image, arm_compute::Format::U8, arm_compute::Format::RGB888);
192         try
193         {
194             // Map buffer if creating a CLTensor
195             map(image, true);
196
197             // Check if the file is large enough to fill the image
198             const size_t current_position = _fs.tellg();
199             _fs.seekg(0, std::ios_base::end);
200             const size_t end_position = _fs.tellg();
201             _fs.seekg(current_position, std::ios_base::beg);
202
203             ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < image.info()->tensor_shape().total_size() * image.info()->element_size(),
204                                      "Not enough data in file");
205             ARM_COMPUTE_UNUSED(end_position);
206
207             switch(image.info()->format())
208             {
209                 case arm_compute::Format::U8:
210                 {
211                     // We need to convert the data from RGB to grayscale:
212                     // Iterate through every pixel of the image
213                     arm_compute::Window window;
214                     window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1));
215                     window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
216
217                     arm_compute::Iterator out(&image, window);
218
219                     unsigned char red   = 0;
220                     unsigned char green = 0;
221                     unsigned char blue  = 0;
222
223                     arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
224                     {
225                         red   = _fs.get();
226                         green = _fs.get();
227                         blue  = _fs.get();
228
229                         *out.ptr() = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
230                     },
231                     out);
232
233                     break;
234                 }
235                 case arm_compute::Format::RGB888:
236                 {
237                     // There is no format conversion needed: we can simply copy the content of the input file to the image one row at the time.
238                     // Create a vertical window to iterate through the image's rows:
239                     arm_compute::Window window;
240                     window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
241
242                     arm_compute::Iterator out(&image, window);
243
244                     arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
245                     {
246                         // Copy one row from the input file to the current row of the image:
247                         _fs.read(reinterpret_cast<std::fstream::char_type *>(out.ptr()), _width * image.info()->element_size());
248                     },
249                     out);
250
251                     break;
252                 }
253                 default:
254                     ARM_COMPUTE_ERROR("Unsupported format");
255             }
256
257             // Unmap buffer if creating a CLTensor
258             unmap(image);
259         }
260         catch(const std::ifstream::failure &e)
261         {
262             ARM_COMPUTE_ERROR("Loading PPM file: %s", e.what());
263         }
264     }
265
266     /** Fill a tensor with 3 planes (one for each channel) with the content of the currently open PPM file.
267      *
268      * @note If the image is a CLImage, the function maps and unmaps the image
269      *
270      * @param[in,out] tensor Tensor with 3 planes to fill (Must be allocated, and of matching dimensions with the opened PPM). Data types supported: U8/F32
271      * @param[in]     bgr    (Optional) Fill the first plane with blue channel (default = false)
272      */
273     template <typename T>
274     void fill_planar_tensor(T &tensor, bool bgr = false)
275     {
276         ARM_COMPUTE_ERROR_ON(!is_open());
277         ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::U8, DataType::F32);
278         ARM_COMPUTE_ERROR_ON(tensor.info()->dimension(0) != _width || tensor.info()->dimension(1) != _height || tensor.info()->dimension(2) != 3);
279
280         try
281         {
282             // Map buffer if creating a CLTensor
283             map(tensor, true);
284
285             // Check if the file is large enough to fill the image
286             const size_t current_position = _fs.tellg();
287             _fs.seekg(0, std::ios_base::end);
288             const size_t end_position = _fs.tellg();
289             _fs.seekg(current_position, std::ios_base::beg);
290
291             ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < tensor.info()->tensor_shape().total_size(),
292                                      "Not enough data in file");
293             ARM_COMPUTE_UNUSED(end_position);
294
295             // Iterate through every pixel of the image
296             arm_compute::Window window;
297             window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1));
298             window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
299             window.set(arm_compute::Window::DimZ, arm_compute::Window::Dimension(0, 1, 1));
300
301             arm_compute::Iterator out(&tensor, window);
302
303             unsigned char red   = 0;
304             unsigned char green = 0;
305             unsigned char blue  = 0;
306
307             size_t stride_z = tensor.info()->strides_in_bytes()[2];
308
309             arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
310             {
311                 red   = _fs.get();
312                 green = _fs.get();
313                 blue  = _fs.get();
314
315                 switch(tensor.info()->data_type())
316                 {
317                     case arm_compute::DataType::U8:
318                     {
319                         *(out.ptr() + 0 * stride_z) = bgr ? blue : red;
320                         *(out.ptr() + 1 * stride_z) = green;
321                         *(out.ptr() + 2 * stride_z) = bgr ? red : blue;
322                         break;
323                     }
324                     case arm_compute::DataType::F32:
325                     {
326                         *reinterpret_cast<float *>(out.ptr() + 0 * stride_z) = static_cast<float>(bgr ? blue : red);
327                         *reinterpret_cast<float *>(out.ptr() + 1 * stride_z) = static_cast<float>(green);
328                         *reinterpret_cast<float *>(out.ptr() + 2 * stride_z) = static_cast<float>(bgr ? red : blue);
329                         break;
330                     }
331                     default:
332                     {
333                         ARM_COMPUTE_ERROR("Unsupported data type");
334                     }
335                 }
336             },
337             out);
338
339             // Unmap buffer if creating a CLTensor
340             unmap(tensor);
341         }
342         catch(const std::ifstream::failure &e)
343         {
344             ARM_COMPUTE_ERROR("Loading PPM file: %s", e.what());
345         }
346     }
347
348 private:
349     std::ifstream _fs;
350     unsigned int  _width, _height;
351 };
352
353 /** Template helper function to save a tensor image to a PPM file.
354  *
355  * @note Only U8 and RGB888 formats supported.
356  * @note Only works with 2D tensors.
357  * @note If the input tensor is a CLTensor, the function maps and unmaps the image
358  *
359  * @param[in] tensor       The tensor to save as PPM file
360  * @param[in] ppm_filename Filename of the file to create.
361  */
362 template <typename T>
363 void save_to_ppm(T &tensor, const std::string &ppm_filename)
364 {
365     ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&tensor, arm_compute::Format::RGB888, arm_compute::Format::U8);
366     ARM_COMPUTE_ERROR_ON(tensor.info()->num_dimensions() > 2);
367
368     std::ofstream fs;
369
370     try
371     {
372         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
373         fs.open(ppm_filename, std::ios::out | std::ios::binary);
374
375         const unsigned int width  = tensor.info()->tensor_shape()[0];
376         const unsigned int height = tensor.info()->tensor_shape()[1];
377
378         fs << "P6\n"
379            << width << " " << height << " 255\n";
380
381         // Map buffer if creating a CLTensor
382         map(tensor, true);
383
384         switch(tensor.info()->format())
385         {
386             case arm_compute::Format::U8:
387             {
388                 arm_compute::Window window;
389                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, 1));
390                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
391
392                 arm_compute::Iterator in(&tensor, window);
393
394                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
395                 {
396                     const unsigned char value = *in.ptr();
397
398                     fs << value << value << value;
399                 },
400                 in);
401
402                 break;
403             }
404             case arm_compute::Format::RGB888:
405             {
406                 arm_compute::Window window;
407                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, width));
408                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
409
410                 arm_compute::Iterator in(&tensor, window);
411
412                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
413                 {
414                     fs.write(reinterpret_cast<std::fstream::char_type *>(in.ptr()), width * tensor.info()->element_size());
415                 },
416                 in);
417
418                 break;
419             }
420             default:
421                 ARM_COMPUTE_ERROR("Unsupported format");
422         }
423
424         // Unmap buffer if creating a CLTensor
425         unmap(tensor);
426     }
427     catch(const std::ofstream::failure &e)
428     {
429         ARM_COMPUTE_ERROR("Writing %s: (%s)", ppm_filename.c_str(), e.what());
430     }
431 }
432
433 /** Load the tensor with pre-trained data from a binary file
434  *
435  * @param[in] tensor   The tensor to be filled. Data type supported: F32.
436  * @param[in] filename Filename of the binary file to load from.
437  */
438 template <typename T>
439 void load_trained_data(T &tensor, const std::string &filename)
440 {
441     ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::F32);
442
443     std::ifstream fs;
444
445     try
446     {
447         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
448         // Open file
449         fs.open(filename, std::ios::in | std::ios::binary);
450
451         if(!fs.good())
452         {
453             throw std::runtime_error("Could not load binary data: " + filename);
454         }
455
456         // Map buffer if creating a CLTensor
457         map(tensor, true);
458
459         Window window;
460
461         window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, 1, 1));
462
463         for(unsigned int d = 1; d < tensor.info()->num_dimensions(); ++d)
464         {
465             window.set(d, Window::Dimension(0, tensor.info()->tensor_shape()[d], 1));
466         }
467
468         arm_compute::Iterator in(&tensor, window);
469
470         execute_window_loop(window, [&](const Coordinates & id)
471         {
472             fs.read(reinterpret_cast<std::fstream::char_type *>(in.ptr()), tensor.info()->tensor_shape()[0] * tensor.info()->element_size());
473         },
474         in);
475
476 #ifdef ARM_COMPUTE_CL
477         // Unmap buffer if creating a CLTensor
478         unmap(tensor);
479 #endif /* ARM_COMPUTE_CL */
480     }
481     catch(const std::ofstream::failure &e)
482     {
483         ARM_COMPUTE_ERROR("Writing %s: (%s)", filename.c_str(), e.what());
484     }
485 }
486
487 /** Obtain numpy type string from DataType.
488  *
489  * @param[in] data_type Data type.
490  *
491  * @return numpy type string.
492  */
493 inline std::string get_typestring(DataType data_type)
494 {
495     // Check endianness
496     const unsigned int i = 1;
497     const char        *c = reinterpret_cast<const char *>(&i);
498     std::string        endianness;
499     if(*c == 1)
500     {
501         endianness = std::string("<");
502     }
503     else
504     {
505         endianness = std::string(">");
506     }
507     const std::string no_endianness("|");
508
509     switch(data_type)
510     {
511         case DataType::U8:
512             return no_endianness + "u" + support::cpp11::to_string(sizeof(uint8_t));
513         case DataType::S8:
514             return no_endianness + "i" + support::cpp11::to_string(sizeof(int8_t));
515         case DataType::U16:
516             return endianness + "u" + support::cpp11::to_string(sizeof(uint16_t));
517         case DataType::S16:
518             return endianness + "i" + support::cpp11::to_string(sizeof(int16_t));
519         case DataType::U32:
520             return endianness + "u" + support::cpp11::to_string(sizeof(uint32_t));
521         case DataType::S32:
522             return endianness + "i" + support::cpp11::to_string(sizeof(int32_t));
523         case DataType::U64:
524             return endianness + "u" + support::cpp11::to_string(sizeof(uint64_t));
525         case DataType::S64:
526             return endianness + "i" + support::cpp11::to_string(sizeof(int64_t));
527         case DataType::F32:
528             return endianness + "f" + support::cpp11::to_string(sizeof(float));
529         case DataType::F64:
530             return endianness + "f" + support::cpp11::to_string(sizeof(double));
531         case DataType::SIZET:
532             return endianness + "u" + support::cpp11::to_string(sizeof(size_t));
533         default:
534             ARM_COMPUTE_ERROR("NOT SUPPORTED!");
535     }
536 }
537 } // namespace utils
538 } // namespace arm_compute
539 #endif /* __UTILS_UTILS_H__*/