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