17#ifdef CPPXDIC_HAVE_CUNCORR
21#include <opencv2/imgcodecs.hpp>
34cv::Mat makeDebugCanvas(
const cv::Mat& gray) {
39 if (gray.type() == CV_8UC1) {
40 normalized = gray.clone();
42 cv::normalize(gray, normalized, 0, 255, cv::NORM_MINMAX, CV_8U);
45 cv::cvtColor(normalized, color, cv::COLOR_GRAY2BGR);
49void overlayMask(cv::Mat& canvas,
const cv::Mat& mask,
const cv::Scalar& color) {
50 if (canvas.empty() || mask.empty()) {
54 if (mask.type() == CV_8UC1) {
55 cv::threshold(mask, mask_binary, 0, 255, cv::THRESH_BINARY);
57 mask.convertTo(mask_binary, CV_8U);
58 cv::threshold(mask_binary, mask_binary, 0, 255, cv::THRESH_BINARY);
60 cv::Mat overlay(canvas.size(), canvas.type(), color);
62 cv::addWeighted(canvas, 0.75, overlay, 0.25, 0.0, blended);
63 blended.copyTo(canvas, mask_binary);
66void drawSeed(cv::Mat& canvas,
const cppxdic::SeedPoint& seed,
const cv::Scalar& color) {
67 if (canvas.empty() || seed.
pw.size() < 2) {
70 const cv::Point center(seed.
pw[0] - 1, seed.
pw[1] - 1);
71 cv::drawMarker(canvas, center, color, cv::MARKER_CROSS, 18, 2);
72 cv::circle(canvas, center, 5, color, 2);
75void drawSeedDisplacement(cv::Mat& canvas,
78 const cv::Scalar& color) {
79 if (canvas.empty() || before.
pw.size() < 2 || after.
pw.size() < 2) {
82 const cv::Point p0(before.
pw[0] - 1, before.
pw[1] - 1);
83 const cv::Point p1(after.
pw[0] - 1, after.
pw[1] - 1);
84 cv::arrowedLine(canvas, p0, p1, color, 2, cv::LINE_AA, 0, 0.15);
87std::string sanitizeStageName(
const std::string& stage_name) {
88 std::string out = stage_name;
89 for (
char& ch : out) {
90 if (!std::isalnum(
static_cast<unsigned char>(ch))) {
103 setupStepParameters();
106std::tuple<std::string, std::vector<int>,
bool>
108 LOG_INFO <<
"-------------------------------------------";
109 LOG_INFO <<
"-------------------------------------------";
110 LOG_INFO <<
"Digital Image Correlation analysis launch";
131 if (!loadProtocol()) {
133 return {
"", {},
false};
136 std::string reftrial = determineReferenceTrial(trial);
137 LOG_INFO <<
"Reference trial: " << reftrial;
140 setupBaseParameters(trial, stereopair, reftrial);
143 int cam_first_num = 0, cam_second_num = 0;
149 const bool need_matching = plan.
match || do_track2;
152 const bool need_frames = plan.
match || plan.
track;
156 std::vector<cv::Mat> cam_first_raw, cam_second_raw;
157 LOG_INFO <<
"Reading video data... ";
158 if (!importVideoFrames(trial, stereopair, cam_first_raw, cam_second_raw)) {
159 LOG_ERROR <<
"Failed to import video frames";
160 return {
"", {},
false};
162 LOG_INFO <<
"Reading done. Frames: " << cam_first_raw.size();
166 size_t keep = cam_first_raw.size() / 2 + 5;
167 cam_first_raw.resize(keep);
168 cam_second_raw.resize(keep);
169 LOG_INFO <<
"Phase 'slide1': keeping " << keep <<
" frames";
173 std::vector<cv::Mat> cam_first_satur, cam_second_satur;
176 performSaturation(cam_first_raw, cam_second_raw, cam_first_satur, cam_second_satur);
178 LOG_INFO <<
"Skipping saturation (im_saturation_mode=false): raw frames used as-is";
179 cam_first_satur = cam_first_raw;
180 cam_second_satur = cam_second_raw;
185 cv::Mat refmask_REF, refmask_trial;
186 SeedPoint ref_seed_point, initial_seed_point_set1;
187 LOG_INFO <<
"Initializing ROI, seed, and matching REF to Trial...";
188 if (!initializeROIAndSeed(cam_first_satur, refmask_REF, refmask_trial,
189 ref_seed_point, initial_seed_point_set1)) {
190 LOG_ERROR <<
"Failed to initialize ROI and seed";
191 return {
"", {},
false};
194 LOG_INFO <<
"--> STEP: ROI loaded and formatted";
195 LOG_INFO <<
"--> STEP: SEED loaded and formatted";
196 LOG_INFO <<
"--> STEP: Matching REF to Trial loaded and formatted";
199 std::vector<cv::Mat> cam_first, cam_second;
202 LOG_INFO <<
"Applying image filtering...";
203 applyImageFiltering(cam_first_satur, cam_second_satur, refmask_trial,
204 cam_first, cam_second);
205 LOG_INFO <<
"--> STEP: filtering done";
207 LOG_INFO <<
"Skipping image filtering";
208 cam_first = cam_first_satur;
209 cam_second = cam_second_satur;
215 cv::Mat refmask_trial_matched;
218 LOG_INFO <<
"\nPerforming camera matching...";
219 if (!performMatching(cam_first_satur, cam_second_satur, refmask_trial,
220 initial_seed_point_set1, refmask_trial_matched,
221 initial_seed_point_set2)) {
223 return {
"", {},
false};
228 saveTrialInfo(trial, stereopair, cam_first_satur.size());
232 LOG_INFO <<
"\nPerforming tracking camera 1...";
233 if (!performTracking1(cam_first, refmask_trial, initial_seed_point_set1)) {
235 return {
"", {},
false};
241 LOG_INFO <<
"\nPerforming tracking camera 2...";
242 if (!performTracking2(cam_second, refmask_trial_matched, initial_seed_point_set2)) {
244 return {
"", {},
false};
250 std::vector<int> pairOrder;
252 unsigned int trial_idx = std::stoi(trial);
253 if (trial_idx > 0 && trial_idx < protocol_info_.
dircond.size()) {
261 for (
int p = 1; p <= config_.
num_pair; ++p) {
262 if (std::find(pairOrder.begin(), pairOrder.end(), p) == pairOrder.end()) {
263 pairOrder.push_back(p);
269 formatOutput(trial, stereopair, pairOrder, pairForced);
272 LOG_INFO <<
"--> STEP: Ncorr analysis completed";
274 return {base_params_.
outputPath, pairOrder, pairForced};
277void StepDWorkflow::setupBaseParameters(
const std::string& trial,
279 const std::string& reftrial) {
284 base_params_.
trial = trial;
300 for (
char ch : config_.subject_id) {
301 if (std::isdigit(ch)) {
302 subject_num = subject_num * 10 + (ch -
'0');
315 std::filesystem::create_directories(base_params_.
outputPath);
323void StepDWorkflow::setupStepParameters() {
338 step2_params_ = step1_params_;
342 step1_2_params_ = step1_params_;
347bool StepDWorkflow::loadProtocol() {
350 LOG_INFO <<
"protocol_path: " << protocol_dir;
355 if (mat_files.empty()) {
362 LOG_WARN <<
"No protocol .mat file in " << protocol_dir
364 <<
" and the default pair order";
365 protocol_info_ = ProtocolInfo{};
368 LOG_ERROR <<
"No protocol file found in: " << protocol_dir
369 <<
" (set ref_trial_id / --reftrial to run without a protocol)";
374 std::string protocol_file = mat_files[0];
375 LOG_INFO <<
"Loading protocol: " << protocol_file;
379 LOG_ERROR <<
"Failed to parse protocol file: " << protocol_file;
386 protocol_info_.
dircond.clear();
387 protocol_info_.
nfcond.clear();
388 protocol_info_.
spdcond.clear();
389 protocol_info_.
repcond.clear();
393 protocol_info_.
dircond.push_back(
"");
394 protocol_info_.
nfcond.push_back(0);
395 protocol_info_.
spdcond.push_back(0);
396 protocol_info_.
repcond.push_back(0);
399 for (
const auto& entry : protocol_data.trials) {
400 protocol_info_.
dircond.push_back(entry.direction);
401 protocol_info_.
nfcond.push_back(
static_cast<int>(entry.force));
402 protocol_info_.
spdcond.push_back(
static_cast<int>(entry.speed));
403 protocol_info_.
repcond.push_back(entry.repetition);
407 LOG_INFO <<
"Protocol loaded: " << protocol_data.
trials.size() <<
" trials";
409 for (
size_t i = 1; i < protocol_info_.
dircond.size(); ++i) {
411 <<
" nf=" << protocol_info_.
nfcond[i]
412 <<
" spd=" << protocol_info_.
spdcond[i]
413 <<
" rep=" << protocol_info_.
repcond[i];
419std::string StepDWorkflow::determineReferenceTrial(
const std::string& trial) {
422 std::ostringstream oss;
423 oss << std::setw(3) << std::setfill(
'0') << config_.
ref_trial_id;
424 LOG_INFO <<
"Reference trial (manual override): " << oss.str();
429 unsigned int trial_idx = std::stoi(trial);
431 if (trial_idx > 0 && trial_idx < protocol_info_.
nfcond.size()) {
432 int trial_nf = protocol_info_.
nfcond[trial_idx];
435 if (config_.
phase_id ==
"loading" || trial_nf == 1) {
436 for (
size_t i = 1; i < protocol_info_.
nfcond.size(); ++i) {
437 if (protocol_info_.
nfcond[i] == 1 &&
438 protocol_info_.
dircond[i] ==
"Ubnf") {
439 std::ostringstream oss;
440 oss << std::setw(3) << std::setfill(
'0') << i;
441 LOG_INFO <<
"Reference trial (nf=1, Ubnf): " << oss.str();
447 else if (config_.
phase_id ==
"slide1" && trial_nf == 5) {
448 for (
size_t i = 1; i < protocol_info_.
nfcond.size(); ++i) {
449 if (protocol_info_.
nfcond[i] == 5 &&
450 protocol_info_.
dircond[i] ==
"Ubnf") {
451 std::ostringstream oss;
452 oss << std::setw(3) << std::setfill(
'0') << i;
453 LOG_INFO <<
"Reference trial (nf=5, Ubnf): " << oss.str();
459 LOG_WARN <<
"No matching reference trial found for trial " << trial
460 <<
" (phase=" << config_.
phase_id <<
", nf=" << trial_nf <<
")";
462 LOG_WARN <<
"Trial index " << trial_idx <<
" out of protocol range";
466 LOG_INFO <<
"Reference trial (fallback): " << trial;
470bool StepDWorkflow::importVideoFrames(
const std::string& trial,
472 std::vector<cv::Mat>& cam_first_raw,
473 std::vector<cv::Mat>& cam_second_raw,
476 std::vector<std::string> cam1_frames, cam2_frames;
479 int trial_num = std::stoi(trial);
482 if (!
Utils::importVid(config_, trial_num, stereopair, cam1_frames, cam2_frames, maxFrames)) {
483 LOG_ERROR <<
"Failed to import video frames for trial " << trial
484 <<
" stereopair " << stereopair;
489 if (cam1_frames.empty() || cam2_frames.empty()) {
490 LOG_ERROR <<
"No frames imported for trial " << trial
491 <<
" stereopair " << stereopair;
496 if (cam1_frames.size() != cam2_frames.size()) {
497 LOG_ERROR <<
"Frame count mismatch: cam1=" << cam1_frames.size()
498 <<
" cam2=" << cam2_frames.size();
503 cam_first_raw.clear();
504 cam_second_raw.clear();
505 cam_first_raw.reserve(cam1_frames.size());
506 cam_second_raw.reserve(cam2_frames.size());
508 for (
size_t i = 0; i < cam1_frames.size(); ++i) {
510 cv::Mat img1 = cv::imread(cam1_frames[i], cv::IMREAD_GRAYSCALE);
512 LOG_ERROR <<
"Failed to load frame: " << cam1_frames[i];
517 cv::Mat img2 = cv::imread(cam2_frames[i], cv::IMREAD_GRAYSCALE);
519 LOG_ERROR <<
"Failed to load frame: " << cam2_frames[i];
523 cam_first_raw.push_back(img1);
524 cam_second_raw.push_back(img2);
527 LOG_INFO <<
"Loaded " << cam_first_raw.size() <<
" frames for each camera";
531void StepDWorkflow::performSaturation(
const std::vector<cv::Mat>& cam_first_raw,
532 const std::vector<cv::Mat>& cam_second_raw,
533 std::vector<cv::Mat>& cam_first_satur,
534 std::vector<cv::Mat>& cam_second_satur) {
539bool StepDWorkflow::initializeROIAndSeed(
const std::vector<cv::Mat>& cam_first_satur,
540 cv::Mat& refmask_REF,
541 cv::Mat& refmask_trial,
542 SeedPoint& ref_seed_point,
543 SeedPoint& after_disp_seed_point) {
544 std::vector<cv::Mat> reftrial_cam_first_raw;
548 if (refmask_REF.empty()) {
558 if (!std::filesystem::exists(base_params_.
matchingfile)) {
560 LOG_INFO <<
"\n--> STEP: MATCHING file computation)";
561 std::vector<cv::Mat> reftrial_cam_second_raw;
562 LOG_INFO <<
"Reading REF Trial video data...";
565 if (!importVideoFrames(base_params_.
reftrial,
567 reftrial_cam_first_raw,
568 reftrial_cam_second_raw,
570 LOG_ERROR <<
"Failed to import REF Trial video frames";
573 LOG_INFO <<
"Reading REF Trial video data done. Frames: " << reftrial_cam_first_raw.size();
575 std::ostringstream message_oss;
576 message_oss <<
"Matching Pair " << base_params_.
stereopair <<
": trial " << base_params_.
reftrial <<
"'s frame 1 TO trial " << base_params_.
trial <<
"'s frame 1";
577 if (!matchingInitialFrame(reftrial_cam_first_raw,
584 after_disp_seed_point)) {
589 LOG_INFO <<
"--> STEP: MATCHING file loaded from checkpoint (skipped computation)";
593 if (std::filesystem::exists(base_params_.
matchingfile)) {
594 auto dic_output = ncorr::DIC_analysis_output::load(base_params_.
matchingfile);
597 refmask_trial, after_disp_seed_point)) {
598 LOG_INFO <<
"--> STEP: MATCHING transformation applied";
600 if (reftrial_cam_first_raw.empty()) {
601 std::vector<cv::Mat> reftrial_cam_second_raw;
603 importVideoFrames(base_params_.
reftrial,
605 reftrial_cam_first_raw,
606 reftrial_cam_second_raw,
609 writeMatchingDebugPanel(
"ipm",
610 reftrial_cam_first_raw.empty() ? cam_first_satur[0] : reftrial_cam_first_raw[0],
615 after_disp_seed_point,
620 LOG_WARN <<
"MATCHING file has no displacements";
624 LOG_WARN <<
"Something went wrong for the MATCHING";
628bool StepDWorkflow::matchingInitialFrame(
const std::vector<cv::Mat>& cam_ref,
629 const std::vector<cv::Mat>& cam_cur,
630 const cv::Mat& refmask_ref,
631 const std::string ncorr_matching_path,
632 const std::string message,
633 const SeedPoint& ref_seed_point,
634 cv::Mat& refmask_cur_matched,
635 SeedPoint& after_disp_seed_point) {
639 std::vector<cv::Mat> cur_imgs;
641 cur_imgs.push_back(cam_cur[0]);
642 cur_imgs.push_back(cam_ref[0]);
646 static_cast<int>(ref_seed_point.pw[0]),
647 static_cast<int>(ref_seed_point.pw[1])
657 SeedPoint matching_seed = ref_seed_point;
658 ncorr::DIC_analysis_output dic_output;
660 dic_output = runNcorrAnalysis(cam_ref[0], cur_imgs, refmask_ref,
661 matching_seed, step1_2_params_,
662 ncorr_matching_path,
false,
true);
663 }
catch (
const std::runtime_error& e) {
664 const std::string what = e.what();
665 if (what.find(
"could not seed") == std::string::npos)
throw;
667 matching_seed = SeedPoint(c[0], c[1]);
669 LOG_WARN <<
"Matching seed (" << ref_seed_point.pw[0] <<
"," << ref_seed_point.pw[1]
670 <<
") did not converge in the other camera (" << what
671 <<
"); retrying from the ROI centre of mass (" << matching_seed.pw[0] <<
","
672 << matching_seed.pw[1] <<
")";
673 step1_2_params_.
initial_seed = {matching_seed.pw[0], matching_seed.pw[1]};
674 dic_output = runNcorrAnalysis(cam_ref[0], cur_imgs, refmask_ref,
675 matching_seed, step1_2_params_,
676 ncorr_matching_path,
false,
true);
680 refmask_cur_matched, after_disp_seed_point)) {
681 LOG_ERROR <<
"No displacement output from matching";
685 LOG_INFO <<
"--> STEP: " << message <<
" done";
689bool StepDWorkflow::performMatching(
const std::vector<cv::Mat>& cam_first_satur,
690 const std::vector<cv::Mat>& cam_second_satur,
692 SeedPoint& ref_seed_point,
693 cv::Mat& after_disp_mask,
694 SeedPoint& after_disp_seed_point) {
695 LOG_INFO <<
"MATCHING STEP - RUN #1";
702 std::string ncorr_matching_path = base_params_.
outputPath +
"/ncorr" +
703 std::to_string(cam_1) + std::to_string(cam_2) +
".bin";
706 if (!std::filesystem::exists(ncorr_matching_path)) {
707 LOG_INFO <<
"\n--> STEP: MATCHING file computation)";
709 std::ostringstream message_oss;
710 message_oss <<
"Matching Inside Camera Pair (1-2) : Cam1's frame 1 VS cam2's frame 1";
712 matchingInitialFrame(cam_first_satur,
719 after_disp_seed_point);
721 LOG_INFO <<
"Checkpoint found: " << ncorr_matching_path;
722 LOG_INFO <<
"--> STEP: MATCHING loaded from checkpoint (skipped computation)";
726 if (std::filesystem::exists(ncorr_matching_path)) {
727 auto dic_output = ncorr::DIC_analysis_output::load(ncorr_matching_path);
730 after_disp_mask, after_disp_seed_point)) {
731 LOG_INFO <<
"--> STEP: MATCHING transformation applied";
733 writeMatchingDebugPanel(
"icm",
739 after_disp_seed_point,
744 LOG_WARN <<
"Matching file has no displacements";
748 LOG_WARN <<
"Something went wrong with the matching";
752bool StepDWorkflow::performTracking(
const int tracking_number,
753 const std::vector<cv::Mat>& cam_frames,
754 const cv::Mat& refmask,
755 const SeedPoint& initial_seed_point) {
760 auto cam_number = tracking_number == 1 ? cam_1 : cam_2;
762 LOG_INFO <<
"\nTRACKING STEP " << tracking_number;
764 std::string output_path = base_params_.
outputPath +
"/ncorr" + std::to_string(cam_number) +
".bin";
767 if (std::filesystem::exists(output_path)) {
768 LOG_INFO <<
"Checkpoint found: " << output_path;
769 LOG_INFO <<
"--> STEP: Ncorr " << cam_number <<
" loaded from checkpoint (skipped computation)";
773 auto step_params_ = step2_params_;
774 if (tracking_number == 1) {
775 step_params_ = step1_params_;
777 step_params_ = step2_params_;
780 static_cast<int>(initial_seed_point.pw[0]),
781 static_cast<int>(initial_seed_point.pw[1])
784 std::vector<cv::Mat> cur_frames = cam_frames;
785 if (tracking_number == 2 && !cur_frames.empty()) {
786 cur_frames.erase(cur_frames.begin());
788 if (cur_frames.empty()) {
789 LOG_ERROR <<
"No current frames available for tracking " << tracking_number;
800 runNcorrAnalysis(cam_frames[0], cur_frames, refmask,
801 initial_seed_point, step_params_,
804 LOG_INFO <<
"--> STEP: Ncorr " << cam_number <<
" done and saved to " << output_path;
808bool StepDWorkflow::performTracking1(
const std::vector<cv::Mat>& cam_first,
809 const cv::Mat& refmask_trial,
810 const SeedPoint& initial_seed_point_set1) {
811 LOG_DEBUG <<
"Performing tracking camera 1..." << initial_seed_point_set1.pw[0] <<
"," << initial_seed_point_set1.pw[1];
812 return performTracking(1, cam_first, refmask_trial, initial_seed_point_set1);
815bool StepDWorkflow::performTracking2(
const std::vector<cv::Mat>& cam_second,
816 const cv::Mat& refmask_trial_matched,
817 const SeedPoint& initial_seed_point_set2) {
818 LOG_DEBUG <<
"Performing tracking camera 2..." << initial_seed_point_set2.pw[0] <<
"," << initial_seed_point_set2.pw[1];
819 return performTracking(2, cam_second, refmask_trial_matched, initial_seed_point_set2);
822void StepDWorkflow::applyImageFiltering(
const std::vector<cv::Mat>& cam_first_satur,
823 const std::vector<cv::Mat>& cam_second_satur,
824 const cv::Mat& refmask_trial,
825 std::vector<cv::Mat>& cam_first,
826 std::vector<cv::Mat>& cam_second) {
828 std::vector<int> param_filt = {25, 300};
831 cam_first_satur, refmask_trial, param_filt,
nullptr);
835 cam_second_satur, refmask_trial, param_filt, &gs_bounds);
837 cam_first = filtered_first;
838 cam_second = filtered_second;
841void StepDWorkflow::saveTrialInfo(
const std::string& trial,
int stereopair,
int num_frames) {
843 std::string filename = base_params_.
outputPath +
"/dic_info_data_target_pair" +
844 std::to_string(stereopair) +
".mat";
848 std::vector<int> idxframe;
850 i <= base_params_.
idxend_set && i < num_frames;
851 i += base_params_.
jump) {
852 idxframe.push_back(i);
858void StepDWorkflow::formatOutput(
const std::string& trial,
860 const std::vector<int>& pairOrder,
866 pairOrder, pairForced);
871 const std::string& output_path,
874 const std::vector<int>& pairOrder,
876 LOG_INFO <<
"Formatting output files (step2_dic_finish equivalent)...";
879 std::string ncorr1_bin = output_path +
"/ncorr" + std::to_string(cam_1) +
".bin";
880 std::string ncorr2_bin = output_path +
"/ncorr" + std::to_string(cam_2) +
".bin";
881 std::string ncorr12_bin = output_path +
"/ncorr" + std::to_string(cam_1) + std::to_string(cam_2) +
".bin";
883 if (!std::filesystem::exists(ncorr1_bin) || !std::filesystem::exists(ncorr2_bin) || !std::filesystem::exists(ncorr12_bin)) {
884 LOG_WARN <<
"cached ncorr result files not found";
885 if (!std::filesystem::exists(ncorr1_bin))
LOG_WARN <<
" Missing: " << ncorr1_bin;
886 if (!std::filesystem::exists(ncorr2_bin))
LOG_WARN <<
" Missing: " << ncorr2_bin;
887 if (!std::filesystem::exists(ncorr12_bin))
LOG_WARN <<
" Missing: " << ncorr12_bin;
892 std::string output_file = output_path +
"/myDIC2DpairResults_C_" +
893 std::to_string(cam_1) +
"_C_" + std::to_string(cam_2) + d_serializer->extension();
895 if (std::filesystem::exists(output_file)) {
896 LOG_INFO <<
"Checkpoint found: " << output_file;
900 LOG_INFO <<
" Loading cached DIC outputs...";
901 ncorr::DIC_analysis_output dic1 = ncorr::DIC_analysis_output::load(ncorr1_bin);
902 ncorr::DIC_analysis_output dic2 = ncorr::DIC_analysis_output::load(ncorr2_bin);
903 ncorr::DIC_analysis_output dic12 = ncorr::DIC_analysis_output::load(ncorr12_bin);
905 if (dic1.disps.empty() || dic2.disps.empty() || dic12.disps.empty()) {
913 LOG_INFO <<
" Applying replacebadcorr (MATLAB step2_dic_finish)...";
919 const size_t n_frames_cam1 = dic1.disps.size();
920 const size_t n_frames_cam2 = dic2.disps.size();
921 const int Factor = dic1.disps[0].get_scalefactor();
922 const double nan = std::numeric_limits<double>::quiet_NaN();
924 LOG_INFO <<
" Processing " << n_frames_cam1 <<
" cam1 frames, "
925 << n_frames_cam2 <<
" cam2 frames, Factor=" << Factor;
926 if (n_frames_cam2 + 1 != n_frames_cam1) {
927 LOG_WARN <<
"cam2 tracking count differs from MATLAB expectation "
928 <<
"(expected " << (n_frames_cam1 - 1) <<
", got " << n_frames_cam2 <<
")";
934 results.
nImages =
static_cast<int>(n_frames_cam1);
958 const auto& roi1 = dic1.disps[0].get_roi();
959 const auto& roi_mask = roi1.get_mask();
960 results.
ROImask = cv::Mat(roi_mask.height(), roi_mask.width(), CV_8U);
961 std::vector<std::pair<int, int>> roi_coords;
962 std::vector<cv::Point2d> Pref;
964 for (
int y = 0; y < roi_mask.height(); ++y) {
965 for (
int x = 0; x < roi_mask.width(); ++x) {
966 results.
ROImask.at<uint8_t>(y, x) = roi_mask(y, x) ? 255 : 0;
967 if (roi_mask(y, x)) {
968 roi_coords.emplace_back(y, x);
969 Pref.emplace_back(
static_cast<double>(x * Factor + 1),
970 static_cast<double>(y * Factor + 1));
974 LOG_INFO <<
" Reference points: " << Pref.size();
976 const size_t n_total_frames = n_frames_cam1 + 1 + n_frames_cam2;
977 results.
Points.resize(n_total_frames);
980 auto fillDirectFrame = [&](
const ncorr::Disp2D& disp,
size_t out_idx) {
981 const auto& u_array = disp.get_u().get_array();
982 const auto& v_array = disp.get_v().get_array();
983 const auto& cc_array = disp.get_cc().get_array();
990 const auto& valid_mask = disp.get_roi().get_mask();
993 pts2d.
x.reserve(Pref.size());
994 pts2d.
y.reserve(Pref.size());
995 std::vector<double> corrcoef;
996 corrcoef.reserve(Pref.size());
998 for (
size_t idx = 0; idx < roi_coords.size(); ++idx) {
999 const auto [y, x] = roi_coords[idx];
1000 const bool valid = valid_mask(y, x);
1001 double u = valid ? u_array(y, x) : nan;
1002 double v = valid ? v_array(y, x) : nan;
1003 double cc = valid ? cc_array(y, x) : nan;
1005 pts2d.
x.push_back(std::isnan(u) ? nan : (Pref[idx].x + u));
1006 pts2d.
y.push_back(std::isnan(v) ? nan : (Pref[idx].y + v));
1007 corrcoef.push_back(cc);
1010 results.
Points[out_idx] = std::move(pts2d);
1011 results.
CorCoeffVec[out_idx] = std::move(corrcoef);
1014 LOG_INFO <<
" Processing cam1 frames...";
1015 for (
size_t ii = 0; ii < n_frames_cam1; ++ii) {
1016 fillDirectFrame(dic1.disps[ii], ii);
1019 LOG_INFO <<
" Processing inter-camera matching frame...";
1020 fillDirectFrame(dic12.disps.front(), n_frames_cam1);
1022 const auto& disp12_ref = dic12.disps.front();
1023 const auto& u12_full = disp12_ref.get_u().get_array();
1024 const auto& v12_full = disp12_ref.get_v().get_array();
1025 const auto& valid12 = disp12_ref.get_roi().get_mask();
1027 LOG_INFO <<
" Processing cam2 frames (mapped through matching)...";
1028 for (
size_t ii = 0; ii < n_frames_cam2; ++ii) {
1029 const auto& disp2 = dic2.disps[ii];
1030 const auto& u2_array = disp2.get_u().get_array();
1031 const auto& v2_array = disp2.get_v().get_array();
1032 const auto& cc2_array = disp2.get_cc().get_array();
1036 const auto& valid2 = disp2.get_roi().get_mask();
1037 const int H2 = disp2.get_u().data_height();
1038 const int W2 = disp2.get_u().data_width();
1040 auto interpolateWeighted = [&](
const auto& arr,
double rx,
double ry) ->
double {
1041 const int x0 =
static_cast<int>(std::floor(rx));
1042 const int y0 =
static_cast<int>(std::floor(ry));
1043 const int x1 = x0 + 1;
1044 const int y1 = y0 + 1;
1046 double numerator = 0.0;
1047 double denominator = 0.0;
1048 const std::array<std::pair<int, int>, 4> samples = {{
1049 {x0, y0}, {x1, y0}, {x0, y1}, {x1, y1}
1052 for (
const auto& [sx, sy] : samples) {
1053 if (sx < 0 || sy < 0 || sx >= W2 || sy >= H2) {
1056 if (!valid2(sy, sx)) {
1059 const double value = arr(sy, sx);
1060 const double dx = rx -
static_cast<double>(sx);
1061 const double dy = ry -
static_cast<double>(sy);
1062 const double dist = std::sqrt(dx * dx + dy * dy);
1063 const double weight = dist < 1e-12 ? 1e12 : 1.0 / dist;
1064 numerator += value * weight;
1065 denominator += weight;
1068 if (denominator > 0.0) {
1069 return numerator / denominator;
1072 const int cx = std::clamp(
static_cast<int>(std::round(rx)), 0, W2 - 1);
1073 const int cy = std::clamp(
static_cast<int>(std::round(ry)), 0, H2 - 1);
1074 return valid2(cy, cx) ? arr(cy, cx) : nan;
1078 pts2d.
x.reserve(Pref.size());
1079 pts2d.
y.reserve(Pref.size());
1080 std::vector<double> corrcoef;
1081 corrcoef.reserve(Pref.size());
1083 for (
size_t idx = 0; idx < roi_coords.size(); ++idx) {
1084 const auto [y, x] = roi_coords[idx];
1085 const double u12 = u12_full(y, x);
1086 const double v12 = v12_full(y, x);
1087 corrcoef.push_back(valid2(y, x) ? cc2_array(y, x) : nan);
1089 if (!valid12(y, x)) {
1090 pts2d.
x.push_back(nan);
1091 pts2d.
y.push_back(nan);
1095 const double cam2_rx =
static_cast<double>(x) + u12 /
static_cast<double>(Factor);
1096 const double cam2_ry =
static_cast<double>(y) + v12 /
static_cast<double>(Factor);
1097 const double u2_mapped = interpolateWeighted(u2_array, cam2_rx, cam2_ry);
1098 const double v2_mapped = interpolateWeighted(v2_array, cam2_rx, cam2_ry);
1100 if (std::isnan(u2_mapped) || std::isnan(v2_mapped)) {
1101 pts2d.
x.push_back(nan);
1102 pts2d.
y.push_back(nan);
1106 pts2d.
x.push_back(Pref[idx].x + u12 + u2_mapped);
1107 pts2d.
y.push_back(Pref[idx].y + v12 + v2_mapped);
1110 results.
Points[n_frames_cam1 + 1 + ii] = std::move(pts2d);
1111 results.
CorCoeffVec[n_frames_cam1 + 1 + ii] = std::move(corrcoef);
1114 LOG_INFO <<
" Creating Delaunay triangulation...";
1115 std::vector<cv::Point2f> pref_float;
1116 pref_float.reserve(Pref.size());
1117 for (
const auto& p : Pref) {
1118 pref_float.emplace_back(
static_cast<float>(p.x),
static_cast<float>(p.y));
1121 double max_edge = 1.1 * std::sqrt(2.0) * Factor;
1129 if (d_serializer->saveDIC2DPairResults(output_file, results)) {
1130 LOG_INFO <<
"Output formatting complete: " << output_file;
1132 LOG_ERROR <<
"Failed to write DIC2DPairResults";
1138 const ncorr::DIC_analysis_output& dic_output,
1139 cv::Mat& output_mask,
1141 if (dic_output.disps.empty()) {
1145 const auto& disp = dic_output.disps.front();
1146 const auto& u_data = disp.get_u();
1147 const auto& v_data = disp.get_v();
1148 const auto& u_array = u_data.get_array();
1149 const auto& v_array = v_data.get_array();
1150 const double scale =
static_cast<double>(disp.get_scalefactor());
1152 cv::Mat U_mapped(u_data.data_height(), u_data.data_width(), CV_64F);
1153 cv::Mat V_mapped(v_data.data_height(), v_data.data_width(), CV_64F);
1154 for (
size_t y = 0; y < static_cast<size_t>(u_data.data_height()); ++y) {
1155 for (
size_t x = 0; x < static_cast<size_t>(u_data.data_width()); ++x) {
1156 U_mapped.at<
double>(
static_cast<int>(y),
static_cast<int>(x)) = u_array(y, x) / scale;
1157 V_mapped.at<
double>(
static_cast<int>(y),
static_cast<int>(x)) = v_array(y, x) / scale;
1166 const auto& valid_mask = disp.get_roi().get_mask();
1167 const int gw =
static_cast<int>(u_data.data_width());
1168 const int gh =
static_cast<int>(u_data.data_height());
1169 auto grid_valid = [&](
int gx,
int gy) {
1170 if (gx < 0 || gy < 0 || gx >= gw || gy >= gh)
return false;
1171 return static_cast<bool>(valid_mask(gy, gx)) && std::isfinite(u_array(gy, gx)) &&
1172 std::isfinite(v_array(gy, gx));
1175 if (!grid_valid(input_seed.
sw[0], input_seed.
sw[1])) {
1176 double sx = 0.0, sy = 0.0;
1178 for (
int gy = 0; gy < gh; ++gy)
1179 for (
int gx = 0; gx < gw; ++gx)
1180 if (grid_valid(gx, gy)) { sx += gx; sy += gy; ++n; }
1182 LOG_ERROR <<
"Displacement field has no valid point: cannot transport the seed";
1185 const double cx = sx / n, cy = sy / n;
1186 int bx = -1, by = -1;
1187 double best = 1e300;
1188 for (
int gy = 0; gy < gh; ++gy)
1189 for (
int gx = 0; gx < gw; ++gx)
1190 if (grid_valid(gx, gy)) {
1191 const double d = (gx - cx) * (gx - cx) + (gy - cy) * (gy - cy);
1192 if (d < best) { best = d; bx = gx; by = gy; }
1194 seed_used.
sw = {bx, by};
1196 LOG_WARN <<
"Seed (" << input_seed.
pw[0] <<
"," << input_seed.
pw[1]
1197 <<
") lies where the matching field is invalid; transporting the seed from the "
1198 "valid grid point nearest the field centroid instead: ("
1199 << seed_used.
pw[0] <<
"," << seed_used.
pw[1] <<
")";
1205 output_mask = input_mask.clone();
1208 ncorr::ROI2D roi_updated = ncorr::update(roi_current, disp, ncorr::INTERP::CUBIC_KEYS, ncorr::ROI_UPDATE_MODE::SKIP_INVALID);
1210 }
catch (
const std::exception& e) {
1211 LOG_WARN <<
"failed to update ROI through displacement field: "
1218void StepDWorkflow::writeNcorrMatSidecar(
const std::string& output_path,
1219 const cv::Mat& ref_img,
1220 const std::vector<cv::Mat>& cur_imgs,
1221 const cv::Mat& roi_mask,
1223 const ncorr::DIC_analysis_output& dic_output)
const {
1224 if (cur_imgs.empty()) {
1228 const std::filesystem::path mat_path = std::filesystem::path(output_path).replace_extension(
".mat");
1229 std::vector<cv::Mat> cur_rois(cur_imgs.size(), roi_mask.clone());
1230 std::vector<ncorr::DIC_analysis_output> dic_outputs = {dic_output};
1232 std::map<std::string, double> dispinfo = {
1238 {
"radius",
static_cast<double>(step_params.
radius)},
1239 {
"spacing",
static_cast<double>(step_params.
spacing)},
1240 {
"subsettrunc", 0.0},
1241 {
"total_threads",
static_cast<double>(step_params.
total_threads)}
1251 LOG_WARN <<
"failed to write ncorr MAT sidecar: "
1256void StepDWorkflow::writeMatchingDebugPanel(
const std::string& stage_name,
1257 const cv::Mat& ref_img,
1258 const cv::Mat& cur_img,
1259 const cv::Mat& mask_before,
1260 const cv::Mat& mask_after,
1261 const SeedPoint& seed_before,
1262 const SeedPoint& seed_after,
1263 const ncorr::DIC_analysis_output& dic_output)
const {
1264 if (!config_.
debug_mode || ref_img.empty() || cur_img.empty()) {
1268 cv::Mat ref_overlay = makeDebugCanvas(ref_img);
1269 cv::Mat cur_overlay = makeDebugCanvas(cur_img);
1270 cv::Mat before_mask_view = makeDebugCanvas(mask_before);
1271 cv::Mat after_mask_view = makeDebugCanvas(mask_after);
1273 overlayMask(ref_overlay, mask_before, cv::Scalar(60, 200, 255));
1274 overlayMask(cur_overlay, mask_after, cv::Scalar(60, 255, 120));
1275 overlayMask(before_mask_view, mask_before, cv::Scalar(0, 0, 255));
1276 overlayMask(after_mask_view, mask_after, cv::Scalar(0, 255, 0));
1278 drawSeed(ref_overlay, seed_before, cv::Scalar(0, 0, 255));
1279 drawSeed(cur_overlay, seed_after, cv::Scalar(0, 255, 255));
1280 drawSeedDisplacement(ref_overlay, seed_before, seed_after, cv::Scalar(255, 255, 0));
1282 const cv::Size panel_size(std::max(ref_overlay.cols, cur_overlay.cols),
1283 std::max(ref_overlay.rows, cur_overlay.rows));
1284 cv::resize(ref_overlay, ref_overlay, panel_size, 0, 0, cv::INTER_NEAREST);
1285 cv::resize(cur_overlay, cur_overlay, panel_size, 0, 0, cv::INTER_NEAREST);
1286 cv::resize(before_mask_view, before_mask_view, panel_size, 0, 0, cv::INTER_NEAREST);
1287 cv::resize(after_mask_view, after_mask_view, panel_size, 0, 0, cv::INTER_NEAREST);
1291 cv::hconcat(ref_overlay, cur_overlay, top_row);
1292 cv::hconcat(before_mask_view, after_mask_view, bottom_row);
1295 cv::vconcat(top_row, bottom_row, panel);
1297 const cv::Point text_origin(18, 30);
1298 const cv::Scalar text_color(255, 255, 255);
1299 cv::putText(panel,
"Stage: " + stage_name, text_origin,
1300 cv::FONT_HERSHEY_SIMPLEX, 0.8, text_color, 2, cv::LINE_AA);
1302 const double dx =
static_cast<double>(seed_after.pw[0] - seed_before.pw[0]);
1303 const double dy =
static_cast<double>(seed_after.pw[1] - seed_before.pw[1]);
1304 std::ostringstream disp_text;
1305 disp_text <<
"Seed disp: (" << dx <<
", " << dy <<
")";
1306 cv::putText(panel, disp_text.str(), cv::Point(18, 62),
1307 cv::FONT_HERSHEY_SIMPLEX, 0.7, text_color, 2, cv::LINE_AA);
1309 if (!dic_output.disps.empty()) {
1310 const auto& cc_array = dic_output.disps.front().get_cc().get_array();
1311 const int seed_x = std::clamp(seed_before.sw[0], 0,
static_cast<int>(cc_array.width()) - 1);
1312 const int seed_y = std::clamp(seed_before.sw[1], 0,
static_cast<int>(cc_array.height()) - 1);
1313 std::ostringstream cc_text;
1314 cc_text <<
"Seed corrcoef: " << cc_array(seed_y, seed_x);
1315 cv::putText(panel, cc_text.str(), cv::Point(18, 94),
1316 cv::FONT_HERSHEY_SIMPLEX, 0.7, text_color, 2, cv::LINE_AA);
1319 const std::filesystem::path debug_dir = std::filesystem::path(base_params_.
outputPath) /
"debug_stepd";
1320 std::filesystem::create_directories(debug_dir);
1321 const std::filesystem::path debug_file = debug_dir / (sanitizeStageName(stage_name) +
".png");
1322 cv::imwrite(debug_file.string(), panel);
1325ncorr::DIC_analysis_output StepDWorkflow::runNcorrAnalysis(
1326 const cv::Mat& ref_img,
1327 const std::vector<cv::Mat>& cur_imgs,
1328 const cv::Mat& roi_mask,
1329 const SeedPoint& seed_point,
1330 const StepParameters& step_params,
1331 const std::string& output_path,
1332 const bool go_parallel,
1333 const bool use_no_update) {
1335 LOG_INFO <<
"Running ncorr DIC analysis...";
1336 LOG_DEBUG <<
" Radius: " << step_params.radius <<
", Spacing: " << step_params.spacing;
1337 LOG_DEBUG <<
" Scalefactor: " << (step_params.spacing + 1) <<
" (spacing + 1)";
1338 LOG_DEBUG <<
" Seed: (" << seed_point.pw[0] <<
", " << seed_point.pw[1] <<
")";
1342 std::vector<ncorr::Image2D> ncorr_images;
1343 std::vector<std::string> temp_image_paths;
1346 std::string temp_dir = base_params_.
outputPath +
"/tmp_ncorr_images";
1347 std::filesystem::create_directories(temp_dir);
1350 std::string ref_path = temp_dir +
"/ref.png";
1351 cv::imwrite(ref_path, ref_img);
1352 ncorr_images.emplace_back(ref_path);
1353 temp_image_paths.push_back(ref_path);
1356 for (
unsigned int i = 0; i < cur_imgs.size(); ++i) {
1357 std::ostringstream oss;
1358 oss << temp_dir <<
"/cur_" << std::setw(4) << std::setfill(
'0') << i <<
".png";
1359 std::string cur_path = oss.str();
1360 cv::imwrite(cur_path, cur_imgs[i]);
1361 ncorr_images.emplace_back(cur_path);
1362 temp_image_paths.push_back(cur_path);
1368 cv::imwrite(temp_dir +
"/roi_mask.png", get_cv_img(roi.get_mask(), 0, 255));
1372 int scalefactor = step_params.spacing + 1;
1374 ncorr::DIC_analysis_input dic_input(
1378 ncorr::INTERP::QUINTIC_BSPLINE_PRECOMPUTE,
1379 ncorr::SUBREGION::CIRCLE,
1381 step_params.total_threads,
1382 use_no_update ? ncorr::DIC_analysis_config::NO_UPDATE : ncorr::DIC_analysis_config::KEEP_MOST_POINTS,
1398 const bool is_matching_call = cur_imgs.size() <= 2;
1399 const double seed_cutoff_corrcoef = is_matching_call
1402 LOG_DEBUG <<
" Seed gate: cutoff_max_corrcoef=" << seed_cutoff_corrcoef
1403 << (is_matching_call ?
" (matching)" :
" (tracking)")
1407 ncorr::DIC_analysis_output dic_output_raw;
1409#ifdef CPPXDIC_HAVE_CUNCORR
1418 if (config_.
dic_engine !=
"ncorr" && cur_imgs.size() > 2) {
1426 ccfg.
seed_x = seed_point.pw[0];
1427 ccfg.
seed_y = seed_point.pw[1];
1431 LOG_INFO <<
" Using cuNCorr DIC engine...";
1437 LOG_INFO <<
" Using parallel DIC processing"
1442 std::vector<ncorr::SeedParams> seeds;
1443 seeds.push_back(ncorr::SeedParams(seed_point.pw[0], seed_point.pw[1]));
1446 ncorr::DIC_analysis_parallel_input dic_parallel_input(dic_input, seeds);
1450 dic_parallel_input.cutoff_max_corrcoef = seed_cutoff_corrcoef;
1452 LOG_INFO <<
" Fixed-step reference updates: every "
1458 ? ncorr::exact_matlab_DIC_analysis_parallel(dic_parallel_input)
1459 : ncorr::matlab_DIC_analysis_parallel(dic_parallel_input);
1461 LOG_INFO <<
" Using Matlab-style sequential DIC processing"
1464 ncorr::DIC_analysis_parallel_input dic_sequential_input(
1465 dic_input, {ncorr::SeedParams(seed_point.pw[0], seed_point.pw[1])},
1469 dic_sequential_input.cutoff_max_corrcoef = seed_cutoff_corrcoef;
1471 ? ncorr::exact_matlab_DIC_analysis_sequential(dic_sequential_input)
1472 : ncorr::matlab_DIC_analysis_sequential(dic_sequential_input);
1476 LOG_INFO <<
"Post-processing displacements...";
1485 ncorr::DIC_analysis_output dic_eulerian_pixels = ncorr::change_perspective_with_inversion(
1488 : ncorr::INTERP::CUBIC_KEYS);
1491 ncorr::DIC_analysis_output dic_lagrangian = ncorr::set_units(dic_output_raw,
"mm", config_.
units_per_pixel);
1492 ncorr::DIC_analysis_output dic_eulerian = ncorr::set_units(dic_eulerian_pixels,
"mm", config_.
units_per_pixel);
1494 LOG_INFO <<
" Created both Lagrangian and Eulerian perspectives";
1498 std::string video_dir = std::filesystem::path(output_path).parent_path().string() +
"/debug_video/";
1499 std::filesystem::create_directories(video_dir);
1500 std::string base_name = std::filesystem::path(output_path).stem().string();
1502 double fps =
static_cast<double>(config_.
video_fps);
1504 LOG_INFO <<
" Saving debug DIC videos to " << video_dir;
1506 ncorr::save_DIC_video(video_dir + base_name +
"_v_eulerian.avi",
1507 dic_input, dic_eulerian, ncorr::DISP::V, alpha, fps);
1508 ncorr::save_DIC_video(video_dir + base_name +
"_u_eulerian.avi",
1509 dic_input, dic_eulerian, ncorr::DISP::U, alpha, fps);
1510 LOG_INFO <<
" ✓ Debug DIC videos saved";
1511 }
catch (
const std::exception& e) {
1512 LOG_WARN <<
"Failed to save debug videos: " << e.what();
1521 save(dic_output_raw, output_path);
1522 writeNcorrMatSidecar(output_path, ref_img, cur_imgs, roi_mask, step_params, dic_output_raw);
1523 LOG_INFO <<
"DIC analysis saved: " << output_path;
1525 return dic_output_raw;
bool ncorr_perspective_interp
bool ncorr_seeds_are_optimized
double ncorr_cutoff_max_corrcoef
bool ncorr_use_exact_matlab
double ncorr_cutoff_max_diffnorm
bool step_d_replacebadcorr
double ncorr_matching_cutoff_max_corrcoef
double ncorr_cutoff_corrcoef
static std::string buildPath(const Config &config, bool with_data_or_dic_path=false, bool with_rawdata=false, bool with_speckles=false, bool with_material=false, bool with_video=false, bool with_protocol=false, bool with_calib=false)
Build the path based on activated parameters and return the proper string.
static std::vector< std::string > findFiles(const std::string &directory, const std::string &pattern)
static std::string buildProtocolDir(const Config &config, bool with_data_or_dic_path=false, bool with_rawdata=false, bool with_speckles=false, bool with_material=false)
Build protocol path.
static std::string buildRoiFilePath(const cppxdic::BaseParameters ¶meters, std::string reftrial, int stereopair, const std::string &extension=".mat")
Build the roi file path.
static bool importVid(const Config &config, int trial, int stereopair, std::vector< std::string > &cam1Frames, std::vector< std::string > &cam2Frames, int maxFrames=0)
static std::string buildSeedFilePath(const cppxdic::BaseParameters ¶meters, std::string reftrial, int stereopair, const std::string &extension=".mat")
Build the seed file path.
static std::string buildMatchingFilePath(const cppxdic::BaseParameters ¶meters, std::string reftrial, int stereopair, const std::string &extension=".bin")
Build the matching file path.
static void getCamerasForPair(int stereopair, int &cam_first, int &cam_second)
Get camera numbers for a given stereopair using the registered table (setCameraPairs).
static std::unique_ptr< DataSerializer > create(const std::string &format)
Create a serializer for the given format string.
static std::vector< int > flipOrientation(const std::vector< int > &faces)
Flip triangle orientation Matches MATLAB: F = F(:, [1 3 2])
static std::vector< int > filterByEdgeLength(const std::vector< int > &faces, const std::vector< cv::Point2f > &vertices, double max_edge_threshold)
Filter irregular triangles by edge length Matches MATLAB: F(EdgeLengthsMax > threshold,...
static std::vector< int > compute(const std::vector< cv::Point2f > &points)
Compute Delaunay triangulation of 2D points Matches MATLAB: DT = delaunayTriangulation(P)
static std::vector< cv::Mat > saturate(const std::vector< cv::Mat > &input, int level)
Saturate image intensities Equivalent to satur.m in Matlab.
static std::pair< std::vector< cv::Mat >, std::pair< double, double > > filterLikeBen(const std::vector< cv::Mat > &input, const cv::Mat &mask, const std::vector< int > ¶m_filt, const std::pair< double, double > *gs_boundaries=nullptr)
Filter images like Ben's method Equivalent to filter_like_ben.m in Matlab.
static bool loadProtocol(const std::string &protocol_path, ProtocolFileData &protocol)
static bool writeMultiFrameNcorrFile(const std::string &filename, const cv::Mat &ref_img, const std::vector< cv::Mat > &cur_imgs, const cv::Mat &ref_roi, const std::vector< cv::Mat > &cur_rois, const std::vector< ncorr::DIC_analysis_output > &dic_outputs, const std::map< std::string, double > &dispinfo, const std::string &type_str="load", const std::string &ref_name="reference")
Write multi-frame ncorr files (ncorr1.mat, ncorr2.mat, etc.) For tracking DIC with multiple current f...
static bool writeTrialInfoFile(const std::string &filename, double fps, const std::vector< int > &frame_indices)
Write dic_info_data_target_pairX.mat file Structure:
static std::vector< int > mapSubset2Pixel(const std::vector< int > &subset_coords, int subset_spacing)
Map subset coordinates to pixel coordinates Equivalent to map_subset2pixel.m.
static std::vector< int > findROICenter(const cv::Mat &roi_mask)
Find center of ROI mask.
static std::vector< int > mapPointCoordinate(const std::vector< int > &point_sw, const cv::Mat &U_mapped, const cv::Mat &V_mapped)
Map point coordinates using displacement fields Equivalent to map_pointcoordinate....
static std::vector< int > mapPixel2Subset(const std::vector< int > &pixel_coords, int subset_spacing)
Map pixel coordinates to subset coordinates Equivalent to map_pixel2subset.m.
static ncorr::ROI2D matToNcorrROI(const cv::Mat &mask)
Convert OpenCV Mat to ncorr ROI2D.
static SeedPoint loadOrCreateSeed(const BaseParameters ¶ms, 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 ¶ms, 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...
static cv::Mat ncorrROIToMat(const ncorr::ROI2D &roi)
Convert ncorr ROI2D to OpenCV Mat.
std::tuple< std::string, std::vector< int >, bool > execute(const std::string &trial, int stereopair, const StagePlan &plan=StagePlan{})
Execute Step D: 2D DIC Analysis Main entry point for 2D DIC processing.
static void formatDic2DPairResults(const Config &config, const StepParameters &step_params, const std::string &output_path, int cam_1, int cam_2, const std::vector< int > &pairOrder, bool pairForced)
Format cached ncorr outputs into myDIC2DpairResults (step2_dic_finish equivalent).
StepDWorkflow(const Config &config)
Constructor.
static bool updateMaskAndSeedFromOutput(const cv::Mat &input_mask, const SeedPoint &input_seed, const ncorr::DIC_analysis_output &dic_output, cv::Mat &output_mask, SeedPoint &output_seed)
Map an ROI mask and seed point through the first displacement field.
Adapter: run the cuNCorr DIC engine and package results as ncorr::DIC_analysis_output.
Lightweight, dependency-free logging facility for CPPxDIC.
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...
int matlab_replacebadcorr(ncorr::DIC_analysis_output &data, double level_corr_coef=1.0, double sigma_spatial=1.0, double sigma_temporal=2.0, double shrink_factor=0.9)
Replaces badly correlated displacement data with spatiotemporally filtered values.
StagePlan — selects which pipeline sub-steps a single process executes.
bool match
ROI/seed + REF->trial matching + camera-to-camera matching.
bool format
combine ncorr outputs -> myDIC2DpairResults
int only_cam
0 = both cameras; otherwise restrict tracking to this camera id
bool track
per-camera NCorr tracking
std::string analysis_type
std::string baseResultPath
Tunables mirrored onto cuncorr::SessionConfig.
int seed_search
coarse ZNCC seed search radius (px)
int seed_y
manual seed row (full-image px); -1 = auto
int seed_x
manual seed column (full-image px); -1 = auto
Which backend actually ran.
bool cuda
true if a CUDA device executed the run
std::string backend
e.g. "cpu" or "cuda:0 <name>"
DIC 2D Pair Results Equivalent to MATLAB's DIC2DpairResults.
std::vector< int > pairOrder
std::vector< Points2D > Points
std::vector< std::vector< double > > CorCoeffVec
std::vector< double > FaceColors
static constexpr int LIMIT_GRAYSCALE_DEFAULT
static constexpr int TRUE_FPS
std::vector< std::string > imgcorr
std::vector< double > cutoff_corrcoef
StepAnalysisInfo stepanalysis
std::vector< TrialEntry > trials
std::vector< double > spddxlcond
std::vector< int > spdcond
std::vector< int > repcond
std::vector< int > nfcond
void getPairOrder(const std::string &dir, std::vector< int > &pairOrder, bool &pairForced) const
std::vector< std::string > dircond
Seed point structure Stores seed points in both pixel world (pw) and subset world (sw) coordinates.
Step parameters structure for DIC analysis Contains DIC-specific parameters like radius,...
std::vector< int > initial_seed
StepAnalysisParams stepanalysis_params