1.3.64
 
Loading...
Searching...
No Matches
CameraCalibration.cpp
Go to the documentation of this file.
1
16#include "CameraCalibration.h"
17
18using namespace helios;
19
20CameraCalibration::CameraCalibration(helios::Context *context) : context(context) {
21}
22
23std::vector<uint> CameraCalibration::addCheckerboard(const helios::int2 &boardsidesize, const float &patchsize, const helios::vec3 &centrelocation, const helios::vec3 &rotationrad, bool firstblack) {
24 helios::vec2 patchfullsize = make_vec2(patchsize, patchsize); // size of each patch
25 std::vector<float> bwv(2, 0);
26 float bw;
27 if (firstblack) {
28 bwv[0] = 1;
29 } else {
30 bwv[1] = 1;
31 }
32 std::vector<uint> UUIDs;
33 for (int inpr = 0; inpr < boardsidesize.x; inpr += 1) {
34 for (int inpc = 0; inpc < boardsidesize.y; inpc += 1) {
35 if ((inpr % 2 == 0 & inpc % 2 == 0) | (inpr % 2 != 0 & inpc % 2 != 0)) { // black - white - black - white -...
36 bw = bwv[0];
37 } else {
38 bw = bwv[1];
39 }
40 helios::RGBcolor binarycolor(bw, bw, bw);
41
42 float xp = 0 - patchsize * (float(boardsidesize.x) - 1) / 2 + patchsize * float(inpr);
43 float yp = 0 - patchsize * (float(boardsidesize.y) - 1) / 2 + patchsize * float(inpc);
44
45 uint UUID = context->addPatch(make_vec3(xp, yp, 0), patchfullsize, nullrotation, binarycolor);
46
47 if (bw == 1) {
48 UUIDs_white.push_back(UUID);
49 context->setPrimitiveData(UUID, "reflectivity_spectrum", "white");
50 } else {
51 UUIDs_black.push_back(UUID);
52 context->setPrimitiveData(UUID, "reflectivity_spectrum", "");
53 }
54
55 context->setPrimitiveData(UUID, "transmissivity_spectrum", "");
56 context->setPrimitiveData(UUID, "twosided_flag", uint(0));
57 UUIDs.push_back(UUID);
58 }
59 }
60 std::vector<helios::vec2> whitespectra(2200);
61 for (int i = 0; i < whitespectra.size(); i++) {
62 whitespectra.at(i).x = 301 + i;
63 whitespectra.at(i).y = 1;
64 }
65 context->setGlobalData("white", whitespectra);
66 context->rotatePrimitive(UUIDs, rotationrad.x, make_vec3(1, 0, 0));
67 context->rotatePrimitive(UUIDs, rotationrad.y, make_vec3(0, 1, 0));
68 context->rotatePrimitive(UUIDs, rotationrad.z, make_vec3(0, 0, 1));
69 context->translatePrimitive(UUIDs, centrelocation);
70 return UUIDs;
71}
72
73std::vector<uint> CameraCalibration::addDefaultCheckerboard(const helios::vec3 &centrelocation, const helios::vec3 &rotationrad) {
74 helios::int2 boardsidesize = make_int2(10, 7);
75 float patchsize = 0.029;
76 bool firstblack = true;
77 std::vector<uint> UUID_checkboard = CameraCalibration::addCheckerboard(boardsidesize, patchsize, centrelocation, rotationrad, firstblack);
78 return UUID_checkboard;
79}
80
81std::vector<uint> CameraCalibration::addColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad, const std::vector<std::vector<helios::RGBcolor>> &colorassignment,
82 const std::vector<std::vector<std::string>> &spectrumassignment) {
83
84 uint Nrow;
85 uint Ncol;
86 if (!colorassignment.empty()) {
87 Nrow = colorassignment.size();
88 Ncol = colorassignment.back().size();
89 } else if (!spectrumassignment.empty()) {
90 Nrow = spectrumassignment.size();
91 Ncol = spectrumassignment.back().size();
92 } else {
93 helios_runtime_error("ERROR (CameraCalibration::addColorboard): No color or spectrum assignment provided.");
94 }
95
96 std::vector<uint> UUIDs;
97 uint UUID;
98 int patch_index = 0; // Linear patch index for labeling
99 for (int irow = 0; irow < Nrow; irow++) {
100 for (int icolumn = 0; icolumn < Ncol; icolumn++) {
101
102 float xp = centrelocation.x - patchsize * (float(Ncol) - 1) / 2.f + patchsize * float(icolumn);
103 float yp = centrelocation.y + patchsize * (float(Nrow) - 1) / 2.f - patchsize * float(irow);
104
105 if (!colorassignment.empty()) {
106 if (irow >= colorassignment.size() || icolumn >= colorassignment.at(irow).size()) {
107 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Dimensions of color assignment array are not consistent. This should be a square matrix.");
108 }
109 UUID = context->addPatch(make_vec3(xp, yp, centrelocation.z), make_vec2(patchsize, patchsize), nullrotation, colorassignment.at(irow).at(icolumn));
110 } else {
111 UUID = context->addPatch(make_vec3(xp, yp, centrelocation.z), make_vec2(patchsize, patchsize), nullrotation);
112 }
113 context->setPrimitiveData(UUID, "twosided_flag", uint(0));
114 UUIDs.push_back(UUID);
115
116 if (!spectrumassignment.empty()) {
117 if (irow >= spectrumassignment.size() || icolumn >= spectrumassignment.at(irow).size()) {
118 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Dimensions of spectrum assignment array are not consistent. This should be a square matrix.");
119 } else if (!context->doesGlobalDataExist(spectrumassignment.at(irow).at(icolumn).c_str())) {
120 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Spectrum assignment label of "
121 "" +
122 spectrumassignment.at(irow).at(icolumn) +
123 ""
124 " does not exist in global data.");
125 }
126 context->setPrimitiveData(UUID, "reflectivity_spectrum", spectrumassignment.at(irow).at(icolumn));
127 }
128
129 patch_index++;
130 }
131 }
132
133 context->rotatePrimitive(UUIDs, rotationrad.x, "x"); // rotate color board
134 context->rotatePrimitive(UUIDs, rotationrad.y, "y");
135 context->rotatePrimitive(UUIDs, rotationrad.z, "z");
136 // Note: This version does not track colorboards in the map (no type specified)
137 return UUIDs;
138}
139
140std::vector<uint> CameraCalibration::addColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad, const std::vector<std::vector<helios::RGBcolor>> &colorassignment,
141 const std::vector<std::vector<std::string>> &spectrumassignment, const std::string &colorboard_type) {
142
143 uint Nrow = colorassignment.size();
144 uint Ncol = colorassignment.back().size();
145
146 if (spectrumassignment.size() != Nrow || spectrumassignment.back().size() != Ncol) {
147 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Color and spectrum assignment dimensions must match.");
148 }
149
150 std::vector<uint> UUIDs;
151 uint UUID;
152 int patch_index = 0; // Linear patch index for labeling
153 for (int irow = 0; irow < Nrow; irow++) {
154 for (int icolumn = 0; icolumn < Ncol; icolumn++) {
155
156 float xp = centrelocation.x - patchsize * (float(Ncol) - 1) / 2.f + patchsize * float(icolumn);
157 float yp = centrelocation.y + patchsize * (float(Nrow) - 1) / 2.f - patchsize * float(irow);
158
159 if (irow >= colorassignment.size() || icolumn >= colorassignment.at(irow).size()) {
160 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Dimensions of color assignment array are not consistent. This should be a square matrix.");
161 }
162 UUID = context->addPatch(make_vec3(xp, yp, centrelocation.z), make_vec2(patchsize, patchsize), nullrotation, colorassignment.at(irow).at(icolumn));
163 context->setPrimitiveData(UUID, "twosided_flag", uint(0));
164 UUIDs.push_back(UUID);
165
166 if (irow >= spectrumassignment.size() || icolumn >= spectrumassignment.at(irow).size()) {
167 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Dimensions of spectrum assignment array are not consistent. This should be a square matrix.");
168 } else if (!context->doesGlobalDataExist(spectrumassignment.at(irow).at(icolumn).c_str())) {
169 helios_runtime_error("ERROR (CameraCalibration::addColorboard): Spectrum assignment label of " + spectrumassignment.at(irow).at(icolumn) + " does not exist in global data.");
170 }
171 context->setPrimitiveData(UUID, "reflectivity_spectrum", spectrumassignment.at(irow).at(icolumn));
172
173 // Add colorboard type and patch index labeling for auto-calibration
174 std::string colorboard_label = "colorboard_" + colorboard_type;
175 context->setPrimitiveData(UUID, colorboard_label.c_str(), uint(patch_index));
176
177 patch_index++;
178 }
179 }
180
181 context->rotatePrimitive(UUIDs, rotationrad.x, "x"); // rotate color board
182 context->rotatePrimitive(UUIDs, rotationrad.y, "y");
183 context->rotatePrimitive(UUIDs, rotationrad.z, "z");
184 // Store colorboard UUIDs in map by type
185 UUIDs_colorboards[colorboard_type] = UUIDs;
186 return UUIDs;
187}
188
189// void CameraCalibration::setColorboardReflectivity(const uint &UUID, const std::string &filename, const std::string &labelname) {
190// //\todo this needs to be updated
191// std::vector<vec2> spectraldata;
192// CameraCalibration::loadXMLlabeldata(filename,labelname,spectraldata);
193// context->setPrimitiveData(UUID, "reflectivity_spectrum", labelname);
194// context->setPrimitiveData(UUID, "reflectivity_spectrum_raw", labelname+"_raw");
195// context->setPrimitiveData(UUID, "transmissivity_spectrum", "");
196// context->setPrimitiveData(UUID, "twosided_flag", uint(0) );
197// context->setGlobalData(labelname.c_str(),spectraldata);
198// context->setGlobalData((labelname+"_raw").c_str(),spectraldata);
199// }
200
201std::vector<uint> CameraCalibration::addDefaultColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad) {
202
203 return addDGKColorboard(centrelocation, patchsize, rotationrad);
204}
205
206std::vector<uint> CameraCalibration::addDGKColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad) {
207
208 // Check if a DGK colorboard already exists
209 if (UUIDs_colorboards.find("DGK") != UUIDs_colorboards.end()) {
210 context->deletePrimitive(UUIDs_colorboards["DGK"]);
211 std::cout << "WARNING (CameraCalibration::addDGKColorboard): Replacing existing DGK colorboard." << std::endl;
212 }
213
214 context->loadXML(helios::resolvePluginAsset("radiation", "spectral_data/color_board/DGK_DKK_colorboard.xml").string().c_str(), true);
215
216 assert(context->doesGlobalDataExist("ColorReference_DGK_01"));
217
218 return CameraCalibration::addColorboard(centrelocation, patchsize, rotationrad, colorassignment_DGK, spectrumassignment_DGK, "DGK");
219}
220
221std::vector<uint> CameraCalibration::addCalibriteColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad) {
222
223 // Check if a Calibrite colorboard already exists
224 if (UUIDs_colorboards.find("Calibrite") != UUIDs_colorboards.end()) {
225 context->deletePrimitive(UUIDs_colorboards["Calibrite"]);
226 std::cout << "WARNING (CameraCalibration::addCalibriteColorboard): Replacing existing Calibrite colorboard." << std::endl;
227 }
228
229 context->loadXML(helios::resolvePluginAsset("radiation", "spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").string().c_str(), true);
230
231 assert(context->doesGlobalDataExist("ColorReference_Calibrite_01"));
232
233 return CameraCalibration::addColorboard(centrelocation, patchsize, rotationrad, colorassignment_Calibrite, spectrumassignment_Calibrite, "Calibrite");
234}
235
236std::vector<uint> CameraCalibration::addSpyderCHECKRColorboard(const helios::vec3 &centrelocation, float patchsize, const helios::vec3 &rotationrad) {
237
238 // Check if a SpyderCHECKR colorboard already exists
239 if (UUIDs_colorboards.find("SpyderCHECKR") != UUIDs_colorboards.end()) {
240 context->deletePrimitive(UUIDs_colorboards["SpyderCHECKR"]);
241 std::cout << "WARNING (CameraCalibration::addSpyderCHECKRColorboard): Replacing existing SpyderCHECKR colorboard." << std::endl;
242 }
243
244 context->loadXML(helios::resolvePluginAsset("radiation", "spectral_data/color_board/Datacolor_SpyderCHECKR_24_colorboard.xml").string().c_str(), true);
245
246 assert(context->doesGlobalDataExist("ColorReference_SpyderCHECKR_01"));
247
248 return CameraCalibration::addColorboard(centrelocation, patchsize, rotationrad, colorassignment_SpyderCHECKR, spectrumassignment_SpyderCHECKR, "SpyderCHECKR");
249}
250
252 std::vector<uint> all_UUIDs;
253 for (const auto &[type, UUIDs]: UUIDs_colorboards) {
254 all_UUIDs.insert(all_UUIDs.end(), UUIDs.begin(), UUIDs.end());
255 }
256 return all_UUIDs;
257}
258
259bool CameraCalibration::writeSpectralXMLfile(const std::string &filename, const std::string &note, const std::string &label, std::vector<vec2> *spectrum) {
260
261 std::ofstream newspectraldata(filename);
262
263 if (newspectraldata.is_open()) {
264
265 newspectraldata << "<helios>\n\n";
266 newspectraldata << "\t<!-- ";
267 newspectraldata << note;
268 newspectraldata << " -->\n";
269 newspectraldata << "\t<globaldata_vec2 label=\"";
270 newspectraldata << label;
271 newspectraldata << "\">";
272
273 for (int i = 0; i < spectrum->size(); ++i) {
274 newspectraldata << "\n\t\t";
275 newspectraldata << spectrum->at(i).x;
276 newspectraldata << " ";
277 newspectraldata << std::to_string(spectrum->at(i).y);
278 }
279 // spectrum->clear();
280 newspectraldata << "\n\t</globaldata_vec2>\n";
281 newspectraldata << "\n</helios>";
282 newspectraldata.close();
283 return true;
284 }
285
286 else {
287 std::cerr << "\n(CameraCalibration::writeSpectralXMLfile) Unable to open file";
288 return false;
289 }
290}
291
292// Load XML file and save data in spectral vectors containing both wavelengths and spectral values
293bool CameraCalibration::loadXMLlabeldata(const std::string &filename, const std::string &labelname, std::vector<vec2> &spectraldata) {
294
295 Context context_temporary;
296 context_temporary.loadXML(filename.c_str(), true);
297
298 if (context_temporary.doesGlobalDataExist(labelname.c_str())) {
299 context_temporary.getGlobalData(labelname.c_str(), spectraldata);
300 return true;
301 } else {
302 std::cerr << "\n(CameraCalibration::loadXMLlabeldata) Cannot find the label " << labelname << " in XML file:" << filename;
303 return false;
304 }
305}
306
307float CameraCalibration::GradientDescent(std::vector<std::vector<float>> *expandedcameraspectra, const std::vector<std::vector<float>> &expandedconstinput, const float &learningrate, const std::vector<std::vector<float>> &truevalues) {
308
309 size_t boardnumber = expandedconstinput.size();
310 size_t wavelengthnumber = expandedconstinput.at(0).size();
311 size_t bandnumber = expandedcameraspectra->size();
312
313 float iloss = 0;
314 for (int iband = 0; iband < expandedcameraspectra->size(); iband++) {
315
316 // Calculate errors
317 std::vector<float> output(boardnumber, 0);
318 std::vector<float> errors(boardnumber);
319 for (int iboardn = 0; iboardn < boardnumber; ++iboardn) {
320 for (int ispecn = 0; ispecn < wavelengthnumber; ++ispecn) {
321 output.at(iboardn) += expandedcameraspectra->at(iband).at(ispecn) * expandedconstinput.at(iboardn).at(ispecn);
322 }
323 errors.at(iboardn) = truevalues.at(iband).at(iboardn) - output.at(iboardn);
324
325 // Calculate root mean square error as loss function
326 iloss += (errors.at(iboardn) * errors.at(iboardn)) / float(boardnumber) / float(bandnumber);
327 }
328
329 // Update extended spectrum
330 std::vector<float> despectrum(wavelengthnumber);
331 for (int ispecn = 0; ispecn < wavelengthnumber; ++ispecn) {
332 for (int iboardn = 0; iboardn < boardnumber; ++iboardn) {
333 despectrum.at(ispecn) += errors.at(iboardn) * expandedconstinput.at(iboardn).at(ispecn);
334 }
335 expandedcameraspectra->at(iband).at(ispecn) += learningrate * despectrum.at(ispecn);
336
337 // Non-negative constrain
338 if (expandedcameraspectra->at(iband).at(ispecn) < 0) {
339 expandedcameraspectra->at(iband).at(ispecn) -= learningrate * despectrum.at(ispecn);
340 }
341 }
342 }
343 return iloss;
344}
345
346std::vector<float> CameraCalibration::expandSpectrum(const std::vector<helios::vec2> &targetspectrum, float normvalue = 1) {
347
348 std::vector<float> extendedspectrum;
349 extendedspectrum.reserve(targetspectrum.size());
350
351 for (vec2 spectralvalue: targetspectrum) {
352 extendedspectrum.push_back(spectralvalue.y / normvalue);
353 }
354
355 extendedspectrum.insert(extendedspectrum.end() - 1, extendedspectrum.begin() + 1, extendedspectrum.end() - 1);
356 return extendedspectrum;
357}
358
359static float normalizevalue(std::vector<std::vector<helios::vec2>> cameraresponsespectra, const std::map<uint, std::vector<vec2>> &simulatedinputspectra) {
360
361 // Find the maximum value of the simulated input spectra multiplied by the camera response spectra
362 float normvalue = 0;
363 for (const auto &cameraspectrum: cameraresponsespectra) {
364 for (const auto &inputspectrum: simulatedinputspectra) {
365 float outputvalue = 0;
366 for (int iwave = 1; iwave < inputspectrum.second.size() - 1; iwave++) {
367 outputvalue += inputspectrum.second.at(iwave).y * cameraspectrum.at(iwave).y * 2;
368 }
369 outputvalue += inputspectrum.second.at(0).y * cameraspectrum.at(0).y;
370 outputvalue += inputspectrum.second.back().y * cameraspectrum.back().y;
371 if (outputvalue > normvalue) {
372 normvalue = outputvalue;
373 }
374 }
375 }
376 return normvalue;
377}
378
379
380std::vector<float> CameraCalibration::updateCameraResponseSpectra(const std::vector<std::string> &camerareponselabels, const std::string &label, const std::map<uint, std::vector<vec2>> &simulatedinputspectra,
381 const std::vector<std::vector<float>> &truevalues) {
382
383 float learningrate = responseupdateparameters.learningrate;
384 int maxiteration = responseupdateparameters.maxiteration;
385 float minloss = responseupdateparameters.minloss;
386 std::vector<float> camerarescales = responseupdateparameters.camerarescales;
387
388 std::vector<std::vector<helios::vec2>> cameraresponsespectra;
389 for (std::string cameraresponselabel: camerareponselabels) {
390 std::vector<vec2> cameraresponsespectrum;
391 context->getGlobalData(cameraresponselabel.c_str(), cameraresponsespectrum);
392 cameraresponsespectra.push_back(cameraresponsespectrum);
393 }
394
395 // Get the highest value of color board by using the original camera response used for normalization
396 float normvalue = normalizevalue(cameraresponsespectra, simulatedinputspectra);
397
398 std::vector<std::vector<float>> expandedcameraspectra;
399 uint bandsnumber = cameraresponsespectra.size();
400 for (const auto &cameraspectrum: cameraresponsespectra) {
401 expandedcameraspectra.push_back(CameraCalibration::expandSpectrum(cameraspectrum, normvalue));
402 }
403
404 std::vector<std::vector<float>> expandedinputspectra;
405 expandedinputspectra.reserve(simulatedinputspectra.size());
406 for (const auto &inputspectrum: simulatedinputspectra) {
407 expandedinputspectra.push_back(CameraCalibration::expandSpectrum(inputspectrum.second));
408 }
409
410 // Update expanded camera response spectra
411 std::vector<float> loss;
412 float initialloss;
413 loss.reserve(maxiteration);
414 float stopiteration = maxiteration;
415 for (int iloop = 0; iloop < maxiteration; ++iloop) {
416 float iloss = CameraCalibration::GradientDescent(&expandedcameraspectra, expandedinputspectra, learningrate, truevalues) / float(bandsnumber);
417 loss.push_back(iloss);
418 if (iloss < minloss) {
419 stopiteration = iloop;
420 break;
421 }
422 // Automatically change learning rate
423 if (iloop == 0) {
424 initialloss = iloss;
425 } else if (iloss > 0.5 * initialloss) {
426 learningrate = learningrate * 2;
427 }
428 }
429
430 // Get the calibrated camera response spectra
431
432 for (int iband = 0; iband < camerareponselabels.size(); iband++) {
433 calibratedcameraspectra[camerareponselabels.at(iband)] = cameraresponsespectra.at(iband);
434 for (int ispec = 0; ispec < cameraresponsespectra.at(iband).size() - 1; ispec++) {
435 calibratedcameraspectra[camerareponselabels.at(iband)].at(ispec).y = expandedcameraspectra.at(iband).at(ispec) * camerarescales.at(iband);
436 }
437 calibratedcameraspectra[camerareponselabels.at(iband)].back().y = expandedcameraspectra.at(iband).back() * camerarescales.at(iband);
438 std::string calibratedlabel = label + "_" + camerareponselabels.at(iband);
439 context->setGlobalData(calibratedlabel.c_str(), calibratedcameraspectra.at(camerareponselabels.at(iband)));
440 }
441
442 // Calculate the final loss
443 float iloss = CameraCalibration::GradientDescent(&expandedcameraspectra, expandedinputspectra, learningrate, truevalues) / float(bandsnumber);
444 std::cout << "The final loss after " << stopiteration << " iteration is: " << iloss << std::endl;
445 loss.push_back(iloss);
446 return loss;
447}
448
449void CameraCalibration::writeCalibratedCameraResponses(const std::vector<std::string> &camerareponselabels, const std::string &calibratemark, float scale) {
450 // write the calibrated camera response spectra in xml files and set them as global data
451
452 for (const std::string &cameraresponselabel: camerareponselabels) {
453 std::vector<vec2> cameraresponsespectrum = calibratedcameraspectra[cameraresponselabel];
454 std::string calibratedlabel = calibratemark + "_" + cameraresponselabel;
455 for (int ispec = 0; ispec < cameraresponsespectrum.size(); ispec++) {
456 cameraresponsespectrum.at(ispec).y = cameraresponsespectrum.at(ispec).y * scale;
457 }
458 context->setGlobalData(calibratedlabel.c_str(), cameraresponsespectrum);
459 CameraCalibration::writeSpectralXMLfile(calibratedlabel + ".xml", "", calibratedlabel, &cameraresponsespectrum);
460 }
461}
462
463void CameraCalibration::distortImage(const std::string &cameralabel, const std::vector<std::string> &bandlabels, const helios::vec2 &focalxy, std::vector<double> &distCoeffs, helios::int2 cameraresolution) {
464
465 helios::int2 camerareoslutionR = cameraresolution;
466
467 // Original image dimensions
468 int cols = cameraresolution.x;
469 int rows = cameraresolution.y;
470
471 // float PPointsRatiox =1.052f;
472 // float PPointsRatioy =0.999f;
473
474 // Distorted image dimensions diff
475 int cols_dif = (cols - camerareoslutionR.x) / 2;
476 int rows_dif = (rows - camerareoslutionR.y) / 2;
477
478 for (int ib = 0; ib < bandlabels.size(); ib++) {
479 std::string global_data_label = "camera_" + cameralabel + "_" + bandlabels.at(ib);
480 std::vector<float> cameradata;
481
482 context->getGlobalData(global_data_label.c_str(), cameradata);
483
484 std::vector<float> distorted_cameradata(camerareoslutionR.x * camerareoslutionR.y, 0);
485
486 // Compute the undistorted image
487 for (int j = 0; j < rows; j++) {
488 for (int i = 0; i < cols; i++) {
489 // Compute the undistorted pixel coordinates
490 double x = (i - cols / 2) / focalxy.x;
491 double y = (j - rows / 2) / focalxy.y;
492 // Apply distortion
493 double r2 = x * x + y * y;
494 double xDistorted = x * (1 + distCoeffs[0] * r2 + distCoeffs[1] * r2 * r2) + 2 * distCoeffs[2] * x * y + distCoeffs[3] * (r2 + 2 * x * x);
495 double yDistorted = y * (1 + distCoeffs[0] * r2 + distCoeffs[1] * r2 * r2) + 2 * distCoeffs[3] * x * y + distCoeffs[2] * (r2 + 2 * y * y);
496
497 // Compute the distorted pixel coordinates
498 int xPixel = int(round(xDistorted * focalxy.x + cols / 2));
499 int yPixel = int(round(yDistorted * focalxy.y + rows / 2));
500
501 // Set the distorted pixel value
502 if (xPixel >= cols_dif && xPixel < cols - cols_dif && yPixel >= rows_dif && yPixel < rows - rows_dif) {
503 int xPos = xPixel - cols_dif;
504 int yPos = yPixel - rows_dif;
505 if (yPos * camerareoslutionR.x + xPos < distorted_cameradata.size()) {
506 distorted_cameradata.at(yPos * camerareoslutionR.x + xPos) = cameradata.at(j * cameraresolution.x + i);
507 }
508 }
509 }
510 }
511 context->setGlobalData(global_data_label.c_str(), distorted_cameradata);
512 }
513}
514
515void undistortImage(std::vector<std::vector<unsigned char>> &image, std::vector<std::vector<unsigned char>> &undistorted, std::vector<std::vector<double>> &K, std::vector<double> &distCoeffs) {
516 // Image dimensions
517 int rows = image.size();
518 int cols = image[0].size();
519
520 // Compute the undistorted image
521 for (int r = 0; r < rows; r++) {
522 for (int c = 0; c < cols; c++) {
523 // Compute the undistorted pixel coordinates
524 double x = (c - K[0][2]) / K[0][0];
525 double y = (r - K[1][2]) / K[1][1];
526
527 // Apply distortion
528 double r2 = x * x + y * y;
529 double xDistorted = x * (1 + distCoeffs[0] * r2 + distCoeffs[1] * r2 * r2) + 2 * distCoeffs[2] * x * y + distCoeffs[3] * (r2 + 2 * x * x);
530 double yDistorted = y * (1 + distCoeffs[0] * r2 + distCoeffs[1] * r2 * r2) + 2 * distCoeffs[3] * x * y + distCoeffs[2] * (r2 + 2 * y * y);
531
532 // Compute the distorted pixel coordinates
533 int xPixel = round(xDistorted * K[0][0] + K[0][2]);
534 int yPixel = round(yDistorted * K[1][1] + K[1][2]);
535
536 // Set the undistorted pixel value
537 if (xPixel >= 0 && xPixel < cols && yPixel >= 0 && yPixel < rows) {
538 undistorted[r][c] = image[yPixel][xPixel];
539 }
540 }
541 }
542}
543
544static void wavelengthboundary(float &lowwavelength, float &highwavelength, const std::vector<vec2> &spectrum) {
545
546 if (spectrum.back().x < highwavelength) {
547 highwavelength = spectrum.back().x;
548 }
549 if (spectrum.at(0).x > lowwavelength) {
550 lowwavelength = spectrum.at(0).x;
551 }
552}
553
554void CameraCalibration::preprocessSpectra(const std::vector<std::string> &sourcelabels, const std::vector<std::string> &cameralabels, std::vector<std::string> &objectlabels, vec2 &wavelengthrange, const std::string &targetlabel) {
555
556 std::map<std::string, std::map<std::string, std::vector<vec2>>> allspectra;
557
558 // Extract and check source spectra from global data
559 std::map<std::string, std::vector<vec2>> Source_spectra;
560 for (const std::string &sourcelable: sourcelabels) {
561 if (context->doesGlobalDataExist(sourcelable.c_str())) {
562 std::vector<vec2> Source_spectrum;
563 context->getGlobalData(sourcelable.c_str(), Source_spectrum);
564 Source_spectra.emplace(sourcelable, Source_spectrum);
565 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Source_spectrum);
566 } else {
567 std::cout << "WARNING: Source (" << sourcelable << ") does not exist in global data" << std::endl;
568 }
569 }
570 allspectra.emplace("source", Source_spectra);
571
572 // Extract and check camera spectra from global data
573 std::map<std::string, std::vector<vec2>> Camera_spectra;
574 for (const std::string &cameralabel: cameralabels) {
575 if (context->doesGlobalDataExist(cameralabel.c_str())) {
576 std::vector<vec2> Camera_spectrum;
577 context->getGlobalData(cameralabel.c_str(), Camera_spectrum);
578 Camera_spectra.emplace(cameralabel, Camera_spectrum);
579 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Camera_spectrum);
580 } else {
581 std::cout << "WARNING: Camera (" << cameralabel << ") does not exist in global data" << std::endl;
582 }
583 }
584 allspectra.emplace("camera", Camera_spectra);
585
586 // Extract and check object spectra from global data
587 std::map<std::string, std::vector<vec2>> Object_spectra;
588 if (!objectlabels.empty()) {
589 for (const std::string &objectlable: objectlabels) {
590 if (context->doesGlobalDataExist(objectlable.c_str())) {
591 std::vector<vec2> Object_spectrum;
592 context->getGlobalData(objectlable.c_str(), Object_spectrum);
593 Object_spectra.emplace(objectlable, Object_spectrum);
594 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
595 } else {
596 std::cout << "WARNING: Object (" << objectlable << ") does not exist in global data" << std::endl;
597 }
598 }
599 }
600
601 // Check if object spectra in global data has been added to UUIDs but are not in the provided object labels;
602 std::vector<uint> exist_UUIDs = context->getAllUUIDs();
603 for (uint UUID: exist_UUIDs) {
604 if (context->doesPrimitiveDataExist(UUID, "reflectivity_spectrum")) {
605 std::string spectralreflectivitylabel;
606 context->getPrimitiveData(UUID, "reflectivity_spectrum", spectralreflectivitylabel);
607 if (context->doesGlobalDataExist(spectralreflectivitylabel.c_str())) {
608 if (std::find(objectlabels.begin(), objectlabels.end(), spectralreflectivitylabel) == objectlabels.end()) {
609 objectlabels.push_back(spectralreflectivitylabel);
610 std::vector<vec2> Object_spectrum;
611 context->getGlobalData(spectralreflectivitylabel.c_str(), Object_spectrum);
612 Object_spectra.emplace(spectralreflectivitylabel, Object_spectrum);
613 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
614 }
615 }
616 }
617
618 if (context->doesPrimitiveDataExist(UUID, "transmissivity_spectrum")) {
619 std::string spectraltransmissivitylabel;
620 context->getPrimitiveData(UUID, "transmissivity_spectrum", spectraltransmissivitylabel);
621 if (context->doesGlobalDataExist(spectraltransmissivitylabel.c_str())) {
622 if (std::find(objectlabels.begin(), objectlabels.end(), spectraltransmissivitylabel) == objectlabels.end()) {
623 objectlabels.push_back(spectraltransmissivitylabel);
624 std::cout << "WARNING: Spectrum (" << spectraltransmissivitylabel << ") has been added to UUID (" << UUID << ") but is not in the provided object labels" << std::endl;
625 std::vector<vec2> Object_spectrum;
626 context->getGlobalData(spectraltransmissivitylabel.c_str(), Object_spectrum);
627 Object_spectra.emplace(spectraltransmissivitylabel, Object_spectrum);
628 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
629 }
630 }
631 }
632 }
633 allspectra.emplace("object", Object_spectra);
634
635 // interpolate spectra
636 // set unifying wavelength resolution for all spectra according to resolution of target spectrum
637 std::vector<vec2> target_spectrum;
638 if (targetlabel.empty()) {
639 target_spectrum = allspectra.at("object").at(objectlabels.at(0));
640 } else if (std::find(objectlabels.begin(), objectlabels.end(), targetlabel) == objectlabels.end()) {
641 std::cout << "WARNING (CameraCalibration::preprocessSpectra()): target label (" << targetlabel << ") is not a member of object labels" << std::endl;
642 target_spectrum = allspectra.at("object").at(objectlabels.at(0));
643 } else {
644 target_spectrum = allspectra.at("object").at(targetlabel);
645 }
646
647 for (const auto &spectralgrouppair: allspectra) {
648 for (const auto &spectrumpair: spectralgrouppair.second) {
649 std::vector<vec2> cal_spectrum;
650 for (auto ispectralvalue: target_spectrum) {
651 if (ispectralvalue.x > wavelengthrange.y) {
652 context->setGlobalData(spectrumpair.first.c_str(), cal_spectrum);
653 break;
654 }
655 if (ispectralvalue.x >= wavelengthrange.x) {
656 cal_spectrum.push_back(make_vec2(ispectralvalue.x, interp1(spectrumpair.second, ispectralvalue.x)));
657 }
658 }
659 allspectra[spectralgrouppair.first][spectrumpair.first] = cal_spectrum;
660 }
661 }
662 processedspectra = allspectra;
663
664 // store wavelengths into global data
665 std::vector<float> wavelengths;
666 for (auto ispectralvalue: target_spectrum) {
667 if (ispectralvalue.x > wavelengthrange.y || ispectralvalue.x == target_spectrum.back().x) {
668 context->setGlobalData("wavelengths", wavelengths);
669 break;
670 }
671 if (ispectralvalue.x >= wavelengthrange.x) {
672 wavelengths.push_back(ispectralvalue.x);
673 }
674 }
675}
676
677float CameraCalibration::getCameraResponseScale(const std::string &cameralabel, const helios::int2 cameraresolution, const std::vector<std::string> &bandlabels, const std::vector<std::vector<float>> &truevalues) {
678
679 std::vector<uint> camera_UUIDs;
680 std::string global_UUID_label = "camera_" + cameralabel + "_pixel_UUID";
681 context->getGlobalData(global_UUID_label.c_str(), camera_UUIDs);
682
683 // Collect all colorboard UUIDs from all colorboards
684 std::vector<uint> all_colorboard_UUIDs;
685 for (const auto &[type, UUIDs]: UUIDs_colorboards) {
686 all_colorboard_UUIDs.insert(all_colorboard_UUIDs.end(), UUIDs.begin(), UUIDs.end());
687 }
688
689 float dotsimreal = 0;
690 float dotsimsim = 0;
691 for (int ib = 0; ib < bandlabels.size(); ib++) {
692
693 std::vector<float> camera_data;
694 std::string global_data_label = "camera_" + cameralabel + "_" + bandlabels.at(ib);
695 context->getGlobalData(global_data_label.c_str(), camera_data);
696
697 for (int icu = 0; icu < all_colorboard_UUIDs.size(); icu++) {
698
699 float count = 0;
700 float sum = 0;
701 for (uint j = 0; j < cameraresolution.y; j++) {
702 for (uint i = 0; i < cameraresolution.x; i++) {
703 uint UUID = camera_UUIDs.at(j * cameraresolution.x + i) - 1;
704 if (UUID == all_colorboard_UUIDs.at(icu)) {
705 float value = camera_data.at(j * cameraresolution.x + i);
706 sum += value;
707 count += 1;
708 }
709 }
710 }
711
712 float sim_cv = sum / count;
713 float real_cv = truevalues.at(ib).at(icu);
714 dotsimreal += (sim_cv * real_cv);
715 dotsimsim += (sim_cv * sim_cv);
716 }
717 }
718 float fluxscale = dotsimreal / dotsimsim;
719 return fluxscale;
720}
721
723 std::filesystem::path file_path = context->resolveFilePath("plugins/radiation/spectral_data/external_data/HET01_UNI_scene.def");
724 std::ifstream inputFile(file_path);
725 std::vector<uint> iCUUIDs;
726 float idata;
727 // read the data from the file
728 while (!inputFile.eof()) {
729 std::vector<float> poslf;
730 std::vector<float> rotatelf_cos;
731 std::vector<float> rotatelf_sc(2);
732 auto widthlengthlf = float(sqrt(0.01f * M_PI));
733 for (int i = 0; i < 7; i++) {
734 inputFile >> idata;
735
736 if (i > 0 && i < 4) {
737 poslf.push_back(idata);
738 } else if (i > 3) {
739 rotatelf_cos.push_back(idata);
740 }
741 }
742
743 rotatelf_sc.at(0) = float(asin(rotatelf_cos.at(2)));
744 rotatelf_sc.at(1) = float(atan2(rotatelf_cos.at(1), rotatelf_cos.at(0)));
745 if (rotatelf_sc.at(1) < 0) {
746 rotatelf_sc.at(1) += 2 * M_PI;
747 }
748 vec3 iposlf = make_vec3(poslf.at(0), poslf.at(1), poslf.at(2));
749 iCUUIDs.push_back(context->addPatch(iposlf, vec2(widthlengthlf, widthlengthlf), make_SphericalCoord(float(0.5 * M_PI + rotatelf_sc.at(0)), rotatelf_sc.at(1))));
750 }
751 inputFile.close();
752 return iCUUIDs;
753}
754
755// Auto-calibration implementation methods
756
757std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_DGK() const {
758 // DGK-DKK Color Chart Lab values (18 patches)
759 // Data extracted from official DGK documentation
760 std::vector<LabColor> dgk_lab_values = {
761 LabColor(97.0f, 0.0f, 1.0f), // 1: White
762 LabColor(73.0f, 0.0f, 0.0f), // 2: Gray 73
763 LabColor(62.0f, 0.0f, 0.0f), // 3: Gray 62
764 LabColor(50.0f, 0.0f, 0.0f), // 4: Gray 50
765 LabColor(38.0f, 0.0f, 0.0f), // 5: Gray 38
766 LabColor(23.0f, 0.0f, 0.0f), // 6: Black
767 LabColor(48.0f, 59.0f, 39.0f), // 7: Red
768 LabColor(92.0f, 1.0f, 95.0f), // 8: Yellow
769 LabColor(64.0f, -40.0f, 54.0f), // 9: Green
770 LabColor(57.0f, -41.0f, -42.0f), // 10: Cyan
771 LabColor(18.0f, -3.0f, -25.0f), // 11: Blue
772 LabColor(49.0f, 60.0f, -3.0f), // 12: Magenta
773 LabColor(41.0f, 51.0f, 26.0f), // 13: CIE TSC 01
774 LabColor(61.0f, 29.0f, 57.0f), // 14: CIE TSC 02
775 LabColor(52.0f, -24.0f, -24.0f), // 15: CIE TSC 06
776 LabColor(52.0f, 47.0f, -14.0f), // 16: CIE TSC 08
777 LabColor(69.0f, 14.0f, 17.0f), // 17: CIE TSC 09
778 LabColor(64.0f, 12.0f, 17.0f) // 18: CIE TSC 10
779 };
780 return dgk_lab_values;
781}
782
783std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_Calibrite() const {
784 // Calibrite ColorChecker Classic reference Lab values (D65 illuminant, post-November 2014)
785 // Source: X-Rite/Calibrite official data converted from D50 to D65 using Bradford transform
786 return {
787 LabColor(37.54f, 14.37f, 14.92f), // 01 Dark Skin
788 LabColor(62.73f, 35.83f, 56.5f), // 02 Light Skin
789 LabColor(28.37f, 15.42f, -49.8f), // 03 Blue Sky
790 LabColor(34.26f, -32.46f, 47.33f), // 04 Foliage
791 LabColor(49.57f, -29.71f, -28.32f), // 05 Blue Flower
792 LabColor(54.38f, -40.93f, 32.27f), // 06 Bluish Green
793 LabColor(80.83f, 4.39f, 79.25f), // 07 Orange
794 LabColor(40.76f, 10.75f, -45.17f), // 08 Purplish Blue
795 LabColor(44.06f, 60.11f, 33.05f), // 09 Moderate Red
796 LabColor(24.06f, 47.57f, -22.74f), // 10 Purple
797 LabColor(72.57f, -23.5f, 56.8f), // 11 Yellow Green
798 LabColor(71.52f, 18.24f, 67.37f), // 12 Orange Yellow
799 LabColor(28.78f, 28.28f, -50.3f), // 13 Blue
800 LabColor(50.63f, -39.72f, 21.65f), // 14 Green
801 LabColor(42.43f, 51.05f, 28.62f), // 15 Red
802 LabColor(81.8f, 4.04f, 79.82f), // 16 Yellow
803 LabColor(50.57f, 48.64f, -14.12f), // 17 Magenta
804 LabColor(49.32f, -21.18f, -49.94f), // 18 Cyan
805 LabColor(95.19f, -1.03f, 2.93f), // 19 White
806 LabColor(81.29f, -0.57f, 0.44f), // 20 Neutral 8
807 LabColor(66.89f, -0.75f, -0.06f), // 21 Neutral 6.5
808 LabColor(50.76f, -0.13f, -0.16f), // 22 Neutral 5
809 LabColor(35.63f, -0.46f, -0.41f), // 23 Neutral 3.5
810 LabColor(20.64f, 0.07f, -0.46f) // 24 Black
811 };
812}
813
814std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_SpyderCHECKR() const {
815 // Datacolor SpyderCHECKR 24 reference Lab values (approximate, compiled from available sources)
816 // Note: These values may need refinement based on official Datacolor documentation
817 return {
818 LabColor(54.38f, -40.93f, 32.27f), // 01 Bluish Green
819 LabColor(49.57f, -29.71f, -28.32f), // 02 Blue Flower (Lavender)
820 LabColor(34.26f, -32.46f, 47.33f), // 03 Foliage (Olive)
821 LabColor(28.37f, 15.42f, -49.8f), // 04 Blue Sky (Blue Gray)
822 LabColor(62.73f, 35.83f, 56.5f), // 05 Light Skin (Light Tan)
823 LabColor(37.54f, 14.37f, 14.92f), // 06 Dark Skin (Brown)
824 LabColor(80.83f, 4.39f, 79.25f), // 07 Orange
825 LabColor(40.76f, 10.75f, -45.17f), // 08 Purplish Blue (Mid Blue)
826 LabColor(44.06f, 60.11f, 33.05f), // 09 Moderate Red (Light Red)
827 LabColor(24.06f, 47.57f, -22.74f), // 10 Purple (Violet)
828 LabColor(72.57f, -23.5f, 56.8f), // 11 Yellow Green
829 LabColor(71.52f, 18.24f, 67.37f), // 12 Orange Yellow (Light Orange)
830 LabColor(49.32f, -21.18f, -49.94f), // 13 Cyan (Light Blue)
831 LabColor(50.57f, 48.64f, -14.12f), // 14 Magenta
832 LabColor(81.8f, 4.04f, 79.82f), // 15 Yellow
833 LabColor(42.43f, 51.05f, 28.62f), // 16 Red
834 LabColor(50.63f, -39.72f, 21.65f), // 17 Green
835 LabColor(28.78f, 28.28f, -50.3f), // 18 Blue
836 LabColor(95.19f, -1.03f, 2.93f), // 19 White
837 LabColor(81.29f, -0.57f, 0.44f), // 20 Light Gray
838 LabColor(66.89f, -0.75f, -0.06f), // 21 Mid Light Gray
839 LabColor(50.76f, -0.13f, -0.16f), // 22 Mid Dark Gray
840 LabColor(35.63f, -0.46f, -0.41f), // 23 Dark Gray
841 LabColor(20.64f, 0.07f, -0.46f) // 24 Black
842 };
843}
844
845std::vector<std::string> CameraCalibration::detectColorBoardTypes() const {
846 // Check for each colorboard type by looking for primitive data
847 std::vector<uint> all_UUIDs = context->getAllUUIDs();
848 std::vector<std::string> detected_types;
849 bool found_DGK = false, found_Calibrite = false, found_SpyderCHECKR = false;
850
851 for (uint UUID: all_UUIDs) {
852 if (!found_DGK && context->doesPrimitiveDataExist(UUID, "colorboard_DGK")) {
853 detected_types.push_back("DGK");
854 found_DGK = true;
855 }
856 if (!found_Calibrite && context->doesPrimitiveDataExist(UUID, "colorboard_Calibrite")) {
857 detected_types.push_back("Calibrite");
858 found_Calibrite = true;
859 }
860 if (!found_SpyderCHECKR && context->doesPrimitiveDataExist(UUID, "colorboard_SpyderCHECKR")) {
861 detected_types.push_back("SpyderCHECKR");
862 found_SpyderCHECKR = true;
863 }
864 // Early exit if all types have been found
865 if (found_DGK && found_Calibrite && found_SpyderCHECKR) {
866 break;
867 }
868 }
869
870 if (detected_types.empty()) {
871 helios_runtime_error("ERROR (CameraCalibration::detectColorBoardTypes): No colorboard detected in the scene. Make sure to add a colorboard using addDGKColorboard(), addCalibriteColorboard(), or addSpyderCHECKRColorboard().");
872 }
873
874 return detected_types;
875}
876
877std::map<int, std::vector<std::vector<bool>>> CameraCalibration::generateColorBoardSegmentationMasks(const std::string &camera_label, const std::string &colorboard_type) const {
878 std::vector<uint> camera_UUIDs;
879 std::string global_data_label = "camera_" + camera_label + "_pixel_UUID";
880
881 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
882 helios_runtime_error("ERROR (CameraCalibration::generateColorBoardSegmentationMasks): Camera pixel UUID data for camera '" + camera_label + "' does not exist. Make sure the radiation model has been run.");
883 }
884
885 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
886
887 // Get camera resolution from global data
888 std::string resolution_label = "camera_" + camera_label + "_resolution";
889 if (!context->doesGlobalDataExist(resolution_label.c_str())) {
890 helios_runtime_error("ERROR (CameraCalibration::generateColorBoardSegmentationMasks): Camera resolution data for camera '" + camera_label + "' does not exist. Make sure the radiation model has been run.");
891 }
892 helios::int2 camera_resolution;
893 context->getGlobalData(resolution_label.c_str(), camera_resolution);
894
895 std::string colorboard_label = "colorboard_" + colorboard_type;
896 std::map<int, std::vector<std::vector<bool>>> label_masks;
897
898 // First pass: identify all unique labels and create binary masks
899 for (int j = 0; j < camera_resolution.y; j++) {
900 for (int i = 0; i < camera_resolution.x; i++) {
901 uint ii = camera_resolution.x - i - 1;
902 uint pixel_idx = j * camera_resolution.x + ii;
903 if (pixel_idx < camera_UUIDs.size()) {
904 uint UUID = camera_UUIDs.at(pixel_idx) - 1;
905 if (context->doesPrimitiveExist(UUID) && context->doesPrimitiveDataExist(UUID, colorboard_label.c_str())) {
906 uint patch_id;
907 context->getPrimitiveData(UUID, colorboard_label.c_str(), patch_id);
908
909 // Initialize mask for this patch if it doesn't exist
910 if (label_masks.find(patch_id) == label_masks.end()) {
911 label_masks[patch_id] = std::vector<std::vector<bool>>(camera_resolution.y, std::vector<bool>(camera_resolution.x, false));
912 }
913
914 label_masks[patch_id][j][i] = true;
915 }
916 }
917 }
918 }
919
920 return label_masks;
921}
922
923
925 // Convert RGB [0,1] to XYZ using sRGB transformation
926 // First apply gamma correction
927 auto gamma_correct = [](float c) { return (c <= 0.04045f) ? c / 12.92f : powf((c + 0.055f) / 1.055f, 2.4f); };
928
929 float r_linear = gamma_correct(rgb.x);
930 float g_linear = gamma_correct(rgb.y);
931 float b_linear = gamma_correct(rgb.z);
932
933 // sRGB to XYZ transformation matrix (D65 illuminant)
934 float X = 0.4124564f * r_linear + 0.3575761f * g_linear + 0.1804375f * b_linear;
935 float Y = 0.2126729f * r_linear + 0.7151522f * g_linear + 0.0721750f * b_linear;
936 float Z = 0.0193339f * r_linear + 0.1191920f * g_linear + 0.9503041f * b_linear;
937
938 // Reference white point D65
939 const float Xn = 0.95047f;
940 const float Yn = 1.00000f;
941 const float Zn = 1.08883f;
942
943 // Normalize by reference white
944 X /= Xn;
945 Y /= Yn;
946 Z /= Zn;
947
948 // Convert to Lab
949 auto f_transform = [](float t) { return (t > 0.008856f) ? cbrtf(t) : (7.787f * t + 16.0f / 116.0f); };
950
951 float fx = f_transform(X);
952 float fy = f_transform(Y);
953 float fz = f_transform(Z);
954
955 float L = 116.0f * fy - 16.0f;
956 float a = 500.0f * (fx - fy);
957 float b = 200.0f * (fy - fz);
958
959 return LabColor(L, a, b);
960}
961
963 // Convert Lab to XYZ
964 float fy = (lab.L + 16.0f) / 116.0f;
965 float fx = lab.a / 500.0f + fy;
966 float fz = fy - lab.b / 200.0f;
967
968 // Reverse f transform
969 auto f_reverse = [](float t) {
970 float t3 = t * t * t;
971 return (t3 > 0.008856f) ? t3 : (t - 16.0f / 116.0f) / 7.787f;
972 };
973
974 float X = f_reverse(fx);
975 float Y = f_reverse(fy);
976 float Z = f_reverse(fz);
977
978 // Reference white point D65
979 const float Xn = 0.95047f;
980 const float Yn = 1.00000f;
981 const float Zn = 1.08883f;
982
983 // Scale by reference white
984 X *= Xn;
985 Y *= Yn;
986 Z *= Zn;
987
988 // XYZ to sRGB transformation matrix
989 float r_linear = 3.2404542f * X - 1.5371385f * Y - 0.4985314f * Z;
990 float g_linear = -0.9692660f * X + 1.8760108f * Y + 0.0415560f * Z;
991 float b_linear = 0.0556434f * X - 0.2040259f * Y + 1.0572252f * Z;
992
993 // Apply gamma correction
994 auto gamma_uncorrect = [](float c) { return (c <= 0.0031308f) ? 12.92f * c : 1.055f * powf(c, 1.0f / 2.4f) - 0.055f; };
995
996 float r = gamma_uncorrect(r_linear);
997 float g = gamma_uncorrect(g_linear);
998 float b = gamma_uncorrect(b_linear);
999
1000 // Clamp to [0, 1]
1001 r = std::max(0.0f, std::min(1.0f, r));
1002 g = std::max(0.0f, std::min(1.0f, g));
1003 b = std::max(0.0f, std::min(1.0f, b));
1004
1005 return helios::make_vec3(r, g, b);
1006}
1007
1008double CameraCalibration::deltaE76(const LabColor &lab1, const LabColor &lab2) const {
1009 // CIE76 Delta E formula: simple Euclidean distance in Lab space
1010 double delta_L = lab1.L - lab2.L;
1011 double delta_a = lab1.a - lab2.a;
1012 double delta_b = lab1.b - lab2.b;
1013
1014 return sqrt(delta_L * delta_L + delta_a * delta_a + delta_b * delta_b);
1015}
1016
1017double CameraCalibration::deltaE2000(const LabColor &lab1, const LabColor &lab2) const {
1018 // Implementation of CIE Delta E 2000 formula
1019 // More perceptually accurate than CIE76
1020
1021 const double kL = 1.0; // Lightness weighting factor
1022 const double kC = 1.0; // Chroma weighting factor
1023 const double kH = 1.0; // Hue weighting factor
1024
1025 double L1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
1026 double L2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
1027
1028 // Calculate C and h for each color
1029 double C1 = sqrt(a1 * a1 + b1 * b1);
1030 double C2 = sqrt(a2 * a2 + b2 * b2);
1031 double C_bar = (C1 + C2) / 2.0;
1032
1033 // G factor for a' calculation
1034 double G = 0.5 * (1.0 - sqrt(pow(C_bar, 7) / (pow(C_bar, 7) + pow(25.0, 7))));
1035
1036 // Calculate a' values
1037 double a1_prime = a1 * (1.0 + G);
1038 double a2_prime = a2 * (1.0 + G);
1039
1040 // Calculate C' values
1041 double C1_prime = sqrt(a1_prime * a1_prime + b1 * b1);
1042 double C2_prime = sqrt(a2_prime * a2_prime + b2 * b2);
1043
1044 // Calculate h' values (hue angles in degrees)
1045 double h1_prime = 0, h2_prime = 0;
1046 if (C1_prime > 0) {
1047 h1_prime = atan2(b1, a1_prime) * 180.0 / M_PI;
1048 if (h1_prime < 0)
1049 h1_prime += 360.0;
1050 }
1051 if (C2_prime > 0) {
1052 h2_prime = atan2(b2, a2_prime) * 180.0 / M_PI;
1053 if (h2_prime < 0)
1054 h2_prime += 360.0;
1055 }
1056
1057 // Calculate differences
1058 double delta_L_prime = L2 - L1;
1059 double delta_C_prime = C2_prime - C1_prime;
1060
1061 double delta_h_prime = 0;
1062 if (C1_prime * C2_prime > 0) {
1063 double diff = h2_prime - h1_prime;
1064 if (abs(diff) <= 180) {
1065 delta_h_prime = diff;
1066 } else if (diff > 180) {
1067 delta_h_prime = diff - 360;
1068 } else {
1069 delta_h_prime = diff + 360;
1070 }
1071 }
1072
1073 double delta_H_prime = 2.0 * sqrt(C1_prime * C2_prime) * sin(delta_h_prime * M_PI / 360.0);
1074
1075 // Calculate averages
1076 double L_bar_prime = (L1 + L2) / 2.0;
1077 double C_bar_prime = (C1_prime + C2_prime) / 2.0;
1078
1079 double h_bar_prime = 0;
1080 if (C1_prime * C2_prime > 0) {
1081 double sum = h1_prime + h2_prime;
1082 double diff = abs(h1_prime - h2_prime);
1083 if (diff <= 180) {
1084 h_bar_prime = sum / 2.0;
1085 } else if (sum < 360) {
1086 h_bar_prime = (sum + 360) / 2.0;
1087 } else {
1088 h_bar_prime = (sum - 360) / 2.0;
1089 }
1090 }
1091
1092 // Calculate T
1093 double T = 1.0 - 0.17 * cos((h_bar_prime - 30) * M_PI / 180.0) + 0.24 * cos(2 * h_bar_prime * M_PI / 180.0) + 0.32 * cos((3 * h_bar_prime + 6) * M_PI / 180.0) - 0.20 * cos((4 * h_bar_prime - 63) * M_PI / 180.0);
1094
1095 // Calculate delta_theta
1096 double delta_theta = 30.0 * exp(-pow((h_bar_prime - 275) / 25.0, 2));
1097
1098 // Calculate RC
1099 double RC = 2.0 * sqrt(pow(C_bar_prime, 7) / (pow(C_bar_prime, 7) + pow(25.0, 7)));
1100
1101 // Calculate SL, SC, SH
1102 double SL = 1.0 + (0.015 * pow(L_bar_prime - 50, 2)) / sqrt(20 + pow(L_bar_prime - 50, 2));
1103 double SC = 1.0 + 0.045 * C_bar_prime;
1104 double SH = 1.0 + 0.015 * C_bar_prime * T;
1105
1106 // Calculate RT
1107 double RT = -sin(2 * delta_theta * M_PI / 180.0) * RC;
1108
1109 // Calculate Delta E 2000
1110 double term1 = delta_L_prime / (kL * SL);
1111 double term2 = delta_C_prime / (kC * SC);
1112 double term3 = delta_H_prime / (kH * SH);
1113 double term4 = RT * term2 * term3;
1114
1115 return sqrt(term1 * term1 + term2 * term2 + term3 * term3 + term4);
1116}