• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018-2021 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_IMAGE_LOADER_H__
25 #define __UTILS_IMAGE_LOADER_H__
26 
27 #include "arm_compute/core/Error.h"
28 #include "arm_compute/core/ITensor.h"
29 #include "arm_compute/core/TensorInfo.h"
30 #include "arm_compute/core/Types.h"
31 
32 #include "utils/Utils.h"
33 
34 #pragma GCC diagnostic push
35 #pragma GCC diagnostic ignored "-Wswitch-default"
36 #pragma GCC diagnostic ignored "-Wstrict-overflow"
37 #include "stb/stb_image.h"
38 #pragma GCC diagnostic pop
39 
40 #include <cstdlib>
41 #include <memory>
42 #include <string>
43 
44 namespace arm_compute
45 {
46 namespace utils
47 {
48 /** Image feeder interface */
49 class IImageDataFeeder
50 {
51 public:
52     /** Virtual base destructor */
53     virtual ~IImageDataFeeder() = default;
54     /** Gets a character from an image feed */
55     virtual uint8_t get() = 0;
56     /** Feed a whole row to a destination pointer
57      *
58      * @param[out] dst      Destination pointer
59      * @param[in]  row_size Row size in terms of bytes
60      */
61     virtual void get_row(uint8_t *dst, size_t row_size) = 0;
62 };
63 /** File Image feeder concrete implementation */
64 class FileImageFeeder : public IImageDataFeeder
65 {
66 public:
67     /** Default constructor
68      *
69      * @param[in] fs Image file stream
70      */
FileImageFeeder(std::ifstream & fs)71     FileImageFeeder(std::ifstream &fs)
72         : _fs(fs)
73     {
74     }
75     // Inherited overridden methods
get()76     uint8_t get() override
77     {
78         return _fs.get();
79     }
get_row(uint8_t * dst,size_t row_size)80     void get_row(uint8_t *dst, size_t row_size) override
81     {
82         ARM_COMPUTE_ERROR_ON(dst == nullptr);
83         _fs.read(reinterpret_cast<std::fstream::char_type *>(dst), row_size);
84     }
85 
86 private:
87     std::ifstream &_fs;
88 };
89 /** Memory Image feeder concrete implementation */
90 class MemoryImageFeeder : public IImageDataFeeder
91 {
92 public:
93     /** Default constructor
94      *
95      * @param[in] data Pointer to data
96      */
MemoryImageFeeder(const uint8_t * data)97     MemoryImageFeeder(const uint8_t *data)
98         : _data(data)
99     {
100     }
101     /** Prevent instances of this class from being copied (As this class contains pointers) */
102     MemoryImageFeeder(const MemoryImageFeeder &) = delete;
103     /** Default move constructor */
104     MemoryImageFeeder(MemoryImageFeeder &&) = default;
105     /** Prevent instances of this class from being copied (As this class contains pointers) */
106     MemoryImageFeeder &operator=(const MemoryImageFeeder &) = delete;
107     /** Default move assignment operator */
108     MemoryImageFeeder &operator=(MemoryImageFeeder &&) = default;
109     // Inherited overridden methods
get()110     uint8_t get() override
111     {
112         return *_data++;
113     }
get_row(uint8_t * dst,size_t row_size)114     void get_row(uint8_t *dst, size_t row_size) override
115     {
116         ARM_COMPUTE_ERROR_ON(dst == nullptr);
117         memcpy(dst, _data, row_size);
118         _data += row_size;
119     }
120 
121 private:
122     const uint8_t *_data;
123 };
124 
125 /** Image loader interface */
126 class IImageLoader
127 {
128 public:
129     /** Default Constructor */
IImageLoader()130     IImageLoader()
131         : _feeder(nullptr), _width(0), _height(0)
132     {
133     }
134     /** Virtual base destructor */
135     virtual ~IImageLoader() = default;
136     /** Return the width of the currently open image file. */
width()137     unsigned int width() const
138     {
139         return _width;
140     }
141     /** Return the height of the currently open image file. */
height()142     unsigned int height() const
143     {
144         return _height;
145     }
146     /** Return true if the image file is currently open */
147     virtual bool is_open() = 0;
148     /** Open an image file and reads its metadata (Width, height)
149      *
150      * @param[in] filename File to open
151      */
152     virtual void open(const std::string &filename) = 0;
153     /** Closes an image file */
154     virtual void close() = 0;
155     /** Initialise an image's metadata with the dimensions of the image file currently open
156      *
157      * @param[out] image  Image to initialise
158      * @param[in]  format Format to use for the image (Must be RGB888 or U8)
159      */
160     template <typename T>
init_image(T & image,Format format)161     void init_image(T &image, Format format)
162     {
163         ARM_COMPUTE_ERROR_ON(!is_open());
164         ARM_COMPUTE_ERROR_ON(format != Format::RGB888 && format != Format::U8);
165 
166         // Use the size of the input image
167         TensorInfo image_info(_width, _height, format);
168         image.allocator()->init(image_info);
169     }
170     /** Fill an image with the content of the currently open image file.
171      *
172      * @note If the image is a CLImage, the function maps and unmaps the image
173      *
174      * @param[in,out] image Image to fill (Must be allocated, and of matching dimensions with the opened image file).
175      */
176     template <typename T>
fill_image(T & image)177     void fill_image(T &image)
178     {
179         ARM_COMPUTE_ERROR_ON(!is_open());
180         ARM_COMPUTE_ERROR_ON(image.info()->dimension(0) != _width || image.info()->dimension(1) != _height);
181         ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&image, Format::U8, Format::RGB888);
182         ARM_COMPUTE_ERROR_ON(_feeder.get() == nullptr);
183         try
184         {
185             // Map buffer if creating a CLTensor
186             map(image, true);
187 
188             // Validate feeding data
189             validate_info(image.info());
190 
191             switch(image.info()->format())
192             {
193                 case Format::U8:
194                 {
195                     // We need to convert the data from RGB to grayscale:
196                     // Iterate through every pixel of the image
197                     Window window;
198                     window.set(Window::DimX, Window::Dimension(0, _width, 1));
199                     window.set(Window::DimY, Window::Dimension(0, _height, 1));
200 
201                     Iterator out(&image, window);
202 
203                     unsigned char red   = 0;
204                     unsigned char green = 0;
205                     unsigned char blue  = 0;
206 
207                     execute_window_loop(window, [&](const Coordinates &)
208                     {
209                         red   = _feeder->get();
210                         green = _feeder->get();
211                         blue  = _feeder->get();
212 
213                         *out.ptr() = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
214                     },
215                     out);
216 
217                     break;
218                 }
219                 case Format::RGB888:
220                 {
221                     // There is no format conversion needed: we can simply copy the content of the input file to the image one row at the time.
222                     // Create a vertical window to iterate through the image's rows:
223                     Window window;
224                     window.set(Window::DimY, Window::Dimension(0, _height, 1));
225 
226                     Iterator out(&image, window);
227                     size_t   row_size = _width * image.info()->element_size();
228 
229                     execute_window_loop(window, [&](const Coordinates &)
230                     {
231                         _feeder->get_row(out.ptr(), row_size);
232                     },
233                     out);
234 
235                     break;
236                 }
237                 default:
238                     ARM_COMPUTE_ERROR("Unsupported format");
239             }
240 
241             // Unmap buffer if creating a CLTensor
242             unmap(image);
243         }
244         catch(const std::ifstream::failure &e)
245         {
246             ARM_COMPUTE_ERROR_VAR("Loading image file: %s", e.what());
247         }
248     }
249     /** Fill a tensor with 3 planes (one for each channel) with the content of the currently open image file.
250      *
251      * @note If the image is a CLImage, the function maps and unmaps the image
252      *
253      * @param[in,out] tensor Tensor with 3 planes to fill (Must be allocated, and of matching dimensions with the opened image). Data types supported: U8/F16/F32
254      * @param[in]     bgr    (Optional) Fill the first plane with blue channel (default = false)
255      */
256     template <typename T>
257     void fill_planar_tensor(T &tensor, bool bgr = false)
258     {
259         ARM_COMPUTE_ERROR_ON(!is_open());
260         ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::U8, DataType::QASYMM8, DataType::F32, DataType::F16);
261 
262         const DataLayout  data_layout  = tensor.info()->data_layout();
263         const TensorShape tensor_shape = tensor.info()->tensor_shape();
264 
265         ARM_COMPUTE_UNUSED(tensor_shape);
266         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH)] != _width);
267         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT)] != _height);
268         ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::CHANNEL)] != 3);
269 
270         ARM_COMPUTE_ERROR_ON(_feeder.get() == nullptr);
271 
272         try
273         {
274             // Map buffer if creating a CLTensor
275             map(tensor, true);
276 
277             // Validate feeding data
278             validate_info(tensor.info());
279 
280             // Stride across channels
281             size_t stride_z = 0;
282 
283             // Iterate through every pixel of the image
284             Window window;
285             if(data_layout == DataLayout::NCHW)
286             {
287                 window.set(Window::DimX, Window::Dimension(0, _width, 1));
288                 window.set(Window::DimY, Window::Dimension(0, _height, 1));
289                 window.set(Window::DimZ, Window::Dimension(0, 1, 1));
290                 stride_z = tensor.info()->strides_in_bytes()[2];
291             }
292             else
293             {
294                 window.set(Window::DimX, Window::Dimension(0, 1, 1));
295                 window.set(Window::DimY, Window::Dimension(0, _width, 1));
296                 window.set(Window::DimZ, Window::Dimension(0, _height, 1));
297                 stride_z = tensor.info()->strides_in_bytes()[0];
298             }
299 
300             Iterator out(&tensor, window);
301 
302             unsigned char red   = 0;
303             unsigned char green = 0;
304             unsigned char blue  = 0;
305 
306             execute_window_loop(window, [&](const Coordinates &)
307             {
308                 red   = _feeder->get();
309                 green = _feeder->get();
310                 blue  = _feeder->get();
311 
312                 switch(tensor.info()->data_type())
313                 {
314                     case DataType::U8:
315                     case DataType::QASYMM8:
316                     {
317                         *(out.ptr() + 0 * stride_z) = bgr ? blue : red;
318                         *(out.ptr() + 1 * stride_z) = green;
319                         *(out.ptr() + 2 * stride_z) = bgr ? red : blue;
320                         break;
321                     }
322                     case DataType::F32:
323                     {
324                         *reinterpret_cast<float *>(out.ptr() + 0 * stride_z) = static_cast<float>(bgr ? blue : red);
325                         *reinterpret_cast<float *>(out.ptr() + 1 * stride_z) = static_cast<float>(green);
326                         *reinterpret_cast<float *>(out.ptr() + 2 * stride_z) = static_cast<float>(bgr ? red : blue);
327                         break;
328                     }
329                     case DataType::F16:
330                     {
331                         *reinterpret_cast<half *>(out.ptr() + 0 * stride_z) = static_cast<half>(bgr ? blue : red);
332                         *reinterpret_cast<half *>(out.ptr() + 1 * stride_z) = static_cast<half>(green);
333                         *reinterpret_cast<half *>(out.ptr() + 2 * stride_z) = static_cast<half>(bgr ? red : blue);
334                         break;
335                     }
336                     default:
337                     {
338                         ARM_COMPUTE_ERROR("Unsupported data type");
339                     }
340                 }
341             },
342             out);
343 
344             // Unmap buffer if creating a CLTensor
345             unmap(tensor);
346         }
catch(const std::ifstream::failure & e)347         catch(const std::ifstream::failure &e)
348         {
349             ARM_COMPUTE_ERROR_VAR("Loading image file: %s", e.what());
350         }
351     }
352 
353 protected:
354     /** Validate metadata */
validate_info(const ITensorInfo * tensor_info)355     virtual void validate_info(const ITensorInfo *tensor_info)
356     {
357         ARM_COMPUTE_UNUSED(tensor_info);
358     }
359 
360 protected:
361     std::unique_ptr<IImageDataFeeder> _feeder;
362     unsigned int                      _width;
363     unsigned int                      _height;
364 };
365 
366 /** PPM Image loader concrete implementation */
367 class PPMLoader : public IImageLoader
368 {
369 public:
370     /** Default Constructor */
PPMLoader()371     PPMLoader()
372         : IImageLoader(), _fs()
373     {
374     }
375 
376     // Inherited methods overridden:
is_open()377     bool is_open() override
378     {
379         return _fs.is_open();
380     }
open(const std::string & filename)381     void open(const std::string &filename) override
382     {
383         ARM_COMPUTE_ERROR_ON(is_open());
384         try
385         {
386             _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
387             _fs.open(filename, std::ios::in | std::ios::binary);
388 
389             unsigned int max_val = 0;
390             std::tie(_width, _height, max_val) = parse_ppm_header(_fs);
391 
392             ARM_COMPUTE_ERROR_ON_MSG_VAR(max_val >= 256, "2 bytes per colour channel not supported in file %s",
393                                          filename.c_str());
394 
395             _feeder = std::make_unique<FileImageFeeder>(_fs);
396         }
397         catch(std::runtime_error &e)
398         {
399             ARM_COMPUTE_ERROR_VAR("Accessing %s: %s", filename.c_str(), e.what());
400         }
401     }
close()402     void close() override
403     {
404         if(is_open())
405         {
406             _fs.close();
407             _feeder = nullptr;
408         }
409         ARM_COMPUTE_ERROR_ON(is_open());
410     }
411 
412 protected:
413     // Inherited methods overridden:
validate_info(const ITensorInfo * tensor_info)414     void validate_info(const ITensorInfo *tensor_info) override
415     {
416         // Check if the file is large enough to fill the image
417         const size_t current_position = _fs.tellg();
418         _fs.seekg(0, std::ios_base::end);
419         const size_t end_position = _fs.tellg();
420         _fs.seekg(current_position, std::ios_base::beg);
421 
422         ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < tensor_info->tensor_shape().total_size(),
423                                  "Not enough data in file");
424         ARM_COMPUTE_UNUSED(end_position, tensor_info);
425     }
426 
427 private:
428     std::ifstream _fs;
429 };
430 
431 /** Class to load the content of a JPEG file into an Image */
432 class JPEGLoader : public IImageLoader
433 {
434 private:
435     /** Custom malloc deleter struct */
436     struct malloc_deleter
437     {
operatormalloc_deleter438         void operator()(uint8_t *p) const
439         {
440             free(p);
441         }
442     };
443 
444 public:
445     /** Default Constructor */
JPEGLoader()446     JPEGLoader()
447         : IImageLoader(), _is_loaded(false), _data(nullptr)
448     {
449     }
450 
451     // Inherited methods overridden:
is_open()452     bool is_open() override
453     {
454         return _is_loaded;
455     }
open(const std::string & filename)456     void open(const std::string &filename) override
457     {
458         int      bpp, width, height;
459         uint8_t *rgb_image = stbi_load(filename.c_str(), &width, &height, &bpp, 3);
460         if(rgb_image == NULL)
461         {
462             ARM_COMPUTE_ERROR_VAR("Accessing %s failed", filename.c_str());
463         }
464         else
465         {
466             _width     = width;
467             _height    = height;
468             _data      = std::unique_ptr<uint8_t, malloc_deleter>(rgb_image);
469             _is_loaded = true;
470             _feeder    = std::make_unique<MemoryImageFeeder>(_data.get());
471         }
472     }
close()473     void close() override
474     {
475         if(is_open())
476         {
477             _width  = 0;
478             _height = 0;
479             release();
480         }
481         ARM_COMPUTE_ERROR_ON(is_open());
482     }
483     /** Explicitly Releases the memory of the loaded data */
release()484     void release()
485     {
486         if(_is_loaded)
487         {
488             _data.reset();
489             _is_loaded = false;
490             _feeder    = nullptr;
491         }
492     }
493 
494 private:
495     bool _is_loaded;
496     std::unique_ptr<uint8_t, malloc_deleter> _data;
497 };
498 
499 /** Factory for generating appropriate image loader**/
500 class ImageLoaderFactory final
501 {
502 public:
503     /** Create an image loader depending on the image type
504      *
505      * @param[in] filename File than needs to be loaded
506      *
507      * @return Image loader
508      */
create(const std::string & filename)509     static std::unique_ptr<IImageLoader> create(const std::string &filename)
510     {
511         ImageType type = arm_compute::utils::get_image_type_from_file(filename);
512         switch(type)
513         {
514             case ImageType::PPM:
515                 return std::make_unique<PPMLoader>();
516             case ImageType::JPEG:
517                 return std::make_unique<JPEGLoader>();
518             case ImageType::UNKNOWN:
519             default:
520                 return nullptr;
521         }
522     }
523 };
524 } // namespace utils
525 } // namespace arm_compute
526 #endif /* __UTILS_IMAGE_LOADER_H__*/
527