CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
roi_manager.cpp
Go to the documentation of this file.
1
7#include "roi_manager.h"
8#include "mat_reader.h"
9#include "logging.h"
10#include <matio.h>
11#if CV_VERSION_MAJOR >= 5
12#include <opencv2/geometry.hpp> // cv::moments moved out of imgproc in OpenCV 5
13#endif
14#include <iostream>
15#include <filesystem>
16#include <cmath>
17
18namespace cppxdic {
19
21 const cv::Mat& reference_image) {
22 if (std::filesystem::exists(params.roifile)) {
23 LOG_INFO << "Loading ROI from: " << params.roifile;
24 return loadROIFromMat(params.roifile);
25 } else {
26 LOG_WARN << "ROI file not found, creating full ROI";
27 return createFullROI(reference_image.size());
28 }
29}
30
31cv::Mat ROIManager::loadROIFromMat(const std::string& roi_file) {
32 mat_t* matfp = Mat_Open(roi_file.c_str(), MAT_ACC_RDONLY);
33 if (!matfp) {
34 LOG_ERROR << "Failed to open ROI file: " << roi_file;
35 return cv::Mat();
36 }
37
38 // Read 'refmask' variable
39 matvar_t* refmask_var = Mat_VarRead(matfp, "refmask");
40 if (!refmask_var) {
41 LOG_ERROR << "Variable 'refmask' not found in " << roi_file;
42 Mat_Close(matfp);
43 return cv::Mat();
44 }
45
46 // Convert to cv::Mat
47 cv::Mat roi_mask;
48 if (refmask_var->class_type == MAT_C_UINT8 && refmask_var->rank == 2) {
49 size_t height = refmask_var->dims[0];
50 size_t width = refmask_var->dims[1];
51
52 roi_mask = cv::Mat(height, width, CV_8UC1);
53 const uint8_t* data = static_cast<const uint8_t*>(refmask_var->data);
54
55 // Check if this is a MATLAB logical array (values are 0 and 1)
56 bool is_logical = (refmask_var->isLogical != 0);
57
58 // MATLAB is column-major, OpenCV is row-major
59 for (size_t y = 0; y < height; ++y) {
60 for (size_t x = 0; x < width; ++x) {
61 uint8_t value = data[x * height + y];
62 // Scale logical values (0,1) to image values (0,255) for proper visualization
63 roi_mask.at<uint8_t>(y, x) = is_logical ? (value * 255) : value;
64 }
65 }
66
67 if (is_logical) {
68 LOG_DEBUG << " Loaded logical mask (scaled 0/1 to 0/255)";
69 }
70 } else {
71 LOG_WARN << "Unexpected refmask type (class=" << refmask_var->class_type
72 << ", rank=" << refmask_var->rank << ")";
73 }
74
75 Mat_VarFree(refmask_var);
76 Mat_Close(matfp);
77
78 return roi_mask;
79}
80
81SeedPoint ROIManager::loadSeedFromMat(const std::string& seed_file) {
82 SeedPoint seed;
83
84 mat_t* matfp = Mat_Open(seed_file.c_str(), MAT_ACC_RDONLY);
85 if (!matfp) {
86 LOG_ERROR << "Failed to open seed file: " << seed_file;
87 return seed;
88 }
89
90 // Read 'seed_point' variable (can be uint16 or double array [x, y])
91 matvar_t* seed_var = Mat_VarRead(matfp, "seed_point");
92 if (!seed_var) {
93 LOG_ERROR << "Variable 'seed_point' not found in " << seed_file;
94 Mat_Close(matfp);
95 return seed;
96 }
97
98 // Handle different data types
99 if (seed_var->rank >= 1 && seed_var->data) {
100 size_t num_elements = 1;
101 for (int i = 0; i < seed_var->rank; i++) {
102 num_elements *= seed_var->dims[i];
103 }
104
105 if (num_elements >= 2) {
106 // Try different data types
107 if (seed_var->data_type == MAT_T_UINT16 || seed_var->data_type == MAT_T_INT16) {
108 const uint16_t* data = static_cast<const uint16_t*>(seed_var->data);
109 seed.pw = {static_cast<int>(data[0]), static_cast<int>(data[1])};
110 } else if (seed_var->data_type == MAT_T_DOUBLE) {
111 const double* data = static_cast<const double*>(seed_var->data);
112 seed.pw = {static_cast<int>(data[0]), static_cast<int>(data[1])};
113 } else if (seed_var->data_type == MAT_T_SINGLE) {
114 const float* data = static_cast<const float*>(seed_var->data);
115 seed.pw = {static_cast<int>(data[0]), static_cast<int>(data[1])};
116 } else {
117 LOG_WARN << "Unexpected seed_point data type: " << seed_var->data_type;
118 // Try to read as uint16 anyway
119 const uint16_t* data = static_cast<const uint16_t*>(seed_var->data);
120 seed.pw = {static_cast<int>(data[0]), static_cast<int>(data[1])};
121 }
122 LOG_DEBUG << " Loaded seed point: (" << seed.pw[0] << ", " << seed.pw[1] << ")";
123 }
124 }
125
126 Mat_VarFree(seed_var);
127 Mat_Close(matfp);
128
129 return seed;
130}
131
133 const cv::Mat& roi_mask) {
134 if (std::filesystem::exists(params.seedfile)) {
135 LOG_INFO << "Loading seed from: " << params.seedfile;
136 return loadSeedFromMat(params.seedfile);
137 } else {
138 LOG_WARN << "Seed file not found, creating default seed at ROI center";
139 SeedPoint seed;
140 seed.pw = findROICenter(roi_mask);
141 return seed;
142 }
143}
144
145// Convert pixel coordinates to subset coordinates (reduced grid indices)
146// MATLAB equivalent: round((pos-1)/(spacing+1)+1) for 1-indexed
147// C++ equivalent: round(pos/(spacing+1)) for 0-indexed
148// spacing+1 = scalefactor (the stride between subset points)
149std::vector<int> ROIManager::mapPixel2Subset(const std::vector<int>& pixel_coords,
150 int subset_spacing) {
151 double scalefactor = static_cast<double>(subset_spacing + 1);
152 return {
153 static_cast<int>(std::round(pixel_coords[0] / scalefactor)),
154 static_cast<int>(std::round(pixel_coords[1] / scalefactor))
155 };
156}
157
158// Convert subset coordinates to pixel coordinates
159// MATLAB equivalent: (pos-1)*(spacing+1)+1 for 1-indexed
160// C++ equivalent: pos*(spacing+1) for 0-indexed
161std::vector<int> ROIManager::mapSubset2Pixel(const std::vector<int>& subset_coords,
162 int subset_spacing) {
163 double scalefactor = static_cast<double>(subset_spacing + 1);
164 return {
165 static_cast<int>(std::round(subset_coords[0] * scalefactor)),
166 static_cast<int>(std::round(subset_coords[1] * scalefactor))
167 };
168}
169
170std::vector<int> ROIManager::mapPointCoordinate(const std::vector<int>& point_sw,
171 const cv::Mat& U_mapped,
172 const cv::Mat& V_mapped) {
173 // Convert subset coords to pixel indices (round to nearest)
174 int x = static_cast<int>(std::round(point_sw[0]));
175 int y = static_cast<int>(std::round(point_sw[1]));
176
177 LOG_DEBUG << "x: " << x << ", y: " << y;
178
179 // Bounds checking
180 if (x < 0 || x >= U_mapped.cols || y < 0 || y >= U_mapped.rows) {
181 LOG_ERROR << "Point coordinates out of bounds: (" << x << ", " << y << ")";
182 return point_sw;
183 }
184
185 // Look up displacement at (y, x)
186 double u = U_mapped.at<double>(y, x);
187 double v = V_mapped.at<double>(y, x);
188
189 LOG_DEBUG << "u: " << u << ", v: " << v;
190
191 // Add displacement to original point (in subset world coordinates)
192 // point_sw is in subset coordinates, u/v are displacements in reduced grid units
193 return {
194 static_cast<int>(std::round(point_sw[0] + u)),
195 static_cast<int>(std::round(point_sw[1] + v))
196 };
197}
198
199bool ROIManager::loadMatchingResults(const std::string& matching_file,
200 cv::Mat& refmask_REF,
201 cv::Mat& refmask_trial,
202 cv::Mat& U_mapped,
203 cv::Mat& V_mapped,
204 int subset_spacing) {
205 mat_t* matfp = Mat_Open(matching_file.c_str(), MAT_ACC_RDONLY);
206 if (!matfp) {
207 LOG_ERROR << "Failed to open matching file: " << matching_file;
208 return false;
209 }
210
211 LOG_INFO << "Loading matching results from: " << matching_file;
212
213 // Load reference mask: reference_save.roi.mask
214 matvar_t* ref_roi_mask = MatReader::readNestedField(matfp, "reference_save.roi.mask");
215 if (ref_roi_mask) {
216 refmask_REF = MatReader::readImage(ref_roi_mask);
217 Mat_VarFree(ref_roi_mask);
218 LOG_DEBUG << " Loaded reference ROI mask: " << refmask_REF.size();
219 } else {
220 LOG_WARN << " Could not load reference_save.roi.mask";
221 }
222
223 // Load current mask: current_save.roi.mask
224 matvar_t* cur_roi_mask = MatReader::readNestedField(matfp, "current_save.roi.mask");
225 if (cur_roi_mask) {
226 refmask_trial = MatReader::readImage(cur_roi_mask);
227 Mat_VarFree(cur_roi_mask);
228 LOG_DEBUG << " Loaded current ROI mask: " << refmask_trial.size();
229 } else {
230 LOG_WARN << " Could not load current_save.roi.mask";
231 }
232
233 // Load displacements: data_dic_save.displacements.plot_u_ref_formatted (cell array)
234 matvar_t* u_cell = MatReader::readNestedField(matfp, "data_dic_save.displacements.plot_u_ref_formatted");
235 if (u_cell && u_cell->class_type == MAT_C_CELL) {
236 // Get first cell (frame 0)
237 matvar_t* u_mat = MatReader::getCellElement(u_cell, 0);
238 if (u_mat) {
239 U_mapped = MatReader::readImage(u_mat);
240 LOG_DEBUG << " Loaded U displacements: " << U_mapped.size();
241 }
242 Mat_VarFree(u_cell);
243 } else {
244 LOG_WARN << " Could not load plot_u_ref_formatted";
245 }
246
247 // Load V displacements: data_dic_save.displacements.plot_v_ref_formatted (cell array)
248 matvar_t* v_cell = MatReader::readNestedField(matfp, "data_dic_save.displacements.plot_v_ref_formatted");
249 if (v_cell && v_cell->class_type == MAT_C_CELL) {
250 // Get first cell (frame 0)
251 matvar_t* v_mat = MatReader::getCellElement(v_cell, 0);
252 if (v_mat) {
253 V_mapped = MatReader::readImage(v_mat);
254 LOG_DEBUG << " Loaded V displacements: " << V_mapped.size();
255 }
256 Mat_VarFree(v_cell);
257 } else {
258 LOG_WARN << " Could not load plot_v_ref_formatted";
259 }
260
261 Mat_Close(matfp);
262
263 bool success = !refmask_REF.empty() && !refmask_trial.empty();
264 if (success) {
265 LOG_INFO << "Successfully loaded matching results";
266 }
267 return success;
268}
269
270cv::Mat ROIManager::createFullROI(const cv::Size& image_size) {
271 // Create full ROI (all pixels valid)
272 LOG_INFO << "Creating full ROI (all pixels valid)";
273 return cv::Mat::ones(image_size, CV_8UC1) * 255;
274}
275
276std::vector<int> ROIManager::findROICenter(const cv::Mat& roi_mask) {
277 cv::Point2f center = computeROICenterOfMass(roi_mask);
278 return {static_cast<int>(center.x), static_cast<int>(center.y)};
279}
280
281cv::Mat ROIManager::ncorrROIToMat(const ncorr::ROI2D& roi) {
282 const auto& mask = roi.get_mask();
283 size_t height = mask.height();
284 size_t width = mask.width();
285
286 cv::Mat cv_mask(height, width, CV_8UC1);
287
288 for (size_t y = 0; y < height; ++y) {
289 for (size_t x = 0; x < width; ++x) {
290 cv_mask.at<uint8_t>(y, x) = mask(y, x) ? 255 : 0;
291 }
292 }
293
294 return cv_mask;
295}
296
297ncorr::ROI2D ROIManager::matToNcorrROI(const cv::Mat& mask) {
298 // Convert cv::Mat to ncorr::Array2D<bool>
299 size_t height = mask.rows;
300 size_t width = mask.cols;
301
302 ncorr::Array2D<bool> bool_mask(height, width);
303
304 for (size_t y = 0; y < height; ++y) {
305 for (size_t x = 0; x < width; ++x) {
306 bool_mask(y, x) = (mask.at<uint8_t>(y, x) > 0);
307 }
308 }
309
310 return ncorr::ROI2D(bool_mask);
311}
312
313cv::Point2f ROIManager::computeROICenterOfMass(const cv::Mat& roi_mask) {
314 cv::Moments m = cv::moments(roi_mask, true);
315
316 if (m.m00 == 0) {
317 // Fallback to image center if no valid pixels
318 return cv::Point2f(roi_mask.cols / 2.0f, roi_mask.rows / 2.0f);
319 }
320
321 return cv::Point2f(m.m10 / m.m00, m.m01 / m.m00);
322}
323
324} // namespace cppxdic
static cv::Mat readImage(matvar_t *var)
Read image from matvar.
static matvar_t * getCellElement(matvar_t *var, size_t index)
Get cell from cell array.
static matvar_t * readNestedField(mat_t *matfp, const std::string &path)
Read a nested struct field (e.g., "data_dic_save.dispinfo.radius")
static std::vector< int > mapSubset2Pixel(const std::vector< int > &subset_coords, int subset_spacing)
Map subset coordinates to pixel coordinates Equivalent to map_subset2pixel.m.
static std::vector< int > findROICenter(const cv::Mat &roi_mask)
Find center of ROI mask.
static std::vector< int > mapPointCoordinate(const std::vector< int > &point_sw, const cv::Mat &U_mapped, const cv::Mat &V_mapped)
Map point coordinates using displacement fields Equivalent to map_pointcoordinate....
static SeedPoint loadSeedFromMat(const std::string &seed_file)
Load seed point from MAT file.
static std::vector< int > mapPixel2Subset(const std::vector< int > &pixel_coords, int subset_spacing)
Map pixel coordinates to subset coordinates Equivalent to map_pixel2subset.m.
static cv::Mat loadROIFromMat(const std::string &roi_file)
Load ROI mask from MAT file.
static ncorr::ROI2D matToNcorrROI(const cv::Mat &mask)
Convert OpenCV Mat to ncorr ROI2D.
static SeedPoint loadOrCreateSeed(const BaseParameters &params, const cv::Mat &roi_mask)
Load or create seed point If seed file doesn't exist, creates a default seed point at ROI center.
static bool loadMatchingResults(const std::string &matching_file, cv::Mat &refmask_REF, cv::Mat &refmask_trial, cv::Mat &U_mapped, cv::Mat &V_mapped, int subset_spacing)
Load matching results from MAT file Extracts ROI masks and displacement fields from matching file.
static cv::Mat loadOrCreateROI(const BaseParameters &params, const cv::Mat &reference_image)
Load or create ROI mask If ROI file doesn't exist, creates a full-image ROI Equivalent to checking an...
static cv::Mat createFullROI(const cv::Size &image_size)
Create default ROI mask (full image)
static cv::Mat ncorrROIToMat(const ncorr::ROI2D &roi)
Convert ncorr ROI2D to OpenCV Mat.
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.
Base parameters structure Contains fundamental paths and settings for DIC analysis Equivalent to base...
Definition parameters.h:20
Seed point structure Stores seed points in both pixel world (pw) and subset world (sw) coordinates.
Definition parameters.h:108
std::vector< int > pw
Definition parameters.h:109