CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
mat_reader.cpp
Go to the documentation of this file.
1
5#include "mat_reader.h"
6#include "dic_structures.h"
7#include "logging.h"
8#include <iostream>
9#include <matio.h>
10#include <sstream>
11#include <cstring>
12#include <algorithm>
13#include <cstdint>
14#include <functional>
15
16namespace cppxdic {
17
18// ============================================================================
19// RAII convenience helpers
20// ============================================================================
21
22MatReader::MatFilePtr MatReader::openMat(const std::string& path) {
23 return MatFilePtr(Mat_Open(path.c_str(), MAT_ACC_RDONLY));
24}
25
26MatReader::MatVarPtr MatReader::readVar(mat_t* matfp, const std::string& name) {
27 if (!matfp) return MatVarPtr{};
28 return MatVarPtr(Mat_VarRead(matfp, name.c_str()));
29}
30
32 if (!matfp) return MatVarPtr{};
33 return MatVarPtr(Mat_VarRead(matfp, nullptr));
34}
35
36// ============================================================================
37// Internal helpers
38// ============================================================================
39
40matvar_t* MatReader::getStructField(matvar_t* s, const char* field, size_t index) noexcept {
41 if (!s || s->class_type != MAT_C_STRUCT) return nullptr;
42 return Mat_VarGetStructFieldByName(s, field, index);
43}
44
45bool MatReader::readTopInt(mat_t* matfp, const char* name, int& out) noexcept {
46 out = 0;
47 MatVarPtr v(Mat_VarRead(matfp, name));
48 if (!v || !v->data) return false;
49 out = static_cast<int>(readScalar(v.get()));
50 return true;
51}
52
53bool MatReader::readTopDouble(mat_t* matfp, const char* name, double& out) noexcept {
54 out = 0.0;
55 MatVarPtr v(Mat_VarRead(matfp, name));
56 if (!v || !v->data) return false;
57 out = readScalar(v.get());
58 return true;
59}
60
61bool MatReader::readTopString(mat_t* matfp, const char* name, std::string& out) {
62 out.clear();
63 MatVarPtr v(Mat_VarRead(matfp, name));
64 if (!v || !v->data) return false;
65 out = readString(v.get());
66 return true;
67}
68
69bool MatReader::readStructInt(matvar_t* s, const char* field, int& out, size_t index) noexcept {
70 out = 0;
71 matvar_t* f = getStructField(s, field, index);
72 if (!f || !f->data) return false;
73 out = static_cast<int>(readScalar(f));
74 return true;
75}
76
77bool MatReader::readStructDouble(matvar_t* s, const char* field, double& out, size_t index) noexcept {
78 out = 0.0;
79 matvar_t* f = getStructField(s, field, index);
80 if (!f || !f->data) return false;
81 out = readScalar(f);
82 return true;
83}
84
85bool MatReader::readStructBool(matvar_t* s, const char* field, bool& out, size_t index) noexcept {
86 out = false;
87 matvar_t* f = getStructField(s, field, index);
88 if (!f || !f->data) return false;
89 out = static_cast<int>(readScalar(f)) != 0;
90 return true;
91}
92
93bool MatReader::readStructString(matvar_t* s, const char* field, std::string& out, size_t index) {
94 out.clear();
95 matvar_t* f = getStructField(s, field, index);
96 if (!f || !f->data) return false;
97 out = readString(f);
98 return true;
99}
100
101// ============================================================================
102// DLT Calibration Loading
103// ============================================================================
104
105bool MatReader::loadDLTCalibration(const std::string& mat_path, DLTCalibrationData& calib) {
106 auto matfp = openMat(mat_path);
107 if (!matfp) {
108 LOG_ERROR << "Failed to open DLT calibration file: " << mat_path;
109 return false;
110 }
111
112 auto dlt_var = readVar(matfp.get(), "DLTstructCam");
113 if (!dlt_var || dlt_var->class_type != MAT_C_STRUCT) {
114 LOG_ERROR << "Variable 'DLTstructCam' not found or not a struct in " << mat_path;
115 return false;
116 }
117
118 calib.filePath = mat_path;
119
120 // DLTparams (float64 1d array, 11 elements)
121 if (matvar_t* f = getStructField(dlt_var.get(), "DLTparams", 0)) {
122 calib.DLTparams = readDoubleArray(f);
123 }
124 if (calib.DLTparams.size() != 11) {
125 LOG_ERROR << "DLTparams has " << calib.DLTparams.size() << " elements (expected 11) in " << mat_path;
126 return false;
127 }
128
129 // indCam (int scalar)
130 readStructInt(dlt_var.get(), "indCam", calib.indCam);
131
132 // imageCentroids (float64 Nx2, column-major in MATLAB)
133 if (matvar_t* f = getStructField(dlt_var.get(), "imageCentroids", 0)) {
134 if (f->data && f->class_type == MAT_C_DOUBLE && f->rank >= 2) {
135 size_t rows = f->dims[0];
136 size_t cols = f->dims[1];
137 calib.imageCentroids_rows = rows;
138 const double* d = static_cast<const double*>(f->data);
139 // Convert column-major to row-major (Nx2)
140 calib.imageCentroids.resize(rows * cols);
141 for (size_t r = 0; r < rows; ++r) {
142 for (size_t c = 0; c < cols; ++c) {
143 calib.imageCentroids[r * cols + c] = d[c * rows + r];
144 }
145 }
146 }
147 }
148
149 // columns (uint8 1d array)
150 if (matvar_t* f = getStructField(dlt_var.get(), "columns", 0)) {
151 if (f->data) {
152 size_t n = 1;
153 for (int i = 0; i < f->rank; ++i) n *= f->dims[i];
154 if (f->class_type == MAT_C_UINT8) {
155 const uint8_t* d = static_cast<const uint8_t*>(f->data);
156 calib.columns.assign(d, d + n);
157 } else if (f->class_type == MAT_C_DOUBLE) {
158 const double* d = static_cast<const double*>(f->data);
159 calib.columns.resize(n);
160 for (size_t i = 0; i < n; ++i) calib.columns[i] = static_cast<uint8_t>(d[i]);
161 }
162 }
163 }
164
165 // C3Dtrue (float64 3d array dim0 x dim1 x 3, column-major)
166 if (matvar_t* f = getStructField(dlt_var.get(), "C3Dtrue", 0)) {
167 if (f->data && f->class_type == MAT_C_DOUBLE && f->rank >= 3) {
168 size_t d0 = f->dims[0];
169 size_t d1 = f->dims[1];
170 size_t d2 = f->dims[2]; // should be 3
171 calib.C3Dtrue_dim0 = d0;
172 calib.C3Dtrue_dim1 = d1;
173 const double* src = static_cast<const double*>(f->data);
174 // Convert column-major (d0 x d1 x d2) to row-major
175 calib.C3Dtrue.resize(d0 * d1 * d2);
176 for (size_t k = 0; k < d2; ++k) {
177 for (size_t j = 0; j < d1; ++j) {
178 for (size_t i = 0; i < d0; ++i) {
179 calib.C3Dtrue[(i * d1 + j) * d2 + k] = src[k * d1 * d0 + j * d0 + i];
180 }
181 }
182 }
183 }
184 }
185
186 LOG_INFO << "Loaded DLT calibration: cam " << calib.indCam
187 << ", " << calib.DLTparams.size() << " DLT params"
188 << ", " << calib.imageCentroids_rows << " centroids"
189 << " from " << mat_path;
190
191 return true;
192}
193
194// ============================================================================
195// Protocol Loading
196// ============================================================================
197
198bool MatReader::loadProtocol(const std::string& protocol_path, ProtocolFileData& protocol) {
199 auto matfp = openMat(protocol_path);
200 if (!matfp) {
201 LOG_ERROR << "Failed to open protocol file: " << protocol_path;
202 return false;
203 }
204
205 // Read 'cond' structure (or fallback to 'protocol')
206 MatVarPtr cond_var(Mat_VarRead(matfp.get(), "cond"));
207 if (!cond_var) {
208 cond_var.reset(Mat_VarRead(matfp.get(), "protocol"));
209 }
210
211 if (!cond_var || cond_var->class_type != MAT_C_STRUCT) {
212 return false;
213 }
214
215 // Read titles field (cell array of strings)
216 if (matvar_t* titles_var = getStructField(cond_var.get(), "titles", 0)) {
217 if (titles_var->class_type == MAT_C_CELL) {
218 protocol.titles = readCellStrings(titles_var);
219 }
220 }
221
222 // Read table field
223 matvar_t* table_var = getStructField(cond_var.get(), "table", 0);
224 if (!table_var || table_var->class_type != MAT_C_CELL || table_var->rank < 2) {
225 return false;
226 }
227
228 const size_t n_trials = table_var->dims[0];
229 const size_t n_fields = table_var->dims[1];
230
231 protocol.n_trials = n_trials;
232 protocol.n_fields = n_fields;
233 protocol.trials.clear();
234 protocol.trials.reserve(n_trials);
235
236 // Find column indices for important fields
237 int dir_idx = -1, nf_idx = -1, spd_idx = -1, rep_idx = -1;
238 for (size_t i = 0; i < protocol.titles.size(); ++i) {
239 if (protocol.titles[i] == "dir") dir_idx = static_cast<int>(i);
240 else if (protocol.titles[i] == "nf") nf_idx = static_cast<int>(i);
241 else if (protocol.titles[i] == "spd") spd_idx = static_cast<int>(i);
242 else if (protocol.titles[i] == "rep") rep_idx = static_cast<int>(i);
243 }
244
245 // Read trial data
246 for (size_t trial = 0; trial < n_trials; ++trial) {
248 info.trial_number = static_cast<int>(trial + 1);
249
250 auto readCellAt = [&](size_t col) -> matvar_t* {
251 // Mat_VarGetCell uses linear index = row + col*rows for MATLAB-style 2D cells.
252 // Your prior code used trial*n_fields + col (row-major). That *can* be wrong depending on matio layout.
253 // To be safe, use matio’s Mat_VarGetCell with MATLAB linear indexing:
254 // idx = trial + col*n_trials
255 const size_t idx = trial + col * n_trials;
256 return Mat_VarGetCell(table_var, idx);
257 };
258
259 // direction
260 if (dir_idx >= 0) {
261 matvar_t* cell = readCellAt(static_cast<size_t>(dir_idx));
262 if (cell && cell->class_type == MAT_C_CHAR && cell->data) {
263 std::string s(static_cast<char*>(cell->data), cell->nbytes);
264 s.erase(s.find_last_not_of(" \t\n\r") + 1);
265 info.direction = std::move(s);
266 }
267 }
268
269 // force
270 if (nf_idx >= 0) {
271 matvar_t* cell = readCellAt(static_cast<size_t>(nf_idx));
272 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
273 info.force = *static_cast<double*>(cell->data);
274 }
275 }
276
277 // speed
278 if (spd_idx >= 0) {
279 matvar_t* cell = readCellAt(static_cast<size_t>(spd_idx));
280 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
281 info.speed = *static_cast<double*>(cell->data);
282 }
283 }
284
285 // repetition
286 if (rep_idx >= 0) {
287 matvar_t* cell = readCellAt(static_cast<size_t>(rep_idx));
288 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
289 info.repetition = static_cast<int>(*static_cast<double*>(cell->data));
290 }
291 }
292
293 protocol.trials.push_back(std::move(info));
294 }
295
296 return true; // matfp auto-closed
297}
298
299std::vector<std::string> MatReader::readCellStrings(matvar_t* cell_var) {
300 std::vector<std::string> strings;
301 if (!cell_var || cell_var->class_type != MAT_C_CELL) return strings;
302
303 const size_t n_cells = getCellArraySize(cell_var);
304 strings.reserve(n_cells);
305
306 for (size_t i = 0; i < n_cells; ++i) {
307 matvar_t* cell = Mat_VarGetCell(cell_var, i);
308 if (cell && cell->class_type == MAT_C_CHAR && cell->data) {
309 std::string str(static_cast<char*>(cell->data), cell->nbytes);
310 str.erase(str.find_last_not_of(" \t\n\r") + 1);
311 strings.push_back(std::move(str));
312 } else {
313 strings.emplace_back();
314 }
315 }
316 return strings;
317}
318
319// ============================================================================
320// Struct field readers (legacy raw-pointer API preserved)
321// ============================================================================
322
323matvar_t* MatReader::readStructField(mat_t* matfp,
324 const std::string& struct_name,
325 const std::string& field_name) {
326 if (!matfp) return nullptr;
327
328 MatVarPtr struct_var(Mat_VarRead(matfp, struct_name.c_str()));
329 if (!struct_var || struct_var->class_type != MAT_C_STRUCT) return nullptr;
330
331 matvar_t* field = Mat_VarGetStructFieldByName(struct_var.get(), field_name.c_str(), 0);
332 if (!field) return nullptr;
333
334 // Deep copy, caller frees
335 return Mat_VarDuplicate(field, 1);
336}
337
338matvar_t* MatReader::readNestedField(mat_t* matfp, const std::string& path) {
339 if (!matfp) return nullptr;
340
341 // Split path by '.'
342 std::vector<std::string> parts;
343 std::istringstream iss(path);
344 std::string part;
345 while (std::getline(iss, part, '.')) parts.push_back(part);
346 if (parts.empty()) return nullptr;
347
348 MatVarPtr current(Mat_VarRead(matfp, parts[0].c_str()));
349 if (!current) return nullptr;
350
351 for (size_t i = 1; i < parts.size(); ++i) {
352 if (current->class_type != MAT_C_STRUCT) return nullptr;
353
354 matvar_t* field = Mat_VarGetStructFieldByName(current.get(), parts[i].c_str(), 0);
355 if (!field) return nullptr;
356
357 MatVarPtr next(Mat_VarDuplicate(field, 1)); // deep copy
358 current.swap(next);
359 }
360
361 return current.release(); // legacy API
362}
363
364// ============================================================================
365// Primitive readers (kept mostly as-is, small safety tweaks)
366// ============================================================================
367
368std::vector<double> MatReader::readDoubleArray(matvar_t* var) {
369 std::vector<double> result;
370 if (!var || !var->data) return result;
371
372 if (var->class_type != MAT_C_DOUBLE) {
373 LOG_WARN << "Variable is not double type";
374 return result;
375 }
376
377 size_t n_elements = 1;
378 for (int i = 0; i < var->rank; ++i) n_elements *= var->dims[i];
379
380 const double* data = static_cast<const double*>(var->data);
381 result.assign(data, data + n_elements);
382 return result;
383}
384
385std::vector<double> MatReader::readDouble2DArray(matvar_t* var, size_t& rows, size_t& cols) {
386 std::vector<double> result;
387 rows = 0;
388 cols = 0;
389
390 if (!var || !var->data || var->rank < 2) return result;
391
392 if (var->class_type != MAT_C_DOUBLE) {
393 LOG_WARN << "Variable is not double type";
394 return result;
395 }
396
397 rows = var->dims[0];
398 cols = var->dims[1];
399
400 const double* data = static_cast<const double*>(var->data);
401 const size_t n_elements = rows * cols;
402
403 // MATLAB column-major -> row-major
404 result.resize(n_elements);
405 for (size_t r = 0; r < rows; ++r) {
406 for (size_t c = 0; c < cols; ++c) {
407 result[r * cols + c] = data[c * rows + r];
408 }
409 }
410 return result;
411}
412
413cv::Mat MatReader::readImage(matvar_t* var) {
414 if (!var || !var->data) return cv::Mat();
415
416 if (var->rank != 2) {
417 LOG_WARN << "Expected 2D array for image";
418 return cv::Mat();
419 }
420
421 const size_t rows = var->dims[0];
422 const size_t cols = var->dims[1];
423
424 if (var->class_type == MAT_C_DOUBLE) {
425 const double* data = static_cast<const double*>(var->data);
426 cv::Mat img(rows, cols, CV_64F);
427 for (size_t r = 0; r < rows; ++r) {
428 for (size_t c = 0; c < cols; ++c) {
429 img.at<double>(r, c) = data[c * rows + r];
430 }
431 }
432 return img;
433 }
434
435 if (var->class_type == MAT_C_UINT8) {
436 const uint8_t* data = static_cast<const uint8_t*>(var->data);
437 cv::Mat img(rows, cols, CV_8U);
438
439 const bool is_logical = (var->isLogical != 0);
440 for (size_t r = 0; r < rows; ++r) {
441 for (size_t c = 0; c < cols; ++c) {
442 const uint8_t value = data[c * rows + r];
443 img.at<uint8_t>(r, c) = is_logical ? (value * 255) : value;
444 }
445 }
446 return img;
447 }
448
449 LOG_WARN << "Unsupported image data type";
450 return cv::Mat();
451}
452
453double MatReader::readScalar(matvar_t* var) {
454 if (!var || !var->data) return 0.0;
455
456 if (var->class_type == MAT_C_DOUBLE) {
457 return static_cast<const double*>(var->data)[0];
458 }
459 if (var->class_type == MAT_C_INT32) {
460 return static_cast<double>(static_cast<const int32_t*>(var->data)[0]);
461 }
462 if (var->class_type == MAT_C_UINT8) {
463 return static_cast<double>(static_cast<const uint8_t*>(var->data)[0]);
464 }
465
466 LOG_WARN << "Unsupported scalar type";
467 return 0.0;
468}
469
470std::string MatReader::readString(matvar_t* var) {
471 if (!var || !var->data) return "";
472
473 size_t len = 1;
474 for (int i = 0; i < var->rank; ++i) len *= var->dims[i];
475
476 if (var->class_type == MAT_C_CHAR) {
477 return std::string(static_cast<const char*>(var->data), len);
478 }
479
480 if (var->class_type == MAT_C_UINT16) {
481 const uint16_t* data = static_cast<const uint16_t*>(var->data);
482 std::string out;
483 out.reserve(len);
484 for (size_t i = 0; i < len; ++i) out.push_back(static_cast<char>(data[i]));
485 return out;
486 }
487
488 LOG_WARN << "Unsupported string type";
489 return "";
490}
491
492size_t MatReader::getCellArraySize(matvar_t* var) {
493 if (!var || var->class_type != MAT_C_CELL) return 0;
494
495 size_t n_cells = 1;
496 for (int i = 0; i < var->rank; ++i) n_cells *= var->dims[i];
497 return n_cells;
498}
499
500matvar_t* MatReader::getCellElement(matvar_t* var, size_t index) {
501 if (!var || var->class_type != MAT_C_CELL) return nullptr;
502 const size_t n_cells = getCellArraySize(var);
503 if (index >= n_cells) return nullptr;
504
505 // Safer than peeking into var->data:
506 return Mat_VarGetCell(var, index);
507}
508
509// ============================================================================
510// Higher-level readers
511// ============================================================================
512
513std::map<std::string, double> MatReader::readDispInfo(mat_t* matfp) {
514 std::map<std::string, double> params;
515 if (!matfp) return params;
516
517 MatVarPtr dispinfo(readNestedField(matfp, "data_dic_save.dispinfo"));
518 if (!dispinfo || dispinfo->class_type != MAT_C_STRUCT) return params;
519
520 const std::vector<std::string> field_names = {
521 "cutoff_corrcoef", "cutoff_diffnorm", "cutoff_iteration",
522 "radius", "spacing", "subsettrunc", "total_threads"
523 };
524
525 for (const auto& field : field_names) {
526 matvar_t* field_var = Mat_VarGetStructFieldByName(dispinfo.get(), field.c_str(), 0);
527 if (field_var && field_var->data) {
528 params[field] = readScalar(field_var);
529 }
530 }
531
532 return params;
533}
534
535bool MatReader::variableExists(mat_t* matfp, const std::string& var_name) {
536 if (!matfp) return false;
537 MatVarPtr var(Mat_VarRead(matfp, var_name.c_str()));
538 return static_cast<bool>(var);
539}
540
541cv::Mat MatReader::readROIMask(const std::string& mat_path, const std::string& var_name) {
542 auto matfp = openMat(mat_path);
543 if (!matfp) {
544 LOG_ERROR << "Failed to open MAT file: " << mat_path;
545 return cv::Mat();
546 }
547
548 auto var = readVar(matfp.get(), var_name);
549 if (!var) {
550 LOG_ERROR << "Variable '" << var_name << "' not found in " << mat_path;
551 return cv::Mat();
552 }
553
554 return readImage(var.get());
555}
556
557bool MatReader::readDIC3Dcombined(const std::string& mat_path, DIC3Dcombined& combined) {
558 auto matfp = openMat(mat_path);
559 if (!matfp) {
560 LOG_ERROR << "Failed to open MAT file: " << mat_path;
561 return false;
562 }
563
564 // Read first variable, else try common names
565 MatVarPtr dic3d_var = readFirstVar(matfp.get());
566 if (!dic3d_var) {
567 const char* var_names[] = {"DIC3Dcombined", "DIC3D", "data"};
568 for (const char* name : var_names) {
569 dic3d_var.reset(Mat_VarRead(matfp.get(), name));
570 if (dic3d_var) break;
571 }
572 }
573
574 if (!dic3d_var || dic3d_var->class_type != MAT_C_STRUCT) {
575 LOG_ERROR << "DIC3Dcombined structure not found in " << mat_path;
576 return false;
577 }
578
579 auto ensure_loaded = [&](matvar_t* var, const char* label) {
580 if (!var) {
581 return false;
582 }
583 if (Mat_VarReadDataAll(matfp.get(), var) != 0) {
584 if (!var->data && var->class_type != MAT_C_STRUCT && var->class_type != MAT_C_CELL) {
585 LOG_WARN << "failed to fully load '" << label << "' from " << mat_path;
586 }
587 }
588 return true;
589 };
590
591 // HDF5 (v7.3) reads defer nested field/cell data — historically each field
592 // parser grew its own ensure_loaded call as it was found empty (Faces was
593 // still missing one, making Step F see "0 faces" and abort). Force-load
594 // the whole tree once instead of relying on per-field patchwork.
595 std::function<void(matvar_t*)> deep_load = [&](matvar_t* v) {
596 if (!v) return;
597 Mat_VarReadDataAll(matfp.get(), v);
598 size_t ne = 1;
599 for (int i = 0; i < v->rank; ++i) ne *= v->dims[i];
600 if (v->class_type == MAT_C_STRUCT) {
601 unsigned nf = Mat_VarGetNumberOfFields(v);
602 char* const* names = Mat_VarGetStructFieldnames(v);
603 for (size_t e = 0; e < ne; ++e)
604 for (unsigned i = 0; i < nf; ++i)
605 deep_load(Mat_VarGetStructFieldByName(v, names[i], e));
606 } else if (v->class_type == MAT_C_CELL) {
607 for (size_t e = 0; e < ne; ++e) deep_load(Mat_VarGetCell(v, static_cast<int>(e)));
608 }
609 };
610 deep_load(dic3d_var.get());
611
612 // Faces (Nx3) — handles both INT32 (C++-written) and DOUBLE (MATLAB-written)
613 if (matvar_t* faces_var = getStructField(dic3d_var.get(), "Faces", 0)) {
614 if (faces_var->data && faces_var->rank >= 2) {
615 const size_t dim0 = faces_var->dims[0];
616 const size_t dim1 = faces_var->dims[1];
617 // Determine nFaces: dims could be {nFaces,3} or {3,nFaces}
618 size_t nFaces = (dim1 == 3) ? dim0 : dim1;
619
620 combined.Faces.clear();
621 combined.Faces.reserve(nFaces * 3);
622
623 if (faces_var->class_type == MAT_C_INT32) {
624 // INT32: written by C++ writer (row-major, 1-indexed after fix)
625 const int32_t* d = static_cast<const int32_t*>(faces_var->data);
626 if (dim1 == 3) {
627 // {nFaces, 3} row-major layout from C++ writer
628 for (size_t i = 0; i < nFaces * 3; ++i) {
629 combined.Faces.push_back(d[i] - 1);
630 }
631 } else {
632 // {3, nFaces} — transpose
633 for (size_t r = 0; r < nFaces; ++r) {
634 for (size_t c = 0; c < 3; ++c) {
635 combined.Faces.push_back(d[c * nFaces + r] - 1);
636 }
637 }
638 }
639 } else if (faces_var->class_type == MAT_C_DOUBLE) {
640 // DOUBLE: written by MATLAB (column-major, 1-indexed)
641 const double* d = static_cast<const double*>(faces_var->data);
642 if (dim1 == 3) {
643 // {nFaces, 3} column-major: col0[nFaces], col1[nFaces], col2[nFaces]
644 for (size_t r = 0; r < nFaces; ++r) {
645 combined.Faces.push_back(static_cast<int>(d[r]) - 1);
646 combined.Faces.push_back(static_cast<int>(d[r + nFaces]) - 1);
647 combined.Faces.push_back(static_cast<int>(d[r + 2 * nFaces]) - 1);
648 }
649 } else {
650 // {3, nFaces} column-major
651 for (size_t r = 0; r < nFaces; ++r) {
652 for (size_t c = 0; c < 3; ++c) {
653 combined.Faces.push_back(static_cast<int>(d[c + r * 3]) - 1);
654 }
655 }
656 }
657 } else {
658 LOG_WARN << "Unexpected Faces class_type " << faces_var->class_type;
659 }
660 LOG_DEBUG << "Loaded " << nFaces << " faces (type=" << faces_var->class_type
661 << ", dims=" << dim0 << "x" << dim1 << ")";
662 }
663 }
664
665 // Points3D (cell array: each cell is either
666 // - C++ format: 1x1 struct with fields x, y, z (each Nx1 double)
667 // - MATLAB format: Nx3 double matrix (column-major: col0=x, col1=y, col2=z)
668 if (matvar_t* points3d_var = getStructField(dic3d_var.get(), "Points3D", 0)) {
669 ensure_loaded(points3d_var, "Points3D");
670 if (points3d_var->class_type == MAT_C_CELL) {
671 const size_t nFrames = getCellArraySize(points3d_var);
672 combined.Points3D.resize(nFrames);
673
674 for (size_t frame = 0; frame < nFrames; ++frame) {
675 matvar_t* frame_var = Mat_VarGetCell(points3d_var, frame);
676 if (!frame_var) continue;
677 ensure_loaded(frame_var, "Points3D cell");
678
679 if (frame_var->class_type == MAT_C_STRUCT) {
680 // C++ format: struct with x, y, z fields
681 matvar_t* x_var = getStructField(frame_var, "x", 0);
682 matvar_t* y_var = getStructField(frame_var, "y", 0);
683 matvar_t* z_var = getStructField(frame_var, "z", 0);
684 ensure_loaded(x_var, "Points3D.x");
685 ensure_loaded(y_var, "Points3D.y");
686 ensure_loaded(z_var, "Points3D.z");
687
688 if (!x_var || !y_var || !z_var ||
689 x_var->class_type != MAT_C_DOUBLE || !x_var->data ||
690 y_var->class_type != MAT_C_DOUBLE || !y_var->data ||
691 z_var->class_type != MAT_C_DOUBLE || !z_var->data) {
692 continue;
693 }
694
695 combined.Points3D[frame].x = readDoubleArray(x_var);
696 combined.Points3D[frame].y = readDoubleArray(y_var);
697 combined.Points3D[frame].z = readDoubleArray(z_var);
698 } else if (frame_var->class_type == MAT_C_DOUBLE && frame_var->data && frame_var->rank >= 2) {
699 const size_t dim0 = frame_var->dims[0];
700 const size_t dim1 = frame_var->dims[1];
701 const double* d = static_cast<const double*>(frame_var->data);
702 if (dim1 == 3) {
703 // {nPts, 3} column-major: col0=x[0..nPts-1], col1=y, col2=z
704 const size_t nPts = dim0;
705 combined.Points3D[frame].x.assign(d, d + nPts);
706 combined.Points3D[frame].y.assign(d + nPts, d + 2 * nPts);
707 combined.Points3D[frame].z.assign(d + 2 * nPts, d + 3 * nPts);
708 } else if (dim0 == 3) {
709 // {3, nPts} column-major: interleaved [x0,y0,z0,x1,y1,z1,...]
710 const size_t nPts = dim1;
711 combined.Points3D[frame].x.resize(nPts);
712 combined.Points3D[frame].y.resize(nPts);
713 combined.Points3D[frame].z.resize(nPts);
714 for (size_t p = 0; p < nPts; ++p) {
715 combined.Points3D[frame].x[p] = d[p * 3 + 0];
716 combined.Points3D[frame].y[p] = d[p * 3 + 1];
717 combined.Points3D[frame].z[p] = d[p * 3 + 2];
718 }
719 } else {
720 LOG_WARN << "Points3D frame " << frame << " has dims "
721 << dim0 << "x" << dim1 << " (expected Nx3 or 3xN) in " << mat_path;
722 continue;
723 }
724 } else {
725 continue;
726 }
727
728 // Consistency check
729 if (combined.Points3D[frame].x.size() != combined.Points3D[frame].y.size() ||
730 combined.Points3D[frame].x.size() != combined.Points3D[frame].z.size()) {
731 LOG_WARN << "inconsistent x/y/z sizes in Points3D frame "
732 << frame << " in " << mat_path;
733
734 combined.Points3D[frame].x.clear();
735 combined.Points3D[frame].y.clear();
736 combined.Points3D[frame].z.clear();
737 }
738 }
739 if (!combined.Points3D.empty() && !combined.Points3D[0].x.empty()) {
740 LOG_DEBUG << "Loaded Points3D: " << nFrames << " frames, "
741 << combined.Points3D[0].x.size() << " points per frame";
742 }
743 }
744 }
745
746 // corrComb (cell array: each cell is Nx1 double vector, one per frame)
747 if (matvar_t* corr_var = getStructField(dic3d_var.get(), "corrComb", 0)) {
748 ensure_loaded(corr_var, "corrComb");
749 if (corr_var->class_type == MAT_C_CELL) {
750 const size_t nFrames = getCellArraySize(corr_var);
751 combined.corrComb.resize(nFrames);
752 for (size_t frame = 0; frame < nFrames; ++frame) {
753 matvar_t* cell = Mat_VarGetCell(corr_var, frame);
754 ensure_loaded(cell, "corrComb cell");
755 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
756 combined.corrComb[frame] = readDoubleArray(cell);
757 }
758 }
759 }
760 }
761
762 // FaceCorrComb (cell array: each cell is Mx1 double vector, one per frame)
763 if (matvar_t* face_corr_var = getStructField(dic3d_var.get(), "FaceCorrComb", 0)) {
764 if (face_corr_var->class_type == MAT_C_CELL) {
765 const size_t nFrames = getCellArraySize(face_corr_var);
766 combined.FaceCorrComb.resize(nFrames);
767 for (size_t frame = 0; frame < nFrames; ++frame) {
768 matvar_t* cell = Mat_VarGetCell(face_corr_var, frame);
769 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
770 combined.FaceCorrComb[frame] = readDoubleArray(cell);
771 }
772 }
773 }
774 }
775
776 // FaceCentroids (cell array: each cell is Mx3 or 3xM double)
777 // Internal format: interleaved [x0,y0,z0, x1,y1,z1, ...]
778 if (matvar_t* fc_var = getStructField(dic3d_var.get(), "FaceCentroids", 0)) {
779 if (fc_var->class_type == MAT_C_CELL) {
780 const size_t nFrames = getCellArraySize(fc_var);
781 combined.FaceCentroids.resize(nFrames);
782 for (size_t frame = 0; frame < nFrames; ++frame) {
783 matvar_t* cell = Mat_VarGetCell(fc_var, frame);
784 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data && cell->rank >= 2) {
785 size_t dim0 = cell->dims[0];
786 size_t dim1 = cell->dims[1];
787 const double* d = static_cast<const double*>(cell->data);
788 if (dim0 == 3) {
789 // {3, nFaces} column-major: already interleaved
790 combined.FaceCentroids[frame].assign(d, d + dim0 * dim1);
791 } else if (dim1 == 3) {
792 // {nFaces, 3} column-major: transpose to interleaved
793 size_t nFaces = dim0;
794 combined.FaceCentroids[frame].resize(nFaces * 3);
795 for (size_t f = 0; f < nFaces; ++f) {
796 combined.FaceCentroids[frame][f*3+0] = d[f];
797 combined.FaceCentroids[frame][f*3+1] = d[f + nFaces];
798 combined.FaceCentroids[frame][f*3+2] = d[f + 2*nFaces];
799 }
800 } else {
801 combined.FaceCentroids[frame].assign(d, d + dim0 * dim1);
802 }
803 }
804 }
805 }
806 }
807
808 // Disp (struct with DispVec and DispMgn cell arrays)
809 if (matvar_t* disp_var = getStructField(dic3d_var.get(), "Disp", 0)) {
810 if (disp_var->class_type == MAT_C_STRUCT) {
811 // DispVec: cell array, each cell is Nx3 or 3xN double
812 // Internal format: interleaved [dx0,dy0,dz0, dx1,dy1,dz1, ...]
813 if (matvar_t* dv_var = getStructField(disp_var, "DispVec", 0)) {
814 if (dv_var->class_type == MAT_C_CELL) {
815 const size_t nFrames = getCellArraySize(dv_var);
816 combined.Disp.DispVec.resize(nFrames);
817 for (size_t frame = 0; frame < nFrames; ++frame) {
818 matvar_t* cell = Mat_VarGetCell(dv_var, frame);
819 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data && cell->rank >= 2) {
820 size_t dim0 = cell->dims[0];
821 size_t dim1 = cell->dims[1];
822 const double* d = static_cast<const double*>(cell->data);
823 if (dim0 == 3) {
824 // {3, nPts} column-major: already interleaved
825 combined.Disp.DispVec[frame].assign(d, d + dim0 * dim1);
826 } else if (dim1 == 3) {
827 // {nPts, 3} column-major: transpose to interleaved
828 size_t nPts = dim0;
829 combined.Disp.DispVec[frame].resize(nPts * 3);
830 for (size_t p = 0; p < nPts; ++p) {
831 combined.Disp.DispVec[frame][p*3+0] = d[p];
832 combined.Disp.DispVec[frame][p*3+1] = d[p + nPts];
833 combined.Disp.DispVec[frame][p*3+2] = d[p + 2*nPts];
834 }
835 } else {
836 combined.Disp.DispVec[frame].assign(d, d + dim0 * dim1);
837 }
838 }
839 }
840 }
841 }
842 // DispMgn: cell array, each cell is Nx1 double vector
843 if (matvar_t* dm_var = getStructField(disp_var, "DispMgn", 0)) {
844 if (dm_var->class_type == MAT_C_CELL) {
845 const size_t nFrames = getCellArraySize(dm_var);
846 combined.Disp.DispMgn.resize(nFrames);
847 for (size_t frame = 0; frame < nFrames; ++frame) {
848 matvar_t* cell = Mat_VarGetCell(dm_var, frame);
849 if (cell && cell->class_type == MAT_C_DOUBLE && cell->data) {
850 combined.Disp.DispMgn[frame] = readDoubleArray(cell);
851 }
852 }
853 }
854 }
855 }
856 }
857
858 // FacePairInds / PointPairInds (handles INT32 or DOUBLE, any dim order)
859 if (matvar_t* facepair_var = getStructField(dic3d_var.get(), "FacePairInds", 0)) {
860 combined.FacePairInds = readIntArray(facepair_var);
861 }
862
863 if (matvar_t* pointpair_var = getStructField(dic3d_var.get(), "PointPairInds", 0)) {
864 combined.PointPairInds = readIntArray(pointpair_var);
865 }
866
867 if (matvar_t* dic2d_info_var = getStructField(dic3d_var.get(), "DIC2Dinfo", 0)) {
868 if (dic2d_info_var->class_type == MAT_C_CELL) {
869 const size_t nEntries = getCellArraySize(dic2d_info_var);
870 combined.DIC2Dinfo.clear();
871 combined.DIC2Dinfo.resize(nEntries);
872 for (size_t i = 0; i < nEntries; ++i) {
873 matvar_t* cell = Mat_VarGetCell(dic2d_info_var, i);
874 if (cell && cell->class_type == MAT_C_STRUCT) {
875 readDIC2DPairResultsFromStruct(cell, combined.DIC2Dinfo[i]);
876 }
877 }
878 }
879 }
880
881 LOG_INFO << "Loaded DIC3Dcombined: "
882 << combined.Points3D.size() << " frames, "
883 << (combined.Points3D.empty() ? 0 : combined.Points3D[0].x.size()) << " points, "
884 << combined.Faces.size() / 3 << " faces";
885
886 return true;
887}
888
889std::vector<int> MatReader::readIntArray(matvar_t* var) {
890 std::vector<int> result;
891 if (!var || !var->data) return result;
892
893 size_t n_elements = 1;
894 for (int i = 0; i < var->rank; ++i) n_elements *= var->dims[i];
895
896 result.reserve(n_elements);
897
898 if (var->class_type == MAT_C_DOUBLE) {
899 const double* data = static_cast<const double*>(var->data);
900 for (size_t i = 0; i < n_elements; ++i) result.push_back(static_cast<int>(data[i]));
901 return result;
902 }
903
904 if (var->class_type == MAT_C_INT32) {
905 const int32_t* data = static_cast<const int32_t*>(var->data);
906 result.assign(data, data + n_elements);
907 return result;
908 }
909
910 if (var->class_type == MAT_C_UINT16) {
911 const uint16_t* data = static_cast<const uint16_t*>(var->data);
912 for (size_t i = 0; i < n_elements; ++i) result.push_back(static_cast<int>(data[i]));
913 return result;
914 }
915
916 if (var->class_type == MAT_C_UINT32) {
917 const uint32_t* data = static_cast<const uint32_t*>(var->data);
918 for (size_t i = 0; i < n_elements; ++i) result.push_back(static_cast<int>(data[i]));
919 return result;
920 }
921
922 return result;
923}
924
925// ============================================================================
926// Updated DIC2DPairResults (top-level fields, RAII, no leaks)
927// ============================================================================
928
929bool MatReader::readDIC2DPairResultsFromStruct(matvar_t* dic2d_root, DIC2DPairResults& result) {
930 if (!dic2d_root || dic2d_root->class_type != MAT_C_STRUCT) {
931 return false;
932 }
933
934 result = DIC2DPairResults{};
935
936 readStructInt(dic2d_root, "nCamRef", result.nCamRef);
937 readStructInt(dic2d_root, "nCamDef", result.nCamDef);
938 readStructInt(dic2d_root, "nImages", result.nImages);
939 if (matvar_t* pair_order_var = getStructField(dic2d_root, "pairOrder", 0)) {
940 result.pairOrder = readIntArray(pair_order_var);
941 } else {
942 result.pairOrder.clear();
943 }
944 readStructBool(dic2d_root, "pairForced", result.pairForced);
945
946 if (matvar_t* roi_var = getStructField(dic2d_root, "ROImask", 0)) {
947 result.ROImask = readImage(roi_var);
948 }
949
950 if (matvar_t* faces_var = getStructField(dic2d_root, "Faces", 0)) {
951 if (faces_var->data && faces_var->rank >= 2) {
952 const size_t rows = faces_var->dims[0];
953 const size_t cols = faces_var->dims[1];
954 const size_t nFaces = (cols == 3) ? rows : ((rows == 3) ? cols : 0);
955
956 result.Faces.clear();
957 if (nFaces > 0) {
958 result.Faces.reserve(nFaces * 3);
959
960 auto pushFaces = [&](auto* dataPtr) {
961 if (cols == 3) {
962 for (size_t f = 0; f < nFaces; ++f) {
963 result.Faces.push_back(static_cast<int>(dataPtr[f]) - 1);
964 result.Faces.push_back(static_cast<int>(dataPtr[f + nFaces]) - 1);
965 result.Faces.push_back(static_cast<int>(dataPtr[f + 2 * nFaces]) - 1);
966 }
967 } else {
968 for (size_t f = 0; f < nFaces; ++f) {
969 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 0]) - 1);
970 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 1]) - 1);
971 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 2]) - 1);
972 }
973 }
974 };
975
976 if (faces_var->class_type == MAT_C_DOUBLE) {
977 pushFaces(static_cast<const double*>(faces_var->data));
978 } else if (faces_var->class_type == MAT_C_UINT16) {
979 pushFaces(static_cast<const uint16_t*>(faces_var->data));
980 } else if (faces_var->class_type == MAT_C_UINT32) {
981 pushFaces(static_cast<const uint32_t*>(faces_var->data));
982 } else if (faces_var->class_type == MAT_C_INT32) {
983 pushFaces(static_cast<const int32_t*>(faces_var->data));
984 }
985 }
986 }
987 }
988
989 if (matvar_t* facecolors_var = getStructField(dic2d_root, "FaceColors", 0)) {
990 result.FaceColors = readDoubleArray(facecolors_var);
991 }
992
993 if (matvar_t* points_var = getStructField(dic2d_root, "Points", 0)) {
994 if (points_var->class_type == MAT_C_CELL) {
995 const size_t nFrames = getCellArraySize(points_var);
996 result.Points.resize(nFrames);
997 for (size_t frame = 0; frame < nFrames; ++frame) {
998 matvar_t* frame_var = Mat_VarGetCell(points_var, frame);
999 if (!frame_var || frame_var->class_type != MAT_C_DOUBLE || !frame_var->data || frame_var->rank < 2) {
1000 continue;
1001 }
1002
1003 const size_t dim0 = frame_var->dims[0];
1004 const size_t dim1 = frame_var->dims[1];
1005 const double* pts_data = static_cast<const double*>(frame_var->data);
1006
1007 if (dim1 == 2) {
1008 const size_t nPoints = dim0;
1009 result.Points[frame].x.resize(nPoints);
1010 result.Points[frame].y.resize(nPoints);
1011 for (size_t i = 0; i < nPoints; ++i) {
1012 result.Points[frame].x[i] = pts_data[i];
1013 result.Points[frame].y[i] = pts_data[i + nPoints];
1014 }
1015 } else if (dim0 == 2) {
1016 const size_t nPoints = dim1;
1017 result.Points[frame].x.resize(nPoints);
1018 result.Points[frame].y.resize(nPoints);
1019 for (size_t i = 0; i < nPoints; ++i) {
1020 result.Points[frame].x[i] = pts_data[i * 2 + 0];
1021 result.Points[frame].y[i] = pts_data[i * 2 + 1];
1022 }
1023 }
1024 }
1025 }
1026 }
1027
1028 if (matvar_t* corr_var = getStructField(dic2d_root, "CorCoeffVec", 0)) {
1029 if (corr_var->class_type == MAT_C_CELL) {
1030 const size_t nFrames = getCellArraySize(corr_var);
1031 result.CorCoeffVec.resize(nFrames);
1032 for (size_t frame = 0; frame < nFrames; ++frame) {
1033 matvar_t* frame_var = Mat_VarGetCell(corr_var, frame);
1034 if (frame_var && frame_var->class_type == MAT_C_DOUBLE && frame_var->data) {
1035 result.CorCoeffVec[frame] = readDoubleArray(frame_var);
1036 }
1037 }
1038 }
1039 }
1040
1041 if (matvar_t* ncorr_var = getStructField(dic2d_root, "ncorrInfo", 0)) {
1042 if (ncorr_var->class_type == MAT_C_STRUCT) {
1043 if (matvar_t* cutoff_cc = getStructField(ncorr_var, "cutoff_corrcoef", 0)) {
1044 result.ncorrInfo.cutoff_corrcoef = readDoubleArray(cutoff_cc);
1045 }
1046
1047 readStructDouble(ncorr_var, "cutoff_diffnorm", result.ncorrInfo.cutoff_diffnorm);
1048 readStructInt(ncorr_var, "cutoff_iteration", result.ncorrInfo.cutoff_iteration);
1049 readStructInt(ncorr_var, "radius", result.ncorrInfo.radius);
1050 readStructInt(ncorr_var, "spacing", result.ncorrInfo.spacing);
1051 readStructDouble(ncorr_var, "pixtounits", result.ncorrInfo.pixtounits);
1052 readStructInt(ncorr_var, "lenscoef", result.ncorrInfo.lenscoef);
1053 readStructBool(ncorr_var, "subsettrunc", result.ncorrInfo.subsettrunc);
1054 readStructInt(ncorr_var, "total_threads", result.ncorrInfo.total_threads);
1055 readStructString(ncorr_var, "type", result.ncorrInfo.type);
1056 readStructString(ncorr_var, "units", result.ncorrInfo.units);
1057
1058 matvar_t* step_var = getStructField(ncorr_var, "stepanalysis", 0);
1059 if (step_var && step_var->class_type == MAT_C_STRUCT) {
1060 readStructBool(step_var, "enabled", result.ncorrInfo.stepanalysis.enabled);
1061 readStructString(step_var, "type", result.ncorrInfo.stepanalysis.type);
1062 readStructBool(step_var, "auto", result.ncorrInfo.stepanalysis.auto_update);
1063 readStructInt(step_var, "step", result.ncorrInfo.stepanalysis.step);
1064 }
1065 }
1066 }
1067
1068 return true;
1069}
1070
1071bool MatReader::readDIC2DPairResults(const std::string& mat_path, DIC2DPairResults& result) {
1072 auto matfp = openMat(mat_path);
1073 if (!matfp) {
1074 LOG_ERROR << "Failed to open MAT file: " << mat_path;
1075 return false;
1076 }
1077
1078 result = DIC2DPairResults{};
1079
1080 auto first_var = readVar(matfp.get(), "DIC2DpairResults");
1081 matvar_t* dic2d_root = nullptr;
1082 if (first_var && first_var->name && std::strcmp(first_var->name, "DIC2DpairResults") == 0 &&
1083 first_var->class_type == MAT_C_STRUCT) {
1084 dic2d_root = first_var.get();
1085 }
1086
1087 if (dic2d_root) {
1088 if (!readDIC2DPairResultsFromStruct(dic2d_root, result)) {
1089 return false;
1090 }
1091 LOG_INFO << "Loaded DIC2DPairResults: "
1092 << "nCamRef=" << result.nCamRef
1093 << ", nCamDef=" << result.nCamDef
1094 << ", nImages=" << result.nImages
1095 << ", pairOrder=" << result.pairOrder.size()
1096 << ", " << result.Points.size() << " frames"
1097 << ", " << result.Faces.size() / 3 << " faces";
1098 return true;
1099 }
1100
1101 std::vector<MatVarPtr> owned_vars;
1102 owned_vars.reserve(10);
1103
1104 auto readAnyVar = [&](const char* name) -> matvar_t* {
1105 if (dic2d_root) {
1106 return getStructField(dic2d_root, name, 0);
1107 }
1108 owned_vars.push_back(readVar(matfp.get(), name));
1109 return owned_vars.back().get();
1110 };
1111
1112 auto readAnyInt = [&](const char* name, int& out) {
1113 if (dic2d_root) {
1114 readStructInt(dic2d_root, name, out);
1115 } else {
1116 readTopInt(matfp.get(), name, out);
1117 }
1118 };
1119
1120 readAnyInt("nCamRef", result.nCamRef);
1121 readAnyInt("nCamDef", result.nCamDef);
1122 readAnyInt("nImages", result.nImages);
1123 if (matvar_t* pair_order_var = readAnyVar("pairOrder")) {
1124 result.pairOrder = readIntArray(pair_order_var);
1125 } else {
1126 result.pairOrder.clear();
1127 }
1128 if (matvar_t* pair_forced_var = readAnyVar("pairForced")) {
1129 result.pairForced = (readScalar(pair_forced_var) != 0.0);
1130 } else {
1131 result.pairForced = false;
1132 }
1133
1134 if (matvar_t* roi_var = readAnyVar("ROImask")) {
1135 result.ROImask = readImage(roi_var);
1136 }
1137
1138 if (matvar_t* faces_var = readAnyVar("Faces")) {
1139 if (faces_var->data && faces_var->rank >= 2) {
1140 const size_t rows = faces_var->dims[0];
1141 const size_t cols = faces_var->dims[1];
1142 const size_t nFaces = (cols == 3) ? rows : ((rows == 3) ? cols : 0);
1143
1144 result.Faces.clear();
1145
1146 if (nFaces == 0) {
1147 LOG_WARN << "Faces has unexpected shape (" << rows << "x" << cols
1148 << "), expected Nx3 or 3xN in " << mat_path;
1149 } else {
1150 result.Faces.reserve(nFaces * 3);
1151
1152 auto pushFaces = [&](auto* dataPtr) {
1153 if (cols == 3) {
1154 // MATLAB Nx3 faces are stored column-major: [all v0, all v1, all v2].
1155 for (size_t f = 0; f < nFaces; ++f) {
1156 result.Faces.push_back(static_cast<int>(dataPtr[f]) - 1);
1157 result.Faces.push_back(static_cast<int>(dataPtr[f + nFaces]) - 1);
1158 result.Faces.push_back(static_cast<int>(dataPtr[f + 2 * nFaces]) - 1);
1159 }
1160 } else {
1161 // C++ writer uses 3xN, which is already interleaved face-by-face in MAT storage.
1162 for (size_t f = 0; f < nFaces; ++f) {
1163 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 0]) - 1);
1164 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 1]) - 1);
1165 result.Faces.push_back(static_cast<int>(dataPtr[f * 3 + 2]) - 1);
1166 }
1167 }
1168 };
1169
1170 if (faces_var->class_type == MAT_C_DOUBLE) {
1171 pushFaces(static_cast<const double*>(faces_var->data));
1172 } else if (faces_var->class_type == MAT_C_UINT16) {
1173 pushFaces(static_cast<const uint16_t*>(faces_var->data));
1174 } else if (faces_var->class_type == MAT_C_UINT32) {
1175 pushFaces(static_cast<const uint32_t*>(faces_var->data));
1176 } else if (faces_var->class_type == MAT_C_INT32) {
1177 pushFaces(static_cast<const int32_t*>(faces_var->data));
1178 } else {
1179 LOG_WARN << "Faces has unsupported type (class_type=" << faces_var->class_type
1180 << ") in " << mat_path;
1181 }
1182 }
1183 }
1184 }
1185
1186 if (matvar_t* facecolors_var = readAnyVar("FaceColors")) {
1187 result.FaceColors = readDoubleArray(facecolors_var);
1188 }
1189
1190 if (matvar_t* points_var = readAnyVar("Points")) {
1191 if (points_var->class_type == MAT_C_CELL) {
1192 const size_t nFrames = getCellArraySize(points_var);
1193 result.Points.resize(nFrames);
1194
1195 for (size_t frame = 0; frame < nFrames; ++frame) {
1196 matvar_t* frame_var = Mat_VarGetCell(points_var, frame);
1197 if (!frame_var || frame_var->class_type != MAT_C_DOUBLE || !frame_var->data || frame_var->rank < 2)
1198 continue;
1199
1200 const size_t dim0 = frame_var->dims[0];
1201 const size_t dim1 = frame_var->dims[1];
1202 const double* pts_data = static_cast<const double*>(frame_var->data);
1203
1204 if (dim1 == 2) {
1205 // {nPoints, 2} column-major: x=col0, y=col1
1206 const size_t nPoints = dim0;
1207 result.Points[frame].x.resize(nPoints);
1208 result.Points[frame].y.resize(nPoints);
1209 for (size_t i = 0; i < nPoints; ++i) {
1210 result.Points[frame].x[i] = pts_data[i];
1211 result.Points[frame].y[i] = pts_data[i + nPoints];
1212 }
1213 } else if (dim0 == 2) {
1214 // {2, nPoints} column-major: interleaved [x0,y0,x1,y1,...]
1215 const size_t nPoints = dim1;
1216 result.Points[frame].x.resize(nPoints);
1217 result.Points[frame].y.resize(nPoints);
1218 for (size_t i = 0; i < nPoints; ++i) {
1219 result.Points[frame].x[i] = pts_data[i * 2 + 0];
1220 result.Points[frame].y[i] = pts_data[i * 2 + 1];
1221 }
1222 }
1223 }
1224 }
1225 }
1226
1227 if (matvar_t* corr_var = readAnyVar("CorCoeffVec")) {
1228 if (corr_var->class_type == MAT_C_CELL) {
1229 const size_t nFrames = getCellArraySize(corr_var);
1230 result.CorCoeffVec.resize(nFrames);
1231
1232 for (size_t frame = 0; frame < nFrames; ++frame) {
1233 matvar_t* frame_var = Mat_VarGetCell(corr_var, frame);
1234 if (frame_var && frame_var->class_type == MAT_C_DOUBLE && frame_var->data) {
1235 result.CorCoeffVec[frame] = readDoubleArray(frame_var);
1236 }
1237 }
1238 }
1239 }
1240
1241 if (matvar_t* ncorr_var = readAnyVar("ncorrInfo")) {
1242 if (ncorr_var->class_type == MAT_C_STRUCT) {
1243 if (matvar_t* cutoff_cc = getStructField(ncorr_var, "cutoff_corrcoef", 0)) {
1244 result.ncorrInfo.cutoff_corrcoef = readDoubleArray(cutoff_cc);
1245 }
1246
1247 readStructDouble(ncorr_var, "cutoff_diffnorm", result.ncorrInfo.cutoff_diffnorm);
1248 readStructInt(ncorr_var, "cutoff_iteration", result.ncorrInfo.cutoff_iteration);
1249 readStructInt(ncorr_var, "radius", result.ncorrInfo.radius);
1250 readStructInt(ncorr_var, "spacing", result.ncorrInfo.spacing);
1251 readStructDouble(ncorr_var, "pixtounits", result.ncorrInfo.pixtounits);
1252 readStructInt(ncorr_var, "lenscoef", result.ncorrInfo.lenscoef);
1253 readStructBool(ncorr_var, "subsettrunc", result.ncorrInfo.subsettrunc);
1254 readStructInt(ncorr_var, "total_threads", result.ncorrInfo.total_threads);
1255 readStructString(ncorr_var, "type", result.ncorrInfo.type);
1256 readStructString(ncorr_var, "units", result.ncorrInfo.units);
1257
1258 matvar_t* step_var = getStructField(ncorr_var, "stepanalysis", 0);
1259 if (step_var && step_var->class_type == MAT_C_STRUCT) {
1260 readStructBool(step_var, "enabled", result.ncorrInfo.stepanalysis.enabled);
1261 readStructString(step_var, "type", result.ncorrInfo.stepanalysis.type);
1262 readStructBool(step_var, "auto", result.ncorrInfo.stepanalysis.auto_update);
1263 readStructInt(step_var, "step", result.ncorrInfo.stepanalysis.step);
1264 }
1265 }
1266 }
1267
1268 LOG_INFO << "Loaded DIC2DPairResults: "
1269 << "nCamRef=" << result.nCamRef
1270 << ", nCamDef=" << result.nCamDef
1271 << ", nImages=" << result.nImages
1272 << ", pairOrder=" << result.pairOrder.size()
1273 << ", " << result.Points.size() << " frames"
1274 << ", " << result.Faces.size() / 3 << " faces";
1275
1276 return true;
1277}
1278
1279} // namespace cppxdic
static bool loadDLTCalibration(const std::string &mat_path, DLTCalibrationData &calib)
static matvar_t * readStructField(mat_t *matfp, const std::string &struct_name, const std::string &field_name)
Read a specific field from a struct variable.
std::unique_ptr< matvar_t, MatVarDeleter > MatVarPtr
Definition mat_reader.h:63
static cv::Mat readImage(matvar_t *var)
Read image from matvar.
static matvar_t * getCellElement(matvar_t *var, size_t index)
Get cell from cell array.
static bool variableExists(mat_t *matfp, const std::string &var_name)
Check if variable exists in MAT file.
static std::vector< double > readDoubleArray(matvar_t *var)
Read double array from matvar.
static MatVarPtr readVar(mat_t *matfp, const std::string &name)
static cv::Mat readROIMask(const std::string &mat_path, const std::string &var_name="refmask")
Read ROI mask from MAT file.
static std::vector< double > readDouble2DArray(matvar_t *var, size_t &rows, size_t &cols)
Read 2D double array from matvar.
static bool readDIC2DPairResults(const std::string &mat_path, struct DIC2DPairResults &result)
Read DIC2DPairResults structure from MAT file Loads myDIC2DpairResults_C_X_C_Y.mat files.
static bool readDIC3Dcombined(const std::string &mat_path, struct DIC3Dcombined &combined)
Read DIC3Dcombined structure from MAT file.
static std::string readString(matvar_t *var)
Read string from matvar.
static std::vector< int > readIntArray(matvar_t *var)
Read int array from matvar.
static matvar_t * readNestedField(mat_t *matfp, const std::string &path)
Read a nested struct field (e.g., "data_dic_save.dispinfo.radius")
static double readScalar(matvar_t *var)
Read scalar from matvar.
static std::vector< std::string > readCellStrings(matvar_t *cell_var)
static MatVarPtr readFirstVar(mat_t *matfp)
static std::map< std::string, double > readDispInfo(mat_t *matfp)
Read dispinfo parameters from MAT file.
static MatFilePtr openMat(const std::string &path)
std::unique_ptr< mat_t, MatFileDeleter > MatFilePtr
Definition mat_reader.h:62
static bool loadProtocol(const std::string &protocol_path, ProtocolFileData &protocol)
static size_t getCellArraySize(matvar_t *var)
Get cell array size.
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
DataSerializer - Abstract base class for format-agnostic DIC data I/O.
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
std::vector< int > Faces
DIC 3D Combined Results Equivalent to MATLAB's DIC3Dcombined This represents stitched results from mu...
std::vector< int > FacePairInds
std::vector< int > PointPairInds
std::vector< Point3DFrame > Points3D
std::vector< std::vector< double > > FaceCorrComb
std::vector< std::vector< double > > corrComb
std::vector< DIC2DPairResults > DIC2Dinfo
std::vector< int > Faces
std::vector< std::vector< double > > FaceCentroids
std::string units
std::vector< double > cutoff_corrcoef
StepAnalysisInfo stepanalysis
std::vector< double > imageCentroids
Definition mat_reader.h:25
std::vector< uint8_t > columns
Definition mat_reader.h:27
std::vector< double > C3Dtrue
Definition mat_reader.h:22
std::vector< double > DLTparams
Definition mat_reader.h:21
std::vector< std::vector< double > > DispVec
std::vector< std::vector< double > > DispMgn
std::vector< TrialEntry > trials
Definition mat_reader.h:43
std::vector< std::string > titles
Definition mat_reader.h:42