diff options
author | Anthony Barbier <anthony.barbier@arm.com> | 2017-06-23 15:42:00 +0100 |
---|---|---|
committer | Anthony Barbier <anthony.barbier@arm.com> | 2017-06-23 16:07:31 +0100 |
commit | dbdab85d6e0f96d3361a9e30310367d89953466c (patch) | |
tree | 0cc80d19fd8192de6eca2d28f7e4062aa9deecbf /utils | |
parent | 664d833b9d7b569db60b0f6d93e80f91f2c07c39 (diff) | |
download | armcl-dbdab85d6e0f96d3361a9e30310367d89953466c.tar.gz armcl-dbdab85d6e0f96d3361a9e30310367d89953466c.tar.bz2 armcl-dbdab85d6e0f96d3361a9e30310367d89953466c.zip |
arm_compute v17.06
Diffstat (limited to 'utils')
-rw-r--r-- | utils/Utils.cpp | 171 | ||||
-rw-r--r-- | utils/Utils.h | 325 |
2 files changed, 496 insertions, 0 deletions
diff --git a/utils/Utils.cpp b/utils/Utils.cpp new file mode 100644 index 000000000..5316690a3 --- /dev/null +++ b/utils/Utils.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017 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. + */ +#include "Utils.h" + +#include <cctype> +#include <cerrno> +#include <iomanip> +#include <string> + +namespace arm_compute +{ +namespace utils +{ +namespace +{ +/* Advance the iterator to the first character which is not a comment + * + * @param[in,out] fs Stream to drop comments from + */ +void discard_comments(std::ifstream &fs) +{ + while(fs.peek() == '#') + { + fs.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + } +} + +/* Advance the string iterator to the next character which is neither a space or a comment + * + * @param[in,out] fs Stream to drop comments from + */ +void discard_comments_and_spaces(std::ifstream &fs) +{ + while(true) + { + discard_comments(fs); + + if(isspace(fs.peek()) == 0) + { + break; + } + + fs.ignore(1); + } +} +} // namespace + +int run_example(int argc, const char **argv, example &func) +{ + std::cout << "\n" + << argv[0] << "\n\n"; + + try + { + func(argc, argv); + + std::cout << "\nTest passed\n"; + return 0; + } +#ifdef ARM_COMPUTE_CL + catch(cl::Error &err) + { + std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + std::cerr << std::endl + << "ERROR " << err.what() << "(" << err.err() << ")" << std::endl; + std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + } +#endif /* ARM_COMPUTE_CL */ + catch(std::runtime_error &err) + { + std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + std::cerr << std::endl + << "ERROR " << err.what() << " " << (errno ? strerror(errno) : "") << std::endl; + std::cerr << "!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + } + + std::cout << "\nTest FAILED\n"; + + return -1; +} + +void draw_detection_rectangle(ITensor *tensor, const DetectionWindow &rect, uint8_t r, uint8_t g, uint8_t b) +{ + ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(tensor, Format::RGB888); + + uint8_t *top = tensor->info()->offset_element_in_bytes(Coordinates(rect.x, rect.y)) + tensor->buffer(); + uint8_t *bottom = tensor->info()->offset_element_in_bytes(Coordinates(rect.x, rect.y + rect.height)) + tensor->buffer(); + uint8_t *left = top; + uint8_t *right = tensor->info()->offset_element_in_bytes(Coordinates(rect.x + rect.width, rect.y)) + tensor->buffer(); + size_t stride = tensor->info()->strides_in_bytes()[Window::DimY]; + + for(size_t x = 0; x < rect.width; ++x) + { + top[0] = r; + top[1] = g; + top[2] = b; + bottom[0] = r; + bottom[1] = g; + bottom[2] = b; + + top += 3; + bottom += 3; + } + + for(size_t y = 0; y < rect.height; ++y) + { + left[0] = r; + left[1] = g; + left[2] = b; + right[0] = r; + right[1] = g; + right[2] = b; + + left += stride; + right += stride; + } +} + +std::tuple<unsigned int, unsigned int, int> parse_ppm_header(std::ifstream &fs) +{ + // Check the PPM magic number is valid + std::array<char, 2> magic_number{ { 0 } }; + fs >> magic_number[0] >> magic_number[1]; + ARM_COMPUTE_ERROR_ON_MSG(magic_number[0] != 'P' || magic_number[1] != '6', "Invalid file type"); + ARM_COMPUTE_UNUSED(magic_number); + + discard_comments_and_spaces(fs); + + unsigned int width = 0; + fs >> width; + + discard_comments_and_spaces(fs); + + unsigned int height = 0; + fs >> height; + + discard_comments_and_spaces(fs); + + int max_val = 0; + fs >> max_val; + + discard_comments(fs); + + ARM_COMPUTE_ERROR_ON_MSG(isspace(fs.peek()) == 0, "Invalid PPM header"); + fs.ignore(1); + + return std::make_tuple(width, height, max_val); +} +} // namespace utils +} // namespace arm_compute diff --git a/utils/Utils.h b/utils/Utils.h new file mode 100644 index 000000000..b519f83a8 --- /dev/null +++ b/utils/Utils.h @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2016, 2017 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_UTILS_H__ +#define __UTILS_UTILS_H__ + +#include "arm_compute/core/Helpers.h" +#include "arm_compute/core/ITensor.h" +#include "arm_compute/core/Types.h" +#include "arm_compute/core/Validate.h" +#include "arm_compute/runtime/Tensor.h" + +#ifdef ARM_COMPUTE_CL +#include "arm_compute/core/CL/OpenCL.h" +#include "arm_compute/runtime/CL/CLTensor.h" +#endif /* ARM_COMPUTE_CL */ + +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> + +namespace arm_compute +{ +namespace utils +{ +/** Signature of an example to run + * + * @param[in] argc Number of command line arguments + * @param[in] argv Command line arguments + */ +using example = void(int argc, const char **argv); + +/** Run an example and handle the potential exceptions it throws + * + * @param[in] argc Number of command line arguments + * @param[in] argv Command line arguments + * @param[in] func Pointer to the function containing the code to run + */ +int run_example(int argc, const char **argv, example &func); + +/** Draw a RGB rectangular window for the detected object + * + * @param[in, out] tensor Input tensor where the rectangle will be drawn on. Format supported: RGB888 + * @param[in] rect Geometry of the rectangular window + * @param[in] r Red colour to use + * @param[in] g Green colour to use + * @param[in] b Blue colour to use + */ +void draw_detection_rectangle(arm_compute::ITensor *tensor, const arm_compute::DetectionWindow &rect, uint8_t r, uint8_t g, uint8_t b); + +/** Parse the ppm header from an input file stream. At the end of the execution, + * the file position pointer will be located at the first pixel stored in the ppm file + * + * @param[in] fs Input file stream to parse + * + * @return The width, height and max value stored in the header of the PPM file + */ +std::tuple<unsigned int, unsigned int, int> parse_ppm_header(std::ifstream &fs); + +/** 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(const std::ifstream::failure &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 <typename T> + 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 <typename T> + 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 + { +#ifdef ARM_COMPUTE_CL + // Map buffer if creating a CLTensor + if(std::is_same<typename std::decay<T>::type, arm_compute::CLImage>::value) + { + image.map(); + } +#endif + // 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<std::fstream::char_type *>(out.ptr()), _width * image.info()->element_size()); + }, + out); + + break; + } + default: + ARM_COMPUTE_ERROR("Unsupported format"); + } + +#ifdef ARM_COMPUTE_CL + // Unmap buffer if creating a CLTensor + if(std::is_same<typename std::decay<T>::type, arm_compute::CLTensor>::value) + { + image.unmap(); + } +#endif + } + catch(const std::ifstream::failure &e) + { + ARM_COMPUTE_ERROR("Loading PPM file: %s", e.what()); + } + } + +private: + std::ifstream _fs; + unsigned int _width, _height; +}; + +/** Template helper function to save a tensor image to a PPM file. + * + * @note Only U8 and RGB888 formats supported. + * @note Only works with 2D tensors. + * @note If the input tensor is a CLTensor, the function maps and unmaps the image + * + * @param[in] tensor The tensor to save as PPM file + * @param[in] ppm_filename Filename of the file to create. + */ +template <typename T> +void save_to_ppm(T &tensor, const std::string &ppm_filename) +{ + ARM_COMPUTE_ERROR_ON_FORMAT_NOT_IN(&tensor, arm_compute::Format::RGB888, arm_compute::Format::U8); + ARM_COMPUTE_ERROR_ON(tensor.info()->num_dimensions() > 2); + + std::ofstream fs; + + try + { + fs.exceptions(std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit); + fs.open(ppm_filename, std::ios::out | std::ios::binary); + + const unsigned int width = tensor.info()->tensor_shape()[0]; + const unsigned int height = tensor.info()->tensor_shape()[1]; + + fs << "P6\n" + << width << " " << height << " 255\n"; + +#ifdef ARM_COMPUTE_CL + // Map buffer if creating a CLTensor + if(std::is_same<typename std::decay<T>::type, arm_compute::CLTensor>::value) + { + tensor.map(); + } +#endif + + switch(tensor.info()->format()) + { + case arm_compute::Format::U8: + { + 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 in(&tensor, window); + + arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id) + { + const unsigned char value = *in.ptr(); + + fs << value << value << value; + }, + in); + + break; + } + case arm_compute::Format::RGB888: + { + arm_compute::Window window; + window.set(arm_compute::Window::DimX, arm_compute::Window::Dimension(0, width, width)); + window.set(arm_compute::Window::DimY, arm_compute::Window::Dimension(0, height, 1)); + + arm_compute::Iterator in(&tensor, window); + + arm_compute::execute_window_loop(window, [&](const arm_compute::Coordinates & id) + { + fs.write(reinterpret_cast<std::fstream::char_type *>(in.ptr()), width * tensor.info()->element_size()); + }, + in); + + break; + } + default: + ARM_COMPUTE_ERROR("Unsupported format"); + } +#ifdef ARM_COMPUTE_CL + // Unmap buffer if creating a CLTensor + if(std::is_same<typename std::decay<T>::type, arm_compute::CLTensor>::value) + { + tensor.unmap(); + } +#endif + } + catch(const std::ofstream::failure &e) + { + ARM_COMPUTE_ERROR("Writing %s: (%s)", ppm_filename.c_str(), e.what()); + } +} +} // namespace utils +} // namespace arm_compute +#endif /* __UTILS_UTILS_H__*/ |