/* * Copyright (c) 2018 ARM Limited. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef __UTILS_IMAGE_LOADER_H__ #define __UTILS_IMAGE_LOADER_H__ #include "arm_compute/core/Error.h" #include "arm_compute/core/ITensor.h" #include "arm_compute/core/TensorInfo.h" #include "arm_compute/core/Types.h" #include "utils/Utils.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" #include "stb/stb_image.h" #pragma GCC diagnostic pop #include #include #include namespace arm_compute { namespace utils { /** Image feeder interface */ class IImageDataFeeder { public: /** Virtual base destructor */ virtual ~IImageDataFeeder() = default; /** Gets a character from an image feed */ virtual uint8_t get() = 0; /** Feed a whole row to a destination pointer * * @param[out] dst Destination pointer * @param[in] row_size Row size in terms of bytes */ virtual void get_row(uint8_t *dst, size_t row_size) = 0; }; /** File Image feeder concrete implementation */ class FileImageFeeder : public IImageDataFeeder { public: /** Default constructor * * @param[in] fs Image file stream */ FileImageFeeder(std::ifstream &fs) : _fs(fs) { } // Inherited overridden methods uint8_t get() override { return _fs.get(); } void get_row(uint8_t *dst, size_t row_size) override { ARM_COMPUTE_ERROR_ON(dst == nullptr); _fs.read(reinterpret_cast(dst), row_size); } private: std::ifstream &_fs; }; /** Memory Image feeder concrete implementation */ class MemoryImageFeeder : public IImageDataFeeder { public: /** Default constructor * * @param[in] data Pointer to data */ MemoryImageFeeder(const uint8_t *data) : _data(data) { } /** Prevent instances of this class from being copied (As this class contains pointers) */ MemoryImageFeeder(const MemoryImageFeeder &) = delete; /** Default move constructor */ MemoryImageFeeder(MemoryImageFeeder &&) = default; /** Prevent instances of this class from being copied (As this class contains pointers) */ MemoryImageFeeder &operator=(const MemoryImageFeeder &) = delete; /** Default move assignment operator */ MemoryImageFeeder &operator=(MemoryImageFeeder &&) = default; // Inherited overridden methods uint8_t get() override { return *_data++; } void get_row(uint8_t *dst, size_t row_size) override { ARM_COMPUTE_ERROR_ON(dst == nullptr); memcpy(dst, _data, row_size); _data += row_size; } private: const uint8_t *_data; }; /** Image loader interface */ class IImageLoader { public: /** Default Constructor */ IImageLoader() : _feeder(nullptr), _width(0), _height(0) { } /** Virtual base destructor */ virtual ~IImageLoader() = default; /** Return the width of the currently open image file. */ unsigned int width() const { return _width; } /** Return the height of the currently open image file. */ unsigned int height() const { return _height; } /** Return true if the image file is currently open */ virtual bool is_open() = 0; /** Open an image file and reads its metadata (Width, height) * * @param[in] filename File to open */ virtual void open(const std::string &filename) = 0; /** Closes an image file */ virtual void close() = 0; /** Initialise an image's metadata with the dimensions of the image file currently open * * @param[out] image Image to initialise * @param[in] format Format to use for the image (Must be RGB888 or U8) */ template void init_image(T &image, Format format) { ARM_COMPUTE_ERROR_ON(!is_open()); ARM_COMPUTE_ERROR_ON(format != Format::RGB888 && format != Format::U8); // Use the size of the input image TensorInfo image_info(_width, _height, format); image.allocator()->init(image_info); } /** Fill an image with the content of the currently open image file. * * @note If the image is a CLImage, the function maps and unmaps the image * * @param[in,out] image Image to fill (Must be allocated, and of matching dimensions with the opened image file). */ template void fill_image(T &image) { ARM_COMPUTE_ERROR_ON(!is_open()); ARM_COMPUTE_ERROR_ON(image.info()->dimension(0) != _width || image.info()->dimension(1) != _height); ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&image, Format::U8, Format::RGB888); ARM_COMPUTE_ERROR_ON(_feeder.get() == nullptr); try { // Map buffer if creating a CLTensor/GCTensor map(image, true); // Validate feeding data validate_info(image.info()); switch(image.info()->format()) { case Format::U8: { // We need to convert the data from RGB to grayscale: // Iterate through every pixel of the image Window window; window.set(Window::DimX, Window::Dimension(0, _width, 1)); window.set(Window::DimY, Window::Dimension(0, _height, 1)); Iterator out(&image, window); unsigned char red = 0; unsigned char green = 0; unsigned char blue = 0; execute_window_loop(window, [&](const Coordinates & id) { red = _feeder->get(); green = _feeder->get(); blue = _feeder->get(); *out.ptr() = 0.2126f * red + 0.7152f * green + 0.0722f * blue; }, out); break; } case Format::RGB888: { // There is no format conversion needed: we can simply copy the content of the input file to the image one row at the time. // Create a vertical window to iterate through the image's rows: Window window; window.set(Window::DimY, Window::Dimension(0, _height, 1)); Iterator out(&image, window); size_t row_size = _width * image.info()->element_size(); execute_window_loop(window, [&](const Coordinates & id) { _feeder->get_row(out.ptr(), row_size); }, out); break; } default: ARM_COMPUTE_ERROR("Unsupported format"); } // Unmap buffer if creating a CLTensor/GCTensor unmap(image); } catch(const std::ifstream::failure &e) { ARM_COMPUTE_ERROR("Loading image file: %s", e.what()); } } /** Fill a tensor with 3 planes (one for each channel) with the content of the currently open image file. * * @note If the image is a CLImage, the function maps and unmaps the image * * @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/F32 * @param[in] bgr (Optional) Fill the first plane with blue channel (default = false) */ template void fill_planar_tensor(T &tensor, bool bgr = false) { ARM_COMPUTE_ERROR_ON(!is_open()); ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::U8, DataType::F32); const DataLayout data_layout = tensor.info()->data_layout(); const TensorShape tensor_shape = tensor.info()->tensor_shape(); ARM_COMPUTE_UNUSED(tensor_shape); ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH)] != _width); ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT)] != _height); ARM_COMPUTE_ERROR_ON(tensor_shape[get_data_layout_dimension_index(data_layout, DataLayoutDimension::CHANNEL)] != 3); ARM_COMPUTE_ERROR_ON(_feeder.get() == nullptr); try { // Map buffer if creating a CLTensor map(tensor, true); // Validate feeding data validate_info(tensor.info()); // Stride across channels size_t stride_z = 0; // Iterate through every pixel of the image Window window; if(data_layout == DataLayout::NCHW) { window.set(Window::DimX, Window::Dimension(0, _width, 1)); window.set(Window::DimY, Window::Dimension(0, _height, 1)); window.set(Window::DimZ, Window::Dimension(0, 1, 1)); stride_z = tensor.info()->strides_in_bytes()[2]; } else { window.set(Window::DimX, Window::Dimension(0, 1, 1)); window.set(Window::DimY, Window::Dimension(0, _width, 1)); window.set(Window::DimZ, Window::Dimension(0, _height, 1)); stride_z = tensor.info()->strides_in_bytes()[0]; } Iterator out(&tensor, window); unsigned char red = 0; unsigned char green = 0; unsigned char blue = 0; execute_window_loop(window, [&](const Coordinates & id) { red = _feeder->get(); green = _feeder->get(); blue = _feeder->get(); switch(tensor.info()->data_type()) { case DataType::U8: { *(out.ptr() + 0 * stride_z) = bgr ? blue : red; *(out.ptr() + 1 * stride_z) = green; *(out.ptr() + 2 * stride_z) = bgr ? red : blue; break; } case DataType::F32: { *reinterpret_cast(out.ptr() + 0 * stride_z) = static_cast(bgr ? blue : red); *reinterpret_cast(out.ptr() + 1 * stride_z) = static_cast(green); *reinterpret_cast(out.ptr() + 2 * stride_z) = static_cast(bgr ? red : blue); break; } default: { ARM_COMPUTE_ERROR("Unsupported data type"); } } }, out); // Unmap buffer if creating a CLTensor unmap(tensor); } catch(const std::ifstream::failure &e) { ARM_COMPUTE_ERROR("Loading image file: %s", e.what()); } } protected: /** Validate metadata */ virtual void validate_info(const ITensorInfo *tensor_info) { } protected: std::unique_ptr _feeder; unsigned int _width; unsigned int _height; }; /** PPM Image loader concrete implementation */ class PPMLoader : public IImageLoader { public: /** Default Constructor */ PPMLoader() : IImageLoader(), _fs() { } // Inherited methods overridden: bool is_open() override { return _fs.is_open(); } void open(const std::string &filename) override { ARM_COMPUTE_ERROR_ON(is_open()); try { _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit); _fs.open(filename, std::ios::in | std::ios::binary); unsigned int max_val = 0; std::tie(_width, _height, max_val) = parse_ppm_header(_fs); ARM_COMPUTE_ERROR_ON_MSG(max_val >= 256, "2 bytes per colour channel not supported in file %s", filename.c_str()); _feeder = support::cpp14::make_unique(_fs); } catch(std::runtime_error &e) { ARM_COMPUTE_ERROR("Accessing %s: %s", filename.c_str(), e.what()); } } void close() override { if(is_open()) { _fs.close(); _feeder = nullptr; } ARM_COMPUTE_ERROR_ON(is_open()); } protected: // Inherited methods overridden: void validate_info(const ITensorInfo *tensor_info) override { // Check if the file is large enough to fill the image const size_t current_position = _fs.tellg(); _fs.seekg(0, std::ios_base::end); const size_t end_position = _fs.tellg(); _fs.seekg(current_position, std::ios_base::beg); ARM_COMPUTE_ERROR_ON_MSG((end_position - current_position) < tensor_info->tensor_shape().total_size(), "Not enough data in file"); ARM_COMPUTE_UNUSED(end_position); } private: std::ifstream _fs; }; /** Class to load the content of a JPEG file into an Image */ class JPEGLoader : public IImageLoader { private: /** Custom malloc deleter struct */ struct malloc_deleter { void operator()(uint8_t *p) const { free(p); } }; public: /** Default Constructor */ JPEGLoader() : IImageLoader(), _is_loaded(false), _data(nullptr) { } // Inherited methods overridden: bool is_open() override { return _is_loaded; } void open(const std::string &filename) override { int bpp, width, height; uint8_t *rgb_image = stbi_load(filename.c_str(), &width, &height, &bpp, 3); if(rgb_image == NULL) { ARM_COMPUTE_ERROR("Accessing %s failed", filename.c_str()); } else { _width = width; _height = height; _data = std::unique_ptr(rgb_image); _is_loaded = true; _feeder = support::cpp14::make_unique(_data.get()); } } void close() override { if(is_open()) { _width = 0; _height = 0; release(); } ARM_COMPUTE_ERROR_ON(is_open()); } /** Explicitly Releases the memory of the loaded data */ void release() { if(_is_loaded) { _data.reset(); _is_loaded = false; _feeder = nullptr; } } private: bool _is_loaded; std::unique_ptr _data; }; /** Factory for generating appropriate image loader**/ class ImageLoaderFactory final { public: /** Create an image loader depending on the image type * * @param[in] filename File than needs to be loaded * * @return Image loader */ static std::unique_ptr create(const std::string &filename) { ImageType type = arm_compute::utils::get_image_type_from_file(filename); switch(type) { case ImageType::PPM: return support::cpp14::make_unique(); case ImageType::JPEG: return support::cpp14::make_unique(); case ImageType::UNKNOWN: default: return nullptr; } } }; } // namespace utils } // namespace arm_compute #endif /* __UTILS_IMAGE_LOADER_H__*/