CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
single_dic_workflow.cpp
Go to the documentation of this file.
1
19
20#include "image_processor.h"
21#include "roi_manager.h"
22#include "parameters.h"
23#include "logging.h"
24#ifdef CPPXDIC_HAVE_CUNCORR
25#include "dic/cuncorr_dic.h"
26#endif
27
28#include <ncorr.h>
29#include <ncorr/frame_reader.h>
30
31#include <matio.h>
32#include <opencv2/opencv.hpp>
33
34#include <algorithm>
35#include <cmath>
36#include <cstdint>
37#include <filesystem>
38#include <fstream>
39#include <iostream>
40#include <limits>
41
42namespace fs = std::filesystem;
43
44namespace singledic {
45
47
48// ---------------------------------------------------------------------------
49// Frame import — video file or numbered image folder.
50// Mirrors readvid(...) in process_single_trial_ncorr.m, honouring the
51// [idx_frame_start, idx_frame_end] window (1-based), frame_jump, and the
52// backward flip.
53// ---------------------------------------------------------------------------
54bool SingleDicWorkflow::importFrames(std::vector<cv::Mat>& frames) const {
55 frames.clear();
56
57 const std::string trial_dir = cfg_.inputTrialDir();
58
59 // Candidate 1: a video file. Vik stores each trial as <trial>.mp4; we also
60 // accept the trial path itself being a file (any extension OpenCV decodes).
61 std::vector<std::string> video_candidates;
62 if (fs::exists(trial_dir) && fs::is_regular_file(trial_dir)) {
63 video_candidates.push_back(trial_dir);
64 }
65 for (const char* ext : {".mp4", ".avi", ".mov", ".mkv"}) {
66 std::string p = trial_dir + ext;
67 if (fs::exists(p)) video_candidates.push_back(p);
68 }
69 // Also: a video file living *inside* the trial directory.
70 if (fs::exists(trial_dir) && fs::is_directory(trial_dir)) {
71 for (const auto& e : fs::directory_iterator(trial_dir)) {
72 if (!e.is_regular_file()) continue;
73 std::string ext = e.path().extension().string();
74 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
75 if (ext == ".mp4" || ext == ".avi" || ext == ".mov" || ext == ".mkv")
76 video_candidates.push_back(e.path().string());
77 }
78 }
79
80 const int start = std::max(1, cfg_.idx_frame_start); // 1-based
81 const int jump = std::max(1, cfg_.frame_jump);
82
83 auto window_done = [&](int count_kept_input_index, int frame_1based) {
84 (void)count_kept_input_index;
85 return cfg_.idx_frame_end > 0 && frame_1based > cfg_.idx_frame_end;
86 };
87
88 if (!video_candidates.empty()) {
89 const std::string& vpath = video_candidates.front();
90 cv::VideoCapture cap(vpath);
91 if (!cap.isOpened()) {
92 LOG_ERROR << "[singledic] Cannot open video: " << vpath;
93 return false;
94 }
95 int frame_idx = 0; // 0-based as read
96 cv::Mat bgr;
97 while (cap.read(bgr)) {
98 ++frame_idx; // now 1-based for this frame
99 if (frame_idx < start) continue;
100 if (window_done(0, frame_idx)) break;
101 if (((frame_idx - start) % jump) != 0) continue;
102 cv::Mat gray;
103 if (bgr.channels() == 3)
104 cv::cvtColor(bgr, gray, cv::COLOR_BGR2GRAY);
105 else
106 gray = bgr.clone();
107 if (gray.type() != CV_8UC1) gray.convertTo(gray, CV_8UC1);
108 frames.push_back(gray);
109 }
110 cap.release();
111 } else if (fs::exists(trial_dir) && fs::is_directory(trial_dir)) {
112 // Candidate 2: numbered image folder (reuse ncorr's natural-sort discovery).
113 std::vector<std::string> paths;
114 try {
115 paths = ncorr::discover_frames(trial_dir, "", "");
116 } catch (const std::exception& e) {
117 LOG_ERROR << "[singledic] " << e.what();
118 return false;
119 }
120 for (size_t i = 0; i < paths.size(); ++i) {
121 int frame_1based = static_cast<int>(i) + 1;
122 if (frame_1based < start) continue;
123 if (window_done(0, frame_1based)) break;
124 if (((frame_1based - start) % jump) != 0) continue;
125 cv::Mat gray = cv::imread(paths[i], cv::IMREAD_GRAYSCALE);
126 if (gray.empty()) {
127 LOG_ERROR << "[singledic] Failed to read frame: " << paths[i];
128 return false;
129 }
130 frames.push_back(gray);
131 }
132 } else {
133 LOG_ERROR << "[singledic] No video or image folder at: " << trial_dir;
134 return false;
135 }
136
137 if (frames.empty()) {
138 LOG_ERROR << "[singledic] No frames imported from " << trial_dir;
139 return false;
140 }
141
142 // Backward tracking flips the temporal order (Vik: flip(im_raw,3)).
143 if (cfg_.tracking_dir == "backward") {
144 std::reverse(frames.begin(), frames.end());
145 }
146
147 LOG_INFO << "[singledic] Imported " << frames.size() << " frame(s) from " << trial_dir;
148 return true;
149}
150
151// ---------------------------------------------------------------------------
152// Saturation — satur(satur(im,'level',high),'method','low','level',low).
153// ---------------------------------------------------------------------------
154std::vector<cv::Mat> SingleDicWorkflow::saturate(const std::vector<cv::Mat>& frames) const {
156 std::vector<cv::Mat> out;
157 out.reserve(frames.size());
158 for (const auto& f : frames) {
159 cv::Mat hi = ImageProcessor::saturate(f, cfg_.limit_grayscale_high, "high");
160 cv::Mat lo = ImageProcessor::saturate(hi, cfg_.limit_grayscale_low, "low");
161 out.push_back(lo);
162 }
163 return out;
164}
165
166// ---------------------------------------------------------------------------
167// Optional bandpass + percentile-normalise (filter_like_ben style):
168// im = bandpassfft(im, low, high);
169// y = prctile(im(roi),[1 99]); im = clamp((im-y1)/(y2-y1),0,1)
170// ---------------------------------------------------------------------------
171std::vector<cv::Mat> SingleDicWorkflow::maybeFilter(const std::vector<cv::Mat>& frames,
172 const cv::Mat& roi_mask) const {
173 if (!cfg_.filter_im_mode) return frames;
174
176 const std::vector<int> param = {cfg_.param_filt_low, cfg_.param_filt_high};
177
178 // Derive grayscale bounds from the reference (frame 0) inside the ROI, then
179 // reuse them for every frame (matches the single-bound approach in Vik's
180 // band-pass video loop).
181 cv::Mat ref_bp = ImageProcessor::applyBandpassFilter(frames.front(), param);
182 auto bounds = ImageProcessor::computePercentileBoundaries(ref_bp, roi_mask, 1.0, 99.0);
183
184 std::vector<cv::Mat> out;
185 out.reserve(frames.size());
186 for (const auto& f : frames) {
187 cv::Mat bp = ImageProcessor::applyBandpassFilter(f, param);
188 out.push_back(ImageProcessor::normalizeAndClamp(bp, bounds));
189 }
190 return out;
191}
192
193// ---------------------------------------------------------------------------
194// ROI — load REF_MASK_*.mat if present, else full-frame ROI.
195// (Reuses cppxdic::ROIManager::loadOrCreateROI, which is non-blocking.)
196// ---------------------------------------------------------------------------
197cv::Mat SingleDicWorkflow::loadOrCreateRoi(const cv::Mat& reference) const {
199 return cppxdic::ROIManager::loadOrCreateROI(bp, reference);
200}
201
202// ---------------------------------------------------------------------------
203// Seed — load REF_SEED_*.mat if present, else ROI centre.
204// ---------------------------------------------------------------------------
205std::vector<int> SingleDicWorkflow::loadOrCreateSeed(const cv::Mat& roi_mask) const {
208 return sp.pw;
209}
210
211// ---------------------------------------------------------------------------
212// Tracking — ONE seeded, matlab-style, frame-parallel DIC pass over the whole
213// stack (reference = frames[0] vs all current frames at once).
214//
215// This mirrors cppxdic::StepDWorkflow::runNcorrAnalysis (the production stepD
216// path) exactly, but drives the engine from in-memory cv::Mat frames (no temp
217// PNG round-trip). The previous implementation looped ncorr::NcorrSession::
218// process_frame() once per frame, which (a) re-ran the full automatic seed
219// search from scratch on every frame, (b) never used the computed seed, and
220// (c) had no cross-frame parallelism — making it ~10-20x slower than xdic.
221// Here we hand the engine the entire stack plus the user seed and let it
222// propagate the seed temporally and distribute frames across threads.
223// ---------------------------------------------------------------------------
224bool SingleDicWorkflow::track(const std::vector<cv::Mat>& frames, const cv::Mat& roi_mask,
225 const std::vector<int>& seed_pw, SingleDicResult& out) const {
226 if (frames.empty()) return false;
227
228 const int spacing = cfg_.base.step_d.spacing;
229 const int scalefactor = spacing + 1; // grid factor = spacing + 1 (matches MATLAB/xdic)
230 const int radius = cfg_.base.step_d.radius;
231 const int threads = std::max(1, cfg_.base.step_d.total_threads);
232
233 // Ensure contiguous 8-bit grayscale buffers.
234 auto as_gray = [](const cv::Mat& m) -> cv::Mat {
235 cv::Mat g = m;
236 if (g.channels() != 1) cv::cvtColor(g, g, cv::COLOR_BGR2GRAY);
237 if (g.type() != CV_8UC1) g.convertTo(g, CV_8UC1);
238 if (!g.isContinuous()) g = g.clone();
239 return g;
240 };
241
242 cv::Mat ref8 = as_gray(frames.front());
243 cv::Mat roi8;
244 if (!roi_mask.empty()) {
245 roi8 = roi_mask;
246 if (roi8.type() != CV_8UC1) roi8.convertTo(roi8, CV_8UC1);
247 if (!roi8.isContinuous()) roi8 = roi8.clone();
248 }
249
250 out.image_size = ref8.size();
251 out.roi_mask = roi8.empty() ? cv::Mat() : roi8.clone();
252 out.subset_spacing = spacing;
253 out.seed_pw = seed_pw;
254 out.frames.clear();
255
256 // Whole-stack in-memory image list: reference first, then every current
257 // frame (identical layout to the production stepD path).
258 std::vector<ncorr::Image2D> imgs;
259 imgs.reserve(frames.size());
260 imgs.emplace_back(ref8);
261 for (size_t i = 1; i < frames.size(); ++i) {
262 imgs.emplace_back(as_gray(frames[i]));
263 }
264
265 // ROI2D: explicit loaded mask, else full-frame.
266 ncorr::ROI2D roi = roi8.empty()
267 ? ncorr::ROI2D(ncorr::Array2D<bool>(ref8.rows, ref8.cols, true))
268 : cppxdic::ROIManager::matToNcorrROI(roi8);
269
270 ncorr::DIC_analysis_input dic_input(imgs, roi, scalefactor,
271 ncorr::INTERP::QUINTIC_BSPLINE_PRECOMPUTE,
272 ncorr::SUBREGION::CIRCLE, radius, threads,
273 ncorr::DIC_analysis_config::NO_UPDATE, cfg_.base.debug_mode);
274
275 // Match production: disable correlation-based reference updates so the
276 // MATLAB fixed-step seed propagation is used (avoids ROI fragmentation).
277 dic_input.update_corrcoef = cfg_.base.ncorr_cutoff_corrcoef;
278
279 // User seed (engine propagates it temporally instead of re-searching).
280 std::vector<ncorr::SeedParams> seeds;
281 if (seed_pw.size() >= 2) {
282 seeds.emplace_back(seed_pw[0], seed_pw[1]);
283 }
284
285 const bool go_parallel = cfg_.base.parallel_processing;
286 const bool exact = cfg_.base.ncorr_use_exact_matlab;
287
288 ncorr::DIC_analysis_output dic_output;
289 try {
290#ifdef CPPXDIC_HAVE_CUNCORR
291 if (cfg_.base.dic_engine != "ncorr") {
292 // cuNCorr engine (CUDA if present, else CPU — identical). Sequence mode: fixed
293 // reference (frames[0]), each frame warm-started from the previous.
295 ccfg.scalefactor = scalefactor;
296 ccfg.subregion_radius = radius;
297 ccfg.num_threads = threads;
298 if (seed_pw.size() >= 2) {
299 ccfg.seed_x = seed_pw[0];
300 ccfg.seed_y = seed_pw[1];
301 }
303 ccfg.debug = cfg_.base.debug_mode;
305 LOG_INFO << "[singledic] running cuNCorr DIC over " << (imgs.size() - 1)
306 << " frame(s), " << threads << " thread(s), seed=("
307 << (seed_pw.size() > 0 ? seed_pw[0] : 0) << ", "
308 << (seed_pw.size() > 1 ? seed_pw[1] : 0) << ")";
309 dic_output = cppxdic::run_cuncorr_dic(imgs, roi, ccfg, &cinfo);
310 LOG_INFO << "[singledic] cuNCorr backend: " << cinfo.backend
311 << (cinfo.cuda ? " (CUDA)" : " (CPU)");
312 } else
313#endif
314 {
315 LOG_INFO << "[singledic] running " << (go_parallel ? "parallel" : "sequential")
316 << (exact ? " exact-matlab" : " matlab") << " (ncorr) DIC over "
317 << (imgs.size() - 1) << " frame(s), " << threads << " thread(s), seed=("
318 << (seed_pw.size() > 0 ? seed_pw[0] : 0) << ", "
319 << (seed_pw.size() > 1 ? seed_pw[1] : 0) << ")";
320 if (go_parallel) {
321 ncorr::DIC_analysis_parallel_input pin(dic_input, seeds);
322 dic_output = exact ? ncorr::exact_matlab_DIC_analysis_parallel(pin)
323 : ncorr::matlab_DIC_analysis_parallel(pin);
324 } else {
325 dic_output = exact
326 ? ncorr::exact_matlab_DIC_analysis_sequential(dic_input, seeds, false)
327 : ncorr::matlab_DIC_analysis_sequential(dic_input, seeds, false);
328 }
329 }
330 } catch (const std::exception& e) {
331 out.message = std::string("DIC analysis failed: ") + e.what();
332 return false;
333 }
334
335 // Unpack each frame's reduced-grid Lagrangian displacement field.
336 const double nan = std::numeric_limits<double>::quiet_NaN();
337 out.frames.reserve(dic_output.disps.size());
338 for (const auto& disp : dic_output.disps) {
339 const auto& u_arr = disp.get_u().get_array();
340 const auto& v_arr = disp.get_v().get_array();
341 const auto& cc_arr = disp.get_cc().get_array();
342 const auto& mask = disp.get_roi().get_mask();
343 const int h = u_arr.height();
344 const int w = u_arr.width();
345
346 FrameResult fr;
347 fr.grid_width = w;
348 fr.grid_height = h;
349 const std::size_t n = static_cast<std::size_t>(w) * h;
350 fr.u.assign(n, nan);
351 fr.v.assign(n, nan);
352 fr.corrcoef.assign(n, nan);
353 for (int i = 0; i < h; ++i) {
354 for (int j = 0; j < w; ++j) {
355 if (i < mask.height() && j < mask.width() && mask(i, j)) {
356 const std::size_t idx = static_cast<std::size_t>(i) * w + j;
357 fr.u[idx] = u_arr(i, j);
358 fr.v[idx] = v_arr(i, j);
359 fr.corrcoef[idx] = cc_arr(i, j);
360 }
361 }
362 }
363 fr.valid = true;
364 out.frames.push_back(std::move(fr));
365 }
366
367 out.ok = true;
368 out.message = "tracking complete";
369 return true;
370}
371
372// ---------------------------------------------------------------------------
373// CSV writer — feature trajectories (seed + per-grid-point u/v/corr per frame).
374// ---------------------------------------------------------------------------
375bool SingleDicWorkflow::writeCsv(const SingleDicResult& res) const {
376 const std::string path = cfg_.ncorrStem() + ".csv";
377 std::ofstream f(path);
378 if (!f) {
379 LOG_ERROR << "[singledic] cannot write CSV: " << path;
380 return false;
381 }
382 f << "frame,grid_x,grid_y,u_px,v_px,corrcoef\n";
383 f.setf(std::ios::fmtflags(0), std::ios::floatfield);
384 for (size_t fi = 0; fi < res.frames.size(); ++fi) {
385 const FrameResult& fr = res.frames[fi];
386 for (int gy = 0; gy < fr.grid_height; ++gy) {
387 for (int gx = 0; gx < fr.grid_width; ++gx) {
388 size_t idx = static_cast<size_t>(gy) * fr.grid_width + gx;
389 if (idx >= fr.u.size()) continue;
390 double u = fr.u[idx];
391 if (std::isnan(u)) continue; // outside ROI
392 double v = (idx < fr.v.size()) ? fr.v[idx] : 0.0;
393 double c = (idx < fr.corrcoef.size()) ? fr.corrcoef[idx] : 0.0;
394 f << (fi + 1) << ',' << gx << ',' << gy << ',' << u << ',' << v << ',' << c << '\n';
395 }
396 }
397 }
398 LOG_INFO << "[singledic] wrote " << path;
399 return true;
400}
401
402namespace {
403// Write a 2D double matrix (row-major in `data`, dims rows x cols) into an open
404// MAT file as a named variable. matio expects column-major, so we transpose.
405bool put_matrix(mat_t* mat, const std::string& name, const std::vector<double>& data, size_t rows,
406 size_t cols) {
407 if (rows * cols != data.size()) return false;
408 std::vector<double> col_major(rows * cols);
409 for (size_t r = 0; r < rows; ++r)
410 for (size_t c = 0; c < cols; ++c) col_major[c * rows + r] = data[r * cols + c];
411 size_t dims[2] = {rows, cols};
412 matvar_t* var =
413 Mat_VarCreate(name.c_str(), MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, col_major.data(), 0);
414 if (!var) return false;
415 Mat_VarWrite(mat, var, MAT_COMPRESSION_NONE);
416 Mat_VarFree(var);
417 return true;
418}
419
420bool put_scalar(mat_t* mat, const std::string& name, double value) {
421 size_t dims[2] = {1, 1};
422 matvar_t* var = Mat_VarCreate(name.c_str(), MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims, &value, 0);
423 if (!var) return false;
424 Mat_VarWrite(mat, var, MAT_COMPRESSION_NONE);
425 Mat_VarFree(var);
426 return true;
427}
428} // namespace
429
430// ---------------------------------------------------------------------------
431// MAT writer — compact, MATLAB-loadable dump of the displacement grids.
432// One file per trial (ncorr<n>.mat) with:
433// spacing (scalar), seed (1x2), Nframe (scalar),
434// U_<k>, V_<k>, C_<k> (k = 1..Nframe) each a grid_height x grid_width matrix
435// This deliberately avoids the heavyweight ncorr-typed MatWriter API (which
436// expects a ncorr::DIC_analysis_output we do not carry through the session
437// path) and keeps the dependency surface small.
438// ---------------------------------------------------------------------------
439bool SingleDicWorkflow::writeMat(const SingleDicResult& res) const {
440 const std::string path = cfg_.ncorrStem() + ".mat";
441 mat_t* mat = Mat_CreateVer(path.c_str(), nullptr, MAT_FT_MAT5);
442 if (!mat) {
443 LOG_ERROR << "[singledic] cannot create MAT: " << path;
444 return false;
445 }
446
447 put_scalar(mat, "spacing", static_cast<double>(res.subset_spacing));
448 put_scalar(mat, "Nframe", static_cast<double>(res.frames.size()));
449 if (res.seed_pw.size() >= 2) {
450 std::vector<double> seed = {static_cast<double>(res.seed_pw[0]),
451 static_cast<double>(res.seed_pw[1])};
452 put_matrix(mat, "seed", seed, 1, 2);
453 }
454
455 for (size_t fi = 0; fi < res.frames.size(); ++fi) {
456 const FrameResult& fr = res.frames[fi];
457 const size_t rows = static_cast<size_t>(std::max(0, fr.grid_height));
458 const size_t cols = static_cast<size_t>(std::max(0, fr.grid_width));
459 const std::string k = std::to_string(fi + 1);
460 if (rows * cols == fr.u.size() && !fr.u.empty()) {
461 put_matrix(mat, "U_" + k, fr.u, rows, cols);
462 if (fr.v.size() == rows * cols) put_matrix(mat, "V_" + k, fr.v, rows, cols);
463 if (fr.corrcoef.size() == rows * cols)
464 put_matrix(mat, "C_" + k, fr.corrcoef, rows, cols);
465 }
466 }
467
468 Mat_Close(mat);
469 LOG_INFO << "[singledic] wrote " << path;
470 return true;
471}
472
473// ---------------------------------------------------------------------------
474// Top-level driver — mirrors process_single_trial_ncorr.m (single camera).
475// ---------------------------------------------------------------------------
477 SingleDicResult result;
478
479 LOG_INFO << "-------------------------------------------";
480 LOG_INFO << "singledic: single-camera 2D DIC analysis";
481 LOG_INFO << " subject=" << cfg_.subject << " bloc=" << cfg_.bloc << " trial=" << cfg_.trial
482 << " ref=" << cfg_.reftrial << " dir=" << cfg_.tracking_dir;
483
484 // Ensure output directories exist.
485 std::error_code ec;
486 fs::create_directories(cfg_.outputTrialDir(), ec);
487 fs::create_directories(cfg_.outputBlocDir(), ec);
488
489 // 1. Import frames.
490 std::vector<cv::Mat> raw;
491 if (!importFrames(raw)) {
492 result.message = "frame import failed";
493 return result;
494 }
495
496 // 2. Saturate.
497 std::vector<cv::Mat> satur = saturate(raw);
498
499 // 3. ROI (load-or-full) on the reference (frame 0).
500 cv::Mat roi = loadOrCreateRoi(satur.front());
501
502 // 4. Optional bandpass filtering (uses ROI for bounds).
503 std::vector<cv::Mat> proc = maybeFilter(satur, roi);
504
505 // 5. Seed (load-or-center).
506 std::vector<int> seed = loadOrCreateSeed(roi);
507 LOG_INFO << "[singledic] seed = (" << (seed.size() > 0 ? seed[0] : 0) << ", "
508 << (seed.size() > 1 ? seed[1] : 0) << ")";
509
510 // NOTE on matching-to-reference: Vik's ncorr_matching2ref_single.m maps the
511 // reference *trial's* ROI/seed onto the current trial via a one-frame DIC.
512 // When do_matching is enabled and the reference trial differs from the
513 // current trial, that mapping would be inserted here. The single-tracking
514 // semantics below are unchanged; see report for the documented gap.
515 if (cfg_.do_matching && cfg_.reftrial != cfg_.trial) {
516 LOG_INFO << "[singledic] (matching-to-reference requested; using trial ROI/seed — "
517 "cross-trial mapping is a documented gap)";
518 }
519
520 // 6. Single tracking pass.
521 if (!track(proc, roi, seed, result)) {
522 if (result.message.empty()) result.message = "tracking failed";
523 return result;
524 }
525
526 // 7. Write outputs.
527 if (cfg_.write_csv) writeCsv(result);
528 if (cfg_.write_mat) writeMat(result);
529
530 LOG_INFO << "--> singledic analysis completed";
531 return result;
532}
533
534} // namespace singledic
std::string dic_engine
Definition config.h:211
bool parallel_processing
Definition config.h:62
bool debug_mode
Definition config.h:84
StepConfig step_d
Definition config.h:138
bool ncorr_use_exact_matlab
Definition config.h:203
int cuncorr_seed_search
Definition config.h:212
double ncorr_cutoff_corrcoef
Definition config.h:181
ImageProcessor class Provides image preprocessing operations for DIC analysis.
static SeedPoint loadOrCreateSeed(const BaseParameters &params, const cv::Mat &roi_mask)
Load or create seed point If seed file doesn't exist, creates a default seed point at ROI center.
static cv::Mat loadOrCreateROI(const BaseParameters &params, const cv::Mat &reference_image)
Load or create ROI mask If ROI file doesn't exist, creates a full-image ROI Equivalent to checking an...
SingleDicResult run()
Run the full pipeline: import, saturate, (filter), ROI/seed, (optional matching), single tracking pas...
SingleDicWorkflow(const SinglediConfig &config)
Wraps a CPPxDIC Config and adds a single-camera sub-path scheme.
int limit_grayscale_high
Upper saturation clamp (satur high).
std::string reftrial
Reference trial name (shared per bloc).
bool do_matching
If true, run a matching-to-reference-trial pass before tracking (Vik ncorr_matching2ref_single....
cppxdic::BaseParameters toBaseParameters() const
Build a BaseParameters view so existing CPPxDIC helpers (ROIManager, ImageProcessor,...
int param_filt_low
Bandpass low cutoff.
std::string inputTrialDir() const
Directory for the current trial's input (subject/bloc/trial).
int idx_frame_start
First frame to analyse (1-based, MATLAB-style).
bool write_mat
Whether to write a MATLAB-compatible .mat sidecar next to outputs.
std::string bloc
Bloc / block folder name.
std::string outputBlocDir() const
Output directory shared per (subject, bloc) — holds ROI/seed reference files.
std::string ncorrStem() const
ncorr tracking output stem (no extension). Mirrors Vik: ncorr<ncorr_number>
std::string tracking_dir
Tracking direction: "forward" or "backward" (backward flips the sequence).
bool filter_im_mode
Apply bandpass+normalise filtering before DIC.
bool write_csv
Whether to write a CSV of the tracked feature trajectories.
std::string outputTrialDir() const
Output directory for the current trial (subject/bloc/trial under dic_path).
int limit_grayscale_low
Lower saturation clamp (satur low).
int idx_frame_end
Last frame (0 = "to end of sequence").
std::string trial
Current trial name (video stem).
Config base
Underlying engine configuration (owns all DIC/ncorr/viz parameters).
int param_filt_high
Bandpass high cutoff.
std::string subject
Subject folder name (e.g. "S01").
Adapter: run the cuNCorr DIC engine and package results as ncorr::DIC_analysis_output.
Lightweight, dependency-free logging facility for CPPxDIC.
#define LOG_ERROR
Definition logging.h:172
#define LOG_INFO
Definition logging.h:170
DataSerializer - Abstract base class for format-agnostic DIC data I/O.
ncorr::DIC_analysis_output run_cuncorr_dic(const std::vector< ncorr::Image2D > &imgs, const ncorr::ROI2D &roi, const CuncorrDicConfig &cfg, CuncorrDicInfo *info=nullptr)
Run cuNCorr in sequence mode (fixed reference = imgs[0], warm-start each frame) over the stack agains...
Single-camera 2D DIC step-D pipeline (no stereo, no 3D).
int spacing
Definition config.h:19
int total_threads
Definition config.h:22
int radius
Definition config.h:18
Base parameters structure Contains fundamental paths and settings for DIC analysis Equivalent to base...
Definition parameters.h:20
Tunables mirrored onto cuncorr::SessionConfig.
Definition cuncorr_dic.h:26
int seed_search
coarse ZNCC seed search radius (px)
Definition cuncorr_dic.h:32
int seed_y
manual seed row (full-image px); -1 = auto
Definition cuncorr_dic.h:31
int seed_x
manual seed column (full-image px); -1 = auto
Definition cuncorr_dic.h:30
Which backend actually ran.
Definition cuncorr_dic.h:37
bool cuda
true if a CUDA device executed the run
Definition cuncorr_dic.h:39
std::string backend
e.g. "cpu" or "cuda:0 <name>"
Definition cuncorr_dic.h:38
Seed point structure Stores seed points in both pixel world (pw) and subset world (sw) coordinates.
Definition parameters.h:108
std::vector< int > pw
Definition parameters.h:109
Full single-trial output: one reference + N tracked frames.
std::string message
Human-readable status.
Eigen::Vector3d hi
Eigen::Vector3d lo