Program Listing for File dataloader_impl.hpp

Return to documentation for file (/home/jenkins/docs/models/dataloader/dataloader_impl.hpp)

#ifndef MODELS_DATALOADER_DATALOADER_IMPL_HPP
#define MODELS_DATALOADER_DATALOADER_IMPL_HPP

#include "dataloader.hpp"

using namespace mlpack;

namespace mlpack {
namespace models {

template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
>DataLoader<
    DatasetX, DatasetY, ScalerType
>::DataLoader()
{
  // Nothing to do here.
}

template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
>DataLoader<
    DatasetX, DatasetY, ScalerType
>::DataLoader(const std::string& dataset,
              const bool shuffle,
              const double validRatio,
              const bool useScaler,
              const std::vector<std::string> augmentation,
              const double augmentationProbability)
{
  InitializeDatasets();
  if (datasetMap.count(dataset))
  {
    // Use utility functions to download the dataset.
    DownloadDataset(dataset);

    if (datasetMap[dataset].datasetType == "csv")
    {
      LoadCSV(datasetMap[dataset].trainPath, true, shuffle, validRatio,
              useScaler, datasetMap[dataset].startTrainingInputFeatures,
              datasetMap[dataset].endTrainingInputFeatures,
              datasetMap[dataset].endTrainingPredictionFeatures,
              datasetMap[dataset].endTrainingPredictionFeatures);

      LoadCSV(datasetMap[dataset].testPath, false, false, validRatio, useScaler,
              datasetMap[dataset].startTestingInputFeatures,
              datasetMap[dataset].endTestingInputFeatures);
    }
    else if (datasetMap[dataset].datasetType == "image-detection")
    {
      std::vector<std::string> augmentations = augmentation;

      // If user doesn't set size for images, set size of images to {64, 64}.
      if (augmentations.size() == 0)
      {
        mlpack::Log::Warn << "Images will be resized to {64, 64}." <<
            "To resize image, pass augmentation argument with" <<
            "resize parameter. For more information refer to the " <<
            "dataloader and augmentation tutorial." << std::endl;

        augmentations.push_back("resize = {64, 64}");
      }

      LoadObjectDetectionDataset(datasetMap[dataset].trainingAnnotationPath,
          datasetMap[dataset].trainingImagesPath, datasetMap[dataset].classes,
          validRatio, shuffle, augmentations, augmentationProbability);

      // Load testing data if any. Most object detection dataset
      // have private evaluation servers.
      if (datasetMap[dataset].testingImagesPath.length() > 0)
      {
        LoadAllImagesFromDirectory(datasetMap[dataset].testingImagesPath,
            testFeatures, testLabels, datasetMap[dataset].imageWidth,
            datasetMap[dataset].imageHeight, datasetMap[dataset].imageDepth);
      }
    }
    else if (datasetMap[dataset].datasetType == "image-classification")
    {
      LoadImageDatasetFromDirectory(datasetMap[dataset].trainingImagesPath,
          datasetMap[dataset].imageWidth, datasetMap[dataset].imageHeight,
          datasetMap[dataset].imageDepth, true, validRatio, shuffle,
          augmentation, augmentationProbability);

      if (datasetMap[dataset].testingImagesPath.length() > 0)
      {
        LoadAllImagesFromDirectory(datasetMap[dataset].testingImagesPath,
            testFeatures, testLabels, datasetMap[dataset].imageWidth,
            datasetMap[dataset].imageHeight, datasetMap[dataset].imageDepth);
      }
    }

    // Preprocess the dataset.
    datasetMap[dataset].PreProcess(trainFeatures, trainLabels,
        validFeatures, validLabels, testFeatures);
  }
  else
  {
    mlpack::Log::Fatal << "Unknown Dataset. " << dataset <<
        " For other datasets try loading data using" <<
        " generic dataloader functions such as LoadCSV." <<
        " Refer to the documentation for more info." << std::endl;
  }
}


template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
> void DataLoader<
    DatasetX, DatasetY, ScalerType
>::LoadCSV(const std::string& datasetPath,
           const bool loadTrainData,
           const bool shuffle,
           const double validRatio,
           const bool useScaler,
           const int startInputFeatures,
           const int endInputFeatures,
           const int startPredictionFeatures,
           const int endPredictionFeatures,
           const std::vector<std::string> augmentation,
           const double augmentationProbability)
{
  arma::mat dataset;
  data::Load(datasetPath, dataset, true);

  if (loadTrainData)
  {
    arma::mat trainDataset, validDataset;
    data::Split(dataset, trainDataset, validDataset, validRatio, shuffle);

    trainFeatures = trainDataset.rows(WrapIndex(startInputFeatures,
        trainDataset.n_rows), WrapIndex(endInputFeatures,
        trainDataset.n_rows));

    trainLabels = trainDataset.rows(WrapIndex(startPredictionFeatures,
        trainDataset.n_rows), WrapIndex(endPredictionFeatures,
        trainDataset.n_rows));

    validFeatures = validDataset.rows(WrapIndex(startInputFeatures,
        validDataset.n_rows), WrapIndex(endInputFeatures,
        validDataset.n_rows));

    validLabels = validDataset.rows(WrapIndex(startPredictionFeatures,
        validDataset.n_rows), WrapIndex(endPredictionFeatures,
        validDataset.n_rows));

    if (useScaler)
    {
      scaler.Fit(trainFeatures);
      scaler.Transform(trainFeatures, trainFeatures);
      scaler.Transform(validFeatures, validFeatures);
    }

    Augmentation augmentations(augmentation, augmentationProbability);
    augmentations.Transform(trainFeatures, 1, dataset.n_rows, 1);

    mlpack::Log::Info << "Training Dataset Loaded." << std::endl;
  }
  else
  {
    if (useScaler)
    {
      scaler.Transform(dataset, dataset);
    }

    testFeatures = dataset.rows(WrapIndex(startInputFeatures, dataset.n_rows),
        WrapIndex(endInputFeatures, dataset.n_rows));

    mlpack::Log::Info << "Testing Dataset Loaded." << std::endl;
  }
}

template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
> void DataLoader<
    DatasetX, DatasetY, ScalerType
>::LoadObjectDetectionDataset(const std::string& pathToAnnotations,
                              const std::string& pathToImages,
                              const std::vector<std::string>& classes,
                              const double validRatio,
                              const bool shuffle,
                              const std::vector<std::string>& augmentations,
                              const double augmentationProbability,
                              const bool absolutePath,
                              const std::string& baseXMLTag,
                              const std::string& imageNameXMLTag,
                              const std::string& sizeXMLTag,
                              const std::string& objectXMLTag,
                              const std::string& bndboxXMLTag,
                              const std::string& classNameXMLTag,
                              const std::string& x1XMLTag,
                              const std::string& y1XMLTag,
                              const std::string& x2XMLTag,
                              const std::string& y2XMLTag)
{
  Augmentation augmentation(augmentations, augmentationProbability);

  std::vector<boost::filesystem::path> annotationsDirectory;

  DatasetX dataset;
  std::deque<arma::vec> labels;

  // Fill the directory.
  Utils::ListDir(pathToAnnotations, annotationsDirectory, absolutePath);

  // Create a map for labels and corresponding class name.
  // This provides faster access to class labels.
  std::unordered_map<std::string, size_t> classMap;
  for (size_t i = 0; i < classes.size(); i++)
    classMap.insert(std::make_pair(classes[i], i));

  // Map to insert values in a column vector.
  std::unordered_map<std::string, size_t> indexMap;
  indexMap.insert(std::make_pair(classNameXMLTag, 0));
  indexMap.insert(std::make_pair(x1XMLTag, 1));
  indexMap.insert(std::make_pair(y1XMLTag, 2));
  indexMap.insert(std::make_pair(x2XMLTag, 3));
  indexMap.insert(std::make_pair(y2XMLTag, 4));

  // Keep track of files loaded.
  size_t totalFiles = annotationsDirectory.size(), loadedFiles = 0;
  size_t imageWidth = 0, imageHeight = 0, imageDepth = 0;

  // Read the XML file.
  for (boost::filesystem::path annotationFile : annotationsDirectory)
  {
    if (annotationFile.string().length() <= 3 ||
        annotationFile.string().substr(
        annotationFile.string().length() - 3) != "xml")
    {
      continue;
    }

    loadedFiles++;
    Log::Info << "Files Loaded : " << loadedFiles << " out of " <<
        totalFiles << "\r" << std::endl;

    // Read the XML file.
    boost::property_tree::ptree xmlFile;
    boost::property_tree::read_xml(annotationFile.string(), xmlFile);

    // Get annotation from XML file.
    boost::property_tree::ptree annotation = xmlFile.get_child(baseXMLTag);

    // Read properties inside annotation file.
    // Get image name.
    std::string imgName = annotation.get_child(imageNameXMLTag).data();

    // If image doesn't exist then skip the current XML file.
    if (!Utils::PathExists(pathToImages + imgName, absolutePath))
    {
      mlpack::Log::Warn << "Image not found! Tried finding " <<
          pathToImages + imgName << std::endl;
      continue;
    }

    // Get the size of image to create image info required
    // by mlpack::data::Load function.
    boost::property_tree::ptree sizeInfo = annotation.get_child(sizeXMLTag);
    imageWidth = std::stoi(sizeInfo.get_child("width").data());
    imageHeight = std::stoi(sizeInfo.get_child("height").data());
    imageDepth = std::stoi(sizeInfo.get_child("depth").data());
    mlpack::data::ImageInfo imageInfo(imageWidth, imageHeight, imageDepth);

    // Load the image.
    // The image loaded here will be in column format i.e. Output will
    // be matrix with the following shape {1, cols * rows * slices} in
    // column major format.
    DatasetX image;
    mlpack::data::Load(pathToImages + imgName, image, imageInfo);

    double horizontalScale = 1.0, verticalScale = 1.0;
    if (augmentation.HasResizeParam())
    {
      augmentation.ResizeTransform(image, imageWidth, imageHeight, imageDepth,
          augmentation.augmentations[0]);

      size_t outputWidth = 0, outputHeight = 0;
      augmentation.GetResizeParam(outputWidth, outputHeight,
          augmentation.augmentations[0]);
      horizontalScale = 1.0 * outputWidth / imageWidth;
      verticalScale = 1.0 * outputHeight / imageHeight;
      imageWidth = outputWidth;
      imageHeight = outputHeight;
    }

    // Iterate over all object in annotation.
    arma::vec boundingBoxes;
    BOOST_FOREACH(boost::property_tree::ptree::value_type const& object,
        annotation)
    {
      arma::vec predictions(5);
      // Iterate over property of the object to get class label and
      // bounding box coordinates.
      if (object.first == objectXMLTag)
      {
        if (classMap.count(object.second.get_child(classNameXMLTag).data()))
        {
          predictions(indexMap[classNameXMLTag]) = classMap[
              object.second.get_child(classNameXMLTag).data()];
          boost::property_tree::ptree const &boundingBox =
              object.second.get_child(bndboxXMLTag);

          BOOST_FOREACH(boost::property_tree::ptree::value_type
              const& coordinate, boundingBox)
          {
            if (indexMap.count(coordinate.first))
            {
              predictions(indexMap[coordinate.first]) =
                  std::stoi(coordinate.second.data());
            }
          }

          // Scale predictions.
          predictions(indexMap[x1XMLTag]) = std::floor(
              predictions(indexMap[x1XMLTag]) * horizontalScale);
          predictions(indexMap[x2XMLTag]) = std::floor(
              predictions(indexMap[x2XMLTag]) * horizontalScale);
          predictions(indexMap[y1XMLTag]) = std::floor(
              predictions(indexMap[y1XMLTag]) * verticalScale);
          predictions(indexMap[y2XMLTag]) = std::floor(
              predictions(indexMap[y2XMLTag]) * verticalScale);
          boundingBoxes.insert_rows(0, predictions);
        }
      }
    }

    // Add object to training set.
    dataset.insert_cols(0, image);
    labels.push_front(boundingBoxes);
  }

  TrainTestSplit(dataset, labels, this->trainFeatures, this->trainLabels,
      this->validFeatures, this->validLabels, validRatio, shuffle);

  // Augment the training data.
  augmentation.Transform(this->trainFeatures, imageWidth, imageHeight,
      imageDepth);
}

template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
> void DataLoader<
    DatasetX, DatasetY, ScalerType
>::LoadAllImagesFromDirectory(const std::string& imagesPath,
                              DatasetX& dataset,
                              DatasetY& labels,
                              const size_t imageWidth,
                              const size_t imageHeight,
                              const size_t imageDepth,
                              const size_t label)
{
  // Get all files in given directory.
  std::vector<boost::filesystem::path> imagesDirectory;
  Utils::ListDir(imagesPath, imagesDirectory);

  std::set<std::string> supportedExtentions = {".jpg", ".png", ".tga",
      ".bmp", ".psd", ".gif", ".hdr", ".pic", ".pnm"};

  // We use to endls here as one of them will be replaced by print
  // command below.
  mlpack::Log::Info << "Found " << imagesDirectory.size() << " belonging to " <<
      label << " class." << std::endl << std::endl;

  size_t loadedImages = 0;
  for (boost::filesystem::path imageName : imagesDirectory)
  {
    if (imageName.string().length() <= 3 ||
        !boost::filesystem::is_regular_file(imageName) ||
        !supportedExtentions.count(imageName.extension().string()))
    {
      continue;
    }
    mlpack::data::ImageInfo imageInfo(imageWidth, imageHeight, imageDepth);

    // Load the image.
    // The image loaded here will be in column format i.e. Output will
    // be matrix with the following shape {1, cols * rows * slices} in
    // column major format.
    DatasetX image;
    mlpack::data::Load(imageName.string(), image, imageInfo);

    // Add object to training set.
    if (image.n_rows == dataset.n_rows || dataset.n_elem == 0)
    {
      labels.insert_cols(0, arma::vec(1).fill(label));
      dataset.insert_cols(0, image);
      loadedImages++;
      mlpack::Log::Info << "Loaded " << loadedImages << " out of " <<
          imagesDirectory.size() << "\r" << std::endl;
    }
  }
}

template<
  typename DatasetX,
  typename DatasetY,
  class ScalerType
> void DataLoader<
    DatasetX, DatasetY, ScalerType
>::LoadImageDatasetFromDirectory(const std::string& pathToDataset,
                                 const size_t imageWidth,
                                 const size_t imageHeight,
                                 const size_t imageDepth,
                                 const bool trainData,
                                 const double validRatio,
                                 const bool shuffle,
                                 const std::vector<std::string>& augmentation,
                                 const double augmentationProbability)
{
  Augmentation augmentations(augmentation, augmentationProbability);
  size_t totalClasses = 0;
  std::map<std::string, size_t> classMap;

  // Fill classes in the vector.
  std::vector<boost::filesystem::path> classes;
  Utils::ListDir(pathToDataset, classes);

  DatasetX dataset;
  DatasetY labels;

  // Iterate the directory.
  for (boost::filesystem::path className : classes)
  {
    if (boost::filesystem::is_directory(className))
    {
      LoadAllImagesFromDirectory(className.string() +
        "/", dataset, labels, imageWidth, imageHeight, imageDepth,
        totalClasses);
      classMap[className.string()] = totalClasses;
      totalClasses++;
    }
  }

  if (!trainData)
  {
    testFeatures = std::move(dataset);
    testLabels = std::move(labels);

    // Only resize augmentation will be applied on test set.
    if (augmentations.HasResizeParam())
    {
      augmentations.ResizeTransform(testFeatures, imageWidth, imageHeight,
          imageDepth, augmentations.augmentations[0]);
    }

    return;
  }

  // Train-validation data split.
  arma::mat completeDataset = arma::join_cols(dataset, labels);
  arma::mat trainingData, validationData;
  mlpack::data::Split(completeDataset, trainingData, validationData,
      validRatio, shuffle);

  trainFeatures = trainingData.rows(0, trainingData.n_rows - 2);
  trainLabels = trainingData.rows(trainingData.n_rows - 1,
      trainingData.n_rows - 1);

  validFeatures = validationData.rows(0, validationData.n_rows - 2);
  validLabels = validationData.rows(validationData.n_rows - 1,
      validationData.n_rows - 1);

  augmentations.Transform(trainFeatures, imageWidth, imageHeight, imageDepth);
  augmentations.Transform(validFeatures, imageWidth, imageHeight, imageDepth);

  mlpack::Log::Info << "Found " << totalClasses << " classes." << std::endl;

  // Print class-label mappings for ease.
  for (std::pair<std::string, int> classMapping : classMap)
  {
    mlpack::Log::Info << classMapping.first << " : " << classMapping.second <<
        std::endl;
  }
}

} // namespace models
} // namespace mlpack

#endif