arm_compute v18.05
[platform/upstream/armcl.git] / utils / Utils.h
1 /*
2  * Copyright (c) 2016-2018 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 "libnpy/npy.hpp"
34 #include "support/ToolchainSupport.h"
35
36 #ifdef ARM_COMPUTE_CL
37 #include "arm_compute/core/CL/OpenCL.h"
38 #include "arm_compute/runtime/CL/CLDistribution1D.h"
39 #include "arm_compute/runtime/CL/CLTensor.h"
40 #endif /* ARM_COMPUTE_CL */
41 #ifdef ARM_COMPUTE_GC
42 #include "arm_compute/runtime/GLES_COMPUTE/GCTensor.h"
43 #endif /* ARM_COMPUTE_GC */
44
45 #include <cstdlib>
46 #include <cstring>
47 #include <fstream>
48 #include <iostream>
49 #include <random>
50 #include <string>
51 #include <tuple>
52 #include <vector>
53
54 namespace arm_compute
55 {
56 namespace utils
57 {
58 /** Abstract Example class.
59  *
60  * All examples have to inherit from this class.
61  */
62 class Example
63 {
64 public:
65     /** Setup the example.
66      *
67      * @param[in] argc Argument count.
68      * @param[in] argv Argument values.
69      */
70     virtual void do_setup(int argc, char **argv) {};
71     /** Run the example. */
72     virtual void do_run() {};
73     /** Teardown the example. */
74     virtual void do_teardown() {};
75
76     /** Default destructor. */
77     virtual ~Example() = default;
78 };
79
80 /** Run an example and handle the potential exceptions it throws
81  *
82  * @param[in] argc    Number of command line arguments
83  * @param[in] argv    Command line arguments
84  * @param[in] example Example to run
85  */
86 int run_example(int argc, char **argv, std::unique_ptr<Example> example);
87
88 template <typename T>
89 int run_example(int argc, char **argv)
90 {
91     return run_example(argc, argv, support::cpp14::make_unique<T>());
92 }
93
94 /** Draw a RGB rectangular window for the detected object
95  *
96  * @param[in, out] tensor Input tensor where the rectangle will be drawn on. Format supported: RGB888
97  * @param[in]      rect   Geometry of the rectangular window
98  * @param[in]      r      Red colour to use
99  * @param[in]      g      Green colour to use
100  * @param[in]      b      Blue colour to use
101  */
102 void draw_detection_rectangle(arm_compute::ITensor *tensor, const arm_compute::DetectionWindow &rect, uint8_t r, uint8_t g, uint8_t b);
103
104 /** Parse the ppm header from an input file stream. At the end of the execution,
105  *  the file position pointer will be located at the first pixel stored in the ppm file
106  *
107  * @param[in] fs Input file stream to parse
108  *
109  * @return The width, height and max value stored in the header of the PPM file
110  */
111 std::tuple<unsigned int, unsigned int, int> parse_ppm_header(std::ifstream &fs);
112
113 /** Parse the npy header from an input file stream. At the end of the execution,
114  *  the file position pointer will be located at the first pixel stored in the npy file
115  *
116  * @param[in] fs Input file stream to parse
117  *
118  * @return The width and height stored in the header of the NPY file
119  */
120 std::tuple<std::vector<unsigned long>, bool, std::string> parse_npy_header(std::ifstream &fs);
121
122 /** Obtain numpy type string from DataType.
123  *
124  * @param[in] data_type Data type.
125  *
126  * @return numpy type string.
127  */
128 inline std::string get_typestring(DataType data_type)
129 {
130     // Check endianness
131     const unsigned int i = 1;
132     const char        *c = reinterpret_cast<const char *>(&i);
133     std::string        endianness;
134     if(*c == 1)
135     {
136         endianness = std::string("<");
137     }
138     else
139     {
140         endianness = std::string(">");
141     }
142     const std::string no_endianness("|");
143
144     switch(data_type)
145     {
146         case DataType::U8:
147         case DataType::QASYMM8:
148             return no_endianness + "u" + support::cpp11::to_string(sizeof(uint8_t));
149         case DataType::S8:
150             return no_endianness + "i" + support::cpp11::to_string(sizeof(int8_t));
151         case DataType::U16:
152             return endianness + "u" + support::cpp11::to_string(sizeof(uint16_t));
153         case DataType::S16:
154             return endianness + "i" + support::cpp11::to_string(sizeof(int16_t));
155         case DataType::U32:
156             return endianness + "u" + support::cpp11::to_string(sizeof(uint32_t));
157         case DataType::S32:
158             return endianness + "i" + support::cpp11::to_string(sizeof(int32_t));
159         case DataType::U64:
160             return endianness + "u" + support::cpp11::to_string(sizeof(uint64_t));
161         case DataType::S64:
162             return endianness + "i" + support::cpp11::to_string(sizeof(int64_t));
163         case DataType::F32:
164             return endianness + "f" + support::cpp11::to_string(sizeof(float));
165         case DataType::F64:
166             return endianness + "f" + support::cpp11::to_string(sizeof(double));
167         case DataType::SIZET:
168             return endianness + "u" + support::cpp11::to_string(sizeof(size_t));
169         default:
170             ARM_COMPUTE_ERROR("NOT SUPPORTED!");
171     }
172 }
173
174 /** Maps a tensor if needed
175  *
176  * @param[in] tensor   Tensor to be mapped
177  * @param[in] blocking Specified if map is blocking or not
178  */
179 template <typename T>
180 inline void map(T &tensor, bool blocking)
181 {
182     ARM_COMPUTE_UNUSED(tensor);
183     ARM_COMPUTE_UNUSED(blocking);
184 }
185
186 /** Unmaps a tensor if needed
187  *
188  * @param tensor  Tensor to be unmapped
189  */
190 template <typename T>
191 inline void unmap(T &tensor)
192 {
193     ARM_COMPUTE_UNUSED(tensor);
194 }
195
196 #ifdef ARM_COMPUTE_CL
197 /** Maps a tensor if needed
198  *
199  * @param[in] tensor   Tensor to be mapped
200  * @param[in] blocking Specified if map is blocking or not
201  */
202 inline void map(CLTensor &tensor, bool blocking)
203 {
204     tensor.map(blocking);
205 }
206
207 /** Unmaps a tensor if needed
208  *
209  * @param tensor  Tensor to be unmapped
210  */
211 inline void unmap(CLTensor &tensor)
212 {
213     tensor.unmap();
214 }
215
216 /** Maps a distribution if needed
217  *
218  * @param[in] distribution Distribution to be mapped
219  * @param[in] blocking     Specified if map is blocking or not
220  */
221 inline void map(CLDistribution1D &distribution, bool blocking)
222 {
223     distribution.map(blocking);
224 }
225
226 /** Unmaps a distribution if needed
227  *
228  * @param distribution  Distribution to be unmapped
229  */
230 inline void unmap(CLDistribution1D &distribution)
231 {
232     distribution.unmap();
233 }
234 #endif /* ARM_COMPUTE_CL */
235
236 #ifdef ARM_COMPUTE_GC
237 /** Maps a tensor if needed
238  *
239  * @param[in] tensor   Tensor to be mapped
240  * @param[in] blocking Specified if map is blocking or not
241  */
242 inline void map(GCTensor &tensor, bool blocking)
243 {
244     tensor.map(blocking);
245 }
246
247 /** Unmaps a tensor if needed
248  *
249  * @param tensor  Tensor to be unmapped
250  */
251 inline void unmap(GCTensor &tensor)
252 {
253     tensor.unmap();
254 }
255 #endif /* ARM_COMPUTE_GC */
256
257 /** Class to load the content of a PPM file into an Image
258  */
259 class PPMLoader
260 {
261 public:
262     PPMLoader()
263         : _fs(), _width(0), _height(0)
264     {
265     }
266     /** Open a PPM file and reads its metadata (Width, height)
267      *
268      * @param[in] ppm_filename File to open
269      */
270     void open(const std::string &ppm_filename)
271     {
272         ARM_COMPUTE_ERROR_ON(is_open());
273         try
274         {
275             _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
276             _fs.open(ppm_filename, std::ios::in | std::ios::binary);
277
278             unsigned int max_val = 0;
279             std::tie(_width, _height, max_val) = parse_ppm_header(_fs);
280
281             ARM_COMPUTE_ERROR_ON_MSG(max_val >= 256, "2 bytes per colour channel not supported in file %s", ppm_filename.c_str());
282         }
283         catch(std::runtime_error &e)
284         {
285             ARM_COMPUTE_ERROR("Accessing %s: %s", ppm_filename.c_str(), e.what());
286         }
287     }
288     /** Return true if a PPM file is currently open
289          */
290     bool is_open()
291     {
292         return _fs.is_open();
293     }
294
295     /** Initialise an image's metadata with the dimensions of the PPM file currently open
296      *
297      * @param[out] image  Image to initialise
298      * @param[in]  format Format to use for the image (Must be RGB888 or U8)
299      */
300     template <typename T>
301     void init_image(T &image, arm_compute::Format format)
302     {
303         ARM_COMPUTE_ERROR_ON(!is_open());
304         ARM_COMPUTE_ERROR_ON(format != arm_compute::Format::RGB888 && format != arm_compute::Format::U8);
305
306         // Use the size of the input PPM image
307         arm_compute::TensorInfo image_info(_width, _height, format);
308         image.allocator()->init(image_info);
309     }
310
311     /** Fill an image with the content of the currently open PPM file.
312      *
313      * @note If the image is a CLImage, the function maps and unmaps the image
314      *
315      * @param[in,out] image Image to fill (Must be allocated, and of matching dimensions with the opened PPM).
316      */
317     template <typename T>
318     void fill_image(T &image)
319     {
320         ARM_COMPUTE_ERROR_ON(!is_open());
321         ARM_COMPUTE_ERROR_ON(image.info()->dimension(0) != _width || image.info()->dimension(1) != _height);
322         ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&image, arm_compute::Format::U8, arm_compute::Format::RGB888);
323         try
324         {
325             // Map buffer if creating a CLTensor/GCTensor
326             map(image, true);
327
328             // Check if the file is large enough to fill the image
329             const size_t current_position = _fs.tellg();
330             _fs.seekg(0, std::ios_base::end);
331             const size_t end_position = _fs.tellg();
332             _fs.seekg(current_position, std::ios_base::beg);
333
334             ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < image.info()->tensor_shape().total_size() * image.info()->element_size(),
335                                      "Not enough data in file");
336             ARM_COMPUTE_UNUSED(end_position);
337
338             switch(image.info()->format())
339             {
340                 case arm_compute::Format::U8:
341                 {
342                     // We need to convert the data from RGB to grayscale:
343                     // Iterate through every pixel of the image
344                     arm_compute::Window window;
345                     window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1));
346                     window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
347
348                     arm_compute::Iterator out(&image, window);
349
350                     unsigned char red   = 0;
351                     unsigned char green = 0;
352                     unsigned char blue  = 0;
353
354                     arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
355                     {
356                         red   = _fs.get();
357                         green = _fs.get();
358                         blue  = _fs.get();
359
360                         *out.ptr() = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
361                     },
362                     out);
363
364                     break;
365                 }
366                 case arm_compute::Format::RGB888:
367                 {
368                     // There is no format conversion needed: we can simply copy the content of the input file to the image one row at the time.
369                     // Create a vertical window to iterate through the image's rows:
370                     arm_compute::Window window;
371                     window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
372
373                     arm_compute::Iterator out(&image, window);
374
375                     arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
376                     {
377                         // Copy one row from the input file to the current row of the image:
378                         _fs.read(reinterpret_cast<std::fstream::char_type *>(out.ptr()), _width * image.info()->element_size());
379                     },
380                     out);
381
382                     break;
383                 }
384                 default:
385                     ARM_COMPUTE_ERROR("Unsupported format");
386             }
387
388             // Unmap buffer if creating a CLTensor/GCTensor
389             unmap(image);
390         }
391         catch(const std::ifstream::failure &e)
392         {
393             ARM_COMPUTE_ERROR("Loading PPM file: %s", e.what());
394         }
395     }
396
397     /** Fill a tensor with 3 planes (one for each channel) with the content of the currently open PPM file.
398      *
399      * @note If the image is a CLImage, the function maps and unmaps the image
400      *
401      * @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
402      * @param[in]     bgr    (Optional) Fill the first plane with blue channel (default = false)
403      */
404     template <typename T>
405     void fill_planar_tensor(T &tensor, bool bgr = false)
406     {
407         ARM_COMPUTE_ERROR_ON(!is_open());
408         ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::U8, DataType::F32);
409
410         const DataLayout  data_layout  = tensor.info()->data_layout();
411         const TensorShape tensor_shape = tensor.info()->tensor_shape();
412
413         ARM_COMPUTE_UNUSED(tensor_shape);
414         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH)] != _width);
415         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT)] != _height);
416         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::CHANNEL)] != 3);
417
418         try
419         {
420             // Map buffer if creating a CLTensor
421             map(tensor, true);
422
423             // Check if the file is large enough to fill the image
424             const size_t current_position = _fs.tellg();
425             _fs.seekg(0, std::ios_base::end);
426             const size_t end_position = _fs.tellg();
427             _fs.seekg(current_position, std::ios_base::beg);
428
429             ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < tensor.info()->tensor_shape().total_size(),
430                                      "Not enough data in file");
431             ARM_COMPUTE_UNUSED(end_position);
432
433             // Stride across channels
434             size_t stride_z = 0;
435
436             // Iterate through every pixel of the image
437             arm_compute::Window window;
438             if(data_layout == DataLayout::NCHW)
439             {
440                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1));
441                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1));
442                 window.set(arm_compute::Window::DimZ, arm_compute::Window::Dimension(0, 1, 1));
443                 stride_z = tensor.info()->strides_in_bytes()[2];
444             }
445             else
446             {
447                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, 1, 1));
448                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _width, 1));
449                 window.set(arm_compute::Window::DimZ, arm_compute::Window::Dimension(0, _height, 1));
450                 stride_z = tensor.info()->strides_in_bytes()[0];
451             }
452
453             arm_compute::Iterator out(&tensor, window);
454
455             unsigned char red   = 0;
456             unsigned char green = 0;
457             unsigned char blue  = 0;
458
459             arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
460             {
461                 red   = _fs.get();
462                 green = _fs.get();
463                 blue  = _fs.get();
464
465                 switch(tensor.info()->data_type())
466                 {
467                     case arm_compute::DataType::U8:
468                     {
469                         *(out.ptr() + 0 * stride_z) = bgr ? blue : red;
470                         *(out.ptr() + 1 * stride_z) = green;
471                         *(out.ptr() + 2 * stride_z) = bgr ? red : blue;
472                         break;
473                     }
474                     case arm_compute::DataType::F32:
475                     {
476                         *reinterpret_cast<float *>(out.ptr() + 0 * stride_z) = static_cast<float>(bgr ? blue : red);
477                         *reinterpret_cast<float *>(out.ptr() + 1 * stride_z) = static_cast<float>(green);
478                         *reinterpret_cast<float *>(out.ptr() + 2 * stride_z) = static_cast<float>(bgr ? red : blue);
479                         break;
480                     }
481                     default:
482                     {
483                         ARM_COMPUTE_ERROR("Unsupported data type");
484                     }
485                 }
486             },
487             out);
488
489             // Unmap buffer if creating a CLTensor
490             unmap(tensor);
491         }
492         catch(const std::ifstream::failure &e)
493         {
494             ARM_COMPUTE_ERROR("Loading PPM file: %s", e.what());
495         }
496     }
497
498     /** Return the width of the currently open PPM file.
499      */
500     unsigned int width() const
501     {
502         return _width;
503     }
504
505     /** Return the height of the currently open PPM file.
506      */
507     unsigned int height() const
508     {
509         return _height;
510     }
511
512 private:
513     std::ifstream _fs;
514     unsigned int  _width, _height;
515 };
516
517 /** Numpy data loader */
518 class NPYLoader
519 {
520 public:
521     /** Default constructor */
522     NPYLoader()
523         : _fs(), _shape(), _fortran_order(false), _typestring()
524     {
525     }
526
527     /** Open a NPY file and reads its metadata
528      *
529      * @param[in] npy_filename File to open
530      */
531     void open(const std::string &npy_filename)
532     {
533         ARM_COMPUTE_ERROR_ON(is_open());
534         try
535         {
536             _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
537             _fs.open(npy_filename, std::ios::in | std::ios::binary);
538
539             std::tie(_shape, _fortran_order, _typestring) = parse_npy_header(_fs);
540         }
541         catch(const std::ifstream::failure &e)
542         {
543             ARM_COMPUTE_ERROR("Accessing %s: %s", npy_filename.c_str(), e.what());
544         }
545     }
546     /** Return true if a NPY file is currently open */
547     bool is_open()
548     {
549         return _fs.is_open();
550     }
551
552     /** Return true if a NPY file is in fortran order */
553     bool is_fortran()
554     {
555         return _fortran_order;
556     }
557
558     /** Initialise the tensor's metadata with the dimensions of the NPY file currently open
559      *
560      * @param[out] tensor Tensor to initialise
561      * @param[in]  dt     Data type to use for the tensor
562      */
563     template <typename T>
564     void init_tensor(T &tensor, arm_compute::DataType dt)
565     {
566         ARM_COMPUTE_ERROR_ON(!is_open());
567         ARM_COMPUTE_ERROR_ON(dt != arm_compute::DataType::F32);
568
569         // Use the size of the input NPY tensor
570         TensorShape shape;
571         shape.set_num_dimensions(_shape.size());
572         for(size_t i = 0; i < _shape.size(); ++i)
573         {
574             shape.set(i, _shape.at(i));
575         }
576
577         arm_compute::TensorInfo tensor_info(shape, 1, dt);
578         tensor.allocator()->init(tensor_info);
579     }
580
581     /** Fill a tensor with the content of the currently open NPY file.
582      *
583      * @note If the tensor is a CLTensor, the function maps and unmaps the tensor
584      *
585      * @param[in,out] tensor Tensor to fill (Must be allocated, and of matching dimensions with the opened NPY).
586      */
587     template <typename T>
588     void fill_tensor(T &tensor)
589     {
590         ARM_COMPUTE_ERROR_ON(!is_open());
591         ARM_COMPUTE_ERROR_ON_DATA_TYPE_NOT_IN(&tensor, arm_compute::DataType::F32);
592         try
593         {
594             // Map buffer if creating a CLTensor
595             map(tensor, true);
596
597             // Check if the file is large enough to fill the tensor
598             const size_t current_position = _fs.tellg();
599             _fs.seekg(0, std::ios_base::end);
600             const size_t end_position = _fs.tellg();
601             _fs.seekg(current_position, std::ios_base::beg);
602
603             ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < tensor.info()->tensor_shape().total_size() * tensor.info()->element_size(),
604                                      "Not enough data in file");
605             ARM_COMPUTE_UNUSED(end_position);
606
607             // Check if the typestring matches the given one
608             std::string expect_typestr = get_typestring(tensor.info()->data_type());
609             ARM_COMPUTE_ERROR_ON_MSG(_typestring != expect_typestr, "Typestrings mismatch");
610
611             // Validate tensor shape
612             ARM_COMPUTE_ERROR_ON_MSG(_shape.size() != tensor.info()->tensor_shape().num_dimensions(), "Tensor ranks mismatch");
613             if(_fortran_order)
614             {
615                 for(size_t i = 0; i < _shape.size(); ++i)
616                 {
617                     ARM_COMPUTE_ERROR_ON_MSG(tensor.info()->tensor_shape()[i] != _shape[i], "Tensor dimensions mismatch");
618                 }
619             }
620             else
621             {
622                 for(size_t i = 0; i < _shape.size(); ++i)
623                 {
624                     ARM_COMPUTE_ERROR_ON_MSG(tensor.info()->tensor_shape()[i] != _shape[_shape.size() - i - 1], "Tensor dimensions mismatch");
625                 }
626             }
627
628             switch(tensor.info()->data_type())
629             {
630                 case arm_compute::DataType::F32:
631                 {
632                     // Read data
633                     if(tensor.info()->padding().empty())
634                     {
635                         // If tensor has no padding read directly from stream.
636                         _fs.read(reinterpret_cast<char *>(tensor.buffer()), tensor.info()->total_size());
637                     }
638                     else
639                     {
640                         // If tensor has padding accessing tensor elements through execution window.
641                         Window window;
642                         window.use_tensor_dimensions(tensor.info()->tensor_shape());
643
644                         execute_window_loop(window, [&](const Coordinates & id)
645                         {
646                             _fs.read(reinterpret_cast<char *>(tensor.ptr_to_element(id)), tensor.info()->element_size());
647                         });
648                     }
649
650                     break;
651                 }
652                 default:
653                     ARM_COMPUTE_ERROR("Unsupported data type");
654             }
655
656             // Unmap buffer if creating a CLTensor
657             unmap(tensor);
658         }
659         catch(const std::ifstream::failure &e)
660         {
661             ARM_COMPUTE_ERROR("Loading NPY file: %s", e.what());
662         }
663     }
664
665 private:
666     std::ifstream              _fs;
667     std::vector<unsigned long> _shape;
668     bool                       _fortran_order;
669     std::string                _typestring;
670 };
671
672 /** Template helper function to save a tensor image to a PPM file.
673  *
674  * @note Only U8 and RGB888 formats supported.
675  * @note Only works with 2D tensors.
676  * @note If the input tensor is a CLTensor, the function maps and unmaps the image
677  *
678  * @param[in] tensor       The tensor to save as PPM file
679  * @param[in] ppm_filename Filename of the file to create.
680  */
681 template <typename T>
682 void save_to_ppm(T &tensor, const std::string &ppm_filename)
683 {
684     ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&tensor, arm_compute::Format::RGB888, arm_compute::Format::U8);
685     ARM_COMPUTE_ERROR_ON(tensor.info()->num_dimensions() > 2);
686
687     std::ofstream fs;
688
689     try
690     {
691         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
692         fs.open(ppm_filename, std::ios::out | std::ios::binary);
693
694         const unsigned int width  = tensor.info()->tensor_shape()[0];
695         const unsigned int height = tensor.info()->tensor_shape()[1];
696
697         fs << "P6\n"
698            << width << " " << height << " 255\n";
699
700         // Map buffer if creating a CLTensor/GCTensor
701         map(tensor, true);
702
703         switch(tensor.info()->format())
704         {
705             case arm_compute::Format::U8:
706             {
707                 arm_compute::Window window;
708                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, 1));
709                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
710
711                 arm_compute::Iterator in(&tensor, window);
712
713                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
714                 {
715                     const unsigned char value = *in.ptr();
716
717                     fs << value << value << value;
718                 },
719                 in);
720
721                 break;
722             }
723             case arm_compute::Format::RGB888:
724             {
725                 arm_compute::Window window;
726                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, width));
727                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
728
729                 arm_compute::Iterator in(&tensor, window);
730
731                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
732                 {
733                     fs.write(reinterpret_cast<std::fstream::char_type *>(in.ptr()), width * tensor.info()->element_size());
734                 },
735                 in);
736
737                 break;
738             }
739             default:
740                 ARM_COMPUTE_ERROR("Unsupported format");
741         }
742
743         // Unmap buffer if creating a CLTensor/GCTensor
744         unmap(tensor);
745     }
746     catch(const std::ofstream::failure &e)
747     {
748         ARM_COMPUTE_ERROR("Writing %s: (%s)", ppm_filename.c_str(), e.what());
749     }
750 }
751
752 /** Template helper function to save a tensor image to a NPY file.
753  *
754  * @note Only F32 data type supported.
755  * @note Only works with 2D tensors.
756  * @note If the input tensor is a CLTensor, the function maps and unmaps the image
757  *
758  * @param[in] tensor        The tensor to save as NPY file
759  * @param[in] npy_filename  Filename of the file to create.
760  * @param[in] fortran_order If true, save matrix in fortran order.
761  */
762 template <typename T>
763 void save_to_npy(T &tensor, const std::string &npy_filename, bool fortran_order)
764 {
765     ARM_COMPUTE_ERROR_ON_DATA_TYPE_NOT_IN(&tensor, arm_compute::DataType::F32);
766     ARM_COMPUTE_ERROR_ON(tensor.info()->num_dimensions() > 2);
767
768     std::ofstream fs;
769
770     try
771     {
772         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
773         fs.open(npy_filename, std::ios::out | std::ios::binary);
774
775         const unsigned int              width  = tensor.info()->tensor_shape()[0];
776         const unsigned int              height = tensor.info()->tensor_shape()[1];
777         std::vector<npy::ndarray_len_t> shape(2);
778
779         if(!fortran_order)
780         {
781             shape[0] = height, shape[1] = width;
782         }
783         else
784         {
785             shape[0] = width, shape[1] = height;
786         }
787
788         // Map buffer if creating a CLTensor
789         map(tensor, true);
790
791         switch(tensor.info()->data_type())
792         {
793             case arm_compute::DataType::F32:
794             {
795                 std::vector<float> tmp; /* Used only to get the typestring */
796                 npy::Typestring    typestring_o{ tmp };
797                 std::string        typestring = typestring_o.str();
798
799                 std::ofstream stream(npy_filename, std::ofstream::binary);
800                 npy::write_header(stream, typestring, fortran_order, shape);
801
802                 arm_compute::Window window;
803                 window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, 1));
804                 window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1));
805
806                 arm_compute::Iterator in(&tensor, window);
807
808                 arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id)
809                 {
810                     stream.write(reinterpret_cast<const char *>(in.ptr()), sizeof(float));
811                 },
812                 in);
813
814                 break;
815             }
816             default:
817                 ARM_COMPUTE_ERROR("Unsupported format");
818         }
819
820         // Unmap buffer if creating a CLTensor
821         unmap(tensor);
822     }
823     catch(const std::ofstream::failure &e)
824     {
825         ARM_COMPUTE_ERROR("Writing %s: (%s)", npy_filename.c_str(), e.what());
826     }
827 }
828
829 /** Load the tensor with pre-trained data from a binary file
830  *
831  * @param[in] tensor   The tensor to be filled. Data type supported: F32.
832  * @param[in] filename Filename of the binary file to load from.
833  */
834 template <typename T>
835 void load_trained_data(T &tensor, const std::string &filename)
836 {
837     ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::F32);
838
839     std::ifstream fs;
840
841     try
842     {
843         fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
844         // Open file
845         fs.open(filename, std::ios::in | std::ios::binary);
846
847         if(!fs.good())
848         {
849             throw std::runtime_error("Could not load binary data: " + filename);
850         }
851
852         // Map buffer if creating a CLTensor/GCTensor
853         map(tensor, true);
854
855         Window window;
856
857         window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, 1, 1));
858
859         for(unsigned int d = 1; d < tensor.info()->num_dimensions(); ++d)
860         {
861             window.set(d, Window::Dimension(0, tensor.info()->tensor_shape()[d], 1));
862         }
863
864         arm_compute::Iterator in(&tensor, window);
865
866         execute_window_loop(window, [&](const Coordinates & id)
867         {
868             fs.read(reinterpret_cast<std::fstream::char_type *>(in.ptr()), tensor.info()->tensor_shape()[0] * tensor.info()->element_size());
869         },
870         in);
871
872         // Unmap buffer if creating a CLTensor/GCTensor
873         unmap(tensor);
874     }
875     catch(const std::ofstream::failure &e)
876     {
877         ARM_COMPUTE_ERROR("Writing %s: (%s)", filename.c_str(), e.what());
878     }
879 }
880
881 template <typename T>
882 void fill_random_tensor(T &tensor, float lower_bound, float upper_bound)
883 {
884     std::random_device rd;
885     std::mt19937       gen(rd());
886
887     Window window;
888     window.use_tensor_dimensions(tensor.info()->tensor_shape());
889
890     map(tensor, true);
891
892     Iterator it(&tensor, window);
893
894     switch(tensor.info()->data_type())
895     {
896         case arm_compute::DataType::F32:
897         {
898             std::uniform_real_distribution<float> dist(lower_bound, upper_bound);
899
900             execute_window_loop(window, [&](const Coordinates & id)
901             {
902                 *reinterpret_cast<float *>(it.ptr()) = dist(gen);
903             },
904             it);
905
906             break;
907         }
908         default:
909         {
910             ARM_COMPUTE_ERROR("Unsupported format");
911         }
912     }
913
914     unmap(tensor);
915 }
916
917 template <typename T>
918 void init_sgemm_output(T &dst, T &src0, T &src1, arm_compute::DataType dt)
919 {
920     dst.allocator()->init(TensorInfo(TensorShape(src1.info()->dimension(0), src0.info()->dimension(1)), 1, dt));
921 }
922 /** This function returns the amount of memory free reading from /proc/meminfo
923  *
924  * @return The free memory in kB
925  */
926 uint64_t get_mem_free_from_meminfo();
927
928 /** Compare to tensor
929  *
930  * @param[in] tensor1 First tensor to be compared.
931  * @param[in] tensor2 Second tensor to be compared.
932  *
933  * @return The number of mismatches
934  */
935 template <typename T>
936 int compare_tensor(ITensor &tensor1, ITensor &tensor2)
937 {
938     ARM_COMPUTE_ERROR_ON_MISMATCHING_DATA_TYPES(&tensor1, &tensor2);
939     ARM_COMPUTE_ERROR_ON_MISMATCHING_SHAPES(&tensor1, &tensor2);
940
941     int    num_mismatches = 0;
942     Window window;
943     window.use_tensor_dimensions(tensor1.info()->tensor_shape());
944
945     map(tensor1, true);
946     map(tensor2, true);
947     Iterator itensor1(&tensor1, window);
948     Iterator itensor2(&tensor2, window);
949
950     execute_window_loop(window, [&](const Coordinates & id)
951     {
952         if(std::abs(*reinterpret_cast<T *>(itensor1.ptr()) - *reinterpret_cast<T *>(itensor2.ptr())) > 0.00001)
953         {
954             ++num_mismatches;
955         }
956     },
957     itensor1, itensor2);
958
959     unmap(itensor1);
960     unmap(itensor2);
961
962     return num_mismatches;
963 }
964 } // namespace utils
965 } // namespace arm_compute
966 #endif /* __UTILS_UTILS_H__*/