arm_compute v18.08
diff --git a/utils/GraphUtils.cpp b/utils/GraphUtils.cpp
index 0edb6f2..f6c2fda 100644
--- a/utils/GraphUtils.cpp
+++ b/utils/GraphUtils.cpp
@@ -26,16 +26,19 @@
 
 #include "arm_compute/core/Helpers.h"
 #include "arm_compute/core/Types.h"
+#include "arm_compute/graph/Logger.h"
 #include "arm_compute/runtime/SubTensor.h"
+#include "utils/ImageLoader.h"
 #include "utils/Utils.h"
 
 #include <iomanip>
+#include <limits>
 
 using namespace arm_compute::graph_utils;
 
 namespace
 {
-std::pair<arm_compute::TensorShape, arm_compute::PermutationVector> compute_permutation_paramaters(const arm_compute::TensorShape &shape,
+std::pair<arm_compute::TensorShape, arm_compute::PermutationVector> compute_permutation_parameters(const arm_compute::TensorShape &shape,
                                                                                                    arm_compute::DataLayout data_layout)
 {
     // Set permutation parameters if needed
@@ -82,9 +85,11 @@
     Window window;
     window.use_tensor_dimensions(tensor.info()->tensor_shape());
 
+    const int channel_idx = get_data_layout_dimension_index(tensor.info()->data_layout(), DataLayoutDimension::CHANNEL);
+
     execute_window_loop(window, [&](const Coordinates & id)
     {
-        const float value                                     = *reinterpret_cast<float *>(tensor.ptr_to_element(id)) - _mean[id.z()];
+        const float value                                     = *reinterpret_cast<float *>(tensor.ptr_to_element(id)) - _mean[id[channel_idx]];
         *reinterpret_cast<float *>(tensor.ptr_to_element(id)) = value;
     });
 }
@@ -144,7 +149,7 @@
 template <typename T>
 void NumPyAccessor::access_numpy_tensor(ITensor &tensor)
 {
-    const int num_elements          = tensor.info()->total_size();
+    const int num_elements          = tensor.info()->tensor_shape().total_size();
     int       num_mismatches        = utils::compare_tensor<T>(tensor, _npy_tensor);
     float     percentage_mismatches = static_cast<float>(num_mismatches) / num_elements;
 
@@ -168,38 +173,245 @@
     return false;
 }
 
-PPMAccessor::PPMAccessor(std::string ppm_path, bool bgr, std::unique_ptr<IPreprocessor> preprocessor)
-    : _ppm_path(std::move(ppm_path)), _bgr(bgr), _preprocessor(std::move(preprocessor))
+ImageAccessor::ImageAccessor(std::string filename, bool bgr, std::unique_ptr<IPreprocessor> preprocessor)
+    : _already_loaded(false), _filename(std::move(filename)), _bgr(bgr), _preprocessor(std::move(preprocessor))
 {
 }
 
-bool PPMAccessor::access_tensor(ITensor &tensor)
+bool ImageAccessor::access_tensor(ITensor &tensor)
 {
-    utils::PPMLoader ppm;
-
-    // Open PPM file
-    ppm.open(_ppm_path);
-
-    // Get permutated shape and permutation parameters
-    TensorShape                    permuted_shape = tensor.info()->tensor_shape();
-    arm_compute::PermutationVector perm;
-    if(tensor.info()->data_layout() != DataLayout::NCHW)
+    if(!_already_loaded)
     {
-        std::tie(permuted_shape, perm) = compute_permutation_paramaters(tensor.info()->tensor_shape(), tensor.info()->data_layout());
-    }
-    ARM_COMPUTE_ERROR_ON_MSG(ppm.width() != permuted_shape.x() || ppm.height() != permuted_shape.y(),
-                             "Failed to load image file: dimensions [%d,%d] not correct, expected [%d,%d].", ppm.width(), ppm.height(), permuted_shape.x(), permuted_shape.y());
+        auto image_loader = utils::ImageLoaderFactory::create(_filename);
+        ARM_COMPUTE_EXIT_ON_MSG(image_loader == nullptr, "Unsupported image type");
 
-    // Fill the tensor with the PPM content (BGR)
-    ppm.fill_planar_tensor(tensor, _bgr);
+        // Open image file
+        image_loader->open(_filename);
 
-    // Preprocess tensor
-    if(_preprocessor)
-    {
-        _preprocessor->preprocess(tensor);
+        // 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_parameters(tensor.info()->tensor_shape(), tensor.info()->data_layout());
+        }
+        ARM_COMPUTE_EXIT_ON_MSG(image_loader->width() != permuted_shape.x() || image_loader->height() != permuted_shape.y(),
+                                "Failed to load image file: dimensions [%d,%d] not correct, expected [%d,%d].",
+                                image_loader->width(), image_loader->height(), permuted_shape.x(), permuted_shape.y());
+
+        // Fill the tensor with the PPM content (BGR)
+        image_loader->fill_planar_tensor(tensor, _bgr);
+
+        // Preprocess tensor
+        if(_preprocessor)
+        {
+            _preprocessor->preprocess(tensor);
+        }
     }
 
-    return true;
+    _already_loaded = !_already_loaded;
+    return _already_loaded;
+}
+
+ValidationInputAccessor::ValidationInputAccessor(const std::string             &image_list,
+                                                 std::string                    images_path,
+                                                 std::unique_ptr<IPreprocessor> preprocessor,
+                                                 bool                           bgr,
+                                                 unsigned int                   start,
+                                                 unsigned int                   end,
+                                                 std::ostream                  &output_stream)
+    : _path(std::move(images_path)), _images(), _preprocessor(std::move(preprocessor)), _bgr(bgr), _offset(0), _output_stream(output_stream)
+{
+    ARM_COMPUTE_EXIT_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
+        std::string image_name = _path + _images[_offset++];
+        jpeg.open(image_name);
+        _output_stream << "[" << _offset << "/" << _images.size() << "] Validating " << image_name << std::endl;
+
+        // 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_parameters(tensor.info()->tensor_shape(),
+                                                                            tensor.info()->data_layout());
+        }
+        ARM_COMPUTE_EXIT_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 JPEG content (BGR)
+        jpeg.fill_planar_tensor(tensor, _bgr);
+
+        // Preprocess tensor
+        if(_preprocessor)
+        {
+            _preprocessor->preprocess(tensor);
+        }
+    }
+
+    return ret;
+}
+
+ValidationOutputAccessor::ValidationOutputAccessor(const std::string &image_list,
+                                                   std::ostream      &output_stream,
+                                                   unsigned int       start,
+                                                   unsigned int       end)
+    : _results(), _output_stream(output_stream), _offset(0), _positive_samples_top1(0), _positive_samples_top5(0)
+{
+    ARM_COMPUTE_EXIT_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 correctly classified labels
+        unsigned int counter = 0;
+        for(std::string line; !std::getline(ifs, line).fail() && counter <= end; ++counter)
+        {
+            // Add label if within range
+            if(counter >= start)
+            {
+                std::stringstream linestream(line);
+                std::string       image_name;
+                int               result;
+
+                linestream >> image_name >> result;
+                _results.emplace_back(result);
+            }
+        }
+    }
+    catch(const std::ifstream::failure &e)
+    {
+        ARM_COMPUTE_ERROR("Accessing %s: %s", image_list.c_str(), e.what());
+    }
+}
+
+void ValidationOutputAccessor::reset()
+{
+    _offset                = 0;
+    _positive_samples_top1 = 0;
+    _positive_samples_top5 = 0;
+}
+
+bool ValidationOutputAccessor::access_tensor(arm_compute::ITensor &tensor)
+{
+    bool ret = _offset < _results.size();
+    if(ret)
+    {
+        // Get results
+        std::vector<size_t> tensor_results;
+        switch(tensor.info()->data_type())
+        {
+            case DataType::QASYMM8:
+                tensor_results = access_predictions_tensor<uint8_t>(tensor);
+                break;
+            case DataType::F32:
+                tensor_results = access_predictions_tensor<float>(tensor);
+                break;
+            default:
+                ARM_COMPUTE_ERROR("NOT SUPPORTED!");
+        }
+
+        // Check if tensor results are within top-n accuracy
+        size_t correct_label = _results[_offset++];
+
+        aggregate_sample(tensor_results, _positive_samples_top1, 1, correct_label);
+        aggregate_sample(tensor_results, _positive_samples_top5, 5, correct_label);
+    }
+
+    // Report top_n accuracy
+    if(_offset >= _results.size())
+    {
+        report_top_n(1, _results.size(), _positive_samples_top1);
+        report_top_n(5, _results.size(), _positive_samples_top5);
+    }
+
+    return ret;
+}
+
+template <typename T>
+std::vector<size_t> ValidationOutputAccessor::access_predictions_tensor(arm_compute::ITensor &tensor)
+{
+    // Get the predicted class
+    std::vector<size_t> index;
+
+    const auto   output_net  = reinterpret_cast<T *>(tensor.buffer() + tensor.info()->offset_first_element_in_bytes());
+    const size_t num_classes = tensor.info()->dimension(0);
+
+    index.resize(num_classes);
+
+    // Sort results
+    std::iota(std::begin(index), std::end(index), static_cast<size_t>(0));
+    std::sort(std::begin(index), std::end(index),
+              [&](size_t a, size_t b)
+    {
+        return output_net[a] > output_net[b];
+    });
+
+    return index;
+}
+
+void ValidationOutputAccessor::aggregate_sample(const std::vector<size_t> &res, size_t &positive_samples, size_t top_n, size_t correct_label)
+{
+    auto is_valid_label = [correct_label](size_t label)
+    {
+        return label == correct_label;
+    };
+
+    if(std::any_of(std::begin(res), std::begin(res) + top_n, is_valid_label))
+    {
+        ++positive_samples;
+    }
+}
+
+void ValidationOutputAccessor::report_top_n(size_t top_n, size_t total_samples, size_t positive_samples)
+{
+    size_t negative_samples = total_samples - positive_samples;
+    float  accuracy         = positive_samples / static_cast<float>(total_samples);
+
+    _output_stream << "----------Top " << top_n << " accuracy ----------" << std::endl
+                   << std::endl;
+    _output_stream << "Positive samples : " << positive_samples << std::endl;
+    _output_stream << "Negative samples : " << negative_samples << std::endl;
+    _output_stream << "Accuracy : " << accuracy << std::endl;
 }
 
 TopNPredictionsAccessor::TopNPredictionsAccessor(const std::string &labels_path, size_t top_n, std::ostream &output_stream)
