Compute Library
 21.02
AssetsLibrary.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017-2020 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 #include "tests/AssetsLibrary.h"
25 
26 #include "Utils.h"
27 #include "utils/TypePrinter.h"
28 
30 
31 #pragma GCC diagnostic push
32 #pragma GCC diagnostic ignored "-Wunused-parameter"
33 #include "libnpy/npy.hpp"
34 #pragma GCC diagnostic pop
35 
36 #include <cctype>
37 #include <fstream>
38 #include <limits>
39 #include <map>
40 #include <mutex>
41 #include <sstream>
42 #include <stdexcept>
43 #include <tuple>
44 #include <unordered_map>
45 #include <utility>
46 
47 namespace arm_compute
48 {
49 namespace test
50 {
51 namespace
52 {
53 template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
54 void rgb_to_luminance(const RawTensor &src, RawTensor &dst)
55 {
56  // Ensure in/out tensors have same image dimensions (independent of element size and number of channels)
57  ARM_COMPUTE_ERROR_ON_MSG(src.num_elements() != dst.num_elements(), "Input and output images must have equal dimensions");
58 
59  const size_t num_elements = dst.num_elements();
60 
61  // Currently, input is always RGB888 (3 U8 channels per element). Output can be U8, U16/S16 or U32
62  // Note that src.data()[i] returns pointer to first channel of element[i], so RGB values have [0,1,2] offsets
63  for(size_t i = 0, j = 0; j < num_elements; i += 3, ++j)
64  {
65  reinterpret_cast<T *>(dst.data())[j] = 0.2126f * src.data()[i] + 0.7152f * src.data()[i + 1] + 0.0722f * src.data()[i + 2];
66  }
67 }
68 
69 void extract_r_from_rgb(const RawTensor &src, RawTensor &dst)
70 {
71  ARM_COMPUTE_ERROR_ON(src.size() != 3 * dst.size());
72 
73  const size_t num_elements = dst.num_elements();
74 
75  for(size_t i = 0, j = 0; j < num_elements; i += 3, ++j)
76  {
77  dst.data()[j] = src.data()[i];
78  }
79 }
80 
81 void extract_g_from_rgb(const RawTensor &src, RawTensor &dst)
82 {
83  ARM_COMPUTE_ERROR_ON(src.size() != 3 * dst.size());
84 
85  const size_t num_elements = dst.num_elements();
86 
87  for(size_t i = 1, j = 0; j < num_elements; i += 3, ++j)
88  {
89  dst.data()[j] = src.data()[i];
90  }
91 }
92 
93 void extract_b_from_rgb(const RawTensor &src, RawTensor &dst)
94 {
95  ARM_COMPUTE_ERROR_ON(src.size() != 3 * dst.size());
96 
97  const size_t num_elements = dst.num_elements();
98 
99  for(size_t i = 2, j = 0; j < num_elements; i += 3, ++j)
100  {
101  dst.data()[j] = src.data()[i];
102  }
103 }
104 
105 void discard_comments(std::ifstream &fs)
106 {
107  while(fs.peek() == '#')
108  {
109  fs.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
110  }
111 }
112 
113 void discard_comments_and_spaces(std::ifstream &fs)
114 {
115  while(true)
116  {
117  discard_comments(fs);
118 
119  if(isspace(fs.peek()) == 0)
120  {
121  break;
122  }
123 
124  fs.ignore(1);
125  }
126 }
127 
128 std::tuple<unsigned int, unsigned int, int> parse_netpbm_format_header(std::ifstream &fs, char number)
129 {
130  // check file type magic number is valid
131  std::array<char, 2> magic_number{ { 0 } };
132  fs >> magic_number[0] >> magic_number[1];
133 
134  if(magic_number[0] != 'P' || magic_number[1] != number)
135  {
136  throw std::runtime_error("File type magic number not supported");
137  }
138 
139  discard_comments_and_spaces(fs);
140 
141  unsigned int width = 0;
142  fs >> width;
143 
144  discard_comments_and_spaces(fs);
145 
146  unsigned int height = 0;
147  fs >> height;
148 
149  discard_comments_and_spaces(fs);
150 
151  int max_value = 0;
152  fs >> max_value;
153 
154  if(!fs.good())
155  {
156  throw std::runtime_error("Cannot read image dimensions");
157  }
158 
159  if(max_value != 255)
160  {
161  throw std::runtime_error("RawTensor doesn't have 8-bit values");
162  }
163 
164  discard_comments(fs);
165 
166  if(isspace(fs.peek()) == 0)
167  {
168  throw std::runtime_error("Invalid image header");
169  }
170 
171  fs.ignore(1);
172 
173  return std::make_tuple(width, height, max_value);
174 }
175 
176 std::tuple<unsigned int, unsigned int, int> parse_ppm_header(std::ifstream &fs)
177 {
178  return parse_netpbm_format_header(fs, '6');
179 }
180 
181 std::tuple<unsigned int, unsigned int, int> parse_pgm_header(std::ifstream &fs)
182 {
183  return parse_netpbm_format_header(fs, '5');
184 }
185 
186 void check_image_size(std::ifstream &fs, size_t raw_size)
187 {
188  const size_t current_position = fs.tellg();
189  fs.seekg(0, std::ios_base::end);
190  const size_t end_position = fs.tellg();
191  fs.seekg(current_position, std::ios_base::beg);
192 
193  if((end_position - current_position) < raw_size)
194  {
195  throw std::runtime_error("Not enough data in file");
196  }
197 }
198 
199 void read_image_buffer(std::ifstream &fs, RawTensor &raw)
200 {
201  fs.read(reinterpret_cast<std::fstream::char_type *>(raw.data()), raw.size());
202 
203  if(!fs.good())
204  {
205  throw std::runtime_error("Failure while reading image buffer");
206  }
207 }
208 
209 RawTensor load_ppm(const std::string &path)
210 {
211  std::ifstream file(path, std::ios::in | std::ios::binary);
212 
213  if(!file.good())
214  {
215  throw framework::FileNotFound("Could not load PPM image: " + path);
216  }
217 
218  unsigned int width = 0;
219  unsigned int height = 0;
220 
221  std::tie(width, height, std::ignore) = parse_ppm_header(file);
222 
223  RawTensor raw(TensorShape(width, height), Format::RGB888);
224 
225  check_image_size(file, raw.size());
226  read_image_buffer(file, raw);
227 
228  return raw;
229 }
230 
231 RawTensor load_pgm(const std::string &path)
232 {
233  std::ifstream file(path, std::ios::in | std::ios::binary);
234 
235  if(!file.good())
236  {
237  throw framework::FileNotFound("Could not load PGM image: " + path);
238  }
239 
240  unsigned int width = 0;
241  unsigned int height = 0;
242 
243  std::tie(width, height, std::ignore) = parse_pgm_header(file);
244 
245  RawTensor raw(TensorShape(width, height), Format::U8);
246 
247  check_image_size(file, raw.size());
248  read_image_buffer(file, raw);
249 
250  return raw;
251 }
252 } // namespace
253 
254 AssetsLibrary::AssetsLibrary(std::string path, std::random_device::result_type seed) //NOLINT
255  : _library_path(std::move(path)),
256  _seed{ seed }
257 {
258 }
259 
260 std::string AssetsLibrary::path() const
261 {
262  return _library_path;
263 }
264 
265 std::random_device::result_type AssetsLibrary::seed() const
266 {
267  return _seed;
268 }
269 
270 void AssetsLibrary::fill(RawTensor &raw, const std::string &name, Format format) const
271 {
272  //FIXME: Should be done by swapping cached buffers
273  const RawTensor &src = get(name, format);
274  std::copy_n(src.data(), raw.size(), raw.data());
275 }
276 
277 void AssetsLibrary::fill(RawTensor &raw, const std::string &name, Channel channel) const
278 {
279  fill(raw, name, get_format_for_channel(channel), channel);
280 }
281 
282 void AssetsLibrary::fill(RawTensor &raw, const std::string &name, Format format, Channel channel) const
283 {
284  const RawTensor &src = get(name, format, channel);
285  std::copy_n(src.data(), raw.size(), raw.data());
286 }
287 
288 const AssetsLibrary::Loader &AssetsLibrary::get_loader(const std::string &extension) const
289 {
290  static std::unordered_map<std::string, Loader> loaders =
291  {
292  { "ppm", load_ppm },
293  { "pgm", load_pgm }
294  };
295 
296  const auto it = loaders.find(extension);
297 
298  if(it != loaders.end())
299  {
300  return it->second;
301  }
302  else
303  {
304  throw std::invalid_argument("Cannot load image with extension '" + extension + "'");
305  }
306 }
307 
308 const AssetsLibrary::Converter &AssetsLibrary::get_converter(Format src, Format dst) const
309 {
310  static std::map<std::pair<Format, Format>, Converter> converters =
311  {
312  { std::make_pair(Format::RGB888, Format::U8), rgb_to_luminance<uint8_t> },
313  { std::make_pair(Format::RGB888, Format::U16), rgb_to_luminance<uint16_t> },
314  { std::make_pair(Format::RGB888, Format::S16), rgb_to_luminance<int16_t> },
315  { std::make_pair(Format::RGB888, Format::U32), rgb_to_luminance<uint32_t> }
316  };
317 
318  const auto it = converters.find(std::make_pair(src, dst));
319 
320  if(it != converters.end())
321  {
322  return it->second;
323  }
324  else
325  {
326  std::stringstream msg;
327  msg << "Cannot convert from format '" << src << "' to format '" << dst << "'\n";
328  throw std::invalid_argument(msg.str());
329  }
330 }
331 
332 const AssetsLibrary::Converter &AssetsLibrary::get_converter(DataType src, Format dst) const
333 {
334  static std::map<std::pair<DataType, Format>, Converter> converters = {};
335 
336  const auto it = converters.find(std::make_pair(src, dst));
337 
338  if(it != converters.end())
339  {
340  return it->second;
341  }
342  else
343  {
344  std::stringstream msg;
345  msg << "Cannot convert from data type '" << src << "' to format '" << dst << "'\n";
346  throw std::invalid_argument(msg.str());
347  }
348 }
349 
350 const AssetsLibrary::Converter &AssetsLibrary::get_converter(DataType src, DataType dst) const
351 {
352  static std::map<std::pair<DataType, DataType>, Converter> converters = {};
353 
354  const auto it = converters.find(std::make_pair(src, dst));
355 
356  if(it != converters.end())
357  {
358  return it->second;
359  }
360  else
361  {
362  std::stringstream msg;
363  msg << "Cannot convert from data type '" << src << "' to data type '" << dst << "'\n";
364  throw std::invalid_argument(msg.str());
365  }
366 }
367 
368 const AssetsLibrary::Converter &AssetsLibrary::get_converter(Format src, DataType dst) const
369 {
370  static std::map<std::pair<Format, DataType>, Converter> converters = {};
371 
372  const auto it = converters.find(std::make_pair(src, dst));
373 
374  if(it != converters.end())
375  {
376  return it->second;
377  }
378  else
379  {
380  std::stringstream msg;
381  msg << "Cannot convert from format '" << src << "' to data type '" << dst << "'\n";
382  throw std::invalid_argument(msg.str());
383  }
384 }
385 
386 const AssetsLibrary::Extractor &AssetsLibrary::get_extractor(Format format, Channel channel) const
387 {
388  static std::map<std::pair<Format, Channel>, Extractor> extractors =
389  {
390  { std::make_pair(Format::RGB888, Channel::R), extract_r_from_rgb },
391  { std::make_pair(Format::RGB888, Channel::G), extract_g_from_rgb },
392  { std::make_pair(Format::RGB888, Channel::B), extract_b_from_rgb }
393  };
394 
395  const auto it = extractors.find(std::make_pair(format, channel));
396 
397  if(it != extractors.end())
398  {
399  return it->second;
400  }
401  else
402  {
403  std::stringstream msg;
404  msg << "Cannot extract channel '" << channel << "' from format '" << format << "'\n";
405  throw std::invalid_argument(msg.str());
406  }
407 }
408 
409 RawTensor AssetsLibrary::load_image(const std::string &name) const
410 {
411 #ifdef _WIN32
412  const std::string image_path = ("\\images\\");
413 #else /* _WIN32 */
414  const std::string image_path = ("/images/");
415 #endif /* _WIN32 */
416 
417  const std::string path = _library_path + image_path + name;
418  const std::string extension = path.substr(path.find_last_of('.') + 1);
419  return (*get_loader(extension))(path);
420 }
421 
422 const RawTensor &AssetsLibrary::find_or_create_raw_tensor(const std::string &name, Format format) const
423 {
424  std::lock_guard<arm_compute::Mutex> guard(_format_lock);
425 
426  const RawTensor *ptr = _cache.find(std::forward_as_tuple(name, format));
427 
428  if(ptr != nullptr)
429  {
430  return *ptr;
431  }
432 
433  RawTensor raw = load_image(name);
434 
435  if(raw.format() != format)
436  {
437  //FIXME: Remove unnecessary copy
438  RawTensor dst(raw.shape(), format);
439  (*get_converter(raw.format(), format))(raw, dst);
440  raw = std::move(dst);
441  }
442 
443  return _cache.add(std::forward_as_tuple(name, format), std::move(raw));
444 }
445 
446 const RawTensor &AssetsLibrary::find_or_create_raw_tensor(const std::string &name, Format format, Channel channel) const
447 {
448  std::lock_guard<arm_compute::Mutex> guard(_channel_lock);
449 
450  const RawTensor *ptr = _cache.find(std::forward_as_tuple(name, format, channel));
451 
452  if(ptr != nullptr)
453  {
454  return *ptr;
455  }
456 
457  const RawTensor &src = get(name, format);
458  //FIXME: Need to change shape to match channel
459  RawTensor dst(src.shape(), get_channel_format(channel));
460 
461  (*get_extractor(format, channel))(src, dst);
462 
463  return _cache.add(std::forward_as_tuple(name, format, channel), std::move(dst));
464 }
465 
467 {
468  return load_image(name).shape();
469 }
470 
471 const RawTensor &AssetsLibrary::get(const std::string &name) const
472 {
473  //FIXME: Format should be derived from the image name. Not be fixed to RGB.
474  return find_or_create_raw_tensor(name, Format::RGB888);
475 }
476 
477 RawTensor AssetsLibrary::get(const std::string &name)
478 {
479  //FIXME: Format should be derived from the image name. Not be fixed to RGB.
480  return RawTensor(find_or_create_raw_tensor(name, Format::RGB888));
481 }
482 
483 RawTensor AssetsLibrary::get(const std::string &name, DataType data_type, int num_channels) const
484 {
485  const RawTensor &raw = get(name);
486 
487  return RawTensor(raw.shape(), data_type, num_channels);
488 }
489 
490 const RawTensor &AssetsLibrary::get(const std::string &name, Format format) const
491 {
492  return find_or_create_raw_tensor(name, format);
493 }
494 
495 RawTensor AssetsLibrary::get(const std::string &name, Format format)
496 {
497  return RawTensor(find_or_create_raw_tensor(name, format));
498 }
499 
500 const RawTensor &AssetsLibrary::get(const std::string &name, Channel channel) const
501 {
502  return get(name, get_format_for_channel(channel), channel);
503 }
504 
505 RawTensor AssetsLibrary::get(const std::string &name, Channel channel)
506 {
507  return RawTensor(get(name, get_format_for_channel(channel), channel));
508 }
509 
510 const RawTensor &AssetsLibrary::get(const std::string &name, Format format, Channel channel) const
511 {
512  return find_or_create_raw_tensor(name, format, channel);
513 }
514 
515 RawTensor AssetsLibrary::get(const std::string &name, Format format, Channel channel)
516 {
517  return RawTensor(find_or_create_raw_tensor(name, format, channel));
518 }
519 
520 namespace detail
521 {
522 inline void validate_npy_header(std::ifstream &stream, const std::string &expect_typestr, const TensorShape &expect_shape)
523 {
524  ARM_COMPUTE_UNUSED(expect_typestr);
525  ARM_COMPUTE_UNUSED(expect_shape);
526 
527  std::string header = npy::read_header(stream);
528 
529  // Parse header
530  std::vector<unsigned long> shape;
531  bool fortran_order = false;
532  std::string typestr;
533  npy::parse_header(header, typestr, fortran_order, shape);
534 
535  // Check if the typestring matches the given one
536  ARM_COMPUTE_ERROR_ON_MSG(typestr != expect_typestr, "Typestrings mismatch");
537 
538  // Validate tensor shape
539  ARM_COMPUTE_ERROR_ON_MSG(shape.size() != expect_shape.num_dimensions(), "Tensor ranks mismatch");
540  if(fortran_order)
541  {
542  for(size_t i = 0; i < shape.size(); ++i)
543  {
544  ARM_COMPUTE_ERROR_ON_MSG(expect_shape[i] != shape[i], "Tensor dimensions mismatch");
545  }
546  }
547  else
548  {
549  for(size_t i = 0; i < shape.size(); ++i)
550  {
551  ARM_COMPUTE_ERROR_ON_MSG(expect_shape[i] != shape[shape.size() - i - 1], "Tensor dimensions mismatch");
552  }
553  }
554 }
555 } // namespace detail
556 } // namespace test
557 } // namespace arm_compute
Subclass of SimpleTensor using uint8_t as value type.
Definition: RawTensor.h:38
Shape of a tensor.
Definition: TensorShape.h:39
size_t size() const override
Total size of the tensor in bytes.
Definition: SimpleTensor.h:338
std::string path() const
Path to assets directory used to initialise library.
Format get_format_for_channel(Channel channel)
Look up the format corresponding to a channel.
Definition: Utils.h:151
1 channel, 1 U8 per channel
Format format() const override
Image format of the tensor.
Definition: SimpleTensor.h:345
#define ARM_COMPUTE_ERROR_ON(cond)
If the condition is true then an error message is printed and an exception thrown.
Definition: Error.h:466
void validate_npy_header(std::ifstream &stream, const std::string &expect_typestr, const TensorShape &expect_shape)
1 channel, 1 U16 per channel
TensorShape shape() const override
Shape of the tensor.
Definition: SimpleTensor.h:320
decltype(strategy::transforms) typedef type
TensorShape get_image_shape(const std::string &name)
Provides a tensor shape for the specified image.
const RawTensor & get(const std::string &name) const
Provides a constant raw tensor for the specified image.
SimpleTensor< float > src
Definition: DFT.cpp:155
Copyright (c) 2017-2021 Arm Limited.
3 channels, 1 U8 per channel
const DataType data_type
Definition: Im2Col.cpp:150
Format get_channel_format(Channel channel)
Return the format of a channel.
Definition: Utils.h:170
#define ARM_COMPUTE_UNUSED(...)
To avoid unused variables warnings.
Definition: Error.h:152
1 channel, 1 U32 per channel
Channel
Available channels.
Definition: Types.h:487
Format
Image colour formats.
Definition: Types.h:54
#define ARM_COMPUTE_ERROR_ON_MSG(cond, msg)
Definition: Error.h:456
void fill(T &&tensor, D &&distribution, std::random_device::result_type seed_offset) const
Fills the specified tensor with random values drawn from distribution.
void end(TokenStream &in, bool &valid)
Definition: MLGOParser.cpp:290
1 channel, 1 S16 per channel
const char * name
RawTensor & add(std::tuple< const std::string &, Format > key, RawTensor raw)
Add the given tensor to the cache.
Definition: TensorCache.h:107
RawTensor * find(std::tuple< const std::string &, Format > key)
Search the cache for a tensor of created from the specified image and format.
Definition: TensorCache.h:95
unsigned int num_dimensions() const
Returns the effective dimensionality of the tensor.
Definition: Dimensions.h:143
std::tuple< unsigned int, unsigned int, int > parse_ppm_header(std::ifstream &fs)
Parse the ppm header from an input file stream.
Definition: Utils.cpp:202
DataType
Available data types.
Definition: Types.h:77
void header(TokenStream &in, bool &valid)
Definition: MLGOParser.cpp:481
AssetsLibrary(std::string path, std::random_device::result_type seed)
Initialises the library with a path to the assets directory.
std::random_device::result_type seed() const
Seed that is used to fill tensors with random values.
const T * data() const
Constant pointer to the underlying buffer.
Definition: SimpleTensor.h:418