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