From 7c3b92403958e8970e901fd15b2fc904e7996eee Mon Sep 17 00:00:00 2001 From: Georgios Pinitas Date: Thu, 21 Jun 2018 19:01:25 +0100 Subject: COMPMID-1308: Add and validate JPEG accessors Change-Id: I93d7345a795b26153600287346d672209dbb2622 Reviewed-on: https://eu-gerrit-1.euhpc.arm.com/137479 Tested-by: Jenkins Reviewed-by: Anthony Barbier --- utils/GraphUtils.cpp | 66 +++++++ utils/GraphUtils.h | 32 +++- utils/ImageLoader.h | 491 +++++++++++++++++++++++++++++++++++++++++++++++++++ utils/Utils.h | 260 --------------------------- 4 files changed, 588 insertions(+), 261 deletions(-) create mode 100644 utils/ImageLoader.h (limited to 'utils') diff --git a/utils/GraphUtils.cpp b/utils/GraphUtils.cpp index b51e122d61..4db053cf9f 100644 --- a/utils/GraphUtils.cpp +++ b/utils/GraphUtils.cpp @@ -27,6 +27,7 @@ #include "arm_compute/core/Helpers.h" #include "arm_compute/core/Types.h" #include "arm_compute/runtime/SubTensor.h" +#include "utils/ImageLoader.h" #include "utils/Utils.h" #include @@ -202,6 +203,71 @@ bool PPMAccessor::access_tensor(ITensor &tensor) return true; } +ValidationInputAccessor::ValidationInputAccessor(const std::string &image_list, + std::string images_path, + bool bgr, + unsigned int start, + unsigned int end) + : _path(std::move(images_path)), _images(), _bgr(bgr), _offset(0) +{ + ARM_COMPUTE_ERROR_ON_MSG(start > end, "Invalid validation range!"); + + std::ifstream ifs; + try + { + ifs.exceptions(std::ifstream::badbit); + ifs.open(image_list, std::ios::in | std::ios::binary); + + // Parse image names + unsigned int counter = 0; + for(std::string line; !std::getline(ifs, line).fail() && counter <= end; ++counter) + { + // Add image to process if withing range + if(counter >= start) + { + std::stringstream linestream(line); + std::string image_name; + + linestream >> image_name; + _images.emplace_back(std::move(image_name)); + } + } + } + catch(const std::ifstream::failure &e) + { + ARM_COMPUTE_ERROR("Accessing %s: %s", image_list.c_str(), e.what()); + } +} + +bool ValidationInputAccessor::access_tensor(arm_compute::ITensor &tensor) +{ + bool ret = _offset < _images.size(); + if(ret) + { + utils::JPEGLoader jpeg; + + // Open JPEG file + jpeg.open(_path + _images[_offset++]); + + // Get permutated shape and permutation parameters + TensorShape permuted_shape = tensor.info()->tensor_shape(); + arm_compute::PermutationVector perm; + if(tensor.info()->data_layout() != DataLayout::NCHW) + { + std::tie(permuted_shape, perm) = compute_permutation_paramaters(tensor.info()->tensor_shape(), + tensor.info()->data_layout()); + } + ARM_COMPUTE_ERROR_ON_MSG(jpeg.width() != permuted_shape.x() || jpeg.height() != permuted_shape.y(), + "Failed to load image file: dimensions [%d,%d] not correct, expected [%d,%d].", + jpeg.width(), jpeg.height(), permuted_shape.x(), permuted_shape.y()); + + // Fill the tensor with the PPM content (BGR) + jpeg.fill_planar_tensor(tensor, _bgr); + } + + return ret; +} + TopNPredictionsAccessor::TopNPredictionsAccessor(const std::string &labels_path, size_t top_n, std::ostream &output_stream) : _labels(), _output_stream(output_stream), _top_n(top_n) { diff --git a/utils/GraphUtils.h b/utils/GraphUtils.h index 597708369d..349d8558fc 100644 --- a/utils/GraphUtils.h +++ b/utils/GraphUtils.h @@ -157,7 +157,7 @@ public: /** Constructor * * @param[in] ppm_path Path to PPM file - * @param[in] bgr (Optional) Fill the first plane with blue channel (default = false) + * @param[in] bgr (Optional) Fill the first plane with blue channel (default = false - RGB format) * @param[in] preprocessor (Optional) PPM pre-processing object */ PPMAccessor(std::string ppm_path, bool bgr = true, std::unique_ptr preprocessor = nullptr); @@ -173,6 +173,36 @@ private: std::unique_ptr _preprocessor; }; +/** Input Accessor used for network validation */ +class ValidationInputAccessor final : public graph::ITensorAccessor +{ +public: + /** Constructor + * + * @param[in] image_list File containing all the images to validate + * @param[in] images_path Path to images. + * @param[in] bgr (Optional) Fill the first plane with blue channel (default = false - RGB format) + * @param[in] start (Optional) Start range + * @param[in] end (Optional) End range + * + * @note + */ + ValidationInputAccessor(const std::string &image_list, + std::string images_path, + bool bgr = true, + unsigned int start = 0, + unsigned int end = 0); + + // Inherited methods overriden: + bool access_tensor(ITensor &tensor) override; + +private: + std::string _path; + std::vector _images; + bool _bgr; + size_t _offset; +}; + /** Result accessor class */ class TopNPredictionsAccessor final : public graph::ITensorAccessor { diff --git a/utils/ImageLoader.h b/utils/ImageLoader.h new file mode 100644 index 0000000000..edc89286a2 --- /dev/null +++ b/utils/ImageLoader.h @@ -0,0 +1,491 @@ +/* + * 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" +#define STB_IMAGE_IMPLEMENTATION +#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; +}; +} // namespace utils +} // namespace arm_compute +#endif /* __UTILS_IMAGE_LOADER_H__*/ diff --git a/utils/Utils.h b/utils/Utils.h index 6cb71fd3ba..c18ad217a4 100644 --- a/utils/Utils.h +++ b/utils/Utils.h @@ -254,266 +254,6 @@ inline void unmap(GCTensor &tensor) } #endif /* ARM_COMPUTE_GC */ -/** Class to load the content of a PPM file into an Image - */ -class PPMLoader -{ -public: - PPMLoader() - : _fs(), _width(0), _height(0) - { - } - /** Open a PPM file and reads its metadata (Width, height) - * - * @param[in] ppm_filename File to open - */ - void open(const std::string &ppm_filename) - { - ARM_COMPUTE_ERROR_ON(is_open()); - try - { - _fs.exceptions(std::ifstream::failbit | std::ifstream::badbit); - _fs.open(ppm_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", ppm_filename.c_str()); - } - catch(std::runtime_error &e) - { - ARM_COMPUTE_ERROR("Accessing %s: %s", ppm_filename.c_str(), e.what()); - } - } - /** Return true if a PPM file is currently open - */ - bool is_open() - { - return _fs.is_open(); - } - - /** Initialise an image's metadata with the dimensions of the PPM 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, arm_compute::Format format) - { - ARM_COMPUTE_ERROR_ON(!is_open()); - ARM_COMPUTE_ERROR_ON(format != arm_compute::Format::RGB888 && format != arm_compute::Format::U8); - - // Use the size of the input PPM image - arm_compute::TensorInfo image_info(_width, _height, format); - image.allocator()->init(image_info); - } - - /** Fill an image with the content of the currently open PPM 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 PPM). - */ - 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, arm_compute::Format::U8, arm_compute::Format::RGB888); - try - { - // Map buffer if creating a CLTensor/GCTensor - map(image, true); - - // 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) < image.info()->tensor_shape().total_size() * image.info()->element_size(), - "Not enough data in file"); - ARM_COMPUTE_UNUSED(end_position); - - switch(image.info()->format()) - { - case arm_compute::Format::U8: - { - // We need to convert the data from RGB to grayscale: - // Iterate through every pixel of the image - arm_compute::Window window; - window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1)); - window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1)); - - arm_compute::Iterator out(&image, window); - - unsigned char red = 0; - unsigned char green = 0; - unsigned char blue = 0; - - arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id) - { - red = _fs.get(); - green = _fs.get(); - blue = _fs.get(); - - *out.ptr() = 0.2126f * red + 0.7152f * green + 0.0722f * blue; - }, - out); - - break; - } - case arm_compute::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: - arm_compute::Window window; - window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1)); - - arm_compute::Iterator out(&image, window); - - arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id) - { - // Copy one row from the input file to the current row of the image: - _fs.read(reinterpret_cast(out.ptr()), _width * image.info()->element_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 PPM file: %s", e.what()); - } - } - - /** Fill a tensor with 3 planes (one for each channel) with the content of the currently open PPM 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 PPM). 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); - - try - { - // Map buffer if creating a CLTensor - map(tensor, true); - - // 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); - - // Stride across channels - size_t stride_z = 0; - - // Iterate through every pixel of the image - arm_compute::Window window; - if(data_layout == DataLayout::NCHW) - { - window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, _width, 1)); - window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _height, 1)); - window.set(arm_compute::Window::DimZ, arm_compute::Window::Dimension(0, 1, 1)); - stride_z = tensor.info()->strides_in_bytes()[2]; - } - else - { - window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, 1, 1)); - window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, _width, 1)); - window.set(arm_compute::Window::DimZ, arm_compute::Window::Dimension(0, _height, 1)); - stride_z = tensor.info()->strides_in_bytes()[0]; - } - - arm_compute::Iterator out(&tensor, window); - - unsigned char red = 0; - unsigned char green = 0; - unsigned char blue = 0; - - arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id) - { - red = _fs.get(); - green = _fs.get(); - blue = _fs.get(); - - switch(tensor.info()->data_type()) - { - case arm_compute::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 arm_compute::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 PPM file: %s", e.what()); - } - } - - /** Return the width of the currently open PPM file. - */ - unsigned int width() const - { - return _width; - } - - /** Return the height of the currently open PPM file. - */ - unsigned int height() const - { - return _height; - } - -private: - std::ifstream _fs; - unsigned int _width, _height; -}; - /** Numpy data loader */ class NPYLoader { -- cgit v1.2.1