CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
utils.cpp
Go to the documentation of this file.
1
5#include "utils.h"
6#include "logging.h"
7#include <iostream>
8#include <filesystem>
9#include <regex>
10#include <sstream>
11#include <iomanip>
12#include <fstream>
13// Video IO
14#include <opencv2/opencv.hpp>
15// MAT file IO
16#include <matio.h>
17// JSON
18#include <nlohmann/json.hpp>
19#include <string>
20#include <string_view>
21#include <algorithm>
22
23bool Utils::dicCheck(const Config& config) {
24 LOG_INFO << "Checking data and protocol...";
25
26 bool success = true;
27
28 // Check parallel processing requirements
29 if (config.parallel_processing) {
30 LOG_INFO << "Parallel processing is enabled";
31
32 if (!checkROIReferences(config)) {
33 success = false;
34 }
35
36 if (!checkSeedReferences(config)) {
37 success = false;
38 }
39 }
40
41 // Check protocol files
42 if (!checkProtocolFiles(config)) {
43 success = false;
44 }
45
46 // Check calibration files
47 if (!checkCalibrationFiles(config)) {
48 success = false;
49 }
50
51 if (success) {
52 LOG_INFO << "...done Checking.";
53 }
54
55 return success;
56}
57
58bool Utils::checkCalibrationFiles(const Config& config) {
59 try {
60 std::string calib_dir = Utils::buildCalibDir(config);
61 if (!std::filesystem::exists(calib_dir)) {
62 LOG_ERROR << "Calibration directory not found: " << calib_dir;
63 return false;
64 }
65
66 // Check for all camera pairs based on config.num_pair
67 std::vector<bool> cam_found(config.num_pair * 2, false);
68
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);
74
75 // Check for each camera (1 through num_pair*2)
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;
82 }
83 }
84 }
85
86 // Check if all cameras were found
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";
91 all_found = false;
92 }
93 }
94
95 if (!all_found) {
96 LOG_ERROR << "Some calibration files missing in " << calib_dir;
97 return false;
98 }
99
100 LOG_INFO << "Calibration files found for all " << (config.num_pair * 2) << " cameras in "
101 << calib_dir;
102 return true;
103 } catch (...) {
104 LOG_ERROR << "Error while checking calibration files";
105 return false;
106 }
107}
108
109bool Utils::checkROIReferences(const Config& config) {
110 LOG_INFO << "Checking ROI References...";
111
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;
115
116 std::string roifile =
117 Utils::likeRoiPath(Utils::buildOutputUntilMaterialDir(config), ref_trial_id_oss.str(),
118 config.phase_id, pair_i, ".mat");
119
120 if (!fileExists(roifile)) {
121 LOG_ERROR << "This file " << 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 "
125 << config.ref_trial_id << " for the subject " << config.subject_id
126 << " not found.";
127 return false;
128 }
129 }
130
131 return true;
132}
133
134bool Utils::checkSeedReferences(const Config& config) {
135 LOG_INFO << "Checking SEED References...";
136
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;
140
141 std::string seedfile =
142 Utils::likeSeedPath(Utils::buildOutputUntilMaterialDir(config), ref_trial_id_oss.str(),
143 config.phase_id, pair_i, ".mat");
144
145 if (!fileExists(seedfile)) {
146 LOG_ERROR << "This file " << 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 "
150 << config.ref_trial_id << " for the subject " << config.subject_id
151 << " not found.";
152 return false;
153 }
154 }
155
156 return true;
157}
158
159bool Utils::checkProtocolFiles(const Config& config) {
160 std::string protocol_dir = Utils::buildProtocolDir(config, true, true, true, true);
161
162 auto protocol_files = findFiles(protocol_dir, "*.mat");
163
164 if (protocol_files.empty()) {
165 // The protocol .mat drives (a) automatic reference-trial selection and
166 // (b) the protocol-derived frame window. Both have explicit config
167 // substitutes (ref_trial_id > 0 and idx_frame_start/end), so a missing
168 // protocol is only fatal when the reference trial must be inferred.
169 if (config.ref_trial_id > 0) {
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 ("
173 << config.idx_frame_start << ".." << config.idx_frame_end << ").";
174 return true;
175 }
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.";
179 return false;
180 }
181
182 return true;
183}
184
185bool Utils::fileExists(const std::string& path) {
186 return std::filesystem::exists(path) && std::filesystem::is_regular_file(path);
187}
188
189bool Utils::directoryExists(const std::string& path) {
190 return std::filesystem::exists(path) && std::filesystem::is_directory(path);
191}
192
193std::vector<std::string> Utils::findFiles(const std::string& directory,
194 const std::string& pattern) {
195 std::vector<std::string> files;
196
197 if (!directoryExists(directory)) {
198 return files;
199 }
200
201 try {
202 // Convert glob pattern to regex
203 std::string regex_pattern = pattern;
204 std::replace(regex_pattern.begin(), regex_pattern.end(), '*', '.');
205 regex_pattern = ".*" + regex_pattern + ".*";
206
207 std::regex file_regex(regex_pattern);
208
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());
214 }
215 }
216 }
217 } catch (const std::exception& e) {
218 LOG_ERROR << "Error searching for files: " << e.what();
219 }
220
221 return files;
222}
223
224std::vector<std::string> Utils::split(const std::string& str, char delimiter) {
225 std::vector<std::string> tokens;
226 std::stringstream ss(str);
227 std::string token;
228
229 while (std::getline(ss, token, delimiter)) {
230 tokens.push_back(token);
231 }
232
233 return tokens;
234}
235
236// Process-wide stereopair table. Defaults to the historical 4-camera rig
237// (pair1 = cams 1,2 ; pair2 = cams 4,3) so callers that never register a
238// table (tests, tools) keep the legacy behaviour. main.cpp / DicAnalysis
239// register Config::camera_pairs so that the dic_params.txt `camera_pairs`
240// key is honoured everywhere (Step D import/tracking, Step E DLT lookup,
241// checkpoint paths...). Before this registry existed the config key was parsed
242// but silently ignored: every pair >= 3 was mapped to cameras (1,2).
243static const std::vector<std::pair<int, int>> kLegacyCameraPairs = {{1, 2}, {4, 3}};
244static std::vector<std::pair<int, int>> g_camera_pairs = kLegacyCameraPairs;
245
246void Utils::setCameraPairs(const std::vector<std::pair<int, int>>& camera_pairs) {
247 g_camera_pairs = camera_pairs.empty() ? kLegacyCameraPairs : camera_pairs;
248}
249
250const std::vector<std::pair<int, int>>& Utils::cameraPairs() {
251 return g_camera_pairs;
252}
253
254void Utils::getCamerasForPair(int stereopair, int& cam_first, int& cam_second) {
255 getCamerasForPair(stereopair, g_camera_pairs, cam_first, cam_second);
256}
257
258void Utils::getCamerasForPair(int stereopair, const std::vector<std::pair<int, int>>& camera_pairs,
259 int& cam_first, int& cam_second) {
260 if (stereopair < 1) {
261 throw std::runtime_error("Invalid stereopair value: must be >= 1");
262 }
263 int idx = stereopair - 1; // 0-indexed
264 if (idx < static_cast<int>(camera_pairs.size())) {
265 cam_first = camera_pairs[idx].first;
266 cam_second = camera_pairs[idx].second;
267 } else {
268 // Beyond the configured table: generic mapping pair N -> cameras (2N-1, 2N).
269 cam_first = 2 * stereopair - 1;
270 cam_second = 2 * stereopair;
271 }
272}
273
274static bool ensure_dir(const std::string& path) {
275 try {
276 if (!std::filesystem::exists(path)) {
277 return std::filesystem::create_directories(path);
278 }
279 return true;
280 } catch (...) {
281 return false;
282 }
283}
284
285// ============================================================================
286// Video ingestion (Section 3a)
287// ----------------------------------------------------------------------------
288// CPPXDIC ingests one .mp4 file per camera (port of MATLAB import_raw_vid.m).
289//
290// Expected container / codec:
291// - Container : MP4 (.mp4). Files are matched by the regex naming convention
292// <subject>_<material>_speckles_<trial:3 digits>_*_cam_<id>.mp4
293// e.g. S09_coating_speckles_007_545_050_trial005_cam_1.mp4
294// - Codec : any codec OpenCV's VideoCapture can decode via the system FFmpeg
295// backend (H.264/AVC is the tested codec). If VideoCapture cannot
296// open the file, importRawVid logs an error and returns false.
297// - Pixels : speckle videos are stored as RGB; to match MATLAB's iloc(:,:,1)
298// we extract ONLY the Red channel (BGR index 2 in OpenCV) as the
299// grayscale frame. Single-channel inputs are used as-is.
300// - Frames : 1-based frame indices (MATLAB convention); frameStart is clamped
301// to >= 1 and frameEnd to <= min(frame_count of both cameras).
302//
303// Frames are decoded to per-camera PNGs under
304// <dic_path>/<subject>/<material>/tmp_frames/T<trial>/pair<n>/cam<id>/ ,
305// which the DIC engine then consumes.
306// ============================================================================
307static std::string find_video_file(const std::string& video_dir, const std::string& subject,
308 const std::string& material, const std::string& trialname,
309 int cam_id) {
310 if (!Utils::directoryExists(video_dir)) return "";
311 // Strict pattern (historical naming):
312 // <subject>_<material>_speckles_<trialname with 3 digits>_.*_cam_<cam_id>.mp4
313 // e.g. S09_coating_speckles_007_545_050_trial005_cam_1.mp4
314 // Relaxed fallback (other acquisition rigs, e.g. MNG "<subj>_<finger>_<block>_<trial>_..."):
315 // any file carrying "_<trialname>_" and ending in "_cam_<cam_id>.mp4" — the camera id
316 // is anchored so cam_1 never matches cam_10 / cam_1_0.
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$");
320
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());
325 }
326 std::sort(names.begin(), names.end()); // deterministic choice when several match
327
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;
335 }
336 return (std::filesystem::path(video_dir) / name).string();
337 }
338 }
339 }
340 return "";
341}
342
343std::string padNumberWithZeros(int num, int n) {
344 std::ostringstream oss;
345 oss << std::setw(n) << std::setfill('0') << num;
346 return oss.str();
347}
348
349bool Utils::importRawVid(const Config& config, int trial, int stereopair, int frameStart,
350 int frameEnd, int frameJump, std::vector<std::string>& cam1Frames,
351 std::vector<std::string>& cam2Frames, int maxFrames) {
352 try {
353 int cam_first = 0, cam_second = 0;
354 getCamerasForPair(stereopair, cam_first, cam_second);
355
356 std::string trialname = padNumberWithZeros(trial, 3);
357 std::string video_dir = Utils::buildVideoDir(config, true, true, true, true);
358
359 std::string vid1 =
360 find_video_file(video_dir, config.subject_id, config.material, trialname, cam_first);
361 std::string vid2 =
362 find_video_file(video_dir, config.subject_id, config.material, trialname, cam_second);
363 if (vid1.empty() || vid2.empty()) {
364 LOG_ERROR << "Video files not found for trial=" << trial << " pair=" << stereopair;
365 return false;
366 }
367
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;
372 return false;
373 }
374
375 LOG_INFO << "Video loaded successfully.";
376 LOG_INFO << "Video 1: " << vid1;
377 LOG_INFO << "Video 2: " << vid2;
378
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;
385
386 // Limit to the first maxFrames selected frames when requested (e.g. only
387 // the reference frame is needed), avoiding a full second video decode.
388 if (maxFrames > 0) {
389 int limitedEnd = frameStart + (maxFrames - 1) * frameJump;
390 frameEnd = std::min(frameEnd, limitedEnd);
391 }
392
393 // Output directory
394 std::ostringstream odir;
395 odir << config.dic_path << "/" << config.subject_id << "/" << config.material
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);
400 if (!ensure_dir(cam1_dir) || !ensure_dir(cam2_dir)) {
401 LOG_ERROR << "Failed to create frame directories";
402 return false;
403 }
404
405 // Extract frames - MATLAB's readvid uses iloc(:,:,1) which takes only the first channel (R)
406 // OpenCV reads as BGR, so we need to extract the R channel (index 2 in BGR)
407 //
408 // Performance: read sequentially instead of seeking before every frame.
409 // On the FFmpeg backend (Linux) cap.set(CAP_PROP_POS_FRAMES, n) seeks to
410 // the previous keyframe and re-decodes forward on every call, turning a
411 // linear read into ~O(N * GOP) work and re-decoding the same frames many
412 // times. We seek once to the start, then read forward; when frameJump > 1
413 // the skipped frames are grabbed (decoded but not converted to Mat).
414 if (frameStart > 1) {
415 cap1.set(cv::CAP_PROP_POS_FRAMES, frameStart - 1);
416 cap2.set(cv::CAP_PROP_POS_FRAMES, frameStart - 1);
417 }
418
419 int cur = frameStart; // 1-based index of the frame the next read()/grab() yields
420 bool eof = false;
421 for (int f = frameStart; f <= frameEnd && !eof; f += frameJump) {
422 // Skip intermediate frames (frameJump > 1) without decoding to Mat.
423 while (cur < f) {
424 if (!cap1.grab() || !cap2.grab()) { eof = true; break; }
425 ++cur;
426 }
427 if (eof) break;
428
429 cv::Mat im1, im2;
430 if (!cap1.read(im1) || !cap2.read(im2)) break;
431 ++cur;
432
433 // Extract R channel to match MATLAB's iloc(:,:,1)
434 // OpenCV reads as BGR, so R is at index 2
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]; // R channel (BGR -> index 2)
440 } else {
441 gray1 = im1;
442 }
443
444 if (im2.channels() == 3) {
445 std::vector<cv::Mat> channels2;
446 cv::split(im2, channels2);
447 gray2 = channels2[2]; // R channel (BGR -> index 2)
448 } else {
449 gray2 = im2;
450 }
451
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());
459 }
460
461 return !cam1Frames.empty() && !cam2Frames.empty();
462 } catch (const std::exception& e) {
463 LOG_ERROR << "importRawVid error: " << e.what();
464 return false;
465 }
466}
467
468bool Utils::importVid(const Config& config, int trial, int stereopair,
469 std::vector<std::string>& cam1Frames, std::vector<std::string>& cam2Frames,
470 int maxFrames) {
471 auto calc_ranges = [](const std::string& phase, int nLoad, int nSlide, int nRelax, int& s,
472 int& e) {
473 if (phase == "loading") {
474 s = 1;
475 e = s + nLoad - 1;
476 return true;
477 }
478 if (phase == "slide1") {
479 s = nLoad + 1;
480 e = s + nSlide - 1;
481 return true;
482 }
483 if (phase == "relax1") {
484 s = nLoad + nSlide + 1;
485 e = s + nRelax - 1;
486 return true;
487 }
488 if (phase == "slide2") {
489 s = nLoad + nSlide + nRelax + 1;
490 e = s + nSlide - 1;
491 return true;
492 }
493 if (phase == "relax2") {
494 s = nLoad + 2 * nSlide + nRelax + 1;
495 e = s + nRelax - 1;
496 return true;
497 }
498 if (phase == "all") {
499 s = 1;
500 e = nLoad + 2 * nSlide + 2 * nRelax;
501 return true;
502 }
503 return false;
504 };
505
506 int frameStart = config.idx_frame_start;
507 int frameEnd = config.idx_frame_end;
508 int frameJump = config.frame_jump;
509
510 // Read protocol to compute ranges if available. With force_frame_window=true
511 // the config window is authoritative and the protocol is not consulted.
512 try {
513 std::string protocol_dir = Utils::buildProtocolDir(config, true, true, true, true);
514
515 auto protos = config.force_frame_window ? std::vector<std::string>{}
516 : Utils::findFiles(protocol_dir, "*.mat");
517 if (config.force_frame_window) {
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";
521 }
522 if (!protos.empty()) {
523 std::string proto_file = protos.front();
524 mat_t* matfp = Mat_Open(proto_file.c_str(), MAT_ACC_RDONLY);
525 if (matfp) {
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);
537 d3 = dd[2];
538 d5 = dd[4];
539 } else {
540 const float* ff = static_cast<const float*>(dur->data);
541 d3 = ff[2];
542 d5 = ff[4];
543 }
544 int fps = 50;
545 nLoad = static_cast<int>(std::round(d3 / 1e3 * fps));
546 // Slide duration depends on dst/spd
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];
553 };
554 double dst = 0.0, spd = 0.0;
555 if (ncond >= 5) {
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];
563 }
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];
569 }
570 }
571 if (spd > 0.0)
572 nSlide = static_cast<int>(std::round(dst / spd * fps));
573 else
574 nSlide = 0;
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)) {
578 frameStart = s;
579 frameEnd = e;
580 }
581 }
582 }
583 }
584 if (cond) Mat_VarFree(cond);
585 Mat_Close(matfp);
586 }
587 }
588 } catch (...) {
589 // Fallback to config frames silently
590 }
591
592 return importRawVid(config, trial, stereopair, frameStart, frameEnd, frameJump, cam1Frames,
593 cam2Frames, maxFrames);
594}
595
596bool Utils::loadROIFromMat(const Config& config, int trial, int stereopair,
597 std::string& roiJsonPath, std::string& roiMaskImagePath) {
598 try {
599 std::ostringstream matname;
600 matname << config.dic_path << "/" << config.subject_id << "/" << config.material
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();
604 if (!fileExists(mat_path)) {
605 roiJsonPath.clear();
606 roiMaskImagePath.clear();
607 return false;
608 }
609 cv::Mat maskImg;
610 mat_t* matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
611 if (matfp) {
612 // Read exact Matlab variable name 'refmask'
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;
631 }
632 maskImg = m.clone();
633 }
634 if (mv) Mat_VarFree(mv);
635 Mat_Close(matfp);
636 }
637 std::ostringstream jsonname;
638 jsonname << config.dic_path << "/" << config.subject_id << "/" << config.material
639 << "/REF_MASK_" << config.subject_id << "_" << config.material << "_"
640 << config.phase_id << "_T" << trial << "_pair" << stereopair << ".json";
641 roiJsonPath = jsonname.str();
642 // If mask found, write mask image alongside JSON
643 roiMaskImagePath.clear();
644 if (!maskImg.empty()) {
645 std::ostringstream pm;
646 pm << config.dic_path << "/" << config.subject_id << "/" << config.material
647 << "/REF_MASK_" << config.subject_id << "_" << config.material << "_"
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();
652 }
653 }
654 nlohmann::json j;
655 j["trial"] = trial;
656 j["pair"] = stereopair;
657 j["phase"] = config.phase_id;
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;
662 return true;
663 } catch (...) {
664 roiJsonPath.clear();
665 roiMaskImagePath.clear();
666 return false;
667 }
668}
669
670bool Utils::loadSeedFromMat(const Config& config, int trial, int stereopair,
671 std::string& seedJsonPath) {
672 try {
673 std::ostringstream matname;
674 matname << config.dic_path << "/" << config.subject_id << "/" << config.material
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();
678 if (!fileExists(mat_path)) {
679 seedJsonPath.clear();
680 return false;
681 }
682 std::vector<std::string> varnames;
683 std::vector<std::pair<double, double>> seeds;
684 double radius = static_cast<double>(config.subregion_radius);
685 mat_t* matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
686 if (matfp) {
687 // Enumerate variables and collect candidates
688 matvar_t* vinfo = nullptr;
689 std::vector<std::string> candidates;
690 while ((vinfo = Mat_VarReadNextInfo(matfp)) != nullptr) {
691 if (vinfo->name) {
692 varnames.emplace_back(vinfo->name);
693 std::string n(vinfo->name);
694 std::string nl = n;
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") {
699 // handled later when reading
700 }
701 }
702 Mat_VarFree(vinfo);
703 }
704 Mat_Close(matfp);
705 // Reopen to read
706 matfp = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
707 if (matfp) {
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;
711 bool ok = 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]);
720 ok = true;
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]);
725 ok = true;
726 }
727 }
728 }
729 Mat_VarFree(mv);
730 return ok;
731 };
732 bool got = false;
733 for (const auto& c : candidates) {
734 if (try_read_points(c)) {
735 got = true;
736 break;
737 }
738 }
739 // Also try common names
740 if (!got) {
741 try_read_points("seeds");
742 }
743 if (!got) {
744 try_read_points("seed");
745 }
746 if (!got) {
747 try_read_points("xy");
748 }
749 // Try read radius
750 auto try_read_radius = [&](const std::string& name) {
751 matvar_t* mv = Mat_VarRead(matfp, name.c_str());
752 if (!mv) return;
753 if (mv->data) {
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];
760 }
761 Mat_VarFree(mv);
762 };
763 try_read_radius("radius");
764 try_read_radius("r");
765 Mat_Close(matfp);
766 }
767 }
768 std::ostringstream jsonname;
769 jsonname << config.dic_path << "/" << config.subject_id << "/" << config.material
770 << "/REF_SEED_" << config.subject_id << "_" << config.material << "_"
771 << config.phase_id << "_T" << trial << "_pair" << stereopair << ".json";
772 seedJsonPath = jsonname.str();
773 std::ofstream ofs(seedJsonPath);
774 ofs << "{\n";
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 << ", ";
783 }
784 ofs << "],\n";
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 << ", ";
790 }
791 ofs << "]\n";
792 ofs << "}\n";
793 return true;
794 } catch (...) {
795 seedJsonPath.clear();
796 return false;
797 }
798}
799
800// ============================================================================
801// Camera Calibration and Distortion Removal
802// ============================================================================
803
804bool Utils::loadCameraParameters(const std::string& mat_path, CameraParameters& params) {
805 params.is_valid = false;
806
807 if (!fileExists(mat_path)) {
808 LOG_ERROR << "Camera parameters file not found: " << mat_path;
809 return false;
810 }
811
812 try {
813 mat_t* mat_file = Mat_Open(mat_path.c_str(), MAT_ACC_RDONLY);
814 if (!mat_file) {
815 LOG_ERROR << "Failed to open MAT file: " << mat_path;
816 return false;
817 }
818
819 // Read 'cameraCBparameters' structure
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";
823 Mat_Close(mat_file);
824 return false;
825 }
826
827 // Get 'cameraParameters' field
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);
832 Mat_Close(mat_file);
833 return false;
834 }
835
836 // Read IntrinsicMatrix (3x3)
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);
840 // MATLAB stores in column-major, OpenCV uses row-major
841 // IntrinsicMatrix in MATLAB: [fx 0 0; 0 fy 0; cx cy 1]
842 params.camera_matrix = cv::Mat(3, 3, CV_64F);
843 params.camera_matrix.at<double>(0, 0) = data[0]; // fx
844 params.camera_matrix.at<double>(0, 1) = data[3]; // skew (usually 0)
845 params.camera_matrix.at<double>(0, 2) = data[6]; // cx
846 params.camera_matrix.at<double>(1, 0) = data[1]; // 0
847 params.camera_matrix.at<double>(1, 1) = data[4]; // fy
848 params.camera_matrix.at<double>(1, 2) = data[7]; // cy
849 params.camera_matrix.at<double>(2, 0) = data[2]; // 0
850 params.camera_matrix.at<double>(2, 1) = data[5]; // 0
851 params.camera_matrix.at<double>(2, 2) = data[8]; // 1
852 } else {
853 LOG_ERROR << "IntrinsicMatrix not found or invalid";
854 Mat_VarFree(cb_struct);
855 Mat_Close(mat_file);
856 return false;
857 }
858
859 // Read RadialDistortion (1x2 or 1x3)
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);
864 size_t n = 1;
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]);
868 }
869 }
870
871 // Read TangentialDistortion (1x2)
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);
876 size_t n = 1;
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]);
880 }
881 }
882
883 // OpenCV distortion format: [k1, k2, p1, p2, k3, k4, k5, k6]
884 // MATLAB: RadialDistortion=[k1, k2, k3], TangentialDistortion=[p1, p2]
885 params.distortion_coeffs = cv::Mat::zeros(1, 5, CV_64F);
886 if (dist_coeffs.size() >= 1)
887 params.distortion_coeffs.at<double>(0, 0) = dist_coeffs[0]; // k1
888 if (dist_coeffs.size() >= 2)
889 params.distortion_coeffs.at<double>(0, 1) = dist_coeffs[1]; // k2
890 if (tan_coeffs.size() >= 1) params.distortion_coeffs.at<double>(0, 2) = tan_coeffs[0]; // p1
891 if (tan_coeffs.size() >= 2) params.distortion_coeffs.at<double>(0, 3) = tan_coeffs[1]; // p2
892 if (dist_coeffs.size() >= 3)
893 params.distortion_coeffs.at<double>(0, 4) = dist_coeffs[2]; // k3
894
895 Mat_VarFree(cb_struct);
896 Mat_Close(mat_file);
897
898 params.is_valid = true;
899 LOG_INFO << "Loaded camera parameters from: " << mat_path;
900 LOG_DEBUG << " Intrinsic matrix: " << params.camera_matrix;
901 LOG_DEBUG << " Distortion coeffs: " << params.distortion_coeffs;
902
903 return true;
904
905 } catch (const std::exception& e) {
906 LOG_ERROR << "Exception loading camera parameters: " << e.what();
907 return false;
908 }
909}
910
911void Utils::undistortPoints(const std::vector<cv::Point2d>& points_in,
912 const CameraParameters& params, std::vector<cv::Point2d>& points_out) {
913 if (!params.is_valid) {
914 LOG_WARN << "Invalid camera parameters, cannot undistort points";
915 points_out = points_in;
916 return;
917 }
918
919 if (points_in.empty()) {
920 points_out.clear();
921 return;
922 }
923
924 // OpenCV's undistortPoints outputs normalized coordinates, we need pixel coordinates
925 // Use undistortImagePoints or manual approach
926
927 points_out.resize(points_in.size());
928
929 for (size_t i = 0; i < points_in.size(); ++i) {
930 points_out[i] = undistortPoint(points_in[i], params);
931 }
932}
933
934cv::Point2d Utils::undistortPoint(const cv::Point2d& point_in, const CameraParameters& params) {
935 if (!params.is_valid) {
936 return point_in;
937 }
938
939 // Extract camera parameters
940 double fx = params.camera_matrix.at<double>(0, 0);
941 double fy = params.camera_matrix.at<double>(1, 1);
942 double cx = params.camera_matrix.at<double>(0, 2);
943 double cy = params.camera_matrix.at<double>(1, 2);
944
945 double k1 = params.distortion_coeffs.at<double>(0, 0);
946 double k2 = params.distortion_coeffs.at<double>(0, 1);
947 double p1 = params.distortion_coeffs.at<double>(0, 2);
948 double p2 = params.distortion_coeffs.at<double>(0, 3);
949 double k3 = params.distortion_coeffs.at<double>(0, 4);
950
951 // Convert to normalized coordinates
952 double x = (point_in.x - cx) / fx;
953 double y = (point_in.y - cy) / fy;
954
955 // Iterative undistortion (Newton-Raphson)
956 double x_u = x;
957 double y_u = y;
958
959 for (int iter = 0; iter < 10; ++iter) {
960 double r2 = x_u * x_u + y_u * y_u;
961 double r4 = r2 * r2;
962 double r6 = r4 * r2;
963
964 // Radial distortion
965 double radial = 1.0 + k1 * r2 + k2 * r4 + k3 * r6;
966
967 // Tangential distortion
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;
970
971 // Distorted coordinates
972 double x_d = x_u * radial + dx_tan;
973 double y_d = y_u * radial + dy_tan;
974
975 // Update undistorted estimate
976 double error_x = x_d - x;
977 double error_y = y_d - y;
978
979 if (std::abs(error_x) < 1e-10 && std::abs(error_y) < 1e-10) {
980 break;
981 }
982
983 // Jacobian-based update (simplified)
984 x_u -= error_x * 0.9;
985 y_u -= error_y * 0.9;
986 }
987
988 // Convert back to pixel coordinates
989 double x_out = x_u * fx + cx;
990 double y_out = y_u * fy + cy;
991
992 return cv::Point2d(x_out, y_out);
993}
994
995// ============================================================================
996// DLT11 Calibration (matches MATLAB DLT11Calibration.m)
997// ============================================================================
998
999bool Utils::DLT11Calibration(const double* P2, const double* P3, size_t N, std::vector<double>& L) {
1000 if (N < 6) {
1001 LOG_ERROR << "DLT11Calibration: need at least 6 point correspondences, got " << N;
1002 return false;
1003 }
1004
1005 // Build the 2N x 11 matrix M and 2N x 1 vector b (P2array)
1006 // MATLAB:
1007 // M(1:2:2*N-1,:) = [P3 ones(N,1) zeros(N,4) -u.*P3]
1008 // M(2:2:2*N,:) = [zeros(N,4) P3 ones(N,1) -v.*P3]
1009 // P2array(1:2:2*N-1) = u
1010 // P2array(2:2:2*N) = v
1011 // L = M \ P2array
1012 Eigen::MatrixXd M = Eigen::MatrixXd::Zero(2 * N, 11);
1013 Eigen::VectorXd b(2 * N);
1014
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];
1021
1022 // Odd row (2i): [X Y Z 1 0 0 0 0 -u*X -u*Y -u*Z]
1023 size_t r1 = 2 * i;
1024 M(r1, 0) = X;
1025 M(r1, 1) = Y;
1026 M(r1, 2) = Z;
1027 M(r1, 3) = 1.0;
1028 M(r1, 8) = -u * X;
1029 M(r1, 9) = -u * Y;
1030 M(r1, 10) = -u * Z;
1031 b(r1) = u;
1032
1033 // Even row (2i+1): [0 0 0 0 X Y Z 1 -v*X -v*Y -v*Z]
1034 size_t r2 = 2 * i + 1;
1035 M(r2, 4) = X;
1036 M(r2, 5) = Y;
1037 M(r2, 6) = Z;
1038 M(r2, 7) = 1.0;
1039 M(r2, 8) = -v * X;
1040 M(r2, 9) = -v * Y;
1041 M(r2, 10) = -v * Z;
1042 b(r2) = v;
1043 }
1044
1045 // Solve via least squares: L = M \ b
1046 Eigen::VectorXd result = M.colPivHouseholderQr().solve(b);
1047
1048 L.resize(11);
1049 for (int i = 0; i < 11; ++i) {
1050 L[i] = result(i);
1051 }
1052
1053 return true;
1054}
1055
1056// ============================================================================
1057// Rigid Body Motion (RBM) Transformation
1058// ============================================================================
1059
1060bool Utils::computeRigidTransform(const std::vector<Eigen::Vector3d>& points_from,
1061 const std::vector<Eigen::Vector3d>& points_to,
1062 RigidTransform& transform) {
1063 transform.is_valid = false;
1064
1065 if (points_from.size() != points_to.size()) {
1066 LOG_ERROR << "Point clouds must have same size for rigid transformation";
1067 return false;
1068 }
1069
1070 if (points_from.size() < 3) {
1071 LOG_ERROR << "Need at least 3 points for rigid transformation";
1072 return false;
1073 }
1074
1075 // Filter out NaN points (matching MATLAB lines 34-42)
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]);
1081 }
1082 }
1083
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";
1088 return false;
1089 }
1090
1091 // Compute centroids (MATLAB lines 44-45)
1092 Eigen::Vector3d centroid_from = Eigen::Vector3d::Zero();
1093 Eigen::Vector3d centroid_to = Eigen::Vector3d::Zero();
1094
1095 for (const auto& p : from_no_nan) {
1096 centroid_from += p;
1097 }
1098 for (const auto& p : to_no_nan) {
1099 centroid_to += p;
1100 }
1101
1102 centroid_from /= from_no_nan.size();
1103 centroid_to /= to_no_nan.size();
1104
1105 // Center point clouds (MATLAB lines 47-48)
1106 std::vector<Eigen::Vector3d> da, db;
1107 for (const auto& p : from_no_nan) {
1108 da.push_back(p - centroid_from);
1109 }
1110 for (const auto& p : to_no_nan) {
1111 db.push_back(p - centroid_to);
1112 }
1113
1114 // Compute cross-correlation matrix M = db^T * da (MATLAB line 53)
1115 Eigen::Matrix3d M = Eigen::Matrix3d::Zero();
1116 for (size_t i = 0; i < da.size(); ++i) {
1117 M += db[i] * da[i].transpose();
1118 }
1119
1120 // SVD decomposition (MATLAB line 54)
1121 Eigen::JacobiSVD<Eigen::Matrix3d> svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV);
1122 Eigen::Matrix3d U = svd.matrixU();
1123 Eigen::Matrix3d V = svd.matrixV();
1124
1125 // Compute rotation ensuring it's a proper rotation (det = +1) (MATLAB lines 55-56)
1126 double det_UV = (U * V.transpose()).determinant();
1127 Eigen::Matrix3d S = Eigen::Matrix3d::Identity();
1128 S(2, 2) = det_UV; // Set diagonal element to det(U*V') to ensure proper rotation
1129
1130 transform.R = U * S * V.transpose();
1131
1132 // Compute translation (MATLAB line 57)
1133 transform.t = centroid_to - transform.R * centroid_from;
1134
1135 transform.is_valid = true;
1136
1137 return true;
1138}
1139
1140std::vector<Eigen::Vector3d> Utils::applyRigidTransform(
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());
1144
1145 if (!transform.is_valid) {
1146 LOG_WARN << "Invalid transform, returning original points";
1147 return points_in;
1148 }
1149
1150 // Apply transformation: p_out = R * p_in + t (MATLAB line 71)
1151 for (const auto& p : points_in) {
1152 if (p.hasNaN()) {
1153 points_out.push_back(p); // Keep NaN points as is
1154 } else {
1155 points_out.push_back(transform.R * p + transform.t);
1156 }
1157 }
1158
1159 return points_out;
1160}
1161
1162std::string Utils::buildBaseDataPath(const Config& config, bool with_data_or_dic_path,
1163 bool with_rawdata, bool with_speckles) {
1164 std::string path = "";
1165
1166 if (with_data_or_dic_path) {
1167 path += config.data_path;
1168
1169 if (with_rawdata) {
1170 path += "/rawdata";
1171 }
1172
1173 path += "/" + config.subject_id;
1174
1175 if (with_speckles) {
1176 path += "/speckles";
1177 }
1178 } else {
1179 path += config.dic_path;
1180 path += "/" + config.subject_id;
1181 }
1182
1183 return path;
1184}
1185
1186std::string Utils::buildOutputUntilTrialDir(const Config& config, int trial) {
1187 std::ostringstream output_ss;
1188 output_ss << buildBaseDataPath(config, false, false, false);
1189 output_ss << "/" << config.material;
1190 output_ss << "/" << std::setw(3) << std::setfill('0') << trial;
1191 output_ss << "/";
1192 return output_ss.str();
1193}
1194
1195std::string Utils::buildOutputUntilPhaseDir(const Config& config, int trial) {
1196 std::ostringstream output_ss;
1197 output_ss << buildBaseDataPath(config, false, false, false);
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();
1202}
1203
1204std::string Utils::buildPath(const Config& config, bool with_data_or_dic_path, bool with_rawdata,
1205 bool with_speckles, bool with_material, bool with_video,
1206 bool with_protocol, bool with_calib) {
1207 std::string path =
1208 buildBaseDataPath(config, with_data_or_dic_path, with_rawdata, with_speckles);
1209
1210 if (with_material) {
1211 path += "/" + config.material;
1212 }
1213
1214 if (with_video) {
1215 path += "/vid";
1216 }
1217
1218 if (with_protocol) {
1219 path += "/protocol";
1220 }
1221
1222 if (with_calib) {
1223 path += "/calib/" + config.calib_folder_set;
1224 }
1225
1226 return path;
1227}
1228
1229std::string Utils::buildUntilMaterialDir(const Config& config, bool with_data_or_dic_path,
1230 bool with_rawdata, bool with_speckles) {
1231 std::string path =
1232 buildPath(config, with_data_or_dic_path, with_rawdata, with_speckles, true, false, false);
1233
1234 return path + "/";
1235}
1236
1237std::string Utils::buildOutputUntilMaterialDir(const Config& config) {
1238 return buildUntilMaterialDir(config, false, false, false);
1239}
1240
1241std::string Utils::buildProtocolDir(const Config& config, bool with_data_or_dic_path,
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);
1245
1246 return path + "/";
1247}
1248
1249std::string Utils::buildVideoDir(const Config& config, bool with_data_or_dic_path,
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);
1253
1254 return path + "/";
1255}
1256
1257std::string Utils::buildCalibDir(const Config& config) {
1258 std::string path = buildPath(config, false, false, false, false, false, false, true);
1259
1260 return path + "/";
1261}
1262
1263std::string Utils::buildPath(const cppxdic::BaseParameters& parameters, bool with_material,
1264 bool with_trial, bool with_phase, bool with_cache) {
1265 std::string path = parameters.baseResultPath + "/" + parameters.subject;
1266
1267 if (with_material) {
1268 path += "/" + parameters.material;
1269 }
1270
1271 if (with_trial) {
1272 path += "/" + parameters.trial;
1273 }
1274
1275 if (with_phase) {
1276 path += "/" + parameters.phase;
1277 }
1278
1279 if (with_cache) {
1280 path += "/.cache";
1281 }
1282
1283 return path;
1284}
1285
1286std::string Utils::buildOutputPath(const cppxdic::BaseParameters& parameters) {
1287 return buildPath(parameters, true, true, true, false);
1288}
1289
1291 return buildPath(parameters, true, true, true, true);
1292}
1293
1294std::string Utils::likeRoiOrSeedPath(const std::string& path, const std::string_view& prefix,
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;
1299
1300 return oss.str();
1301}
1302
1303std::string Utils::likeRoiPath(const std::string& path, const std::string& reftrial,
1304 const std::string& phase, int stereopair,
1305 const std::string& extension) {
1306 return likeRoiOrSeedPath(path, "REF_MASK_", reftrial, phase, stereopair, extension);
1307}
1308
1309std::string Utils::likeSeedPath(const std::string& path, const std::string& reftrial,
1310 const std::string& phase, int stereopair,
1311 const std::string& extension) {
1312 return likeRoiOrSeedPath(path, "REF_SEED_", reftrial, phase, stereopair, extension);
1313}
1314
1315std::string Utils::buildRoiOrSeedLikeFilePath(const cppxdic::BaseParameters& parameters,
1316 std::string reftrial, int stereopair,
1317 std::string_view prefix,
1318 const std::string& extension) {
1319 return likeRoiOrSeedPath(buildPath(parameters, true, false, false, false), prefix, reftrial,
1320 parameters.phase, stereopair, extension);
1321}
1322
1323std::string Utils::buildRoiFilePath(const cppxdic::BaseParameters& parameters, std::string reftrial,
1324 int stereopair, const std::string& extension) {
1325 return buildRoiOrSeedLikeFilePath(parameters, reftrial, stereopair, "REF_MASK_", extension);
1326}
1327
1329 std::string reftrial, int stereopair,
1330 const std::string& extension) {
1331 return buildRoiOrSeedLikeFilePath(parameters, reftrial, stereopair, "REF_SEED_", extension);
1332}
1333
1335 std::string reftrial, int stereopair,
1336 const std::string& extension) {
1337 return parameters.outputPath + "/MATCHING2" + reftrial + "_pair" + std::to_string(stereopair) +
1338 extension;
1339}
1340
1341std::string Utils::buildDic3DCombinedFilePath(const std::string pathdir, int num_pair,
1342 const std::string& ext) {
1343 return pathdir + "DIC3Dcombined_" + std::to_string(num_pair) + "Pairs_stitched" + ext;
1344}
1345
1346std::string Utils::buildDic3DPPresultsFilePath(const std::string pathdir, int num_pair,
1347 const std::string& fileversion,
1348 const std::string& ext) {
1349 return pathdir + "DIC3DPPresults_" + std::to_string(num_pair) + "Pairs_cum_" + fileversion +
1350 ext;
1351}
1352
1353std::string Utils::buildVizPath(const std::string pathdir, const std::string filename) {
1354 return pathdir + "viz/" + filename;
1355}
1356
1357std::string Utils::buildDic2DPairResultsFilePath(const std::string pathdir, int cam_1, int cam_2,
1358 const std::string& extension) {
1359 return pathdir + "/myDIC2DpairResults_C_" + std::to_string(cam_1) + "_C_" +
1360 std::to_string(cam_2) + extension;
1361}
1362
1363std::string Utils::buildNcorrFilePath(const std::string pathdir, int cam_1, int cam_2,
1364 const std::string& extension) {
1365 if (cam_2 == -1) {
1366 return pathdir + "/ncorr" + std::to_string(cam_1) + extension;
1367 }
1368 return pathdir + "/ncorr" + std::to_string(cam_1) + std::to_string(cam_2) + extension;
1369}
int ref_trial_id
Definition config.h:75
std::string dic_path
Definition config.h:48
std::string subject_id
Definition config.h:65
std::string material
Definition config.h:258
int idx_frame_start
Definition config.h:78
bool parallel_processing
Definition config.h:62
int num_pair
Definition config.h:34
int subregion_radius
Definition config.h:131
std::string phase_id
Definition config.h:66
int frame_jump
Definition config.h:80
std::string calib_folder_set
Definition config.h:72
int idx_frame_end
Definition config.h:79
std::string data_path
Definition config.h:47
bool force_frame_window
Definition config.h:60
static cv::Point2d undistortPoint(const cv::Point2d &point_in, const CameraParameters &params)
Undistort single 2D point.
Definition utils.cpp:934
static std::string buildNcorrFilePath(const std::string pathdir, int cam_1, int cam_2=-1, const std::string &extension=".bin")
Definition utils.cpp:1363
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.
Definition utils.cpp:1249
static void setCameraPairs(const std::vector< std::pair< int, int > > &camera_pairs)
Register the process-wide stereopair -> (cam_first, cam_second) table.
Definition utils.cpp:246
static std::string likeRoiPath(const std::string &path, const std::string &reftrial, const std::string &phase, int stereopair, const std::string &extension)
Definition utils.cpp:1303
static bool fileExists(const std::string &path)
Definition utils.cpp:185
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)
Definition utils.cpp:349
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.
Definition utils.cpp:1204
static std::string likeSeedPath(const std::string &path, const std::string &reftrial, const std::string &phase, int stereopair, const std::string &extension)
Definition utils.cpp:1309
static std::string buildOutputCachePath(const cppxdic::BaseParameters &parameters)
Build the output cache path.
Definition utils.cpp:1290
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.
Definition utils.cpp:1162
static std::vector< std::string > split(const std::string &str, char delimiter)
Definition utils.cpp:224
static std::vector< std::string > findFiles(const std::string &directory, const std::string &pattern)
Definition utils.cpp:193
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)
Definition utils.cpp:1294
static std::string buildDic3DPPresultsFilePath(const std::string pathdir, int num_pair, const std::string &fileversion, const std::string &ext)
Definition utils.cpp:1346
static std::string buildDic2DPairResultsFilePath(const std::string pathdir, int cam_1, int cam_2, const std::string &extension)
Definition utils.cpp:1357
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.
Definition utils.cpp:1241
static std::string buildRoiFilePath(const cppxdic::BaseParameters &parameters, std::string reftrial, int stereopair, const std::string &extension=".mat")
Build the roi file path.
Definition utils.cpp:1323
static bool importVid(const Config &config, int trial, int stereopair, std::vector< std::string > &cam1Frames, std::vector< std::string > &cam2Frames, int maxFrames=0)
Definition utils.cpp:468
static std::string buildSeedFilePath(const cppxdic::BaseParameters &parameters, std::string reftrial, int stereopair, const std::string &extension=".mat")
Build the seed file path.
Definition utils.cpp:1328
static std::string buildCalibDir(const Config &config)
Build the calib path.
Definition utils.cpp:1257
static bool dicCheck(const Config &config)
Utility functions implementation for CPPXDIC.
Definition utils.cpp:23
static std::string buildVizPath(const std::string pathdir, const std::string filename)
Definition utils.cpp:1353
static void undistortPoints(const std::vector< cv::Point2d > &points_in, const CameraParameters &params, std::vector< cv::Point2d > &points_out)
Undistort 2D points using camera parameters Matches MATLAB: undistortPoints(points,...
Definition utils.cpp:911
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...
Definition utils.cpp:1060
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...
Definition utils.cpp:999
static std::string buildOutputUntilPhaseDir(const Config &config, int trial)
Build output dir until the Phase folder.
Definition utils.cpp:1195
static const std::vector< std::pair< int, int > > & cameraPairs()
Currently registered stereopair table (see setCameraPairs).
Definition utils.cpp:250
static std::string buildOutputUntilMaterialDir(const Config &config)
Build output path until material folder.
Definition utils.cpp:1237
static std::string buildOutputUntilTrialDir(const Config &config, int trial)
Build output dir until Trial folder.
Definition utils.cpp:1186
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.
Definition utils.cpp:1140
static std::string buildMatchingFilePath(const cppxdic::BaseParameters &parameters, std::string reftrial, int stereopair, const std::string &extension=".bin")
Build the matching file path.
Definition utils.cpp:1334
static std::string buildUntilMaterialDir(const Config &config, bool with_data_or_dic_path, bool with_rawdata, bool with_speckles)
Build path until material folder.
Definition utils.cpp:1229
static bool directoryExists(const std::string &path)
Definition utils.cpp:189
static bool loadROIFromMat(const Config &config, int trial, int stereopair, std::string &roiJsonPath, std::string &roiMaskImagePath)
Definition utils.cpp:596
static bool loadCameraParameters(const std::string &mat_path, CameraParameters &params)
Load camera calibration parameters from MATLAB .mat file Matches MATLAB: load('cameraCBparameters_cam...
Definition utils.cpp:804
static std::string buildOutputPath(const cppxdic::BaseParameters &parameters)
Build the output path.
Definition utils.cpp:1286
static bool loadSeedFromMat(const Config &config, int trial, int stereopair, std::string &seedJsonPath)
Definition utils.cpp:670
static std::string buildDic3DCombinedFilePath(const std::string pathdir, int num_pair, const std::string &ext)
Definition utils.cpp:1341
static void getCamerasForPair(int stereopair, int &cam_first, int &cam_second)
Get camera numbers for a given stereopair using the registered table (setCameraPairs).
Definition utils.cpp:254
Lightweight, dependency-free logging facility for CPPxDIC.
#define LOG_DEBUG
Definition logging.h:169
#define LOG_ERROR
Definition logging.h:172
#define LOG_WARN
Definition logging.h:171
#define LOG_INFO
Definition logging.h:170
cv::Mat distortion_coeffs
Definition utils.h:67
cv::Mat camera_matrix
Definition utils.h:66
Eigen::Vector3d t
Definition utils.h:114
Eigen::Matrix3d R
Definition utils.h:113
Base parameters structure Contains fundamental paths and settings for DIC analysis Equivalent to base...
Definition parameters.h:20
std::string baseResultPath
Definition parameters.h:23
static std::vector< std::pair< int, int > > g_camera_pairs
Definition utils.cpp:244
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)
Definition utils.cpp:307
static bool ensure_dir(const std::string &path)
Definition utils.cpp:274
static const std::vector< std::pair< int, int > > kLegacyCameraPairs
Definition utils.cpp:243
std::string padNumberWithZeros(int num, int n)
Definition utils.cpp:343