1.3.77
 
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
559
560 // Extract and check source spectra from global data
561 std::map<std::string, std::vector<vec2>> Source_spectra;
562 for (const std::string &sourcelable: sourcelabels) {
563 if (context->doesGlobalDataExist(sourcelable.c_str())) {
564 std::vector<vec2> Source_spectrum;
565 context->getGlobalData(sourcelable.c_str(), Source_spectrum);
566 Source_spectra.emplace(sourcelable, Source_spectrum);
567 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Source_spectrum);
568 } else {
569 warnings.addWarning("missing_source_spectrum", "Source (" + sourcelable + ") does not exist in global data");
570 }
571 }
572 allspectra.emplace("source", Source_spectra);
573
574 // Extract and check camera spectra from global data
575 std::map<std::string, std::vector<vec2>> Camera_spectra;
576 for (const std::string &cameralabel: cameralabels) {
577 if (context->doesGlobalDataExist(cameralabel.c_str())) {
578 std::vector<vec2> Camera_spectrum;
579 context->getGlobalData(cameralabel.c_str(), Camera_spectrum);
580 Camera_spectra.emplace(cameralabel, Camera_spectrum);
581 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Camera_spectrum);
582 } else {
583 warnings.addWarning("missing_camera_spectrum", "Camera (" + cameralabel + ") does not exist in global data");
584 }
585 }
586 allspectra.emplace("camera", Camera_spectra);
587
588 // Extract and check object spectra from global data
589 std::map<std::string, std::vector<vec2>> Object_spectra;
590 if (!objectlabels.empty()) {
591 for (const std::string &objectlable: objectlabels) {
592 if (context->doesGlobalDataExist(objectlable.c_str())) {
593 std::vector<vec2> Object_spectrum;
594 context->getGlobalData(objectlable.c_str(), Object_spectrum);
595 Object_spectra.emplace(objectlable, Object_spectrum);
596 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
597 } else {
598 warnings.addWarning("missing_object_spectrum", "Object (" + objectlable + ") does not exist in global data");
599 }
600 }
601 }
602
603 // Check if object spectra in global data has been added to UUIDs but are not in the provided object labels;
604 std::vector<uint> exist_UUIDs = context->getAllUUIDs();
605 for (uint UUID: exist_UUIDs) {
606 if (context->doesPrimitiveDataExist(UUID, "reflectivity_spectrum")) {
607 std::string spectralreflectivitylabel;
608 context->getPrimitiveData(UUID, "reflectivity_spectrum", spectralreflectivitylabel);
609 if (context->doesGlobalDataExist(spectralreflectivitylabel.c_str())) {
610 if (std::find(objectlabels.begin(), objectlabels.end(), spectralreflectivitylabel) == objectlabels.end()) {
611 objectlabels.push_back(spectralreflectivitylabel);
612 std::vector<vec2> Object_spectrum;
613 context->getGlobalData(spectralreflectivitylabel.c_str(), Object_spectrum);
614 Object_spectra.emplace(spectralreflectivitylabel, Object_spectrum);
615 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
616 }
617 }
618 }
619
620 if (context->doesPrimitiveDataExist(UUID, "transmissivity_spectrum")) {
621 std::string spectraltransmissivitylabel;
622 context->getPrimitiveData(UUID, "transmissivity_spectrum", spectraltransmissivitylabel);
623 if (context->doesGlobalDataExist(spectraltransmissivitylabel.c_str())) {
624 if (std::find(objectlabels.begin(), objectlabels.end(), spectraltransmissivitylabel) == objectlabels.end()) {
625 objectlabels.push_back(spectraltransmissivitylabel);
626 warnings.addWarning("spectrum_not_in_object_labels", "Spectrum (" + spectraltransmissivitylabel + ") has been added to UUID (" + std::to_string(UUID) + ") but is not in the provided object labels");
627 std::vector<vec2> Object_spectrum;
628 context->getGlobalData(spectraltransmissivitylabel.c_str(), Object_spectrum);
629 Object_spectra.emplace(spectraltransmissivitylabel, Object_spectrum);
630 wavelengthboundary(wavelengthrange.x, wavelengthrange.y, Object_spectrum);
631 }
632 }
633 }
634 }
635 allspectra.emplace("object", Object_spectra);
636
637 warnings.report(std::cerr);
638
639 // interpolate spectra
640 // set unifying wavelength resolution for all spectra according to resolution of target spectrum
641 std::vector<vec2> target_spectrum;
642 if (targetlabel.empty()) {
643 target_spectrum = allspectra.at("object").at(objectlabels.at(0));
644 } else if (std::find(objectlabels.begin(), objectlabels.end(), targetlabel) == objectlabels.end()) {
645 std::cout << "WARNING (CameraCalibration::preprocessSpectra()): target label (" << targetlabel << ") is not a member of object labels" << std::endl;
646 target_spectrum = allspectra.at("object").at(objectlabels.at(0));
647 } else {
648 target_spectrum = allspectra.at("object").at(targetlabel);
649 }
650
651 for (const auto &spectralgrouppair: allspectra) {
652 for (const auto &spectrumpair: spectralgrouppair.second) {
653 std::vector<vec2> cal_spectrum;
654 for (auto ispectralvalue: target_spectrum) {
655 if (ispectralvalue.x > wavelengthrange.y) {
656 context->setGlobalData(spectrumpair.first.c_str(), cal_spectrum);
657 break;
658 }
659 if (ispectralvalue.x >= wavelengthrange.x) {
660 cal_spectrum.push_back(make_vec2(ispectralvalue.x, interp1(spectrumpair.second, ispectralvalue.x)));
661 }
662 }
663 allspectra[spectralgrouppair.first][spectrumpair.first] = cal_spectrum;
664 }
665 }
666 processedspectra = allspectra;
667
668 // store wavelengths into global data
669 std::vector<float> wavelengths;
670 for (auto ispectralvalue: target_spectrum) {
671 if (ispectralvalue.x > wavelengthrange.y || ispectralvalue.x == target_spectrum.back().x) {
672 context->setGlobalData("wavelengths", wavelengths);
673 break;
674 }
675 if (ispectralvalue.x >= wavelengthrange.x) {
676 wavelengths.push_back(ispectralvalue.x);
677 }
678 }
679}
680
681float CameraCalibration::getCameraResponseScale(const std::string &cameralabel, const helios::int2 cameraresolution, const std::vector<std::string> &bandlabels, const std::vector<std::vector<float>> &truevalues) {
682
683 std::vector<uint> camera_UUIDs;
684 std::string global_UUID_label = "camera_" + cameralabel + "_pixel_UUID";
685 context->getGlobalData(global_UUID_label.c_str(), camera_UUIDs);
686
687 // Collect all colorboard UUIDs from all colorboards
688 std::vector<uint> all_colorboard_UUIDs;
689 for (const auto &[type, UUIDs]: UUIDs_colorboards) {
690 all_colorboard_UUIDs.insert(all_colorboard_UUIDs.end(), UUIDs.begin(), UUIDs.end());
691 }
692
693 float dotsimreal = 0;
694 float dotsimsim = 0;
695 for (int ib = 0; ib < bandlabels.size(); ib++) {
696
697 std::vector<float> camera_data;
698 std::string global_data_label = "camera_" + cameralabel + "_" + bandlabels.at(ib);
699 context->getGlobalData(global_data_label.c_str(), camera_data);
700
701 for (int icu = 0; icu < all_colorboard_UUIDs.size(); icu++) {
702
703 float count = 0;
704 float sum = 0;
705 for (uint j = 0; j < cameraresolution.y; j++) {
706 for (uint i = 0; i < cameraresolution.x; i++) {
707 uint UUID = camera_UUIDs.at(j * cameraresolution.x + i) - 1;
708 if (UUID == all_colorboard_UUIDs.at(icu)) {
709 float value = camera_data.at(j * cameraresolution.x + i);
710 sum += value;
711 count += 1;
712 }
713 }
714 }
715
716 float sim_cv = sum / count;
717 float real_cv = truevalues.at(ib).at(icu);
718 dotsimreal += (sim_cv * real_cv);
719 dotsimsim += (sim_cv * sim_cv);
720 }
721 }
722 float fluxscale = dotsimreal / dotsimsim;
723 return fluxscale;
724}
725
727 std::filesystem::path file_path = context->resolveFilePath("plugins/radiation/spectral_data/external_data/HET01_UNI_scene.def");
728 std::ifstream inputFile(file_path);
729 std::vector<uint> iCUUIDs;
730 float idata;
731 // read the data from the file
732 while (!inputFile.eof()) {
733 std::vector<float> poslf;
734 std::vector<float> rotatelf_cos;
735 std::vector<float> rotatelf_sc(2);
736 auto widthlengthlf = float(sqrt(0.01f * M_PI));
737 for (int i = 0; i < 7; i++) {
738 inputFile >> idata;
739
740 if (i > 0 && i < 4) {
741 poslf.push_back(idata);
742 } else if (i > 3) {
743 rotatelf_cos.push_back(idata);
744 }
745 }
746
747 rotatelf_sc.at(0) = float(asin(rotatelf_cos.at(2)));
748 rotatelf_sc.at(1) = float(atan2(rotatelf_cos.at(1), rotatelf_cos.at(0)));
749 if (rotatelf_sc.at(1) < 0) {
750 rotatelf_sc.at(1) += 2 * M_PI;
751 }
752 vec3 iposlf = make_vec3(poslf.at(0), poslf.at(1), poslf.at(2));
753 iCUUIDs.push_back(context->addPatch(iposlf, vec2(widthlengthlf, widthlengthlf), make_SphericalCoord(float(0.5 * M_PI + rotatelf_sc.at(0)), rotatelf_sc.at(1))));
754 }
755 inputFile.close();
756 return iCUUIDs;
757}
758
759// Auto-calibration implementation methods
760
761std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_DGK() const {
762 // DGK-DKK Color Chart Lab values (18 patches)
763 // Data extracted from official DGK documentation
764 std::vector<LabColor> dgk_lab_values = {
765 LabColor(97.0f, 0.0f, 1.0f), // 1: White
766 LabColor(73.0f, 0.0f, 0.0f), // 2: Gray 73
767 LabColor(62.0f, 0.0f, 0.0f), // 3: Gray 62
768 LabColor(50.0f, 0.0f, 0.0f), // 4: Gray 50
769 LabColor(38.0f, 0.0f, 0.0f), // 5: Gray 38
770 LabColor(23.0f, 0.0f, 0.0f), // 6: Black
771 LabColor(48.0f, 59.0f, 39.0f), // 7: Red
772 LabColor(92.0f, 1.0f, 95.0f), // 8: Yellow
773 LabColor(64.0f, -40.0f, 54.0f), // 9: Green
774 LabColor(57.0f, -41.0f, -42.0f), // 10: Cyan
775 LabColor(18.0f, -3.0f, -25.0f), // 11: Blue
776 LabColor(49.0f, 60.0f, -3.0f), // 12: Magenta
777 LabColor(41.0f, 51.0f, 26.0f), // 13: CIE TSC 01
778 LabColor(61.0f, 29.0f, 57.0f), // 14: CIE TSC 02
779 LabColor(52.0f, -24.0f, -24.0f), // 15: CIE TSC 06
780 LabColor(52.0f, 47.0f, -14.0f), // 16: CIE TSC 08
781 LabColor(69.0f, 14.0f, 17.0f), // 17: CIE TSC 09
782 LabColor(64.0f, 12.0f, 17.0f) // 18: CIE TSC 10
783 };
784 return dgk_lab_values;
785}
786
787std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_Calibrite() const {
788 // Calibrite ColorChecker Classic reference Lab values (D65 illuminant, post-November 2014)
789 // Source: X-Rite/Calibrite official data converted from D50 to D65 using Bradford transform
790 return {
791 LabColor(37.54f, 14.37f, 14.92f), // 01 Dark Skin
792 LabColor(62.73f, 35.83f, 56.5f), // 02 Light Skin
793 LabColor(28.37f, 15.42f, -49.8f), // 03 Blue Sky
794 LabColor(34.26f, -32.46f, 47.33f), // 04 Foliage
795 LabColor(49.57f, -29.71f, -28.32f), // 05 Blue Flower
796 LabColor(54.38f, -40.93f, 32.27f), // 06 Bluish Green
797 LabColor(80.83f, 4.39f, 79.25f), // 07 Orange
798 LabColor(40.76f, 10.75f, -45.17f), // 08 Purplish Blue
799 LabColor(44.06f, 60.11f, 33.05f), // 09 Moderate Red
800 LabColor(24.06f, 47.57f, -22.74f), // 10 Purple
801 LabColor(72.57f, -23.5f, 56.8f), // 11 Yellow Green
802 LabColor(71.52f, 18.24f, 67.37f), // 12 Orange Yellow
803 LabColor(28.78f, 28.28f, -50.3f), // 13 Blue
804 LabColor(50.63f, -39.72f, 21.65f), // 14 Green
805 LabColor(42.43f, 51.05f, 28.62f), // 15 Red
806 LabColor(81.8f, 4.04f, 79.82f), // 16 Yellow
807 LabColor(50.57f, 48.64f, -14.12f), // 17 Magenta
808 LabColor(49.32f, -21.18f, -49.94f), // 18 Cyan
809 LabColor(95.19f, -1.03f, 2.93f), // 19 White
810 LabColor(81.29f, -0.57f, 0.44f), // 20 Neutral 8
811 LabColor(66.89f, -0.75f, -0.06f), // 21 Neutral 6.5
812 LabColor(50.76f, -0.13f, -0.16f), // 22 Neutral 5
813 LabColor(35.63f, -0.46f, -0.41f), // 23 Neutral 3.5
814 LabColor(20.64f, 0.07f, -0.46f) // 24 Black
815 };
816}
817
818std::vector<CameraCalibration::LabColor> CameraCalibration::getReferenceLab_SpyderCHECKR() const {
819 // Datacolor SpyderCHECKR 24 reference Lab values (approximate, compiled from available sources)
820 // Note: These values may need refinement based on official Datacolor documentation
821 return {
822 LabColor(54.38f, -40.93f, 32.27f), // 01 Bluish Green
823 LabColor(49.57f, -29.71f, -28.32f), // 02 Blue Flower (Lavender)
824 LabColor(34.26f, -32.46f, 47.33f), // 03 Foliage (Olive)
825 LabColor(28.37f, 15.42f, -49.8f), // 04 Blue Sky (Blue Gray)
826 LabColor(62.73f, 35.83f, 56.5f), // 05 Light Skin (Light Tan)
827 LabColor(37.54f, 14.37f, 14.92f), // 06 Dark Skin (Brown)
828 LabColor(80.83f, 4.39f, 79.25f), // 07 Orange
829 LabColor(40.76f, 10.75f, -45.17f), // 08 Purplish Blue (Mid Blue)
830 LabColor(44.06f, 60.11f, 33.05f), // 09 Moderate Red (Light Red)
831 LabColor(24.06f, 47.57f, -22.74f), // 10 Purple (Violet)
832 LabColor(72.57f, -23.5f, 56.8f), // 11 Yellow Green
833 LabColor(71.52f, 18.24f, 67.37f), // 12 Orange Yellow (Light Orange)
834 LabColor(49.32f, -21.18f, -49.94f), // 13 Cyan (Light Blue)
835 LabColor(50.57f, 48.64f, -14.12f), // 14 Magenta
836 LabColor(81.8f, 4.04f, 79.82f), // 15 Yellow
837 LabColor(42.43f, 51.05f, 28.62f), // 16 Red
838 LabColor(50.63f, -39.72f, 21.65f), // 17 Green
839 LabColor(28.78f, 28.28f, -50.3f), // 18 Blue
840 LabColor(95.19f, -1.03f, 2.93f), // 19 White
841 LabColor(81.29f, -0.57f, 0.44f), // 20 Light Gray
842 LabColor(66.89f, -0.75f, -0.06f), // 21 Mid Light Gray
843 LabColor(50.76f, -0.13f, -0.16f), // 22 Mid Dark Gray
844 LabColor(35.63f, -0.46f, -0.41f), // 23 Dark Gray
845 LabColor(20.64f, 0.07f, -0.46f) // 24 Black
846 };
847}
848
849std::vector<std::string> CameraCalibration::detectColorBoardTypes() const {
850 // Check for each colorboard type by looking for primitive data
851 std::vector<uint> all_UUIDs = context->getAllUUIDs();
852 std::vector<std::string> detected_types;
853 bool found_DGK = false, found_Calibrite = false, found_SpyderCHECKR = false;
854
855 for (uint UUID: all_UUIDs) {
856 if (!found_DGK && context->doesPrimitiveDataExist(UUID, "colorboard_DGK")) {
857 detected_types.push_back("DGK");
858 found_DGK = true;
859 }
860 if (!found_Calibrite && context->doesPrimitiveDataExist(UUID, "colorboard_Calibrite")) {
861 detected_types.push_back("Calibrite");
862 found_Calibrite = true;
863 }
864 if (!found_SpyderCHECKR && context->doesPrimitiveDataExist(UUID, "colorboard_SpyderCHECKR")) {
865 detected_types.push_back("SpyderCHECKR");
866 found_SpyderCHECKR = true;
867 }
868 // Early exit if all types have been found
869 if (found_DGK && found_Calibrite && found_SpyderCHECKR) {
870 break;
871 }
872 }
873
874 if (detected_types.empty()) {
875 helios_runtime_error("ERROR (CameraCalibration::detectColorBoardTypes): No colorboard detected in the scene. Make sure to add a colorboard using addDGKColorboard(), addCalibriteColorboard(), or addSpyderCHECKRColorboard().");
876 }
877
878 return detected_types;
879}
880
881std::map<int, std::vector<std::vector<bool>>> CameraCalibration::generateColorBoardSegmentationMasks(const std::string &camera_label, const std::string &colorboard_type) const {
882 std::vector<uint> camera_UUIDs;
883 std::string global_data_label = "camera_" + camera_label + "_pixel_UUID";
884
885 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
886 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.");
887 }
888
889 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
890
891 // Get camera resolution from global data
892 std::string resolution_label = "camera_" + camera_label + "_resolution";
893 if (!context->doesGlobalDataExist(resolution_label.c_str())) {
894 helios_runtime_error("ERROR (CameraCalibration::generateColorBoardSegmentationMasks): Camera resolution data for camera '" + camera_label + "' does not exist. Make sure the radiation model has been run.");
895 }
896 helios::int2 camera_resolution;
897 context->getGlobalData(resolution_label.c_str(), camera_resolution);
898
899 std::string colorboard_label = "colorboard_" + colorboard_type;
900 std::map<int, std::vector<std::vector<bool>>> label_masks;
901
902 // First pass: identify all unique labels and create binary masks
903 for (int j = 0; j < camera_resolution.y; j++) {
904 for (int i = 0; i < camera_resolution.x; i++) {
905 uint ii = camera_resolution.x - i - 1;
906 uint pixel_idx = j * camera_resolution.x + ii;
907 if (pixel_idx < camera_UUIDs.size()) {
908 uint UUID = camera_UUIDs.at(pixel_idx) - 1;
909 if (context->doesPrimitiveExist(UUID) && context->doesPrimitiveDataExist(UUID, colorboard_label.c_str())) {
910 uint patch_id;
911 context->getPrimitiveData(UUID, colorboard_label.c_str(), patch_id);
912
913 // Initialize mask for this patch if it doesn't exist
914 if (label_masks.find(patch_id) == label_masks.end()) {
915 label_masks[patch_id] = std::vector<std::vector<bool>>(camera_resolution.y, std::vector<bool>(camera_resolution.x, false));
916 }
917
918 label_masks[patch_id][j][i] = true;
919 }
920 }
921 }
922 }
923
924 return label_masks;
925}
926
927
929 // Convert RGB [0,1] to XYZ using sRGB transformation
930 // First apply gamma correction
931 auto gamma_correct = [](float c) { return (c <= 0.04045f) ? c / 12.92f : powf((c + 0.055f) / 1.055f, 2.4f); };
932
933 float r_linear = gamma_correct(rgb.x);
934 float g_linear = gamma_correct(rgb.y);
935 float b_linear = gamma_correct(rgb.z);
936
937 // sRGB to XYZ transformation matrix (D65 illuminant)
938 float X = 0.4124564f * r_linear + 0.3575761f * g_linear + 0.1804375f * b_linear;
939 float Y = 0.2126729f * r_linear + 0.7151522f * g_linear + 0.0721750f * b_linear;
940 float Z = 0.0193339f * r_linear + 0.1191920f * g_linear + 0.9503041f * b_linear;
941
942 // Reference white point D65
943 const float Xn = 0.95047f;
944 const float Yn = 1.00000f;
945 const float Zn = 1.08883f;
946
947 // Normalize by reference white
948 X /= Xn;
949 Y /= Yn;
950 Z /= Zn;
951
952 // Convert to Lab
953 auto f_transform = [](float t) { return (t > 0.008856f) ? cbrtf(t) : (7.787f * t + 16.0f / 116.0f); };
954
955 float fx = f_transform(X);
956 float fy = f_transform(Y);
957 float fz = f_transform(Z);
958
959 float L = 116.0f * fy - 16.0f;
960 float a = 500.0f * (fx - fy);
961 float b = 200.0f * (fy - fz);
962
963 return LabColor(L, a, b);
964}
965
967 // Convert Lab to XYZ
968 float fy = (lab.L + 16.0f) / 116.0f;
969 float fx = lab.a / 500.0f + fy;
970 float fz = fy - lab.b / 200.0f;
971
972 // Reverse f transform
973 auto f_reverse = [](float t) {
974 float t3 = t * t * t;
975 return (t3 > 0.008856f) ? t3 : (t - 16.0f / 116.0f) / 7.787f;
976 };
977
978 float X = f_reverse(fx);
979 float Y = f_reverse(fy);
980 float Z = f_reverse(fz);
981
982 // Reference white point D65
983 const float Xn = 0.95047f;
984 const float Yn = 1.00000f;
985 const float Zn = 1.08883f;
986
987 // Scale by reference white
988 X *= Xn;
989 Y *= Yn;
990 Z *= Zn;
991
992 // XYZ to sRGB transformation matrix
993 float r_linear = 3.2404542f * X - 1.5371385f * Y - 0.4985314f * Z;
994 float g_linear = -0.9692660f * X + 1.8760108f * Y + 0.0415560f * Z;
995 float b_linear = 0.0556434f * X - 0.2040259f * Y + 1.0572252f * Z;
996
997 // Apply gamma correction
998 auto gamma_uncorrect = [](float c) { return (c <= 0.0031308f) ? 12.92f * c : 1.055f * powf(c, 1.0f / 2.4f) - 0.055f; };
999
1000 float r = gamma_uncorrect(r_linear);
1001 float g = gamma_uncorrect(g_linear);
1002 float b = gamma_uncorrect(b_linear);
1003
1004 // Clamp to [0, 1]
1005 r = std::max(0.0f, std::min(1.0f, r));
1006 g = std::max(0.0f, std::min(1.0f, g));
1007 b = std::max(0.0f, std::min(1.0f, b));
1008
1009 return helios::make_vec3(r, g, b);
1010}
1011
1012double CameraCalibration::deltaE76(const LabColor &lab1, const LabColor &lab2) const {
1013 // CIE76 Delta E formula: simple Euclidean distance in Lab space
1014 double delta_L = lab1.L - lab2.L;
1015 double delta_a = lab1.a - lab2.a;
1016 double delta_b = lab1.b - lab2.b;
1017
1018 return sqrt(delta_L * delta_L + delta_a * delta_a + delta_b * delta_b);
1019}
1020
1021double CameraCalibration::deltaE2000(const LabColor &lab1, const LabColor &lab2) const {
1022 // Implementation of CIE Delta E 2000 formula
1023 // More perceptually accurate than CIE76
1024
1025 const double kL = 1.0; // Lightness weighting factor
1026 const double kC = 1.0; // Chroma weighting factor
1027 const double kH = 1.0; // Hue weighting factor
1028
1029 double L1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
1030 double L2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
1031
1032 // Calculate C and h for each color
1033 double C1 = sqrt(a1 * a1 + b1 * b1);
1034 double C2 = sqrt(a2 * a2 + b2 * b2);
1035 double C_bar = (C1 + C2) / 2.0;
1036
1037 // G factor for a' calculation
1038 double G = 0.5 * (1.0 - sqrt(pow(C_bar, 7) / (pow(C_bar, 7) + pow(25.0, 7))));
1039
1040 // Calculate a' values
1041 double a1_prime = a1 * (1.0 + G);
1042 double a2_prime = a2 * (1.0 + G);
1043
1044 // Calculate C' values
1045 double C1_prime = sqrt(a1_prime * a1_prime + b1 * b1);
1046 double C2_prime = sqrt(a2_prime * a2_prime + b2 * b2);
1047
1048 // Calculate h' values (hue angles in degrees)
1049 double h1_prime = 0, h2_prime = 0;
1050 if (C1_prime > 0) {
1051 h1_prime = atan2(b1, a1_prime) * 180.0 / M_PI;
1052 if (h1_prime < 0)
1053 h1_prime += 360.0;
1054 }
1055 if (C2_prime > 0) {
1056 h2_prime = atan2(b2, a2_prime) * 180.0 / M_PI;
1057 if (h2_prime < 0)
1058 h2_prime += 360.0;
1059 }
1060
1061 // Calculate differences
1062 double delta_L_prime = L2 - L1;
1063 double delta_C_prime = C2_prime - C1_prime;
1064
1065 double delta_h_prime = 0;
1066 if (C1_prime * C2_prime > 0) {
1067 double diff = h2_prime - h1_prime;
1068 if (abs(diff) <= 180) {
1069 delta_h_prime = diff;
1070 } else if (diff > 180) {
1071 delta_h_prime = diff - 360;
1072 } else {
1073 delta_h_prime = diff + 360;
1074 }
1075 }
1076
1077 double delta_H_prime = 2.0 * sqrt(C1_prime * C2_prime) * sin(delta_h_prime * M_PI / 360.0);
1078
1079 // Calculate averages
1080 double L_bar_prime = (L1 + L2) / 2.0;
1081 double C_bar_prime = (C1_prime + C2_prime) / 2.0;
1082
1083 double h_bar_prime = 0;
1084 if (C1_prime * C2_prime > 0) {
1085 double sum = h1_prime + h2_prime;
1086 double diff = abs(h1_prime - h2_prime);
1087 if (diff <= 180) {
1088 h_bar_prime = sum / 2.0;
1089 } else if (sum < 360) {
1090 h_bar_prime = (sum + 360) / 2.0;
1091 } else {
1092 h_bar_prime = (sum - 360) / 2.0;
1093 }
1094 }
1095
1096 // Calculate T
1097 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);
1098
1099 // Calculate delta_theta
1100 double delta_theta = 30.0 * exp(-pow((h_bar_prime - 275) / 25.0, 2));
1101
1102 // Calculate RC
1103 double RC = 2.0 * sqrt(pow(C_bar_prime, 7) / (pow(C_bar_prime, 7) + pow(25.0, 7)));
1104
1105 // Calculate SL, SC, SH
1106 double SL = 1.0 + (0.015 * pow(L_bar_prime - 50, 2)) / sqrt(20 + pow(L_bar_prime - 50, 2));
1107 double SC = 1.0 + 0.045 * C_bar_prime;
1108 double SH = 1.0 + 0.015 * C_bar_prime * T;
1109
1110 // Calculate RT
1111 double RT = -sin(2 * delta_theta * M_PI / 180.0) * RC;
1112
1113 // Calculate Delta E 2000
1114 double term1 = delta_L_prime / (kL * SL);
1115 double term2 = delta_C_prime / (kC * SC);
1116 double term3 = delta_H_prime / (kH * SH);
1117 double term4 = RT * term2 * term3;
1118
1119 return sqrt(term1 * term1 + term2 * term2 + term3 * term3 + term4);
1120}