arm_compute v17.10
[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 private:
267     std::ifstream _fs;
268     unsigned int  _width, _height;
269 };
270
271 /** Template helper function to save a tensor image to a PPM file.
272  *
273  * @note Only U8 and RGB888 formats supported.
274  * @note Only works with 2D tensors.
275  * @note If the input tensor is a CLTensor, the function maps and unmaps the image
276  *
277  * @param[in] tensor       The tensor to save as PPM file
278  * @param[in] ppm_filename Filename of the file to create.
279  */
280 template <typename T>
281 void save_to_ppm(T &tensor, const std::string &ppm_filename)
282 {
283     ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&tensor, arm_compute::Format::RGB888, arm_compute::Format::U8);
284     ARM_COMPUTE_ERROR_ON(tensor.info()->num_dimensions() > 2);
285
286     std::ofstream fs;
287
288     try
289     {
290         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
291         fs.open(ppm_filename, std::ios::out | std::ios::binary);
292
293         const unsigned int width  = tensor.info()->tensor_shape()[0];
294         const unsigned int height = tensor.info()->tensor_shape()[1];
295
296         fs << "P6\n"
297            << width << " " << height << " 255\n";
298
299         // Map buffer if creating a CLTensor
300         map(tensor, true);
301
302         switch(tensor.info()->format())
303         {
304             case arm_compute::Format::U8:
305             {
306                 arm_compute::Window window;
307                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, 1));
308                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
309
310                 arm_compute::Iterator in(&tensor, window);
311
312                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
313                 {
314                     const unsigned char value = *in.ptr();
315
316                     fs << value << value << value;
317                 },
318                 in);
319
320                 break;
321             }
322             case arm_compute::Format::RGB888:
323             {
324                 arm_compute::Window window;
325                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, width));
326                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
327
328                 arm_compute::Iterator in(&tensor, window);
329
330                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
331                 {
332                     fs.write(reinterpret_cast<std::fstream::char_type *>(in.ptr()), width * tensor.info()->element_size());
333                 },
334                 in);
335
336                 break;
337             }
338             default:
339                 ARM_COMPUTE_ERROR("Unsupported format");
340         }
341
342         // Unmap buffer if creating a CLTensor
343         unmap(tensor);
344     }
345     catch(const std::ofstream::failure &e)
346     {
347         ARM_COMPUTE_ERROR("Writing %s: (%s)", ppm_filename.c_str(), e.what());
348     }
349 }
350
351 /** Load the tensor with pre-trained data from a binary file
352  *
353  * @param[in] tensor   The tensor to be filled. Data type supported: F32.
354  * @param[in] filename Filename of the binary file to load from.
355  */
356 template <typename T>
357 void load_trained_data(T &tensor, const std::string &filename)
358 {
359     ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::F32);
360
361     std::ifstream fs;
362
363     try
364     {
365         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
366         // Open file
367         fs.open(filename, std::ios::in | std::ios::binary);
368
369         if(!fs.good())
370         {
371             throw std::runtime_error("Could not load binary data: " + filename);
372         }
373
374         // Map buffer if creating a CLTensor
375         map(tensor, true);
376
377         Window window;
378
379         window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, 1, 1));
380
381         for(unsigned int d = 1; d < tensor.info()->num_dimensions(); ++d)
382         {
383             window.set(d, Window::Dimension(0, tensor.info()->tensor_shape()[d], 1));
384         }
385
386         arm_compute::Iterator in(&tensor, window);
387
388         execute_window_loop(window, [&](const Coordinates & id)
389         {
390             fs.read(reinterpret_cast<std::fstream::char_type *>(in.ptr()), tensor.info()->tensor_shape()[0] * tensor.info()->element_size());
391         },
392         in);
393
394 #ifdef ARM_COMPUTE_CL
395         // Unmap buffer if creating a CLTensor
396         unmap(tensor);
397 #endif /* ARM_COMPUTE_CL */
398     }
399     catch(const std::ofstream::failure &e)
400     {
401         ARM_COMPUTE_ERROR("Writing %s: (%s)", filename.c_str(), e.what());
402     }
403 }
404
405 /** Obtain numpy type string from DataType.
406  *
407  * @param[in] data_type Data type.
408  *
409  * @return numpy type string.
410  */
411 inline std::string get_typestring(DataType data_type)
412 {
413     // Check endianness
414     const unsigned int i = 1;
415     const char        *c = reinterpret_cast<const char *>(&i);
416     std::string        endianness;
417     if(*c == 1)
418     {
419         endianness = std::string("<");
420     }
421     else
422     {
423         endianness = std::string(">");
424     }
425     const std::string no_endianness("|");
426
427     switch(data_type)
428     {
429         case DataType::U8:
430             return no_endianness + "u" + support::cpp11::to_string(sizeof(uint8_t));
431         case DataType::S8:
432             return no_endianness + "i" + support::cpp11::to_string(sizeof(int8_t));
433         case DataType::U16:
434             return endianness + "u" + support::cpp11::to_string(sizeof(uint16_t));
435         case DataType::S16:
436             return endianness + "i" + support::cpp11::to_string(sizeof(int16_t));
437         case DataType::U32:
438             return endianness + "u" + support::cpp11::to_string(sizeof(uint32_t));
439         case DataType::S32:
440             return endianness + "i" + support::cpp11::to_string(sizeof(int32_t));
441         case DataType::U64:
442             return endianness + "u" + support::cpp11::to_string(sizeof(uint64_t));
443         case DataType::S64:
444             return endianness + "i" + support::cpp11::to_string(sizeof(int64_t));
445         case DataType::F32:
446             return endianness + "f" + support::cpp11::to_string(sizeof(float));
447         case DataType::F64:
448             return endianness + "f" + support::cpp11::to_string(sizeof(double));
449         case DataType::SIZET:
450             return endianness + "u" + support::cpp11::to_string(sizeof(size_t));
451         default:
452             ARM_COMPUTE_ERROR("NOT SUPPORTED!");
453     }
454 }
455 } // namespace utils
456 } // namespace arm_compute
457 #endif /* __UTILS_UTILS_H__*/