1.3.49
 
Loading...
Searching...
No Matches
RadiationCamera.cpp
Go to the documentation of this file.
1
17#include "RadiationModel.h"
18
19#include <queue>
20#include <set>
21#include <stack>
22
23#include "global.h"
24
25using namespace helios;
26
27void RadiationModel::addRadiationCamera(const std::string &camera_label, const std::vector<std::string> &band_label, const helios::vec3 &position, const helios::vec3 &lookat, const CameraProperties &camera_properties, uint antialiasing_samples) {
28
29 if (camera_properties.FOV_aspect_ratio <= 0) {
30 helios_runtime_error("ERROR (RadiationModel::addRadiationCamera): Field of view aspect ratio must be greater than 0.");
31 } else if (antialiasing_samples == 0) {
32 helios_runtime_error("ERROR (RadiationModel::addRadiationCamera): The model requires at least 1 antialiasing sample to run.");
33 } else if (camera_properties.camera_resolution.x <= 0 || camera_properties.camera_resolution.y <= 0) {
34 helios_runtime_error("ERROR (RadiationModel::addRadiationCamera): Camera resolution must be at least 1x1.");
35 } else if (camera_properties.HFOV < 0 || camera_properties.HFOV > 180.f) {
36 helios_runtime_error("ERROR (RadiationModel::addRadiationCamera): Camera horizontal field of view must be between 0 and 180 degrees.");
37 }
38
39 RadiationCamera camera(camera_label, band_label, position, lookat, camera_properties, antialiasing_samples);
40 if (cameras.find(camera_label) == cameras.end()) {
41 cameras.emplace(camera_label, camera);
42 } else {
43 if (message_flag) {
44 std::cout << "Camera with label " << camera_label << "already exists. Existing properties will be replaced by new inputs." << std::endl;
45 }
46 cameras.erase(camera_label);
47 cameras.emplace(camera_label, camera);
48 }
49
50 if (iscameravisualizationenabled) {
51 buildCameraModelGeometry(camera_label);
52 }
53
54 radiativepropertiesneedupdate = true;
55}
56
57void RadiationModel::addRadiationCamera(const std::string &camera_label, const std::vector<std::string> &band_label, const helios::vec3 &position, const helios::SphericalCoord &viewing_direction, const CameraProperties &camera_properties,
58 uint antialiasing_samples) {
59
60 vec3 lookat = position + sphere2cart(viewing_direction);
61 addRadiationCamera(camera_label, band_label, position, lookat, camera_properties, antialiasing_samples);
62}
63
64void RadiationModel::setCameraSpectralResponse(const std::string &camera_label, const std::string &band_label, const std::string &global_data) {
65 if (cameras.find(camera_label) == cameras.end()) {
66 helios_runtime_error("ERROR (setCameraSpectralResponse): Camera '" + camera_label + "' does not exist.");
67 } else if (!doesBandExist(band_label)) {
68 helios_runtime_error("ERROR (setCameraSpectralResponse): Band '" + band_label + "' does not exist.");
69 }
70
71 cameras.at(camera_label).band_spectral_response[band_label] = global_data;
72
73 radiativepropertiesneedupdate = true;
74}
75
76void RadiationModel::setCameraSpectralResponseFromLibrary(const std::string &camera_label, const std::string &camera_library_name) {
77
78 if (cameras.find(camera_label) == cameras.end()) {
79 helios_runtime_error("ERROR (setCameraSpectralResponseFromLibrary): Camera '" + camera_label + "' does not exist.");
80 }
81
82 const auto &band_labels = cameras.at(camera_label).band_labels;
83
84 if (!context->doesGlobalDataExist("spectral_library_loaded")) {
85 context->loadXML("plugins/radiation/spectral_data/camera_spectral_library.xml");
86 }
87
88 for (const auto &band: band_labels) {
89 std::string response_spectrum = camera_library_name + "_" + band;
90 if (!context->doesGlobalDataExist(response_spectrum.c_str()) || context->getGlobalDataType(response_spectrum.c_str()) != HELIOS_TYPE_VEC2) {
91 helios_runtime_error("ERROR (setCameraSpectralResponseFromLibrary): Band '" + band + "' referenced in spectral library camera " + camera_library_name + " does not exist for camera '" + camera_label + "'.");
92 }
93
94 cameras.at(camera_label).band_spectral_response[band] = response_spectrum;
95 }
96
97 radiativepropertiesneedupdate = true;
98}
99
100void RadiationModel::setCameraPosition(const std::string &camera_label, const helios::vec3 &position) {
101 if (cameras.find(camera_label) == cameras.end()) {
102 helios_runtime_error("ERROR (RadiationModel::setCameraPosition): Camera '" + camera_label + "' does not exist.");
103 } else if (position == cameras.at(camera_label).lookat) {
104 helios_runtime_error("ERROR (RadiationModel::setCameraPosition): Camera position cannot be equal to the 'lookat' position.");
105 }
106
107 cameras.at(camera_label).position = position;
108
109 if (iscameravisualizationenabled) {
110 updateCameraModelPosition(camera_label);
111 }
112}
113
114helios::vec3 RadiationModel::getCameraPosition(const std::string &camera_label) const {
115
116 if (cameras.find(camera_label) == cameras.end()) {
117 helios_runtime_error("ERROR (RadiationModel::getCameraPosition): Camera '" + camera_label + "' does not exist.");
118 }
119
120 return cameras.at(camera_label).position;
121}
122
123void RadiationModel::setCameraLookat(const std::string &camera_label, const helios::vec3 &lookat) {
124 if (cameras.find(camera_label) == cameras.end()) {
125 helios_runtime_error("ERROR (RadiationModel::setCameraLookat): Camera '" + camera_label + "' does not exist.");
126 }
127
128 cameras.at(camera_label).lookat = lookat;
129
130 if (iscameravisualizationenabled) {
131 updateCameraModelPosition(camera_label);
132 }
133}
134
135helios::vec3 RadiationModel::getCameraLookat(const std::string &camera_label) const {
136
137 if (cameras.find(camera_label) == cameras.end()) {
138 helios_runtime_error("ERROR (RadiationModel::getCameraLookat): Camera '" + camera_label + "' does not exist.");
139 }
140
141 return cameras.at(camera_label).lookat;
142}
143
144void RadiationModel::setCameraOrientation(const std::string &camera_label, const helios::vec3 &direction) {
145 if (cameras.find(camera_label) == cameras.end()) {
146 helios_runtime_error("ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label + "' does not exist.");
147 }
148
149 cameras.at(camera_label).lookat = cameras.at(camera_label).position + direction;
150
151 if (iscameravisualizationenabled) {
152 updateCameraModelPosition(camera_label);
153 }
154}
155
156helios::SphericalCoord RadiationModel::getCameraOrientation(const std::string &camera_label) const {
157
158 if (cameras.find(camera_label) == cameras.end()) {
159 helios_runtime_error("ERROR (RadiationModel::getCameraOrientation): Camera '" + camera_label + "' does not exist.");
160 }
161
162 return cart2sphere(cameras.at(camera_label).lookat - cameras.at(camera_label).position);
163}
164
165void RadiationModel::setCameraOrientation(const std::string &camera_label, const helios::SphericalCoord &direction) {
166 if (cameras.find(camera_label) == cameras.end()) {
167 helios_runtime_error("ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label + "' does not exist.");
168 }
169
170 cameras.at(camera_label).lookat = cameras.at(camera_label).position + sphere2cart(direction);
171
172 if (iscameravisualizationenabled) {
173 updateCameraModelPosition(camera_label);
174 }
175}
176
177std::vector<std::string> RadiationModel::getAllCameraLabels() {
178 std::vector<std::string> labels(cameras.size());
179 uint cam = 0;
180 for (const auto &camera: cameras) {
181 labels.at(cam) = camera.second.label;
182 cam++;
183 }
184 return labels;
185}
186
187std::string RadiationModel::writeCameraImage(const std::string &camera, const std::vector<std::string> &bands, const std::string &imagefile_base, const std::string &image_path, int frame, float flux_to_pixel_conversion) {
188
189 // check if camera exists
190 if (cameras.find(camera) == cameras.end()) {
191 std::cout << "ERROR (RadiationModel::writeCameraImage): camera with label " << camera << " does not exist. Skipping image write for this camera." << std::endl;
192 return "";
193 }
194
195 if (bands.size() != 1 && bands.size() != 3) {
196 helios_runtime_error("ERROR (RadiationModel::writeCameraImage): input vector of band labels should either have length of 1 (grayscale image) or length of 3 (RGB image). Skipping image write for this camera.");
197 }
198
199 std::vector<std::vector<float>> camera_data(bands.size());
200
201 uint b = 0;
202 for (const auto &band: bands) {
203
204 // check if band exists
205 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
206 std::cout << "ERROR (RadiationModel::writeCameraImage): camera " << camera << " band with label " << band << " does not exist. Skipping image write for this camera." << std::endl;
207 return "";
208 }
209
210 // std::string global_data_label = "camera_" + camera + "_" + bands.at(b);
211 //
212 // if (!context->doesGlobalDataExist(global_data_label.c_str())) {
213 // std::cout << "ERROR (RadiationModel::writeCameraImage): image data for camera " << camera << ", band " << bands.at(b) << " has not been created. Did you run the radiation model? Skipping image write for this camera." << std::endl;
214 // return;
215 // }
216 //
217 // context->getGlobalData(global_data_label.c_str(), camera_data.at(b));
218
219 camera_data.at(b) = cameras.at(camera).pixel_data.at(band);
220
221 b++;
222 }
223
224 std::string frame_str;
225 if (frame >= 0) {
226 frame_str = std::to_string(frame);
227 }
228
229 std::string output_path = image_path;
230 if (!image_path.empty() && !validateOutputPath(output_path)) {
231 helios_runtime_error("ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
232 } else if (!getFileName(output_path).empty()) {
233 helios_runtime_error("ERROR(RadiationModel::writeCameraImage): Image output directory contains a filename. This argument should be the path to a directory not a file.");
234 }
235
236 std::ostringstream outfile;
237 outfile << output_path;
238
239 if (frame >= 0) {
240 outfile << camera << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".jpeg";
241 } else {
242 outfile << camera << "_" << imagefile_base << ".jpeg";
243 }
244 std::ofstream testfile(outfile.str());
245
246 if (!testfile.is_open()) {
247 std::cout << "ERROR (RadiationModel::writeCameraImage): image file " << outfile.str() << " could not be opened. Check that the path exists and that you have write permission. Skipping image write for this camera." << std::endl;
248 return "";
249 }
250 testfile.close();
251
252 int2 camera_resolution = cameras.at(camera).resolution;
253
254 std::vector<RGBcolor> pixel_data_RGB(camera_resolution.x * camera_resolution.y);
255
256 RGBcolor pixel_color;
257 for (uint j = 0; j < camera_resolution.y; j++) {
258 for (uint i = 0; i < camera_resolution.x; i++) {
259 if (camera_data.size() == 1) {
260 float c = camera_data.front().at(j * camera_resolution.x + i);
261 pixel_color = make_RGBcolor(c, c, c);
262 } else {
263 pixel_color = make_RGBcolor(camera_data.at(0).at(j * camera_resolution.x + i), camera_data.at(1).at(j * camera_resolution.x + i), camera_data.at(2).at(j * camera_resolution.x + i));
264 }
265 pixel_color.scale(flux_to_pixel_conversion);
266 uint ii = camera_resolution.x - i - 1;
267 uint jj = camera_resolution.y - j - 1;
268 pixel_data_RGB.at(jj * camera_resolution.x + ii) = pixel_color;
269 }
270 }
271
272 writeJPEG(outfile.str(), camera_resolution.x, camera_resolution.y, pixel_data_RGB);
273
274 return outfile.str();
275}
276
277std::string RadiationModel::writeNormCameraImage(const std::string &camera, const std::vector<std::string> &bands, const std::string &imagefile_base, const std::string &image_path, int frame) {
278 float maxval = 0;
279 // Find maximum mean value over all bands
280 for (const std::string &band: bands) {
281 std::string global_data_label = "camera_" + camera + "_" + band;
282 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
283 std::cout << "ERROR (RadiationModel::writeNormCameraImage): camera " << camera << " band with label " << band << " does not exist. Skipping image write for this camera." << std::endl;
284 return "";
285 } else if (!context->doesGlobalDataExist(global_data_label.c_str())) {
286 std::cout << "ERROR (RadiationModel::writeNormCameraImage): image data for camera " << camera << ", band " << band << " has not been created. Did you run the radiation model? Skipping image write for this camera." << std::endl;
287 return "";
288 }
289 std::vector<float> cameradata;
290 context->getGlobalData(global_data_label.c_str(), cameradata);
291 for (float val: cameradata) {
292 if (val > maxval) {
293 maxval = val;
294 }
295 }
296 }
297 // Normalize all bands
298 for (const std::string &band: bands) {
299 std::string global_data_label = "camera_" + camera + "_" + band;
300 std::vector<float> cameradata;
301 context->getGlobalData(global_data_label.c_str(), cameradata);
302 for (float &val: cameradata) {
303 val = val / maxval;
304 }
305 context->setGlobalData(global_data_label.c_str(), cameradata);
306 }
307
308 return RadiationModel::writeCameraImage(camera, bands, imagefile_base, image_path, frame);
309}
310
311void RadiationModel::writeCameraImageData(const std::string &camera, const std::string &band, const std::string &imagefile_base, const std::string &image_path, int frame) {
312
313 // check if camera exists
314 if (cameras.find(camera) == cameras.end()) {
315 std::cout << "ERROR (RadiationModel::writeCameraImageData): camera with label " << camera << " does not exist. Skipping image write for this camera." << std::endl;
316 return;
317 }
318
319 std::vector<float> camera_data;
320
321 // check if band exists
322 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
323 std::cout << "ERROR (RadiationModel::writeCameraImageData): camera " << camera << " band with label " << band << " does not exist. Skipping image write for this camera." << std::endl;
324 return;
325 }
326
327 std::string global_data_label = "camera_" + camera + "_" + band;
328
329 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
330 std::cout << "ERROR (RadiationModel::writeCameraImageData): image data for camera " << camera << ", band " << band << " has not been created. Did you run the radiation model? Skipping image write for this camera." << std::endl;
331 return;
332 }
333
334 context->getGlobalData(global_data_label.c_str(), camera_data);
335
336 std::string frame_str;
337 if (frame >= 0) {
338 frame_str = std::to_string(frame);
339 }
340
341 std::string output_path = image_path;
342 if (!image_path.empty() && !validateOutputPath(output_path)) {
343 helios_runtime_error("ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
344 } else if (!getFileName(output_path).empty()) {
345 helios_runtime_error("ERROR(RadiationModel::writeCameraImage): Image output directory contains a filename. This argument should be the path to a directory not a file.");
346 }
347
348 std::ostringstream outfile;
349 outfile << output_path;
350
351 if (frame >= 0) {
352 outfile << camera << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
353 } else {
354 outfile << camera << "_" << imagefile_base << ".txt";
355 }
356
357 std::ofstream outfilestream(outfile.str());
358
359 if (!outfilestream.is_open()) {
360 std::cout << "ERROR (RadiationModel::writeCameraImageData): image file " << outfile.str() << " could not be opened. Check that the path exists and that you have write permission. Skipping image write for this camera." << std::endl;
361 return;
362 }
363
364 int2 camera_resolution = cameras.at(camera).resolution;
365
366 for (int j = 0; j < camera_resolution.y; j++) {
367 for (int i = camera_resolution.x - 1; i >= 0; i--) {
368 outfilestream << camera_data.at(j * camera_resolution.x + i) << " ";
369 }
370 outfilestream << "\n";
371 }
372
373 outfilestream.close();
374}
375
376void RadiationModel::setCameraCalibration(CameraCalibration *CameraCalibration) {
377 cameracalibration = CameraCalibration;
378 calibration_flag = true;
379}
380
381void RadiationModel::updateCameraResponse(const std::string &orginalcameralabel, const std::vector<std::string> &sourcelabels_raw, const std::vector<std::string> &cameraresponselabels, vec2 &wavelengthrange,
382 const std::vector<std::vector<float>> &truevalues, const std::string &calibratedmark) {
383
384 std::vector<std::string> objectlabels;
385 vec2 wavelengthrange_c = wavelengthrange;
386 cameracalibration->preprocessSpectra(sourcelabels_raw, cameraresponselabels, objectlabels, wavelengthrange_c);
387
388 RadiationCamera calibratecamera = cameras.at(orginalcameralabel);
389 CameraProperties cameraproperties;
390 cameraproperties.HFOV = calibratecamera.HFOV_degrees;
391 cameraproperties.camera_resolution = calibratecamera.resolution;
392 cameraproperties.focal_plane_distance = calibratecamera.focal_length;
393 cameraproperties.lens_diameter = calibratecamera.lens_diameter;
394 cameraproperties.FOV_aspect_ratio = calibratecamera.FOV_aspect_ratio;
395
396 std::vector<uint> UUIDs_target = cameracalibration->getColorBoardUUIDs();
397 std::string cameralabel = "calibration";
398 std::map<uint, std::vector<vec2>> simulatedcolorboardspectra;
399 for (uint UUID: UUIDs_target) {
400 simulatedcolorboardspectra.emplace(UUID, NULL);
401 }
402
403 for (uint ID = 0; ID < radiation_sources.size(); ID++) {
405 }
406
407 std::vector<float> wavelengths;
408 context->getGlobalData("wavelengths", wavelengths);
409 int numberwavelengths = wavelengths.size();
410
411 for (int iw = 0; iw < numberwavelengths; iw++) {
412 std::string wavelengthlabel = std::to_string(wavelengths.at(iw));
413
414 std::vector<std::string> sourcelabels;
415 for (std::string sourcelabel_raw: sourcelabels_raw) {
416 std::vector<vec2> icalsource;
417 icalsource.push_back(cameracalibration->processedspectra.at("source").at(sourcelabel_raw).at(iw));
418 icalsource.push_back(cameracalibration->processedspectra.at("source").at(sourcelabel_raw).at(iw));
419 icalsource.at(1).x += 1;
420 std::string sourcelable = "Cal_source_" + sourcelabel_raw;
421 sourcelabels.push_back(sourcelable);
422 context->setGlobalData(sourcelable.c_str(), icalsource);
423 }
424
425 std::vector<vec2> icalcamera(2);
426 icalcamera.at(0).y = 1;
427 icalcamera.at(1).y = 1;
428 icalcamera.at(0).x = wavelengths.at(iw);
429 icalcamera.at(1).x = wavelengths.at(iw) + 1;
430 std::string camlable = "Cal_cameraresponse";
431 context->setGlobalData(camlable.c_str(), icalcamera);
432
433 for (auto objectpair: cameracalibration->processedspectra.at("object")) {
434 std::vector<vec2> spectrum_obj;
435 spectrum_obj.push_back(objectpair.second.at(iw));
436 spectrum_obj.push_back(objectpair.second.at(iw));
437 spectrum_obj.at(1).x += 1;
438 context->setGlobalData(objectpair.first.c_str(), spectrum_obj);
439 }
440
441 RadiationModel::addRadiationBand(wavelengthlabel, std::stof(wavelengthlabel), std::stof(wavelengthlabel) + 1);
442 RadiationModel::disableEmission(wavelengthlabel);
443
444 uint ID = 0;
445 for (std::string sourcelabel_raw: sourcelabels_raw) {
446 RadiationModel::setSourceSpectrum(ID, sourcelabels.at(ID).c_str());
447 RadiationModel::setSourceFlux(ID, wavelengthlabel, 1);
448 ID++;
449 }
450 RadiationModel::setScatteringDepth(wavelengthlabel, 1);
451 RadiationModel::setDiffuseRadiationFlux(wavelengthlabel, 0);
452 RadiationModel::setDiffuseRadiationExtinctionCoeff(wavelengthlabel, 0.f, make_vec3(-0.5, 0.5, 1));
453
454 RadiationModel::addRadiationCamera(cameralabel, {wavelengthlabel}, calibratecamera.position, calibratecamera.lookat, cameraproperties, 10);
455 RadiationModel::setCameraSpectralResponse(cameralabel, wavelengthlabel, camlable);
457 RadiationModel::runBand({wavelengthlabel});
458
459 std::vector<float> camera_data;
460 std::string global_data_label = "camera_" + cameralabel + "_" + wavelengthlabel;
461 context->getGlobalData(global_data_label.c_str(), camera_data);
462
463 std::vector<uint> pixel_labels;
464 std::string global_data_label_UUID = "camera_" + cameralabel + "_pixel_UUID";
465 context->getGlobalData(global_data_label_UUID.c_str(), pixel_labels);
466
467 for (uint j = 0; j < calibratecamera.resolution.y; j++) {
468 for (uint i = 0; i < calibratecamera.resolution.x; i++) {
469 float icdata = camera_data.at(j * calibratecamera.resolution.x + i);
470
471 uint UUID = pixel_labels.at(j * calibratecamera.resolution.x + i) - 1;
472 if (find(UUIDs_target.begin(), UUIDs_target.end(), UUID) != UUIDs_target.end()) {
473 if (simulatedcolorboardspectra.at(UUID).empty()) {
474 simulatedcolorboardspectra.at(UUID).push_back(make_vec2(wavelengths.at(iw), icdata / float(numberwavelengths)));
475 } else if (simulatedcolorboardspectra.at(UUID).back().x == wavelengths.at(iw)) {
476 simulatedcolorboardspectra.at(UUID).back().y += icdata / float(numberwavelengths);
477 } else if (simulatedcolorboardspectra.at(UUID).back().x != wavelengths.at(iw)) {
478 simulatedcolorboardspectra.at(UUID).push_back(make_vec2(wavelengths.at(iw), icdata / float(numberwavelengths)));
479 }
480 }
481 }
482 }
483 }
484 // Update camera response spectra
485 cameracalibration->updateCameraResponseSpectra(cameraresponselabels, calibratedmark, simulatedcolorboardspectra, truevalues);
486 // Reset color board spectra
487 std::vector<uint> UUIDs_colorbd = cameracalibration->getColorBoardUUIDs();
488 for (uint UUID: UUIDs_colorbd) {
489 std::string colorboardspectra;
490 context->getPrimitiveData(UUID, "reflectivity_spectrum", colorboardspectra);
491 context->setPrimitiveData(UUID, "reflectivity_spectrum", colorboardspectra + "_raw");
492 }
493}
494
495void RadiationModel::runRadiationImaging(const std::string &cameralabel, const std::vector<std::string> &sourcelabels, const std::vector<std::string> &bandlabels, const std::vector<std::string> &cameraresponselabels, helios::vec2 wavelengthrange,
496 float fluxscale, float diffusefactor, uint scatteringdepth) {
497
498 float sources_fluxsum = 0;
499 std::vector<float> sources_fluxes;
500 for (uint ID = 0; ID < sourcelabels.size(); ID++) {
501 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
502 sources_fluxes.push_back(RadiationModel::integrateSpectrum(Source_spectrum, wavelengthrange.x, wavelengthrange.y));
503 RadiationModel::setSourceSpectrum(ID, sourcelabels.at(ID).c_str());
504 RadiationModel::setSourceSpectrumIntegral(ID, sources_fluxes.at(ID));
505 sources_fluxsum += sources_fluxes.at(ID);
506 }
507
508 RadiationModel::addRadiationBand(bandlabels.at(0), wavelengthrange.x, wavelengthrange.y);
509 RadiationModel::disableEmission(bandlabels.at(0));
510 for (uint ID = 0; ID < radiation_sources.size(); ID++) {
511 RadiationModel::setSourceFlux(ID, bandlabels.at(0), (1 - diffusefactor) * sources_fluxes.at(ID) * fluxscale);
512 }
513 RadiationModel::setScatteringDepth(bandlabels.at(0), scatteringdepth);
514 RadiationModel::setDiffuseRadiationFlux(bandlabels.at(0), diffusefactor * sources_fluxsum);
515 RadiationModel::setDiffuseRadiationExtinctionCoeff(bandlabels.at(0), 1.f, make_vec3(-0.5, 0.5, 1));
516
517 if (bandlabels.size() > 1) {
518 for (int iband = 1; iband < bandlabels.size(); iband++) {
519 RadiationModel::copyRadiationBand(bandlabels.at(iband - 1), bandlabels.at(iband), wavelengthrange.x, wavelengthrange.y);
520 for (uint ID = 0; ID < radiation_sources.size(); ID++) {
521 RadiationModel::setSourceFlux(ID, bandlabels.at(iband), (1 - diffusefactor) * sources_fluxes.at(ID) * fluxscale);
522 }
523 RadiationModel::setDiffuseRadiationFlux(bandlabels.at(iband), diffusefactor * sources_fluxsum);
524 }
525 }
526
527 for (int iband = 0; iband < bandlabels.size(); iband++) {
528 RadiationModel::setCameraSpectralResponse(cameralabel, bandlabels.at(iband), cameraresponselabels.at(iband));
529 }
530
532 RadiationModel::runBand(bandlabels);
533}
534
535void RadiationModel::runRadiationImaging(const std::vector<std::string> &cameralabels, const std::vector<std::string> &sourcelabels, const std::vector<std::string> &bandlabels, const std::vector<std::string> &cameraresponselabels,
536 helios::vec2 wavelengthrange, float fluxscale, float diffusefactor, uint scatteringdepth) {
537
538 float sources_fluxsum = 0;
539 std::vector<float> sources_fluxes;
540 for (uint ID = 0; ID < sourcelabels.size(); ID++) {
541 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
542 sources_fluxes.push_back(RadiationModel::integrateSpectrum(Source_spectrum, wavelengthrange.x, wavelengthrange.y));
543 RadiationModel::setSourceSpectrum(ID, sourcelabels.at(ID).c_str());
544 RadiationModel::setSourceSpectrumIntegral(ID, sources_fluxes.at(ID));
545 sources_fluxsum += sources_fluxes.at(ID);
546 }
547
548 RadiationModel::addRadiationBand(bandlabels.at(0), wavelengthrange.x, wavelengthrange.y);
549 RadiationModel::disableEmission(bandlabels.at(0));
550 for (uint ID = 0; ID < radiation_sources.size(); ID++) {
551 RadiationModel::setSourceFlux(ID, bandlabels.at(0), (1 - diffusefactor) * sources_fluxes.at(ID) * fluxscale);
552 }
553 RadiationModel::setScatteringDepth(bandlabels.at(0), scatteringdepth);
554 RadiationModel::setDiffuseRadiationFlux(bandlabels.at(0), diffusefactor * sources_fluxsum);
555 RadiationModel::setDiffuseRadiationExtinctionCoeff(bandlabels.at(0), 1.f, make_vec3(-0.5, 0.5, 1));
556
557 if (bandlabels.size() > 1) {
558 for (int iband = 1; iband < bandlabels.size(); iband++) {
559 RadiationModel::copyRadiationBand(bandlabels.at(iband - 1), bandlabels.at(iband), wavelengthrange.x, wavelengthrange.y);
560 for (uint ID = 0; ID < radiation_sources.size(); ID++) {
561 RadiationModel::setSourceFlux(ID, bandlabels.at(iband), (1 - diffusefactor) * sources_fluxes.at(ID) * fluxscale);
562 }
563 RadiationModel::setDiffuseRadiationFlux(bandlabels.at(iband), diffusefactor * sources_fluxsum);
564 }
565 }
566
567 for (int ic = 0; ic < cameralabels.size(); ic++) {
568 for (int iband = 0; iband < bandlabels.size(); iband++) {
569 RadiationModel::setCameraSpectralResponse(cameralabels.at(ic), bandlabels.at(iband), cameraresponselabels.at(iband));
570 }
571 }
572
573
575 RadiationModel::runBand(bandlabels);
576}
577
578float RadiationModel::getCameraResponseScale(const std::string &orginalcameralabel, const std::vector<std::string> &cameraresponselabels, const std::vector<std::string> &bandlabels, const std::vector<std::string> &sourcelabels, vec2 &wavelengthrange,
579 const std::vector<std::vector<float>> &truevalues) {
580
581
582 RadiationCamera calibratecamera = cameras.at(orginalcameralabel);
583 CameraProperties cameraproperties;
584 cameraproperties.HFOV = calibratecamera.HFOV_degrees;
585 cameraproperties.camera_resolution = calibratecamera.resolution;
586 cameraproperties.focal_plane_distance = calibratecamera.focal_length;
587 cameraproperties.lens_diameter = calibratecamera.lens_diameter;
588 cameraproperties.FOV_aspect_ratio = calibratecamera.FOV_aspect_ratio;
589
590 std::string cameralabel = orginalcameralabel + "Scale";
591 RadiationModel::addRadiationCamera(cameralabel, bandlabels, calibratecamera.position, calibratecamera.lookat, cameraproperties, 20);
592 RadiationModel::runRadiationImaging(cameralabel, sourcelabels, bandlabels, cameraresponselabels, wavelengthrange, 1, 0);
593
594 // Get camera spectral response scale based on comparing true values and calibrated image
595 float camerascale = cameracalibration->getCameraResponseScale(cameralabel, cameraproperties.camera_resolution, bandlabels, truevalues);
596 return camerascale;
597}
598
599
600void RadiationModel::writePrimitiveDataLabelMap(const std::string &cameralabel, const std::string &primitive_data_label, const std::string &imagefile_base, const std::string &image_path, int frame, float padvalue) {
601
602 if (cameras.find(cameralabel) == cameras.end()) {
603 helios_runtime_error("ERROR (RadiationModel::writePrimitiveDataLabelMap): Camera '" + cameralabel + "' does not exist.");
604 }
605
606 // Get image UUID labels
607 std::vector<uint> camera_UUIDs;
608 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
609 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
610 helios_runtime_error("ERROR (RadiationModel::writePrimitiveDataLabelMap): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
611 }
612 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
613 std::vector<uint> pixel_UUIDs = camera_UUIDs;
614 int2 camera_resolution = cameras.at(cameralabel).resolution;
615
616 std::string frame_str;
617 if (frame >= 0) {
618 frame_str = std::to_string(frame);
619 }
620
621 std::string output_path = image_path;
622 if (!image_path.empty() && !validateOutputPath(output_path)) {
623 helios_runtime_error("ERROR (RadiationModel::writePrimitiveDataLabelMap): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
624 } else if (!getFileName(output_path).empty()) {
625 helios_runtime_error("ERROR(RadiationModel::writePrimitiveDataLabelMap): Image output directory contains a filename. This argument should be the path to a directory not a file.");
626 }
627
628 std::ostringstream outfile;
629 outfile << output_path;
630
631 if (frame >= 0) {
632 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
633 } else {
634 outfile << cameralabel << "_" << imagefile_base << ".txt";
635 }
636
637 // Output label image in ".txt" format
638 std::ofstream pixel_data(outfile.str());
639
640 if (!pixel_data.is_open()) {
641 helios_runtime_error("ERROR (RadiationModel::writePrimitiveDataLabelMap): Could not open file '" + outfile.str() + "' for writing.");
642 }
643
644 bool empty_flag = true;
645 for (uint j = 0; j < camera_resolution.y; j++) {
646 for (uint i = 0; i < camera_resolution.x; i++) {
647 uint ii = camera_resolution.x - i - 1;
648 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
649 if (context->doesPrimitiveExist(UUID) && context->doesPrimitiveDataExist(UUID, primitive_data_label.c_str())) {
650 HeliosDataType datatype = context->getPrimitiveDataType(primitive_data_label.c_str());
651 if (datatype == HELIOS_TYPE_FLOAT) {
652 float labeldata;
653 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
654 pixel_data << labeldata << " ";
655 empty_flag = false;
656 } else if (datatype == HELIOS_TYPE_UINT) {
657 uint labeldata;
658 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
659 pixel_data << labeldata << " ";
660 empty_flag = false;
661 } else if (datatype == HELIOS_TYPE_INT) {
662 int labeldata;
663 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
664 pixel_data << labeldata << " ";
665 empty_flag = false;
666 } else if (datatype == HELIOS_TYPE_DOUBLE) {
667 double labeldata;
668 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
669 pixel_data << labeldata << " ";
670 empty_flag = false;
671 } else {
672 pixel_data << padvalue << " ";
673 }
674 } else {
675 pixel_data << padvalue << " ";
676 }
677 }
678 pixel_data << "\n";
679 }
680 pixel_data.close();
681
682 if (empty_flag) {
683 std::cerr << "WARNING (RadiationModel::writePrimitiveDataLabelMap): No primitive data of " << primitive_data_label << " found in camera image. Primitive data map contains only padded values." << std::endl;
684 }
685}
686
687void RadiationModel::writeObjectDataLabelMap(const std::string &cameralabel, const std::string &object_data_label, const std::string &imagefile_base, const std::string &image_path, int frame, float padvalue) {
688
689 if (cameras.find(cameralabel) == cameras.end()) {
690 helios_runtime_error("ERROR (RadiationModel::writeObjectDataLabelMap): Camera '" + cameralabel + "' does not exist.");
691 }
692
693 // Get image UUID labels
694 std::vector<uint> camera_UUIDs;
695 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
696 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
697 helios_runtime_error("ERROR (RadiationModel::writeObjectDataLabelMap): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
698 }
699 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
700 std::vector<uint> pixel_UUIDs = camera_UUIDs;
701 int2 camera_resolution = cameras.at(cameralabel).resolution;
702
703 std::string frame_str;
704 if (frame >= 0) {
705 frame_str = std::to_string(frame);
706 }
707
708 std::string output_path = image_path;
709 if (!image_path.empty() && !validateOutputPath(output_path)) {
710 helios_runtime_error("ERROR (RadiationModel::writeObjectDataLabelMap): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
711 } else if (!getFileName(output_path).empty()) {
712 helios_runtime_error("ERROR(RadiationModel::writeObjectDataLabelMap): Image output directory contains a filename. This argument should be the path to a directory not a file.");
713 }
714
715 std::ostringstream outfile;
716 outfile << output_path;
717
718 if (frame >= 0) {
719 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
720 } else {
721 outfile << cameralabel << "_" << imagefile_base << ".txt";
722 }
723
724 // Output label image in ".txt" format
725 std::ofstream pixel_data(outfile.str());
726
727 if (!pixel_data.is_open()) {
728 helios_runtime_error("ERROR (RadiationModel::writeObjectDataLabelMap): Could not open file '" + outfile.str() + "' for writing.");
729 }
730
731 bool empty_flag = true;
732 for (uint j = 0; j < camera_resolution.y; j++) {
733 for (uint i = 0; i < camera_resolution.x; i++) {
734 uint ii = camera_resolution.x - i - 1;
735 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
736 if (!context->doesPrimitiveExist(UUID)) {
737 pixel_data << padvalue << " ";
738 continue;
739 }
740 uint objID = context->getPrimitiveParentObjectID(UUID);
741 if (context->doesObjectExist(objID) && context->doesObjectDataExist(objID, object_data_label.c_str())) {
742 HeliosDataType datatype = context->getObjectDataType(object_data_label.c_str());
743 if (datatype == HELIOS_TYPE_FLOAT) {
744 float labeldata;
745 context->getObjectData(objID, object_data_label.c_str(), labeldata);
746 pixel_data << labeldata << " ";
747 empty_flag = false;
748 } else if (datatype == HELIOS_TYPE_UINT) {
749 uint labeldata;
750 context->getObjectData(objID, object_data_label.c_str(), labeldata);
751 pixel_data << labeldata << " ";
752 empty_flag = false;
753 } else if (datatype == HELIOS_TYPE_INT) {
754 int labeldata;
755 context->getObjectData(objID, object_data_label.c_str(), labeldata);
756 pixel_data << labeldata << " ";
757 empty_flag = false;
758 } else if (datatype == HELIOS_TYPE_DOUBLE) {
759 double labeldata;
760 context->getObjectData(objID, object_data_label.c_str(), labeldata);
761 pixel_data << labeldata << " ";
762 empty_flag = false;
763 } else {
764 pixel_data << padvalue << " ";
765 }
766 } else {
767 pixel_data << padvalue << " ";
768 }
769 }
770 pixel_data << "\n";
771 }
772 pixel_data.close();
773
774 if (empty_flag) {
775 std::cerr << "WARNING (RadiationModel::writeObjectDataLabelMap): No object data of " << object_data_label << " found in camera image. Object data map contains only padded values." << std::endl;
776 }
777}
778
779void RadiationModel::writeDepthImageData(const std::string &cameralabel, const std::string &imagefile_base, const std::string &image_path, int frame) {
780
781 if (cameras.find(cameralabel) == cameras.end()) {
782 helios_runtime_error("ERROR (RadiationModel::writeDepthImageData): Camera '" + cameralabel + "' does not exist.");
783 }
784
785 std::string global_data_label = "camera_" + cameralabel + "_pixel_depth";
786 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
787 helios_runtime_error("ERROR (RadiationModel::writeDepthImageData): Depth data for camera '" + cameralabel + "' does not exist. Was the radiation model run for the camera?");
788 }
789 std::vector<float> camera_depth;
790 context->getGlobalData(global_data_label.c_str(), camera_depth);
791 helios::vec3 camera_position = cameras.at(cameralabel).position;
792 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
793
794 int2 camera_resolution = cameras.at(cameralabel).resolution;
795
796 std::string frame_str;
797 if (frame >= 0) {
798 frame_str = std::to_string(frame);
799 }
800
801 std::string output_path = image_path;
802 if (!image_path.empty() && !validateOutputPath(output_path)) {
803 helios_runtime_error("ERROR (RadiationModel::writeDepthImageData): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
804 } else if (!getFileName(output_path).empty()) {
805 helios_runtime_error("ERROR(RadiationModel::writeDepthImageData): Image output directory contains a filename. This argument should be the path to a directory not a file.");
806 }
807
808 std::ostringstream outfile;
809 outfile << output_path;
810
811 if (frame >= 0) {
812 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
813 } else {
814 outfile << cameralabel << "_" << imagefile_base << ".txt";
815 }
816
817 // Output label image in ".txt" format
818 std::ofstream pixel_data(outfile.str());
819
820 if (!pixel_data.is_open()) {
821 helios_runtime_error("ERROR (RadiationModel::writeDepthImageData): Could not open file '" + outfile.str() + "' for writing.");
822 }
823
824 for (int j = 0; j < camera_resolution.y; j++) {
825 for (int i = camera_resolution.x - 1; i >= 0; i--) {
826 pixel_data << camera_depth.at(j * camera_resolution.x + i) << " ";
827 }
828 pixel_data << "\n";
829 }
830
831 pixel_data.close();
832}
833
834void RadiationModel::writeNormDepthImage(const std::string &cameralabel, const std::string &imagefile_base, float max_depth, const std::string &image_path, int frame) {
835
836 if (cameras.find(cameralabel) == cameras.end()) {
837 helios_runtime_error("ERROR (RadiationModel::writeNormDepthImage): Camera '" + cameralabel + "' does not exist.");
838 }
839
840 std::string global_data_label = "camera_" + cameralabel + "_pixel_depth";
841 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
842 helios_runtime_error("ERROR (RadiationModel::writeNormDepthImage): Depth data for camera '" + cameralabel + "' does not exist. Was the radiation model run for the camera?");
843 }
844 std::vector<float> camera_depth;
845 context->getGlobalData(global_data_label.c_str(), camera_depth);
846 helios::vec3 camera_position = cameras.at(cameralabel).position;
847 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
848
849 int2 camera_resolution = cameras.at(cameralabel).resolution;
850
851 std::string frame_str;
852 if (frame >= 0) {
853 frame_str = std::to_string(frame);
854 }
855
856 std::string output_path = image_path;
857 if (!image_path.empty() && !validateOutputPath(output_path)) {
858 helios_runtime_error("ERROR (RadiationModel::writeNormDepthImage): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
859 } else if (!getFileName(output_path).empty()) {
860 helios_runtime_error("ERROR(RadiationModel::writeNormDepthImage): Image output directory contains a filename. This argument should be the path to a directory not a file.");
861 }
862
863 std::ostringstream outfile;
864 outfile << output_path;
865
866 if (frame >= 0) {
867 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".jpeg";
868 } else {
869 outfile << cameralabel << "_" << imagefile_base << ".jpeg";
870 }
871
872 float min_depth = 99999;
873 for (int i = 0; i < camera_depth.size(); i++) {
874 if (camera_depth.at(i) < 0 || camera_depth.at(i) > max_depth) {
875 camera_depth.at(i) = max_depth;
876 }
877 if (camera_depth.at(i) < min_depth) {
878 min_depth = camera_depth.at(i);
879 }
880 }
881 for (int i = 0; i < camera_depth.size(); i++) {
882 camera_depth.at(i) = 1.f - (camera_depth.at(i) - min_depth) / (max_depth - min_depth);
883 }
884
885 std::vector<RGBcolor> pixel_data(camera_resolution.x * camera_resolution.y);
886
887 RGBcolor pixel_color;
888 for (uint j = 0; j < camera_resolution.y; j++) {
889 for (uint i = 0; i < camera_resolution.x; i++) {
890
891 float c = camera_depth.at(j * camera_resolution.x + i);
892 pixel_color = make_RGBcolor(c, c, c);
893
894 uint ii = camera_resolution.x - i - 1;
895 uint jj = camera_resolution.y - j - 1;
896 pixel_data.at(jj * camera_resolution.x + ii) = pixel_color;
897 }
898 }
899
900 writeJPEG(outfile.str(), camera_resolution.x, camera_resolution.y, pixel_data);
901}
902
903// DEPRECATED
904void RadiationModel::writeImageBoundingBoxes(const std::string &cameralabel, const std::string &primitive_data_label, uint object_class_ID, const std::string &imagefile_base, const std::string &image_path, bool append_label_file, int frame) {
905
906 if (cameras.find(cameralabel) == cameras.end()) {
907 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel + "' does not exist.");
908 }
909
910 // Get image UUID labels
911 std::vector<uint> camera_UUIDs;
912 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
913 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
914 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
915 }
916 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
917 std::vector<uint> pixel_UUIDs = camera_UUIDs;
918 int2 camera_resolution = cameras.at(cameralabel).resolution;
919
920 std::string frame_str;
921 if (frame >= 0) {
922 frame_str = std::to_string(frame);
923 }
924
925 std::string output_path = image_path;
926 if (!image_path.empty() && !validateOutputPath(output_path)) {
927 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
928 } else if (!getFileName(output_path).empty()) {
929 helios_runtime_error("ERROR(RadiationModel::writeImageBoundingBoxes): Image output directory contains a filename. This argument should be the path to a directory not a file.");
930 }
931
932 std::ostringstream outfile;
933 outfile << output_path;
934
935 if (frame >= 0) {
936 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
937 } else {
938 outfile << cameralabel << "_" << imagefile_base << ".txt";
939 }
940
941 // Output label image in ".txt" format
942 std::ofstream label_file;
943 if (append_label_file) {
944 label_file.open(outfile.str(), std::ios::out | std::ios::app);
945 } else {
946 label_file.open(outfile.str());
947 }
948
949 if (!label_file.is_open()) {
950 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Could not open file '" + outfile.str() + "'.");
951 }
952
953 std::map<int, vec4> pdata_bounds;
954
955 for (int j = 0; j < camera_resolution.y; j++) {
956 for (int i = 0; i < camera_resolution.x; i++) {
957 uint ii = camera_resolution.x - i - 1;
958 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
959 if (context->doesPrimitiveExist(UUID) && context->doesPrimitiveDataExist(UUID, primitive_data_label.c_str())) {
960
961 uint labeldata;
962
963 HeliosDataType datatype = context->getPrimitiveDataType(primitive_data_label.c_str());
964 if (datatype == HELIOS_TYPE_UINT) {
965 uint labeldata_ui;
966 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_ui);
967 labeldata = labeldata_ui;
968 } else if (datatype == HELIOS_TYPE_INT) {
969 int labeldata_i;
970 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_i);
971 labeldata = (uint) labeldata_i;
972 } else {
973 continue;
974 }
975
976 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
977 pdata_bounds[labeldata] = make_vec4(1e6, -1, 1e6, -1);
978 }
979
980 if (i < pdata_bounds[labeldata].x) {
981 pdata_bounds[labeldata].x = i;
982 }
983 if (i > pdata_bounds[labeldata].y) {
984 pdata_bounds[labeldata].y = i;
985 }
986 if (j < pdata_bounds[labeldata].z) {
987 pdata_bounds[labeldata].z = j;
988 }
989 if (j > pdata_bounds[labeldata].w) {
990 pdata_bounds[labeldata].w = j;
991 }
992 }
993 }
994 }
995
996 for (auto box: pdata_bounds) {
997 vec4 bbox = box.second;
998 if (bbox.x == bbox.y || bbox.z == bbox.w) { // filter boxes of zeros size
999 continue;
1000 }
1001 label_file << object_class_ID << " " << (bbox.x + 0.5 * (bbox.y - bbox.x)) / float(camera_resolution.x) << " " << (bbox.z + 0.5 * (bbox.w - bbox.z)) / float(camera_resolution.y) << " " << std::setprecision(6) << std::fixed
1002 << (bbox.y - bbox.x) / float(camera_resolution.x) << " " << (bbox.w - bbox.z) / float(camera_resolution.y) << std::endl;
1003 }
1004
1005 label_file.close();
1006}
1007
1008// DEPRECATED
1009void RadiationModel::writeImageBoundingBoxes_ObjectData(const std::string &cameralabel, const std::string &object_data_label, uint object_class_ID, const std::string &imagefile_base, const std::string &image_path, bool append_label_file, int frame) {
1010
1011 if (cameras.find(cameralabel) == cameras.end()) {
1012 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel + "' does not exist.");
1013 }
1014
1015 // Get image UUID labels
1016 std::vector<uint> camera_UUIDs;
1017 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1018 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
1019 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
1020 }
1021 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1022 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1023 int2 camera_resolution = cameras.at(cameralabel).resolution;
1024
1025 std::string frame_str;
1026 if (frame >= 0) {
1027 frame_str = std::to_string(frame);
1028 }
1029
1030 std::string output_path = image_path;
1031 if (!image_path.empty() && !validateOutputPath(output_path)) {
1032 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
1033 } else if (!getFileName(output_path).empty()) {
1034 helios_runtime_error("ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Image output directory contains a filename. This argument should be the path to a directory not a file.");
1035 }
1036
1037 std::ostringstream outfile;
1038 outfile << output_path;
1039
1040 if (frame >= 0) {
1041 outfile << cameralabel << "_" << imagefile_base << "_" << std::setw(5) << std::setfill('0') << frame_str << ".txt";
1042 } else {
1043 outfile << cameralabel << "_" << imagefile_base << ".txt";
1044 }
1045
1046 // Output label image in ".txt" format
1047 std::ofstream label_file;
1048 if (append_label_file) {
1049 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1050 } else {
1051 label_file.open(outfile.str());
1052 }
1053
1054 if (!label_file.is_open()) {
1055 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open file '" + outfile.str() + "'.");
1056 }
1057
1058 std::map<int, vec4> pdata_bounds;
1059
1060 for (int j = 0; j < camera_resolution.y; j++) {
1061 for (int i = 0; i < camera_resolution.x; i++) {
1062 uint ii = camera_resolution.x - i - 1;
1063 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1064
1065 if (!context->doesPrimitiveExist(UUID)) {
1066 continue;
1067 }
1068
1069 uint objID = context->getPrimitiveParentObjectID(UUID);
1070
1071 if (!context->doesObjectExist(objID) || !context->doesObjectDataExist(objID, object_data_label.c_str())) {
1072 continue;
1073 }
1074
1075 uint labeldata;
1076
1077 HeliosDataType datatype = context->getObjectDataType(object_data_label.c_str());
1078 if (datatype == HELIOS_TYPE_UINT) {
1079 uint labeldata_ui;
1080 context->getObjectData(objID, object_data_label.c_str(), labeldata_ui);
1081 labeldata = labeldata_ui;
1082 } else if (datatype == HELIOS_TYPE_INT) {
1083 int labeldata_i;
1084 context->getObjectData(objID, object_data_label.c_str(), labeldata_i);
1085 labeldata = (uint) labeldata_i;
1086 } else {
1087 continue;
1088 }
1089
1090 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1091 pdata_bounds[labeldata] = make_vec4(1e6, -1, 1e6, -1);
1092 }
1093
1094 if (i < pdata_bounds[labeldata].x) {
1095 pdata_bounds[labeldata].x = i;
1096 }
1097 if (i > pdata_bounds[labeldata].y) {
1098 pdata_bounds[labeldata].y = i;
1099 }
1100 if (j < pdata_bounds[labeldata].z) {
1101 pdata_bounds[labeldata].z = j;
1102 }
1103 if (j > pdata_bounds[labeldata].w) {
1104 pdata_bounds[labeldata].w = j;
1105 }
1106 }
1107 }
1108
1109 for (auto box: pdata_bounds) {
1110 vec4 bbox = box.second;
1111 if (bbox.x == bbox.y || bbox.z == bbox.w) { // filter boxes of zeros size
1112 continue;
1113 }
1114 label_file << object_class_ID << " " << (bbox.x + 0.5 * (bbox.y - bbox.x)) / float(camera_resolution.x) << " " << (bbox.z + 0.5 * (bbox.w - bbox.z)) / float(camera_resolution.y) << " " << std::setprecision(6) << std::fixed
1115 << (bbox.y - bbox.x) / float(camera_resolution.x) << " " << (bbox.w - bbox.z) / float(camera_resolution.y) << std::endl;
1116 }
1117
1118 label_file.close();
1119}
1120
1121void RadiationModel::writeImageBoundingBoxes(const std::string &cameralabel, const std::string &primitive_data_label, const uint &object_class_ID, const std::string &image_file, const std::string &classes_txt_file, const std::string &image_path) {
1122 writeImageBoundingBoxes(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1123}
1124
1125void RadiationModel::writeImageBoundingBoxes(const std::string &cameralabel, const std::vector<std::string> &primitive_data_label, const std::vector<uint> &object_class_ID, const std::string &image_file, const std::string &classes_txt_file,
1126 const std::string &image_path) {
1127
1128 if (cameras.find(cameralabel) == cameras.end()) {
1129 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel + "' does not exist.");
1130 }
1131
1132 if (primitive_data_label.size() != object_class_ID.size()) {
1133 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
1134 }
1135
1136 // Get image UUID labels
1137 std::vector<uint> camera_UUIDs;
1138 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1139 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
1140 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
1141 }
1142 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1143 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1144 int2 camera_resolution = cameras.at(cameralabel).resolution;
1145
1146 std::string output_path = image_path;
1147 if (!image_path.empty() && !validateOutputPath(output_path)) {
1148 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
1149 } else if (!getFileName(output_path).empty()) {
1150 helios_runtime_error("ERROR(RadiationModel::writeImageBoundingBoxes): Image output directory contains a filename. This argument should be the path to a directory not a file.");
1151 }
1152
1153 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() + ".txt";
1154
1155 std::ofstream label_file(outfile_txt);
1156
1157 if (!label_file.is_open()) {
1158 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output bounding box file '" + outfile_txt + "'.");
1159 }
1160
1161 // Map to store bounding boxes for each data label class combination
1162 std::map<std::pair<uint, uint>, vec4> pdata_bounds; // (class_id, label_value) -> bbox
1163
1164 // Iterate through all pixels
1165 for (int j = 0; j < camera_resolution.y; j++) {
1166 for (int i = 0; i < camera_resolution.x; i++) {
1167 uint ii = camera_resolution.x - i - 1;
1168 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1169
1170 if (context->doesPrimitiveExist(UUID)) {
1171 // Check each primitive data label
1172 for (size_t label_idx = 0; label_idx < primitive_data_label.size(); label_idx++) {
1173 const std::string &data_label = primitive_data_label[label_idx];
1174 uint class_id = object_class_ID[label_idx];
1175
1176 if (context->doesPrimitiveDataExist(UUID, data_label.c_str())) {
1177 uint labeldata;
1178 bool has_data = false;
1179
1180 HeliosDataType datatype = context->getPrimitiveDataType(data_label.c_str());
1181 if (datatype == HELIOS_TYPE_UINT) {
1182 uint labeldata_ui;
1183 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_ui);
1184 labeldata = labeldata_ui;
1185 has_data = true;
1186 } else if (datatype == HELIOS_TYPE_INT) {
1187 int labeldata_i;
1188 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_i);
1189 labeldata = (uint) labeldata_i;
1190 has_data = true;
1191 }
1192
1193 if (has_data) {
1194 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1195
1196 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1197 pdata_bounds[key] = make_vec4(1e6, -1, 1e6, -1);
1198 }
1199
1200 if (i < pdata_bounds[key].x) {
1201 pdata_bounds[key].x = i;
1202 }
1203 if (i > pdata_bounds[key].y) {
1204 pdata_bounds[key].y = i;
1205 }
1206 if (j < pdata_bounds[key].z) {
1207 pdata_bounds[key].z = j;
1208 }
1209 if (j > pdata_bounds[key].w) {
1210 pdata_bounds[key].w = j;
1211 }
1212 }
1213 }
1214 }
1215 }
1216 }
1217 }
1218
1219 for (auto box: pdata_bounds) {
1220 uint class_id = box.first.first;
1221 vec4 bbox = box.second;
1222 if (bbox.x == bbox.y || bbox.z == bbox.w) { // filter boxes of zero size
1223 continue;
1224 }
1225 label_file << class_id << " " << (bbox.x + 0.5 * (bbox.y - bbox.x)) / float(camera_resolution.x) << " " << (bbox.z + 0.5 * (bbox.w - bbox.z)) / float(camera_resolution.y) << " " << std::setprecision(6) << std::fixed
1226 << (bbox.y - bbox.x) / float(camera_resolution.x) << " " << (bbox.w - bbox.z) / float(camera_resolution.y) << std::endl;
1227 }
1228
1229 label_file.close();
1230
1231 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1232 if (!classes_txt_stream.is_open()) {
1233 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output classes file '" + output_path + classes_txt_file + ".");
1234 }
1235 for (int i = 0; i < object_class_ID.size(); i++) {
1236 classes_txt_stream << object_class_ID.at(i) << " " << primitive_data_label.at(i) << std::endl;
1237 }
1238 classes_txt_stream.close();
1239}
1240
1241void RadiationModel::writeImageBoundingBoxes_ObjectData(const std::string &cameralabel, const std::string &object_data_label, const uint &object_class_ID, const std::string &image_file, const std::string &classes_txt_file,
1242 const std::string &image_path) {
1243 writeImageBoundingBoxes_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1244}
1245
1246void RadiationModel::writeImageBoundingBoxes_ObjectData(const std::string &cameralabel, const std::vector<std::string> &object_data_label, const std::vector<uint> &object_class_ID, const std::string &image_file, const std::string &classes_txt_file,
1247 const std::string &image_path) {
1248
1249 if (cameras.find(cameralabel) == cameras.end()) {
1250 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel + "' does not exist.");
1251 }
1252
1253 if (object_data_label.size() != object_class_ID.size()) {
1254 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
1255 }
1256
1257 // Get image UUID labels
1258 std::vector<uint> camera_UUIDs;
1259 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1260 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
1261 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
1262 }
1263 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1264 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1265 int2 camera_resolution = cameras.at(cameralabel).resolution;
1266
1267 std::string output_path = image_path;
1268 if (!image_path.empty() && !validateOutputPath(output_path)) {
1269 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path + "'. Check that the path exists and that you have write permission.");
1270 } else if (!getFileName(output_path).empty()) {
1271 helios_runtime_error("ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Image output directory contains a filename. This argument should be the path to a directory not a file.");
1272 }
1273
1274 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() + ".txt";
1275
1276 std::ofstream label_file(outfile_txt);
1277
1278 if (!label_file.is_open()) {
1279 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output bounding box file '" + outfile_txt + "'.");
1280 }
1281
1282 // Map to store bounding boxes for each data label class combination
1283 std::map<std::pair<uint, uint>, vec4> pdata_bounds; // (class_id, label_value) -> bbox
1284
1285 // Iterate through all pixels
1286 for (int j = 0; j < camera_resolution.y; j++) {
1287 for (int i = 0; i < camera_resolution.x; i++) {
1288 uint ii = camera_resolution.x - i - 1;
1289 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1290
1291 if (!context->doesPrimitiveExist(UUID)) {
1292 continue;
1293 }
1294
1295 uint objID = context->getPrimitiveParentObjectID(UUID);
1296
1297 if (!context->doesObjectExist(objID)) {
1298 continue;
1299 }
1300
1301 // Check each object data label
1302 for (size_t label_idx = 0; label_idx < object_data_label.size(); label_idx++) {
1303 const std::string &data_label = object_data_label[label_idx];
1304 uint class_id = object_class_ID[label_idx];
1305
1306 if (context->doesObjectDataExist(objID, data_label.c_str())) {
1307 uint labeldata;
1308 bool has_data = false;
1309
1310 HeliosDataType datatype = context->getObjectDataType(data_label.c_str());
1311 if (datatype == HELIOS_TYPE_UINT) {
1312 uint labeldata_ui;
1313 context->getObjectData(objID, data_label.c_str(), labeldata_ui);
1314 labeldata = labeldata_ui;
1315 has_data = true;
1316 } else if (datatype == HELIOS_TYPE_INT) {
1317 int labeldata_i;
1318 context->getObjectData(objID, data_label.c_str(), labeldata_i);
1319 labeldata = (uint) labeldata_i;
1320 has_data = true;
1321 }
1322
1323 if (has_data) {
1324 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1325
1326 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1327 pdata_bounds[key] = make_vec4(1e6, -1, 1e6, -1);
1328 }
1329
1330 if (i < pdata_bounds[key].x) {
1331 pdata_bounds[key].x = i;
1332 }
1333 if (i > pdata_bounds[key].y) {
1334 pdata_bounds[key].y = i;
1335 }
1336 if (j < pdata_bounds[key].z) {
1337 pdata_bounds[key].z = j;
1338 }
1339 if (j > pdata_bounds[key].w) {
1340 pdata_bounds[key].w = j;
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347
1348 for (auto box: pdata_bounds) {
1349 uint class_id = box.first.first;
1350 vec4 bbox = box.second;
1351 if (bbox.x == bbox.y || bbox.z == bbox.w) { // filter boxes of zero size
1352 continue;
1353 }
1354 label_file << class_id << " " << (bbox.x + 0.5 * (bbox.y - bbox.x)) / float(camera_resolution.x) << " " << (bbox.z + 0.5 * (bbox.w - bbox.z)) / float(camera_resolution.y) << " " << std::setprecision(6) << std::fixed
1355 << (bbox.y - bbox.x) / float(camera_resolution.x) << " " << (bbox.w - bbox.z) / float(camera_resolution.y) << std::endl;
1356 }
1357
1358 label_file.close();
1359
1360 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1361 if (!classes_txt_stream.is_open()) {
1362 helios_runtime_error("ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output classes file '" + output_path + classes_txt_file + ".");
1363 }
1364 for (int i = 0; i < object_class_ID.size(); i++) {
1365 classes_txt_stream << object_class_ID.at(i) << " " << object_data_label.at(i) << std::endl;
1366 }
1367 classes_txt_stream.close();
1368}
1369
1370// Helper function to initialize or load existing COCO JSON structure and get image ID
1371std::pair<nlohmann::json, int> RadiationModel::initializeCOCOJsonWithImageId(const std::string &filename, bool append_file, const std::string &cameralabel, const helios::int2 &camera_resolution, const std::string &image_file) {
1372 nlohmann::json coco_json;
1373 int image_id = 0;
1374
1375 if (append_file) {
1376 std::ifstream existing_file(filename);
1377 if (existing_file.is_open()) {
1378 try {
1379 existing_file >> coco_json;
1380 } catch (const std::exception &e) {
1381 coco_json.clear();
1382 }
1383 existing_file.close();
1384 }
1385 }
1386
1387 // Initialize JSON structure if empty
1388 if (coco_json.empty()) {
1389 coco_json["categories"] = nlohmann::json::array();
1390 coco_json["images"] = nlohmann::json::array();
1391 coco_json["annotations"] = nlohmann::json::array();
1392 }
1393
1394 // Extract just the filename (no path) from the image file
1395 std::filesystem::path image_path_obj(image_file);
1396 std::string filename_only = image_path_obj.filename().string();
1397
1398 // Check if this image already exists in the JSON
1399 bool image_exists = false;
1400 for (const auto &img: coco_json["images"]) {
1401 if (img["file_name"] == filename_only) {
1402 image_id = img["id"];
1403 image_exists = true;
1404 break;
1405 }
1406 }
1407
1408 // If image doesn't exist, add it with a new unique ID
1409 if (!image_exists) {
1410 // Find the next available image ID
1411 int max_image_id = -1;
1412 for (const auto &img: coco_json["images"]) {
1413 if (img["id"] > max_image_id) {
1414 max_image_id = img["id"];
1415 }
1416 }
1417 image_id = max_image_id + 1;
1418
1419 // Add the new image entry
1420 nlohmann::json image_entry;
1421 image_entry["id"] = image_id;
1422 image_entry["file_name"] = filename_only;
1423 image_entry["height"] = camera_resolution.y;
1424 image_entry["width"] = camera_resolution.x;
1425 coco_json["images"].push_back(image_entry);
1426 }
1427
1428 return std::make_pair(coco_json, image_id);
1429}
1430
1431// Helper function to initialize or load existing COCO JSON structure (backward compatibility)
1432nlohmann::json RadiationModel::initializeCOCOJson(const std::string &filename, bool append_file, const std::string &cameralabel, const helios::int2 &camera_resolution, const std::string &image_file) {
1433 return initializeCOCOJsonWithImageId(filename, append_file, cameralabel, camera_resolution, image_file).first;
1434}
1435
1436// Helper function to add category to COCO JSON if it doesn't exist
1437void RadiationModel::addCategoryToCOCO(nlohmann::json &coco_json, const std::vector<uint> &object_class_ID, const std::vector<std::string> &category_name) {
1438 if (object_class_ID.size() != category_name.size()) {
1439 helios_runtime_error("ERROR (RadiationModel::addCategoryToCOCO): The lengths of object_class_ID and category_name vectors must be the same.");
1440 }
1441
1442 for (size_t i = 0; i < object_class_ID.size(); ++i) {
1443 bool category_exists = false;
1444 for (auto &cat: coco_json["categories"]) {
1445 if (cat["id"] == object_class_ID[i]) {
1446 category_exists = true;
1447 break;
1448 }
1449 }
1450 if (!category_exists) {
1451 nlohmann::json category;
1452 category["id"] = object_class_ID[i];
1453 category["name"] = category_name[i];
1454 category["supercategory"] = "none";
1455 coco_json["categories"].push_back(category);
1456 }
1457 }
1458}
1459
1460// Helper function to write COCO JSON with proper formatting
1461void RadiationModel::writeCOCOJson(const nlohmann::json &coco_json, const std::string &filename) {
1462 std::ofstream json_file(filename);
1463 if (!json_file.is_open()) {
1464 helios_runtime_error("ERROR (RadiationModel): Could not open file '" + filename + "'.");
1465 }
1466
1467 // Use standard JSON formatting for now (can optimize array formatting later)
1468 json_file << coco_json.dump(2) << std::endl;
1469 json_file.close();
1470}
1471
1472// Helper function to generate label masks from either primitive or object data
1473std::map<int, std::vector<std::vector<bool>>> RadiationModel::generateLabelMasks(const std::string &cameralabel, const std::string &data_label, bool use_object_data) {
1474 std::vector<uint> camera_UUIDs;
1475 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1476 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1477 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1478 int2 camera_resolution = cameras.at(cameralabel).resolution;
1479
1480 std::map<int, std::vector<std::vector<bool>>> label_masks;
1481
1482 // First pass: identify all unique labels and create binary masks
1483 for (int j = 0; j < camera_resolution.y; j++) {
1484 for (int i = 0; i < camera_resolution.x; i++) {
1485 uint ii = camera_resolution.x - i - 1;
1486 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1487
1488 if (context->doesPrimitiveExist(UUID)) {
1489 uint labeldata;
1490 bool has_data = false;
1491
1492 if (use_object_data) {
1493 // Object data version
1494 uint objID = context->getPrimitiveParentObjectID(UUID);
1495 if (objID != 0 && context->doesObjectDataExist(objID, data_label.c_str())) {
1496 HeliosDataType datatype = context->getObjectDataType(data_label.c_str());
1497 if (datatype == HELIOS_TYPE_UINT) {
1498 uint labeldata_ui;
1499 context->getObjectData(objID, data_label.c_str(), labeldata_ui);
1500 labeldata = labeldata_ui;
1501 has_data = true;
1502 } else if (datatype == HELIOS_TYPE_INT) {
1503 int labeldata_i;
1504 context->getObjectData(objID, data_label.c_str(), labeldata_i);
1505 labeldata = (uint) labeldata_i;
1506 has_data = true;
1507 }
1508 }
1509 } else {
1510 // Primitive data version
1511 if (context->doesPrimitiveDataExist(UUID, data_label.c_str())) {
1512 HeliosDataType datatype = context->getPrimitiveDataType(data_label.c_str());
1513 if (datatype == HELIOS_TYPE_UINT) {
1514 uint labeldata_ui;
1515 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_ui);
1516 labeldata = labeldata_ui;
1517 has_data = true;
1518 } else if (datatype == HELIOS_TYPE_INT) {
1519 int labeldata_i;
1520 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_i);
1521 labeldata = (uint) labeldata_i;
1522 has_data = true;
1523 }
1524 }
1525 }
1526
1527 if (has_data) {
1528 // Initialize mask for this label if not exists
1529 if (label_masks.find(labeldata) == label_masks.end()) {
1530 label_masks[labeldata] = std::vector<std::vector<bool>>(camera_resolution.y, std::vector<bool>(camera_resolution.x, false));
1531 }
1532 label_masks[labeldata][j][i] = true;
1533 }
1534 }
1535 }
1536 }
1537
1538 return label_masks;
1539}
1540
1541// Helper function to find starting boundary pixel (topmost-leftmost)
1542std::pair<int, int> RadiationModel::findStartingBoundaryPixel(const std::vector<std::vector<bool>> &mask, const helios::int2 &camera_resolution) {
1543 for (int j = 0; j < camera_resolution.y; j++) {
1544 for (int i = 0; i < camera_resolution.x; i++) {
1545 if (mask[j][i]) {
1546 // Check if this pixel is on the boundary
1547 for (int di = -1; di <= 1; di++) {
1548 for (int dj = -1; dj <= 1; dj++) {
1549 if (di == 0 && dj == 0)
1550 continue;
1551 int ni = i + di;
1552 int nj = j + dj;
1553 if (ni < 0 || ni >= camera_resolution.x || nj < 0 || nj >= camera_resolution.y || !mask[nj][ni]) {
1554 return {i, j}; // Found boundary pixel
1555 }
1556 }
1557 }
1558 }
1559 }
1560 }
1561 return {-1, -1}; // No boundary found
1562}
1563
1564// Helper function to trace boundary using Moore neighborhood algorithm
1565std::vector<std::pair<int, int>> RadiationModel::traceBoundaryMoore(const std::vector<std::vector<bool>> &mask, int start_x, int start_y, const helios::int2 &camera_resolution) {
1566 std::vector<std::pair<int, int>> contour;
1567
1568 // 8-connected neighbors in clockwise order starting from East
1569 int dx[] = {1, 1, 0, -1, -1, -1, 0, 1};
1570 int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
1571
1572 int x = start_x, y = start_y;
1573 int dir = 6; // Start looking West (opposite of East)
1574
1575 do {
1576 contour.push_back({x, y});
1577
1578 // Look for next boundary pixel
1579 int start_dir = (dir + 6) % 8; // Start looking 3 positions counter-clockwise from where we came
1580 bool found = false;
1581
1582 for (int i = 0; i < 8; i++) {
1583 int check_dir = (start_dir + i) % 8;
1584 int nx = x + dx[check_dir];
1585 int ny = y + dy[check_dir];
1586
1587 // Check if this neighbor is inside bounds and inside the mask
1588 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.y && mask[ny][nx]) {
1589 x = nx;
1590 y = ny;
1591 dir = check_dir;
1592 found = true;
1593 break;
1594 }
1595 }
1596
1597 if (!found)
1598 break; // No next boundary pixel found
1599
1600 } while (!(x == start_x && y == start_y) && contour.size() < camera_resolution.x * camera_resolution.y);
1601
1602 return contour;
1603}
1604
1605// Helper function to trace boundary using simple connected components
1606std::vector<std::pair<int, int>> RadiationModel::traceBoundarySimple(const std::vector<std::vector<bool>> &mask, int start_x, int start_y, const helios::int2 &camera_resolution) {
1607 std::vector<std::pair<int, int>> contour;
1608 std::set<std::pair<int, int>> visited_boundary;
1609
1610 // Use a simple approach: walk along the boundary
1611 std::queue<std::pair<int, int>> boundary_queue;
1612 boundary_queue.push({start_x, start_y});
1613 visited_boundary.insert({start_x, start_y});
1614
1615 while (!boundary_queue.empty()) {
1616 auto [x, y] = boundary_queue.front();
1617 boundary_queue.pop();
1618 contour.push_back({x, y});
1619
1620 // 8-connected neighbors
1621 for (int di = -1; di <= 1; di++) {
1622 for (int dj = -1; dj <= 1; dj++) {
1623 if (di == 0 && dj == 0)
1624 continue;
1625 int nx = x + di;
1626 int ny = y + dj;
1627
1628 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.y && mask[ny][nx] && visited_boundary.find({nx, ny}) == visited_boundary.end()) {
1629
1630 // Check if this pixel is on the boundary
1631 bool is_boundary = false;
1632 for (int ddi = -1; ddi <= 1; ddi++) {
1633 for (int ddj = -1; ddj <= 1; ddj++) {
1634 if (ddi == 0 && ddj == 0)
1635 continue;
1636 int nnx = nx + ddi;
1637 int nny = ny + ddj;
1638 if (nnx < 0 || nnx >= camera_resolution.x || nny < 0 || nny >= camera_resolution.y || !mask[nny][nnx]) {
1639 is_boundary = true;
1640 break;
1641 }
1642 }
1643 if (is_boundary)
1644 break;
1645 }
1646
1647 if (is_boundary) {
1648 boundary_queue.push({nx, ny});
1649 visited_boundary.insert({nx, ny});
1650 }
1651 }
1652 }
1653 }
1654 }
1655
1656 return contour;
1657}
1658
1659// Helper function to generate annotations from label masks
1660std::vector<std::map<std::string, std::vector<float>>> RadiationModel::generateAnnotationsFromMasks(const std::map<int, std::vector<std::vector<bool>>> &label_masks, uint object_class_ID, const helios::int2 &camera_resolution, int image_id) {
1661 std::vector<std::map<std::string, std::vector<float>>> annotations;
1662 int annotation_id = 0;
1663
1664 for (const auto &label_pair: label_masks) {
1665 int label_value = label_pair.first;
1666 const auto &mask = label_pair.second;
1667
1668 // Create a visited mask for connected components
1669 std::vector<std::vector<bool>> visited(camera_resolution.y, std::vector<bool>(camera_resolution.x, false));
1670
1671 // Find all connected components for this label
1672 for (int j = 0; j < camera_resolution.y; j++) {
1673 for (int i = 0; i < camera_resolution.x; i++) {
1674 if (mask[j][i] && !visited[j][i]) {
1675 // Find boundary pixel for this component
1676 int boundary_i = i, boundary_j = j;
1677 bool is_boundary = false;
1678
1679 // Check if this pixel is on the boundary
1680 for (int di = -1; di <= 1; di++) {
1681 for (int dj = -1; dj <= 1; dj++) {
1682 int ni = i + di;
1683 int nj = j + dj;
1684 if (ni < 0 || ni >= camera_resolution.x || nj < 0 || nj >= camera_resolution.y || !mask[nj][ni]) {
1685 is_boundary = true;
1686 boundary_i = i;
1687 boundary_j = j;
1688 break;
1689 }
1690 }
1691 if (is_boundary)
1692 break;
1693 }
1694
1695 if (is_boundary) {
1696 // First, mark all pixels in this connected component using flood fill
1697 std::stack<std::pair<int, int>> stack;
1698 std::vector<std::pair<int, int>> component_pixels;
1699 stack.push({i, j});
1700 visited[j][i] = true;
1701
1702 int min_x = i, max_x = i, min_y = j, max_y = j;
1703 int area = 0;
1704
1705 while (!stack.empty()) {
1706 auto [ci, cj] = stack.top();
1707 stack.pop();
1708 area++;
1709 component_pixels.push_back({ci, cj});
1710
1711 min_x = std::min(min_x, ci);
1712 max_x = std::max(max_x, ci);
1713 min_y = std::min(min_y, cj);
1714 max_y = std::max(max_y, cj);
1715
1716 // Check 4-connected neighbors
1717 for (int di = -1; di <= 1; di++) {
1718 for (int dj = -1; dj <= 1; dj++) {
1719 if (abs(di) + abs(dj) != 1)
1720 continue; // Only 4-connected
1721 int ni = ci + di;
1722 int nj = cj + dj;
1723 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.y && mask[nj][ni] && !visited[nj][ni]) {
1724 stack.push({ni, nj});
1725 visited[nj][ni] = true;
1726 }
1727 }
1728 }
1729 }
1730
1731 // Now trace the boundary of this component
1732 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
1733 bool is_boundary_start = false;
1734
1735 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
1736 is_boundary_start = true;
1737 }
1738
1739 if (is_boundary_start) {
1740 // Try Moore neighborhood boundary tracing first
1741 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
1742
1743 // If Moore tracing didn't work well, fall back to simple boundary collection
1744 if (contour.size() < 10) {
1745 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
1746 }
1747
1748 if (contour.size() >= 3) {
1749 // Create annotation
1750 std::map<std::string, std::vector<float>> annotation;
1751 annotation["id"] = {(float) annotation_id++};
1752 annotation["image_id"] = {(float) image_id};
1753 annotation["category_id"] = {(float) object_class_ID};
1754 annotation["bbox"] = {(float) min_x, (float) min_y, (float) (max_x - min_x), (float) (max_y - min_y)};
1755 annotation["area"] = {(float) area};
1756 annotation["iscrowd"] = {0.0f};
1757
1758 // Convert contour to segmentation format (flatten coordinates)
1759 std::vector<float> segmentation;
1760 for (const auto &point: contour) {
1761 segmentation.push_back((float) point.first); // x coordinate
1762 segmentation.push_back((float) point.second); // y coordinate
1763 }
1764 annotation["segmentation"] = segmentation;
1765
1766 annotations.push_back(annotation);
1767 }
1768 }
1769 }
1770 }
1771 }
1772 }
1773 }
1774
1775 return annotations;
1776}
1777
1778void RadiationModel::writeImageSegmentationMasks(const std::string &cameralabel, const std::string &primitive_data_label, const uint &object_class_ID, const std::string &json_filename, const std::string &image_file, bool append_file) {
1779 writeImageSegmentationMasks(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, append_file);
1780}
1781
1782void RadiationModel::writeImageSegmentationMasks(const std::string &cameralabel, const std::vector<std::string> &primitive_data_label, const std::vector<uint> &object_class_ID, const std::string &json_filename, const std::string &image_file,
1783 bool append_file) {
1784
1785 if (cameras.find(cameralabel) == cameras.end()) {
1786 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks): Camera '" + cameralabel + "' does not exist.");
1787 }
1788
1789 if (primitive_data_label.size() != object_class_ID.size()) {
1790 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
1791 }
1792
1793 // Check that camera pixel data exists
1794 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1795 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
1796 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
1797 }
1798
1799 // Check that all primitive data labels exist
1800 std::vector<std::string> all_primitive_data = context->listAllPrimitiveDataLabels();
1801 for (const auto &data_label: primitive_data_label) {
1802 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), data_label) == all_primitive_data.end()) {
1803 std::cerr << "WARNING (RadiationModel::writeImageSegmentationMasks): Primitive data label '" << data_label << "' does not exist in the context." << std::endl;
1804 }
1805 }
1806
1807 // Check that image file exists
1808 if (!std::filesystem::exists(image_file)) {
1809 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks): Image file '" + image_file + "' does not exist.");
1810 }
1811
1812 // Validate and ensure JSON filename has .json extension
1813 std::string validated_json_filename = json_filename;
1814 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) != ".json") {
1815 validated_json_filename += ".json";
1816 }
1817
1818 // Use the validated filename directly
1819 std::string outfile = validated_json_filename;
1820
1821 // Write annotations to JSON file
1822 int2 camera_resolution = cameras.at(cameralabel).resolution;
1823 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
1824 nlohmann::json coco_json = coco_json_pair.first;
1825 int image_id = coco_json_pair.second;
1826 addCategoryToCOCO(coco_json, object_class_ID, primitive_data_label);
1827
1828 // Process each data label and class ID pair
1829 for (size_t i = 0; i < primitive_data_label.size(); ++i) {
1830 // Generate label masks using helper function (primitive data version)
1831 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, primitive_data_label[i], false);
1832
1833 // Generate annotations from masks using helper function
1834 std::vector<std::map<std::string, std::vector<float>>> annotations = generateAnnotationsFromMasks(label_masks, object_class_ID[i], camera_resolution, image_id);
1835
1836 // Find the highest existing annotation ID to avoid conflicts
1837 int max_annotation_id = -1;
1838 for (const auto &existing_ann: coco_json["annotations"]) {
1839 if (existing_ann["id"] > max_annotation_id) {
1840 max_annotation_id = existing_ann["id"];
1841 }
1842 }
1843
1844 // Add new annotations for this data label
1845 for (const auto &ann: annotations) {
1846 nlohmann::json json_annotation;
1847 json_annotation["id"] = max_annotation_id + 1;
1848 json_annotation["image_id"] = (int) ann.at("image_id")[0];
1849 json_annotation["category_id"] = (int) ann.at("category_id")[0];
1850
1851 const auto &bbox = ann.at("bbox");
1852 json_annotation["bbox"] = {(int) bbox[0], (int) bbox[1], (int) bbox[2], (int) bbox[3]};
1853 json_annotation["area"] = (int) ann.at("area")[0];
1854
1855 const auto &seg = ann.at("segmentation");
1856 std::vector<int> segmentation_coords;
1857 for (float coord: seg) {
1858 segmentation_coords.push_back((int) coord);
1859 }
1860 json_annotation["segmentation"] = {segmentation_coords};
1861 json_annotation["iscrowd"] = (int) ann.at("iscrowd")[0];
1862
1863 coco_json["annotations"].push_back(json_annotation);
1864 max_annotation_id++;
1865 }
1866 }
1867
1868 // Write JSON to file
1869 writeCOCOJson(coco_json, outfile);
1870}
1871
1872void RadiationModel::writeImageSegmentationMasks_ObjectData(const std::string &cameralabel, const std::string &object_data_label, const uint &object_class_ID, const std::string &json_filename, const std::string &image_file, bool append_file) {
1873 writeImageSegmentationMasks_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, append_file);
1874}
1875
1876void RadiationModel::writeImageSegmentationMasks_ObjectData(const std::string &cameralabel, const std::vector<std::string> &object_data_label, const std::vector<uint> &object_class_ID, const std::string &json_filename, const std::string &image_file,
1877 bool append_file) {
1878
1879 if (cameras.find(cameralabel) == cameras.end()) {
1880 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Camera '" + cameralabel + "' does not exist.");
1881 }
1882
1883 if (object_data_label.size() != object_class_ID.size()) {
1884 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
1885 }
1886
1887 // Check that camera pixel data exists
1888 std::string global_data_label = "camera_" + cameralabel + "_pixel_UUID";
1889 if (!context->doesGlobalDataExist(global_data_label.c_str())) {
1890 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Pixel labels for camera '" + cameralabel + "' do not exist. Was the radiation model run to generate labels?");
1891 }
1892
1893 // Check that all object data labels exist
1894 std::vector<std::string> all_object_data = context->listAllObjectDataLabels();
1895 for (const auto &data_label: object_data_label) {
1896 if (std::find(all_object_data.begin(), all_object_data.end(), data_label) == all_object_data.end()) {
1897 std::cerr << "WARNING (RadiationModel::writeImageSegmentationMasks_ObjectData): Object data label '" << data_label << "' does not exist in the context." << std::endl;
1898 }
1899 }
1900
1901 // Check that image file exists
1902 if (!std::filesystem::exists(image_file)) {
1903 helios_runtime_error("ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Image file '" + image_file + "' does not exist.");
1904 }
1905
1906 // Validate and ensure JSON filename has .json extension
1907 std::string validated_json_filename = json_filename;
1908 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) != ".json") {
1909 validated_json_filename += ".json";
1910 }
1911
1912 // Use the validated filename directly
1913 std::string outfile = validated_json_filename;
1914
1915 // Write annotations to JSON file
1916 int2 camera_resolution = cameras.at(cameralabel).resolution;
1917 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
1918 nlohmann::json coco_json = coco_json_pair.first;
1919 int image_id = coco_json_pair.second;
1920 addCategoryToCOCO(coco_json, object_class_ID, object_data_label);
1921
1922 // Process each data label and class ID pair
1923 for (size_t i = 0; i < object_data_label.size(); ++i) {
1924 // Generate label masks using helper function (object data version)
1925 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, object_data_label[i], true);
1926
1927 // Generate annotations from masks using helper function
1928 std::vector<std::map<std::string, std::vector<float>>> annotations = generateAnnotationsFromMasks(label_masks, object_class_ID[i], camera_resolution, image_id);
1929
1930 // Find the highest existing annotation ID to avoid conflicts
1931 int max_annotation_id = -1;
1932 for (const auto &existing_ann: coco_json["annotations"]) {
1933 if (existing_ann["id"] > max_annotation_id) {
1934 max_annotation_id = existing_ann["id"];
1935 }
1936 }
1937
1938 // Add new annotations for this data label
1939 for (const auto &ann: annotations) {
1940 nlohmann::json json_annotation;
1941 json_annotation["id"] = max_annotation_id + 1;
1942 json_annotation["image_id"] = (int) ann.at("image_id")[0];
1943 json_annotation["category_id"] = (int) ann.at("category_id")[0];
1944
1945 const auto &bbox = ann.at("bbox");
1946 json_annotation["bbox"] = {(int) bbox[0], (int) bbox[1], (int) bbox[2], (int) bbox[3]};
1947 json_annotation["area"] = (int) ann.at("area")[0];
1948
1949 const auto &seg = ann.at("segmentation");
1950 std::vector<int> segmentation_coords;
1951 for (float coord: seg) {
1952 segmentation_coords.push_back((int) coord);
1953 }
1954 json_annotation["segmentation"] = {segmentation_coords};
1955 json_annotation["iscrowd"] = (int) ann.at("iscrowd")[0];
1956
1957 coco_json["annotations"].push_back(json_annotation);
1958 max_annotation_id++;
1959 }
1960 }
1961
1962 // Write JSON to file
1963 writeCOCOJson(coco_json, outfile);
1964}
1965
1966void RadiationModel::setPadValue(const std::string &cameralabel, const std::vector<std::string> &bandlabels, const std::vector<float> &padvalues) {
1967 for (uint b = 0; b < bandlabels.size(); b++) {
1968 std::string bandlabel = bandlabels.at(b);
1969
1970 std::string image_value_label = "camera_" + cameralabel + "_" + bandlabel;
1971 std::vector<float> cameradata;
1972 context->getGlobalData(image_value_label.c_str(), cameradata);
1973
1974 std::vector<uint> camera_UUIDs;
1975 std::string image_UUID_label = "camera_" + cameralabel + "_pixel_UUID";
1976 context->getGlobalData(image_UUID_label.c_str(), camera_UUIDs);
1977
1978 for (uint i = 0; i < cameradata.size(); i++) {
1979 uint UUID = camera_UUIDs.at(i) - 1;
1980 if (!context->doesPrimitiveExist(UUID)) {
1981 cameradata.at(i) = padvalues.at(b);
1982 }
1983 }
1984 context->setGlobalData(image_value_label.c_str(), cameradata);
1985 }
1986}
1987
1988void RadiationModel::calibrateCamera(const std::string &originalcameralabel, const std::vector<std::string> &sourcelabels, const std::vector<std::string> &cameraresplabels_raw, const std::vector<std::string> &bandlabels, const float scalefactor,
1989 const std::vector<std::vector<float>> &truevalues, const std::string &calibratedmark) {
1990
1991 if (cameras.find(originalcameralabel) == cameras.end()) {
1992 helios_runtime_error("ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel + " does not exist.");
1993 } else if (radiation_sources.empty()) {
1994 helios_runtime_error("ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
1995 }
1996
1997 CameraCalibration cameracalibration_(context);
1998 if (!calibration_flag) {
1999 std::cout << "No color board added, use default color calibration." << std::endl;
2000 cameracalibration = &cameracalibration_;
2001 vec3 centrelocation = make_vec3(0, 0, 0.2); // Location of color board
2002 vec3 rotationrad = make_vec3(0, 0, 1.5705); // Rotation angle of color board
2003 cameracalibration->addDefaultColorboard(centrelocation, 0.1, rotationrad);
2004 }
2005 vec2 wavelengthrange = make_vec2(-10000, 10000);
2006
2007 // Calibrated camera response labels
2008 std::vector<std::string> cameraresplabels_cal(cameraresplabels_raw.size());
2009
2010 for (int iband = 0; iband < bandlabels.size(); iband++) {
2011 cameraresplabels_cal.at(iband) = calibratedmark + "_" + cameraresplabels_raw.at(iband);
2012 }
2013
2014 RadiationModel::runRadiationImaging(originalcameralabel, sourcelabels, bandlabels, cameraresplabels_raw, wavelengthrange, 1, 0);
2015 // Update camera responses
2016 RadiationModel::updateCameraResponse(originalcameralabel, sourcelabels, cameraresplabels_raw, wavelengthrange, truevalues, calibratedmark);
2017
2018 float camerascale = RadiationModel::getCameraResponseScale(originalcameralabel, cameraresplabels_cal, bandlabels, sourcelabels, wavelengthrange, truevalues);
2019
2020 std::cout << "Camera response scale: " << camerascale << std::endl;
2021 // Scale and write calibrated camera responses
2022 cameracalibration->writeCalibratedCameraResponses(cameraresplabels_raw, calibratedmark, camerascale * scalefactor);
2023}
2024
2025void RadiationModel::calibrateCamera(const std::string &originalcameralabel, const float scalefactor, const std::vector<std::vector<float>> &truevalues, const std::string &calibratedmark) {
2026
2027 if (cameras.find(originalcameralabel) == cameras.end()) {
2028 helios_runtime_error("ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel + " does not exist.");
2029 } else if (radiation_sources.empty()) {
2030 helios_runtime_error("ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2031 }
2032
2033 CameraCalibration cameracalibration_(context);
2034 if (!calibration_flag) {
2035 std::cout << "No color board added, use default color calibration." << std::endl;
2036 vec3 centrelocation = make_vec3(0, 0, 0.2); // Location of color board
2037 vec3 rotationrad = make_vec3(0, 0, 1.5705); // Rotation angle of color board
2038 cameracalibration_.addDefaultColorboard(centrelocation, 0.1, rotationrad);
2039 RadiationModel::setCameraCalibration(&cameracalibration_);
2040 }
2041
2042 vec2 wavelengthrange = make_vec2(-10000, 10000);
2043
2044 std::vector<std::string> bandlabels = cameras.at(originalcameralabel).band_labels;
2045
2046 // Get camera response spectra labels from camera
2047 std::vector<std::string> cameraresplabels_cal(cameras.at(originalcameralabel).band_spectral_response.size());
2048 std::vector<std::string> cameraresplabels_raw = cameraresplabels_cal;
2049
2050 int iband = 0;
2051 for (auto &band: cameras.at(originalcameralabel).band_spectral_response) {
2052 cameraresplabels_raw.at(iband) = band.second;
2053 cameraresplabels_cal.at(iband) = calibratedmark + "_" + band.second;
2054 iband++;
2055 }
2056
2057 // Get labels of radiation sources from camera
2058 std::vector<std::string> sourcelabels(radiation_sources.size());
2059 int isource = 0;
2060 for (auto &source: radiation_sources) {
2061 if (source.source_spectrum.empty()) {
2062 helios_runtime_error("ERROR (RadiationModel::calibrateCamera): A spectral distribution was not specified for source " + source.source_spectrum_label + ". Cannot perform camera calibration.");
2063 }
2064 sourcelabels.at(isource) = source.source_spectrum_label;
2065 isource++;
2066 }
2067
2069 RadiationModel::runBand(bandlabels);
2070 // Update camera responses
2071 RadiationModel::updateCameraResponse(originalcameralabel, sourcelabels, cameraresplabels_raw, wavelengthrange, truevalues, calibratedmark);
2072
2073 float camerascale = RadiationModel::getCameraResponseScale(originalcameralabel, cameraresplabels_cal, bandlabels, sourcelabels, wavelengthrange, truevalues);
2074
2075 std::cout << "Camera response scale: " << camerascale << std::endl;
2076 // Scale and write calibrated camera responses
2077 cameracalibration->writeCalibratedCameraResponses(cameraresplabels_raw, calibratedmark, camerascale * scalefactor);
2078}
2079
2080std::vector<helios::vec2> RadiationModel::generateGaussianCameraResponse(float FWHM, float mu, float centrawavelength, const helios::int2 &wavebanrange) {
2081
2082 // Convert FWHM to sigma
2083 float sigma = FWHM / (2 * std::sqrt(2 * std::log(2)));
2084
2085 size_t lenspectra = wavebanrange.y - wavebanrange.x;
2086 std::vector<helios::vec2> cameraresponse(lenspectra);
2087
2088
2089 for (int i = 0; i < lenspectra; ++i) {
2090 cameraresponse.at(i).x = float(wavebanrange.x + i);
2091 }
2092
2093 // Gaussian function
2094 for (size_t i = 0; i < lenspectra; ++i) {
2095 cameraresponse.at(i).y = centrawavelength * std::exp(-std::pow((cameraresponse.at(i).x - mu), 2) / (2 * std::pow(sigma, 2)));
2096 }
2097
2098
2099 return cameraresponse;
2100}
2101
2102void RadiationModel::applyImageProcessingPipeline(const std::string &cameralabel, const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float saturation_adjustment, float brightness_adjustment,
2103 float contrast_adjustment, float gain_adjustment) {
2104
2105 if (cameras.find(cameralabel) == cameras.end()) {
2106 helios_runtime_error("ERROR (RadiationModel::applyImageProcessingPipeline): Camera '" + cameralabel + "' does not exist.");
2107 }
2108 RadiationCamera &camera = cameras.at(cameralabel);
2109 if (camera.pixel_data.find(red_band_label) == camera.pixel_data.end() || camera.pixel_data.find(green_band_label) == camera.pixel_data.end() || camera.pixel_data.find(blue_band_label) == camera.pixel_data.end()) {
2110 helios_runtime_error("ERROR (RadiationModel::applyImageProcessingPipeline): One or more specified band labels do not exist for the camera pixel data.");
2111 }
2112
2113 // Step 1: Smart Auto-Exposure (percentile-based normalization)
2114 camera.autoExposure(red_band_label, green_band_label, blue_band_label, gain_adjustment);
2115
2116 // Step 2: Camera spectral correction
2117 camera.applyCameraSpectralCorrection(red_band_label, green_band_label, blue_band_label, this->context);
2118
2119 // Step 3: Scene-adaptive white balance
2120 camera.whiteBalanceAuto(red_band_label, green_band_label, blue_band_label);
2121
2122 // Step 4: Brightness and contrast adjustments in linear space
2123 if (brightness_adjustment != 1.f || contrast_adjustment != 1.f) {
2124 camera.adjustBrightnessContrast(red_band_label, green_band_label, blue_band_label, brightness_adjustment, contrast_adjustment);
2125 }
2126
2127 // Step 5: Saturation adjustment
2128 if (saturation_adjustment != 1.f) {
2129 camera.adjustSaturation(red_band_label, green_band_label, blue_band_label, saturation_adjustment);
2130 }
2131
2132 // Step 6: Gamma compression (final step)
2133 camera.gammaCompress(red_band_label, green_band_label, blue_band_label);
2134}
2135
2137
2138 float min_P = (std::numeric_limits<float>::max)();
2139 float max_P = 0.0f;
2140 for (const auto &[channel_label, data]: pixel_data) {
2141 for (float v: data) {
2142 if (v < min_P) {
2143 min_P = v;
2144 }
2145 if (v > max_P) {
2146 max_P = v;
2147 }
2148 }
2149 }
2150
2151 for (auto &[channel_label, data]: pixel_data) {
2152 for (float &v: data) {
2153 v = (v - min_P) / (max_P - min_P); // Normalize to [0, 1]
2154 }
2155 }
2156}
2157
2158void RadiationCamera::whiteBalance(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float p) {
2159
2160#ifdef HELIOS_DEBUG
2161 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2162 helios_runtime_error("ERROR (RadiationModel::whiteBalance): One or more specified band labels do not exist for the camera pixel data.");
2163 }
2164#endif
2165
2166 auto &data_red = pixel_data.at(red_band_label);
2167 auto &data_green = pixel_data.at(green_band_label);
2168 auto &data_blue = pixel_data.at(blue_band_label);
2169
2170 const std::size_t N = data_red.size();
2171 if (data_green.size() != N || data_blue.size() != N) {
2172 throw std::invalid_argument("All channels must have the same length");
2173 }
2174 if (p < 1.0f) {
2175 throw std::invalid_argument("Minkowski exponent p must satisfy p >= 1");
2176 }
2177
2178 // Compute Minkowski means:
2179 // \[ M_R = \Bigl(\frac{1}{N}\sum_{i=1}^{N}R_i^p\Bigr)^{1/p},\quad
2180 // M_G = \Bigl(\frac{1}{N}\sum_{i=1}^{N}G_i^p\Bigr)^{1/p},\quad
2181 // M_B = \Bigl(\frac{1}{N}\sum_{i=1}^{N}B_i^p\Bigr)^{1/p} \]
2182 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
2183 for (std::size_t i = 0; i < N; ++i) {
2184 acc_r += std::pow(data_red[i], p);
2185 acc_g += std::pow(data_green[i], p);
2186 acc_b += std::pow(data_blue[i], p);
2187 }
2188 float mean_r_p = acc_r / static_cast<float>(N);
2189 float mean_g_p = acc_g / static_cast<float>(N);
2190 float mean_b_p = acc_b / static_cast<float>(N);
2191
2192 float M_R = std::pow(mean_r_p, 1.0f / p);
2193 float M_G = std::pow(mean_g_p, 1.0f / p);
2194 float M_B = std::pow(mean_b_p, 1.0f / p);
2195
2196 // Avoid division by zero
2197 const float eps = 1e-6f;
2198 if (M_R < eps || M_G < eps || M_B < eps) {
2199 throw std::runtime_error("Channel Minkowski mean too small");
2200 }
2201
2202 // Compute gray reference:
2203 // \[ M = \frac{M_R + M_G + M_B}{3} \]
2204 float M = (M_R + M_G + M_B) / 3.0f;
2205
2206 // Derive per-channel gains:
2207 // \[ s_R = M / M_R,\quad s_G = M / M_G,\quad s_B = M / M_B \]
2208 helios::vec3 scale;
2209 scale.x = M / M_R;
2210 scale.y = M / M_G;
2211 scale.z = M / M_B;
2212
2213 // Apply gains to each pixel:
2214 // \[ R'_i = s_R\,R_i,\quad G'_i = s_G\,G_i,\quad B'_i = s_B\,B_i \]
2215 for (std::size_t i = 0; i < N; ++i) {
2216 data_red[i] *= scale.x;
2217 data_green[i] *= scale.y;
2218 data_blue[i] *= scale.z;
2219 }
2220}
2221
2222void RadiationCamera::whiteBalanceGrayEdge(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, int derivative_order, float p) {
2223
2224#ifdef HELIOS_DEBUG
2225 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2226 helios_runtime_error("ERROR (RadiationModel::whiteBalanceGrayEdge): One or more specified band labels do not exist for the camera pixel data.");
2227 }
2228#endif
2229
2230 auto &data_red = pixel_data.at(red_band_label);
2231 auto &data_green = pixel_data.at(green_band_label);
2232 auto &data_blue = pixel_data.at(blue_band_label);
2233
2234 const int width = resolution.x;
2235 const int height = resolution.y;
2236 const std::size_t N = width * height;
2237
2238 if (p < 1.0f) {
2239 throw std::invalid_argument("Minkowski exponent p must satisfy p >= 1");
2240 }
2241 if (derivative_order < 1 || derivative_order > 2) {
2242 throw std::invalid_argument("Derivative order must be 1 or 2");
2243 }
2244
2245 // Compute derivatives using simple finite differences
2246 std::vector<float> deriv_red(N, 0.0f);
2247 std::vector<float> deriv_green(N, 0.0f);
2248 std::vector<float> deriv_blue(N, 0.0f);
2249
2250 if (derivative_order == 1) {
2251 // First-order derivatives (gradient magnitude)
2252 for (int y = 1; y < height - 1; ++y) {
2253 for (int x = 1; x < width - 1; ++x) {
2254 int idx = y * width + x;
2255
2256 // Sobel operator for gradient estimation
2257 float dx_r = (data_red[(y - 1) * width + (x + 1)] + 2 * data_red[y * width + (x + 1)] + data_red[(y + 1) * width + (x + 1)]) -
2258 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[y * width + (x - 1)] + data_red[(y + 1) * width + (x - 1)]) / 8.0f;
2259 float dy_r = (data_red[(y + 1) * width + (x - 1)] + 2 * data_red[(y + 1) * width + x] + data_red[(y + 1) * width + (x + 1)]) -
2260 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[(y - 1) * width + x] + data_red[(y - 1) * width + (x + 1)]) / 8.0f;
2261 deriv_red[idx] = std::sqrt(dx_r * dx_r + dy_r * dy_r);
2262
2263 float dx_g = (data_green[(y - 1) * width + (x + 1)] + 2 * data_green[y * width + (x + 1)] + data_green[(y + 1) * width + (x + 1)]) -
2264 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[y * width + (x - 1)] + data_green[(y + 1) * width + (x - 1)]) / 8.0f;
2265 float dy_g = (data_green[(y + 1) * width + (x - 1)] + 2 * data_green[(y + 1) * width + x] + data_green[(y + 1) * width + (x + 1)]) -
2266 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[(y - 1) * width + x] + data_green[(y - 1) * width + (x + 1)]) / 8.0f;
2267 deriv_green[idx] = std::sqrt(dx_g * dx_g + dy_g * dy_g);
2268
2269 float dx_b = (data_blue[(y - 1) * width + (x + 1)] + 2 * data_blue[y * width + (x + 1)] + data_blue[(y + 1) * width + (x + 1)]) -
2270 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[y * width + (x - 1)] + data_blue[(y + 1) * width + (x - 1)]) / 8.0f;
2271 float dy_b = (data_blue[(y + 1) * width + (x - 1)] + 2 * data_blue[(y + 1) * width + x] + data_blue[(y + 1) * width + (x + 1)]) -
2272 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[(y - 1) * width + x] + data_blue[(y - 1) * width + (x + 1)]) / 8.0f;
2273 deriv_blue[idx] = std::sqrt(dx_b * dx_b + dy_b * dy_b);
2274 }
2275 }
2276 } else {
2277 // Second-order derivatives (Laplacian)
2278 for (int y = 1; y < height - 1; ++y) {
2279 for (int x = 1; x < width - 1; ++x) {
2280 int idx = y * width + x;
2281
2282 deriv_red[idx] = std::abs(data_red[(y - 1) * width + x] + data_red[(y + 1) * width + x] + data_red[y * width + (x - 1)] + data_red[y * width + (x + 1)] - 4 * data_red[idx]);
2283
2284 deriv_green[idx] = std::abs(data_green[(y - 1) * width + x] + data_green[(y + 1) * width + x] + data_green[y * width + (x - 1)] + data_green[y * width + (x + 1)] - 4 * data_green[idx]);
2285
2286 deriv_blue[idx] = std::abs(data_blue[(y - 1) * width + x] + data_blue[(y + 1) * width + x] + data_blue[y * width + (x - 1)] + data_blue[y * width + (x + 1)] - 4 * data_blue[idx]);
2287 }
2288 }
2289 }
2290
2291 // Compute Minkowski means of derivatives
2292 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
2293 int valid_pixels = 0;
2294
2295 for (std::size_t i = 0; i < N; ++i) {
2296 if (deriv_red[i] > 0 || deriv_green[i] > 0 || deriv_blue[i] > 0) {
2297 acc_r += std::pow(deriv_red[i], p);
2298 acc_g += std::pow(deriv_green[i], p);
2299 acc_b += std::pow(deriv_blue[i], p);
2300 valid_pixels++;
2301 }
2302 }
2303
2304 if (valid_pixels == 0) {
2305 // No edges detected, fall back to standard white balance
2306 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
2307 return;
2308 }
2309
2310 float mean_r_p = acc_r / static_cast<float>(valid_pixels);
2311 float mean_g_p = acc_g / static_cast<float>(valid_pixels);
2312 float mean_b_p = acc_b / static_cast<float>(valid_pixels);
2313
2314 float M_R = std::pow(mean_r_p, 1.0f / p);
2315 float M_G = std::pow(mean_g_p, 1.0f / p);
2316 float M_B = std::pow(mean_b_p, 1.0f / p);
2317
2318 // Avoid division by zero
2319 const float eps = 1e-6f;
2320 if (M_R < eps || M_G < eps || M_B < eps) {
2321 // Fall back to standard white balance
2322 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
2323 return;
2324 }
2325
2326 // Compute gray reference
2327 float M = (M_R + M_G + M_B) / 3.0f;
2328
2329 // Derive per-channel gains
2330 helios::vec3 scale;
2331 scale.x = M / M_R;
2332 scale.y = M / M_G;
2333 scale.z = M / M_B;
2334
2335 // Apply gains to each pixel
2336 for (std::size_t i = 0; i < N; ++i) {
2337 data_red[i] *= scale.x;
2338 data_green[i] *= scale.y;
2339 data_blue[i] *= scale.z;
2340 }
2341}
2342
2343void RadiationCamera::whiteBalanceWhitePatch(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float percentile) {
2344
2345#ifdef HELIOS_DEBUG
2346 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2347 helios_runtime_error("ERROR (RadiationModel::whiteBalanceWhitePatch): One or more specified band labels do not exist for the camera pixel data.");
2348 }
2349#endif
2350
2351 if (percentile <= 0.0f || percentile > 1.0f) {
2352 throw std::invalid_argument("Percentile must be in range (0, 1]");
2353 }
2354
2355 auto &data_red = pixel_data.at(red_band_label);
2356 auto &data_green = pixel_data.at(green_band_label);
2357 auto &data_blue = pixel_data.at(blue_band_label);
2358
2359 const std::size_t N = data_red.size();
2360
2361 // Find the percentile values for each channel
2362 std::vector<float> sorted_red = data_red;
2363 std::vector<float> sorted_green = data_green;
2364 std::vector<float> sorted_blue = data_blue;
2365
2366 std::size_t k = static_cast<std::size_t>(percentile * (N - 1));
2367
2368 std::nth_element(sorted_red.begin(), sorted_red.begin() + k, sorted_red.end());
2369 std::nth_element(sorted_green.begin(), sorted_green.begin() + k, sorted_green.end());
2370 std::nth_element(sorted_blue.begin(), sorted_blue.begin() + k, sorted_blue.end());
2371
2372 float white_r = sorted_red[k];
2373 float white_g = sorted_green[k];
2374 float white_b = sorted_blue[k];
2375
2376 // Avoid division by zero
2377 const float eps = 1e-6f;
2378 if (white_r < eps || white_g < eps || white_b < eps) {
2379 throw std::runtime_error("White patch values too small");
2380 }
2381
2382 // Apply gains to normalize to white
2383 for (std::size_t i = 0; i < N; ++i) {
2384 data_red[i] /= white_r;
2385 data_green[i] /= white_g;
2386 data_blue[i] /= white_b;
2387 }
2388}
2389
2390void RadiationCamera::whiteBalanceAuto(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label) {
2391
2392#ifdef HELIOS_DEBUG
2393 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2394 helios_runtime_error("ERROR (RadiationModel::whiteBalanceAuto): One or more specified band labels do not exist for the camera pixel data.");
2395 }
2396#endif
2397
2398 const auto &data_red = pixel_data.at(red_band_label);
2399 const auto &data_green = pixel_data.at(green_band_label);
2400 const auto &data_blue = pixel_data.at(blue_band_label);
2401
2402 const std::size_t N = data_red.size();
2403 const int width = resolution.x;
2404 const int height = resolution.y;
2405
2406 // Analyze scene characteristics
2407
2408 // 1. Check for green dominance (vegetation indicator)
2409 float mean_r = 0.0f, mean_g = 0.0f, mean_b = 0.0f;
2410 for (std::size_t i = 0; i < N; ++i) {
2411 mean_r += data_red[i];
2412 mean_g += data_green[i];
2413 mean_b += data_blue[i];
2414 }
2415 mean_r /= N;
2416 mean_g /= N;
2417 mean_b /= N;
2418
2419 float green_dominance = mean_g / (mean_r + mean_g + mean_b + 1e-6f);
2420
2421 // 2. Check for bright pixels (white patch candidates)
2422 float bright_threshold = 0.9f;
2423 int bright_pixels = 0;
2424 for (std::size_t i = 0; i < N; ++i) {
2425 float max_val = std::max({data_red[i], data_green[i], data_blue[i]});
2426 if (max_val > bright_threshold) {
2427 bright_pixels++;
2428 }
2429 }
2430 float bright_ratio = static_cast<float>(bright_pixels) / N;
2431
2432 // 3. Compute edge density (texture indicator)
2433 float edge_density = 0.0f;
2434 for (int y = 1; y < height - 1; ++y) {
2435 for (int x = 1; x < width - 1; ++x) {
2436 int idx = y * width + x;
2437
2438 // Simple gradient magnitude
2439 float dx = std::abs(data_green[idx + 1] - data_green[idx - 1]);
2440 float dy = std::abs(data_green[idx + width] - data_green[idx - width]);
2441 edge_density += std::sqrt(dx * dx + dy * dy);
2442 }
2443 }
2444 edge_density /= ((width - 2) * (height - 2));
2445
2446 // Select algorithm based on scene characteristics
2447
2448 // For LED-lit simulation scenes, avoid White Patch since bright pixels are colored LED lights
2449 // Gray Edge works better for controlled lighting scenarios with known spectral bias
2450 whiteBalanceGrayEdge(red_band_label, green_band_label, blue_band_label, 1, 5.0f);
2451}
2452
2453void RadiationCamera::applyCameraSpectralCorrection(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, helios::Context *context) {
2454
2455#ifdef HELIOS_DEBUG
2456 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2457 helios_runtime_error("ERROR (RadiationCamera::applyCameraSpectralCorrection): One or more specified band labels do not exist for the camera pixel data.");
2458 }
2459#endif
2460
2461 // Check if spectral response data exists for all bands
2462 if (band_spectral_response.find(red_band_label) == band_spectral_response.end() || band_spectral_response.find(green_band_label) == band_spectral_response.end() || band_spectral_response.find(blue_band_label) == band_spectral_response.end()) {
2463 return;
2464 }
2465
2466 // Get spectral response identifiers
2467 std::string red_response_id = band_spectral_response.at(red_band_label);
2468 std::string green_response_id = band_spectral_response.at(green_band_label);
2469 std::string blue_response_id = band_spectral_response.at(blue_band_label);
2470
2471 // Skip if using uniform response (no spectral bias to correct)
2472 if (red_response_id == "uniform" && green_response_id == "uniform" && blue_response_id == "uniform") {
2473 return;
2474 }
2475
2476 try {
2477 // Access spectral response data from global data (assuming vec2 format: wavelength, response)
2478 std::vector<helios::vec2> red_spectrum, green_spectrum, blue_spectrum;
2479
2480 if (red_response_id != "uniform" && context->doesGlobalDataExist(red_response_id.c_str())) {
2481 context->getGlobalData(red_response_id.c_str(), red_spectrum);
2482 }
2483 if (green_response_id != "uniform" && context->doesGlobalDataExist(green_response_id.c_str())) {
2484 context->getGlobalData(green_response_id.c_str(), green_spectrum);
2485 }
2486 if (blue_response_id != "uniform" && context->doesGlobalDataExist(blue_response_id.c_str())) {
2487 context->getGlobalData(blue_response_id.c_str(), blue_spectrum);
2488 }
2489
2490 // If we don't have spectral data for all channels, skip correction
2491 if (red_spectrum.empty() || green_spectrum.empty() || blue_spectrum.empty()) {
2492 return;
2493 }
2494
2495 // Compute integrated response (area under curve) for each channel
2496 // This represents the total sensitivity of each channel
2497 float red_integrated = 0.0f, green_integrated = 0.0f, blue_integrated = 0.0f;
2498
2499 // Simple trapezoidal integration
2500 for (size_t i = 1; i < red_spectrum.size(); ++i) {
2501 float dw = red_spectrum[i].x - red_spectrum[i - 1].x;
2502 red_integrated += 0.5f * (red_spectrum[i].y + red_spectrum[i - 1].y) * dw;
2503 }
2504 for (size_t i = 1; i < green_spectrum.size(); ++i) {
2505 float dw = green_spectrum[i].x - green_spectrum[i - 1].x;
2506 green_integrated += 0.5f * (green_spectrum[i].y + green_spectrum[i - 1].y) * dw;
2507 }
2508 for (size_t i = 1; i < blue_spectrum.size(); ++i) {
2509 float dw = blue_spectrum[i].x - blue_spectrum[i - 1].x;
2510 blue_integrated += 0.5f * (blue_spectrum[i].y + blue_spectrum[i - 1].y) * dw;
2511 }
2512
2513 // Avoid division by zero
2514 if (red_integrated <= 0 || green_integrated <= 0 || blue_integrated <= 0) {
2515 return;
2516 }
2517
2518 // Compute correction factors to normalize channels relative to their integrated sensitivity
2519 // Use the minimum integrated response as reference to avoid amplifying noise
2520 float reference_response = std::min({red_integrated, green_integrated, blue_integrated});
2521
2522 helios::vec3 correction_factors;
2523 correction_factors.x = reference_response / red_integrated; // Red correction
2524 correction_factors.y = reference_response / green_integrated; // Green correction
2525 correction_factors.z = reference_response / blue_integrated; // Blue correction
2526
2527 // Apply correction factors to pixel data
2528 auto &data_red = pixel_data.at(red_band_label);
2529 auto &data_green = pixel_data.at(green_band_label);
2530 auto &data_blue = pixel_data.at(blue_band_label);
2531
2532 const std::size_t N = data_red.size();
2533 for (std::size_t i = 0; i < N; ++i) {
2534 data_red[i] *= correction_factors.x;
2535 data_green[i] *= correction_factors.y;
2536 data_blue[i] *= correction_factors.z;
2537 }
2538
2539 } catch (const std::exception &e) {
2540 return;
2541 }
2542}
2543
2544void RadiationCamera::reinhardToneMapping(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label) {
2545
2546#ifdef HELIOS_DEBUG
2547 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2548 helios_runtime_error("ERROR (RadiationModel::reinhardToneMapping): One or more specified band labels do not exist for the camera pixel data.");
2549 }
2550#endif
2551
2552 const std::size_t N = resolution.x * resolution.y;
2553 constexpr float eps = 1e-6f;
2554
2555 auto &data_red = pixel_data.at(red_band_label);
2556 auto &data_green = pixel_data.at(green_band_label);
2557 auto &data_blue = pixel_data.at(blue_band_label);
2558 for (std::size_t i = 0; i < N; ++i) {
2559 float R = data_red[i], G = data_green[i], B = data_blue[i];
2560 float L = luminance(R, G, B);
2561 float s = (L > eps) ? (L / (1.0f + L)) / L : 0.0f;
2562
2563 data_red[i] = R * s;
2564 data_green[i] = G * s;
2565 data_blue[i] = B * s;
2566 }
2567}
2568
2569void RadiationCamera::applyGain(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float percentile) {
2570
2571#ifdef HELIOS_DEBUG
2572 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2573 helios_runtime_error("ERROR (RadiationModel::applyGain): One or more specified band labels do not exist for the camera pixel data.");
2574 }
2575#endif
2576
2577 const std::size_t N = resolution.x * resolution.y;
2578
2579 auto &data_red = pixel_data.at(red_band_label);
2580 auto &data_green = pixel_data.at(green_band_label);
2581 auto &data_blue = pixel_data.at(blue_band_label);
2582
2583 std::vector<float> luminance_pixel;
2584 luminance_pixel.reserve(N);
2585 for (std::size_t i = 0; i < N; ++i) {
2586 luminance_pixel.push_back(luminance(data_red[i], data_green[i], data_blue[i]));
2587 }
2588
2589 std::size_t k = std::size_t(percentile * (luminance_pixel.size() - 1));
2590 std::nth_element(luminance_pixel.begin(), luminance_pixel.begin() + k, luminance_pixel.end());
2591 float peak = luminance_pixel[k];
2592 float gain = (peak > 0.0f) ? 1.0f / peak : 1.0f;
2593
2594 for (auto &[channel, data]: pixel_data) {
2595 for (float &v: data) {
2596 v *= gain;
2597 }
2598 }
2599}
2600
2601void RadiationCamera::globalHistogramEqualization(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label) {
2602
2603#ifdef HELIOS_DEBUG
2604 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2605 helios_runtime_error("ERROR (RadiationModel::globalHistogramEquilization): One or more specified band labels do not exist for the camera pixel data.");
2606 }
2607#endif
2608
2609 const size_t N = resolution.x * resolution.y;
2610 const float eps = 1e-6f;
2611
2612 auto &data_red = pixel_data.at(red_band_label);
2613 auto &data_green = pixel_data.at(green_band_label);
2614 auto &data_blue = pixel_data.at(blue_band_label);
2615
2616 /* luminance array and store original chromaticity */
2617 std::vector<float> lum(N);
2618 std::vector<float> chroma_r(N), chroma_g(N), chroma_b(N);
2619
2620 for (size_t i = 0; i < N; ++i) {
2621 vec3 p(data_red[i], data_green[i], data_blue[i]);
2622 lum[i] = 0.2126f * p.x + 0.7152f * p.y + 0.0722f * p.z;
2623
2624 // Store chromaticity ratios (color information)
2625 if (lum[i] > eps) {
2626 chroma_r[i] = p.x / lum[i];
2627 chroma_g[i] = p.y / lum[i];
2628 chroma_b[i] = p.z / lum[i];
2629 } else {
2630 chroma_r[i] = 1.0f;
2631 chroma_g[i] = 1.0f;
2632 chroma_b[i] = 1.0f;
2633 }
2634 }
2635
2636 /* build CDF on 2048-bin histogram */
2637 const int B = 2048;
2638 std::vector<int> hist(B, 0);
2639 for (float v: lum) {
2640 int b = int(std::clamp(v, 0.0f, 1.0f - eps) * B);
2641 if (b >= 0 && b < 2048) {
2642 hist[b]++;
2643 }
2644 }
2645 std::vector<float> cdf(B);
2646 int acc = 0;
2647 for (int b = 0; b < B; ++b) {
2648 acc += hist[b];
2649 cdf[b] = float(acc) / float(N);
2650 }
2651
2652 /* remap - only adjust luminance, preserve chromaticity */
2653 for (size_t i = 0; i < N; ++i) {
2654 // Handle bright pixels (> 1.0) specially
2655 if (lum[i] >= 1.0f) {
2656 data_red[i] = std::min(1.0f, data_red[i]);
2657 data_green[i] = std::min(1.0f, data_green[i]);
2658 data_blue[i] = std::min(1.0f, data_blue[i]);
2659 continue;
2660 }
2661
2662 int b = int(std::clamp(lum[i], 0.0f, 1.0f - eps) * B);
2663
2664 if (b < 0 || b >= 2048) {
2665 continue;
2666 }
2667
2668 constexpr float k = 0.2f; // how far to pull towards equalised value (0.2–0.3 OK)
2669 constexpr float cs = 0.2f; // S-curve strength (0.4–0.7 recommended)
2670
2671 float Yeq = cdf[b]; // equalised luminance ∈[0,1]
2672 float Ynew = (1.0f - k) * lum[i] + k * Yeq; // partial equalisation
2673
2674 /* symmetric S-curve centred at 0.5 : y = ½ + (x–½)*(1+cs–2·cs·|x–½|) */
2675 float t = Ynew - 0.5f;
2676 Ynew = 0.5f + t * (1.0f + cs - 2.0f * cs * std::fabs(t));
2677
2678 // Reconstruct RGB using new luminance but original chromaticity
2679 data_red[i] = Ynew * chroma_r[i];
2680 data_green[i] = Ynew * chroma_g[i];
2681 data_blue[i] = Ynew * chroma_b[i];
2682 }
2683}
2684
2685void RadiationCamera::adjustSBC(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float saturation, float brightness, float contrast) {
2686#ifdef HELIOS_DEBUG
2687 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2688 helios_runtime_error("ERROR (RadiationModel::adjustSBC): One or more specified band labels do not exist for the camera pixel data.");
2689 }
2690#endif
2691
2692 constexpr float kRedW = 0.2126f;
2693 constexpr float kGreenW = 0.7152f;
2694 constexpr float kBlueW = 0.0722f;
2695
2696 const size_t N = resolution.x * resolution.y;
2697
2698 auto &data_red = pixel_data.at(red_band_label);
2699 auto &data_green = pixel_data.at(green_band_label);
2700 auto &data_blue = pixel_data.at(blue_band_label);
2701
2702 for (int i = 0; i < N; ++i) {
2703
2704 helios::vec3 p(data_red[i], data_green[i], data_blue[i]);
2705
2706 /* ----- 1. luminance ----- */
2707 float Y = kRedW * p.x + kGreenW * p.y + kBlueW * p.z;
2708
2709 /* ----- 2. saturation ----- */
2710 p = helios::vec3(Y, Y, Y) + (p - helios::vec3(Y, Y, Y)) * saturation;
2711
2712 /* ----- 3. brightness (gain) ----- */
2713 p *= brightness;
2714
2715 /* ----- 4. contrast ----- */
2716 p = (p - helios::vec3(0.5f, 0.5f, 0.5f)) * contrast + helios::vec3(0.5f, 0.5f, 0.5f);
2717
2718 /* ----- 5. clamp to valid range ----- */
2719 data_red[i] = clamp(p.x, 0.0f, 1.0f);
2720 data_green[i] = clamp(p.y, 0.0f, 1.0f);
2721 data_blue[i] = clamp(p.z, 0.0f, 1.0f);
2722 }
2723}
2724
2725// void RadiationCamera::applyCCM(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label) {
2726//
2727// const std::size_t N = resolution.x * resolution.y;
2728// auto &data_red = pixel_data.at(red_band_label);
2729// auto &data_green = pixel_data.at(green_band_label);
2730// auto &data_blue = pixel_data.at(blue_band_label);
2731// for (std::size_t i = 0; i < N; ++i) {
2732// float R = data_red[i], G = data_green[i], B = data_blue[i];
2733// data_red[i] = color_correction_matrix[0] * R + color_correction_matrix[1] * G + color_correction_matrix[2] * B + color_correction_matrix[9];
2734// data_green[i] = color_correction_matrix[3] * R + color_correction_matrix[4] * G + color_correction_matrix[5] * B + color_correction_matrix[10];
2735// data_blue[i] = color_correction_matrix[6] * R + color_correction_matrix[7] * G + color_correction_matrix[8] * B + color_correction_matrix[11];
2736// }
2737// }
2738
2739void RadiationCamera::gammaCompress(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label) {
2740
2741#ifdef HELIOS_DEBUG
2742 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2743 helios_runtime_error("ERROR (RadiationModel::gammaCompress): One or more specified band labels do not exist for the camera pixel data.");
2744 }
2745#endif
2746
2747 for (float &v: pixel_data.at(red_band_label)) {
2748 v = lin_to_srgb(std::fmaxf(0.0f, v));
2749 }
2750 for (float &v: pixel_data.at(green_band_label)) {
2751 v = lin_to_srgb(std::fmaxf(0.0f, v));
2752 }
2753 for (float &v: pixel_data.at(blue_band_label)) {
2754 v = lin_to_srgb(std::fmaxf(0.0f, v));
2755 }
2756}
2757
2758// New methods for improved image processing pipeline
2759
2760void RadiationCamera::autoExposure(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float gain_multiplier) {
2761#ifdef HELIOS_DEBUG
2762 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2763 helios_runtime_error("ERROR (RadiationModel::autoExposure): One or more specified band labels do not exist for the camera pixel data.");
2764 }
2765#endif
2766
2767 auto &data_red = pixel_data.at(red_band_label);
2768 auto &data_green = pixel_data.at(green_band_label);
2769 auto &data_blue = pixel_data.at(blue_band_label);
2770
2771 const std::size_t N = data_red.size();
2772
2773 // Calculate luminance for each pixel
2774 std::vector<float> luminance_values(N);
2775 for (std::size_t i = 0; i < N; ++i) {
2776 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
2777 }
2778
2779 // Sort luminance values to find percentiles
2780 std::vector<float> sorted_luminance = luminance_values;
2781 std::sort(sorted_luminance.begin(), sorted_luminance.end());
2782
2783 // Calculate 95th percentile for exposure (prevents bright outliers from under-exposing scene)
2784 std::size_t p95_idx = static_cast<std::size_t>(0.95f * (N - 1));
2785 float p95_luminance = sorted_luminance[p95_idx];
2786
2787 // Calculate median luminance for scene analysis
2788 std::size_t median_idx = N / 2;
2789 float median_luminance = sorted_luminance[median_idx];
2790
2791 // Target median luminance scaled appropriately for the data range
2792 // Since RGB data is not normalized to [0,1], we need to scale the target accordingly
2793 float target_median = 0.18f; // Calibrated based on empirical testing
2794 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
2795
2796 // Clamp auto-gain to reasonable range to prevent over/under exposure
2797 // auto_gain = std::clamp(auto_gain, 0.0005f, 0.5f);
2798
2799 // Apply final gain (auto-exposure * manual adjustment)
2800 float final_gain = auto_gain * gain_multiplier;
2801
2802 // Apply gain to all channels
2803 for (std::size_t i = 0; i < N; ++i) {
2804 data_red[i] *= final_gain;
2805 data_green[i] *= final_gain;
2806 data_blue[i] *= final_gain;
2807 }
2808}
2809
2810void RadiationCamera::adjustBrightnessContrast(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float brightness, float contrast) {
2811#ifdef HELIOS_DEBUG
2812 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2813 helios_runtime_error("ERROR (RadiationModel::adjustBrightnessContrast): One or more specified band labels do not exist for the camera pixel data.");
2814 }
2815#endif
2816
2817 auto &data_red = pixel_data.at(red_band_label);
2818 auto &data_green = pixel_data.at(green_band_label);
2819 auto &data_blue = pixel_data.at(blue_band_label);
2820
2821 const std::size_t N = data_red.size();
2822
2823 for (std::size_t i = 0; i < N; ++i) {
2824 // Apply brightness adjustment
2825 float r = data_red[i] * brightness;
2826 float g = data_green[i] * brightness;
2827 float b = data_blue[i] * brightness;
2828
2829 // Apply contrast adjustment (around 0.5 midpoint in linear space)
2830 r = 0.5f + (r - 0.5f) * contrast;
2831 g = 0.5f + (g - 0.5f) * contrast;
2832 b = 0.5f + (b - 0.5f) * contrast;
2833
2834 // Store results (allow values outside [0,1] range for HDR processing)
2835 data_red[i] = r;
2836 data_green[i] = g;
2837 data_blue[i] = b;
2838 }
2839}
2840
2841void RadiationCamera::adjustSaturation(const std::string &red_band_label, const std::string &green_band_label, const std::string &blue_band_label, float saturation) {
2842#ifdef HELIOS_DEBUG
2843 if (pixel_data.find(red_band_label) == pixel_data.end() || pixel_data.find(green_band_label) == pixel_data.end() || pixel_data.find(blue_band_label) == pixel_data.end()) {
2844 helios_runtime_error("ERROR (RadiationModel::adjustSaturation): One or more specified band labels do not exist for the camera pixel data.");
2845 }
2846#endif
2847
2848 auto &data_red = pixel_data.at(red_band_label);
2849 auto &data_green = pixel_data.at(green_band_label);
2850 auto &data_blue = pixel_data.at(blue_band_label);
2851
2852 const std::size_t N = data_red.size();
2853
2854 for (std::size_t i = 0; i < N; ++i) {
2855 float r = data_red[i];
2856 float g = data_green[i];
2857 float b = data_blue[i];
2858
2859 // Calculate luminance for this pixel
2860 float lum = luminance(r, g, b);
2861
2862 // Apply saturation adjustment by interpolating between luminance (grayscale) and original color
2863 data_red[i] = lum + saturation * (r - lum);
2864 data_green[i] = lum + saturation * (g - lum);
2865 data_blue[i] = lum + saturation * (b - lum);
2866 }
2867}