CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
mat_writer.cpp
Go to the documentation of this file.
1
7#include "mat_writer.h"
8#include "logging.h"
9#include <iostream>
10#include <cstring>
11#include <vector>
12#include <stdexcept>
13#include <Eigen/Dense>
14#include <filesystem>
15#include <algorithm>
16
17namespace cppxdic {
18
19// Reusable 1x1 dims for Mat_VarCreate scalar calls (avoids compound literal temporaries)
20static size_t scalar_dims[2] = {1, 1};
21
22// Create an empty MATLAB char ('') variable. matio + HDF5 (v7.3) segfaults
23// when a struct field or cell element is left NULL during Mat_VarWrite, so
24// we use this as a safe placeholder for "empty string" fields like `path`.
25static matvar_t* createEmptyCharVar(const char* name = nullptr) {
26 size_t dims[2] = {0, 0};
27 return Mat_VarCreate(name, MAT_C_CHAR, MAT_T_UINT8, 2, dims, nullptr, 0);
28}
29
30bool MatWriter::writeMatchingFile(const std::string& filename,
31 const cv::Mat& ref_img,
32 const cv::Mat& cur_img,
33 const cv::Mat& ref_roi,
34 const cv::Mat& cur_roi,
35 const ncorr::DIC_analysis_output& dic_output,
36 const std::map<std::string, double>& dispinfo) {
37
38 // Format dispinfo and displacements
39 matvar_t* dispinfo_var = formatDispInfo(dispinfo);
40 matvar_t* displacements_var = formatDisplacements(dic_output);
41
42 // Write using common function
43 bool success = writeDicNcorrFile(filename, ref_img, cur_img, ref_roi, cur_roi,
44 dispinfo_var, displacements_var, dic_output);
45
46 if (success) {
47 LOG_INFO << "Wrote MATCHING file: " << filename;
48 }
49
50 return success;
51}
52
53bool MatWriter::writeMatchingFile(const std::string& filename,
54 const cv::Mat& ref_img,
55 const cv::Mat& cur_img,
56 const cv::Mat& ref_roi,
57 const cv::Mat& cur_roi,
58 const ncorr::DIC_analysis_output& dic_lagrangian,
59 const ncorr::DIC_analysis_output& dic_eulerian,
60 const std::map<std::string, double>& dispinfo) {
61
62 // Format dispinfo and displacements with both perspectives
63 matvar_t* dispinfo_var = formatDispInfo(dispinfo);
64 matvar_t* displacements_var = formatDisplacements(dic_lagrangian, dic_eulerian);
65
66 // Write using common function (use lagrangian for ROI update)
67 bool success = writeDicNcorrFile(filename, ref_img, cur_img, ref_roi, cur_roi,
68 dispinfo_var, displacements_var, dic_lagrangian);
69
70 if (success) {
71 LOG_INFO << "Wrote MATCHING file with both perspectives: " << filename;
72 }
73
74 return success;
75}
76
77bool MatWriter::writeDicNcorrFile(const std::string& filename,
78 const cv::Mat& ref_img,
79 const cv::Mat& cur_img,
80 const cv::Mat& ref_roi,
81 const cv::Mat& cur_roi,
82 matvar_t* dispinfo_var,
83 matvar_t* displacements_var,
84 const ncorr::DIC_analysis_output& dic_output) {
85
86 // Create MAT file (v7.3 HDF5 format for xDIC compatibility)
87 mat_t* matfp = createMatFileHDF5(filename);
88 if (!matfp) {
89 LOG_ERROR << "Failed to create MAT file: " << filename;
90 return false;
91 }
92
93 // ===== CREATE REFERENCE_SAVE STRUCT =====
94 std::vector<std::string> ref_fields = {"gs", "name", "path", "roi", "type"};
95 matvar_t* reference_save = createStructVariable("reference_save", ref_fields);
96
97 // Add ref image
98 writeMatVariable(matfp, "ref_gs_temp", ref_img);
99 matvar_t* ref_gs = Mat_VarRead(matfp, "ref_gs_temp");
100 addFieldToStruct(reference_save, "gs", ref_gs, 0);
101
102 // Add ref ROI
103 std::vector<std::string> roi_fields = {"mask"};
104 matvar_t* ref_roi_struct = createStructVariable("roi", roi_fields);
105 writeMatVariable(matfp, "ref_roi_temp", ref_roi);
106 matvar_t* ref_roi_mask = Mat_VarRead(matfp, "ref_roi_temp");
107 addFieldToStruct(ref_roi_struct, "mask", ref_roi_mask, 0);
108 addFieldToStruct(reference_save, "roi", ref_roi_struct, 0);
109
110 // ===== UPDATE CURRENT ROI WITH DISPLACEMENT FIELD =====
111 cv::Mat cur_roi_updated = cur_roi.clone(); // Default to original
112 if (!dic_output.disps.empty()) {
113 try {
114 // Convert cv::Mat to ncorr::ROI2D
115 ncorr::ROI2D roi_current = convertMatToROI2D(cur_roi);
116
117 // Apply ROI update using first displacement field
118 // This updates the ROI boundary based on displacement interpolation
119 ncorr::ROI2D roi_updated = ncorr::update(
120 roi_current,
121 dic_output.disps[0], // First displacement field
122 ncorr::INTERP::CUBIC_KEYS, // Cubic interpolation for smooth boundaries
123 ncorr::ROI_UPDATE_MODE::SKIP_INVALID
124 );
125
126 // Convert back to cv::Mat
127 cur_roi_updated = convertROI2DToMat(roi_updated);
128
129 LOG_DEBUG << " Applied ROI update with displacement field";
130 } catch (const std::exception& e) {
131 LOG_WARN << "ROI update failed: " << e.what() << "; using original ROI instead";
132 // cur_roi_updated remains as clone of cur_roi
133 }
134 }
135
136 // ===== CREATE CURRENT_SAVE STRUCT =====
137 matvar_t* current_save = createStructVariable("current_save", ref_fields);
138
139 // Add cur image
140 writeMatVariable(matfp, "cur_gs_temp", cur_img);
141 matvar_t* cur_gs = Mat_VarRead(matfp, "cur_gs_temp");
142 addFieldToStruct(current_save, "gs", cur_gs, 0);
143
144 // Add cur ROI (updated)
145 matvar_t* cur_roi_struct = createStructVariable("roi", roi_fields);
146 writeMatVariable(matfp, "cur_roi_temp", cur_roi_updated); // Write UPDATED ROI
147 matvar_t* cur_roi_mask = Mat_VarRead(matfp, "cur_roi_temp");
148 addFieldToStruct(cur_roi_struct, "mask", cur_roi_mask, 0);
149 addFieldToStruct(current_save, "roi", cur_roi_struct, 0);
150
151 // ===== CREATE DATA_DIC_SAVE STRUCT =====
152 std::vector<std::string> data_fields = {"dispinfo", "displacements", "straininfo", "strains"};
153 matvar_t* data_dic_save = createStructVariable("data_dic_save", data_fields);
154
155 // Add dispinfo and displacements (passed as arguments)
156 addFieldToStruct(data_dic_save, "dispinfo", dispinfo_var, 0);
157 addFieldToStruct(data_dic_save, "displacements", displacements_var, 0);
158
159 // Add empty straininfo struct (placeholder for compatibility)
160 std::vector<std::string> straininfo_fields = {"radius", "subsettrunc"};
161 matvar_t* straininfo_var = createStructVariable("straininfo", straininfo_fields);
162 addFieldToStruct(data_dic_save, "straininfo", straininfo_var, 0);
163
164 // Add empty strains struct (placeholder for compatibility)
165 std::vector<std::string> strains_fields = {
166 "plot_exx_ref_formatted", "plot_exy_ref_formatted", "plot_eyy_ref_formatted",
167 "roi_ref_formatted", "plot_exx_cur_formatted", "plot_exy_cur_formatted",
168 "plot_eyy_cur_formatted", "roi_cur_formatted"
169 };
170 matvar_t* strains_var = createStructVariable("strains", strains_fields);
171 addFieldToStruct(data_dic_save, "strains", strains_var, 0);
172
173 // ===== WRITE ALL STRUCTS TO FILE =====
174 Mat_VarWrite(matfp, reference_save, MAT_COMPRESSION_NONE);
175 Mat_VarWrite(matfp, current_save, MAT_COMPRESSION_NONE);
176 Mat_VarWrite(matfp, data_dic_save, MAT_COMPRESSION_NONE);
177
178 // Clean up
179 Mat_VarFree(reference_save);
180 Mat_VarFree(current_save);
181 Mat_VarFree(data_dic_save);
182
183 Mat_Close(matfp);
184
185 return true;
186}
187
188bool MatWriter::writeMultiFrameNcorrFile(const std::string& filename,
189 const cv::Mat& ref_img,
190 const std::vector<cv::Mat>& cur_imgs,
191 const cv::Mat& ref_roi,
192 const std::vector<cv::Mat>& cur_rois,
193 const std::vector<ncorr::DIC_analysis_output>& dic_outputs,
194 const std::map<std::string, double>& dispinfo,
195 const std::string& type_str,
196 const std::string& ref_name) {
197
198 if (cur_imgs.empty()) {
199 LOG_ERROR << "No current images provided";
200 return false;
201 }
202
203 size_t n_frames = cur_imgs.size();
204 LOG_INFO << "Writing multi-frame ncorr file with " << n_frames << " frames...";
205
206 ncorr::DIC_analysis_output aggregated_output;
207 bool have_aggregated_output = false;
208 if (dic_outputs.size() == 1 && dic_outputs[0].disps.size() == n_frames) {
209 aggregated_output = dic_outputs[0];
210 have_aggregated_output = true;
211 } else if (dic_outputs.size() == n_frames) {
212 std::vector<ncorr::Disp2D> aggregated_disps;
213 aggregated_disps.reserve(n_frames);
214 bool all_frames_present = true;
215 for (const auto& output : dic_outputs) {
216 if (output.disps.empty()) {
217 all_frames_present = false;
218 break;
219 }
220 aggregated_disps.push_back(output.disps.front());
221 }
222 if (all_frames_present) {
223 aggregated_output = ncorr::DIC_analysis_output(
224 aggregated_disps,
225 dic_outputs.front().perspective_type,
226 dic_outputs.front().units,
227 dic_outputs.front().units_per_pixel);
228 have_aggregated_output = true;
229 }
230 }
231
232 // Create MAT file (v7.3 HDF5 format)
233 mat_t* matfp = createMatFileHDF5(filename);
234 if (!matfp) {
235 LOG_ERROR << "Failed to create MAT file: " << filename;
236 return false;
237 }
238
239 // ===== CREATE REFERENCE_SAVE STRUCT (single image) =====
240 std::vector<std::string> ref_fields = {"gs", "name", "path", "roi", "type"};
241 matvar_t* reference_save = createStructVariable("reference_save", ref_fields);
242
243 // Add ref image (single 2D array)
244 writeMatVariable(matfp, "ref_gs_temp", ref_img);
245 matvar_t* ref_gs = Mat_VarRead(matfp, "ref_gs_temp");
246 addFieldToStruct(reference_save, "gs", ref_gs, 0);
247
248 // Add ref ROI
249 std::vector<std::string> roi_fields = {"mask"};
250 matvar_t* ref_roi_struct = createStructVariable("roi", roi_fields);
251 writeMatVariable(matfp, "ref_roi_temp", ref_roi);
252 matvar_t* ref_roi_mask = Mat_VarRead(matfp, "ref_roi_temp");
253 addFieldToStruct(ref_roi_struct, "mask", ref_roi_mask, 0);
254 addFieldToStruct(reference_save, "roi", ref_roi_struct, 0);
255
256 // ===== CREATE CURRENT_SAVE STRUCT (cell arrays for multi-frame) =====
257 matvar_t* current_save = createStructVariable("current_save", ref_fields);
258
259 // Create gs cell array (n_frames x 1)
260 std::vector<size_t> cell_dims = {n_frames, 1};
261 matvar_t* gs_cell = Mat_VarCreate("gs", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
262
263 for (size_t i = 0; i < n_frames; ++i) {
264 // Write each image temporarily and read it back
265 std::string temp_name = "cur_gs_temp_" + std::to_string(i);
266 writeMatVariable(matfp, temp_name, cur_imgs[i]);
267 matvar_t* img_var = Mat_VarRead(matfp, temp_name.c_str());
268 Mat_VarSetCell(gs_cell, i, img_var);
269 }
270
271 addFieldToStruct(current_save, "gs", gs_cell, 0);
272
273 // Create roi cell array (n_frames x 1)
274 matvar_t* roi_cell = Mat_VarCreate("roi", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
275
276 for (size_t i = 0; i < n_frames; ++i) {
277 // Use provided ROI or clone from reference if not enough provided
278 cv::Mat cur_roi_to_use = (i < cur_rois.size()) ? cur_rois[i] : ref_roi.clone();
279
280 // Update ROI with displacement if available
281 const ncorr::Disp2D* roi_disp = nullptr;
282 if (have_aggregated_output && i < aggregated_output.disps.size()) {
283 roi_disp = &aggregated_output.disps[i];
284 } else if (i < dic_outputs.size() && !dic_outputs[i].disps.empty()) {
285 roi_disp = &dic_outputs[i].disps.front();
286 }
287
288 if (roi_disp) {
289 try {
290 ncorr::ROI2D roi_current = convertMatToROI2D(cur_roi_to_use);
291 ncorr::ROI2D roi_updated = ncorr::update(
292 roi_current,
293 *roi_disp,
294 ncorr::INTERP::CUBIC_KEYS,
295 ncorr::ROI_UPDATE_MODE::SKIP_INVALID
296 );
297 cur_roi_to_use = convertROI2DToMat(roi_updated);
298 } catch (const std::exception& e) {
299 LOG_WARN << "ROI update failed for frame " << i << ": " << e.what();
300 }
301 }
302
303 // Create ROI struct for this frame
304 matvar_t* frame_roi_struct = createStructVariable("roi", roi_fields);
305 std::string temp_roi_name = "cur_roi_temp_" + std::to_string(i);
306 writeMatVariable(matfp, temp_roi_name, cur_roi_to_use);
307 matvar_t* roi_mask_var = Mat_VarRead(matfp, temp_roi_name.c_str());
308 addFieldToStruct(frame_roi_struct, "mask", roi_mask_var, 0);
309
310 Mat_VarSetCell(roi_cell, i, frame_roi_struct);
311 }
312
313 addFieldToStruct(current_save, "roi", roi_cell, 0);
314
315 // Create name cell array (current_1, current_2, ...)
316 matvar_t* name_cell = Mat_VarCreate("name", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
317 for (size_t i = 0; i < n_frames; ++i) {
318 std::string name = "current_" + std::to_string(i + 1); // 1-indexed
319 std::vector<size_t> str_dims = {1, name.length()};
320 matvar_t* name_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, str_dims.data(),
321 (void*)name.c_str(), 0);
322 Mat_VarSetCell(name_cell, i, name_var);
323 }
324 addFieldToStruct(current_save, "name", name_cell, 0);
325
326 // Create path cell array (empty for all frames)
327 matvar_t* path_cell = Mat_VarCreate("path", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
328 for (size_t i = 0; i < n_frames; ++i) {
329 // matio HDF5 writer segfaults on NULL cells; store an empty char ''
330 Mat_VarSetCell(path_cell, i, createEmptyCharVar());
331 }
332 addFieldToStruct(current_save, "path", path_cell, 0);
333
334 // Create type cell array (use type_str parameter for all frames)
335 matvar_t* type_cell = Mat_VarCreate("type", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
336 for (size_t i = 0; i < n_frames; ++i) {
337 std::vector<size_t> str_dims = {1, type_str.length()};
338 matvar_t* type_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8, 2, str_dims.data(),
339 (void*)type_str.c_str(), 0);
340 Mat_VarSetCell(type_cell, i, type_var);
341 }
342 addFieldToStruct(current_save, "type", type_cell, 0);
343
344 // Add name and type to reference_save (use ref_name parameter)
345 std::vector<size_t> ref_name_dims = {1, ref_name.length()};
346 matvar_t* ref_name_var = Mat_VarCreate("name", MAT_C_CHAR, MAT_T_UINT8, 2, ref_name_dims.data(),
347 (void*)ref_name.c_str(), 0);
348 addFieldToStruct(reference_save, "name", ref_name_var, 0);
349
350 std::vector<size_t> ref_type_dims = {1, type_str.length()};
351 matvar_t* ref_type_var = Mat_VarCreate("type", MAT_C_CHAR, MAT_T_UINT8, 2, ref_type_dims.data(),
352 (void*)type_str.c_str(), 0);
353 addFieldToStruct(reference_save, "type", ref_type_var, 0);
354
355 // Add empty path to reference_save (matio NULL fields segfault on HDF5 write)
356 addFieldToStruct(reference_save, "path", createEmptyCharVar("path"), 0);
357
358 // ===== CREATE DATA_DIC_SAVE STRUCT =====
359 // Format dispinfo
360 matvar_t* dispinfo_var = formatDispInfo(dispinfo);
361
362 // Format displacements (multi-frame)
363 matvar_t* displacements_var = nullptr;
364 if (have_aggregated_output) {
365 displacements_var = formatDisplacements(aggregated_output);
366 }
367
368 std::vector<std::string> data_fields = {"dispinfo", "displacements", "straininfo", "strains"};
369 matvar_t* data_dic_save = createStructVariable("data_dic_save", data_fields);
370 addFieldToStruct(data_dic_save, "dispinfo", dispinfo_var, 0);
371
372 if (displacements_var) {
373 addFieldToStruct(data_dic_save, "displacements", displacements_var, 0);
374 }
375
376 // Add empty straininfo and strains (placeholders)
377 std::vector<std::string> straininfo_fields = {"radius", "subsettrunc"};
378 matvar_t* straininfo_var = createStructVariable("straininfo", straininfo_fields);
379 addFieldToStruct(data_dic_save, "straininfo", straininfo_var, 0);
380
381 std::vector<std::string> strains_fields = {
382 "plot_exx_ref_formatted", "plot_exy_ref_formatted", "plot_eyy_ref_formatted",
383 "roi_ref_formatted", "plot_exx_cur_formatted", "plot_exy_cur_formatted",
384 "plot_eyy_cur_formatted", "roi_cur_formatted"
385 };
386 matvar_t* strains_var = createStructVariable("strains", strains_fields);
387 addFieldToStruct(data_dic_save, "strains", strains_var, 0);
388
389 // ===== WRITE ALL STRUCTS TO FILE =====
390 Mat_VarWrite(matfp, reference_save, MAT_COMPRESSION_NONE);
391 Mat_VarWrite(matfp, current_save, MAT_COMPRESSION_NONE);
392 Mat_VarWrite(matfp, data_dic_save, MAT_COMPRESSION_NONE);
393
394 // Clean up
395 Mat_VarFree(reference_save);
396 Mat_VarFree(current_save);
397 Mat_VarFree(data_dic_save);
398
399 Mat_Close(matfp);
400
401 LOG_INFO << "Successfully wrote multi-frame ncorr file: " << filename;
402 return true;
403}
404
405bool MatWriter::writeROIMaskFile(const std::string& filename,
406 const cv::Mat& mask) {
407 mat_t* matfp = createMatFileV5(filename);
408 if (!matfp) {
409 LOG_ERROR << "Failed to create ROI mask file: " << filename;
410 return false;
411 }
412
413 // Write mask as 'refmask' variable (uint8 2D array)
414 bool success = writeMatVariable(matfp, "refmask", mask);
415
416 Mat_Close(matfp);
417 return success;
418}
419
420bool MatWriter::writeSeedFile(const std::string& filename,
421 int seed_x,
422 int seed_y) {
423 mat_t* matfp = createMatFileV5(filename);
424 if (!matfp) {
425 LOG_ERROR << "Failed to create seed file: " << filename;
426 return false;
427 }
428
429 // Write seed_point as uint16 1D array [x, y]
430 uint16_t seed_data[2] = {static_cast<uint16_t>(seed_x), static_cast<uint16_t>(seed_y)};
431 std::vector<size_t> dims = {2};
432
433 bool success = writeArrayVariable(matfp, "seed_point", seed_data, dims,
434 MAT_T_UINT16, MAT_C_UINT16);
435
436 Mat_Close(matfp);
437 return success;
438}
439
440bool MatWriter::writeTrialInfoFile(const std::string& filename,
441 double fps,
442 const std::vector<int>& frame_indices) {
443 mat_t* matfp = createMatFileV5(filename);
444 if (!matfp) {
445 LOG_ERROR << "Failed to create trial info file: " << filename;
446 return false;
447 }
448
449 // Write actual_fps_meas
450 if (!writeScalarVariable(matfp, "actual_fps_meas", fps)) {
451 Mat_Close(matfp);
452 return false;
453 }
454
455 // Write idxframe as uint8 1D array
456 std::vector<uint8_t> idx_data(frame_indices.begin(), frame_indices.end());
457 std::vector<size_t> dims = {idx_data.size()};
458
459 bool success = writeArrayVariable(matfp, "idxframe", idx_data.data(), dims,
460 MAT_T_UINT8, MAT_C_UINT8);
461
462 Mat_Close(matfp);
463 return success;
464}
465
466bool MatWriter::writeDICResultFile(const std::string& filename,
467 const ncorr::DIC_analysis_input& dic_input,
468 const ncorr::DIC_analysis_output& dic_output) {
469 mat_t* matfp = createMatFileHDF5(filename);
470 if (!matfp) {
471 LOG_ERROR << "Failed to create DIC result file: " << filename;
472 return false;
473 }
474
475 // Create input struct with actual values
476 std::vector<std::string> input_fields = {"radius", "spacing", "subregion_type", "interp_type"};
477 matvar_t* input_struct = createStructVariable("input", input_fields);
478
479 // Populate input fields
480 size_t scalar_dims[2] = {1, 1};
481 double radius_val = static_cast<double>(dic_input.r);
482 double spacing_val = static_cast<double>(dic_input.scalefactor);
483 matvar_t* radius_var = Mat_VarCreate("radius", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, scalar_dims, &radius_val, 0);
484 matvar_t* spacing_var = Mat_VarCreate("spacing", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, scalar_dims, &spacing_val, 0);
485 Mat_VarSetStructFieldByName(input_struct, "radius", 0, radius_var);
486 Mat_VarSetStructFieldByName(input_struct, "spacing", 0, spacing_var);
487
488 // Write input struct
489 Mat_VarWrite(matfp, input_struct, MAT_COMPRESSION_NONE);
490 Mat_VarFree(input_struct);
491
492 // Create output struct with displacement data
493 size_t n_frames = dic_output.disps.size();
494 if (n_frames > 0) {
495 std::vector<std::string> output_fields = {"u", "v", "cc", "roi"};
496 matvar_t* output_struct = createStructVariable("output", output_fields);
497
498 // Create cell arrays for displacement components
499 size_t cell_dims[2] = {n_frames, 1};
500 matvar_t* u_cell = Mat_VarCreate("u", MAT_C_CELL, MAT_T_CELL, 2, cell_dims, nullptr, 0);
501 matvar_t* v_cell = Mat_VarCreate("v", MAT_C_CELL, MAT_T_CELL, 2, cell_dims, nullptr, 0);
502 matvar_t* cc_cell = Mat_VarCreate("cc", MAT_C_CELL, MAT_T_CELL, 2, cell_dims, nullptr, 0);
503 matvar_t* roi_cell = Mat_VarCreate("roi", MAT_C_CELL, MAT_T_CELL, 2, cell_dims, nullptr, 0);
504
505 // Fill cells with actual data from each frame
506 for (size_t i = 0; i < n_frames; ++i) {
507 const auto& disp = dic_output.disps[i];
508 const auto& u_array = disp.get_u().get_array();
509 const auto& v_array = disp.get_v().get_array();
510 const auto& cc_array = disp.get_cc().get_array();
511 const auto& roi_mask = disp.get_roi().get_mask();
512
513 size_t height = u_array.height();
514 size_t width = u_array.width();
515 size_t data_dims[2] = {height, width};
516
517 // Create u displacement matrix (column-major for MATLAB)
518 double* u_data = new double[height * width];
519 for (size_t y = 0; y < height; ++y) {
520 for (size_t x = 0; x < width; ++x) {
521 u_data[x * height + y] = u_array(y, x);
522 }
523 }
524 matvar_t* u_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, u_data, MAT_F_DONT_COPY_DATA);
525 Mat_VarSetCell(u_cell, i, u_mat);
526
527 // Create v displacement matrix
528 double* v_data = new double[height * width];
529 for (size_t y = 0; y < height; ++y) {
530 for (size_t x = 0; x < width; ++x) {
531 v_data[x * height + y] = v_array(y, x);
532 }
533 }
534 matvar_t* v_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, v_data, MAT_F_DONT_COPY_DATA);
535 Mat_VarSetCell(v_cell, i, v_mat);
536
537 // Create correlation coefficient matrix
538 double* cc_data = new double[height * width];
539 for (size_t y = 0; y < height; ++y) {
540 for (size_t x = 0; x < width; ++x) {
541 cc_data[x * height + y] = cc_array(y, x);
542 }
543 }
544 matvar_t* cc_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, cc_data, MAT_F_DONT_COPY_DATA);
545 Mat_VarSetCell(cc_cell, i, cc_mat);
546
547 // Create ROI mask matrix
548 uint8_t* roi_data = new uint8_t[height * width];
549 for (size_t y = 0; y < height; ++y) {
550 for (size_t x = 0; x < width; ++x) {
551 roi_data[x * height + y] = roi_mask(y, x) ? 1 : 0;
552 }
553 }
554 matvar_t* roi_mat = Mat_VarCreate("", MAT_C_UINT8, MAT_T_UINT8, 2, data_dims, roi_data, MAT_F_DONT_COPY_DATA);
555 Mat_VarSetCell(roi_cell, i, roi_mat);
556 }
557
558 // Add cells to output struct
559 Mat_VarSetStructFieldByName(output_struct, "u", 0, u_cell);
560 Mat_VarSetStructFieldByName(output_struct, "v", 0, v_cell);
561 Mat_VarSetStructFieldByName(output_struct, "cc", 0, cc_cell);
562 Mat_VarSetStructFieldByName(output_struct, "roi", 0, roi_cell);
563
564 // Write output struct
565 Mat_VarWrite(matfp, output_struct, MAT_COMPRESSION_NONE);
566 Mat_VarFree(output_struct);
567 }
568
569 Mat_Close(matfp);
570
571 LOG_INFO << "Wrote DIC result file: " << filename << " (" << n_frames << " frames)";
572 return true;
573}
574
575// Private helper methods
576
577mat_t* MatWriter::createMatFileHDF5(const std::string& filename) {
578 // Create HDF5-based MAT file (v7.3)
579 mat_t* matfp = Mat_CreateVer(filename.c_str(), nullptr, MAT_FT_MAT73);
580 return matfp;
581}
582
583mat_t* MatWriter::createMatFileV5(const std::string& filename) {
584 // Create standard MAT file (v5)
585 mat_t* matfp = Mat_CreateVer(filename.c_str(), nullptr, MAT_FT_MAT5);
586 return matfp;
587}
588
589bool MatWriter::writeMatVariable(mat_t* matfp,
590 const std::string& varname,
591 const cv::Mat& mat) {
592 if (mat.empty()) {
593 LOG_ERROR << "Cannot write empty Mat";
594 return false;
595 }
596
597 // Convert OpenCV Mat to matio format
598 // Note: MATLAB is column-major, OpenCV is row-major
599 std::vector<size_t> dims = {static_cast<size_t>(mat.rows), static_cast<size_t>(mat.cols)};
600
601 matio_types mat_type;
602 matio_classes mat_class;
603
604 switch (mat.type()) {
605 case CV_8UC1:
606 mat_type = MAT_T_UINT8;
607 mat_class = MAT_C_UINT8;
608 break;
609 case CV_16UC1:
610 mat_type = MAT_T_UINT16;
611 mat_class = MAT_C_UINT16;
612 break;
613 case CV_32FC1:
614 mat_type = MAT_T_SINGLE;
615 mat_class = MAT_C_SINGLE;
616 break;
617 case CV_64FC1:
618 mat_type = MAT_T_DOUBLE;
619 mat_class = MAT_C_DOUBLE;
620 break;
621 default:
622 LOG_ERROR << "Unsupported Mat type: " << mat.type();
623 return false;
624 }
625
626 // Transpose data for MATLAB column-major format
627 cv::Mat transposed = mat.t();
628
629 matvar_t* matvar = Mat_VarCreate(varname.c_str(), mat_class, mat_type,
630 2, dims.data(), transposed.data, 0);
631 if (!matvar) {
632 LOG_ERROR << "Failed to create variable: " << varname;
633 return false;
634 }
635
636 int result = Mat_VarWrite(matfp, matvar, MAT_COMPRESSION_NONE);
637 Mat_VarFree(matvar);
638
639 return (result == 0);
640}
641
642bool MatWriter::writeArrayVariable(mat_t* matfp,
643 const std::string& varname,
644 const void* data,
645 const std::vector<size_t>& dims,
646 matio_types data_type,
647 matio_classes class_type) {
648 int rank = dims.size();
649
650 matvar_t* matvar = Mat_VarCreate(varname.c_str(), class_type, data_type,
651 rank, const_cast<size_t*>(dims.data()), (void*)data, 0);
652 if (!matvar) {
653 LOG_ERROR << "Failed to create array variable: " << varname;
654 return false;
655 }
656
657 int result = Mat_VarWrite(matfp, matvar, MAT_COMPRESSION_NONE);
658 Mat_VarFree(matvar);
659
660 return (result == 0);
661}
662
663bool MatWriter::writeStringVariable(mat_t* matfp,
664 const std::string& varname,
665 const std::string& str) {
666 std::vector<size_t> dims = {1, str.length()};
667
668 matvar_t* matvar = Mat_VarCreate(varname.c_str(), MAT_C_CHAR, MAT_T_UTF8,
669 2, dims.data(), (void*)str.c_str(), 0);
670 if (!matvar) {
671 LOG_ERROR << "Failed to create string variable: " << varname;
672 return false;
673 }
674
675 int result = Mat_VarWrite(matfp, matvar, MAT_COMPRESSION_NONE);
676 Mat_VarFree(matvar);
677
678 return (result == 0);
679}
680
681bool MatWriter::writeScalarVariable(mat_t* matfp,
682 const std::string& varname,
683 double value) {
684 std::vector<size_t> dims = {1, 1};
685
686 matvar_t* matvar = Mat_VarCreate(varname.c_str(), MAT_C_DOUBLE, MAT_T_DOUBLE,
687 2, dims.data(), &value, 0);
688 if (!matvar) {
689 LOG_ERROR << "Failed to create scalar variable: " << varname;
690 return false;
691 }
692
693 int result = Mat_VarWrite(matfp, matvar, MAT_COMPRESSION_NONE);
694 Mat_VarFree(matvar);
695
696 return (result == 0);
697}
698
699matvar_t* MatWriter::createStructVariable(const std::string& struct_name,
700 const std::vector<std::string>& field_names) {
701 // Create struct variable
702 std::vector<size_t> dims = {1, 1}; // Scalar struct
703
704 // Convert field names to char** format
705 std::vector<const char*> c_field_names;
706 for (const auto& name : field_names) {
707 c_field_names.push_back(name.c_str());
708 }
709
710 matvar_t* struct_var = Mat_VarCreateStruct(struct_name.c_str(),
711 2, dims.data(),
712 c_field_names.data(),
713 field_names.size());
714
715 return struct_var;
716}
717
718bool MatWriter::addFieldToStruct(matvar_t* struct_var,
719 const std::string& field_name,
720 matvar_t* field_var,
721 size_t index) {
722 if (!struct_var || struct_var->class_type != MAT_C_STRUCT) {
723 LOG_ERROR << "Not a struct variable";
724 return false;
725 }
726
727 if (!field_var) {
728 LOG_ERROR << "Null field variable adding: " << field_name;
729 return false;
730 }
731
732 // Use matio API to set struct field
733 Mat_VarSetStructFieldByName(struct_var, field_name.c_str(), index, field_var);
734 return true;
735}
736
737void MatWriter::convertDispToArrays(const ncorr::Disp2D& disp,
738 std::vector<double>& u_array,
739 std::vector<double>& v_array,
740 cv::Mat& roi_mask) {
741 const auto& u = disp.get_u().get_array();
742 const auto& v = disp.get_v().get_array();
743 const auto& roi = disp.get_roi().get_mask();
744
745 size_t height = u.height();
746 size_t width = u.width();
747
748 // Convert displacement arrays
749 u_array.resize(height * width);
750 v_array.resize(height * width);
751
752 for (size_t y = 0; y < height; ++y) {
753 for (size_t x = 0; x < width; ++x) {
754 size_t idx = y * width + x;
755 u_array[idx] = u(y, x);
756 v_array[idx] = v(y, x);
757 }
758 }
759
760 // Convert ROI mask
761 roi_mask = cv::Mat(height, width, CV_8UC1);
762 for (size_t y = 0; y < height; ++y) {
763 for (size_t x = 0; x < width; ++x) {
764 roi_mask.at<uint8_t>(y, x) = roi(y, x) ? 255 : 0;
765 }
766 }
767}
768
769matvar_t* MatWriter::formatDispInfo(const std::map<std::string, double>& params) {
770 // Create dispinfo struct with all required fields
771 std::vector<std::string> field_names = {
772 "cutoff_corrcoef", "cutoff_diffnorm", "cutoff_iteration", "imgcorr",
773 "lenscoef", "pixtounits", "radius", "spacing", "subsettrunc",
774 "total_threads", "type", "units"
775 };
776
777 matvar_t* dispinfo = createStructVariable("dispinfo", field_names);
778 if (!dispinfo) return nullptr;
779
780 // Helper to write scalar field (1x1)
781 auto writeScalarField = [&](const std::string& field_name, double value) {
782 size_t dims[2] = {1, 1};
783 double* data = new double[1];
784 data[0] = value;
785 matvar_t* field_var = Mat_VarCreate(field_name.c_str(), MAT_C_DOUBLE, MAT_T_DOUBLE,
786 2, dims, data, MAT_F_DONT_COPY_DATA);
787 if (field_var) {
788 Mat_VarSetStructFieldByName(dispinfo, field_name.c_str(), 0, field_var);
789 }
790 };
791
792 // Populate numeric fields from params map (all 1x1 except cutoff_corrcoef)
793 for (const auto& p : params) {
794 if (p.first == "type" || p.first == "units" || p.first == "cutoff_corrcoef") {
795 continue; // Handle these separately
796 }
797 writeScalarField(p.first, p.second);
798 }
799
800 // Special handling for cutoff_corrcoef (1x2 array)
801 size_t corrcoef_dims[2] = {1, 2};
802 double* corrcoef_data = new double[2];
803 // Use value from params if available, otherwise default
804 auto it = params.find("cutoff_corrcoef");
805 corrcoef_data[0] = (it != params.end()) ? it->second : 0.0;
806 corrcoef_data[1] = corrcoef_data[0]; // Both values same by default
807 matvar_t* corrcoef_var = Mat_VarCreate("cutoff_corrcoef", MAT_C_DOUBLE, MAT_T_DOUBLE,
808 2, corrcoef_dims, corrcoef_data, MAT_F_DONT_COPY_DATA);
809 Mat_VarSetStructFieldByName(dispinfo, "cutoff_corrcoef", 0, corrcoef_var);
810
811 // Add empty imgcorr struct
812 std::vector<std::string> imgcorr_fields = {"idx_ref", "idx_cur"};
813 matvar_t* imgcorr_var = createStructVariable("imgcorr", imgcorr_fields);
814 Mat_VarSetStructFieldByName(dispinfo, "imgcorr", 0, imgcorr_var);
815
816 // Add lenscoef (default 0.0 if not in params)
817 auto lenscoef_it = params.find("lenscoef");
818 double lenscoef_val = (lenscoef_it != params.end()) ? lenscoef_it->second : 0.0;
819 writeScalarField("lenscoef", lenscoef_val);
820
821 // Add pixtounits (default 1.0 if not in params)
822 auto pixtounits_it = params.find("pixtounits");
823 double pixtounits_val = (pixtounits_it != params.end()) ? pixtounits_it->second : 1.0;
824 writeScalarField("pixtounits", pixtounits_val);
825
826 // Write string fields
827 std::string type_str = "Regular";
828 size_t type_dims[2] = {1, type_str.length()};
829 matvar_t* type_var = Mat_VarCreate("type", MAT_C_CHAR, MAT_T_UTF8, 2, type_dims,
830 (void*)type_str.c_str(), 0);
831 Mat_VarSetStructFieldByName(dispinfo, "type", 0, type_var);
832
833 std::string units_str = "pixels";
834 size_t units_dims[2] = {1, units_str.length()};
835 matvar_t* units_var = Mat_VarCreate("units", MAT_C_CHAR, MAT_T_UTF8, 2, units_dims,
836 (void*)units_str.c_str(), 0);
837 Mat_VarSetStructFieldByName(dispinfo, "units", 0, units_var);
838
839 return dispinfo;
840}
841
842matvar_t* MatWriter::formatDisplacements(const ncorr::DIC_analysis_output& dic_output) {
843 size_t n_frames = dic_output.disps.size();
844 if (n_frames == 0) return nullptr;
845
846 // Create cell arrays for each field
847 size_t frame_dims[2] = {n_frames, 1};
848
849 matvar_t* plot_u_dic = Mat_VarCreate("plot_u_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
850 matvar_t* plot_v_dic = Mat_VarCreate("plot_v_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
851 matvar_t* plot_u_ref = Mat_VarCreate("plot_u_ref_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
852 matvar_t* plot_v_ref = Mat_VarCreate("plot_v_ref_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
853 matvar_t* roi_dic = Mat_VarCreate("roi_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
854 matvar_t* plot_corrcoef = Mat_VarCreate("plot_corrcoef_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
855
856 // Fill cells with data from each frame
857 for (size_t i = 0; i < n_frames; ++i) {
858 const auto& disp = dic_output.disps[i];
859 const auto& u_array = disp.get_u().get_array();
860 const auto& v_array = disp.get_v().get_array();
861 const auto& roi_mask = disp.get_roi().get_mask();
862
863 size_t height = u_array.height();
864 size_t width = u_array.width();
865 size_t data_dims[2] = {height, width};
866
867 // Create u displacement matrix (column-major for MATLAB)
868 double* u_data = new double[height * width];
869 for (size_t y = 0; y < height; ++y) {
870 for (size_t x = 0; x < width; ++x) {
871 u_data[x * height + y] = u_array(y, x);
872 }
873 }
874 matvar_t* u_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, u_data, MAT_F_DONT_COPY_DATA);
875 Mat_VarSetCell(plot_u_dic, i, u_mat);
876 Mat_VarSetCell(plot_u_ref, i, Mat_VarDuplicate(u_mat, 1));
877
878 // Create v displacement matrix
879 double* v_data = new double[height * width];
880 for (size_t y = 0; y < height; ++y) {
881 for (size_t x = 0; x < width; ++x) {
882 v_data[x * height + y] = v_array(y, x);
883 }
884 }
885 matvar_t* v_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, v_data, MAT_F_DONT_COPY_DATA);
886 Mat_VarSetCell(plot_v_dic, i, v_mat);
887 Mat_VarSetCell(plot_v_ref, i, Mat_VarDuplicate(v_mat, 1));
888
889 // Create ROI mask matrix
890 uint8_t* roi_data = new uint8_t[height * width];
891 for (size_t y = 0; y < height; ++y) {
892 for (size_t x = 0; x < width; ++x) {
893 roi_data[x * height + y] = roi_mask(y, x) ? 1 : 0;
894 }
895 }
896 matvar_t* roi_mat = Mat_VarCreate("", MAT_C_UINT8, MAT_T_UINT8, 2, data_dims, roi_data, MAT_F_DONT_COPY_DATA);
897 Mat_VarSetCell(roi_dic, i, roi_mat);
898
899 // Correlation coefficient - use actual data from DIC output
900 const auto& cc_array = disp.get_cc().get_array();
901 double* cc_data = new double[height * width];
902 for (size_t y = 0; y < height; ++y) {
903 for (size_t x = 0; x < width; ++x) {
904 cc_data[x * height + y] = cc_array(y, x);
905 }
906 }
907 matvar_t* cc_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, cc_data, MAT_F_DONT_COPY_DATA);
908 Mat_VarSetCell(plot_corrcoef, i, cc_mat);
909 }
910
911 // Create struct and populate
912 std::vector<std::string> field_names = {
913 "plot_corrcoef_dic", "plot_u_dic", "plot_v_dic",
914 "plot_u_ref_formatted", "plot_v_ref_formatted", "roi_dic"
915 };
916
917 matvar_t* displacements = createStructVariable("displacements", field_names);
918 Mat_VarSetStructFieldByName(displacements, "plot_corrcoef_dic", 0, plot_corrcoef);
919 Mat_VarSetStructFieldByName(displacements, "plot_u_dic", 0, plot_u_dic);
920 Mat_VarSetStructFieldByName(displacements, "plot_v_dic", 0, plot_v_dic);
921 Mat_VarSetStructFieldByName(displacements, "plot_u_ref_formatted", 0, plot_u_ref);
922 Mat_VarSetStructFieldByName(displacements, "plot_v_ref_formatted", 0, plot_v_ref);
923 Mat_VarSetStructFieldByName(displacements, "roi_dic", 0, roi_dic);
924
925 return displacements;
926}
927
928matvar_t* MatWriter::formatDisplacements(const ncorr::DIC_analysis_output& dic_lagrangian,
929 const ncorr::DIC_analysis_output& dic_eulerian) {
930 size_t n_frames = dic_lagrangian.disps.size();
931 if (n_frames == 0) return nullptr;
932
933 // Verify both outputs have same number of frames
934 if (dic_eulerian.disps.size() != n_frames) {
935 throw std::runtime_error("Lagrangian and Eulerian outputs must have same number of frames");
936 }
937
938 // Create cell arrays for each field
939 size_t frame_dims[2] = {n_frames, 1};
940
941 matvar_t* plot_u_dic = Mat_VarCreate("plot_u_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
942 matvar_t* plot_v_dic = Mat_VarCreate("plot_v_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
943 matvar_t* plot_u_ref = Mat_VarCreate("plot_u_ref_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
944 matvar_t* plot_v_ref = Mat_VarCreate("plot_v_ref_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
945 matvar_t* plot_u_cur = Mat_VarCreate("plot_u_cur_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
946 matvar_t* plot_v_cur = Mat_VarCreate("plot_v_cur_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
947 matvar_t* roi_dic = Mat_VarCreate("roi_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
948 matvar_t* roi_ref = Mat_VarCreate("roi_ref_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
949 matvar_t* roi_cur = Mat_VarCreate("roi_cur_formatted", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
950 matvar_t* plot_corrcoef = Mat_VarCreate("plot_corrcoef_dic", MAT_C_CELL, MAT_T_CELL, 2, frame_dims, nullptr, 0);
951
952 // Fill cells with data from each frame
953 for (size_t i = 0; i < n_frames; ++i) {
954 const auto& disp_lag = dic_lagrangian.disps[i];
955 const auto& disp_eul = dic_eulerian.disps[i];
956
957 // Get arrays from Lagrangian
958 const auto& u_lag_array = disp_lag.get_u().get_array();
959 const auto& v_lag_array = disp_lag.get_v().get_array();
960 const auto& cc_lag_array = disp_lag.get_cc().get_array();
961 const auto& roi_lag_mask = disp_lag.get_roi().get_mask();
962
963 // Get arrays from Eulerian
964 const auto& u_eul_array = disp_eul.get_u().get_array();
965 const auto& v_eul_array = disp_eul.get_v().get_array();
966 const auto& roi_eul_mask = disp_eul.get_roi().get_mask();
967
968 size_t height = u_lag_array.height();
969 size_t width = u_lag_array.width();
970 size_t data_dims[2] = {height, width};
971
972 // Create u displacement matrices (column-major for MATLAB)
973
974 // Raw DIC (plot_u_dic) - from Lagrangian
975 double* u_dic_data = new double[height * width];
976 for (size_t y = 0; y < height; ++y) {
977 for (size_t x = 0; x < width; ++x) {
978 u_dic_data[x * height + y] = u_lag_array(y, x);
979 }
980 }
981 matvar_t* u_dic_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, u_dic_data, MAT_F_DONT_COPY_DATA);
982 Mat_VarSetCell(plot_u_dic, i, u_dic_mat);
983
984 // Lagrangian formatted (plot_u_ref_formatted)
985 double* u_ref_data = new double[height * width];
986 for (size_t y = 0; y < height; ++y) {
987 for (size_t x = 0; x < width; ++x) {
988 u_ref_data[x * height + y] = u_lag_array(y, x);
989 }
990 }
991 matvar_t* u_ref_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, u_ref_data, MAT_F_DONT_COPY_DATA);
992 Mat_VarSetCell(plot_u_ref, i, u_ref_mat);
993
994 // Eulerian formatted (plot_u_cur_formatted)
995 double* u_cur_data = new double[height * width];
996 for (size_t y = 0; y < height; ++y) {
997 for (size_t x = 0; x < width; ++x) {
998 u_cur_data[x * height + y] = u_eul_array(y, x);
999 }
1000 }
1001 matvar_t* u_cur_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, u_cur_data, MAT_F_DONT_COPY_DATA);
1002 Mat_VarSetCell(plot_u_cur, i, u_cur_mat);
1003
1004 // Create v displacement matrices
1005
1006 // Raw DIC (plot_v_dic) - from Lagrangian
1007 double* v_dic_data = new double[height * width];
1008 for (size_t y = 0; y < height; ++y) {
1009 for (size_t x = 0; x < width; ++x) {
1010 v_dic_data[x * height + y] = v_lag_array(y, x);
1011 }
1012 }
1013 matvar_t* v_dic_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, v_dic_data, MAT_F_DONT_COPY_DATA);
1014 Mat_VarSetCell(plot_v_dic, i, v_dic_mat);
1015
1016 // Lagrangian formatted (plot_v_ref_formatted)
1017 double* v_ref_data = new double[height * width];
1018 for (size_t y = 0; y < height; ++y) {
1019 for (size_t x = 0; x < width; ++x) {
1020 v_ref_data[x * height + y] = v_lag_array(y, x);
1021 }
1022 }
1023 matvar_t* v_ref_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, v_ref_data, MAT_F_DONT_COPY_DATA);
1024 Mat_VarSetCell(plot_v_ref, i, v_ref_mat);
1025
1026 // Eulerian formatted (plot_v_cur_formatted)
1027 double* v_cur_data = new double[height * width];
1028 for (size_t y = 0; y < height; ++y) {
1029 for (size_t x = 0; x < width; ++x) {
1030 v_cur_data[x * height + y] = v_eul_array(y, x);
1031 }
1032 }
1033 matvar_t* v_cur_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, v_cur_data, MAT_F_DONT_COPY_DATA);
1034 Mat_VarSetCell(plot_v_cur, i, v_cur_mat);
1035
1036 // Create ROI mask matrices
1037
1038 // Raw ROI (roi_dic) - from Lagrangian
1039 uint8_t* roi_dic_data = new uint8_t[height * width];
1040 for (size_t y = 0; y < height; ++y) {
1041 for (size_t x = 0; x < width; ++x) {
1042 roi_dic_data[x * height + y] = roi_lag_mask(y, x) ? 1 : 0;
1043 }
1044 }
1045 matvar_t* roi_dic_mat = Mat_VarCreate("", MAT_C_UINT8, MAT_T_UINT8, 2, data_dims, roi_dic_data, MAT_F_DONT_COPY_DATA);
1046 Mat_VarSetCell(roi_dic, i, roi_dic_mat);
1047
1048 // Lagrangian ROI (roi_ref_formatted)
1049 uint8_t* roi_ref_data = new uint8_t[height * width];
1050 for (size_t y = 0; y < height; ++y) {
1051 for (size_t x = 0; x < width; ++x) {
1052 roi_ref_data[x * height + y] = roi_lag_mask(y, x) ? 1 : 0;
1053 }
1054 }
1055 matvar_t* roi_ref_mat = Mat_VarCreate("", MAT_C_UINT8, MAT_T_UINT8, 2, data_dims, roi_ref_data, MAT_F_DONT_COPY_DATA);
1056 Mat_VarSetCell(roi_ref, i, roi_ref_mat);
1057
1058 // Eulerian ROI (roi_cur_formatted)
1059 uint8_t* roi_cur_data = new uint8_t[height * width];
1060 for (size_t y = 0; y < height; ++y) {
1061 for (size_t x = 0; x < width; ++x) {
1062 roi_cur_data[x * height + y] = roi_eul_mask(y, x) ? 1 : 0;
1063 }
1064 }
1065 matvar_t* roi_cur_mat = Mat_VarCreate("", MAT_C_UINT8, MAT_T_UINT8, 2, data_dims, roi_cur_data, MAT_F_DONT_COPY_DATA);
1066 Mat_VarSetCell(roi_cur, i, roi_cur_mat);
1067
1068 // Correlation coefficient from Lagrangian
1069 double* cc_data = new double[height * width];
1070 for (size_t y = 0; y < height; ++y) {
1071 for (size_t x = 0; x < width; ++x) {
1072 cc_data[x * height + y] = cc_lag_array(y, x);
1073 }
1074 }
1075 matvar_t* cc_mat = Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, data_dims, cc_data, MAT_F_DONT_COPY_DATA);
1076 Mat_VarSetCell(plot_corrcoef, i, cc_mat);
1077 }
1078
1079 // Create struct and populate with all fields
1080 std::vector<std::string> field_names = {
1081 "plot_corrcoef_dic",
1082 "plot_u_dic", "plot_v_dic",
1083 "plot_u_ref_formatted", "plot_v_ref_formatted",
1084 "plot_u_cur_formatted", "plot_v_cur_formatted",
1085 "roi_dic",
1086 "roi_ref_formatted", "roi_cur_formatted"
1087 };
1088
1089 matvar_t* displacements = createStructVariable("displacements", field_names);
1090 Mat_VarSetStructFieldByName(displacements, "plot_corrcoef_dic", 0, plot_corrcoef);
1091 Mat_VarSetStructFieldByName(displacements, "plot_u_dic", 0, plot_u_dic);
1092 Mat_VarSetStructFieldByName(displacements, "plot_v_dic", 0, plot_v_dic);
1093 Mat_VarSetStructFieldByName(displacements, "plot_u_ref_formatted", 0, plot_u_ref);
1094 Mat_VarSetStructFieldByName(displacements, "plot_v_ref_formatted", 0, plot_v_ref);
1095 Mat_VarSetStructFieldByName(displacements, "plot_u_cur_formatted", 0, plot_u_cur);
1096 Mat_VarSetStructFieldByName(displacements, "plot_v_cur_formatted", 0, plot_v_cur);
1097 Mat_VarSetStructFieldByName(displacements, "roi_dic", 0, roi_dic);
1098 Mat_VarSetStructFieldByName(displacements, "roi_ref_formatted", 0, roi_ref);
1099 Mat_VarSetStructFieldByName(displacements, "roi_cur_formatted", 0, roi_cur);
1100
1101 return displacements;
1102}
1103
1104bool MatWriter::convertBinToMat(const std::string& bin_path,
1105 const std::string& mat_path,
1106 const ncorr::DIC_analysis_input& dic_input) {
1107 // Load DIC output from binary
1108 ncorr::DIC_analysis_output dic_output;
1109 try {
1110 dic_output = ncorr::DIC_analysis_output::load(bin_path);
1111 } catch (const std::exception& e) {
1112 LOG_ERROR << "Failed to load .bin file: " << bin_path << ": " << e.what();
1113 return false;
1114 }
1115
1116 // Write to .mat format
1117 bool success = writeDICResultFile(mat_path, dic_input, dic_output);
1118
1119 if (success) {
1120 LOG_INFO << "Converted " << bin_path << " -> " << mat_path;
1121 }
1122
1123 return success;
1124}
1125
1126bool MatWriter::writeDIC2DPairResults(const std::string& filename,
1127 const DIC2DPairResults& results) {
1128 mat_t* matfp = createMatFileHDF5(filename);
1129 if (!matfp) {
1130 LOG_ERROR << "Failed to create DIC2DPairResults file: " << filename;
1131 return false;
1132 }
1133
1134 // Write scalar fields
1135 writeScalarVariable(matfp, "nCamRef", results.nCamRef);
1136 writeScalarVariable(matfp, "nCamDef", results.nCamDef);
1137 writeScalarVariable(matfp, "nImages", results.nImages);
1138 writeScalarVariable(matfp, "pairForced", results.pairForced ? 1.0 : 0.0);
1139 if (!results.pairOrder.empty()) {
1140 std::vector<double> pair_order(results.pairOrder.begin(), results.pairOrder.end());
1141 std::vector<size_t> dims = {1, pair_order.size()};
1142 writeArrayVariable(matfp, "pairOrder", pair_order.data(), dims, MAT_T_DOUBLE, MAT_C_DOUBLE);
1143 }
1144
1145 // Write ROI mask
1146 if (!results.ROImask.empty()) {
1147 writeMatVariable(matfp, "ROImask", results.ROImask);
1148 }
1149
1150 // Write ncorrInfo structure
1151 std::vector<std::string> ncorr_fields = {
1152 "cutoff_corrcoef", "cutoff_diffnorm", "cutoff_iteration",
1153 "imgcorr", "lenscoef", "pixtounits", "radius", "spacing",
1154 "stepanalysis", "subsettrunc", "total_threads", "type", "units"
1155 };
1156 matvar_t* ncorr_struct = createStructVariable("ncorrInfo", ncorr_fields);
1157
1158 // Add cutoff_corrcoef array (MATLAB: 1×N row vector)
1159 if (!results.ncorrInfo.cutoff_corrcoef.empty()) {
1160 std::vector<size_t> dims = {1, results.ncorrInfo.cutoff_corrcoef.size()};
1161 matvar_t* cutoff_var = Mat_VarCreate("cutoff_corrcoef", MAT_C_DOUBLE, MAT_T_DOUBLE,
1162 2, dims.data(), (void*)results.ncorrInfo.cutoff_corrcoef.data(), 0);
1163 Mat_VarSetStructFieldByName(ncorr_struct, "cutoff_corrcoef", 0, cutoff_var);
1164 }
1165
1166 // Add scalar fields
1167 matvar_t* diffnorm_var = Mat_VarCreate("cutoff_diffnorm", MAT_C_DOUBLE, MAT_T_DOUBLE,
1168 2, scalar_dims, (void*)&results.ncorrInfo.cutoff_diffnorm, 0);
1169 Mat_VarSetStructFieldByName(ncorr_struct, "cutoff_diffnorm", 0, diffnorm_var);
1170
1171 matvar_t* iteration_var = Mat_VarCreate("cutoff_iteration", MAT_C_INT32, MAT_T_INT32,
1172 2, scalar_dims, (void*)&results.ncorrInfo.cutoff_iteration, 0);
1173 Mat_VarSetStructFieldByName(ncorr_struct, "cutoff_iteration", 0, iteration_var);
1174
1175 matvar_t* lenscoef_var = Mat_VarCreate("lenscoef", MAT_C_INT32, MAT_T_INT32,
1176 2, scalar_dims, (void*)&results.ncorrInfo.lenscoef, 0);
1177 Mat_VarSetStructFieldByName(ncorr_struct, "lenscoef", 0, lenscoef_var);
1178
1179 matvar_t* pixtounits_var = Mat_VarCreate("pixtounits", MAT_C_DOUBLE, MAT_T_DOUBLE,
1180 2, scalar_dims, (void*)&results.ncorrInfo.pixtounits, 0);
1181 Mat_VarSetStructFieldByName(ncorr_struct, "pixtounits", 0, pixtounits_var);
1182
1183 matvar_t* radius_var = Mat_VarCreate("radius", MAT_C_INT32, MAT_T_INT32,
1184 2, scalar_dims, (void*)&results.ncorrInfo.radius, 0);
1185 Mat_VarSetStructFieldByName(ncorr_struct, "radius", 0, radius_var);
1186
1187 matvar_t* spacing_var = Mat_VarCreate("spacing", MAT_C_INT32, MAT_T_INT32,
1188 2, scalar_dims, (void*)&results.ncorrInfo.spacing, 0);
1189 Mat_VarSetStructFieldByName(ncorr_struct, "spacing", 0, spacing_var);
1190
1191 // Add stepanalysis struct
1192 std::vector<std::string> stepanalysis_fields = {"enabled", "type", "auto", "step"};
1193 matvar_t* stepanalysis_struct = createStructVariable("stepanalysis", stepanalysis_fields);
1194
1195 int enabled_int = results.ncorrInfo.stepanalysis.enabled ? 1 : 0;
1196 matvar_t* enabled_var = Mat_VarCreate("enabled", MAT_C_INT32, MAT_T_INT32,
1197 2, scalar_dims, &enabled_int, 0);
1198 Mat_VarSetStructFieldByName(stepanalysis_struct, "enabled", 0, enabled_var);
1199
1200 if (!results.ncorrInfo.stepanalysis.type.empty()) {
1201 std::vector<size_t> dims = {1, results.ncorrInfo.stepanalysis.type.length()};
1202 matvar_t* type_var = Mat_VarCreate("type", MAT_C_CHAR, MAT_T_UINT8,
1203 2, dims.data(), (void*)results.ncorrInfo.stepanalysis.type.c_str(), 0);
1204 Mat_VarSetStructFieldByName(stepanalysis_struct, "type", 0, type_var);
1205 }
1206
1207 int auto_int = results.ncorrInfo.stepanalysis.auto_update ? 1 : 0;
1208 matvar_t* auto_var = Mat_VarCreate("auto", MAT_C_INT32, MAT_T_INT32,
1209 2, scalar_dims, &auto_int, 0);
1210 Mat_VarSetStructFieldByName(stepanalysis_struct, "auto", 0, auto_var);
1211
1212 matvar_t* step_var = Mat_VarCreate("step", MAT_C_INT32, MAT_T_INT32,
1213 2, scalar_dims, (void*)&results.ncorrInfo.stepanalysis.step, 0);
1214 Mat_VarSetStructFieldByName(stepanalysis_struct, "step", 0, step_var);
1215
1216 Mat_VarSetStructFieldByName(ncorr_struct, "stepanalysis", 0, stepanalysis_struct);
1217
1218 int subsettrunc_int = results.ncorrInfo.subsettrunc ? 1 : 0;
1219 matvar_t* subsettrunc_var = Mat_VarCreate("subsettrunc", MAT_C_INT32, MAT_T_INT32,
1220 2, scalar_dims, &subsettrunc_int, 0);
1221 Mat_VarSetStructFieldByName(ncorr_struct, "subsettrunc", 0, subsettrunc_var);
1222
1223 matvar_t* threads_var = Mat_VarCreate("total_threads", MAT_C_INT32, MAT_T_INT32,
1224 2, scalar_dims, (void*)&results.ncorrInfo.total_threads, 0);
1225 Mat_VarSetStructFieldByName(ncorr_struct, "total_threads", 0, threads_var);
1226
1227 // Add string fields
1228 if (!results.ncorrInfo.type.empty()) {
1229 std::vector<size_t> dims = {1, results.ncorrInfo.type.length()};
1230 matvar_t* type_var = Mat_VarCreate("type", MAT_C_CHAR, MAT_T_UINT8,
1231 2, dims.data(), (void*)results.ncorrInfo.type.c_str(), 0);
1232 Mat_VarSetStructFieldByName(ncorr_struct, "type", 0, type_var);
1233 }
1234
1235 if (!results.ncorrInfo.units.empty()) {
1236 std::vector<size_t> dims = {1, results.ncorrInfo.units.length()};
1237 matvar_t* units_var = Mat_VarCreate("units", MAT_C_CHAR, MAT_T_UINT8,
1238 2, dims.data(), (void*)results.ncorrInfo.units.c_str(), 0);
1239 Mat_VarSetStructFieldByName(ncorr_struct, "units", 0, units_var);
1240 }
1241
1242 // Add imgcorr cell array
1243 if (!results.ncorrInfo.imgcorr.empty()) {
1244 std::vector<size_t> cell_dims = {results.ncorrInfo.imgcorr.size(), 1};
1245 matvar_t* imgcorr_cell = Mat_VarCreate("imgcorr", MAT_C_CELL, MAT_T_CELL,
1246 2, cell_dims.data(), nullptr, 0);
1247 for (size_t i = 0; i < results.ncorrInfo.imgcorr.size(); ++i) {
1248 const std::string& img = results.ncorrInfo.imgcorr[i];
1249 if (!img.empty()) {
1250 std::vector<size_t> dims = {1, img.length()};
1251 matvar_t* str_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8,
1252 2, dims.data(), (void*)img.c_str(), 0);
1253 Mat_VarSetCell(imgcorr_cell, i, str_var);
1254 }
1255 }
1256 Mat_VarSetStructFieldByName(ncorr_struct, "imgcorr", 0, imgcorr_cell);
1257 }
1258
1259 // Write ncorrInfo struct
1260 Mat_VarWrite(matfp, ncorr_struct, MAT_COMPRESSION_NONE);
1261 Mat_VarFree(ncorr_struct);
1262
1263 // Write Points as cell array (MATLAB: 1×nFrames cell, each cell is nPts×2)
1264 size_t n_frames = results.Points.size();
1265 if (n_frames > 0) {
1266 std::vector<size_t> cell_dims = {1, n_frames};
1267 matvar_t* points_cell = Mat_VarCreate("Points", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
1268
1269 for (size_t i = 0; i < n_frames; ++i) {
1270 const auto& pts = results.Points[i];
1271 size_t n_points = pts.x.size();
1272
1273 if (n_points == 0) {
1274 Mat_VarSetCell(points_cell, i, nullptr);
1275 continue;
1276 }
1277
1278 // Create N×2 array in MATLAB column-major layout.
1279 std::vector<double> xy_data(n_points * 2);
1280 for (size_t j = 0; j < n_points; ++j) {
1281 xy_data[j] = pts.x[j];
1282 xy_data[j + n_points] = pts.y[j];
1283 }
1284
1285 std::vector<size_t> dims = {n_points, 2};
1286 matvar_t* xy_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims.data(),
1287 xy_data.data(), 0);
1288
1289 Mat_VarSetCell(points_cell, i, xy_var);
1290 }
1291
1292 Mat_VarWrite(matfp, points_cell, MAT_COMPRESSION_NONE);
1293 Mat_VarFree(points_cell);
1294 }
1295
1296 // Write CorCoeffVec as cell array (1×nFrames, each cell is nPts×1 array)
1297 if (!results.CorCoeffVec.empty()) {
1298 matvar_t* corcoeff_cell = createCellArrayFromScalars("CorCoeffVec", results.CorCoeffVec, results.CorCoeffVec.size());
1299 if (corcoeff_cell) {
1300 Mat_VarWrite(matfp, corcoeff_cell, MAT_COMPRESSION_NONE);
1301 Mat_VarFree(corcoeff_cell);
1302 }
1303 }
1304
1305 // Write Faces (MATLAB: nFaces×3 double, 1-indexed)
1306 if (!results.Faces.empty()) {
1307 size_t n_faces = results.Faces.size() / 3;
1308 std::vector<size_t> dims = {n_faces, 3};
1309 std::vector<double> faces_dbl(n_faces * 3);
1310 for (size_t i = 0; i < n_faces; ++i) {
1311 faces_dbl[i] = static_cast<double>(results.Faces[i * 3 + 0] + 1);
1312 faces_dbl[i + n_faces] = static_cast<double>(results.Faces[i * 3 + 1] + 1);
1313 faces_dbl[i + 2 * n_faces] = static_cast<double>(results.Faces[i * 3 + 2] + 1);
1314 }
1315 writeArrayVariable(matfp, "Faces", faces_dbl.data(), dims,
1316 MAT_T_DOUBLE, MAT_C_DOUBLE);
1317 }
1318
1319 // Write FaceColors (MATLAB: nFaces×1)
1320 if (!results.FaceColors.empty()) {
1321 std::vector<size_t> dims = {results.FaceColors.size(), 1};
1322 writeArrayVariable(matfp, "FaceColors", results.FaceColors.data(), dims,
1323 MAT_T_DOUBLE, MAT_C_DOUBLE);
1324 }
1325
1326 Mat_Close(matfp);
1327 LOG_INFO << "Wrote DIC2DPairResults: " << filename;
1328 return true;
1329}
1330
1332 const DIC3Dcombined& combined,
1333 const std::string& struct_name,
1334 const std::vector<std::string>& extra_fields) {
1335 // Base fields for DIC3Dcombined
1336 std::vector<std::string> combined_fields = {
1337 "pairIndices", "Points3D", "Faces", "FaceColors", "corrComb",
1338 "FaceCorrComb", "FaceCentroids", "Disp", "FacePairInds",
1339 "PointPairInds", "calibration", "distortion", "AllPairsResults", "DIC2Dinfo"
1340 };
1341 // Append any extra fields (e.g. Deform, FaceIsoInd for PPresults)
1342 for (const auto& f : extra_fields) {
1343 combined_fields.push_back(f);
1344 }
1345
1346 matvar_t* combined_struct = createStructVariable(struct_name, combined_fields);
1347 if (!combined_struct) {
1348 LOG_ERROR << "Failed to create " << struct_name << " struct";
1349 return nullptr;
1350 }
1351
1352 // pairIndices (MATLAB: nPairs x 2 double matrix)
1353 if (!combined.pairIndices.empty()) {
1354 size_t nCams = combined.pairIndices.size();
1355 size_t nPairs = nCams / 2;
1356 if (nPairs == 0) nPairs = 1;
1357 std::vector<double> pair_dbl(nPairs * 2, 0.0);
1358 for (size_t i = 0; i < nPairs; ++i) {
1359 if (2 * i < combined.pairIndices.size()) {
1360 pair_dbl[i] = static_cast<double>(combined.pairIndices[2 * i]);
1361 }
1362 if (2 * i + 1 < combined.pairIndices.size()) {
1363 pair_dbl[i + nPairs] = static_cast<double>(combined.pairIndices[2 * i + 1]);
1364 }
1365 }
1366 std::vector<size_t> dims = {nPairs, 2};
1367 matvar_t* pair_ind_var = Mat_VarCreate("pairIndices", MAT_C_DOUBLE, MAT_T_DOUBLE,
1368 2, dims.data(), pair_dbl.data(), 0);
1369 Mat_VarSetStructFieldByName(combined_struct, "pairIndices", 0, pair_ind_var);
1370 }
1371
1372 // Points3D cell array (MATLAB: 1×nFrames cell, each cell is nPts×3 double)
1373 size_t n_frames = combined.Points3D.size();
1374 {
1375 std::vector<size_t> cell_dims = {1, n_frames};
1376 matvar_t* points3d_cell = Mat_VarCreate("Points3D", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
1377 for (size_t i = 0; i < n_frames; ++i) {
1378 const auto& pts = combined.Points3D[i];
1379 size_t n_points = pts.x.size();
1380 std::vector<size_t> dims = {n_points, 3};
1381 std::vector<double> pts_data(n_points * 3);
1382 for (size_t p = 0; p < n_points; ++p) {
1383 pts_data[p] = pts.x[p];
1384 pts_data[p + n_points] = pts.y[p];
1385 pts_data[p + 2 * n_points] = pts.z[p];
1386 }
1387 matvar_t* pt_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
1388 2, dims.data(), pts_data.data(), 0);
1389 Mat_VarSetCell(points3d_cell, i, pt_var);
1390 }
1391 Mat_VarSetStructFieldByName(combined_struct, "Points3D", 0, points3d_cell);
1392 }
1393
1394 // Faces (MATLAB: nFaces×3 double, 1-indexed)
1395 if (!combined.Faces.empty()) {
1396 size_t n_faces = combined.Faces.size() / 3;
1397 std::vector<size_t> dims = {n_faces, 3};
1398 std::vector<double> faces_dbl(n_faces * 3);
1399 for (size_t i = 0; i < n_faces; ++i) {
1400 faces_dbl[i] = static_cast<double>(combined.Faces[i * 3 + 0] + 1);
1401 faces_dbl[i + n_faces] = static_cast<double>(combined.Faces[i * 3 + 1] + 1);
1402 faces_dbl[i + 2 * n_faces] = static_cast<double>(combined.Faces[i * 3 + 2] + 1);
1403 }
1404 Mat_VarSetStructFieldByName(combined_struct, "Faces", 0,
1405 Mat_VarCreate("Faces", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims.data(), faces_dbl.data(), 0));
1406 }
1407
1408 // FaceColors (MATLAB: nFaces×1 double)
1409 if (!combined.FaceColors.empty()) {
1410 std::vector<size_t> dims = {combined.FaceColors.size(), 1};
1411 Mat_VarSetStructFieldByName(combined_struct, "FaceColors", 0,
1412 Mat_VarCreate("FaceColors", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims.data(), (void*)combined.FaceColors.data(), 0));
1413 }
1414
1415 // corrComb cell array (MATLAB: 1×nFrames cell, each cell is nPts×1)
1416 if (!combined.corrComb.empty()) {
1417 size_t n = combined.corrComb.size();
1418 std::vector<size_t> cd = {1, n};
1419 matvar_t* corr_cell = Mat_VarCreate("corrComb", MAT_C_CELL, MAT_T_CELL, 2, cd.data(), nullptr, 0);
1420 for (size_t i = 0; i < n; ++i) {
1421 const auto& cf = combined.corrComb[i];
1422 if (!cf.empty()) {
1423 std::vector<size_t> d = {cf.size(), 1};
1424 Mat_VarSetCell(corr_cell, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), (void*)cf.data(), 0));
1425 }
1426 }
1427 Mat_VarSetStructFieldByName(combined_struct, "corrComb", 0, corr_cell);
1428 }
1429
1430 // FaceCorrComb cell array (MATLAB: 1×nFrames cell, each cell is nFaces×1)
1431 if (!combined.FaceCorrComb.empty()) {
1432 size_t n = combined.FaceCorrComb.size();
1433 std::vector<size_t> cd = {1, n};
1434 matvar_t* fcc = Mat_VarCreate("FaceCorrComb", MAT_C_CELL, MAT_T_CELL, 2, cd.data(), nullptr, 0);
1435 for (size_t i = 0; i < n; ++i) {
1436 const auto& fc = combined.FaceCorrComb[i];
1437 if (!fc.empty()) {
1438 std::vector<size_t> d = {fc.size(), 1};
1439 Mat_VarSetCell(fcc, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), (void*)fc.data(), 0));
1440 }
1441 }
1442 Mat_VarSetStructFieldByName(combined_struct, "FaceCorrComb", 0, fcc);
1443 }
1444
1445 // FaceCentroids cell array (MATLAB: 1×nFrames cell, each cell is nFaces×3)
1446 if (!combined.FaceCentroids.empty()) {
1447 size_t n = combined.FaceCentroids.size();
1448 std::vector<size_t> cd = {1, n};
1449 matvar_t* cc = Mat_VarCreate("FaceCentroids", MAT_C_CELL, MAT_T_CELL, 2, cd.data(), nullptr, 0);
1450 for (size_t i = 0; i < n; ++i) {
1451 const auto& c = combined.FaceCentroids[i];
1452 if (!c.empty()) {
1453 size_t n_faces = c.size() / 3;
1454 std::vector<size_t> d = {n_faces, 3};
1455 std::vector<double> centroid_data(n_faces * 3);
1456 for (size_t f = 0; f < n_faces; ++f) {
1457 centroid_data[f] = c[f * 3 + 0];
1458 centroid_data[f + n_faces] = c[f * 3 + 1];
1459 centroid_data[f + 2 * n_faces] = c[f * 3 + 2];
1460 }
1461 Mat_VarSetCell(cc, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), centroid_data.data(), 0));
1462 }
1463 }
1464 Mat_VarSetStructFieldByName(combined_struct, "FaceCentroids", 0, cc);
1465 }
1466
1467 // Disp sub-struct (MATLAB: 1×nFrames cells)
1468 {
1469 std::vector<std::string> disp_fields = {"DispVec", "DispMgn"};
1470 matvar_t* disp_struct = createStructVariable("Disp", disp_fields);
1471 if (!combined.Disp.DispVec.empty()) {
1472 size_t n = combined.Disp.DispVec.size();
1473 std::vector<size_t> cd = {1, n};
1474 matvar_t* dvc = Mat_VarCreate("DispVec", MAT_C_CELL, MAT_T_CELL, 2, cd.data(), nullptr, 0);
1475 for (size_t i = 0; i < n; ++i) {
1476 const auto& dv = combined.Disp.DispVec[i];
1477 if (!dv.empty()) {
1478 size_t nPts = dv.size() / 3;
1479 std::vector<size_t> d = {nPts, 3};
1480 std::vector<double> disp_data(nPts * 3);
1481 for (size_t p = 0; p < nPts; ++p) {
1482 disp_data[p] = dv[p * 3 + 0];
1483 disp_data[p + nPts] = dv[p * 3 + 1];
1484 disp_data[p + 2 * nPts] = dv[p * 3 + 2];
1485 }
1486 Mat_VarSetCell(dvc, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), disp_data.data(), 0));
1487 }
1488 }
1489 Mat_VarSetStructFieldByName(disp_struct, "DispVec", 0, dvc);
1490 }
1491 if (!combined.Disp.DispMgn.empty()) {
1492 size_t n = combined.Disp.DispMgn.size();
1493 std::vector<size_t> cd = {1, n};
1494 matvar_t* dmc = Mat_VarCreate("DispMgn", MAT_C_CELL, MAT_T_CELL, 2, cd.data(), nullptr, 0);
1495 for (size_t i = 0; i < n; ++i) {
1496 const auto& dm = combined.Disp.DispMgn[i];
1497 if (!dm.empty()) {
1498 std::vector<size_t> d = {dm.size(), 1};
1499 Mat_VarSetCell(dmc, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), (void*)dm.data(), 0));
1500 }
1501 }
1502 Mat_VarSetStructFieldByName(disp_struct, "DispMgn", 0, dmc);
1503 }
1504 Mat_VarSetStructFieldByName(combined_struct, "Disp", 0, disp_struct);
1505 }
1506
1507 // FacePairInds (MATLAB: nFaces×1 double)
1508 if (!combined.FacePairInds.empty()) {
1509 std::vector<size_t> dims = {combined.FacePairInds.size(), 1};
1510 std::vector<double> fpi_dbl(combined.FacePairInds.size());
1511 for (size_t i = 0; i < combined.FacePairInds.size(); ++i)
1512 fpi_dbl[i] = static_cast<double>(combined.FacePairInds[i]);
1513 Mat_VarSetStructFieldByName(combined_struct, "FacePairInds", 0,
1514 Mat_VarCreate("FacePairInds", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims.data(), fpi_dbl.data(), 0));
1515 }
1516
1517 // PointPairInds (MATLAB: nPts×1 double)
1518 if (!combined.PointPairInds.empty()) {
1519 std::vector<size_t> dims = {combined.PointPairInds.size(), 1};
1520 std::vector<double> ppi_dbl(combined.PointPairInds.size());
1521 for (size_t i = 0; i < combined.PointPairInds.size(); ++i)
1522 ppi_dbl[i] = static_cast<double>(combined.PointPairInds[i]);
1523 Mat_VarSetStructFieldByName(combined_struct, "PointPairInds", 0,
1524 Mat_VarCreate("PointPairInds", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, dims.data(), ppi_dbl.data(), 0));
1525 }
1526
1527 // calibration sub-struct
1528 if (!combined.calibration.DLT_paths.empty()) {
1529 std::vector<std::string> calib_fields = {"DLTpath", "DLTparameters"};
1530 matvar_t* calib_struct = createStructVariable("calibration", calib_fields);
1531 size_t rows = combined.calibration.DLT_paths.size();
1532 size_t cols = (rows > 0) ? combined.calibration.DLT_paths[0].size() : 0;
1533 Mat_VarSetStructFieldByName(calib_struct, "DLTpath", 0,
1534 createCellArray2DFromStrings("DLTpath", combined.calibration.DLT_paths, rows, cols));
1535 Mat_VarSetStructFieldByName(calib_struct, "DLTparameters", 0,
1536 createCellArray2DFromVectors("DLTparameters", combined.calibration.DLT_params, rows, cols));
1537 Mat_VarSetStructFieldByName(combined_struct, "calibration", 0, calib_struct);
1538 }
1539
1540 // distortion sub-struct
1541 if (!combined.distortion.distortion_models.empty()) {
1542 std::vector<std::string> dist_fields = {"distortionModel", "distortionPath"};
1543 matvar_t* dist_struct = createStructVariable("distortion", dist_fields);
1544 size_t rows = combined.distortion.distortion_models.size();
1545 size_t cols = (rows > 0) ? combined.distortion.distortion_models[0].size() : 0;
1546 Mat_VarSetStructFieldByName(dist_struct, "distortionModel", 0,
1547 createCellArray2DFromStrings("distortionModel", combined.distortion.distortion_models, rows, cols));
1548 Mat_VarSetStructFieldByName(dist_struct, "distortionPath", 0,
1549 createCellArray2DFromStrings("distortionPath", combined.distortion.distortion_paths, rows, cols));
1550 Mat_VarSetStructFieldByName(combined_struct, "distortion", 0, dist_struct);
1551 }
1552
1553 // AllPairsResults and DIC2Dinfo: built purely in memory and attached
1554 // directly. (The previous write-to-file + Mat_VarRead round-trip through
1555 // the write handle crashed in Mat_VarSetStructFieldByName — the historical
1556 // Step-E stitching segfault — and left duplicate top-level variables that
1557 // doubled the file size.)
1558 matvar_t* apr_var = buildAllPairsResultsVar(combined.AllPairsResults);
1559 if (apr_var) Mat_VarSetStructFieldByName(combined_struct, "AllPairsResults", 0, apr_var);
1560 matvar_t* dic2d_var = buildDIC2DinfoVar(combined.DIC2Dinfo);
1561 if (dic2d_var) Mat_VarSetStructFieldByName(combined_struct, "DIC2Dinfo", 0, dic2d_var);
1562
1563 return combined_struct;
1564}
1565
1566bool MatWriter::write3DCombinedResults(const std::string& filename,
1567 const DIC3Dcombined& combined,
1568 const std::string& struct_name) {
1569 mat_t* matfp = createMatFileHDF5(filename);
1570 if (!matfp) {
1571 LOG_ERROR << "Failed to create file: " << filename;
1572 return false;
1573 }
1574
1575 matvar_t* combined_struct = buildCombinedStructFields(matfp, combined, struct_name);
1576 if (!combined_struct) {
1577 Mat_Close(matfp);
1578 return false;
1579 }
1580
1581 Mat_VarWrite(matfp, combined_struct, MAT_COMPRESSION_NONE);
1582 Mat_VarFree(combined_struct);
1583 Mat_Close(matfp);
1584 LOG_INFO << "Wrote " << struct_name << ": " << filename;
1585 return true;
1586}
1587
1588bool MatWriter::write3DPPresults(const std::string& filename,
1589 const DIC3DPPresults& ppresults) {
1590 mat_t* matfp = createMatFileHDF5(filename);
1591 if (!matfp) {
1592 LOG_ERROR << "Failed to create DIC3DPPresults file: " << filename;
1593 return false;
1594 }
1595
1596 // Build combined struct with extra fields for PP results
1597 std::vector<std::string> extra_fields = {"Deform", "FaceIsoInd", "deftype"};
1598 matvar_t* pp_struct = buildCombinedStructFields(matfp, ppresults, "DIC3DPPresults", extra_fields);
1599 if (!pp_struct) {
1600 Mat_Close(matfp);
1601 return false;
1602 }
1603
1604 // Add Deform sub-struct inside the parent struct
1605 if (!ppresults.deform_full.frames.empty()) {
1606 LOG_INFO << "Writing full Deform group with " << ppresults.deform_full.n_frames
1607 << " frames and " << ppresults.deform_full.n_faces << " faces...";
1608 matvar_t* deform_struct = buildDeformationStruct("Deform", ppresults.deform_full);
1609 if (deform_struct) {
1610 Mat_VarSetStructFieldByName(pp_struct, "Deform", 0, deform_struct);
1611 } else {
1612 LOG_WARN << "Failed to build Deform struct";
1613 }
1614 } else {
1615 LOG_WARN << "No deformation data available, skipping Deform group";
1616 }
1617
1618 // Add FaceIsoInd as cell array (MATLAB: 1×nFrames cell, each cell is nFaces×1)
1619 if (!ppresults.FaceIsoInd.empty()) {
1620 size_t n_frames_iso = ppresults.FaceIsoInd.size();
1621 std::vector<size_t> cell_dims = {1, n_frames_iso};
1622 matvar_t* iso_cell = Mat_VarCreate("FaceIsoInd", MAT_C_CELL, MAT_T_CELL, 2, cell_dims.data(), nullptr, 0);
1623 for (size_t i = 0; i < n_frames_iso; ++i) {
1624 const auto& iso = ppresults.FaceIsoInd[i];
1625 if (!iso.empty()) {
1626 std::vector<size_t> d = {iso.size(), 1};
1627 Mat_VarSetCell(iso_cell, i, Mat_VarCreate("", MAT_C_DOUBLE, MAT_T_DOUBLE, 2, d.data(), (void*)iso.data(), 0));
1628 }
1629 }
1630 Mat_VarSetStructFieldByName(pp_struct, "FaceIsoInd", 0, iso_cell);
1631 }
1632
1633 // Add deftype string inside the struct
1634 if (!ppresults.deftype.empty()) {
1635 size_t str_dims[2] = {1, ppresults.deftype.size()};
1636 matvar_t* deftype_var = Mat_VarCreate("deftype", MAT_C_CHAR, MAT_T_UTF8,
1637 2, str_dims, (void*)ppresults.deftype.c_str(), 0);
1638 if (deftype_var) {
1639 Mat_VarSetStructFieldByName(pp_struct, "deftype", 0, deftype_var);
1640 }
1641 }
1642
1643 // Write the complete struct and close
1644 Mat_VarWrite(matfp, pp_struct, MAT_COMPRESSION_NONE);
1645 Mat_VarFree(pp_struct);
1646 Mat_Close(matfp);
1647 LOG_INFO << "Wrote DIC3DPPresults: " << filename;
1648 return true;
1649}
1650
1651matvar_t* MatWriter::buildDeformationStruct(const std::string& group_name,
1652 const FrameDeformationResult& deform_data) {
1653 size_t n_frames = deform_data.n_frames;
1654
1655 // Prepare data: vector<vector<T>> where outer = frames, inner = faces
1656 std::vector<std::vector<double>> Area_data(n_frames), Lamda1_data(n_frames), Lamda2_data(n_frames);
1657 std::vector<std::vector<double>> J_data(n_frames), Emgn_data(n_frames), emgn_data(n_frames);
1658 std::vector<std::vector<double>> Epc1_data(n_frames), Epc2_data(n_frames);
1659 std::vector<std::vector<double>> epc1_data(n_frames), epc2_data(n_frames);
1660 std::vector<std::vector<double>> EShearMax_data(n_frames), eShearMax_data(n_frames);
1661 std::vector<std::vector<double>> Eeq_data(n_frames), eeq_data(n_frames), Dnorm_data(n_frames);
1662
1663 std::vector<std::vector<Eigen::Vector3d>> D1_data(n_frames), D2_data(n_frames), D3_data(n_frames);
1664 std::vector<std::vector<Eigen::Vector3d>> d1_data(n_frames), d2_data(n_frames), d3_data(n_frames);
1665 std::vector<std::vector<Eigen::Vector3d>> Drec1_data(n_frames), Drec2_data(n_frames);
1666 std::vector<std::vector<Eigen::Vector3d>> Epc1vec_data(n_frames), Epc2vec_data(n_frames);
1667 std::vector<std::vector<Eigen::Vector3d>> Epc1vecCur_data(n_frames), Epc2vecCur_data(n_frames);
1668 std::vector<std::vector<Eigen::Vector3d>> epc1vec_data(n_frames), epc2vec_data(n_frames);
1669 std::vector<std::vector<Eigen::Vector3d>> EShearMaxVec1_data(n_frames), EShearMaxVec2_data(n_frames);
1670 std::vector<std::vector<Eigen::Vector3d>> EShearMaxVecCur1_data(n_frames), EShearMaxVecCur2_data(n_frames);
1671 std::vector<std::vector<Eigen::Vector3d>> eShearMaxVec1_data(n_frames), eShearMaxVec2_data(n_frames);
1672
1673 std::vector<std::vector<Eigen::Matrix3d>> Fmat_data(n_frames), Cmat_data(n_frames);
1674 std::vector<std::vector<Eigen::Matrix3d>> Emat_data(n_frames), emat_data(n_frames);
1675
1676 for (size_t i = 0; i < n_frames && i < deform_data.frames.size(); ++i) {
1677 const auto& frame = deform_data.frames[i];
1678 Area_data[i] = frame.Area; Lamda1_data[i] = frame.Lamda1; Lamda2_data[i] = frame.Lamda2;
1679 J_data[i] = frame.J; Emgn_data[i] = frame.Emgn; emgn_data[i] = frame.emgn;
1680 Epc1_data[i] = frame.Epc1; Epc2_data[i] = frame.Epc2;
1681 epc1_data[i] = frame.epc1; epc2_data[i] = frame.epc2;
1682 EShearMax_data[i] = frame.EShearMax; eShearMax_data[i] = frame.eShearMax;
1683 Eeq_data[i] = frame.Eeq; eeq_data[i] = frame.eeq; Dnorm_data[i] = frame.Dnorm;
1684 D1_data[i] = frame.D1; D2_data[i] = frame.D2; D3_data[i] = frame.D3;
1685 d1_data[i] = frame.d1; d2_data[i] = frame.d2; d3_data[i] = frame.d3;
1686 Drec1_data[i] = frame.Drec1; Drec2_data[i] = frame.Drec2;
1687 Epc1vec_data[i] = frame.Epc1vec; Epc2vec_data[i] = frame.Epc2vec;
1688 Epc1vecCur_data[i] = frame.Epc1vecCur; Epc2vecCur_data[i] = frame.Epc2vecCur;
1689 epc1vec_data[i] = frame.epc1vec; epc2vec_data[i] = frame.epc2vec;
1690 EShearMaxVec1_data[i] = frame.EShearMaxVec1; EShearMaxVec2_data[i] = frame.EShearMaxVec2;
1691 EShearMaxVecCur1_data[i] = frame.EShearMaxVecCur1; EShearMaxVecCur2_data[i] = frame.EShearMaxVecCur2;
1692 eShearMaxVec1_data[i] = frame.eShearMaxVec1; eShearMaxVec2_data[i] = frame.eShearMaxVec2;
1693 Fmat_data[i] = frame.Fmat; Cmat_data[i] = frame.Cmat;
1694 Emat_data[i] = frame.Emat; emat_data[i] = frame.emat;
1695 }
1696
1697 // Create cell arrays for all fields
1698 matvar_t* Area_cell = createCellArrayFromScalars("Area", Area_data, n_frames);
1699 matvar_t* Lamda1_cell = createCellArrayFromScalars("Lamda1", Lamda1_data, n_frames);
1700 matvar_t* Lamda2_cell = createCellArrayFromScalars("Lamda2", Lamda2_data, n_frames);
1701 matvar_t* J_cell = createCellArrayFromScalars("J", J_data, n_frames);
1702 matvar_t* Emgn_cell = createCellArrayFromScalars("Emgn", Emgn_data, n_frames);
1703 matvar_t* emgn_cell = createCellArrayFromScalars("emgn", emgn_data, n_frames);
1704 matvar_t* Epc1_cell = createCellArrayFromScalars("Epc1", Epc1_data, n_frames);
1705 matvar_t* Epc2_cell = createCellArrayFromScalars("Epc2", Epc2_data, n_frames);
1706 matvar_t* epc1_cell = createCellArrayFromScalars("epc1", epc1_data, n_frames);
1707 matvar_t* epc2_cell = createCellArrayFromScalars("epc2", epc2_data, n_frames);
1708 matvar_t* EShearMax_cell = createCellArrayFromScalars("EShearMax", EShearMax_data, n_frames);
1709 matvar_t* eShearMax_cell = createCellArrayFromScalars("eShearMax", eShearMax_data, n_frames);
1710 matvar_t* Eeq_cell = createCellArrayFromScalars("Eeq", Eeq_data, n_frames);
1711 matvar_t* eeq_cell = createCellArrayFromScalars("eeq", eeq_data, n_frames);
1712 matvar_t* Dnorm_cell = createCellArrayFromScalars("Dnorm", Dnorm_data, n_frames);
1713
1714 matvar_t* D1_cell = createCellArrayFromVectors("D1", D1_data, n_frames);
1715 matvar_t* D2_cell = createCellArrayFromVectors("D2", D2_data, n_frames);
1716 matvar_t* D3_cell = createCellArrayFromVectors("D3", D3_data, n_frames);
1717 matvar_t* d1_cell = createCellArrayFromVectors("d1", d1_data, n_frames);
1718 matvar_t* d2_cell = createCellArrayFromVectors("d2", d2_data, n_frames);
1719 matvar_t* d3_cell = createCellArrayFromVectors("d3", d3_data, n_frames);
1720 matvar_t* Drec1_cell = createCellArrayFromVectors("Drec1", Drec1_data, n_frames);
1721 matvar_t* Drec2_cell = createCellArrayFromVectors("Drec2", Drec2_data, n_frames);
1722 matvar_t* Epc1vec_cell = createCellArrayFromVectors("Epc1vec", Epc1vec_data, n_frames);
1723 matvar_t* Epc2vec_cell = createCellArrayFromVectors("Epc2vec", Epc2vec_data, n_frames);
1724 matvar_t* Epc1vecCur_cell = createCellArrayFromVectors("Epc1vecCur", Epc1vecCur_data, n_frames);
1725 matvar_t* Epc2vecCur_cell = createCellArrayFromVectors("Epc2vecCur", Epc2vecCur_data, n_frames);
1726 matvar_t* epc1vec_cell = createCellArrayFromVectors("epc1vec", epc1vec_data, n_frames);
1727 matvar_t* epc2vec_cell = createCellArrayFromVectors("epc2vec", epc2vec_data, n_frames);
1728 matvar_t* EShearMaxVec1_cell = createCellArrayFromVectors("EShearMaxVec1", EShearMaxVec1_data, n_frames);
1729 matvar_t* EShearMaxVec2_cell = createCellArrayFromVectors("EShearMaxVec2", EShearMaxVec2_data, n_frames);
1730 matvar_t* EShearMaxVecCur1_cell = createCellArrayFromVectors("EShearMaxVecCur1", EShearMaxVecCur1_data, n_frames);
1731 matvar_t* EShearMaxVecCur2_cell = createCellArrayFromVectors("EShearMaxVecCur2", EShearMaxVecCur2_data, n_frames);
1732 matvar_t* eShearMaxVec1_cell = createCellArrayFromVectors("eShearMaxVec1", eShearMaxVec1_data, n_frames);
1733 matvar_t* eShearMaxVec2_cell = createCellArrayFromVectors("eShearMaxVec2", eShearMaxVec2_data, n_frames);
1734
1735 matvar_t* Fmat_cell = createCellArrayFromMatrices("Fmat", Fmat_data, n_frames);
1736 matvar_t* Cmat_cell = createCellArrayFromMatrices("Cmat", Cmat_data, n_frames);
1737 matvar_t* Emat_cell = createCellArrayFromMatrices("Emat", Emat_data, n_frames);
1738 matvar_t* emat_cell = createCellArrayFromMatrices("emat", emat_data, n_frames);
1739
1740 // Create parent struct
1741 std::vector<std::string> deform_fields = {
1742 "Area", "Lamda1", "Lamda2", "J", "Emgn", "emgn",
1743 "Epc1", "Epc2", "epc1", "epc2", "EShearMax", "eShearMax",
1744 "Eeq", "eeq", "Dnorm", "D1", "D2", "D3", "d1", "d2", "d3",
1745 "Drec1", "Drec2", "Epc1vec", "Epc2vec", "Epc1vecCur", "Epc2vecCur",
1746 "epc1vec", "epc2vec", "EShearMaxVec1", "EShearMaxVec2",
1747 "EShearMaxVecCur1", "EShearMaxVecCur2", "eShearMaxVec1", "eShearMaxVec2",
1748 "Fmat", "Cmat", "Emat", "emat"
1749 };
1750 matvar_t* deform_struct = createStructVariable(group_name, deform_fields);
1751
1752 Mat_VarSetStructFieldByName(deform_struct, "Area", 0, Area_cell);
1753 Mat_VarSetStructFieldByName(deform_struct, "Lamda1", 0, Lamda1_cell);
1754 Mat_VarSetStructFieldByName(deform_struct, "Lamda2", 0, Lamda2_cell);
1755 Mat_VarSetStructFieldByName(deform_struct, "J", 0, J_cell);
1756 Mat_VarSetStructFieldByName(deform_struct, "Emgn", 0, Emgn_cell);
1757 Mat_VarSetStructFieldByName(deform_struct, "emgn", 0, emgn_cell);
1758 Mat_VarSetStructFieldByName(deform_struct, "Epc1", 0, Epc1_cell);
1759 Mat_VarSetStructFieldByName(deform_struct, "Epc2", 0, Epc2_cell);
1760 Mat_VarSetStructFieldByName(deform_struct, "epc1", 0, epc1_cell);
1761 Mat_VarSetStructFieldByName(deform_struct, "epc2", 0, epc2_cell);
1762 Mat_VarSetStructFieldByName(deform_struct, "EShearMax", 0, EShearMax_cell);
1763 Mat_VarSetStructFieldByName(deform_struct, "eShearMax", 0, eShearMax_cell);
1764 Mat_VarSetStructFieldByName(deform_struct, "Eeq", 0, Eeq_cell);
1765 Mat_VarSetStructFieldByName(deform_struct, "eeq", 0, eeq_cell);
1766 Mat_VarSetStructFieldByName(deform_struct, "Dnorm", 0, Dnorm_cell);
1767 Mat_VarSetStructFieldByName(deform_struct, "D1", 0, D1_cell);
1768 Mat_VarSetStructFieldByName(deform_struct, "D2", 0, D2_cell);
1769 Mat_VarSetStructFieldByName(deform_struct, "D3", 0, D3_cell);
1770 Mat_VarSetStructFieldByName(deform_struct, "d1", 0, d1_cell);
1771 Mat_VarSetStructFieldByName(deform_struct, "d2", 0, d2_cell);
1772 Mat_VarSetStructFieldByName(deform_struct, "d3", 0, d3_cell);
1773 Mat_VarSetStructFieldByName(deform_struct, "Drec1", 0, Drec1_cell);
1774 Mat_VarSetStructFieldByName(deform_struct, "Drec2", 0, Drec2_cell);
1775 Mat_VarSetStructFieldByName(deform_struct, "Epc1vec", 0, Epc1vec_cell);
1776 Mat_VarSetStructFieldByName(deform_struct, "Epc2vec", 0, Epc2vec_cell);
1777 Mat_VarSetStructFieldByName(deform_struct, "Epc1vecCur", 0, Epc1vecCur_cell);
1778 Mat_VarSetStructFieldByName(deform_struct, "Epc2vecCur", 0, Epc2vecCur_cell);
1779 Mat_VarSetStructFieldByName(deform_struct, "epc1vec", 0, epc1vec_cell);
1780 Mat_VarSetStructFieldByName(deform_struct, "epc2vec", 0, epc2vec_cell);
1781 Mat_VarSetStructFieldByName(deform_struct, "EShearMaxVec1", 0, EShearMaxVec1_cell);
1782 Mat_VarSetStructFieldByName(deform_struct, "EShearMaxVec2", 0, EShearMaxVec2_cell);
1783 Mat_VarSetStructFieldByName(deform_struct, "EShearMaxVecCur1", 0, EShearMaxVecCur1_cell);
1784 Mat_VarSetStructFieldByName(deform_struct, "EShearMaxVecCur2", 0, EShearMaxVecCur2_cell);
1785 Mat_VarSetStructFieldByName(deform_struct, "eShearMaxVec1", 0, eShearMaxVec1_cell);
1786 Mat_VarSetStructFieldByName(deform_struct, "eShearMaxVec2", 0, eShearMaxVec2_cell);
1787 Mat_VarSetStructFieldByName(deform_struct, "Fmat", 0, Fmat_cell);
1788 Mat_VarSetStructFieldByName(deform_struct, "Cmat", 0, Cmat_cell);
1789 Mat_VarSetStructFieldByName(deform_struct, "Emat", 0, Emat_cell);
1790 Mat_VarSetStructFieldByName(deform_struct, "emat", 0, emat_cell);
1791
1792 LOG_DEBUG << "Built deformation struct '" << group_name << "' with "
1793 << n_frames << " frames and " << deform_data.n_faces << " faces";
1794 return deform_struct;
1795}
1796
1798 const std::string& group_name,
1799 const FrameDeformationResult& deform_data) {
1800 if (!matfp) {
1801 LOG_ERROR << "Invalid MAT file pointer";
1802 return false;
1803 }
1804
1805 matvar_t* deform_struct = buildDeformationStruct(group_name, deform_data);
1806 if (!deform_struct) {
1807 LOG_ERROR << "Failed to build deformation struct";
1808 return false;
1809 }
1810
1811 Mat_VarWrite(matfp, deform_struct, MAT_COMPRESSION_NONE);
1812 Mat_VarFree(deform_struct);
1813
1814 LOG_INFO << "Successfully wrote all deformation fields to '" << group_name << "' group";
1815 return true;
1816}
1817
1819 const std::vector<std::vector<Eigen::Vector3d>>& disp_vec,
1820 const std::vector<std::vector<double>>& disp_mgn,
1821 size_t n_frames) {
1822 if (!matfp) {
1823 LOG_ERROR << "Invalid MAT file pointer";
1824 return false;
1825 }
1826
1827 LOG_INFO << "Writing displacement group with " << n_frames << " frames";
1828
1829 // Create DispVec cell array (Nx3 per frame)
1830 matvar_t* disp_vec_cell = createCellArrayFromVectors("DispVec", disp_vec, n_frames);
1831 if (!disp_vec_cell) {
1832 LOG_ERROR << "Failed to create DispVec cell array";
1833 return false;
1834 }
1835
1836 // Create DispMgn cell array (Nx1 per frame)
1837 matvar_t* disp_mgn_cell = createCellArrayFromScalars("DispMgn", disp_mgn, n_frames);
1838 if (!disp_mgn_cell) {
1839 LOG_ERROR << "Failed to create DispMgn cell array";
1840 Mat_VarFree(disp_vec_cell);
1841 return false;
1842 }
1843
1844 // Write both to file
1845 Mat_VarWrite(matfp, disp_vec_cell, MAT_COMPRESSION_NONE);
1846 Mat_VarWrite(matfp, disp_mgn_cell, MAT_COMPRESSION_NONE);
1847
1848 // Free cell arrays
1849 Mat_VarFree(disp_vec_cell);
1850 Mat_VarFree(disp_mgn_cell);
1851
1852 LOG_INFO << "Successfully wrote displacement fields (DispVec, DispMgn)";
1853 return true;
1854}
1855
1857 const std::vector<std::vector<Eigen::Vector3d>>& face_centroids,
1858 const std::vector<std::vector<double>>& face_corr_comb,
1859 const std::vector<std::vector<double>>& face_iso_ind,
1860 size_t n_frames) {
1861 if (!matfp) {
1862 LOG_ERROR << "Invalid MAT file pointer";
1863 return false;
1864 }
1865
1866 LOG_INFO << "Writing face-based arrays with " << n_frames << " frames";
1867
1868 // Create FaceCentroids cell array (Mx3 per frame)
1869 matvar_t* face_centroids_cell = createCellArrayFromVectors("FaceCentroids", face_centroids, n_frames);
1870 if (!face_centroids_cell) {
1871 LOG_ERROR << "Failed to create FaceCentroids cell array";
1872 return false;
1873 }
1874
1875 // Create FaceCorrComb cell array (Mx1 per frame)
1876 matvar_t* face_corr_comb_cell = createCellArrayFromScalars("FaceCorrComb", face_corr_comb, n_frames);
1877 if (!face_corr_comb_cell) {
1878 LOG_ERROR << "Failed to create FaceCorrComb cell array";
1879 Mat_VarFree(face_centroids_cell);
1880 return false;
1881 }
1882
1883 // Create FaceIsoInd cell array (Mx1 per frame)
1884 matvar_t* face_iso_ind_cell = createCellArrayFromScalars("FaceIsoInd", face_iso_ind, n_frames);
1885 if (!face_iso_ind_cell) {
1886 LOG_ERROR << "Failed to create FaceIsoInd cell array";
1887 Mat_VarFree(face_centroids_cell);
1888 Mat_VarFree(face_corr_comb_cell);
1889 return false;
1890 }
1891
1892 // Write all to file
1893 Mat_VarWrite(matfp, face_centroids_cell, MAT_COMPRESSION_NONE);
1894 Mat_VarWrite(matfp, face_corr_comb_cell, MAT_COMPRESSION_NONE);
1895 Mat_VarWrite(matfp, face_iso_ind_cell, MAT_COMPRESSION_NONE);
1896
1897 // Free cell arrays
1898 Mat_VarFree(face_centroids_cell);
1899 Mat_VarFree(face_corr_comb_cell);
1900 Mat_VarFree(face_iso_ind_cell);
1901
1902 LOG_INFO << "Successfully wrote face arrays (FaceCentroids, FaceCorrComb, FaceIsoInd)";
1903 return true;
1904}
1905
1907 const CalibrationData& calibration) {
1908 if (!matfp) {
1909 LOG_ERROR << "Invalid MAT file pointer";
1910 return false;
1911 }
1912
1913 LOG_INFO << "Writing calibration group...";
1914
1915 // Determine dimensions (should be 2x2 for stereo pairs)
1916 size_t rows = calibration.DLT_paths.size();
1917 size_t cols = (rows > 0) ? calibration.DLT_paths[0].size() : 0;
1918
1919 if (rows == 0 || cols == 0) {
1920 LOG_WARN << "Empty calibration data, skipping";
1921 return true;
1922 }
1923
1924 // Create DLTpath cell array (2x2 strings)
1925 matvar_t* dlt_path_cell = createCellArray2DFromStrings("DLTpath", calibration.DLT_paths, rows, cols);
1926 if (!dlt_path_cell) {
1927 LOG_ERROR << "Failed to create DLTpath cell array";
1928 return false;
1929 }
1930
1931 // Create DLTparameters cell array (2x2 double vectors)
1932 matvar_t* dlt_params_cell = createCellArray2DFromVectors("DLTparameters", calibration.DLT_params, rows, cols);
1933 if (!dlt_params_cell) {
1934 LOG_ERROR << "Failed to create DLTparameters cell array";
1935 Mat_VarFree(dlt_path_cell);
1936 return false;
1937 }
1938
1939 // Create calibration struct/group to wrap both fields
1940 std::vector<std::string> calib_fields = {"DLTpath", "DLTparameters"};
1941 matvar_t* calib_struct = createStructVariable("calibration", calib_fields);
1942 if (!calib_struct) {
1943 LOG_ERROR << "Failed to create calibration struct";
1944 Mat_VarFree(dlt_path_cell);
1945 Mat_VarFree(dlt_params_cell);
1946 return false;
1947 }
1948
1949 // Add fields to calibration struct
1950 Mat_VarSetStructFieldByName(calib_struct, "DLTpath", 0, dlt_path_cell);
1951 Mat_VarSetStructFieldByName(calib_struct, "DLTparameters", 0, dlt_params_cell);
1952
1953 // Write calibration struct to file (writes as HDF5 group)
1954 Mat_VarWrite(matfp, calib_struct, MAT_COMPRESSION_NONE);
1955 Mat_VarFree(calib_struct);
1956
1957 LOG_INFO << "Successfully wrote calibration group (DLTpath, DLTparameters)";
1958 return true;
1959}
1960
1962 const DistortionData& distortion) {
1963 if (!matfp) {
1964 LOG_ERROR << "Invalid MAT file pointer";
1965 return false;
1966 }
1967
1968 LOG_INFO << "Writing distortion group...";
1969
1970 // Determine dimensions (should be 2x2 for stereo pairs)
1971 size_t rows = distortion.distortion_models.size();
1972 size_t cols = (rows > 0) ? distortion.distortion_models[0].size() : 0;
1973
1974 if (rows == 0 || cols == 0) {
1975 LOG_WARN << "Empty distortion data, skipping";
1976 return true;
1977 }
1978
1979 // Create distortionModel cell array (2x2 strings)
1980 matvar_t* dist_model_cell = createCellArray2DFromStrings("distortionModel", distortion.distortion_models, rows, cols);
1981 if (!dist_model_cell) {
1982 LOG_ERROR << "Failed to create distortionModel cell array";
1983 return false;
1984 }
1985
1986 // Create distortionPath cell array (2x2 strings)
1987 matvar_t* dist_path_cell = createCellArray2DFromStrings("distortionPath", distortion.distortion_paths, rows, cols);
1988 if (!dist_path_cell) {
1989 LOG_ERROR << "Failed to create distortionPath cell array";
1990 Mat_VarFree(dist_model_cell);
1991 return false;
1992 }
1993
1994 // Create distortion struct/group to wrap both fields
1995 std::vector<std::string> dist_fields = {"distortionModel", "distortionPath"};
1996 matvar_t* dist_struct = createStructVariable("distortion", dist_fields);
1997 if (!dist_struct) {
1998 LOG_ERROR << "Failed to create distortion struct";
1999 Mat_VarFree(dist_model_cell);
2000 Mat_VarFree(dist_path_cell);
2001 return false;
2002 }
2003
2004 // Add fields to distortion struct
2005 Mat_VarSetStructFieldByName(dist_struct, "distortionModel", 0, dist_model_cell);
2006 Mat_VarSetStructFieldByName(dist_struct, "distortionPath", 0, dist_path_cell);
2007
2008 // Write distortion struct to file (writes as HDF5 group)
2009 Mat_VarWrite(matfp, dist_struct, MAT_COMPRESSION_NONE);
2010 Mat_VarFree(dist_struct);
2011
2012 LOG_INFO << "Successfully wrote distortion group (distortionModel, distortionPath)";
2013 return true;
2014}
2015
2016// Build the AllPairsResults cell array purely in memory (no file I/O).
2017// Returns nullptr when there is nothing to attach. Previously this wrote the
2018// cell as a top-level variable and the caller read it back from the write
2019// handle to embed it in the combined struct — that read-back produced matvar
2020// objects carrying live HDF5-internal state whose re-embedding crashed
2021// Mat_VarSetStructFieldByName (the long-standing Step-E stitching segfault),
2022// and it also duplicated ~half the file size with stray top-level variables.
2023matvar_t* MatWriter::buildAllPairsResultsVar(const std::vector<DIC3DpairResults>& all_pairs) {
2024 LOG_INFO << "Building AllPairsResults with " << all_pairs.size() << " pairs...";
2025
2026 if (all_pairs.empty()) {
2027 LOG_WARN << "Empty AllPairsResults, skipping";
2028 return nullptr;
2029 }
2030
2031 // Create cell array (1 x n_pairs)
2032 size_t n_pairs = all_pairs.size();
2033 std::vector<size_t> cell_dims = {1, n_pairs};
2034 matvar_t* cell_array = Mat_VarCreate("AllPairsResults", MAT_C_CELL, MAT_T_CELL,
2035 2, cell_dims.data(), nullptr, 0);
2036
2037 if (!cell_array) {
2038 LOG_ERROR << "Failed to create AllPairsResults cell array";
2039 return nullptr;
2040 }
2041
2042 // Write each pair as a struct
2043 for (size_t i = 0; i < n_pairs; ++i) {
2044 const auto& pair = all_pairs[i];
2045
2046 // Create struct for this pair
2047 std::vector<std::string> pair_fields = {
2048 "cameraPairInd", "calibration", "distortionModel", "distortionPath",
2049 "Faces", "FaceColors", "Points3D", "Disp", "FaceCentroids",
2050 "corrComb", "FaceCorrComb"
2051 };
2052 matvar_t* pair_struct = createStructVariable("pair", pair_fields);
2053
2054 // Write cameraPairInd (MATLAB: 1×2 double)
2055 if (!pair.cameraPairInd.empty()) {
2056 std::vector<size_t> dims = {1, pair.cameraPairInd.size()};
2057 std::vector<double> cam_pair_dbl(pair.cameraPairInd.size());
2058 for (size_t j = 0; j < pair.cameraPairInd.size(); ++j)
2059 cam_pair_dbl[j] = static_cast<double>(pair.cameraPairInd[j]);
2060 matvar_t* cam_var = Mat_VarCreate("cameraPairInd", MAT_C_DOUBLE, MAT_T_DOUBLE,
2061 2, dims.data(), cam_pair_dbl.data(), 0);
2062 Mat_VarSetStructFieldByName(pair_struct, "cameraPairInd", 0, cam_var);
2063 }
2064
2065 // Write calibration struct
2066 std::vector<std::string> calib_fields = {"DLTpath", "DLTparameters"};
2067 matvar_t* calib_struct = createStructVariable("calibration", calib_fields);
2068
2069 // DLTpath (cell 2x1)
2070 if (!pair.DLTpath.empty()) {
2071 std::vector<size_t> path_dims = {2, 1};
2072 matvar_t* path_cell = Mat_VarCreate("DLTpath", MAT_C_CELL, MAT_T_CELL,
2073 2, path_dims.data(), nullptr, 0);
2074 for (size_t j = 0; j < pair.DLTpath.size() && j < 2; ++j) {
2075 std::vector<size_t> str_dims = {1, pair.DLTpath[j].length()};
2076 matvar_t* str_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8,
2077 2, str_dims.data(), (void*)pair.DLTpath[j].c_str(), 0);
2078 Mat_VarSetCell(path_cell, j, str_var);
2079 }
2080 Mat_VarSetStructFieldByName(calib_struct, "DLTpath", 0, path_cell);
2081 }
2082
2083 // DLTparameters (cell 2x1, each cell is 1×11 double)
2084 if (!pair.DLTparameters.empty()) {
2085 std::vector<size_t> param_dims = {2, 1};
2086 matvar_t* param_cell = Mat_VarCreate("DLTparameters", MAT_C_CELL, MAT_T_CELL,
2087 2, param_dims.data(), nullptr, 0);
2088 for (size_t j = 0; j < pair.DLTparameters.size() && j < 2; ++j) {
2089 if (!pair.DLTparameters[j].empty()) {
2090 std::vector<size_t> vec_dims = {1, pair.DLTparameters[j].size()};
2091 matvar_t* vec_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2092 2, vec_dims.data(), (void*)pair.DLTparameters[j].data(), 0);
2093 Mat_VarSetCell(param_cell, j, vec_var);
2094 }
2095 }
2096 Mat_VarSetStructFieldByName(calib_struct, "DLTparameters", 0, param_cell);
2097 }
2098
2099 Mat_VarSetStructFieldByName(pair_struct, "calibration", 0, calib_struct);
2100
2101 // Write distortionModel (cell 2x1)
2102 if (!pair.distortionModel.empty()) {
2103 std::vector<size_t> model_dims = {2, 1};
2104 matvar_t* model_cell = Mat_VarCreate("distortionModel", MAT_C_CELL, MAT_T_CELL,
2105 2, model_dims.data(), nullptr, 0);
2106 for (size_t j = 0; j < pair.distortionModel.size() && j < 2; ++j) {
2107 std::vector<size_t> str_dims = {1, pair.distortionModel[j].length()};
2108 matvar_t* str_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8,
2109 2, str_dims.data(), (void*)pair.distortionModel[j].c_str(), 0);
2110 Mat_VarSetCell(model_cell, j, str_var);
2111 }
2112 Mat_VarSetStructFieldByName(pair_struct, "distortionModel", 0, model_cell);
2113 }
2114
2115 // Write distortionPath (cell 2x1)
2116 if (!pair.distortionPath.empty()) {
2117 std::vector<size_t> path_dims = {2, 1};
2118 matvar_t* path_cell = Mat_VarCreate("distortionPath", MAT_C_CELL, MAT_T_CELL,
2119 2, path_dims.data(), nullptr, 0);
2120 for (size_t j = 0; j < pair.distortionPath.size() && j < 2; ++j) {
2121 std::vector<size_t> str_dims = {1, pair.distortionPath[j].length()};
2122 matvar_t* str_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8,
2123 2, str_dims.data(), (void*)pair.distortionPath[j].c_str(), 0);
2124 Mat_VarSetCell(path_cell, j, str_var);
2125 }
2126 Mat_VarSetStructFieldByName(pair_struct, "distortionPath", 0, path_cell);
2127 }
2128
2129 // Write Faces (nFaces x 3, convert 0-indexed C++ to 1-indexed MATLAB)
2130 if (!pair.Faces.empty()) {
2131 size_t nFaces = pair.Faces.size() / 3;
2132 std::vector<size_t> face_dims = {nFaces, 3};
2133 std::vector<double> faces_double(nFaces * 3);
2134 for (size_t f = 0; f < nFaces; ++f) {
2135 faces_double[f] = static_cast<double>(pair.Faces[f * 3 + 0] + 1);
2136 faces_double[f + nFaces] = static_cast<double>(pair.Faces[f * 3 + 1] + 1);
2137 faces_double[f + 2 * nFaces] = static_cast<double>(pair.Faces[f * 3 + 2] + 1);
2138 }
2139 matvar_t* faces_var = Mat_VarCreate("Faces", MAT_C_DOUBLE, MAT_T_DOUBLE,
2140 2, face_dims.data(), faces_double.data(), 0);
2141 Mat_VarSetStructFieldByName(pair_struct, "Faces", 0, faces_var);
2142 }
2143
2144 // Write FaceColors (nFaces x 1)
2145 if (!pair.FaceColors.empty()) {
2146 std::vector<size_t> fc_dims = {pair.FaceColors.size(), 1};
2147 matvar_t* fc_var = Mat_VarCreate("FaceColors", MAT_C_DOUBLE, MAT_T_DOUBLE,
2148 2, fc_dims.data(), (void*)pair.FaceColors.data(), 0);
2149 Mat_VarSetStructFieldByName(pair_struct, "FaceColors", 0, fc_var);
2150 }
2151
2152 // Write Points3D (MATLAB: 1×nImages cell, each cell is nPts×3)
2153 if (!pair.Points3D.empty()) {
2154 std::vector<size_t> pts_dims = {1, pair.Points3D.size()};
2155 matvar_t* pts_cell = Mat_VarCreate("Points3D", MAT_C_CELL, MAT_T_CELL,
2156 2, pts_dims.data(), nullptr, 0);
2157 for (size_t frame = 0; frame < pair.Points3D.size(); ++frame) {
2158 const auto& pts = pair.Points3D[frame];
2159 size_t nPts = pts.x.size();
2160 std::vector<size_t> pt_dims = {nPts, 3};
2161 std::vector<double> pts_data(nPts * 3);
2162 for (size_t p = 0; p < nPts; ++p) {
2163 pts_data[p] = pts.x[p];
2164 pts_data[p + nPts] = pts.y[p];
2165 pts_data[p + 2 * nPts] = pts.z[p];
2166 }
2167 matvar_t* pt_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2168 2, pt_dims.data(), pts_data.data(), 0);
2169 Mat_VarSetCell(pts_cell, frame, pt_var);
2170 }
2171 Mat_VarSetStructFieldByName(pair_struct, "Points3D", 0, pts_cell);
2172 }
2173
2174 // Write Disp struct
2175 std::vector<std::string> disp_fields = {"DispVec", "DispMgn"};
2176 matvar_t* disp_struct = createStructVariable("Disp", disp_fields);
2177
2178 // DispVec (MATLAB: 1×nImages cell, each cell is nPts×3)
2179 if (!pair.Disp.DispVec.empty()) {
2180 std::vector<size_t> dv_dims = {1, pair.Disp.DispVec.size()};
2181 matvar_t* dv_cell = Mat_VarCreate("DispVec", MAT_C_CELL, MAT_T_CELL,
2182 2, dv_dims.data(), nullptr, 0);
2183 for (size_t frame = 0; frame < pair.Disp.DispVec.size(); ++frame) {
2184 const auto& vec = pair.Disp.DispVec[frame];
2185 if (!vec.empty()) {
2186 size_t nPts = vec.size() / 3;
2187 std::vector<size_t> vec_dims = {nPts, 3};
2188 std::vector<double> vec_data(nPts * 3);
2189 for (size_t p = 0; p < nPts; ++p) {
2190 vec_data[p] = vec[p * 3 + 0];
2191 vec_data[p + nPts] = vec[p * 3 + 1];
2192 vec_data[p + 2 * nPts] = vec[p * 3 + 2];
2193 }
2194 matvar_t* vec_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2195 2, vec_dims.data(), vec_data.data(), 0);
2196 Mat_VarSetCell(dv_cell, frame, vec_var);
2197 }
2198 }
2199 Mat_VarSetStructFieldByName(disp_struct, "DispVec", 0, dv_cell);
2200 }
2201
2202 // DispMgn (MATLAB: 1×nImages cell, each cell is nPts×1)
2203 if (!pair.Disp.DispMgn.empty()) {
2204 std::vector<size_t> dm_dims = {1, pair.Disp.DispMgn.size()};
2205 matvar_t* dm_cell = Mat_VarCreate("DispMgn", MAT_C_CELL, MAT_T_CELL,
2206 2, dm_dims.data(), nullptr, 0);
2207 for (size_t frame = 0; frame < pair.Disp.DispMgn.size(); ++frame) {
2208 const auto& mgn = pair.Disp.DispMgn[frame];
2209 if (!mgn.empty()) {
2210 std::vector<size_t> mgn_dims = {mgn.size(), 1};
2211 matvar_t* mgn_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2212 2, mgn_dims.data(), (void*)mgn.data(), 0);
2213 Mat_VarSetCell(dm_cell, frame, mgn_var);
2214 }
2215 }
2216 Mat_VarSetStructFieldByName(disp_struct, "DispMgn", 0, dm_cell);
2217 }
2218
2219 Mat_VarSetStructFieldByName(pair_struct, "Disp", 0, disp_struct);
2220
2221 // Write FaceCentroids (MATLAB: 1×nImages cell, each cell is nFaces×3)
2222 if (!pair.FaceCentroids.empty()) {
2223 std::vector<size_t> fc_dims = {1, pair.FaceCentroids.size()};
2224 matvar_t* fc_cell = Mat_VarCreate("FaceCentroids", MAT_C_CELL, MAT_T_CELL,
2225 2, fc_dims.data(), nullptr, 0);
2226 for (size_t frame = 0; frame < pair.FaceCentroids.size(); ++frame) {
2227 const auto& centroids = pair.FaceCentroids[frame];
2228 if (!centroids.empty()) {
2229 size_t nFaces = centroids.size() / 3;
2230 std::vector<size_t> cent_dims = {nFaces, 3};
2231 std::vector<double> centroid_data(nFaces * 3);
2232 for (size_t f = 0; f < nFaces; ++f) {
2233 centroid_data[f] = centroids[f * 3 + 0];
2234 centroid_data[f + nFaces] = centroids[f * 3 + 1];
2235 centroid_data[f + 2 * nFaces] = centroids[f * 3 + 2];
2236 }
2237 matvar_t* cent_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2238 2, cent_dims.data(), centroid_data.data(), 0);
2239 Mat_VarSetCell(fc_cell, frame, cent_var);
2240 }
2241 }
2242 Mat_VarSetStructFieldByName(pair_struct, "FaceCentroids", 0, fc_cell);
2243 }
2244
2245 // Write corrComb (MATLAB: 1×nImages cell, each cell is nPoints×1)
2246 if (!pair.corrComb.empty()) {
2247 std::vector<size_t> cc_dims = {1, pair.corrComb.size()};
2248 matvar_t* cc_cell = Mat_VarCreate("corrComb", MAT_C_CELL, MAT_T_CELL,
2249 2, cc_dims.data(), nullptr, 0);
2250 for (size_t frame = 0; frame < pair.corrComb.size(); ++frame) {
2251 const auto& corr = pair.corrComb[frame];
2252 if (!corr.empty()) {
2253 std::vector<size_t> corr_dims = {corr.size(), 1};
2254 matvar_t* corr_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2255 2, corr_dims.data(), (void*)corr.data(), 0);
2256 Mat_VarSetCell(cc_cell, frame, corr_var);
2257 }
2258 }
2259 Mat_VarSetStructFieldByName(pair_struct, "corrComb", 0, cc_cell);
2260 }
2261
2262 // Write FaceCorrComb (MATLAB: 1×nImages cell, each cell is nFaces×1)
2263 if (!pair.FaceCorrComb.empty()) {
2264 std::vector<size_t> fcc_dims = {1, pair.FaceCorrComb.size()};
2265 matvar_t* fcc_cell = Mat_VarCreate("FaceCorrComb", MAT_C_CELL, MAT_T_CELL,
2266 2, fcc_dims.data(), nullptr, 0);
2267 for (size_t frame = 0; frame < pair.FaceCorrComb.size(); ++frame) {
2268 const auto& face_corr = pair.FaceCorrComb[frame];
2269 if (!face_corr.empty()) {
2270 std::vector<size_t> fc_dims = {face_corr.size(), 1};
2271 matvar_t* fc_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2272 2, fc_dims.data(), (void*)face_corr.data(), 0);
2273 Mat_VarSetCell(fcc_cell, frame, fc_var);
2274 }
2275 }
2276 Mat_VarSetStructFieldByName(pair_struct, "FaceCorrComb", 0, fcc_cell);
2277 }
2278
2279 // Set this pair struct into the cell array
2280 Mat_VarSetCell(cell_array, i, pair_struct);
2281 }
2282
2283 LOG_INFO << "AllPairsResults built (" << n_pairs << " pairs)";
2284 return cell_array;
2285}
2286
2287// Build the DIC2Dinfo cell array purely in memory (no file I/O) — see
2288// buildAllPairsResultsVar for why the old write-then-read-back scheme had to go.
2289matvar_t* MatWriter::buildDIC2DinfoVar(const std::vector<DIC2DPairResults>& dic2d_info) {
2290 LOG_INFO << "Building DIC2Dinfo with " << dic2d_info.size() << " entries...";
2291
2292 if (dic2d_info.empty()) {
2293 LOG_WARN << "Empty DIC2Dinfo, skipping";
2294 return nullptr;
2295 }
2296
2297 // Create cell array (n_entries x 1)
2298 size_t n_entries = dic2d_info.size();
2299 std::vector<size_t> array_dims = {n_entries, 1};
2300 matvar_t* obj_array = Mat_VarCreate("DIC2Dinfo", MAT_C_CELL, MAT_T_CELL,
2301 2, array_dims.data(), nullptr, 0);
2302
2303 if (!obj_array) {
2304 LOG_ERROR << "Failed to create DIC2Dinfo cell array";
2305 return nullptr;
2306 }
2307
2308 // Write each DIC2DPairResults as a struct
2309 for (size_t i = 0; i < n_entries; ++i) {
2310 const auto& dic2d = dic2d_info[i];
2311
2312 // Create struct for this entry
2313 std::vector<std::string> dic2d_fields = {
2314 "nCamRef", "nCamDef", "nImages", "pairOrder", "pairForced", "ROImask", "ncorrInfo",
2315 "Points", "CorCoeffVec", "Faces", "FaceColors"
2316 };
2317 matvar_t* dic2d_struct = createStructVariable("DIC2D", dic2d_fields);
2318
2319 // Write scalar fields (MATLAB stores as 1×1 double)
2320 double ncamref_dbl = static_cast<double>(dic2d.nCamRef);
2321 matvar_t* ncamref_var = Mat_VarCreate("nCamRef", MAT_C_DOUBLE, MAT_T_DOUBLE,
2322 2, scalar_dims, &ncamref_dbl, 0);
2323 Mat_VarSetStructFieldByName(dic2d_struct, "nCamRef", 0, ncamref_var);
2324
2325 double ncamdef_dbl = static_cast<double>(dic2d.nCamDef);
2326 matvar_t* ncamdef_var = Mat_VarCreate("nCamDef", MAT_C_DOUBLE, MAT_T_DOUBLE,
2327 2, scalar_dims, &ncamdef_dbl, 0);
2328 Mat_VarSetStructFieldByName(dic2d_struct, "nCamDef", 0, ncamdef_var);
2329
2330 double nimages_dbl = static_cast<double>(dic2d.nImages);
2331 matvar_t* nimages_var = Mat_VarCreate("nImages", MAT_C_DOUBLE, MAT_T_DOUBLE,
2332 2, scalar_dims, &nimages_dbl, 0);
2333 Mat_VarSetStructFieldByName(dic2d_struct, "nImages", 0, nimages_var);
2334
2335 if (!dic2d.pairOrder.empty()) {
2336 std::vector<double> pair_order(dic2d.pairOrder.begin(), dic2d.pairOrder.end());
2337 std::vector<size_t> pair_dims = {1, pair_order.size()};
2338 matvar_t* pair_order_var = Mat_VarCreate("pairOrder", MAT_C_DOUBLE, MAT_T_DOUBLE,
2339 2, pair_dims.data(), pair_order.data(), 0);
2340 Mat_VarSetStructFieldByName(dic2d_struct, "pairOrder", 0, pair_order_var);
2341 }
2342
2343 double pair_forced_dbl = dic2d.pairForced ? 1.0 : 0.0;
2344 matvar_t* pair_forced_var = Mat_VarCreate("pairForced", MAT_C_DOUBLE, MAT_T_DOUBLE,
2345 2, scalar_dims, &pair_forced_dbl, 0);
2346 Mat_VarSetStructFieldByName(dic2d_struct, "pairForced", 0, pair_forced_var);
2347
2348 // Write ROImask if available — built directly (column-major transpose);
2349 // the old write-to-file + Mat_VarRead round-trip crashed here.
2350 if (!dic2d.ROImask.empty() && dic2d.ROImask.type() == CV_8UC1) {
2351 std::vector<size_t> roi_dims = {static_cast<size_t>(dic2d.ROImask.rows),
2352 static_cast<size_t>(dic2d.ROImask.cols)};
2353 cv::Mat roi_t = dic2d.ROImask.t();
2354 matvar_t* roi_var = Mat_VarCreate("ROImask", MAT_C_UINT8, MAT_T_UINT8,
2355 2, roi_dims.data(), roi_t.data, 0);
2356 if (roi_var) Mat_VarSetStructFieldByName(dic2d_struct, "ROImask", 0, roi_var);
2357 }
2358
2359 // Write ncorrInfo struct (reuse existing logic from writeDIC2DPairResults)
2360 // For now, create minimal ncorrInfo with basic fields
2361 std::vector<std::string> ncorr_fields = {
2362 "cutoff_corrcoef", "cutoff_diffnorm", "cutoff_iteration",
2363 "imgcorr", "lenscoef", "pixtounits", "radius", "spacing",
2364 "stepanalysis", "subsettrunc", "total_threads", "type", "units"
2365 };
2366 matvar_t* ncorr_struct = createStructVariable("ncorrInfo", ncorr_fields);
2367
2368 // Add basic ncorrInfo fields from dic2d.ncorrInfo
2369 if (!dic2d.ncorrInfo.cutoff_corrcoef.empty()) {
2370 std::vector<size_t> dims = {1, dic2d.ncorrInfo.cutoff_corrcoef.size()};
2371 matvar_t* cutoff_var = Mat_VarCreate("cutoff_corrcoef", MAT_C_DOUBLE, MAT_T_DOUBLE,
2372 2, dims.data(), (void*)dic2d.ncorrInfo.cutoff_corrcoef.data(), 0);
2373 Mat_VarSetStructFieldByName(ncorr_struct, "cutoff_corrcoef", 0, cutoff_var);
2374 }
2375
2376 // Add stepanalysis
2377 std::vector<std::string> stepanalysis_fields = {"enabled", "type", "auto", "step"};
2378 matvar_t* stepanalysis_struct = createStructVariable("stepanalysis", stepanalysis_fields);
2379
2380 double enabled_dbl = dic2d.ncorrInfo.stepanalysis.enabled ? 1.0 : 0.0;
2381 matvar_t* enabled_var = Mat_VarCreate("enabled", MAT_C_DOUBLE, MAT_T_DOUBLE,
2382 2, scalar_dims, &enabled_dbl, 0);
2383 Mat_VarSetStructFieldByName(stepanalysis_struct, "enabled", 0, enabled_var);
2384
2385 if (!dic2d.ncorrInfo.stepanalysis.type.empty()) {
2386 std::vector<size_t> dims = {1, dic2d.ncorrInfo.stepanalysis.type.length()};
2387 matvar_t* type_var = Mat_VarCreate("type", MAT_C_CHAR, MAT_T_UINT8,
2388 2, dims.data(), (void*)dic2d.ncorrInfo.stepanalysis.type.c_str(), 0);
2389 Mat_VarSetStructFieldByName(stepanalysis_struct, "type", 0, type_var);
2390 }
2391
2392 double auto_dbl = dic2d.ncorrInfo.stepanalysis.auto_update ? 1.0 : 0.0;
2393 matvar_t* auto_var = Mat_VarCreate("auto", MAT_C_DOUBLE, MAT_T_DOUBLE,
2394 2, scalar_dims, &auto_dbl, 0);
2395 Mat_VarSetStructFieldByName(stepanalysis_struct, "auto", 0, auto_var);
2396
2397 double step_dbl = static_cast<double>(dic2d.ncorrInfo.stepanalysis.step);
2398 matvar_t* step_var = Mat_VarCreate("step", MAT_C_DOUBLE, MAT_T_DOUBLE,
2399 2, scalar_dims, &step_dbl, 0);
2400 Mat_VarSetStructFieldByName(stepanalysis_struct, "step", 0, step_var);
2401
2402 Mat_VarSetStructFieldByName(ncorr_struct, "stepanalysis", 0, stepanalysis_struct);
2403
2404 Mat_VarSetStructFieldByName(dic2d_struct, "ncorrInfo", 0, ncorr_struct);
2405
2406 // Write Points (MATLAB: 1×nFrames cell, each cell is nPts×2)
2407 if (!dic2d.Points.empty()) {
2408 std::vector<size_t> pts_dims = {1, dic2d.Points.size()};
2409 matvar_t* pts_cell = Mat_VarCreate("Points", MAT_C_CELL, MAT_T_CELL,
2410 2, pts_dims.data(), nullptr, 0);
2411 for (size_t frame = 0; frame < dic2d.Points.size(); ++frame) {
2412 const auto& pts = dic2d.Points[frame];
2413 size_t nPts = pts.x.size();
2414 if (nPts > 0) {
2415 std::vector<size_t> pt_dims = {nPts, 2};
2416 std::vector<double> pts_data(nPts * 2);
2417 for (size_t p = 0; p < nPts; ++p) {
2418 pts_data[p] = pts.x[p];
2419 pts_data[p + nPts] = pts.y[p];
2420 }
2421 matvar_t* pt_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2422 2, pt_dims.data(), pts_data.data(), 0);
2423 Mat_VarSetCell(pts_cell, frame, pt_var);
2424 }
2425 }
2426 Mat_VarSetStructFieldByName(dic2d_struct, "Points", 0, pts_cell);
2427 }
2428
2429 // Write CorCoeffVec (MATLAB: 1×nFrames cell, each cell is nPts×1)
2430 if (!dic2d.CorCoeffVec.empty()) {
2431 std::vector<size_t> cc_dims = {1, dic2d.CorCoeffVec.size()};
2432 matvar_t* cc_cell = Mat_VarCreate("CorCoeffVec", MAT_C_CELL, MAT_T_CELL,
2433 2, cc_dims.data(), nullptr, 0);
2434 for (size_t frame = 0; frame < dic2d.CorCoeffVec.size(); ++frame) {
2435 const auto& corr = dic2d.CorCoeffVec[frame];
2436 if (!corr.empty()) {
2437 std::vector<size_t> corr_dims = {corr.size(), 1};
2438 matvar_t* corr_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2439 2, corr_dims.data(), (void*)corr.data(), 0);
2440 Mat_VarSetCell(cc_cell, frame, corr_var);
2441 }
2442 }
2443 Mat_VarSetStructFieldByName(dic2d_struct, "CorCoeffVec", 0, cc_cell);
2444 }
2445
2446 // Write Faces (nFaces x 3, convert 0-indexed C++ to 1-indexed MATLAB)
2447 if (!dic2d.Faces.empty()) {
2448 size_t nFaces = dic2d.Faces.size() / 3;
2449 std::vector<size_t> face_dims = {nFaces, 3};
2450 std::vector<double> faces_double(nFaces * 3);
2451 for (size_t fi = 0; fi < nFaces; ++fi) {
2452 faces_double[fi] = static_cast<double>(dic2d.Faces[fi * 3 + 0] + 1);
2453 faces_double[fi + nFaces] = static_cast<double>(dic2d.Faces[fi * 3 + 1] + 1);
2454 faces_double[fi + 2 * nFaces] = static_cast<double>(dic2d.Faces[fi * 3 + 2] + 1);
2455 }
2456 matvar_t* faces_var = Mat_VarCreate("Faces", MAT_C_DOUBLE, MAT_T_DOUBLE,
2457 2, face_dims.data(), faces_double.data(), 0);
2458 Mat_VarSetStructFieldByName(dic2d_struct, "Faces", 0, faces_var);
2459 }
2460
2461 // Write FaceColors (nFaces x 1)
2462 if (!dic2d.FaceColors.empty()) {
2463 std::vector<size_t> fc_dims = {dic2d.FaceColors.size(), 1};
2464 matvar_t* fc_var = Mat_VarCreate("FaceColors", MAT_C_DOUBLE, MAT_T_DOUBLE,
2465 2, fc_dims.data(), (void*)dic2d.FaceColors.data(), 0);
2466 Mat_VarSetStructFieldByName(dic2d_struct, "FaceColors", 0, fc_var);
2467 }
2468
2469 // Set this struct into the cell array
2470 Mat_VarSetCell(obj_array, i, dic2d_struct);
2471 }
2472
2473 LOG_INFO << "DIC2Dinfo built (" << n_entries << " entries)";
2474 return obj_array;
2475}
2476
2477// Helper function implementations ============================================
2478
2479ncorr::ROI2D MatWriter::convertMatToROI2D(const cv::Mat& mask) {
2480 // Convert cv::Mat (CV_8U) to ncorr::Array2D<bool>
2481 ncorr::Array2D<bool> mask_array(mask.rows, mask.cols);
2482
2483 for (int i = 0; i < mask.rows; ++i) {
2484 for (int j = 0; j < mask.cols; ++j) {
2485 mask_array(i, j) = (mask.at<uint8_t>(i, j) > 0);
2486 }
2487 }
2488
2489 // Create ROI2D from mask array
2490 return ncorr::ROI2D(std::move(mask_array));
2491}
2492
2493cv::Mat MatWriter::convertROI2DToMat(const ncorr::ROI2D& roi) {
2494 // Extract mask from ROI2D
2495 const auto& mask_array = roi.get_mask();
2496
2497 // Create cv::Mat (CV_8U)
2498 cv::Mat mask(mask_array.height(), mask_array.width(), CV_8UC1);
2499
2500 for (ncorr::ROI2D::difference_type i = 0; i < mask_array.height(); ++i) {
2501 for (ncorr::ROI2D::difference_type j = 0; j < mask_array.width(); ++j) {
2502 mask.at<uint8_t>(i, j) = mask_array(i, j) ? 255 : 0;
2503 }
2504 }
2505
2506 return mask;
2507}
2508
2509// Cell Array Helper Functions ================================================
2510
2511matvar_t* MatWriter::createCellArrayFromScalars(
2512 const std::string& name,
2513 const std::vector<std::vector<double>>& data,
2514 size_t n_frames) {
2515
2516 // Create cell array (1 x n_frames)
2517 std::vector<size_t> cell_dims = {1, n_frames};
2518 matvar_t* cell_array = Mat_VarCreate(name.c_str(), MAT_C_CELL, MAT_T_CELL,
2519 2, cell_dims.data(), nullptr, 0);
2520
2521 if (!cell_array) {
2522 LOG_ERROR << "Failed to create cell array: " << name;
2523 return nullptr;
2524 }
2525
2526 // Fill each cell with scalar array
2527 for (size_t i = 0; i < n_frames && i < data.size(); ++i) {
2528 const auto& frame_data = data[i];
2529 size_t n_elements = frame_data.size();
2530
2531 if (n_elements == 0) {
2532 // Empty cell
2533 Mat_VarSetCell(cell_array, i, nullptr);
2534 continue;
2535 }
2536
2537 // Create double array for this frame (MATLAB column vector: N×1)
2538 std::vector<size_t> dims = {n_elements, 1};
2539 matvar_t* cell_data = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2540 2, dims.data(), (void*)frame_data.data(), 0);
2541
2542 Mat_VarSetCell(cell_array, i, cell_data);
2543 }
2544
2545 return cell_array;
2546}
2547
2548matvar_t* MatWriter::createCellArrayFromVectors(
2549 const std::string& name,
2550 const std::vector<std::vector<Eigen::Vector3d>>& data,
2551 size_t n_frames) {
2552
2553 // Create cell array (1 x n_frames)
2554 std::vector<size_t> cell_dims = {1, n_frames};
2555 matvar_t* cell_array = Mat_VarCreate(name.c_str(), MAT_C_CELL, MAT_T_CELL,
2556 2, cell_dims.data(), nullptr, 0);
2557
2558 if (!cell_array) {
2559 LOG_ERROR << "Failed to create cell array: " << name;
2560 return nullptr;
2561 }
2562
2563 // Fill each cell with Nx3 array
2564 for (size_t i = 0; i < n_frames && i < data.size(); ++i) {
2565 const auto& frame_data = data[i];
2566 size_t n_vectors = frame_data.size();
2567
2568 if (n_vectors == 0) {
2569 Mat_VarSetCell(cell_array, i, nullptr);
2570 continue;
2571 }
2572
2573 // Convert vector<Vector3d> to flat double array (Nx3 in MATLAB column-major)
2574 std::vector<double> flat_data(n_vectors * 3);
2575 for (size_t j = 0; j < n_vectors; ++j) {
2576 flat_data[j] = frame_data[j](0);
2577 flat_data[j + n_vectors] = frame_data[j](1);
2578 flat_data[j + 2 * n_vectors] = frame_data[j](2);
2579 }
2580
2581 // Create N×3 array
2582 std::vector<size_t> dims = {n_vectors, 3};
2583 matvar_t* cell_data = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2584 2, dims.data(), flat_data.data(), 0);
2585
2586 Mat_VarSetCell(cell_array, i, cell_data);
2587 }
2588
2589 return cell_array;
2590}
2591
2592matvar_t* MatWriter::createCellArrayFromMatrices(
2593 const std::string& name,
2594 const std::vector<std::vector<Eigen::Matrix3d>>& data,
2595 size_t n_frames) {
2596
2597 // Create cell array (1 x n_frames)
2598 std::vector<size_t> cell_dims = {1, n_frames};
2599 matvar_t* cell_array = Mat_VarCreate(name.c_str(), MAT_C_CELL, MAT_T_CELL,
2600 2, cell_dims.data(), nullptr, 0);
2601
2602 if (!cell_array) {
2603 LOG_ERROR << "Failed to create cell array: " << name;
2604 return nullptr;
2605 }
2606
2607 // Fill each cell with 3×3×N 3D array (legacy MATLAB convention)
2608 for (size_t i = 0; i < n_frames && i < data.size(); ++i) {
2609 const auto& frame_data = data[i];
2610 size_t n_matrices = frame_data.size();
2611
2612 if (n_matrices == 0) {
2613 Mat_VarSetCell(cell_array, i, nullptr);
2614 continue;
2615 }
2616
2617 // Convert vector<Matrix3d> to MATLAB [3,3,N] column-major 3D array.
2618 std::vector<double> flat_data(n_matrices * 9);
2619 for (size_t j = 0; j < n_matrices; ++j) {
2620 for (int col = 0; col < 3; ++col) {
2621 for (int row = 0; row < 3; ++row) {
2622 flat_data[row + 3 * col + 9 * j] = frame_data[j](row, col);
2623 }
2624 }
2625 }
2626
2627 // Create 3×3×N array
2628 std::vector<size_t> dims = {3, 3, n_matrices};
2629 matvar_t* cell_data = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2630 3, dims.data(), flat_data.data(), 0);
2631
2632 Mat_VarSetCell(cell_array, i, cell_data);
2633 }
2634
2635 return cell_array;
2636}
2637
2638matvar_t* MatWriter::createCellArray2DFromStrings(
2639 const std::string& name,
2640 const std::vector<std::vector<std::string>>& data,
2641 size_t rows,
2642 size_t cols) {
2643
2644 // Create 2D cell array (rows x cols)
2645 std::vector<size_t> cell_dims = {rows, cols};
2646 matvar_t* cell_array = Mat_VarCreate(name.c_str(), MAT_C_CELL, MAT_T_CELL,
2647 2, cell_dims.data(), nullptr, 0);
2648
2649 if (!cell_array) {
2650 LOG_ERROR << "Failed to create 2D cell array: " << name;
2651 return nullptr;
2652 }
2653
2654 // Fill each cell with string
2655 for (size_t i = 0; i < rows && i < data.size(); ++i) {
2656 for (size_t j = 0; j < cols && j < data[i].size(); ++j) {
2657 const std::string& str = data[i][j];
2658
2659 if (str.empty()) {
2660 // Empty cell
2661 Mat_VarSetCell(cell_array, i * cols + j, nullptr);
2662 continue;
2663 }
2664
2665 // Create string variable
2666 std::vector<size_t> dims = {1, str.length()};
2667 matvar_t* str_var = Mat_VarCreate(nullptr, MAT_C_CHAR, MAT_T_UINT8,
2668 2, dims.data(), (void*)str.c_str(), 0);
2669
2670 Mat_VarSetCell(cell_array, i * cols + j, str_var);
2671 }
2672 }
2673
2674 return cell_array;
2675}
2676
2677matvar_t* MatWriter::createCellArray2DFromVectors(
2678 const std::string& name,
2679 const std::vector<std::vector<std::vector<double>>>& data,
2680 size_t rows,
2681 size_t cols) {
2682
2683 // Create 2D cell array (rows x cols)
2684 std::vector<size_t> cell_dims = {rows, cols};
2685 matvar_t* cell_array = Mat_VarCreate(name.c_str(), MAT_C_CELL, MAT_T_CELL,
2686 2, cell_dims.data(), nullptr, 0);
2687
2688 if (!cell_array) {
2689 LOG_ERROR << "Failed to create 2D cell array: " << name;
2690 return nullptr;
2691 }
2692
2693 // Fill each cell with double vector
2694 for (size_t i = 0; i < rows && i < data.size(); ++i) {
2695 for (size_t j = 0; j < cols && j < data[i].size(); ++j) {
2696 const auto& vec = data[i][j];
2697
2698 if (vec.empty()) {
2699 // Empty cell
2700 Mat_VarSetCell(cell_array, i * cols + j, nullptr);
2701 continue;
2702 }
2703
2704 // Create double array (MATLAB: 1×N row vector)
2705 std::vector<size_t> dims = {1, vec.size()};
2706 matvar_t* vec_var = Mat_VarCreate(nullptr, MAT_C_DOUBLE, MAT_T_DOUBLE,
2707 2, dims.data(), (void*)vec.data(), 0);
2708
2709 Mat_VarSetCell(cell_array, i * cols + j, vec_var);
2710 }
2711 }
2712
2713 return cell_array;
2714}
2715
2716} // namespace cppxdic
static matvar_t * buildAllPairsResultsVar(const std::vector< DIC3DpairResults > &all_pairs)
Build the AllPairsResults cell array (full DIC3DpairResults structs) purely in memory — the caller at...
static bool writeDistortionGroup(mat_t *matfp, const DistortionData &distortion)
Write distortion group (distortionModel, distortionPath)
static bool writeCalibrationGroup(mat_t *matfp, const CalibrationData &calibration)
Write calibration group (DLTparameters, DLTpath)
static bool writeMultiFrameNcorrFile(const std::string &filename, const cv::Mat &ref_img, const std::vector< cv::Mat > &cur_imgs, const cv::Mat &ref_roi, const std::vector< cv::Mat > &cur_rois, const std::vector< ncorr::DIC_analysis_output > &dic_outputs, const std::map< std::string, double > &dispinfo, const std::string &type_str="load", const std::string &ref_name="reference")
Write multi-frame ncorr files (ncorr1.mat, ncorr2.mat, etc.) For tracking DIC with multiple current f...
static bool writeSeedFile(const std::string &filename, int seed_x, int seed_y)
Write REF_SEED_xxx_phase_pairX.mat file Simple structure: seed_point (uint16 1D array [x,...
static bool writeDIC2DPairResults(const std::string &filename, const DIC2DPairResults &results)
Write myDIC2DpairResults_C_X_C_Y.mat file Contains processed 2D DIC results with triangulation.
static bool write3DCombinedResults(const std::string &filename, const DIC3Dcombined &combined, const std::string &struct_name="DIC3Dcombined")
Write DIC3Dcombined_XPairs_stitched.mat file Contains 3D reconstruction results.
static bool writeTrialInfoFile(const std::string &filename, double fps, const std::vector< int > &frame_indices)
Write dic_info_data_target_pairX.mat file Structure:
static bool write3DPPresults(const std::string &filename, const DIC3DPPresults &ppresults)
Write DIC3DPPresults_XPairs_Y_v1.mat file Contains deformation and strain analysis results.
static matvar_t * buildDeformationStruct(const std::string &group_name, const FrameDeformationResult &deform_data)
Build deformation struct as matvar_t* for embedding in parent struct Does NOT write to file.
static bool writeDisplacementGroup(mat_t *matfp, const std::vector< std::vector< Eigen::Vector3d > > &disp_vec, const std::vector< std::vector< double > > &disp_mgn, size_t n_frames)
Write displacement group (DispVec, DispMgn)
static bool writeMatchingFile(const std::string &filename, const cv::Mat &ref_img, const cv::Mat &cur_img, const cv::Mat &ref_roi, const cv::Mat &cur_roi, const ncorr::DIC_analysis_output &dic_output, const std::map< std::string, double > &dispinfo)
Write MATCHING2xxx_pair.mat file Structure:
static bool writeROIMaskFile(const std::string &filename, const cv::Mat &mask)
Write REF_MASK_xxx_phase_pairX.mat file Simple structure: refmask (uint8 2D array)
static matvar_t * buildCombinedStructFields(mat_t *matfp, const DIC3Dcombined &combined, const std::string &struct_name, const std::vector< std::string > &extra_fields={})
Build combined struct fields as a matvar_t* (for embedding in parent struct) Does NOT write to file.
static matvar_t * buildDIC2DinfoVar(const std::vector< DIC2DPairResults > &dic2d_info)
Build the DIC2Dinfo cell array of DIC2DPairResults purely in memory — the caller attaches it to a str...
static bool writeDICResultFile(const std::string &filename, const ncorr::DIC_analysis_input &dic_input, const ncorr::DIC_analysis_output &dic_output)
Write ncorr1.mat / ncorr2.mat / ncorr12.mat files Full DIC results with dispinfo and displacements St...
static bool writeDeformationGroup(mat_t *matfp, const std::string &group_name, const FrameDeformationResult &deform_data)
Write deformation fields to HDF5 group Writes all 36 deformation measures as cell arrays.
static bool convertBinToMat(const std::string &bin_path, const std::string &mat_path, const ncorr::DIC_analysis_input &dic_input)
Convert ncorr .bin output to .mat format Wrapper to load .bin and save as MATLAB-compatible ....
static bool writeFaceArrays(mat_t *matfp, const std::vector< std::vector< Eigen::Vector3d > > &face_centroids, const std::vector< std::vector< double > > &face_corr_comb, const std::vector< std::vector< double > > &face_iso_ind, size_t n_frames)
Write face-based cell arrays (FaceCentroids, FaceCorrComb, FaceIsoInd)
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.
static matvar_t * createEmptyCharVar(const char *name=nullptr)
static size_t scalar_dims[2]
std::vector< std::vector< std::string > > DLT_paths
std::vector< std::vector< std::vector< double > > > DLT_params
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 Post-Processing Results Equivalent to MATLAB's DIC3DPPresults.
std::vector< std::vector< double > > FaceIsoInd
FrameDeformationResult deform_full
DIC 3D Combined Results Equivalent to MATLAB's DIC3Dcombined This represents stitched results from mu...
std::vector< int > FacePairInds
DistortionData distortion
std::vector< int > PointPairInds
std::vector< DIC3DpairResults > AllPairsResults
std::vector< Point3DFrame > Points3D
std::vector< int > pairIndices
std::vector< double > FaceColors
std::vector< std::vector< double > > FaceCorrComb
std::vector< std::vector< double > > corrComb
std::vector< DIC2DPairResults > DIC2Dinfo
CalibrationData calibration
std::vector< int > Faces
std::vector< std::vector< double > > FaceCentroids
std::string units
std::vector< std::string > imgcorr
std::vector< double > cutoff_corrcoef
StepAnalysisInfo stepanalysis
std::vector< std::vector< double > > DispVec
std::vector< std::vector< double > > DispMgn
std::vector< std::vector< std::string > > distortion_models
std::vector< std::vector< std::string > > distortion_paths
Frame-based deformation result Contains deformation results for all frames.
std::vector< DeformationResult > frames