CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
generate_ncorr_bin.cpp
Go to the documentation of this file.
1
22#include <iostream>
23#include <string>
24#include <vector>
25#include <filesystem>
26#include <cmath>
27
28#include <matio.h>
29#include <hdf5.h>
30
31#include "ncorr.h"
32#include "mat_reader.h"
33#include "logging.h"
34
35using namespace ncorr;
36using namespace cppxdic;
37
38// ---------------------------------------------------------------------------
39// Helpers for reading nested HDF5 groups as matio structs
40// ---------------------------------------------------------------------------
41
42static matvar_t* getField(matvar_t* parent, const char* name) {
43 if (!parent || parent->class_type != MAT_C_STRUCT) return nullptr;
44 return Mat_VarGetStructFieldByName(parent, name, 0);
45}
46
47// ---------------------------------------------------------------------------
48// HDF5 helpers for reading object reference arrays (MATLAB cell arrays)
49// ---------------------------------------------------------------------------
50
51// Dereference an object reference with the 1.10+ signature regardless of the
52// library's default API mapping (Ubuntu's hdf5 1.10 maps the H5Rdereference
53// macro to the old 3-arg H5Rdereference1, which does not compile here).
54static hid_t h5Deref(hid_t file_id, const hobj_ref_t& ref) {
55#if H5_VERSION_GE(1, 10, 0)
56 return H5Rdereference2(file_id, H5P_DEFAULT, H5R_OBJECT, &ref);
57#else
58 return H5Rdereference(file_id, H5R_OBJECT, &ref);
59#endif
60}
61
62// Read a 2D double dataset from an HDF5 object id (column-major → Array2D row-major)
63static Array2D<double> h5ReadDoubleArray2D(hid_t file_id, const hobj_ref_t& ref) {
64 hid_t obj_id = h5Deref(file_id, ref);
65 if (obj_id < 0) return {};
66
67 hid_t space = H5Dget_space(obj_id);
68 int ndims = H5Sget_simple_extent_ndims(space);
69 if (ndims != 2) {
70 H5Sclose(space);
71 H5Dclose(obj_id);
72 return {};
73 }
74
75 hsize_t dims[2];
76 H5Sget_simple_extent_dims(space, dims, nullptr);
77 H5Sclose(space);
78
79 // HDF5/MATLAB: dims[0] = cols, dims[1] = rows (column-major storage)
80 const size_t cols = dims[0];
81 const size_t rows = dims[1];
82
83 std::vector<double> buf(rows * cols);
84 herr_t err = H5Dread(obj_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buf.data());
85 H5Dclose(obj_id);
86
87 if (err < 0) return {};
88
89 Array2D<double> arr(static_cast<std::ptrdiff_t>(rows),
90 static_cast<std::ptrdiff_t>(cols));
91 // buf is column-major: buf[c * rows + r]
92 for (size_t c = 0; c < cols; ++c)
93 for (size_t r = 0; r < rows; ++r)
94 arr(static_cast<std::ptrdiff_t>(r),
95 static_cast<std::ptrdiff_t>(c)) = buf[c * rows + r];
96 return arr;
97}
98
99// Read a 2D uint8/logical mask from an HDF5 object ref → Array2D<bool>
100static Array2D<bool> h5ReadMask2D(hid_t file_id, const hobj_ref_t& ref) {
101 hid_t obj_id = h5Deref(file_id, ref);
102 if (obj_id < 0) return {};
103
104 hid_t space = H5Dget_space(obj_id);
105 int ndims = H5Sget_simple_extent_ndims(space);
106 if (ndims != 2) {
107 H5Sclose(space);
108 H5Dclose(obj_id);
109 return {};
110 }
111
112 hsize_t dims[2];
113 H5Sget_simple_extent_dims(space, dims, nullptr);
114 H5Sclose(space);
115
116 const size_t cols = dims[0];
117 const size_t rows = dims[1];
118
119 // Determine native type
120 hid_t dtype = H5Dget_type(obj_id);
121 H5T_class_t tclass = H5Tget_class(dtype);
122 H5Tclose(dtype);
123
124 Array2D<bool> mask(static_cast<std::ptrdiff_t>(rows),
125 static_cast<std::ptrdiff_t>(cols), false);
126
127 if (tclass == H5T_INTEGER) {
128 std::vector<uint8_t> buf(rows * cols);
129 H5Dread(obj_id, H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, buf.data());
130 for (size_t c = 0; c < cols; ++c)
131 for (size_t r = 0; r < rows; ++r)
132 mask(static_cast<std::ptrdiff_t>(r),
133 static_cast<std::ptrdiff_t>(c)) = (buf[c * rows + r] != 0);
134 } else if (tclass == H5T_FLOAT) {
135 std::vector<double> buf(rows * cols);
136 H5Dread(obj_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buf.data());
137 for (size_t c = 0; c < cols; ++c)
138 for (size_t r = 0; r < rows; ++r)
139 mask(static_cast<std::ptrdiff_t>(r),
140 static_cast<std::ptrdiff_t>(c)) = (buf[c * rows + r] != 0.0);
141 } else {
142 LOG_WARN << "unsupported HDF5 type class " << tclass << " for ROI mask";
143 }
144
145 H5Dclose(obj_id);
146 return mask;
147}
148
149// Read an object reference array dataset, return vector of hobj_ref_t and count
150static std::vector<hobj_ref_t> h5ReadObjRefArray(hid_t file_id, const char* path, size_t& count) {
151 count = 0;
152 hid_t dset = H5Dopen2(file_id, path, H5P_DEFAULT);
153 if (dset < 0) return {};
154
155 hid_t space = H5Dget_space(dset);
156 hssize_t nElems = H5Sget_simple_extent_npoints(space);
157 H5Sclose(space);
158
159 if (nElems <= 0) {
160 H5Dclose(dset);
161 return {};
162 }
163
164 count = static_cast<size_t>(nElems);
165 std::vector<hobj_ref_t> refs(count);
166 herr_t err = H5Dread(dset, H5T_STD_REF_OBJ, H5S_ALL, H5S_ALL, H5P_DEFAULT, refs.data());
167 H5Dclose(dset);
168
169 if (err < 0) {
170 count = 0;
171 return {};
172 }
173 return refs;
174}
175
176// ---------------------------------------------------------------------------
177// Main
178// ---------------------------------------------------------------------------
179
180int main(int argc, char* argv[]) {
181 if (argc < 2) {
182 std::cerr << "Usage: generate_ncorr_bin <input.mat> [output.bin]\n";
183 return 1;
184 }
185
186 std::string input_path = argv[1];
187 std::string output_path;
188 if (argc >= 3) {
189 output_path = argv[2];
190 } else {
191 // Replace .mat with .bin
192 output_path = input_path;
193 auto pos = output_path.rfind(".mat");
194 if (pos != std::string::npos) {
195 output_path.replace(pos, 4, ".bin");
196 } else {
197 output_path += ".bin";
198 }
199 }
200
201 if (!std::filesystem::exists(input_path)) {
202 LOG_ERROR << "input file not found: " << input_path;
203 return 1;
204 }
205
206 LOG_INFO << "Input: " << input_path;
207 LOG_INFO << "Output: " << output_path;
208
209 // -----------------------------------------------------------------------
210 // Open MAT file via matio (for dispinfo scalars/strings)
211 // -----------------------------------------------------------------------
212 auto matfp = MatReader::openMat(input_path);
213 if (!matfp) {
214 LOG_ERROR << "failed to open MAT file: " << input_path;
215 return 1;
216 }
217
218 auto data_dic_save = MatReader::readVar(matfp.get(), "data_dic_save");
219 if (!data_dic_save || data_dic_save->class_type != MAT_C_STRUCT) {
220 LOG_ERROR << "'data_dic_save' not found or not a struct";
221 return 1;
222 }
223
224 // -----------------------------------------------------------------------
225 // Read dispinfo via matio (works fine for scalars/strings)
226 // -----------------------------------------------------------------------
227 matvar_t* dispinfo = getField(data_dic_save.get(), "dispinfo");
228 if (!dispinfo || dispinfo->class_type != MAT_C_STRUCT) {
229 LOG_ERROR << "'data_dic_save.dispinfo' not found";
230 return 1;
231 }
232
233 int spacing = 0;
234 if (matvar_t* f = getField(dispinfo, "spacing")) {
235 spacing = static_cast<int>(MatReader::readScalar(f));
236 }
237 const std::ptrdiff_t scalefactor = spacing + 1;
238
239 double pixtounits = 1.0;
240 if (matvar_t* f = getField(dispinfo, "pixtounits")) {
241 pixtounits = MatReader::readScalar(f);
242 }
243
244 std::string units = "pixels";
245 if (matvar_t* f = getField(dispinfo, "units")) {
246 units = MatReader::readString(f);
247 }
248
249 std::string type_str = "Lagrangian";
250 if (matvar_t* f = getField(dispinfo, "type")) {
251 type_str = MatReader::readString(f);
252 }
253
254 PERSPECTIVE perspective = PERSPECTIVE::LAGRANGIAN;
255 if (type_str.find("ulerian") != std::string::npos ||
256 type_str.find("Eulerian") != std::string::npos) {
257 perspective = PERSPECTIVE::EULERIAN;
258 }
259
260 LOG_INFO << " spacing=" << spacing << " (scalefactor=" << scalefactor << ")";
261 LOG_INFO << " pixtounits=" << pixtounits;
262 LOG_INFO << " units=\"" << units << "\"";
263 LOG_INFO << " type=\"" << type_str << "\"";
264
265 // Close matio handle before opening HDF5 directly
266 data_dic_save.reset();
267 matfp.reset();
268
269 // -----------------------------------------------------------------------
270 // Open as HDF5 for reading object reference arrays
271 // -----------------------------------------------------------------------
272 hid_t file_id = H5Fopen(input_path.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
273 if (file_id < 0) {
274 LOG_ERROR << "failed to open HDF5 file: " << input_path;
275 return 1;
276 }
277
278 const char* u_path = "/data_dic_save/displacements/plot_u_dic";
279 const char* v_path = "/data_dic_save/displacements/plot_v_dic";
280 const char* cc_path = "/data_dic_save/displacements/plot_corrcoef_dic";
281 const char* roi_path = "/data_dic_save/displacements/roi_dic";
282
283 size_t nFrames = 0;
284 auto u_refs = h5ReadObjRefArray(file_id, u_path, nFrames);
285 if (nFrames == 0) {
286 LOG_ERROR << "plot_u_dic has 0 references";
287 H5Fclose(file_id);
288 return 1;
289 }
290 LOG_INFO << " nFrames=" << nFrames;
291
292 size_t nV = 0, nCC = 0, nROI = 0;
293 auto v_refs = h5ReadObjRefArray(file_id, v_path, nV);
294 auto cc_refs = h5ReadObjRefArray(file_id, cc_path, nCC);
295 auto roi_refs = h5ReadObjRefArray(file_id, roi_path, nROI);
296
297 if (nV != nFrames) {
298 LOG_ERROR << "plot_v_dic has " << nV << " refs (expected " << nFrames << ")";
299 H5Fclose(file_id);
300 return 1;
301 }
302
303 // -----------------------------------------------------------------------
304 // Probe ROI: roi_dic refs point to ncorr_class_roi objects (HDF5 structs),
305 // not simple 2D boolean masks. Try to read the first one; if it fails or
306 // has wrong dimensions, fall back to all-true masks for every frame.
307 // -----------------------------------------------------------------------
308 bool roi_usable = false;
309 if (nROI > 0) {
310 // Peek at first ROI reference to check if it's a plain 2D mask
311 Array2D<bool> test_mask = h5ReadMask2D(file_id, roi_refs[0]);
312 // We'll know the expected dims from the first u frame
313 Array2D<double> u0 = h5ReadDoubleArray2D(file_id, u_refs[0]);
314 if (!test_mask.empty() && !u0.empty() &&
315 test_mask.height() == u0.height() && test_mask.width() == u0.width()) {
316 roi_usable = true;
317 }
318 }
319 if (!roi_usable && nROI > 0) {
320 LOG_INFO << " Note: roi_dic contains ncorr_class_roi objects (not simple masks). "
321 << "Using all-true ROI masks — non-ROI pixels have zero displacement.";
322 }
323
324 // -----------------------------------------------------------------------
325 // Build DIC_analysis_output
326 // -----------------------------------------------------------------------
327 std::vector<Disp2D> disps;
328 disps.reserve(nFrames);
329
330 size_t skipped = 0;
331 for (size_t f = 0; f < nFrames; ++f) {
332 Array2D<double> u_arr = h5ReadDoubleArray2D(file_id, u_refs[f]);
333 Array2D<double> v_arr = h5ReadDoubleArray2D(file_id, v_refs[f]);
334
335 if (u_arr.empty() || v_arr.empty()) {
336 LOG_WARN << "frame " << f << " has empty u or v array, skipping";
337 ++skipped;
338 disps.emplace_back();
339 continue;
340 }
341
342 // Correlation coefficient
343 Array2D<double> cc_arr;
344 if (f < nCC) {
345 cc_arr = h5ReadDoubleArray2D(file_id, cc_refs[f]);
346 }
347 if (cc_arr.empty()) {
348 cc_arr = Array2D<double>(u_arr.height(), u_arr.width(), 0.0);
349 }
350
351 // ROI mask
352 Array2D<bool> mask;
353 if (roi_usable && f < nROI) {
354 mask = h5ReadMask2D(file_id, roi_refs[f]);
355 }
356 if (mask.empty() || mask.height() != u_arr.height() || mask.width() != u_arr.width()) {
357 mask = Array2D<bool>(u_arr.height(), u_arr.width(), true);
358 }
359
360 // Validate u/v dimensions
361 if (u_arr.height() != v_arr.height() || u_arr.width() != v_arr.width()) {
362 LOG_WARN << "frame " << f << " u/v dimension mismatch ("
363 << u_arr.height() << "x" << u_arr.width() << " vs "
364 << v_arr.height() << "x" << v_arr.width() << ")";
365 ++skipped;
366 disps.emplace_back();
367 continue;
368 }
369
370 ROI2D roi(std::move(mask));
371 disps.emplace_back(std::move(v_arr), std::move(u_arr), std::move(cc_arr),
372 roi, scalefactor);
373
374 if (f == 0) {
375 LOG_INFO << " Frame 0 size: "
376 << disps.back().data_height() << "x"
377 << disps.back().data_width()
378 << ", ROI regions: " << disps.back().get_roi().size_regions()
379 << ", scalefactor: " << disps.back().get_scalefactor();
380 }
381 }
382
383 H5Fclose(file_id);
384
385 LOG_INFO << " Built " << disps.size() << " displacement frames ("
386 << skipped << " skipped)";
387
388 DIC_analysis_output output(disps, perspective, units, pixtounits);
389
390 // -----------------------------------------------------------------------
391 // Save as ncorr native binary
392 // -----------------------------------------------------------------------
393 save(output, output_path);
394 LOG_INFO << "Saved: " << output_path;
395
396 // -----------------------------------------------------------------------
397 // Verification: reload and compare
398 // -----------------------------------------------------------------------
399 LOG_INFO << "Verifying round-trip...";
400 auto reloaded = DIC_analysis_output::load(output_path);
401 bool ok = true;
402 if (reloaded.disps.size() != output.disps.size()) {
403 LOG_ERROR << "FAIL: frame count mismatch: " << reloaded.disps.size()
404 << " vs " << output.disps.size();
405 ok = false;
406 }
407 if (reloaded.units != output.units) {
408 LOG_ERROR << "FAIL: units mismatch: \"" << reloaded.units
409 << "\" vs \"" << output.units << "\"";
410 ok = false;
411 }
412 if (std::abs(reloaded.units_per_pixel - output.units_per_pixel) > 1e-12) {
413 LOG_ERROR << "FAIL: units_per_pixel mismatch: " << reloaded.units_per_pixel
414 << " vs " << output.units_per_pixel;
415 ok = false;
416 }
417 if (ok && !reloaded.disps.empty()) {
418 for (size_t f = 0; f < reloaded.disps.size(); ++f) {
419 if (reloaded.disps[f].data_height() > 0) {
420 LOG_INFO << " Reloaded frame " << f << " size: "
421 << reloaded.disps[f].data_height() << "x"
422 << reloaded.disps[f].data_width()
423 << ", ROI regions: " << reloaded.disps[f].get_roi().size_regions()
424 << ", scalefactor: " << reloaded.disps[f].get_scalefactor();
425 break;
426 }
427 }
428 }
429
430 if (ok) {
431 LOG_INFO << "Verification OK";
432 } else {
433 LOG_ERROR << "Verification FAILED";
434 return 1;
435 }
436
437 return 0;
438}
static MatVarPtr readVar(mat_t *matfp, const std::string &name)
static std::string readString(matvar_t *var)
Read string from matvar.
static double readScalar(matvar_t *var)
Read scalar from matvar.
static MatFilePtr openMat(const std::string &path)
int main(int argc, char *argv[])
static Array2D< double > h5ReadDoubleArray2D(hid_t file_id, const hobj_ref_t &ref)
static hid_t h5Deref(hid_t file_id, const hobj_ref_t &ref)
static Array2D< bool > h5ReadMask2D(hid_t file_id, const hobj_ref_t &ref)
static matvar_t * getField(matvar_t *parent, const char *name)
static std::vector< hobj_ref_t > h5ReadObjRefArray(hid_t file_id, const char *path, size_t &count)
Lightweight, dependency-free logging facility for CPPxDIC.
#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.
int count