14#include <opencv2/opencv.hpp>
18#include <nlohmann/json.hpp>
24 LOG_INFO <<
"Checking data and protocol...";
30 LOG_INFO <<
"Parallel processing is enabled";
32 if (!checkROIReferences(config)) {
36 if (!checkSeedReferences(config)) {
42 if (!checkProtocolFiles(config)) {
47 if (!checkCalibrationFiles(config)) {
58bool Utils::checkCalibrationFiles(
const Config& config) {
61 if (!std::filesystem::exists(calib_dir)) {
62 LOG_ERROR <<
"Calibration directory not found: " << calib_dir;
67 std::vector<bool> cam_found(config.
num_pair * 2,
false);
69 for (
const auto& entry : std::filesystem::directory_iterator(calib_dir)) {
70 if (!entry.is_regular_file())
continue;
71 auto name = entry.path().filename().string();
72 std::string lower = name;
73 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
76 for (
int cam_id = 1; cam_id <= config.
num_pair * 2; ++cam_id) {
77 std::string cam_str =
"cam_" + std::to_string(cam_id);
78 std::string camera_str =
"camera" + std::to_string(cam_id);
79 if (lower.find(cam_str) != std::string::npos ||
80 lower.find(camera_str) != std::string::npos) {
81 cam_found[cam_id - 1] =
true;
87 bool all_found =
true;
88 for (
int cam_id = 1; cam_id <= config.
num_pair * 2; ++cam_id) {
89 if (!cam_found[cam_id - 1]) {
90 LOG_ERROR <<
"Camera " << cam_id <<
" calibration file missing";
96 LOG_ERROR <<
"Some calibration files missing in " << calib_dir;
100 LOG_INFO <<
"Calibration files found for all " << (config.
num_pair * 2) <<
" cameras in "
104 LOG_ERROR <<
"Error while checking calibration files";
109bool Utils::checkROIReferences(
const Config& config) {
110 LOG_INFO <<
"Checking ROI References...";
112 for (
int pair_i = 1; pair_i <= config.
num_pair; ++pair_i) {
113 std::ostringstream ref_trial_id_oss;
114 ref_trial_id_oss << std::setfill(
'0') << std::setw(3) << config.
ref_trial_id;
116 std::string roifile =
122 <<
" must exist to be able to run DIC analysis in parallel."
123 <<
" This file comes from Drawing Reference ROI process."
124 <<
" ROI file for the stereopair " << pair_i <<
" from the Reference Trial "
134bool Utils::checkSeedReferences(
const Config& config) {
135 LOG_INFO <<
"Checking SEED References...";
137 for (
int pair_i = 1; pair_i <= config.
num_pair; ++pair_i) {
138 std::ostringstream ref_trial_id_oss;
139 ref_trial_id_oss << std::setfill(
'0') << std::setw(3) << config.
ref_trial_id;
141 std::string seedfile =
147 <<
" must exist to be able to run DIC analysis in parallel."
148 <<
" This file comes from Selecting the seed of ROI process."
149 <<
" SEED file for the stereopair " << pair_i <<
" from the Reference Trial "
159bool Utils::checkProtocolFiles(
const Config& config) {
162 auto protocol_files =
findFiles(protocol_dir,
"*.mat");
164 if (protocol_files.empty()) {
170 LOG_WARN <<
"No protocol .mat file in " << protocol_dir
171 <<
" — reference trial comes from ref_trial_id=" << config.
ref_trial_id
172 <<
" and the frame window from idx_frame_start/end ("
176 LOG_ERROR <<
"Protocol files in " << protocol_dir
177 <<
" must exist to be able to run DIC analysis (needed to pick the reference "
178 "trial; set ref_trial_id / --reftrial to run without one). Protocol not found.";
186 return std::filesystem::exists(path) && std::filesystem::is_regular_file(path);
190 return std::filesystem::exists(path) && std::filesystem::is_directory(path);
194 const std::string& pattern) {
195 std::vector<std::string> files;
203 std::string regex_pattern = pattern;
204 std::replace(regex_pattern.begin(), regex_pattern.end(),
'*',
'.');
205 regex_pattern =
".*" + regex_pattern +
".*";
207 std::regex file_regex(regex_pattern);
209 for (
const auto& entry : std::filesystem::directory_iterator(directory)) {
210 if (entry.is_regular_file()) {
211 std::string filename = entry.path().filename().string();
212 if (std::regex_match(filename, file_regex)) {
213 files.push_back(entry.path().string());
217 }
catch (
const std::exception& e) {
218 LOG_ERROR <<
"Error searching for files: " << e.what();
224std::vector<std::string>
Utils::split(
const std::string& str,
char delimiter) {
225 std::vector<std::string> tokens;
226 std::stringstream ss(str);
229 while (std::getline(ss, token, delimiter)) {
230 tokens.push_back(token);
259 int& cam_first,
int& cam_second) {
260 if (stereopair < 1) {
261 throw std::runtime_error(
"Invalid stereopair value: must be >= 1");
263 int idx = stereopair - 1;
264 if (idx <
static_cast<int>(camera_pairs.size())) {
265 cam_first = camera_pairs[idx].first;
266 cam_second = camera_pairs[idx].second;
269 cam_first = 2 * stereopair - 1;
270 cam_second = 2 * stereopair;
276 if (!std::filesystem::exists(path)) {
277 return std::filesystem::create_directories(path);
307static std::string
find_video_file(
const std::string& video_dir,
const std::string& subject,
308 const std::string& material,
const std::string& trialname,
317 const std::regex strict(subject +
"_" + material +
"_speckles_" + trialname +
"_.*_cam_" +
318 std::to_string(cam_id) +
"\\.mp4$");
319 const std::regex relaxed(
".*_" + trialname +
"_.*_cam_" + std::to_string(cam_id) +
"\\.mp4$");
321 std::vector<std::string> names;
322 for (
const auto& entry : std::filesystem::directory_iterator(video_dir)) {
323 if (!entry.is_regular_file())
continue;
324 names.push_back(entry.path().filename().string());
326 std::sort(names.begin(), names.end());
328 for (
const std::regex* pat : {&strict, &relaxed}) {
329 for (
const auto& name : names) {
330 if (std::regex_match(name, *pat)) {
331 if (pat == &relaxed) {
332 LOG_WARN <<
"Video for trial " << trialname <<
" cam " << cam_id
333 <<
" matched by the relaxed pattern (no '" << subject <<
"_" << material
334 <<
"_speckles_' prefix): " << name;
336 return (std::filesystem::path(video_dir) / name).
string();
344 std::ostringstream oss;
345 oss << std::setw(n) << std::setfill(
'0') << num;
350 int frameEnd,
int frameJump, std::vector<std::string>& cam1Frames,
351 std::vector<std::string>& cam2Frames,
int maxFrames) {
353 int cam_first = 0, cam_second = 0;
363 if (vid1.empty() || vid2.empty()) {
364 LOG_ERROR <<
"Video files not found for trial=" << trial <<
" pair=" << stereopair;
368 cv::VideoCapture cap1(vid1);
369 cv::VideoCapture cap2(vid2);
370 if (!cap1.isOpened() || !cap2.isOpened()) {
371 LOG_ERROR <<
"Failed to open video files: " << vid1 <<
" or " << vid2;
375 LOG_INFO <<
"Video loaded successfully.";
379 int total1 =
static_cast<int>(cap1.get(cv::CAP_PROP_FRAME_COUNT));
380 int total2 =
static_cast<int>(cap2.get(cv::CAP_PROP_FRAME_COUNT));
381 if (frameEnd <= 0) frameEnd = std::min(total1, total2);
382 frameStart = std::max(1, frameStart);
383 frameEnd = std::min(frameEnd, std::min(total1, total2));
384 if (frameJump <= 0) frameJump = 1;
389 int limitedEnd = frameStart + (maxFrames - 1) * frameJump;
390 frameEnd = std::min(frameEnd, limitedEnd);
394 std::ostringstream odir;
396 <<
"/tmp_frames/T" << trial <<
"/pair" << stereopair <<
"/";
397 std::string base_dir = odir.str();
398 std::string cam1_dir = base_dir +
"cam" + std::to_string(cam_first);
399 std::string cam2_dir = base_dir +
"cam" + std::to_string(cam_second);
401 LOG_ERROR <<
"Failed to create frame directories";
414 if (frameStart > 1) {
415 cap1.set(cv::CAP_PROP_POS_FRAMES, frameStart - 1);
416 cap2.set(cv::CAP_PROP_POS_FRAMES, frameStart - 1);
419 int cur = frameStart;
421 for (
int f = frameStart; f <= frameEnd && !eof; f += frameJump) {
424 if (!cap1.grab() || !cap2.grab()) { eof =
true;
break; }
430 if (!cap1.read(im1) || !cap2.read(im2))
break;
435 cv::Mat gray1, gray2;
436 if (im1.channels() == 3) {
437 std::vector<cv::Mat> channels1;
438 cv::split(im1, channels1);
439 gray1 = channels1[2];
444 if (im2.channels() == 3) {
445 std::vector<cv::Mat> channels2;
446 cv::split(im2, channels2);
447 gray2 = channels2[2];
452 std::ostringstream f1, f2;
453 f1 << cam1_dir <<
"/frame_" << std::setw(6) << std::setfill(
'0') << f <<
".png";
454 f2 << cam2_dir <<
"/frame_" << std::setw(6) << std::setfill(
'0') << f <<
".png";
455 cv::imwrite(f1.str(), gray1);
456 cv::imwrite(f2.str(), gray2);
457 cam1Frames.push_back(f1.str());
458 cam2Frames.push_back(f2.str());
461 return !cam1Frames.empty() && !cam2Frames.empty();
462 }
catch (
const std::exception& e) {
463 LOG_ERROR <<
"importRawVid error: " << e.what();
469 std::vector<std::string>& cam1Frames, std::vector<std::string>& cam2Frames,
471 auto calc_ranges = [](
const std::string& phase,
int nLoad,
int nSlide,
int nRelax,
int& s,
473 if (phase ==
"loading") {
478 if (phase ==
"slide1") {
483 if (phase ==
"relax1") {
484 s = nLoad + nSlide + 1;
488 if (phase ==
"slide2") {
489 s = nLoad + nSlide + nRelax + 1;
493 if (phase ==
"relax2") {
494 s = nLoad + 2 * nSlide + nRelax + 1;
498 if (phase ==
"all") {
500 e = nLoad + 2 * nSlide + 2 * nRelax;
518 LOG_INFO <<
"force_frame_window=true: using config frames " << frameStart <<
".."
519 << (frameEnd > 0 ? std::to_string(frameEnd) : std::string(
"end"))
520 <<
" (jump " << frameJump <<
"), protocol window ignored";
522 if (!protos.empty()) {
523 std::string proto_file = protos.front();
524 mat_t* matfp = Mat_Open(proto_file.c_str(), MAT_ACC_RDONLY);
526 matvar_t* cond = Mat_VarRead(matfp,
"cond");
527 if (cond && cond->class_type == MAT_C_STRUCT) {
528 matvar_t* table = Mat_VarGetStructFieldByName(cond,
"table", 0);
529 matvar_t* dur = Mat_VarGetStructFieldByName(cond,
"dur", 0);
530 if (table && table->class_type == MAT_C_CELL && dur &&
531 (dur->class_type == MAT_C_DOUBLE || dur->class_type == MAT_C_SINGLE)) {
532 int nLoad = 0, nSlide = 0, nRelax = 0;
533 if (dur->data && dur->nbytes >= 5 * dur->data_size) {
534 double d3 = 0.0, d5 = 0.0;
535 if (dur->class_type == MAT_C_DOUBLE) {
536 const double* dd =
static_cast<const double*
>(dur->data);
540 const float* ff =
static_cast<const float*
>(dur->data);
545 nLoad =
static_cast<int>(std::round(d3 / 1e3 * fps));
547 size_t ntrial = table->dims[0];
548 size_t ncond = table->dims[1];
549 size_t i =
static_cast<size_t>(std::max(1, trial) - 1);
550 auto cell_at = [&](
size_t ii,
size_t jj) -> matvar_t* {
551 size_t idx = ii + jj * ntrial;
552 return static_cast<matvar_t**
>(table->data)[idx];
554 double dst = 0.0, spd = 0.0;
556 matvar_t* c_dst = cell_at(i, 4);
557 matvar_t* c_spd = cell_at(i, 3);
558 if (c_dst && c_dst->data) {
559 if (c_dst->class_type == MAT_C_DOUBLE)
560 dst =
static_cast<const double*
>(c_dst->data)[0];
561 else if (c_dst->class_type == MAT_C_SINGLE)
562 dst =
static_cast<const float*
>(c_dst->data)[0];
564 if (c_spd && c_spd->data) {
565 if (c_spd->class_type == MAT_C_DOUBLE)
566 spd =
static_cast<const double*
>(c_spd->data)[0];
567 else if (c_spd->class_type == MAT_C_SINGLE)
568 spd =
static_cast<const float*
>(c_spd->data)[0];
572 nSlide =
static_cast<int>(std::round(dst / spd * fps));
575 nRelax =
static_cast<int>(std::round(d5 / 1e3 * fps));
576 int s = frameStart, e = frameEnd;
577 if (calc_ranges(config.
phase_id, nLoad, nSlide, nRelax, s, e)) {
584 if (cond) Mat_VarFree(cond);
592 return importRawVid(config, trial, stereopair, frameStart, frameEnd, frameJump, cam1Frames,
593 cam2Frames, maxFrames);
597 std::string& roiJsonPath, std::string& roiMaskImagePath) {
599 std::ostringstream matname;
601 <<
"/REF_MASK_" << std::setw(3) << std::setfill(
'0') << config.
ref_trial_id <<
"_"
602 << config.
phase_id <<
"_pair" << stereopair <<
".mat";
603 std::string mat_path = matname.str();
606 roiMaskImagePath.clear();
610 mat_t* matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
613 matvar_t* mv = Mat_VarRead(matfp,
"refmask");
614 if (mv && mv->rank == 2 && mv->data && mv->dims[0] > 0 && mv->dims[1] > 0) {
615 size_t h = mv->dims[0], w = mv->dims[1];
616 cv::Mat m(h, w, CV_8UC1, cv::Scalar(0));
617 if (mv->class_type == MAT_C_DOUBLE) {
618 const double* d =
static_cast<const double*
>(mv->data);
619 for (
size_t y = 0; y < h; ++y)
620 for (
size_t x = 0; x < w; ++x)
621 m.at<uchar>(y, x) = d[y + x * h] > 0.5 ? 255 : 0;
622 }
else if (mv->class_type == MAT_C_SINGLE) {
623 const float* d =
static_cast<const float*
>(mv->data);
624 for (
size_t y = 0; y < h; ++y)
625 for (
size_t x = 0; x < w; ++x)
626 m.at<uchar>(y, x) = d[y + x * h] > 0.5 ? 255 : 0;
627 }
else if (mv->class_type == MAT_C_UINT8) {
628 const uint8_t* d =
static_cast<const uint8_t*
>(mv->data);
629 for (
size_t y = 0; y < h; ++y)
630 for (
size_t x = 0; x < w; ++x) m.at<uchar>(y, x) = d[y + x * h] ? 255 : 0;
634 if (mv) Mat_VarFree(mv);
637 std::ostringstream jsonname;
640 << config.
phase_id <<
"_T" << trial <<
"_pair" << stereopair <<
".json";
641 roiJsonPath = jsonname.str();
643 roiMaskImagePath.clear();
644 if (!maskImg.empty()) {
645 std::ostringstream pm;
648 << config.
phase_id <<
"_T" << trial <<
"_pair" << stereopair <<
".png";
649 if (
ensure_dir(std::filesystem::path(pm.str()).parent_path().string())) {
650 cv::imwrite(pm.str(), maskImg);
651 roiMaskImagePath = pm.str();
656 j[
"pair"] = stereopair;
658 j[
"source"] = {{
"type",
"mat"}, {
"path", mat_path}};
659 j[
"mask_path"] = roiMaskImagePath;
660 std::ofstream ofs(roiJsonPath);
661 ofs << j.dump(2) << std::endl;
665 roiMaskImagePath.clear();
671 std::string& seedJsonPath) {
673 std::ostringstream matname;
675 <<
"/REF_SEED_" << std::setw(3) << std::setfill(
'0') << config.
ref_trial_id <<
"_"
676 << config.
phase_id <<
"_pair" << stereopair <<
".mat";
677 std::string mat_path = matname.str();
679 seedJsonPath.clear();
682 std::vector<std::string> varnames;
683 std::vector<std::pair<double, double>> seeds;
685 mat_t* matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
688 matvar_t* vinfo =
nullptr;
689 std::vector<std::string> candidates;
690 while ((vinfo = Mat_VarReadNextInfo(matfp)) !=
nullptr) {
692 varnames.emplace_back(vinfo->name);
693 std::string n(vinfo->name);
695 std::transform(nl.begin(), nl.end(), nl.begin(), ::tolower);
696 if (nl.find(
"seed") != std::string::npos || nl ==
"xy" || nl ==
"points") {
697 candidates.push_back(n);
698 }
else if (nl ==
"radius" || nl ==
"r") {
706 matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
708 auto try_read_points = [&](
const std::string& name) ->
bool {
709 matvar_t* mv = Mat_VarRead(matfp, name.c_str());
710 if (!mv)
return false;
712 if (mv->rank == 2 && mv->data && mv->dims[1] >= 2) {
713 size_t rows = mv->dims[0];
714 size_t cols = mv->dims[1];
715 if (cols >= 2 && rows >= 1) {
716 if (mv->class_type == MAT_C_DOUBLE) {
717 const double* d =
static_cast<const double*
>(mv->data);
718 for (
size_t i = 0; i < rows; i++)
719 seeds.emplace_back(d[i + 0 * rows], d[i + 1 * rows]);
721 }
else if (mv->class_type == MAT_C_SINGLE) {
722 const float* d =
static_cast<const float*
>(mv->data);
723 for (
size_t i = 0; i < rows; i++)
724 seeds.emplace_back(d[i + 0 * rows], d[i + 1 * rows]);
733 for (
const auto& c : candidates) {
734 if (try_read_points(c)) {
741 try_read_points(
"seeds");
744 try_read_points(
"seed");
747 try_read_points(
"xy");
750 auto try_read_radius = [&](
const std::string& name) {
751 matvar_t* mv = Mat_VarRead(matfp, name.c_str());
754 if (mv->class_type == MAT_C_DOUBLE)
755 radius =
static_cast<const double*
>(mv->data)[0];
756 else if (mv->class_type == MAT_C_SINGLE)
757 radius =
static_cast<const float*
>(mv->data)[0];
758 else if (mv->class_type == MAT_C_INT32)
759 radius =
static_cast<const int32_t*
>(mv->data)[0];
763 try_read_radius(
"radius");
764 try_read_radius(
"r");
768 std::ostringstream jsonname;
771 << config.
phase_id <<
"_T" << trial <<
"_pair" << stereopair <<
".json";
772 seedJsonPath = jsonname.str();
773 std::ofstream ofs(seedJsonPath);
775 ofs <<
" \"trial\": " << trial <<
",\n";
776 ofs <<
" \"pair\": " << stereopair <<
",\n";
777 ofs <<
" \"phase\": \"" << config.
phase_id <<
"\",\n";
778 ofs <<
" \"source\": { \"type\": \"mat\", \"path\": \"" << mat_path <<
"\" },\n";
779 ofs <<
" \"vars\": [";
780 for (
size_t i = 0; i < varnames.size(); ++i) {
781 ofs <<
"\"" << varnames[i] <<
"\"";
782 if (i + 1 < varnames.size()) ofs <<
", ";
785 ofs <<
" \"radius\": " << radius <<
",\n";
786 ofs <<
" \"seeds\": [";
787 for (
size_t i = 0; i < seeds.size(); ++i) {
788 ofs <<
"{ \"x\": " << seeds[i].first <<
", \"y\": " << seeds[i].second <<
" }";
789 if (i + 1 < seeds.size()) ofs <<
", ";
795 seedJsonPath.clear();
808 LOG_ERROR <<
"Camera parameters file not found: " << mat_path;
813 mat_t* mat_file = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
815 LOG_ERROR <<
"Failed to open MAT file: " << mat_path;
820 matvar_t* cb_struct = Mat_VarRead(mat_file,
"cameraCBparameters");
821 if (!cb_struct || cb_struct->class_type != MAT_C_STRUCT) {
822 LOG_ERROR <<
"Variable 'cameraCBparameters' not found or not a struct";
828 matvar_t* cam_params = Mat_VarGetStructFieldByName(cb_struct,
"cameraParameters", 0);
829 if (!cam_params || cam_params->class_type != MAT_C_STRUCT) {
830 LOG_ERROR <<
"Field 'cameraParameters' not found or not a struct";
831 Mat_VarFree(cb_struct);
837 matvar_t* intrinsic = Mat_VarGetStructFieldByName(cam_params,
"IntrinsicMatrix", 0);
838 if (intrinsic && intrinsic->data && intrinsic->class_type == MAT_C_DOUBLE) {
839 double* data =
static_cast<double*
>(intrinsic->data);
853 LOG_ERROR <<
"IntrinsicMatrix not found or invalid";
854 Mat_VarFree(cb_struct);
860 matvar_t* radial = Mat_VarGetStructFieldByName(cam_params,
"RadialDistortion", 0);
861 std::vector<double> dist_coeffs;
862 if (radial && radial->data && radial->class_type == MAT_C_DOUBLE) {
863 double* data =
static_cast<double*
>(radial->data);
865 for (
int i = 0; i < radial->rank; i++) n *= radial->dims[i];
866 for (
size_t i = 0; i < n && i < 3; ++i) {
867 dist_coeffs.push_back(data[i]);
872 matvar_t* tangential = Mat_VarGetStructFieldByName(cam_params,
"TangentialDistortion", 0);
873 std::vector<double> tan_coeffs;
874 if (tangential && tangential->data && tangential->class_type == MAT_C_DOUBLE) {
875 double* data =
static_cast<double*
>(tangential->data);
877 for (
int i = 0; i < tangential->rank; i++) n *= tangential->dims[i];
878 for (
size_t i = 0; i < n && i < 2; ++i) {
879 tan_coeffs.push_back(data[i]);
886 if (dist_coeffs.size() >= 1)
888 if (dist_coeffs.size() >= 2)
890 if (tan_coeffs.size() >= 1) params.
distortion_coeffs.at<
double>(0, 2) = tan_coeffs[0];
891 if (tan_coeffs.size() >= 2) params.
distortion_coeffs.at<
double>(0, 3) = tan_coeffs[1];
892 if (dist_coeffs.size() >= 3)
895 Mat_VarFree(cb_struct);
899 LOG_INFO <<
"Loaded camera parameters from: " << mat_path;
905 }
catch (
const std::exception& e) {
906 LOG_ERROR <<
"Exception loading camera parameters: " << e.what();
914 LOG_WARN <<
"Invalid camera parameters, cannot undistort points";
915 points_out = points_in;
919 if (points_in.empty()) {
927 points_out.resize(points_in.size());
929 for (
size_t i = 0; i < points_in.size(); ++i) {
952 double x = (point_in.x - cx) / fx;
953 double y = (point_in.y - cy) / fy;
959 for (
int iter = 0; iter < 10; ++iter) {
960 double r2 = x_u * x_u + y_u * y_u;
965 double radial = 1.0 + k1 * r2 + k2 * r4 + k3 * r6;
968 double dx_tan = 2.0 * p1 * x_u * y_u + p2 * (r2 + 2.0 * x_u * x_u);
969 double dy_tan = p1 * (r2 + 2.0 * y_u * y_u) + 2.0 * p2 * x_u * y_u;
972 double x_d = x_u * radial + dx_tan;
973 double y_d = y_u * radial + dy_tan;
976 double error_x = x_d - x;
977 double error_y = y_d - y;
979 if (std::abs(error_x) < 1e-10 && std::abs(error_y) < 1e-10) {
984 x_u -= error_x * 0.9;
985 y_u -= error_y * 0.9;
989 double x_out = x_u * fx + cx;
990 double y_out = y_u * fy + cy;
992 return cv::Point2d(x_out, y_out);
1001 LOG_ERROR <<
"DLT11Calibration: need at least 6 point correspondences, got " << N;
1012 Eigen::MatrixXd M = Eigen::MatrixXd::Zero(2 * N, 11);
1013 Eigen::VectorXd b(2 * N);
1015 for (
size_t i = 0; i < N; ++i) {
1016 double u = P2[i * 2 + 0];
1017 double v = P2[i * 2 + 1];
1018 double X = P3[i * 3 + 0];
1019 double Y = P3[i * 3 + 1];
1020 double Z = P3[i * 3 + 2];
1034 size_t r2 = 2 * i + 1;
1046 Eigen::VectorXd result = M.colPivHouseholderQr().solve(b);
1049 for (
int i = 0; i < 11; ++i) {
1061 const std::vector<Eigen::Vector3d>& points_to,
1065 if (points_from.size() != points_to.size()) {
1066 LOG_ERROR <<
"Point clouds must have same size for rigid transformation";
1070 if (points_from.size() < 3) {
1071 LOG_ERROR <<
"Need at least 3 points for rigid transformation";
1076 std::vector<Eigen::Vector3d> from_no_nan, to_no_nan;
1077 for (
size_t i = 0; i < points_from.size(); ++i) {
1078 if (!points_from[i].hasNaN() && !points_to[i].hasNaN()) {
1079 from_no_nan.push_back(points_from[i]);
1080 to_no_nan.push_back(points_to[i]);
1084 if (from_no_nan.size() < 3) {
1085 LOG_ERROR <<
"Too few valid points after removing NaNs (" << from_no_nan.size() <<
" of "
1086 << points_from.size() <<
" points valid, need at least 3)."
1087 <<
" This usually indicates poor correlation/tracking in the DIC analysis";
1092 Eigen::Vector3d centroid_from = Eigen::Vector3d::Zero();
1093 Eigen::Vector3d centroid_to = Eigen::Vector3d::Zero();
1095 for (
const auto& p : from_no_nan) {
1098 for (
const auto& p : to_no_nan) {
1102 centroid_from /= from_no_nan.size();
1103 centroid_to /= to_no_nan.size();
1106 std::vector<Eigen::Vector3d> da, db;
1107 for (
const auto& p : from_no_nan) {
1108 da.push_back(p - centroid_from);
1110 for (
const auto& p : to_no_nan) {
1111 db.push_back(p - centroid_to);
1115 Eigen::Matrix3d M = Eigen::Matrix3d::Zero();
1116 for (
size_t i = 0; i < da.size(); ++i) {
1117 M += db[i] * da[i].transpose();
1121 Eigen::JacobiSVD<Eigen::Matrix3d> svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV);
1122 Eigen::Matrix3d U = svd.matrixU();
1123 Eigen::Matrix3d V = svd.matrixV();
1126 double det_UV = (U * V.transpose()).determinant();
1127 Eigen::Matrix3d S = Eigen::Matrix3d::Identity();
1130 transform.
R = U * S * V.transpose();
1133 transform.
t = centroid_to - transform.
R * centroid_from;
1141 const std::vector<Eigen::Vector3d>& points_in,
const RigidTransform& transform) {
1142 std::vector<Eigen::Vector3d> points_out;
1143 points_out.reserve(points_in.size());
1146 LOG_WARN <<
"Invalid transform, returning original points";
1151 for (
const auto& p : points_in) {
1153 points_out.push_back(p);
1155 points_out.push_back(transform.
R * p + transform.
t);
1163 bool with_rawdata,
bool with_speckles) {
1164 std::string path =
"";
1166 if (with_data_or_dic_path) {
1175 if (with_speckles) {
1176 path +=
"/speckles";
1187 std::ostringstream output_ss;
1189 output_ss <<
"/" << config.
material;
1190 output_ss <<
"/" << std::setw(3) << std::setfill(
'0') << trial;
1192 return output_ss.str();
1196 std::ostringstream output_ss;
1198 output_ss <<
"/" << config.
material;
1199 output_ss <<
"/" << std::setw(3) << std::setfill(
'0') << trial;
1200 output_ss <<
"/" << config.
phase_id <<
"/";
1201 return output_ss.str();
1205 bool with_speckles,
bool with_material,
bool with_video,
1206 bool with_protocol,
bool with_calib) {
1210 if (with_material) {
1218 if (with_protocol) {
1219 path +=
"/protocol";
1230 bool with_rawdata,
bool with_speckles) {
1232 buildPath(config, with_data_or_dic_path, with_rawdata, with_speckles,
true,
false,
false);
1242 bool with_rawdata,
bool with_speckles,
bool with_material) {
1243 std::string path =
buildPath(config, with_data_or_dic_path, with_rawdata, with_speckles,
1244 with_material,
false,
true);
1250 bool with_rawdata,
bool with_speckles,
bool with_material) {
1251 std::string path =
buildPath(config, with_data_or_dic_path, with_rawdata, with_speckles,
1252 with_material,
true,
false);
1258 std::string path =
buildPath(config,
false,
false,
false,
false,
false,
false,
true);
1264 bool with_trial,
bool with_phase,
bool with_cache) {
1267 if (with_material) {
1272 path +=
"/" + parameters.
trial;
1276 path +=
"/" + parameters.
phase;
1287 return buildPath(parameters,
true,
true,
true,
false);
1291 return buildPath(parameters,
true,
true,
true,
true);
1295 const std::string& reftrial,
const std::string& phase,
1296 int stereopair,
const std::string& extension) {
1297 std::ostringstream oss;
1298 oss << path <<
"/" << prefix << reftrial <<
"_" << phase <<
"_pair" << stereopair << extension;
1304 const std::string& phase,
int stereopair,
1305 const std::string& extension) {
1306 return likeRoiOrSeedPath(path,
"REF_MASK_", reftrial, phase, stereopair, extension);
1310 const std::string& phase,
int stereopair,
1311 const std::string& extension) {
1312 return likeRoiOrSeedPath(path,
"REF_SEED_", reftrial, phase, stereopair, extension);
1316 std::string reftrial,
int stereopair,
1317 std::string_view prefix,
1318 const std::string& extension) {
1320 parameters.
phase, stereopair, extension);
1324 int stereopair,
const std::string& extension) {
1325 return buildRoiOrSeedLikeFilePath(parameters, reftrial, stereopair,
"REF_MASK_", extension);
1329 std::string reftrial,
int stereopair,
1330 const std::string& extension) {
1331 return buildRoiOrSeedLikeFilePath(parameters, reftrial, stereopair,
"REF_SEED_", extension);
1335 std::string reftrial,
int stereopair,
1336 const std::string& extension) {
1337 return parameters.
outputPath +
"/MATCHING2" + reftrial +
"_pair" + std::to_string(stereopair) +
1342 const std::string& ext) {
1343 return pathdir +
"DIC3Dcombined_" + std::to_string(num_pair) +
"Pairs_stitched" + ext;
1347 const std::string& fileversion,
1348 const std::string& ext) {
1349 return pathdir +
"DIC3DPPresults_" + std::to_string(num_pair) +
"Pairs_cum_" + fileversion +
1354 return pathdir +
"viz/" + filename;
1358 const std::string& extension) {
1359 return pathdir +
"/myDIC2DpairResults_C_" + std::to_string(cam_1) +
"_C_" +
1360 std::to_string(cam_2) + extension;
1364 const std::string& extension) {
1366 return pathdir +
"/ncorr" + std::to_string(cam_1) + extension;
1368 return pathdir +
"/ncorr" + std::to_string(cam_1) + std::to_string(cam_2) + extension;
std::string calib_folder_set
static cv::Point2d undistortPoint(const cv::Point2d &point_in, const CameraParameters ¶ms)
Undistort single 2D point.
static std::string buildNcorrFilePath(const std::string pathdir, int cam_1, int cam_2=-1, const std::string &extension=".bin")
static std::string buildVideoDir(const Config &config, bool with_data_or_dic_path=false, bool with_rawdata=false, bool with_speckles=false, bool with_material=false)
Build the video path.
static void setCameraPairs(const std::vector< std::pair< int, int > > &camera_pairs)
Register the process-wide stereopair -> (cam_first, cam_second) table.
static std::string likeRoiPath(const std::string &path, const std::string &reftrial, const std::string &phase, int stereopair, const std::string &extension)
static bool fileExists(const std::string &path)
static bool importRawVid(const Config &config, int trial, int stereopair, int frameStart, int frameEnd, int frameJump, std::vector< std::string > &cam1Frames, std::vector< std::string > &cam2Frames, int maxFrames=0)
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::string likeSeedPath(const std::string &path, const std::string &reftrial, const std::string &phase, int stereopair, const std::string &extension)
static std::string buildOutputCachePath(const cppxdic::BaseParameters ¶meters)
Build the output cache path.
static std::string buildBaseDataPath(const Config &config, bool with_data_or_dic_path=false, bool with_rawdata=false, bool with_speckles=false)
Build base data path.
static std::vector< std::string > split(const std::string &str, char delimiter)
static std::vector< std::string > findFiles(const std::string &directory, const std::string &pattern)
static std::string likeRoiOrSeedPath(const std::string &path, const std::string_view &prefix, const std::string &reftrial, const std::string &phase, int stereopair, const std::string &extension)
static std::string buildDic3DPPresultsFilePath(const std::string pathdir, int num_pair, const std::string &fileversion, const std::string &ext)
static std::string buildDic2DPairResultsFilePath(const std::string pathdir, int cam_1, int cam_2, const std::string &extension)
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 buildCalibDir(const Config &config)
Build the calib path.
static bool dicCheck(const Config &config)
Utility functions implementation for CPPXDIC.
static std::string buildVizPath(const std::string pathdir, const std::string filename)
static void undistortPoints(const std::vector< cv::Point2d > &points_in, const CameraParameters ¶ms, std::vector< cv::Point2d > &points_out)
Undistort 2D points using camera parameters Matches MATLAB: undistortPoints(points,...
static bool computeRigidTransform(const std::vector< Eigen::Vector3d > &points_from, const std::vector< Eigen::Vector3d > &points_to, RigidTransform &transform)
Compute optimal rigid body transformation between two point clouds Matches MATLAB: rigidTransformatio...
static bool DLT11Calibration(const double *P2, const double *P3, size_t N, std::vector< double > &L)
Compute DLT (Direct Linear Transformation) 11 parameters from 2D-3D point correspondences Matches MAT...
static std::string buildOutputUntilPhaseDir(const Config &config, int trial)
Build output dir until the Phase folder.
static const std::vector< std::pair< int, int > > & cameraPairs()
Currently registered stereopair table (see setCameraPairs).
static std::string buildOutputUntilMaterialDir(const Config &config)
Build output path until material folder.
static std::string buildOutputUntilTrialDir(const Config &config, int trial)
Build output dir until Trial folder.
static std::vector< Eigen::Vector3d > applyRigidTransform(const std::vector< Eigen::Vector3d > &points_in, const RigidTransform &transform)
Apply rigid transformation to point cloud points_out = R * points_in + t.
static std::string buildMatchingFilePath(const cppxdic::BaseParameters ¶meters, std::string reftrial, int stereopair, const std::string &extension=".bin")
Build the matching file path.
static std::string buildUntilMaterialDir(const Config &config, bool with_data_or_dic_path, bool with_rawdata, bool with_speckles)
Build path until material folder.
static bool directoryExists(const std::string &path)
static bool loadROIFromMat(const Config &config, int trial, int stereopair, std::string &roiJsonPath, std::string &roiMaskImagePath)
static bool loadCameraParameters(const std::string &mat_path, CameraParameters ¶ms)
Load camera calibration parameters from MATLAB .mat file Matches MATLAB: load('cameraCBparameters_cam...
static std::string buildOutputPath(const cppxdic::BaseParameters ¶meters)
Build the output path.
static bool loadSeedFromMat(const Config &config, int trial, int stereopair, std::string &seedJsonPath)
static std::string buildDic3DCombinedFilePath(const std::string pathdir, int num_pair, const std::string &ext)
static void getCamerasForPair(int stereopair, int &cam_first, int &cam_second)
Get camera numbers for a given stereopair using the registered table (setCameraPairs).
Lightweight, dependency-free logging facility for CPPxDIC.
cv::Mat distortion_coeffs
Base parameters structure Contains fundamental paths and settings for DIC analysis Equivalent to base...
std::string baseResultPath
static std::vector< std::pair< int, int > > g_camera_pairs
static std::string find_video_file(const std::string &video_dir, const std::string &subject, const std::string &material, const std::string &trialname, int cam_id)
static bool ensure_dir(const std::string &path)
static const std::vector< std::pair< int, int > > kLegacyCameraPairs
std::string padNumberWithZeros(int num, int n)