@@ -322,7 +534,6 @@
             break;
         }
         case DataType::S8:
-        case DataType::QS8:
         {
             std::uniform_int_distribution<int8_t> distribution_s8(_lower.get<int8_t>(), _upper.get<int8_t>());
             fill<int8_t>(tensor, distribution_s8);
@@ -335,7 +546,6 @@
             break;
         }
         case DataType::S16:
-        case DataType::QS16:
         {
             std::uniform_int_distribution<int16_t> distribution_s16(_lower.get<int16_t>(), _upper.get<int16_t>());
             fill<int16_t>(tensor, distribution_s16);
@@ -390,103 +600,19 @@
 }
 
 NumPyBinLoader::NumPyBinLoader(std::string filename, DataLayout file_layout)
-    : _filename(std::move(filename)), _file_layout(file_layout)
+    : _already_loaded(false), _filename(std::move(filename)), _file_layout(file_layout)
 {
 }
 
 bool NumPyBinLoader::access_tensor(ITensor &tensor)
 {
-    const TensorShape          tensor_shape = tensor.info()->tensor_shape();
-    std::vector<unsigned long> shape;
-
-    // Open file
-    std::ifstream stream(_filename, std::ios::in | std::ios::binary);
-    ARM_COMPUTE_ERROR_ON_MSG(!stream.good(), "Failed to load binary data");
-    std::string header = npy::read_header(stream);
-
-    // Parse header
-    bool        fortran_order = false;
-    std::string typestr;
-    npy::parse_header(header, typestr, fortran_order, shape);
-
-    // Check if the typestring matches the given one
-    std::string expect_typestr = arm_compute::utils::get_typestring(tensor.info()->data_type());
-    ARM_COMPUTE_ERROR_ON_MSG(typestr != expect_typestr, "Typestrings mismatch");
-
-    // Reverse vector in case of non fortran order
-    if(!fortran_order)
+    if(!_already_loaded)
     {
-        std::reverse(shape.begin(), shape.end());
+        utils::NPYLoader loader;
+        loader.open(_filename, _file_layout);
+        loader.fill_tensor(tensor);
     }
 
-    // Correct dimensions (Needs to match TensorShape dimension corrections)
-    if(shape.size() != tensor_shape.num_dimensions())
-    {
-        for(int i = static_cast<int>(shape.size()) - 1; i > 0; --i)
-        {
-            if(shape[i] == 1)
-            {
-                shape.pop_back();
-            }
-            else
-            {
-                break;
-            }
-        }
-    }
-
-    bool are_layouts_different = (_file_layout != tensor.info()->data_layout());
-
-    // Validate tensor ranks
-    ARM_COMPUTE_ERROR_ON_MSG(shape.size() != tensor_shape.num_dimensions(), "Tensor ranks mismatch");
-
-    // Set permutation parameters if needed
-    TensorShape                    permuted_shape = tensor_shape;
-    arm_compute::PermutationVector perm;
-    if(are_layouts_different)
-    {
-        std::tie(permuted_shape, perm) = compute_permutation_paramaters(tensor_shape, tensor.info()->data_layout());
-    }
-
-    // Validate shapes
-    for(size_t i = 0; i < shape.size(); ++i)
-    {
-        ARM_COMPUTE_ERROR_ON_MSG(permuted_shape[i] != shape[i], "Tensor dimensions mismatch");
-    }
-
-    // Validate shapes and copy tensor
-    if(!are_layouts_different || perm.num_dimensions() <= 2)
-    {
-        // Read data
-        if(tensor.info()->padding().empty() && (dynamic_cast<SubTensor *>(&tensor) == nullptr))
-        {
-            // If tensor has no padding read directly from stream.
-            stream.read(reinterpret_cast<char *>(tensor.buffer()), tensor.info()->total_size());
-        }
-        else
-        {
-            // If tensor has padding accessing tensor elements through execution window.
-            Window window;
-            window.use_tensor_dimensions(tensor_shape);
-
-            execute_window_loop(window, [&](const Coordinates & id)
-            {
-                stream.read(reinterpret_cast<char *>(tensor.ptr_to_element(id)), tensor.info()->element_size());
-            });
-        }
-    }
-    else
-    {
-        // If tensor has padding accessing tensor elements through execution window.
-        Window window;
-        window.use_tensor_dimensions(permuted_shape);
-
-        execute_window_loop(window, [&](const Coordinates & id)
-        {
-            Coordinates coords(id);
-            arm_compute::permute(coords, perm);
-            stream.read(reinterpret_cast<char *>(tensor.ptr_to_element(coords)), tensor.info()->element_size());
-        });
-    }
-    return true;
+    _already_loaded = !_already_loaded;
+    return _already_loaded;
 }