From 492d84fab03bf1f55832501f9ebf6eb6774f89a7 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Thu, 12 Oct 2017 17:09:43 +0200 Subject: [PATCH] Dump intermediate results to PNG images. --- CMakeLists.txt | 2 +- cmake/modules/Findpng++.cmake | 2 +- src/Options.cpp | 20 ++++++-- src/Options.hpp | 4 +- src/PNGDumper.cpp | 89 +++++++++++++++++++++++++++++++++++ src/PNGDumper.hpp | 28 +++++++++++ src/main.cpp | 14 +++++- src/utils.hpp | 14 ++++++ 8 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 src/PNGDumper.cpp create mode 100644 src/PNGDumper.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 012d9a3..f42649a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,4 +41,4 @@ target_link_libraries(fmri ${GLOG_LIBRARIES}) # Require png++ find_package(png++ REQUIRED) include_directories(${png++_INCLUDE_DIRS}) -target_link_libraries(fmri ${png++-LIBRARIES}) +target_link_libraries(fmri ${png++_LIBRARIES}) diff --git a/cmake/modules/Findpng++.cmake b/cmake/modules/Findpng++.cmake index ac893c9..b058dc4 100644 --- a/cmake/modules/Findpng++.cmake +++ b/cmake/modules/Findpng++.cmake @@ -50,7 +50,7 @@ find_path(png++_INCLUDE_DIR src) set(png++_INCLUDE_DIRS ${png++_INCLUDE_DIR} ${PNG_INCLUDE_DIRS}) -set(png++_LIBRARIES ${PNG_LIBRARIES}) +set(png++_LIBRARY ${PNG_LIBRARIES}) find_package_handle_standard_args(png++ DEFAULT_MSG png++_INCLUDE_DIR) diff --git a/src/Options.cpp b/src/Options.cpp index 1b17b1c..bb04560 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -17,7 +17,8 @@ Options: -n (required) the model file to simulate -w (required) the trained weights -m means file. Will be substracted from input if available. - -l labels file. Will be used to print prediction labels if available.)END" << endl; + -l labels file. Will be used to print prediction labels if available. + -d Image dump dir. Will be filled with PNG images of intermediate results.)END" << endl; exit(exitcode); } @@ -33,11 +34,12 @@ Options Options::parse(const int argc, char *const argv[]) { string model; string weights; string means; + string dump; string labels; char c; - while ((c = getopt(argc, argv, "hm:w:n:l:")) != -1) { + while ((c = getopt(argc, argv, "hm:w:n:l:d:")) != -1) { switch (c) { case 'h': show_help(argv[0], 0); @@ -63,6 +65,10 @@ Options Options::parse(const int argc, char *const argv[]) { labels = optarg; break; + case 'd': + dump = optarg; + break; + case '?': show_help(argv[0], 1); break; @@ -91,14 +97,15 @@ Options Options::parse(const int argc, char *const argv[]) { show_help(argv[0], 1); } - return Options(move(model), move(weights), move(means), move(labels), move(inputs)); + return Options(move(model), move(weights), move(means), move(labels), move(dump), move(inputs)); } -Options::Options(string &&model, string &&weights, string&& means, string&& labels, vector &&inputs) noexcept: +Options::Options(string &&model, string &&weights, string&& means, string&& labels, string&& dumpPath, vector &&inputs) noexcept: modelPath(move(model)), weightsPath(move(weights)), meansPath(means), labelsPath(labels), + dumpPath(dumpPath), inputPaths(move(inputs)) { } @@ -124,3 +131,8 @@ const string& Options::labels() const { return labelsPath; } + +const string &Options::imageDump() const +{ + return dumpPath; +} diff --git a/src/Options.hpp b/src/Options.hpp index b5d672b..296e4e3 100644 --- a/src/Options.hpp +++ b/src/Options.hpp @@ -16,6 +16,7 @@ namespace fmri { const string& weights() const; const string& means() const; const string& labels() const; + const string& imageDump() const; const vector& inputs() const; @@ -24,8 +25,9 @@ namespace fmri { const string weightsPath; const string meansPath; const string labelsPath; + const string dumpPath; const vector inputPaths; - Options(string &&, string &&, string&&, string&&, vector &&) noexcept; + Options(string &&, string &&, string&&, string&&, string&&, vector &&) noexcept; }; } diff --git a/src/PNGDumper.cpp b/src/PNGDumper.cpp new file mode 100644 index 0000000..8cf9ad9 --- /dev/null +++ b/src/PNGDumper.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include +#include + +#include "PNGDumper.hpp" + +using namespace fmri; +using namespace std; + +static void ensureDir(const string& dir) +{ + struct stat s; + if (stat(dir.c_str(), &s) == 0) { + CHECK(S_ISDIR(s.st_mode)) << dir << " already exists and is not a directory." << endl; + return; + } + + switch (errno) { + case ENOENT: + PCHECK(mkdir(dir.c_str(), 0777) == 0) << "Couldn't create directory"; + return; + + default: + perror("Unusable dump dir"); + break; + } +} + +PNGDumper::PNGDumper(string_view baseDir) : + baseDir_(baseDir) +{ + ensureDir(baseDir_); +} + +void PNGDumper::dump(const LayerData &layerData) +{ + if (layerData.shape().size() == 4) { + // We have a series of images. + dumpImageSeries(layerData); + } else { + LOG(INFO) << "Unable to dump this type of layer to png."; + } +} + +void PNGDumper::dumpImageSeries(const LayerData &layer) +{ + const auto& shape = layer.shape(); + const auto imagePixels = shape[2] * shape[3]; + + // Buffer for storing the current image data. + vector buffer(imagePixels); + + auto data = layer.data(); + + for (int i = 0; i < shape[0]; ++i) { + for (int j = 0; j < shape[1]; ++j) { + memcpy(buffer.data(), data, imagePixels * sizeof(DType)); + + // advance the buffer; + data += imagePixels; + + clamp(buffer.begin(), buffer.end(), 0.0, 255.0); + + png::image image(shape[2], shape[3]); + + for (int y = 0; y < shape[2]; ++y) { + for (int x = 0; x < shape[3]; ++x) { + image[x][y] = png::gray_pixel((int) buffer[x + y * shape[3]]); + } + } + + image.write(getFilename(layer.name(), i, j)); + } + } +} + +string PNGDumper::getFilename(const string &layerName, int i, int j) +{ + stringstream nameBuilder; + + nameBuilder << baseDir_ + << "/" << layerName + << "-" << i + << "-" << j << ".png"; + + return nameBuilder.str(); +} diff --git a/src/PNGDumper.hpp b/src/PNGDumper.hpp new file mode 100644 index 0000000..9d4fa7f --- /dev/null +++ b/src/PNGDumper.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "LayerData.hpp" +#include "utils.hpp" + +namespace fmri +{ + using std::string; + using std::string_view; + + class PNGDumper + { + public: + PNGDumper(string_view baseDir); + + void dump(const LayerData& layerData); + + private: + const string baseDir_; + + void dumpImageSeries(const LayerData &data); + + string getFilename(const string &basic_string, int i, int j); + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index da385bd..9667390 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,9 @@ #include #include +#include #include "Options.hpp" #include "Simulator.hpp" -#include "utils.hpp" +#include "PNGDumper.hpp" using namespace std; using namespace fmri; @@ -12,10 +13,15 @@ int main(int argc, char *const argv[]) { Options options = Options::parse(argc, argv); vector labels; - if (options.labels() != "") { + if (!options.labels().empty()) { labels = read_vector(options.labels()); } + unique_ptr pngDumper; + if (!options.imageDump().empty()) { + pngDumper.reset(new PNGDumper(options.imageDump())); + } + Simulator simulator(options.model(), options.weights(), options.means()); for (const auto &image : options.inputs()) { @@ -33,6 +39,10 @@ int main(int argc, char *const argv[]) { } else { LOG(INFO) << "Best result: " << *(resultRow.data(), resultRow.data() + resultRow.numEntries()) << endl; } + + for (auto& layer : res) { + pngDumper->dump(layer); + } } google::ShutdownGoogleLogging(); diff --git a/src/utils.hpp b/src/utils.hpp index 37b546b..97549c5 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -86,4 +86,18 @@ namespace fmri return res; } + template + void clamp(It begin, It end, + typename std::iterator_traits::value_type minimum, + typename std::iterator_traits::value_type maximum) + { + const auto extremes = std::minmax(begin, end); + const auto diff = *extremes.second - *extremes.first; + + const auto offset = minimum - *extremes.first; + const auto scaling = diff / (maximum - minimum); + + std::for_each(begin, end, [offset, scaling] (typename std::iterator_traits::reference v) { v = (v + offset) * scaling;}); + } + }