From d550bc7f8370593d38452df1510b57b25ecf059d Mon Sep 17 00:00:00 2001 From: Jerome Date: Sat, 6 May 2023 11:51:51 +0200 Subject: [PATCH] Added actual image processing and logger. --- cpp/include/Slicer360ToPerspective.hpp | 5 ++ cpp/include/logger.hpp | 31 +++++++++++ cpp/include/progress_timer.hpp | 29 ++++++++++ cpp/src/Slicer360ToPerspective.cpp | 9 +++- cpp/src/main.cpp | 75 ++++++++++++++++++++++---- cpp/src/progress_timer.cpp | 32 +++++++++++ 6 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 cpp/include/logger.hpp create mode 100644 cpp/include/progress_timer.hpp create mode 100644 cpp/src/progress_timer.cpp diff --git a/cpp/include/Slicer360ToPerspective.hpp b/cpp/include/Slicer360ToPerspective.hpp index 066eae5..13d4cf8 100644 --- a/cpp/include/Slicer360ToPerspective.hpp +++ b/cpp/include/Slicer360ToPerspective.hpp @@ -15,6 +15,11 @@ class Slicer360ToPerspective { /// @param panorama_image The 360-degree image to be used as an input. Slicer360ToPerspective(Image const& panorama_image_ = Image(1,1,3)); + /// @brief Sets the 360-degree image to be used as an input. + /// @param panorama_image_ 360-degree image to be used as an input. + /// @return A reference to the current object. + Slicer360ToPerspective & SetPanoramaImage(Image const& panorama_image_); + /// @brief Sets the width of the output images. /// @param width Width of the output images in pixels. /// @param height Height of the output images in pixels. By default = -1. If < 0, the height is set to the width to yield a square image. diff --git a/cpp/include/logger.hpp b/cpp/include/logger.hpp new file mode 100644 index 0000000..4c83941 --- /dev/null +++ b/cpp/include/logger.hpp @@ -0,0 +1,31 @@ +#ifndef H_Logger +#define H_Logger + +#include +#include +#include +#include + +/// @brief Implementation of the Logger class to print messages to the console. It is defined as a template class to save the .cpp file. +template +class Logger_impl { + public: + // static constexpr char date_format[] = "%FT%T%z";// Works on Linux but not on Windows... + static constexpr char date_format[] = "%Y-%m-%dT%H:%M:%S"; + static void log(const std::string& message) { + auto now = std::chrono::system_clock::now(); + std::time_t now_time = std::chrono::system_clock::to_time_t(now); + std::cout << std::put_time(std::localtime(&now_time), date_format) << " " << message << std::endl; + } + + static void error(const std::string& message) { + auto now = std::chrono::system_clock::now(); + std::time_t now_time = std::chrono::system_clock::to_time_t(now); + std::cerr << std::put_time(std::localtime(&now_time), date_format) << " ERROR: " << message << std::endl; + } +}; + +/// @brief Logger class to print messages to the console. +using Logger = Logger_impl<1>; + +#endif diff --git a/cpp/include/progress_timer.hpp b/cpp/include/progress_timer.hpp new file mode 100644 index 0000000..da9b75f --- /dev/null +++ b/cpp/include/progress_timer.hpp @@ -0,0 +1,29 @@ +#ifndef H_ProgressTimer +#define H_ProgressTimer + +#include +#include + +/// @brief This class implements a progress timer that predicts how long a loop will take to complete. +class ProgressTimer { + public: + /// @brief Creates a new progress timer. + /// @param num_iterations Number of iterations to perform. + ProgressTimer(size_t num_iterations); + + /// @brief Increments the iteration counter and prints the progress. + /// @return A string with the current progress. + std::string increment(); + + /// @brief Converts a number of seconds to a string with hours, minutes and seconds. + /// @param seconds Total number of seconds (input). + /// @return "hh:mm:ss.uuuuuu" + static std::string seconds_to_hms_str(double seconds); + + private: + std::chrono::steady_clock::time_point m_start; + size_t m_num_iterations; + size_t m_current_iteration; +}; + +#endif diff --git a/cpp/src/Slicer360ToPerspective.cpp b/cpp/src/Slicer360ToPerspective.cpp index 7f94749..11724f7 100644 --- a/cpp/src/Slicer360ToPerspective.cpp +++ b/cpp/src/Slicer360ToPerspective.cpp @@ -6,7 +6,14 @@ #include Slicer360ToPerspective::Slicer360ToPerspective(Image const& panorama_image_) : panorama_image(panorama_image_), output_image_width(2), interpolation_method(Image::InterpMethod::BILINEAR), output_folder(".") -{} +{ + SetOutputImageSize(0); +} + +Slicer360ToPerspective & Slicer360ToPerspective::SetPanoramaImage(Image const& panorama_image_) { + panorama_image = panorama_image_; + return *this; +} Slicer360ToPerspective & Slicer360ToPerspective::SetOutputImageSize(int width, int height) { output_image_width = (width > 0) ? width : 1024; diff --git a/cpp/src/main.cpp b/cpp/src/main.cpp index bdd4953..4a46c37 100644 --- a/cpp/src/main.cpp +++ b/cpp/src/main.cpp @@ -1,6 +1,13 @@ #include #include #include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; std::string convert_interpolation_mode_to_str(Image::InterpMethod interpolation_mode) { switch(interpolation_mode) { @@ -13,9 +20,20 @@ std::string convert_interpolation_mode_to_str(Image::InterpMethod interpolation_ } } +void scan_folder_for_files(const fs::path& folder_path, std::vector& files, bool recursive = true) +{ + for(const auto& entry : fs::directory_iterator(folder_path)) + { + if(fs::is_directory(entry) && recursive) + scan_folder_for_files(entry, files, recursive); + if(fs::is_regular_file(entry)) + files.push_back(entry.path()); + } +} + struct ProgramConfiguration { std::string cameras_filename; - std::string images_folder; + std::string images_folder = ""; std::string output_folder = "."; std::string output_format = "jpg"; Image::InterpMethod interpolation_mode = Image::InterpMethod::BILINEAR; @@ -44,8 +62,13 @@ bool decode_arguments(cmd_line_parser::parser const& parser, ProgramConfiguratio if(parser.parsed("cameras")) program_config.cameras_filename = parser.get("cameras"); - if(parser.parsed("images")) + if(parser.parsed("images")) { program_config.images_folder = parser.get("images"); + if(!fs::exists(program_config.images_folder)) { + std::cerr << "Error: images folder \"" << program_config.images_folder << "\" does not exist." << std::endl; + return false; + } + } if(parser.parsed("output_folder")) program_config.output_folder = parser.get("output_folder"); @@ -104,6 +127,7 @@ void configure_parser(cmd_line_parser::parser & parser) { } int main(int argc, char** argv) { + // std::locale::global(std::locale("en_US.utf8"));// DEBUG std::cout.precision(16); // Default configuration @@ -128,13 +152,13 @@ int main(int argc, char** argv) { // Load the cameras std::vector cameras; { - std::cout << "Loading cameras from \"" << program_config.cameras_filename << "\"..." << std::endl; + Logger::log("Loading cameras from \"" + program_config.cameras_filename + "\"..."); Slicer360ToPerspective slicer; // create an empty slicer slicer.LoadCamerasFromFile(program_config.cameras_filename); // load the cameras from the file into the slicer cameras = slicer.cameras; // copy the cameras that were loaded - std::cout << "Loaded " << cameras.size() << " cameras." << std::endl; + Logger::log("Loaded " + std::to_string(cameras.size()) + " cameras."); if(!cameras.size()) { // If no cameras were loaded, abort - std::cerr << "Aborting now." << std::endl; + Logger::error("No cameras have been found, aborting now."); return 1; } } @@ -153,13 +177,46 @@ int main(int argc, char** argv) { // Generate the coverage image if requested if(program_config.coverage) { - std::cout << "Generating coverage image..." << std::endl; + Logger::log("Generating coverage image..."); Image coverage_image = slicer.ComputeCoverage(program_config.size, true); - std::cout << "Coverage image generated." << std::endl; + Logger::log("Coverage image generated."); auto [min_cov, max_cov, avg_cov] = coverage_image.ComputeMinMaxAvg(); - std::cout << "Coverage statistics (per pixel) :\n\tmin = " << int(min_cov) << "\n\tmax = " << int(max_cov) << "\n\tavg = " << avg_cov << std::endl; - std::cout << "Exporting coverage image..." << std::endl; + Logger::log("Coverage statistics (per pixel) :\tmin = " + std::to_string(int(min_cov)) + "\tmax = " + std::to_string(int(max_cov)) + "\tavg = " + std::to_string(avg_cov)); + Logger::log("Exporting coverage image..."); coverage_image.Normalized().Colorized(Image::Colormap::PARULA); coverage_image.Save(program_config.output_folder + "/coverage." + program_config.output_format); } + + // Generate the perspective images + if(program_config.images_folder != "") { + Logger::log("Generating perspective images..."); + std::vector image_files; + scan_folder_for_files(program_config.images_folder, image_files); + + ProgressTimer progress_timer(image_files.size()); + + for(fs::path const& image_file : image_files) { + std::string image_path = image_file.string(); + Logger::log("Processing \"" + image_path + "\"..."); + Image input_image(image_path); + if(program_config.normalize) { + Logger::log("\tNormalizing image..."); + input_image = input_image.HistogramNormalized(); + } + slicer.SetPanoramaImage(input_image); + Logger::log("\tProjecting " + image_file.string() + " using " + std::to_string(slicer.cameras.size()) + " cameras..."); + std::vector projected_images = slicer.ProjectToAllCameras(); + #pragma omp parallel for + for(unsigned int i = 0; i < projected_images.size(); ++i) { + #pragma omp critical + {Logger::log("\t\tSaving perspective image " + std::to_string(i) + "...");} + std::string output_image_path = program_config.output_folder + "/" + image_file.stem().string() + "_" + std::to_string(i) + "." + program_config.output_format; + projected_images[i].Save(output_image_path); + } + Logger::log(progress_timer.increment()); + } + Logger::log(std::to_string(image_files.size()) + " perspective images generated."); + } + + return 0; } diff --git a/cpp/src/progress_timer.cpp b/cpp/src/progress_timer.cpp new file mode 100644 index 0000000..65f459f --- /dev/null +++ b/cpp/src/progress_timer.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +ProgressTimer::ProgressTimer(size_t num_iterations) + : m_start(std::chrono::steady_clock::now()), m_num_iterations(num_iterations), m_current_iteration(0) +{ +} + +std::string ProgressTimer::increment() +{ + ++m_current_iteration; + auto current_time = std::chrono::steady_clock::now(); + auto elapsed_time = std::chrono::duration_cast(current_time - m_start).count(); + double progress_percent = static_cast(m_current_iteration) / m_num_iterations * 100; + double elapsed_seconds = elapsed_time / 1000.0; + double remaining_seconds = (m_num_iterations - m_current_iteration) * (elapsed_seconds / m_current_iteration); + return "Progress: " + std::to_string(progress_percent) + " %, Elapsed time: " + seconds_to_hms_str(elapsed_seconds) + ", Remaining time: " + seconds_to_hms_str(remaining_seconds); +} + +std::string ProgressTimer::seconds_to_hms_str(double seconds) { + unsigned int hours = seconds / 3600; + unsigned int minutes = std::fmod(seconds, 3600.0) / 60; + unsigned int remaining_seconds = std::fmod(seconds, 60.0); + + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << hours << ":" + << std::setfill('0') << std::setw(2) << minutes << ":" + << std::setfill('0') << std::setw(2) << remaining_seconds << "." + << std::setfill('0') << std::setw(6) << int(std::fmod(seconds, 1) * 1000000); + return oss.str(); +}