From 0963bfb4f9c77fdb62aa6f7b1677c8520ae4404c Mon Sep 17 00:00:00 2001 From: John Aoga Date: Thu, 18 Jun 2026 03:00:15 +0200 Subject: [PATCH 1/2] feat: add dependency-free leveled logging facility Replace ad-hoc std::cout/std::cerr/printf output across the engine with a small, dependency-free leveled logger (include/ncorr/log.h, src/log.cpp). - Five severity levels (TRACE/DEBUG/INFO/WARN/ERROR) plus OFF, with independent console and log-file thresholds. - Thread-safe (mutex), safe under the OpenMP parallel DIC regions. - Stream-style macros (NLOG_INFO << ...) that short-circuit message construction when the level is disabled, keeping the per-frame / per-iteration hot loops cheap. - Configurable via environment variables (NCORR_LOG_LEVEL, NCORR_LOG_FILE, NCORR_LOG_CONSOLE) since the library has no CLI; the existing `debug` flag lowers the console threshold to DEBUG. Migrated all ~164 print sites in ncorr.cpp, Image2D.cpp, Array2D.h and ROI2D.h to the appropriate level (errors->ERROR, warnings->WARN, progress->INFO, diagnostics->DEBUG). Documented in README. Co-Authored-By: Claude Opus 4.8 --- CMakeLists.txt | 4 +- README.md | 25 ++++ include/Array2D.h | 8 +- include/ROI2D.h | 3 +- include/ncorr/log.h | 164 +++++++++++++++++++++ src/Image2D.cpp | 31 ++-- src/log.cpp | 235 ++++++++++++++++++++++++++++++ src/ncorr.cpp | 343 ++++++++++++++++++++++---------------------- 8 files changed, 620 insertions(+), 193 deletions(-) create mode 100644 include/ncorr/log.h create mode 100644 src/log.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 979eb6d..3d5541f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,8 @@ option(FORCE_FETCH_DEPENDENCIES "Force using FetchContent for all dependencies" # included, since the install directories are searched automatically by g++. # Set files -SET(ncorr_src src/ncorr.cpp src/Strain2D.cpp src/Disp2D.cpp src/Data2D.cpp src/ROI2D.cpp src/Image2D.cpp src/Array2D.cpp src/session.cpp src/config.cpp) -SET(ncorr_h include/ncorr.h include/Strain2D.h include/Disp2D.h include/Data2D.h include/ROI2D.h include/Image2D.h include/Array2D.h include/ncorr/session.h include/ncorr/config.h include/ncorr/ini.h) +SET(ncorr_src src/ncorr.cpp src/Strain2D.cpp src/Disp2D.cpp src/Data2D.cpp src/ROI2D.cpp src/Image2D.cpp src/Array2D.cpp src/session.cpp src/config.cpp src/log.cpp) +SET(ncorr_h include/ncorr.h include/Strain2D.h include/Disp2D.h include/Data2D.h include/ROI2D.h include/Image2D.h include/Array2D.h include/ncorr/session.h include/ncorr/config.h include/ncorr/ini.h include/ncorr/log.h) # Set include directory INCLUDE_DIRECTORIES(include) diff --git a/README.md b/README.md index cf09614..dc4a1d2 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,31 @@ cd examples/ohtcfrp Check the video directory in the `examples/ohtcfrp` directory for the result video. +## Logging +CppNCorr uses a small, dependency-free leveled logger (`include/ncorr/log.h`) +instead of ad-hoc `std::cout`/`std::cerr`. Messages carry a severity — **TRACE, +DEBUG, INFO, WARN, ERROR** — and go to the console (INFO/DEBUG/TRACE to `stdout`, +WARN/ERROR to `stderr`) and, optionally, a full-detail log file. The stream-style +macros (`NLOG_INFO << ...`) short-circuit message construction when the level is +disabled, so they are cheap inside the per-frame / per-iteration DIC loops. + +Because the library has no command line of its own, configuration is driven by +environment variables (applied lazily on first use) and by the engine's existing +`debug` flag: + +```bash +export NCORR_LOG_LEVEL=info # trace|debug|info|warn|error|off (console threshold) +export NCORR_LOG_FILE=ncorr.log # also write a full debug-level log here (empty = none) +export NCORR_LOG_CONSOLE=0 # disable console output entirely +``` + +Setting `debug = true` in the DIC analysis input lowers the console threshold to +DEBUG so the engine's debug diagnostics appear. A parent application can also +configure the logger programmatically via `ncorr::log::set_level()`, +`ncorr::log::set_file()`, and `ncorr::log::Logger::instance()`. When consumed by +CPPxDIC, that project propagates its own logging settings here via the +`NCORR_LOG_*` environment variables so engine logs share the same verbosity and +destination. diff --git a/include/Array2D.h b/include/Array2D.h index c65158d..fd17349 100644 --- a/include/Array2D.h +++ b/include/Array2D.h @@ -19,6 +19,8 @@ #include #include +#include "ncorr/log.h" + // Non standard libraries extern "C" { // Blas - for matrix multiplication void dgemm_(char*, char*, int*, int*, int*, double*, double*, int*, double*, int*, double*, double*, int*); @@ -2613,7 +2615,7 @@ namespace details { pointer allocate(difference_type s, const void * = 0) { pointer ptr = static_cast(malloc_function(s * sizeof(value_type))); if (!ptr) { - std::cerr << "Failed to allocate memory using allocate in fftw_allocator." << std::endl; + NLOG_ERROR << "Failed to allocate memory using allocate in fftw_allocator."; throw std::bad_alloc(); } @@ -3003,9 +3005,7 @@ typename Array2D::pointer Array2D::allocate(difference_typ ptr_new = alloc.allocate(s_init); } catch (std::bad_alloc &e) { // Bad alloc doesn't have a what() message, so just add one to terminal - std::cerr << "---------------------------------------------" << std::endl - << "Error: failed to allocate memory for Array2D." << std::endl - << "---------------------------------------------" << std::endl; + NLOG_ERROR << "Error: failed to allocate memory for Array2D."; throw; } diff --git a/include/ROI2D.h b/include/ROI2D.h index 839e215..822d2c6 100644 --- a/include/ROI2D.h +++ b/include/ROI2D.h @@ -11,6 +11,7 @@ #include #include "Array2D.h" +#include "ncorr/log.h" namespace ncorr { @@ -442,7 +443,7 @@ T_container& fill(T_container &A, const Array2D&boundary, const T &val) // Check for empty boundary first - this is not an error, just nothing to fill if (boundary.empty() || boundary.height() == 0) { // If boundary is empty just return - std::cout << "Warning: The boundary is empty or the height is equal 0 - just nothing to fill" << std::endl; + NLOG_WARN << "Warning: The boundary is empty or the height is equal 0 - just nothing to fill"; return A; } diff --git a/include/ncorr/log.h b/include/ncorr/log.h new file mode 100644 index 0000000..0097f8f --- /dev/null +++ b/include/ncorr/log.h @@ -0,0 +1,164 @@ +#pragma once +/** + * @file log.h + * @brief Lightweight, dependency-free logging facility for CppNCorr. + * + * CppNCorr is a static library consumed by other projects (e.g. CPPxDIC), so + * the logger is intentionally tiny and brings in no third-party dependency. It + * provides: + * - Five severity levels (Trace, Debug, Info, Warn, Error) plus Off. + * - Independent thresholds for the console and an optional log file, so a file + * sink can capture full Debug detail while the console stays quiet. + * - Thread-safe emission (an internal mutex), safe to call from OpenMP + * parallel regions used by the DIC engine. + * - Stream-style macros (NLOG_INFO << ...) that short-circuit message + * construction when the level is disabled — important for the per-frame / + * per-iteration hot loops in ncorr.cpp. + * + * Because the library has no command line of its own, configuration is driven by + * environment variables (applied lazily on first use) and by the engine's + * existing @c debug flag: + * - @c NCORR_LOG_LEVEL trace|debug|info|warn|error|off (console threshold) + * - @c NCORR_LOG_FILE path to a log file (full Debug detail is written) + * - @c NCORR_LOG_CONSOLE 0|1|true|false (disable/enable console output) + * + * A parent application can also configure the logger programmatically via the + * helpers below before invoking the engine. + */ + +#include +#include +#include +#include +#include + +namespace ncorr { +namespace log { + +/// Severity levels, ordered from most to least verbose. +enum class Level { Trace = 0, Debug = 1, Info = 2, Warn = 3, Error = 4, Off = 5 }; + +/// Parse a level name (case-insensitive): trace, debug, info, warn|warning, +/// error, off. Returns @p fallback if @p s is empty or unrecognized. +Level level_from_string(const std::string& s, Level fallback = Level::Info); + +/// Short, fixed-width display name for a level (e.g. "INFO "). +const char* level_name(Level l); + +/** + * @brief Process-wide singleton logger. All members are thread-safe. + */ +class Logger { +public: + static Logger& instance(); + + void set_console_level(Level l); + void set_file_level(Level l); + Level console_level() const; + Level file_level() const; + + /// Open (or replace) the file sink. An empty path closes the file sink. + /// @return false if @p path could not be opened (file sink left closed). + bool set_file(const std::string& path); + + /// Enable or disable console output entirely. + void set_console_enabled(bool on); + + /// Include timestamp + source location in console output too (the file sink + /// always includes them). Off by default to keep the console readable. + void set_verbose_format(bool on); + + /// Apply NCORR_LOG_LEVEL / NCORR_LOG_FILE / NCORR_LOG_CONSOLE. Runs once + /// automatically on first use; calling again re-applies them. + void configure_from_env(); + + /// True if a message at @p l would reach any sink. Use to guard expensive + /// message construction in hot loops. + bool enabled(Level l); + + /// Emit a fully-formed message (trailing newlines are trimmed) at @p l. + void write(Level l, const char* file, int line, const std::string& msg); + +private: + Logger(); + void ensure_env(); + + mutable std::mutex mtx_; + Level console_level_ = Level::Info; + Level file_level_ = Level::Debug; + bool console_enabled_ = true; + bool verbose_format_ = false; + bool color_ = false; + bool env_done_ = false; + std::ofstream file_; +}; + +/// Set the console threshold (convenience wrapper). +void set_level(Level l); + +/// Lower the console threshold to Debug when @p on is true (used to honour the +/// engine's existing @c debug flag). A no-op when @p on is false. +void set_debug(bool on); + +/// Convenience: open a log file sink (full Debug detail). +bool set_file(const std::string& path); + +/// True if a message at @p l would be emitted by any sink. +inline bool enabled(Level l) { return Logger::instance().enabled(l); } + +/** + * @brief RAII stream builder; flushes its accumulated text to the logger when it + * is destroyed at the end of the full expression. + */ +class Stream { +public: + Stream(Level level, const char* file, int line) + : level_(level), file_(file), line_(line) {} + ~Stream() { Logger::instance().write(level_, file_, line_, oss_.str()); } + + Stream(const Stream&) = delete; + Stream& operator=(const Stream&) = delete; + + template + Stream& operator<<(const T& v) { + oss_ << v; + return *this; + } + /// Support stream manipulators such as std::endl / std::setprecision. + Stream& operator<<(std::ostream& (*manip)(std::ostream&)) { + oss_ << manip; + return *this; + } + +private: + Level level_; + const char* file_; + int line_; + std::ostringstream oss_; +}; + +/// Helper that turns the conditional logging expression back into a void +/// statement (glog idiom). @c operator& has lower precedence than @c << but +/// higher than @c ?:, so the macro is safe inside an unbraced if/else. +class Voidify { +public: + Voidify() = default; + void operator&(Stream&) {} +}; + +} // namespace log +} // namespace ncorr + +// Stream-style logging macros. When the level is disabled, the right-hand side +// (message construction) is never evaluated. +#define NLOG_AT(lvl) \ + !::ncorr::log::enabled(lvl) \ + ? (void)0 \ + : ::ncorr::log::Voidify() & \ + ::ncorr::log::Stream((lvl), __FILE__, __LINE__) + +#define NLOG_TRACE NLOG_AT(::ncorr::log::Level::Trace) +#define NLOG_DEBUG NLOG_AT(::ncorr::log::Level::Debug) +#define NLOG_INFO NLOG_AT(::ncorr::log::Level::Info) +#define NLOG_WARN NLOG_AT(::ncorr::log::Level::Warn) +#define NLOG_ERROR NLOG_AT(::ncorr::log::Level::Error) diff --git a/src/Image2D.cpp b/src/Image2D.cpp index 5faef33..481da6f 100644 --- a/src/Image2D.cpp +++ b/src/Image2D.cpp @@ -7,6 +7,7 @@ */ #include "Image2D.h" +#include "ncorr/log.h" #include #include #include @@ -364,7 +365,7 @@ ImageProcessor::filter_like_ben(const std::vector& input, // Compute boundaries from FIRST FILTERED image using percentiles (5th, 95th) if not provided if (gs_boundaries == nullptr) { boundaries = compute_percentile_boundaries(filtered_images[0], mask, 5.0, 95.0); - std::cout << " Computed filter boundaries: [" << boundaries.first << ", " << boundaries.second << "]" << std::endl; + NLOG_INFO << " Computed filter boundaries: [" << boundaries.first << ", " << boundaries.second << "]"; } else { boundaries = *gs_boundaries; } @@ -431,7 +432,7 @@ std::pair ImageProcessor::compute_percentile_boundaries(const cv } if (values.empty()) { - std::cerr << "Warning: No pixels in mask for percentile computation" << std::endl; + NLOG_WARN << "Warning: No pixels in mask for percentile computation"; return {0.0, 255.0}; } @@ -570,7 +571,7 @@ std::vector VideoImporter::import_video( cv::VideoCapture cap(video_path); if (!cap.isOpened()) { - std::cerr << "Failed to open video file: " << video_path << std::endl; + NLOG_ERROR << "Failed to open video file: " << video_path; return images; } @@ -579,9 +580,9 @@ std::vector VideoImporter::import_video( int frame_end = (params.frame_end < 0) ? total_frames : std::min(params.frame_end, total_frames); int frame_jump = std::max(1, params.frame_jump); - std::cout << "Importing video: " << video_path << std::endl; - std::cout << " Total frames: " << total_frames << std::endl; - std::cout << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")" << std::endl; + NLOG_INFO << "Importing video: " << video_path; + NLOG_INFO << " Total frames: " << total_frames; + NLOG_INFO << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")"; int imported_count = 0; for (int f = frame_start; f <= frame_end; f += frame_jump) { @@ -589,7 +590,7 @@ std::vector VideoImporter::import_video( cv::Mat frame; if (!cap.read(frame)) { - std::cerr << " Warning: Failed to read frame " << f << std::endl; + NLOG_WARN << " Warning: Failed to read frame " << f; break; } @@ -610,7 +611,7 @@ std::vector VideoImporter::import_video( } cap.release(); - std::cout << " Imported " << imported_count << " frames" << std::endl; + NLOG_INFO << " Imported " << imported_count << " frames"; return images; } @@ -629,7 +630,7 @@ std::vector VideoImporter::import_video_to_files( cv::VideoCapture cap(video_path); if (!cap.isOpened()) { - std::cerr << "Failed to open video file: " << video_path << std::endl; + NLOG_ERROR << "Failed to open video file: " << video_path; return images; } @@ -638,10 +639,10 @@ std::vector VideoImporter::import_video_to_files( int frame_end = (params.frame_end < 0) ? total_frames : std::min(params.frame_end, total_frames); int frame_jump = std::max(1, params.frame_jump); - std::cout << "Importing video to files: " << video_path << std::endl; - std::cout << " Output directory: " << output_dir << std::endl; - std::cout << " Total frames: " << total_frames << std::endl; - std::cout << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")" << std::endl; + NLOG_INFO << "Importing video to files: " << video_path; + NLOG_INFO << " Output directory: " << output_dir; + NLOG_INFO << " Total frames: " << total_frames; + NLOG_INFO << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")"; int imported_count = 0; for (int f = frame_start; f <= frame_end; f += frame_jump) { @@ -649,7 +650,7 @@ std::vector VideoImporter::import_video_to_files( cv::Mat frame; if (!cap.read(frame)) { - std::cerr << " Warning: Failed to read frame " << f << std::endl; + NLOG_WARN << " Warning: Failed to read frame " << f; break; } @@ -674,7 +675,7 @@ std::vector VideoImporter::import_video_to_files( } cap.release(); - std::cout << " Imported and saved " << imported_count << " frames" << std::endl; + NLOG_INFO << " Imported and saved " << imported_count << " frames"; return images; } diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..8f4bda6 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,235 @@ +/** + * @file log.cpp + * @brief Implementation of the CppNCorr logging facility (see ncorr/log.h). + */ + +#include "ncorr/log.h" + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#define NCORR_ISATTY(fd) _isatty(fd) +#define NCORR_FILENO(f) _fileno(f) +#else +#include +#define NCORR_ISATTY(fd) ::isatty(fd) +#define NCORR_FILENO(f) ::fileno(f) +#endif + +namespace ncorr { +namespace log { + +namespace { + +/// Lowercase a copy of @p s (ASCII only). +std::string to_lower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return s; +} + +/// Strip the directory portion of a path so log lines show just the file name. +const char* base_name(const char* path) { + if (!path) return ""; + const char* base = path; + for (const char* p = path; *p; ++p) { + if (*p == '/' || *p == '\\') base = p + 1; + } + return base; +} + +/// ANSI colour escape for a level (empty when colour is disabled by the caller). +const char* color_for(Level l) { + switch (l) { + case Level::Trace: return "\033[37m"; // grey + case Level::Debug: return "\033[36m"; // cyan + case Level::Info: return "\033[32m"; // green + case Level::Warn: return "\033[33m"; // yellow + case Level::Error: return "\033[31m"; // red + default: return ""; + } +} +const char* color_reset() { return "\033[0m"; } + +/// Format "YYYY-MM-DD HH:MM:SS.mmm" for the current wall-clock time. +std::string timestamp() { + using namespace std::chrono; + const auto now = system_clock::now(); + const auto t = system_clock::to_time_t(now); + const auto ms = + duration_cast(now.time_since_epoch()).count() % 1000; + std::tm tm_buf{}; +#if defined(_WIN32) + localtime_s(&tm_buf, &t); +#else + localtime_r(&t, &tm_buf); +#endif + char buf[32]; + std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_buf); + char out[40]; + std::snprintf(out, sizeof(out), "%s.%03d", buf, static_cast(ms)); + return out; +} + +/// Remove trailing CR/LF/space so callers can keep stray "<< std::endl". +std::string rtrim(const std::string& s) { + std::size_t end = s.size(); + while (end > 0) { + const char c = s[end - 1]; + if (c == '\n' || c == '\r' || c == ' ' || c == '\t') + --end; + else + break; + } + return s.substr(0, end); +} + +} // namespace + +Level level_from_string(const std::string& s, Level fallback) { + const std::string v = to_lower(s); + if (v == "trace") return Level::Trace; + if (v == "debug") return Level::Debug; + if (v == "info") return Level::Info; + if (v == "warn" || v == "warning") return Level::Warn; + if (v == "error" || v == "err") return Level::Error; + if (v == "off" || v == "none" || v == "silent") return Level::Off; + return fallback; +} + +const char* level_name(Level l) { + switch (l) { + case Level::Trace: return "TRACE"; + case Level::Debug: return "DEBUG"; + case Level::Info: return "INFO "; + case Level::Warn: return "WARN "; + case Level::Error: return "ERROR"; + case Level::Off: return "OFF "; + } + return "?????"; +} + +Logger& Logger::instance() { + static Logger logger; + return logger; +} + +Logger::Logger() { + color_ = NCORR_ISATTY(NCORR_FILENO(stderr)) != 0; +} + +void Logger::ensure_env() { + if (!env_done_) { + env_done_ = true; + configure_from_env(); + } +} + +void Logger::configure_from_env() { + if (const char* lvl = std::getenv("NCORR_LOG_LEVEL")) { + console_level_ = level_from_string(lvl, console_level_); + } + if (const char* con = std::getenv("NCORR_LOG_CONSOLE")) { + const std::string v = to_lower(con); + console_enabled_ = !(v == "0" || v == "false" || v == "off" || v == "no"); + } + if (const char* path = std::getenv("NCORR_LOG_FILE")) { + if (path[0] != '\0') { + file_.open(path, std::ios::out | std::ios::app); + } + } +} + +void Logger::set_console_level(Level l) { + std::lock_guard lk(mtx_); + console_level_ = l; +} + +void Logger::set_file_level(Level l) { + std::lock_guard lk(mtx_); + file_level_ = l; +} + +Level Logger::console_level() const { + std::lock_guard lk(mtx_); + return console_level_; +} + +Level Logger::file_level() const { + std::lock_guard lk(mtx_); + return file_level_; +} + +bool Logger::set_file(const std::string& path) { + std::lock_guard lk(mtx_); + if (file_.is_open()) file_.close(); + if (path.empty()) return true; + file_.open(path, std::ios::out | std::ios::app); + return file_.is_open(); +} + +void Logger::set_console_enabled(bool on) { + std::lock_guard lk(mtx_); + console_enabled_ = on; +} + +void Logger::set_verbose_format(bool on) { + std::lock_guard lk(mtx_); + verbose_format_ = on; +} + +bool Logger::enabled(Level l) { + std::lock_guard lk(mtx_); + ensure_env(); + if (l == Level::Off) return false; + const bool to_console = console_enabled_ && l >= console_level_; + const bool to_file = file_.is_open() && l >= file_level_; + return to_console || to_file; +} + +void Logger::write(Level l, const char* file, int line, const std::string& msg) { + std::lock_guard lk(mtx_); + ensure_env(); + if (l == Level::Off) return; + + const std::string text = rtrim(msg); + const char* fname = base_name(file); + + if (console_enabled_ && l >= console_level_) { + std::ostream& os = (l >= Level::Warn) ? std::cerr : std::cout; + if (color_) os << color_for(l); + os << "[" << level_name(l) << "]"; + if (color_) os << color_reset(); + if (verbose_format_) { + os << " " << timestamp() << " " << fname << ":" << line; + } + os << " " << text << "\n"; + } + + if (file_.is_open() && l >= file_level_) { + file_ << timestamp() << " [" << level_name(l) << "] " << fname << ":" + << line << " " << text << "\n"; + file_.flush(); + } +} + +void set_level(Level l) { Logger::instance().set_console_level(l); } + +void set_debug(bool on) { + if (on && Logger::instance().console_level() > Level::Debug) { + Logger::instance().set_console_level(Level::Debug); + } +} + +bool set_file(const std::string& path) { + return Logger::instance().set_file(path); +} + +} // namespace log +} // namespace ncorr diff --git a/src/ncorr.cpp b/src/ncorr.cpp index 59db062..11e2076 100644 --- a/src/ncorr.cpp +++ b/src/ncorr.cpp @@ -6,6 +6,7 @@ */ #include "ncorr.h" +#include "ncorr/log.h" #include #include #include @@ -59,7 +60,7 @@ namespace details { const auto &nlinfo_old = disp.get_roi().get_nlinfo(region_idx); if (nlinfo_old.empty()) { // If nlinfo_old is empty then an initial guess cannot be found. - std::cerr << "WARNING - d_initial_guess : nlinfo_old of region " << region_idx << " is empty params initial guess cannot be found\n"; + NLOG_WARN << "WARNING - d_initial_guess : nlinfo_old of region " << region_idx << " is empty params initial guess cannot be found"; return false; } @@ -118,7 +119,7 @@ namespace details { if (std::isnan(fo_pair.first(0))) { // Interpolated out of bounds; return as failure. - std::cerr << "WARNING - d_newton : Interpolated out of bounds (first order returns NaN); for old (p1, p2) = " << params(2) << " ," << params(3) << std::endl; + NLOG_WARN << "WARNING - d_newton : Interpolated out of bounds (first order returns NaN); for old (p1, p2) = " << params(2) << " ," << params(3); return false; } @@ -162,7 +163,7 @@ namespace details { } } // Something failed - std::cerr << "WARNING - d_newton : hessian failed; for old (p1, p2) = " << params(2) << " ," << params(3) << std::endl; + NLOG_WARN << "WARNING - d_newton : hessian failed; for old (p1, p2) = " << params(2) << " ," << params(3); return false; } @@ -319,7 +320,7 @@ namespace details { return true; } // Something failed - std::cerr << "WARNING - sr_inital_guess : ref_template_ssd_inv less than eps; for (p1, p2) = " << params(0) << " ," << params(1) << std::endl; + NLOG_WARN << "WARNING - sr_inital_guess : ref_template_ssd_inv less than eps; for (p1, p2) = " << params(0) << " ," << params(1); return false; } @@ -381,7 +382,7 @@ namespace details { // Note that for some forms of interpolation, the entire array // cannot be interpolated (near boundaries - these values // will be NaNs). Possibly update this later. - std::cout << "INFO - sr_iterative_search: near boundaries interpolation yield NaN; for (p1, p2) = " << p1 << " ," << p2 << std::endl; + NLOG_DEBUG << "INFO - sr_iterative_search: near boundaries interpolation yield NaN; for (p1, p2) = " << p1 << " ," << p2; return false; } @@ -495,7 +496,7 @@ namespace details { if (std::isnan(A_cur_template(p1_shifted, p2_shifted))) { // If interpolated out of range, return false. - std::cout << "INFO - sr_newton: near boundaries interpolation yield NaN; for (p1, p2) = " << p1_shifted << " ," << p2_shifted << std::endl; + NLOG_DEBUG << "INFO - sr_newton: near boundaries interpolation yield NaN; for (p1, p2) = " << p1_shifted << " ," << p2_shifted; return false; } @@ -585,7 +586,7 @@ namespace details { } } // Something failed - std::cout << "WARNING - sr_newton: hessian probably failed; for (p1, p2) = " << params(0) << " ," << params(1) << std::endl; + NLOG_WARN << "WARNING - sr_newton: hessian probably failed; for (p1, p2) = " << params(0) << " ," << params(1); return false; } } @@ -1062,9 +1063,9 @@ Data2D update(const Data2D &data, const Disp2D &disp, INTERP interp_type, ROI_UP // Check for empty ROI after update and warn if (roi_new.get_points() == 0) { - std::cerr << "WARNING: ROI update in Data2D::update() produced an empty ROI!" << std::endl; - std::cerr << " Original ROI had " << data.get_roi().get_points() << " points." << std::endl; - std::cerr << " Mode: " << (mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID") << std::endl; + NLOG_WARN << "WARNING: ROI update in Data2D::update() produced an empty ROI!"; + NLOG_WARN << " Original ROI had " << data.get_roi().get_points() << " points."; + NLOG_WARN << " Mode: " << (mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID"); } // Get signed distance array for roi_new - this guides queue such that @@ -1953,10 +1954,11 @@ Disp2D RGDIC(const Array2D &A_ref, ROI2D::difference_type r, ROI2D::difference_type num_threads, double cutoff_corrcoef, - bool debug) { + bool debug) { + ncorr::log::set_debug(debug); typedef ROI2D::difference_type difference_type; - - std::cout << "DEBUG: RGDIC called with num_threads=" << num_threads << std::endl; + + NLOG_DEBUG << "DEBUG: RGDIC called with num_threads=" << num_threads; if (!A_ref.same_size(A_cur)) { throw std::invalid_argument("Attempted to perform RGDIC on reference image input of size: " + A_ref.size_2D_string() + @@ -2130,9 +2132,10 @@ Disp2D RGDIC_without_thread(const Array2D &A_ref, double cutoff_corrcoef, bool debug, const std::vector& seeds_by_region = {}, - bool seeds_are_optimized = false) { + bool seeds_are_optimized = false) { + ncorr::log::set_debug(debug); typedef ROI2D::difference_type difference_type; - + if (!A_ref.same_size(A_cur)) { throw std::invalid_argument("Attempted to perform RGDIC on reference image input of size: " + A_ref.size_2D_string() + " with current image input of size: " + A_cur.size_2D_string() + ". Sizes must be the same."); @@ -2216,15 +2219,15 @@ Disp2D RGDIC_without_thread(const Array2D &A_ref, Array2D seed_params; if (use_provided_seeds) { - std::cout << "WARNING - Using provided seed for region " << region_idx << std::endl; + NLOG_WARN << "WARNING - Using provided seed for region " << region_idx; // Use user-provided seed for this region if (region_idx < static_cast(seeds_by_region.size())) { const SeedParams& provided_seed = seeds_by_region[region_idx]; - std::cout << "DEBUG::Provided seed: " << provided_seed.y << " " << provided_seed.x << std::endl; + NLOG_DEBUG << "DEBUG::Provided seed: " << provided_seed.y << " " << provided_seed.x; if (seeds_are_optimized) { // Seeds are already optimized - use directly without optimization - std::cout << "DEBUG::Already Optimized seed: " << provided_seed.y << " " << provided_seed.x << std::endl; + NLOG_DEBUG << "DEBUG::Already Optimized seed: " << provided_seed.y << " " << provided_seed.x; seed_params = provided_seed.to_array(); } else { // Seeds need optimization - create seed array and optimize @@ -2244,18 +2247,18 @@ Disp2D RGDIC_without_thread(const Array2D &A_ref, params_buf(8) = 0.0; // corrcoef (will be computed) params_buf(9) = 0.0; // diff_norm (will be computed) - std::cout << "Params_buf = " << params_buf; + NLOG_DEBUG << "Params_buf = " << params_buf; // Optimize the seed using nloptimizer auto result = sr_nloptimizer(params_buf); if (result.second) { // converged successfully seed_params = result.first; if (debug) { - std::cout << "DEBUG::Optimized seed: " << seed_params(0) << " " << seed_params(1) << std::endl; + NLOG_DEBUG << "DEBUG::Optimized seed: " << seed_params(0) << " " << seed_params(1); } } else { - std::cerr << "Warning - Seed optimization doesn't converge. Use non optimized seed" << std::endl; + NLOG_WARN << "Warning - Seed optimization doesn't converge. Use non optimized seed"; } // If optimization failed, seed_params remains empty } @@ -2415,17 +2418,17 @@ DIC_analysis_input::DIC_analysis_input(const std::vector &imgs, // Apply manual overrides if provided (use -1 as sentinel for "not specified") if (roi_update_mode_override != static_cast(-1)) { this->roi_update_mode = roi_update_mode_override; - std::cout << "Manual override: roi_update_mode = " << (roi_update_mode_override == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID") << std::endl; + NLOG_INFO << "Manual override: roi_update_mode = " << (roi_update_mode_override == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID"); } if (accumulation_mode_override != static_cast(-1)) { this->accumulation_mode = accumulation_mode_override; - std::cout << "Manual override: accumulation_mode = " << (accumulation_mode_override == ACCUMULATION_MODE::ON_THE_FLY ? "ON_THE_FLY" : "POST_PROCESS") << std::endl; + NLOG_INFO << "Manual override: accumulation_mode = " << (accumulation_mode_override == ACCUMULATION_MODE::ON_THE_FLY ? "ON_THE_FLY" : "POST_PROCESS"); } // Set save_disps_steps (enforced if debug is true) this->save_disps_steps = save_disps_steps_override || debug; if (this->save_disps_steps) { - std::cout << "Step displacement data will be saved (save_disps_steps=true)" << std::endl; + NLOG_INFO << "Step displacement data will be saved (save_disps_steps=true)"; } } @@ -2743,23 +2746,23 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { step_disps.resize(DIC_input.imgs.size()-1); step_rois.resize(DIC_input.imgs.size()-1); step_ref_idx.resize(DIC_input.imgs.size()-1); - std::cout << "Using POST_PROCESS (MATLAB-style) accumulation mode." << std::endl; + NLOG_INFO << "Using POST_PROCESS (MATLAB-style) accumulation mode."; } - - // Set ROI for the reference image - this gets updated if reference image + + // Set ROI for the reference image - this gets updated if reference image // gets updated. Then, cycle over images and perform DIC. ROI2D roi_ref = DIC_input.roi; for (difference_type ref_idx = 0, cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { // -------------------------------------------------------------------// // Perform RGDIC -----------------------------------------------------// // -------------------------------------------------------------------// - std::cout << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "." << std::endl; - std::cout << "Reference image: " << DIC_input.imgs[ref_idx] << "." << std::endl; - std::cout << "Current image: " << DIC_input.imgs[cur_idx] << "." << std::endl; - + NLOG_INFO << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "."; + NLOG_INFO << "Reference image: " << DIC_input.imgs[ref_idx] << "."; + NLOG_INFO << "Current image: " << DIC_input.imgs[cur_idx] << "."; + std::chrono::time_point start_rgdic = std::chrono::system_clock::now(); - - auto disps = RGDIC(DIC_input.imgs[ref_idx].get_gs(), + + auto disps = RGDIC(DIC_input.imgs[ref_idx].get_gs(), DIC_input.imgs[cur_idx].get_gs(), roi_ref, DIC_input.scalefactor, @@ -2772,7 +2775,7 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { std::chrono::time_point end_rgdic = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_rgdic = end_rgdic - start_rgdic; - std::cout << "Time: " << elapsed_seconds_rgdic.count() << "." << std::endl; + NLOG_INFO << "Time: " << elapsed_seconds_rgdic.count() << "."; // -------------------------------------------------------------------// // -------------------------------------------------------------------// // -------------------------------------------------------------------// @@ -2791,14 +2794,14 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { auto added_disps = add(std::vector({ DIC_output.disps[ref_idx-1], disps }), DIC_input.interp_type); if (added_disps.get_roi().get_points() == 0) { - std::cerr << "WARNING: add() at frame " << cur_idx << " produced empty ROI!" << std::endl; - std::cerr << " Previous disp[" << ref_idx-1 << "] had " << DIC_output.disps[ref_idx-1].get_roi().get_points() << " points." << std::endl; - std::cerr << " Current disps had " << disps.get_roi().get_points() << " points." << std::endl; - std::cerr << " Using current disps directly instead of combined result." << std::endl; + NLOG_WARN << "WARNING: add() at frame " << cur_idx << " produced empty ROI!"; + NLOG_WARN << " Previous disp[" << ref_idx-1 << "] had " << DIC_output.disps[ref_idx-1].get_roi().get_points() << " points."; + NLOG_WARN << " Current disps had " << disps.get_roi().get_points() << " points."; + NLOG_WARN << " Using current disps directly instead of combined result."; DIC_output.disps[cur_idx-1] = disps; } else if (added_disps.get_roi().get_points() < disps.get_roi().get_points() / 2) { - std::cerr << "WARNING: add() at frame " << cur_idx << " lost significant points!" << std::endl; - std::cerr << " Current disps: " << disps.get_roi().get_points() << ", Combined: " << added_disps.get_roi().get_points() << std::endl; + NLOG_WARN << "WARNING: add() at frame " << cur_idx << " lost significant points!"; + NLOG_WARN << " Current disps: " << disps.get_roi().get_points() << ", Combined: " << added_disps.get_roi().get_points(); DIC_output.disps[cur_idx-1] = added_disps; } else { DIC_output.disps[cur_idx-1] = added_disps; @@ -2814,9 +2817,9 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { Array2D cc_values = disps.get_cc().get_array()(disps.get_cc().get_roi().get_mask()); if (!cc_values.empty()) { double selected_corrcoef = prctile(cc_values, DIC_input.prctile_corrcoef); - std::cout << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "." << std::endl; + NLOG_INFO << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "."; if (selected_corrcoef > DIC_input.update_corrcoef) { - std::cout << "DA::Updating reference image..." << ref_idx << " -> " << cur_idx << std::endl; + NLOG_INFO << "DA::Updating reference image..." << ref_idx << " -> " << cur_idx; auto prev_ref_idx = ref_idx; // Update the reference image index as well as the reference roi ref_idx = cur_idx; @@ -2825,13 +2828,13 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { // Check if updated ROI is empty and warn/handle if (roi_ref.get_points() == 0) { - std::cerr << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!" << std::endl; - std::cerr << " Previous ROI had " << prev_roi.get_points() << " points." << std::endl; - std::cerr << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID") << std::endl; + NLOG_WARN << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!"; + NLOG_WARN << " Previous ROI had " << prev_roi.get_points() << " points."; + NLOG_WARN << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID"); // Revert to previous ROI to prevent crash in subsequent analysis roi_ref = prev_roi; ref_idx = prev_ref_idx; // Keep previous reference - std::cerr << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis." << std::endl; + NLOG_WARN << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis."; } if (DIC_input.debug) { @@ -2842,7 +2845,7 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { cv::imwrite("roi_" + std::to_string(cur_idx) + "_prev.png", prev_img); cv::imwrite("roi_" + std::to_string(cur_idx) + "_curr.png", curr_img); cv::imwrite("diff_roi_" + std::to_string(cur_idx) + ".png", diff); - std::cout << "DEBUG::Saved difference image for frame " << cur_idx << std::endl; + NLOG_DEBUG << "DEBUG::Saved difference image for frame " << cur_idx; } } } @@ -2850,7 +2853,7 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { // POST_PROCESS mode: Combine step displacements at the end if (use_post_process) { - std::cout << std::endl << "Post-processing: Combining step displacements..." << std::endl; + NLOG_INFO << std::endl << "Post-processing: Combining step displacements..."; for (difference_type cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { // Find chain of step displacements needed to reach this frame @@ -2877,13 +2880,13 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { auto combined = add_with_rois(chain_disps, chain_rois, DIC_input.interp_type); if (combined.get_roi().get_points() == 0) { - std::cerr << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!" << std::endl; - std::cerr << " Using last step displacement instead." << std::endl; + NLOG_WARN << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!"; + NLOG_WARN << " Using last step displacement instead."; DIC_output.disps[cur_idx-1] = step_disps[cur_idx-1]; } else { DIC_output.disps[cur_idx-1] = combined; - std::cout << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps, " - << combined.get_roi().get_points() << " valid points." << std::endl; + NLOG_INFO << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps, " + << combined.get_roi().get_points() << " valid points."; } } } @@ -2898,13 +2901,13 @@ DIC_analysis_output DIC_analysis(const DIC_analysis_input &DIC_input) { std::string step_filename = "DIC_analysis_step_data.bin"; save(step_data, step_filename); - std::cout << "Step displacement data saved to " << step_filename << std::endl; + NLOG_INFO << "Step displacement data saved to " << step_filename; } // End timer for entire analysis std::chrono::time_point end_analysis = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_analysis = end_analysis - start_analysis; - std::cout << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "." << std::endl; + NLOG_INFO << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "."; return DIC_output; } @@ -2935,7 +2938,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, step_disps.resize(DIC_input.imgs.size()-1); step_rois.resize(DIC_input.imgs.size()-1); step_ref_idx.resize(DIC_input.imgs.size()-1); - std::cout << "Using POST_PROCESS (MATLAB-style) accumulation mode." << std::endl; + NLOG_INFO << "Using POST_PROCESS (MATLAB-style) accumulation mode."; } ROI2D roi_ref = DIC_input.roi; @@ -2943,13 +2946,13 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, std::vector current_seeds = seeds_by_region; bool current_seeds_optimized = seeds_are_optimized; for (difference_type ref_idx = 0, cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { - std::cout << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "." << std::endl; - std::cout << "Reference image: " << DIC_input.imgs[ref_idx] << "." << std::endl; - std::cout << "Current image: " << DIC_input.imgs[cur_idx] << "." << std::endl; + NLOG_INFO << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "."; + NLOG_INFO << "Reference image: " << DIC_input.imgs[ref_idx] << "."; + NLOG_INFO << "Current image: " << DIC_input.imgs[cur_idx] << "."; std::chrono::time_point start_rgdic = std::chrono::system_clock::now(); - std::cout << "Debug:: " << current_seeds[0].x << " " << current_seeds[0].y << std::endl; + NLOG_DEBUG << "Debug:: " << current_seeds[0].x << " " << current_seeds[0].y; auto disps = RGDIC_without_thread(DIC_input.imgs[ref_idx].get_gs(), DIC_input.imgs[cur_idx].get_gs(), roi_ref, @@ -2964,7 +2967,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, std::chrono::time_point end_rgdic = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_rgdic = end_rgdic - start_rgdic; - std::cout << "Time: " << elapsed_seconds_rgdic.count() << "." << std::endl; + NLOG_INFO << "Time: " << elapsed_seconds_rgdic.count() << "."; // Store displacements based on accumulation mode if (use_post_process) { @@ -2976,7 +2979,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, if (ref_idx > 0) { auto added_disps = add(std::vector({ DIC_output.disps[ref_idx-1], disps }), DIC_input.interp_type); if (added_disps.get_roi().get_points() == 0) { - std::cerr << "WARNING: add() at frame " << cur_idx << " produced empty ROI! Using current disps." << std::endl; + NLOG_WARN << "WARNING: add() at frame " << cur_idx << " produced empty ROI! Using current disps."; DIC_output.disps[cur_idx-1] = disps; } else { DIC_output.disps[cur_idx-1] = added_disps; @@ -2989,9 +2992,9 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, Array2D cc_values = disps.get_cc().get_array()(disps.get_cc().get_roi().get_mask()); if (!cc_values.empty()) { double selected_corrcoef = prctile(cc_values, DIC_input.prctile_corrcoef); - std::cout << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "." << std::endl; + NLOG_INFO << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "."; if (selected_corrcoef > DIC_input.update_corrcoef) { - std::cout << "DAS::Updating reference image..." << ref_idx << " -> " << cur_idx << std::endl; + NLOG_INFO << "DAS::Updating reference image..." << ref_idx << " -> " << cur_idx; auto prev_ref_idx = ref_idx; // Update the reference image index as well as the reference roi ref_idx = cur_idx; @@ -3040,24 +3043,24 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, if (!updated_seeds.empty()) { current_seeds = updated_seeds; current_seeds_optimized = false; // Seeds need re-optimization with new reference - std::cout << " Seeds propagated: " << updated_seeds.size() << "/" << seeds_by_region.size() << " remain in updated ROI." << std::endl; + NLOG_INFO << " Seeds propagated: " << updated_seeds.size() << "/" << seeds_by_region.size() << " remain in updated ROI."; } else { - std::cerr << " WARNING: No seeds remain in updated ROI. Clearing seeds (auto-detect will be used)." << std::endl; + NLOG_WARN << " WARNING: No seeds remain in updated ROI. Clearing seeds (auto-detect will be used)."; current_seeds.clear(); } } // Check if updated ROI is empty and warn/handle if (false && roi_ref.get_points() == 0) { - std::cerr << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!" << std::endl; - std::cerr << " Previous ROI had " << prev_roi.get_points() << " points." << std::endl; - std::cerr << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID") << std::endl; + NLOG_WARN << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!"; + NLOG_WARN << " Previous ROI had " << prev_roi.get_points() << " points."; + NLOG_WARN << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID"); // Revert to previous ROI to prevent crash in subsequent analysis roi_ref = prev_roi; ref_idx = prev_ref_idx; // Keep previous reference current_seeds = seeds_by_region; // Revert seeds too current_seeds_optimized = seeds_are_optimized; - std::cerr << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis." << std::endl; + NLOG_WARN << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis."; } if (DIC_input.debug) { @@ -3068,7 +3071,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, cv::imwrite("roi_" + std::to_string(cur_idx) + "_prev.png", prev_img); cv::imwrite("roi_" + std::to_string(cur_idx) + "_curr.png", curr_img); cv::imwrite("diff_roi_" + std::to_string(cur_idx) + ".png", diff); - std::cout << "DEBUG::Saved difference image for frame " << cur_idx << std::endl; + NLOG_DEBUG << "DEBUG::Saved difference image for frame " << cur_idx; } } } @@ -3076,7 +3079,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, // POST_PROCESS mode: Combine step displacements at the end if (use_post_process) { - std::cout << std::endl << "Post-processing: Combining step displacements..." << std::endl; + NLOG_INFO << std::endl << "Post-processing: Combining step displacements..."; for (difference_type cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { std::vector chain_disps; std::vector chain_rois; @@ -3092,11 +3095,11 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, } else { auto combined = add_with_rois(chain_disps, chain_rois, DIC_input.interp_type); if (combined.get_roi().get_points() == 0) { - std::cerr << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!" << std::endl; + NLOG_WARN << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!"; DIC_output.disps[cur_idx-1] = step_disps[cur_idx-1]; } else { DIC_output.disps[cur_idx-1] = combined; - std::cout << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps." << std::endl; + NLOG_INFO << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps."; } } } @@ -3111,12 +3114,12 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_input &DIC_input, std::string step_filename = "DIC_analysis_sequential_step_data.bin"; save(step_data, step_filename); - std::cout << "Step displacement data saved to " << step_filename << std::endl; + NLOG_INFO << "Step displacement data saved to " << step_filename; } std::chrono::time_point end_analysis = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_analysis = end_analysis - start_analysis; - std::cout << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "." << std::endl; + NLOG_INFO << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "."; return DIC_output; } @@ -3149,7 +3152,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p step_disps.resize(DIC_input.imgs.size()-1); step_rois.resize(DIC_input.imgs.size()-1); step_ref_idx.resize(DIC_input.imgs.size()-1); - std::cout << "Using POST_PROCESS (MATLAB-style) accumulation mode." << std::endl; + NLOG_INFO << "Using POST_PROCESS (MATLAB-style) accumulation mode."; } ROI2D roi_ref = DIC_input.roi; @@ -3157,9 +3160,9 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p std::vector current_seeds = parallel_input.seeds_by_region; bool current_seeds_optimized = parallel_input.seeds_are_optimized; for (difference_type ref_idx = 0, cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { - std::cout << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "." << std::endl; - std::cout << "Reference image: " << DIC_input.imgs[ref_idx] << "." << std::endl; - std::cout << "Current image: " << DIC_input.imgs[cur_idx] << "." << std::endl; + NLOG_INFO << std::endl << "Processing displacement field " << cur_idx << " of " << DIC_input.imgs.size() - 1 << "."; + NLOG_INFO << "Reference image: " << DIC_input.imgs[ref_idx] << "."; + NLOG_INFO << "Current image: " << DIC_input.imgs[cur_idx] << "."; std::chrono::time_point start_rgdic = std::chrono::system_clock::now(); @@ -3177,7 +3180,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p std::chrono::time_point end_rgdic = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_rgdic = end_rgdic - start_rgdic; - std::cout << "Time: " << elapsed_seconds_rgdic.count() << "." << std::endl; + NLOG_INFO << "Time: " << elapsed_seconds_rgdic.count() << "."; // Store displacements based on accumulation mode if (use_post_process) { @@ -3189,7 +3192,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p if (ref_idx > 0) { auto added_disps = add(std::vector({ DIC_output.disps[ref_idx-1], disps }), DIC_input.interp_type); if (added_disps.get_roi().get_points() == 0) { - std::cerr << "WARNING: add() at frame " << cur_idx << " produced empty ROI! Using current disps." << std::endl; + NLOG_WARN << "WARNING: add() at frame " << cur_idx << " produced empty ROI! Using current disps."; DIC_output.disps[cur_idx-1] = disps; } else { DIC_output.disps[cur_idx-1] = added_disps; @@ -3202,9 +3205,9 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p Array2D cc_values = disps.get_cc().get_array()(disps.get_cc().get_roi().get_mask()); if (!cc_values.empty()) { double selected_corrcoef = prctile(cc_values, DIC_input.prctile_corrcoef); - std::cout << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "." << std::endl; + NLOG_INFO << "Selected correlation coefficient value: " << selected_corrcoef << ". Correlation coefficient update value: " << DIC_input.update_corrcoef << "."; if (selected_corrcoef > DIC_input.update_corrcoef) { - std::cout << "DASP::Updating reference image..." << ref_idx << " -> " << cur_idx << std::endl; + NLOG_INFO << "DASP::Updating reference image..." << ref_idx << " -> " << cur_idx; auto prev_ref_idx = ref_idx; // Update the reference image index as well as the reference roi ref_idx = cur_idx; @@ -3249,24 +3252,24 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p if (!updated_seeds.empty()) { current_seeds = updated_seeds; current_seeds_optimized = false; - std::cout << " Seeds propagated: " << updated_seeds.size() << "/" << parallel_input.seeds_by_region.size() << " remain in updated ROI." << std::endl; + NLOG_INFO << " Seeds propagated: " << updated_seeds.size() << "/" << parallel_input.seeds_by_region.size() << " remain in updated ROI."; } else { - std::cerr << " WARNING: No seeds remain in updated ROI. Clearing seeds (auto-detect will be used)." << std::endl; + NLOG_WARN << " WARNING: No seeds remain in updated ROI. Clearing seeds (auto-detect will be used)."; current_seeds.clear(); } } // Check if updated ROI is empty and warn/handle if (roi_ref.get_points() == 0) { - std::cerr << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!" << std::endl; - std::cerr << " Previous ROI had " << prev_roi.get_points() << " points." << std::endl; - std::cerr << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID") << std::endl; + NLOG_WARN << "WARNING: ROI update at frame " << cur_idx << " produced an empty ROI!"; + NLOG_WARN << " Previous ROI had " << prev_roi.get_points() << " points."; + NLOG_WARN << " Mode: " << (DIC_input.roi_update_mode == ROI_UPDATE_MODE::SKIP_ALL ? "SKIP_ALL" : "SKIP_INVALID"); // Revert to previous ROI and seeds to prevent crash roi_ref = prev_roi; ref_idx = prev_ref_idx; current_seeds = parallel_input.seeds_by_region; current_seeds_optimized = parallel_input.seeds_are_optimized; - std::cerr << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis." << std::endl; + NLOG_WARN << " Reverting to previous ROI (frame " << ref_idx << ") to continue analysis."; } if (DIC_input.debug) { @@ -3277,7 +3280,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p cv::imwrite("roi_" + std::to_string(cur_idx) + "_prev.png", prev_img); cv::imwrite("roi_" + std::to_string(cur_idx) + "_curr.png", curr_img); cv::imwrite("diff_roi_" + std::to_string(cur_idx) + ".png", diff); - std::cout << "DEBUG::Saved difference image for frame " << cur_idx << std::endl; + NLOG_DEBUG << "DEBUG::Saved difference image for frame " << cur_idx; } } } @@ -3285,7 +3288,7 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p // POST_PROCESS mode: Combine step displacements at the end if (use_post_process) { - std::cout << std::endl << "Post-processing: Combining step displacements..." << std::endl; + NLOG_INFO << std::endl << "Post-processing: Combining step displacements..."; for (difference_type cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { std::vector chain_disps; std::vector chain_rois; @@ -3301,11 +3304,11 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p } else { auto combined = add_with_rois(chain_disps, chain_rois, DIC_input.interp_type); if (combined.get_roi().get_points() == 0) { - std::cerr << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!" << std::endl; + NLOG_WARN << "WARNING: Post-process add_with_rois for frame " << cur_idx << " produced empty ROI!"; DIC_output.disps[cur_idx-1] = step_disps[cur_idx-1]; } else { DIC_output.disps[cur_idx-1] = combined; - std::cout << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps." << std::endl; + NLOG_INFO << "Frame " << cur_idx << ": Combined " << chain_disps.size() << " steps."; } } } @@ -3320,12 +3323,12 @@ DIC_analysis_output DIC_analysis_sequential(const DIC_analysis_parallel_input &p std::string step_filename = "DIC_analysis_sequential_seeds_step_data.bin"; save(step_data, step_filename); - std::cout << "Step displacement data saved to " << step_filename << std::endl; + NLOG_INFO << "Step displacement data saved to " << step_filename; } std::chrono::time_point end_analysis = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds_analysis = end_analysis - start_analysis; - std::cout << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "." << std::endl; + NLOG_INFO << std::endl << "Total DIC analysis time: " << elapsed_seconds_analysis.count() << "."; return DIC_output; } @@ -3384,9 +3387,9 @@ DIC_analysis_output change_perspective(const DIC_analysis_output &DIC_output, IN DIC_output_updated.units_per_pixel = DIC_output.units_per_pixel; // Cycle over displacement fields and convert - std::cout << std::endl << "Changing perspective..." << std::endl; + NLOG_INFO << std::endl << "Changing perspective..."; for (difference_type disp_idx = 0; disp_idx < difference_type(DIC_output.disps.size()); ++disp_idx) { - std::cout << "Displacement field " << disp_idx+1 << " of " << DIC_output.disps.size() << "." << std::endl; + NLOG_INFO << "Displacement field " << disp_idx+1 << " of " << DIC_output.disps.size() << "."; DIC_output_updated.disps.push_back(details::update(DIC_output.disps[disp_idx], interp_type)); } @@ -3418,9 +3421,9 @@ DIC_analysis_output change_perspective_with_inversion(const DIC_analysis_output DIC_output_updated.units_per_pixel = DIC_output.units_per_pixel; // Cycle over displacement fields and convert - std::cout << std::endl << "Changing perspective with sign inversion (Matlab-compatible)..." << std::endl; + NLOG_INFO << std::endl << "Changing perspective with sign inversion (Matlab-compatible)..."; for (difference_type disp_idx = 0; disp_idx < difference_type(DIC_output.disps.size()); ++disp_idx) { - std::cout << "Displacement field " << disp_idx+1 << " of " << DIC_output.disps.size() << "." << std::endl; + NLOG_INFO << "Displacement field " << disp_idx+1 << " of " << DIC_output.disps.size() << "."; // Apply perspective conversion via interpolation Disp2D disp_eulerian = details::update(DIC_output.disps[disp_idx], interp_type); @@ -3490,7 +3493,7 @@ DIC_analysis_output filter_by_correlation(const DIC_analysis_output &DIC_output, DIC_output_filtered.units = DIC_output.units; DIC_output_filtered.units_per_pixel = DIC_output.units_per_pixel; - std::cout << std::endl << "Filtering by correlation coefficient (threshold: " << cutoff_corrcoef << ")..." << std::endl; + NLOG_INFO << std::endl << "Filtering by correlation coefficient (threshold: " << cutoff_corrcoef << ")..."; // Process each displacement field for (difference_type disp_idx = 0; disp_idx < difference_type(DIC_output.disps.size()); ++disp_idx) { @@ -3516,12 +3519,12 @@ DIC_analysis_output filter_by_correlation(const DIC_analysis_output &DIC_output, } } - std::cout << " Displacement " << disp_idx+1 << ": kept " << points_kept - << " / " << points_total << " points"; - if (points_total > 0) { - std::cout << " (" << (100.0 * points_kept / points_total) << "%)"; - } - std::cout << "." << std::endl; + NLOG_INFO << " Displacement " << disp_idx+1 << ": kept " << points_kept + << " / " << points_total << " points" + << (points_total > 0 + ? " (" + std::to_string(100.0 * points_kept / points_total) + "%)" + : std::string()) + << "."; // Create new ROI with filtered mask ROI2D roi_filtered(std::move(mask_filtered)); @@ -3793,9 +3796,9 @@ strain_analysis_output strain_analysis(const strain_analysis_input &strain_input strain_analysis_output strain_output; // Cycle over each displacement field and calculate corresponding strains - std::cout << std::endl << "Processing strain fields..." << std::endl; - for (difference_type disp_idx = 0; disp_idx < difference_type(strain_input.DIC_output.disps.size()); ++disp_idx) { - std::cout << "Strain field " << disp_idx+1 << " of " << strain_input.DIC_output.disps.size() << "." << std::endl; + NLOG_INFO << std::endl << "Processing strain fields..."; + for (difference_type disp_idx = 0; disp_idx < difference_type(strain_input.DIC_output.disps.size()); ++disp_idx) { + NLOG_INFO << "Strain field " << disp_idx+1 << " of " << strain_input.DIC_output.disps.size() << "."; // Calculate strain field for this Disp2D strain_output.strains.push_back(LS_strain(strain_input.DIC_output.disps[disp_idx], strain_input.DIC_output.perspective_type, @@ -4233,14 +4236,14 @@ void save_ncorr_data_over_img_video(const std::string &filename, // All other parameters will be checked when calling cv_ncorr_data_over_img() - std::cout << std::endl << "Saving video: " << filename << "..." << std::endl; + NLOG_INFO << std::endl << "Saving video: " << filename << "..."; // Initialize video cv::VideoWriter output_video; // Cycle over data and save for (difference_type data_idx = 0; data_idx < difference_type(data.size()); ++data_idx) { - std::cout << "Frame " << data_idx+1 << " of " << data.size() << "." << std::endl; + NLOG_INFO << "Frame " << data_idx+1 << " of " << data.size() << "."; auto cv_data_img = details::cv_ncorr_data_over_img(imgs.size() == 1 ? imgs.front() : imgs[data_idx], data[data_idx], @@ -4566,7 +4569,7 @@ Disp2D compute_displacements( // Must form union with valid points auto roi_valid = roi_reduced.form_union(A_vp); if (debug) { - std::cout << "RGDIC completed with manual seed" << std::endl; + NLOG_DEBUG << "RGDIC completed with manual seed"; } return Disp2D(std::move(A_v), std::move(A_u), std::move(A_cc), roi_valid, scalefactor); } @@ -4589,8 +4592,8 @@ std::vector compute_only_seed_points( std::vector selected_data; if (debug) { - std::cout << "\n=== Compute seed parameters for all frames with ROI updates ===> "; - std::cout << "\n starting with " << seeds_by_region.size() << " seeds" << std::endl; + NLOG_DEBUG << "\n=== Compute seed parameters for all frames with ROI updates ===> "; + NLOG_DEBUG << "\n starting with " << seeds_by_region.size() << " seeds"; } // Start with initial reference and ROI @@ -4603,7 +4606,7 @@ std::vector compute_only_seed_points( const Array2D& A_cur = A_curs[idx]; if (debug) { - std::cout << "\n=== Seeding analyzing frame " << (idx + 1) << " ===> "; + NLOG_DEBUG << "\n=== Seeding analyzing frame " << (idx + 1) << " ===> "; } // Get subregion nonlinear optimizer @@ -4624,7 +4627,7 @@ std::vector compute_only_seed_points( if (seed_results.success) { // Seed analysis successful - store data if (debug) { - std::cout << "Successful!" << std::endl; + NLOG_DEBUG << "Successful!"; } selected_data.emplace_back(roi_current, seed_results.seeds, sr_nloptimizer); @@ -4632,7 +4635,7 @@ std::vector compute_only_seed_points( } else { // Seed analysis failed - update reference and propagate seeds if (debug) { - std::cout << "Failed! Updating reference ===>" << std::endl; + NLOG_DEBUG << "Failed! Updating reference ===>"; } auto A_prev = A_curs[idx-1]; @@ -4661,13 +4664,13 @@ std::vector compute_only_seed_points( seeds_current = propagate_seeds(prev_seedparams, scalefactor); if (debug) { - std::cout << "Reference updated, seeds propagated <====" << std::endl; + NLOG_DEBUG << "Reference updated, seeds propagated <===="; } } } if (debug) { - std::cout << "\nComputed seed parameters for " << selected_data.size() << " frames" << std::endl; + NLOG_DEBUG << "\nComputed seed parameters for " << selected_data.size() << " frames"; } return selected_data; @@ -4746,7 +4749,7 @@ SeedAnalysisResult analyze_seeds( if (!result_pair.second) { if (debug) { - std::cout << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> global failed" << std::endl; + NLOG_DEBUG << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> global failed"; } result.success = false; continue; @@ -4758,7 +4761,7 @@ SeedAnalysisResult analyze_seeds( if (!result_iter.second) { if (debug) { - std::cout << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> iterative search failed" << std::endl; + NLOG_DEBUG << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> iterative search failed"; } result.success = false; continue; @@ -4787,9 +4790,9 @@ SeedAnalysisResult analyze_seeds( // Check quality thresholds if (seed_result.corrcoef > cutoff_max_corrcoef || convergence.diffnorm > cutoff_max_diffnorm) { if (debug) { - std::cout << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> corrcoef: " << seed_result.corrcoef << ", diffnorm: " << convergence.diffnorm << std::endl; + NLOG_DEBUG << region_idx << ": Seed analysis failed at seed " << seed_pos.x << ", " << seed_pos.y << "=> corrcoef: " << seed_result.corrcoef << ", diffnorm: " << convergence.diffnorm; } - std::cout << result.success << std::endl; + NLOG_DEBUG << result.success; result.success = false; //TODO: Solve this } region_idx++; @@ -4924,8 +4927,8 @@ matlab_seed_segment matlab_compute_seed_segment(const DIC_analysis_parallel_inpu const auto& predicted_seed = predicted_seeds[region_idx]; if (!matlab_seed_matches_region(roi_reduced, predicted_seed, DIC_input.scalefactor, region_idx)) { if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: seed for region " << region_idx - << " is outside its ROI at frame " << cur_idx << "." << std::endl; + NLOG_DEBUG << "matlab_DIC_analysis: seed for region " << region_idx + << " is outside its ROI at frame " << cur_idx << "."; } frame_success = false; break; @@ -4935,8 +4938,8 @@ matlab_seed_segment matlab_compute_seed_segment(const DIC_analysis_parallel_inpu auto global_result = sr_nloptimizer.global(params_init); if (!global_result.second) { if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: global seed optimization failed for region " - << region_idx << " at frame " << cur_idx << "." << std::endl; + NLOG_DEBUG << "matlab_DIC_analysis: global seed optimization failed for region " + << region_idx << " at frame " << cur_idx << "."; } frame_success = false; break; @@ -4955,12 +4958,12 @@ matlab_seed_segment matlab_compute_seed_segment(const DIC_analysis_parallel_inpu iteration_saturated || !matlab_seed_matches_region(roi_reduced, optimized_seed, DIC_input.scalefactor, region_idx)) { if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: seed quality failed for region " << region_idx + NLOG_DEBUG << "matlab_DIC_analysis: seed quality failed for region " << region_idx << " at frame " << cur_idx << " corrcoef=" << optimized_seed.corrcoef << " diffnorm=" << diffnorm << " iterations=" << num_iterations - << " saturated=" << (iteration_saturated ? "yes" : "no") << "." << std::endl; + << " saturated=" << (iteration_saturated ? "yes" : "no") << "."; } frame_success = false; break; @@ -4971,7 +4974,7 @@ matlab_seed_segment matlab_compute_seed_segment(const DIC_analysis_parallel_inpu if (!frame_success || !matlab_seed_positions_are_unique(optimized_seeds, DIC_input.scalefactor)) { if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: stopping seed schedule at frame " << cur_idx << "." << std::endl; + NLOG_DEBUG << "matlab_DIC_analysis: stopping seed schedule at frame " << cur_idx << "."; } break; } @@ -5032,13 +5035,12 @@ std::vector matlab_run_segment_dic(const DIC_analysis_parallel_input& in } const difference_type requested_threads = std::min(num_frames, DIC_input.num_threads); - std::cout << "\n[matlab_run_segment_dic] Parallel dispatch:" + NLOG_DEBUG << "\n[matlab_run_segment_dic] Parallel dispatch:" << "\n num_frames = " << num_frames << "\n DIC num_threads = " << DIC_input.num_threads << "\n OMP_NUM_THREADS = " << (std::getenv("OMP_NUM_THREADS") ? std::getenv("OMP_NUM_THREADS") : "not set") << "\n omp_get_max_threads() = " << omp_get_max_threads() - << "\n Requesting = " << requested_threads << " threads" - << std::endl; + << "\n Requesting = " << requested_threads << " threads"; std::vector per_thread_seconds(requested_threads, 0.0); std::vector per_thread_frames (requested_threads, 0); @@ -5057,25 +5059,25 @@ std::vector matlab_run_segment_dic(const DIC_analysis_parallel_input& in per_thread_frames [tid] += 1; #pragma omp critical { - std::cout << " [segment_dic] frame " << frame_idx + NLOG_DEBUG << " [segment_dic] frame " << frame_idx << " on thread " << tid << " / " << nthr - << " took " << dt << " s" << std::endl; + << " took " << dt << " s"; } } const double t_wall = omp_get_wtime() - t_wall_begin; double total_cpu = 0.0; - std::cout << "[matlab_run_segment_dic] per-thread summary:" << std::endl; + NLOG_DEBUG << "[matlab_run_segment_dic] per-thread summary:"; for (int t = 0; t < requested_threads; ++t) { - std::cout << " thread " << t + NLOG_DEBUG << " thread " << t << ": " << per_thread_frames[t] << " frames, " - << per_thread_seconds[t] << " s" << std::endl; + << per_thread_seconds[t] << " s"; total_cpu += per_thread_seconds[t]; } - std::cout << " wall = " << t_wall << " s, sum-of-threads = " + NLOG_DEBUG << " wall = " << t_wall << " s, sum-of-threads = " << total_cpu << " s, speedup = " << (t_wall > 0.0 ? total_cpu / t_wall : 0.0) - << " (ideal " << requested_threads << ")" << std::endl; + << " (ideal " << requested_threads << ")"; return segment_disps; } @@ -5154,9 +5156,9 @@ DIC_analysis_output matlab_DIC_analysis_impl(const DIC_analysis_parallel_input& const difference_type segment_end_idx = ref_idx + difference_type(segment.seeds_by_frame.size()); if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: processed segment " + NLOG_DEBUG << "matlab_DIC_analysis: processed segment " << (ref_idx + 1) << " -> " << (segment_end_idx + 1) - << " (" << segment.seeds_by_frame.size() << " frame(s))." << std::endl; + << " (" << segment.seeds_by_frame.size() << " frame(s))."; } ++num_segments; @@ -5183,8 +5185,8 @@ DIC_analysis_output matlab_DIC_analysis_impl(const DIC_analysis_parallel_input& // --------------------------------------------------------------------- if (num_segments > 1) { if (DIC_input.debug) { - std::cout << "matlab_DIC_analysis: chaining " << num_segments - << " segments back to global reference (frame 1)." << std::endl; + NLOG_DEBUG << "matlab_DIC_analysis: chaining " << num_segments + << " segments back to global reference (frame 1)."; } for (difference_type cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); ++cur_idx) { // Build chain of step displacements from frame 0 to cur_idx. @@ -5207,8 +5209,8 @@ DIC_analysis_output matlab_DIC_analysis_impl(const DIC_analysis_parallel_input& auto combined = add_with_rois(chain_disps, chain_rois, DIC_input.interp_type); if (combined.get_roi().get_points() == 0) { - std::cerr << "WARNING: matlab_DIC_analysis multi-ref chaining produced empty ROI at frame " - << (cur_idx + 1) << "; keeping segment-local displacement." << std::endl; + NLOG_WARN << "WARNING: matlab_DIC_analysis multi-ref chaining produced empty ROI at frame " + << (cur_idx + 1) << "; keeping segment-local displacement."; continue; } DIC_output.disps[cur_idx - 1] = std::move(combined); @@ -5231,7 +5233,7 @@ DIC_analysis_output matlab_DIC_analysis_impl(const DIC_analysis_parallel_input& "matlab_DIC_analysis_parallel_step_data.bin" : "matlab_DIC_analysis_sequential_step_data.bin"; save(step_data, step_filename); - std::cout << "Step displacement data saved to " << step_filename << std::endl; + NLOG_INFO << "Step displacement data saved to " << step_filename; } return DIC_output; @@ -5269,10 +5271,10 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp throw std::invalid_argument("DIC_analysis_parallel requires seeds_by_region to be provided."); } - std::cout << std::endl << "Starting seed-based parallel DIC analysis..." << std::endl; - std::cout << "Number of regions: " << input.seeds_by_region.size() << std::endl; - std::cout << "Cutoff max diffnorm: " << input.cutoff_max_diffnorm << std::endl; - std::cout << "Cutoff max corrcoef: " << input.cutoff_max_corrcoef << std::endl; + NLOG_INFO << std::endl << "Starting seed-based parallel DIC analysis..."; + NLOG_INFO << "Number of regions: " << input.seeds_by_region.size(); + NLOG_INFO << "Cutoff max diffnorm: " << input.cutoff_max_diffnorm; + NLOG_INFO << "Cutoff max corrcoef: " << input.cutoff_max_corrcoef; std::chrono::time_point start_analysis = std::chrono::system_clock::now(); @@ -5284,7 +5286,7 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp DIC_output.units_per_pixel = 1.0; // PHASE 1: Compute seed parameters for all frames with ROI updates - std::cout << "\nPhase 1: Computing seed parameters for all frames..." << std::endl; + NLOG_INFO << "\nPhase 1: Computing seed parameters for all frames..."; // Prepare current images std::vector> A_curs; @@ -5312,28 +5314,28 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp throw std::runtime_error("No valid seed parameters could be computed."); } - std::cout << "Successfully computed seed parameters for " << seed_data.size() << " frames" << std::endl; + NLOG_INFO << "Successfully computed seed parameters for " << seed_data.size() << " frames"; // PHASE 2: Parallel displacement computation using precomputed seed data - std::cout << "\nPhase 2: Computing displacements in parallel..." << std::endl; + NLOG_INFO << "\nPhase 2: Computing displacements in parallel..."; size_t safe_batch_size = seed_data.size(); // Use OpenMP for parallel execution (Python uses ThreadPoolExecutor, C++ uses OpenMP) - std::cout << "Safe batch size: " << safe_batch_size << std::endl; - std::cout << "Number of threads: " << DIC_input.num_threads << std::endl; - std::cout << "static_cast(safe_batch_size): " << static_cast(safe_batch_size) << std::endl; - std::cout << "Number of threads: " << std::min(static_cast(safe_batch_size), DIC_input.num_threads) << std::endl; + NLOG_INFO << "Safe batch size: " << safe_batch_size; + NLOG_INFO << "Number of threads: " << DIC_input.num_threads; + NLOG_DEBUG << "static_cast(safe_batch_size): " << static_cast(safe_batch_size); + NLOG_DEBUG << "Number of threads: " << std::min(static_cast(safe_batch_size), DIC_input.num_threads); #pragma omp parallel for num_threads(std::min(static_cast(safe_batch_size), DIC_input.num_threads)) schedule(dynamic) for (size_t i = 0; i < safe_batch_size; ++i) { difference_type frame_idx = static_cast(i) + 1; int thread_id = omp_get_thread_num(); int total_threads = omp_get_num_threads(); - printf("Running on thread %d of %d\n", thread_id, total_threads); + NLOG_DEBUG << "Running on thread " << thread_id << " of " << total_threads; #pragma omp critical { - std::cout << " Processing frame " << frame_idx << " (parallel)" << std::endl; + NLOG_INFO << " Processing frame " << frame_idx << " (parallel)"; } // Use precomputed seed data for this frame @@ -5345,7 +5347,7 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp if (frame_data.seed_params_by_region.empty()) { #pragma omp critical { - std::cerr << " WARNING: No seed params for frame " << frame_idx << ", skipping." << std::endl; + NLOG_WARN << " WARNING: No seed params for frame " << frame_idx << ", skipping."; } continue; } @@ -5371,7 +5373,7 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp } catch (const std::exception& e) { #pragma omp critical { - std::cerr << " ERROR: Frame " << frame_idx << " failed: " << e.what() << std::endl; + NLOG_ERROR << " ERROR: Frame " << frame_idx << " failed: " << e.what(); } } } @@ -5379,7 +5381,7 @@ DIC_analysis_output DIC_analysis_parallel(const DIC_analysis_parallel_input& inp // End timer std::chrono::time_point end_analysis = std::chrono::system_clock::now(); std::chrono::duration elapsed_seconds = end_analysis - start_analysis; - std::cout << std::endl << "Total parallel DIC analysis time: " << elapsed_seconds.count() << " seconds" << std::endl; + NLOG_INFO << std::endl << "Total parallel DIC analysis time: " << elapsed_seconds.count() << " seconds"; return DIC_output; } @@ -5737,9 +5739,9 @@ DIC_analysis_output exact_matlab_DIC_analysis_impl(const DIC_analysis_parallel_i const difference_type segment_end_idx = ref_idx + difference_type(segment.seeds_by_frame.size()); if (DIC_input.debug) { - std::cout << mode_name << ": processed segment " + NLOG_DEBUG << mode_name << ": processed segment " << (ref_idx + 1) << " -> " << (segment_end_idx + 1) - << " (" << segment.seeds_by_frame.size() << " frame(s))." << std::endl; + << " (" << segment.seeds_by_frame.size() << " frame(s))."; } ++num_segments; ref_idx = segment_end_idx; @@ -5751,10 +5753,9 @@ DIC_analysis_output exact_matlab_DIC_analysis_impl(const DIC_analysis_parallel_i if (num_segments > 1) { if (DIC_input.debug) { - std::cout << mode_name << ": chaining " << num_segments + NLOG_DEBUG << mode_name << ": chaining " << num_segments << " segments back to global reference (frame 1) " - << "via exact_add_with_rois (MATLAB ncorr_alg_addanalysis)." - << std::endl; + << "via exact_add_with_rois (MATLAB ncorr_alg_addanalysis)."; } for (difference_type cur_idx = 1; cur_idx < difference_type(DIC_input.imgs.size()); @@ -5772,10 +5773,10 @@ DIC_analysis_output exact_matlab_DIC_analysis_impl(const DIC_analysis_parallel_i auto combined = exact_add_with_rois(chain_disps, chain_rois); if (combined.get_roi().get_points() == 0) { - std::cerr << "WARNING: " << mode_name + NLOG_WARN << "WARNING: " << mode_name << " multi-ref chaining produced empty ROI at frame " << (cur_idx + 1) - << "; keeping segment-local displacement." << std::endl; + << "; keeping segment-local displacement."; continue; } DIC_output.disps[cur_idx - 1] = std::move(combined); @@ -5792,7 +5793,7 @@ DIC_analysis_output exact_matlab_DIC_analysis_impl(const DIC_analysis_parallel_i "exact_matlab_DIC_analysis_parallel_step_data.bin" : "exact_matlab_DIC_analysis_sequential_step_data.bin"; save(step_data, filename); - std::cout << "Step displacement data saved to " << filename << std::endl; + NLOG_INFO << "Step displacement data saved to " << filename; } return DIC_output; From 431362cb8c3b27fd40076d18fcd5e926caf3b8e1 Mon Sep 17 00:00:00 2001 From: John Aoga Date: Thu, 18 Jun 2026 13:19:08 +0200 Subject: [PATCH 2/2] style: clang-format new logging files (lint CI) Apply clang-format 18.1.8 (the version pinned by the lint workflow) to the newly added include/ncorr/log.h and src/log.cpp so the 'lint (clang-format on changed files)' check passes. Co-Authored-By: Claude Opus 4.8 --- include/ncorr/log.h | 26 +++++++++++------------ src/log.cpp | 51 +++++++++++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/include/ncorr/log.h b/include/ncorr/log.h index 0097f8f..c26969a 100644 --- a/include/ncorr/log.h +++ b/include/ncorr/log.h @@ -49,7 +49,7 @@ const char* level_name(Level l); * @brief Process-wide singleton logger. All members are thread-safe. */ class Logger { -public: + public: static Logger& instance(); void set_console_level(Level l); @@ -79,7 +79,7 @@ class Logger { /// Emit a fully-formed message (trailing newlines are trimmed) at @p l. void write(Level l, const char* file, int line, const std::string& msg); -private: + private: Logger(); void ensure_env(); @@ -104,16 +104,17 @@ void set_debug(bool on); bool set_file(const std::string& path); /// True if a message at @p l would be emitted by any sink. -inline bool enabled(Level l) { return Logger::instance().enabled(l); } +inline bool enabled(Level l) { + return Logger::instance().enabled(l); +} /** * @brief RAII stream builder; flushes its accumulated text to the logger when it * is destroyed at the end of the full expression. */ class Stream { -public: - Stream(Level level, const char* file, int line) - : level_(level), file_(file), line_(line) {} + public: + Stream(Level level, const char* file, int line) : level_(level), file_(file), line_(line) {} ~Stream() { Logger::instance().write(level_, file_, line_, oss_.str()); } Stream(const Stream&) = delete; @@ -130,7 +131,7 @@ class Stream { return *this; } -private: + private: Level level_; const char* file_; int line_; @@ -141,7 +142,7 @@ class Stream { /// statement (glog idiom). @c operator& has lower precedence than @c << but /// higher than @c ?:, so the macro is safe inside an unbraced if/else. class Voidify { -public: + public: Voidify() = default; void operator&(Stream&) {} }; @@ -151,11 +152,10 @@ class Voidify { // Stream-style logging macros. When the level is disabled, the right-hand side // (message construction) is never evaluated. -#define NLOG_AT(lvl) \ - !::ncorr::log::enabled(lvl) \ - ? (void)0 \ - : ::ncorr::log::Voidify() & \ - ::ncorr::log::Stream((lvl), __FILE__, __LINE__) +#define NLOG_AT(lvl) \ + !::ncorr::log::enabled(lvl) \ + ? (void)0 \ + : ::ncorr::log::Voidify() & ::ncorr::log::Stream((lvl), __FILE__, __LINE__) #define NLOG_TRACE NLOG_AT(::ncorr::log::Level::Trace) #define NLOG_DEBUG NLOG_AT(::ncorr::log::Level::Debug) diff --git a/src/log.cpp b/src/log.cpp index 8f4bda6..e02abc2 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -47,23 +47,30 @@ const char* base_name(const char* path) { /// ANSI colour escape for a level (empty when colour is disabled by the caller). const char* color_for(Level l) { switch (l) { - case Level::Trace: return "\033[37m"; // grey - case Level::Debug: return "\033[36m"; // cyan - case Level::Info: return "\033[32m"; // green - case Level::Warn: return "\033[33m"; // yellow - case Level::Error: return "\033[31m"; // red - default: return ""; + case Level::Trace: + return "\033[37m"; // grey + case Level::Debug: + return "\033[36m"; // cyan + case Level::Info: + return "\033[32m"; // green + case Level::Warn: + return "\033[33m"; // yellow + case Level::Error: + return "\033[31m"; // red + default: + return ""; } } -const char* color_reset() { return "\033[0m"; } +const char* color_reset() { + return "\033[0m"; +} /// Format "YYYY-MM-DD HH:MM:SS.mmm" for the current wall-clock time. std::string timestamp() { using namespace std::chrono; const auto now = system_clock::now(); const auto t = system_clock::to_time_t(now); - const auto ms = - duration_cast(now.time_since_epoch()).count() % 1000; + const auto ms = duration_cast(now.time_since_epoch()).count() % 1000; std::tm tm_buf{}; #if defined(_WIN32) localtime_s(&tm_buf, &t); @@ -105,12 +112,18 @@ Level level_from_string(const std::string& s, Level fallback) { const char* level_name(Level l) { switch (l) { - case Level::Trace: return "TRACE"; - case Level::Debug: return "DEBUG"; - case Level::Info: return "INFO "; - case Level::Warn: return "WARN "; - case Level::Error: return "ERROR"; - case Level::Off: return "OFF "; + case Level::Trace: + return "TRACE"; + case Level::Debug: + return "DEBUG"; + case Level::Info: + return "INFO "; + case Level::Warn: + return "WARN "; + case Level::Error: + return "ERROR"; + case Level::Off: + return "OFF "; } return "?????"; } @@ -213,13 +226,15 @@ void Logger::write(Level l, const char* file, int line, const std::string& msg) } if (file_.is_open() && l >= file_level_) { - file_ << timestamp() << " [" << level_name(l) << "] " << fname << ":" - << line << " " << text << "\n"; + file_ << timestamp() << " [" << level_name(l) << "] " << fname << ":" << line << " " << text + << "\n"; file_.flush(); } } -void set_level(Level l) { Logger::instance().set_console_level(l); } +void set_level(Level l) { + Logger::instance().set_console_level(l); +} void set_debug(bool on) { if (on && Logger::instance().console_level() > Level::Debug) {