28using namespace helios;
32 if (antialiasing_samples == 0) {
33 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCamera): The model requires at least 1 antialiasing sample to run.");
35 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCamera): Camera resolution must be at least 1x1.");
36 }
else if (camera_properties.
HFOV < 0 || camera_properties.
HFOV > 180.f) {
37 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCamera): Camera horizontal field of view must be between 0 and 180 degrees.");
47 for (
const auto &band : band_label) {
48 auto it = radiation_bands.find(band);
49 if (it != radiation_bands.end() && it->second.scatteringDepth == 0) {
50 warnings.
addWarning(
"camera_band_zero_scattering_depth",
51 "Camera '" + camera_label +
"' is bound to band '" + band +
52 "' which has scatteringDepth == 0. Camera pixels for this band will be "
53 "zero because camera ray tracing relies on scattered flux. Call "
54 "setScatteringDepth(\"" + band +
"\", >=1) before runBand().");
57 warnings.
report(std::cerr);
63 std::cerr <<
"WARNING (RadiationModel::addRadiationCamera): FOV_aspect_ratio is deprecated and will be ignored. The value is auto-calculated from camera_resolution to ensure square pixels." << std::endl;
67 RadiationCamera camera(camera_label, band_label, position, lookat, modified_properties, antialiasing_samples);
68 if (cameras.find(camera_label) == cameras.end()) {
69 cameras.emplace(camera_label, camera);
72 std::cout <<
"Camera with label " << camera_label <<
"already exists. Existing properties will be replaced by new inputs." << std::endl;
74 cameras.erase(camera_label);
75 cameras.emplace(camera_label, camera);
78 if (iscameravisualizationenabled) {
79 buildCameraModelGeometry(camera_label);
84 populateCameraMetadata(camera_label, metadata);
85 camera_metadata[camera_label] = metadata;
87 radiativepropertiesneedupdate =
true;
91 uint antialiasing_samples) {
94 addRadiationCamera(camera_label, band_label, position, lookat, camera_properties, antialiasing_samples);
98 if (cameras.find(camera_label) == cameras.end()) {
99 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Camera '" + camera_label +
"' does not exist.");
101 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Band '" + band_label +
"' does not exist.");
104 cameras.at(camera_label).band_spectral_response[band_label] = global_data;
106 radiativepropertiesneedupdate =
true;
111 if (cameras.find(camera_label) == cameras.end()) {
112 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Camera '" + camera_label +
"' does not exist.");
115 const auto &band_labels = cameras.at(camera_label).band_labels;
121 for (
const auto &band: band_labels) {
122 std::string response_spectrum = camera_library_name +
"_" + band;
124 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Band '" + band +
"' referenced in spectral library camera " + camera_library_name +
" does not exist for camera '" + camera_label +
"'.");
127 cameras.at(camera_label).band_spectral_response[band] = response_spectrum;
130 radiativepropertiesneedupdate =
true;
139 const std::vector<std::string> &custom_band_labels) {
145 pugi::xml_document xmldoc;
146 pugi::xml_parse_result result = xmldoc.load_file(library_path.string().c_str());
149 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Failed to load camera library file '" + library_path.string() +
"'. " + result.description());
152 pugi::xml_node helios_node = xmldoc.child(
"helios");
153 if (helios_node.empty()) {
154 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera library XML must have '<helios>' root tag.");
158 pugi::xml_node camera_node;
159 for (pugi::xml_node cam = helios_node.child(
"camera"); cam; cam = cam.next_sibling(
"camera")) {
160 std::string label = cam.attribute(
"label").value();
161 if (label == library_camera_label) {
167 if (camera_node.empty()) {
168 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera '" + library_camera_label +
"' not found in camera library.");
172 std::string manufacturer = camera_node.child(
"manufacturer").child_value();
173 std::string model = camera_node.child(
"model").child_value();
176 std::string camera_type = camera_node.child(
"type").child_value();
177 if (camera_type.empty()) {
178 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Missing required 'type' field for camera '" + library_camera_label +
"'.");
180 if (camera_type !=
"rgb" && camera_type !=
"spectral" && camera_type !=
"thermal") {
181 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid camera type '" + camera_type +
"' for camera '" + library_camera_label +
"'. Must be one of: 'rgb', 'spectral', or 'thermal'.");
184 float sensor_width_mm;
185 if (!
helios::parse_float(camera_node.child(
"sensor_width_mm").child_value(), sensor_width_mm)) {
186 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing sensor_width_mm for camera '" + library_camera_label +
"'.");
189 int resolution_width;
190 if (!
helios::parse_int(camera_node.child(
"resolution_width").child_value(), resolution_width)) {
191 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_width for camera '" + library_camera_label +
"'.");
194 int resolution_height;
195 if (!
helios::parse_int(camera_node.child(
"resolution_height").child_value(), resolution_height)) {
196 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_height for camera '" + library_camera_label +
"'.");
200 float focal_length_mm;
201 if (!
helios::parse_float(camera_node.child(
"focal_length_mm").child_value(), focal_length_mm)) {
202 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing focal_length_mm for camera '" + library_camera_label +
"'.");
205 float lens_diameter_mm;
206 if (!
helios::parse_float(camera_node.child(
"lens_diameter_mm").child_value(), lens_diameter_mm)) {
207 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing lens_diameter_mm for camera '" + library_camera_label +
"'.");
211 float focal_plane_distance_m = 2.0f;
212 if (camera_node.child(
"focal_plane_distance_m")) {
213 if (!
helios::parse_float(camera_node.child(
"focal_plane_distance_m").child_value(), focal_plane_distance_m)) {
214 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid focal_plane_distance_m for camera '" + library_camera_label +
"'.");
219 std::string lens_make = camera_node.child(
"lens_make").child_value();
220 std::string lens_model = camera_node.child(
"lens_model").child_value();
221 std::string lens_specification = camera_node.child(
"lens_specification").child_value();
224 std::string exposure_mode = camera_node.child(
"exposure").child_value();
225 if (exposure_mode.empty()) {
226 exposure_mode =
"auto";
230 float shutter_speed = 1.0f / 125.0f;
231 if (camera_node.child(
"shutter_speed")) {
232 if (!
helios::parse_float(camera_node.child(
"shutter_speed").child_value(), shutter_speed)) {
233 std::cerr <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Invalid shutter_speed for camera '" << library_camera_label <<
"'. Using default 1/125 second." << std::endl;
234 shutter_speed = 1.0f / 125.0f;
239 std::string white_balance_mode = camera_node.child(
"white_balance").child_value();
240 if (white_balance_mode.empty()) {
241 white_balance_mode =
"auto";
242 }
else if (white_balance_mode !=
"auto" && white_balance_mode !=
"off") {
243 std::cerr <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Invalid white_balance mode '" << white_balance_mode <<
"' for camera '" << library_camera_label <<
"'. Must be 'auto' or 'off'. Using default 'auto'." << std::endl;
244 white_balance_mode =
"auto";
254 float HFOV_rad = 2.0f * atan(sensor_width_mm / (2.0f * focal_length_mm));
255 camera_properties.
HFOV = HFOV_rad * 180.0f /
M_PI;
264 camera_properties.
lens_diameter = lens_diameter_mm / 1000.0f;
270 camera_properties.
model = manufacturer +
" " + model;
278 camera_properties.
exposure = exposure_mode;
286 std::vector<std::string> xml_band_labels;
288 std::vector<std::pair<float, float>> spectral_wavelength_ranges;
290 for (pugi::xml_node spectral_node = camera_node.child(
"spectral_response"); spectral_node; spectral_node = spectral_node.next_sibling(
"spectral_response")) {
292 std::string xml_band_label = spectral_node.attribute(
"label").value();
293 if (xml_band_label.empty()) {
294 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): spectral_response node missing 'label' attribute for camera '" + library_camera_label +
"'.");
297 xml_band_labels.push_back(xml_band_label);
300 std::vector<helios::vec2> spectral_data;
301 std::string data_str = spectral_node.child_value();
303 if (!data_str.empty()) {
304 std::istringstream data_stream(data_str);
305 float wavelength, response;
306 while (data_stream >> wavelength >> response) {
311 if (spectral_data.empty()) {
312 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Empty spectral response data for band '" + xml_band_label +
"' in camera '" + library_camera_label +
"'.");
316 spectral_wavelength_ranges.emplace_back(spectral_data.front().x, spectral_data.back().x);
319 std::string global_data_label = library_camera_label +
"_" + xml_band_label;
320 context->
setGlobalData(global_data_label.c_str(), spectral_data);
323 if (xml_band_labels.empty()) {
324 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): No spectral response data found for camera '" + library_camera_label +
"'.");
328 std::vector<std::string> effective_band_labels;
329 if (!custom_band_labels.empty()) {
330 if (custom_band_labels.size() != xml_band_labels.size()) {
331 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): custom_band_labels size (" + std::to_string(custom_band_labels.size()) +
") does not match number of spectral responses in library (" +
332 std::to_string(xml_band_labels.size()) +
") for camera '" + library_camera_label +
"'.");
334 effective_band_labels = custom_band_labels;
336 effective_band_labels = xml_band_labels;
340 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
341 const std::string &band_label = effective_band_labels[i];
343 float min_wavelength = spectral_wavelength_ranges[i].first;
344 float max_wavelength = spectral_wavelength_ranges[i].second;
353 std::cout <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Band '" << band_label <<
"' did not exist and was automatically created with wavelength range [" << min_wavelength <<
", " << max_wavelength <<
"] nm." << std::endl;
358 addRadiationCamera(camera_label, effective_band_labels, position, lookat, camera_properties, antialiasing_samples);
361 cameras.at(camera_label).camera_type = camera_type;
364 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
365 std::string global_data_label = library_camera_label +
"_" + xml_band_labels[i];
371 if (cameras.find(camera_label) == cameras.end()) {
372 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera '" + camera_label +
"' does not exist.");
373 }
else if (position == cameras.at(camera_label).lookat) {
374 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera position cannot be equal to the 'lookat' position.");
377 cameras.at(camera_label).position = position;
379 if (iscameravisualizationenabled) {
380 updateCameraModelPosition(camera_label);
386 if (cameras.find(camera_label) == cameras.end()) {
387 helios_runtime_error(
"ERROR (RadiationModel::getCameraPosition): Camera '" + camera_label +
"' does not exist.");
390 return cameras.at(camera_label).position;
394 if (cameras.find(camera_label) == cameras.end()) {
395 helios_runtime_error(
"ERROR (RadiationModel::setCameraLookat): Camera '" + camera_label +
"' does not exist.");
398 cameras.at(camera_label).lookat = lookat;
400 if (iscameravisualizationenabled) {
401 updateCameraModelPosition(camera_label);
407 if (cameras.find(camera_label) == cameras.end()) {
408 helios_runtime_error(
"ERROR (RadiationModel::getCameraLookat): Camera '" + camera_label +
"' does not exist.");
411 return cameras.at(camera_label).lookat;
415 if (cameras.find(camera_label) == cameras.end()) {
416 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
419 cameras.at(camera_label).lookat = cameras.at(camera_label).position + direction;
421 if (iscameravisualizationenabled) {
422 updateCameraModelPosition(camera_label);
428 if (cameras.find(camera_label) == cameras.end()) {
429 helios_runtime_error(
"ERROR (RadiationModel::getCameraOrientation): Camera '" + camera_label +
"' does not exist.");
432 return cart2sphere(cameras.at(camera_label).lookat - cameras.at(camera_label).position);
436 if (cameras.find(camera_label) == cameras.end()) {
437 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
440 cameras.at(camera_label).lookat = cameras.at(camera_label).position +
sphere2cart(direction);
442 if (iscameravisualizationenabled) {
443 updateCameraModelPosition(camera_label);
450 if (cameras.find(camera_label) == cameras.end()) {
451 helios_runtime_error(
"ERROR (RadiationModel::getCameraParameters): Camera '" + camera_label +
"' does not exist.");
455 const auto &camera = cameras.at(camera_label);
460 camera_properties.
HFOV = camera.HFOV_degrees;
465 camera_properties.
model = camera.model;
466 camera_properties.
lens_make = camera.lens_make;
467 camera_properties.
lens_model = camera.lens_model;
469 camera_properties.
exposure = camera.exposure;
472 camera_properties.
camera_zoom = camera.camera_zoom;
475 return camera_properties;
481 if (cameras.find(camera_label) == cameras.end()) {
482 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera '" + camera_label +
"' does not exist.");
487 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera resolution must be at least 1x1.");
488 }
else if (camera_properties.
HFOV <= 0 || camera_properties.
HFOV >= 180.f) {
489 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera horizontal field of view must be between 0 and 180 degrees.");
490 }
else if (camera_properties.
camera_zoom <= 0.0f) {
491 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.");
495 auto &camera = cameras.at(camera_label);
499 camera.HFOV_degrees = camera_properties.
HFOV;
504 camera.model = camera_properties.
model;
505 camera.exposure = camera_properties.
exposure;
508 camera.camera_zoom = camera_properties.
camera_zoom;
511 camera.FOV_aspect_ratio = float(camera.resolution.x) / float(camera.resolution.y);
514 radiativepropertiesneedupdate =
true;
517 if (iscameravisualizationenabled) {
518 updateCameraModelPosition(camera_label);
523 std::vector<std::string> labels(cameras.size());
525 for (
const auto &camera: cameras) {
526 labels.at(cam) = camera.second.label;
532std::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) {
535 if (cameras.find(camera) == cameras.end()) {
536 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
540 if (bands.size() != 1 && bands.size() != 3) {
541 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.");
544 std::vector<std::vector<float>> camera_data(bands.size());
547 for (
const auto &band: bands) {
550 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
551 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
555 camera_data.at(b) = cameras.at(camera).pixel_data.at(band);
562 bool is_rgb = (camera_data.size() == 3);
564 for (
auto &band_data: camera_data) {
565 for (
float &v: band_data) {
571 std::string frame_str;
573 frame_str = std::to_string(frame);
576 std::string output_path = image_path;
578 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
580 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
583 std::ostringstream outfile;
584 outfile << output_path;
587 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
589 outfile << camera <<
"_" << imagefile_base <<
".jpeg";
591 std::ofstream testfile(outfile.str());
593 if (!testfile.is_open()) {
594 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;
599 int2 camera_resolution = cameras.at(camera).resolution;
601 std::vector<RGBcolor> pixel_data_RGB(camera_resolution.
x * camera_resolution.
y);
604 for (
uint j = 0; j < camera_resolution.
y; j++) {
605 for (
uint i = 0; i < camera_resolution.
x; i++) {
606 if (camera_data.size() == 1) {
607 float c = camera_data.front().at(j * camera_resolution.
x + i);
610 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));
612 pixel_color.
scale(flux_to_pixel_conversion);
613 uint ii = camera_resolution.
x - i - 1;
614 uint jj = camera_resolution.
y - j - 1;
615 pixel_data_RGB.at(jj * camera_resolution.
x + ii) = pixel_color;
619 writeJPEG(outfile.str(), camera_resolution.
x, camera_resolution.
y, pixel_data_RGB);
621 std::string image_filepath = outfile.str();
624 if (metadata_enabled_cameras.find(camera) != metadata_enabled_cameras.end()) {
627 if (camera_metadata.find(camera) != camera_metadata.end()) {
628 saved_image_processing = camera_metadata.at(camera).image_processing;
633 populateCameraMetadata(camera, metadata);
636 metadata.image_processing = saved_image_processing;
639 metadata.image_processing.
exposure_gain = cameras.at(camera).applied_exposure_gain;
643 metadata.image_processing.
color_space = is_rgb ?
"sRGB" :
"linear";
646 size_t last_slash = image_filepath.find_last_of(
"/\\");
647 std::string filename_only = (last_slash != std::string::npos) ? image_filepath.substr(last_slash + 1) : image_filepath;
648 metadata.
path = filename_only;
651 camera_metadata[camera] = metadata;
652 writeCameraMetadataFile(camera, output_path);
655 return image_filepath;
658std::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) {
661 for (
const std::string &band: bands) {
662 std::string global_data_label =
"camera_" + camera +
"_" + band;
663 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
664 std::cout <<
"ERROR (RadiationModel::writeNormCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
667 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;
670 std::vector<float> cameradata;
671 context->
getGlobalData(global_data_label.c_str(), cameradata);
672 for (
float val: cameradata) {
679 for (
const std::string &band: bands) {
680 std::string global_data_label =
"camera_" + camera +
"_" + band;
681 std::vector<float> cameradata;
682 context->
getGlobalData(global_data_label.c_str(), cameradata);
683 for (
float &val: cameradata) {
686 context->
setGlobalData(global_data_label.c_str(), cameradata);
695 if (cameras.find(camera) == cameras.end()) {
696 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
700 std::vector<float> camera_data;
703 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
704 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
708 std::string global_data_label =
"camera_" + camera +
"_" + band;
711 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;
715 context->
getGlobalData(global_data_label.c_str(), camera_data);
717 std::string frame_str;
719 frame_str = std::to_string(frame);
722 std::string output_path = image_path;
724 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
726 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
729 std::ostringstream outfile;
730 outfile << output_path;
733 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
735 outfile << camera <<
"_" << imagefile_base <<
".txt";
738 std::ofstream outfilestream(outfile.str());
740 if (!outfilestream.is_open()) {
741 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;
745 int2 camera_resolution = cameras.at(camera).resolution;
747 for (
int j = 0; j < camera_resolution.
y; j++) {
748 for (
int i = camera_resolution.
x - 1; i >= 0; i--) {
749 outfilestream << camera_data.at(j * camera_resolution.
x + i) <<
" ";
751 outfilestream <<
"\n";
754 outfilestream.close();
759 if (cameras.find(camera) == cameras.end()) {
760 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' does not exist.");
763 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
764 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' band with label '" + band +
"' does not exist.");
767 std::string global_data_label =
"camera_" + camera +
"_" + band;
770 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Image data for camera '" + camera +
"', band '" + band +
"' has not been created. Did you run the radiation model?");
773 std::vector<float> camera_data;
774 context->
getGlobalData(global_data_label.c_str(), camera_data);
776 std::string output_path = image_path;
778 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
780 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
783 std::ostringstream outfile;
784 outfile << output_path;
786 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
788 outfile << camera <<
"_" << imagefile_base <<
".exr";
791 int2 camera_resolution = cameras.at(camera).resolution;
794 std::vector<float> flipped_data(camera_resolution.
x * camera_resolution.
y);
795 for (
int j = 0; j < camera_resolution.
y; j++) {
796 for (
int i = 0; i < camera_resolution.
x; i++) {
797 int ii = camera_resolution.
x - i - 1;
798 flipped_data[j * camera_resolution.
x + i] = camera_data[j * camera_resolution.
x + ii];
802 helios::writeEXR(outfile.str(), camera_resolution.
x, camera_resolution.
y, flipped_data);
807 if (cameras.find(camera) == cameras.end()) {
808 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' does not exist.");
811 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): 'bands' vector is empty.");
814 int2 camera_resolution = cameras.at(camera).resolution;
815 size_t num_pixels = camera_resolution.
x * camera_resolution.
y;
817 std::vector<std::vector<float>> channel_data(bands.size());
818 std::vector<std::string> channel_names(bands.size());
820 for (
size_t b = 0; b < bands.size(); b++) {
821 const std::string &band = bands[b];
823 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
824 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' band with label '" + band +
"' does not exist.");
827 std::string global_data_label =
"camera_" + camera +
"_" + band;
829 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Image data for camera '" + camera +
"', band '" + band +
"' has not been created. Did you run the radiation model?");
832 std::vector<float> raw_data;
836 channel_data[b].resize(num_pixels);
837 for (
int j = 0; j < camera_resolution.
y; j++) {
838 for (
int i = 0; i < camera_resolution.
x; i++) {
839 int ii = camera_resolution.
x - i - 1;
840 channel_data[b][j * camera_resolution.
x + i] = raw_data[j * camera_resolution.
x + ii];
844 channel_names[b] = band;
847 std::string output_path = image_path;
849 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
851 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
854 std::ostringstream outfile;
855 outfile << output_path;
857 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
859 outfile << camera <<
"_" << imagefile_base <<
".exr";
862 helios::writeEXR(outfile.str(), camera_resolution.
x, camera_resolution.
y, channel_data, channel_names);
867 calibration_flag =
true;
871 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
873 std::vector<std::string> objectlabels;
874 vec2 wavelengthrange_c = wavelengthrange;
875 cameracalibration->
preprocessSpectra(sourcelabels_raw, cameraresponselabels, objectlabels, wavelengthrange_c);
879 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
883 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
885 cameraproperties.
exposure = calibratecamera.exposure;
886 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
889 std::string cameralabel =
"calibration";
890 std::map<uint, std::vector<vec2>> simulatedcolorboardspectra;
891 for (
uint UUID: UUIDs_target) {
892 simulatedcolorboardspectra.emplace(UUID, NULL);
895 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
899 std::vector<float> wavelengths;
901 int numberwavelengths = wavelengths.size();
903 for (
int iw = 0; iw < numberwavelengths; iw++) {
904 std::string wavelengthlabel = std::to_string(wavelengths.at(iw));
906 std::vector<std::string> sourcelabels;
907 for (std::string sourcelabel_raw: sourcelabels_raw) {
908 std::vector<vec2> icalsource;
909 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
910 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
911 icalsource.at(1).x += 1;
912 std::string sourcelable =
"Cal_source_" + sourcelabel_raw;
913 sourcelabels.push_back(sourcelable);
917 std::vector<vec2> icalcamera(2);
918 icalcamera.at(0).y = 1;
919 icalcamera.at(1).y = 1;
920 icalcamera.at(0).x = wavelengths.at(iw);
921 icalcamera.at(1).x = wavelengths.at(iw) + 1;
922 std::string camlable =
"Cal_cameraresponse";
925 for (
auto objectpair: cameracalibration->processedspectra.at(
"object")) {
926 std::vector<vec2> spectrum_obj;
927 spectrum_obj.push_back(objectpair.second.at(iw));
928 spectrum_obj.push_back(objectpair.second.at(iw));
929 spectrum_obj.at(1).x += 1;
930 context->
setGlobalData(objectpair.first.c_str(), spectrum_obj);
937 for (std::string sourcelabel_raw: sourcelabels_raw) {
951 std::vector<float> camera_data;
952 std::string global_data_label =
"camera_" + cameralabel +
"_" + wavelengthlabel;
953 context->
getGlobalData(global_data_label.c_str(), camera_data);
955 std::vector<uint> pixel_labels;
956 std::string global_data_label_UUID =
"camera_" + cameralabel +
"_pixel_UUID";
957 context->
getGlobalData(global_data_label_UUID.c_str(), pixel_labels);
959 for (
uint j = 0; j < calibratecamera.resolution.
y; j++) {
960 for (
uint i = 0; i < calibratecamera.resolution.
x; i++) {
961 float icdata = camera_data.at(j * calibratecamera.resolution.
x + i);
963 uint UUID = pixel_labels.at(j * calibratecamera.resolution.
x + i) - 1;
964 if (find(UUIDs_target.begin(), UUIDs_target.end(), UUID) != UUIDs_target.end()) {
965 if (simulatedcolorboardspectra.at(UUID).empty()) {
966 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
967 }
else if (simulatedcolorboardspectra.at(UUID).back().x == wavelengths.at(iw)) {
968 simulatedcolorboardspectra.at(UUID).back().y += icdata / float(numberwavelengths);
969 }
else if (simulatedcolorboardspectra.at(UUID).back().x != wavelengths.at(iw)) {
970 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
980 for (
uint UUID: UUIDs_colorbd) {
981 std::string colorboardspectra;
982 context->
getPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra);
983 context->
setPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra +
"_raw");
988 float fluxscale,
float diffusefactor,
uint scatteringdepth) {
990 float sources_fluxsum = 0;
991 std::vector<float> sources_fluxes;
992 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
993 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
997 sources_fluxsum += sources_fluxes.at(ID);
1002 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1009 if (bandlabels.size() > 1) {
1010 for (
int iband = 1; iband < bandlabels.size(); iband++) {
1012 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1019 for (
int iband = 0; iband < bandlabels.size(); iband++) {
1027void 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,
1028 helios::vec2 wavelengthrange,
float fluxscale,
float diffusefactor,
uint scatteringdepth) {
1030 float sources_fluxsum = 0;
1031 std::vector<float> sources_fluxes;
1032 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
1033 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
1037 sources_fluxsum += sources_fluxes.at(ID);
1042 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1049 if (bandlabels.size() > 1) {
1050 for (
int iband = 1; iband < bandlabels.size(); iband++) {
1052 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1059 for (
int ic = 0; ic < cameralabels.size(); ic++) {
1060 for (
int iband = 0; iband < bandlabels.size(); iband++) {
1070float 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,
1071 const std::vector<std::vector<float>> &truevalues) {
1076 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
1080 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
1082 cameraproperties.
exposure = calibratecamera.exposure;
1083 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
1085 std::string cameralabel = orginalcameralabel +
"Scale";
1097 if (cameras.find(cameralabel) == cameras.end()) {
1098 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
1102 std::vector<uint> camera_UUIDs;
1103 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1105 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1107 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1108 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1109 int2 camera_resolution = cameras.at(cameralabel).resolution;
1111 std::string frame_str;
1113 frame_str = std::to_string(frame);
1116 std::string output_path = image_path;
1118 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1120 helios_runtime_error(
"ERROR(RadiationModel::writePrimitiveDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
1123 std::ostringstream outfile;
1124 outfile << output_path;
1127 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1129 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1133 std::ofstream pixel_data(outfile.str());
1135 if (!pixel_data.is_open()) {
1136 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1139 bool empty_flag =
true;
1141 for (
uint j = 0; j < camera_resolution.
y; j++) {
1142 for (
uint i = 0; i < camera_resolution.
x; i++) {
1143 uint ii = camera_resolution.
x - i - 1;
1144 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1150 pixel_data << labeldata <<
" ";
1155 pixel_data << labeldata <<
" ";
1160 pixel_data << labeldata <<
" ";
1165 pixel_data << labeldata <<
" ";
1168 pixel_data << padvalue <<
" ";
1171 pixel_data << padvalue <<
" ";
1179 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;
1185 if (cameras.find(cameralabel) == cameras.end()) {
1186 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
1190 std::vector<uint> camera_UUIDs;
1191 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1193 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1195 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1196 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1197 int2 camera_resolution = cameras.at(cameralabel).resolution;
1199 std::string frame_str;
1201 frame_str = std::to_string(frame);
1204 std::string output_path = image_path;
1206 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1208 helios_runtime_error(
"ERROR(RadiationModel::writeObjectDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
1211 std::ostringstream outfile;
1212 outfile << output_path;
1215 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1217 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1221 std::ofstream pixel_data(outfile.str());
1223 if (!pixel_data.is_open()) {
1224 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1227 bool empty_flag =
true;
1229 for (
uint j = 0; j < camera_resolution.
y; j++) {
1230 for (
uint i = 0; i < camera_resolution.
x; i++) {
1231 uint ii = camera_resolution.
x - i - 1;
1232 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1234 pixel_data << padvalue <<
" ";
1242 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1243 pixel_data << labeldata <<
" ";
1247 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1248 pixel_data << labeldata <<
" ";
1252 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1253 pixel_data << labeldata <<
" ";
1257 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1258 pixel_data << labeldata <<
" ";
1261 pixel_data << padvalue <<
" ";
1264 pixel_data << padvalue <<
" ";
1272 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;
1278 if (cameras.find(cameralabel) == cameras.end()) {
1279 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Camera '" + cameralabel +
"' does not exist.");
1282 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1284 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1286 std::vector<float> camera_depth;
1287 context->
getGlobalData(global_data_label.c_str(), camera_depth);
1288 helios::vec3 camera_position = cameras.at(cameralabel).position;
1289 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1291 int2 camera_resolution = cameras.at(cameralabel).resolution;
1293 std::string frame_str;
1295 frame_str = std::to_string(frame);
1298 std::string output_path = image_path;
1300 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1302 helios_runtime_error(
"ERROR(RadiationModel::writeDepthImageData): Expected a directory path but got a file path for argument 'image_path'.");
1305 std::ostringstream outfile;
1306 outfile << output_path;
1309 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1311 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1315 std::ofstream pixel_data(outfile.str());
1317 if (!pixel_data.is_open()) {
1318 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Could not open file '" + outfile.str() +
"' for writing.");
1321 for (
int j = 0; j < camera_resolution.
y; j++) {
1322 for (
int i = camera_resolution.
x - 1; i >= 0; i--) {
1323 pixel_data << camera_depth.at(j * camera_resolution.
x + i) <<
" ";
1333 if (cameras.find(cameralabel) == cameras.end()) {
1334 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Camera '" + cameralabel +
"' does not exist.");
1337 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1339 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1341 std::vector<float> camera_depth;
1342 context->
getGlobalData(global_data_label.c_str(), camera_depth);
1344 int2 camera_resolution = cameras.at(cameralabel).resolution;
1346 std::string output_path = image_path;
1348 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1350 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
1353 std::ostringstream outfile;
1354 outfile << output_path;
1356 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
1358 outfile << cameralabel <<
"_" << imagefile_base <<
".exr";
1362 std::vector<float> flipped_data(camera_resolution.
x * camera_resolution.
y);
1363 for (
int j = 0; j < camera_resolution.
y; j++) {
1364 for (
int i = 0; i < camera_resolution.
x; i++) {
1365 int ii = camera_resolution.
x - i - 1;
1366 flipped_data[j * camera_resolution.
x + i] = camera_depth[j * camera_resolution.
x + ii];
1370 helios::writeEXR(outfile.str(), camera_resolution.
x, camera_resolution.
y, flipped_data);
1375 if (cameras.find(cameralabel) == cameras.end()) {
1376 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Camera '" + cameralabel +
"' does not exist.");
1379 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1381 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1383 std::vector<float> camera_depth;
1384 context->
getGlobalData(global_data_label.c_str(), camera_depth);
1385 helios::vec3 camera_position = cameras.at(cameralabel).position;
1386 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1388 int2 camera_resolution = cameras.at(cameralabel).resolution;
1390 std::string frame_str;
1392 frame_str = std::to_string(frame);
1395 std::string output_path = image_path;
1397 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1399 helios_runtime_error(
"ERROR(RadiationModel::writeNormDepthImage): Expected a directory path but got a file path for argument 'image_path'.");
1402 std::ostringstream outfile;
1403 outfile << output_path;
1406 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
1408 outfile << cameralabel <<
"_" << imagefile_base <<
".jpeg";
1411 float min_depth = 99999;
1412 for (
int i = 0; i < camera_depth.size(); i++) {
1413 if (camera_depth.at(i) < 0 || camera_depth.at(i) > max_depth) {
1414 camera_depth.at(i) = max_depth;
1416 if (camera_depth.at(i) < min_depth) {
1417 min_depth = camera_depth.at(i);
1420 for (
int i = 0; i < camera_depth.size(); i++) {
1421 camera_depth.at(i) = 1.f - (camera_depth.at(i) - min_depth) / (max_depth - min_depth);
1424 std::vector<RGBcolor> pixel_data(camera_resolution.
x * camera_resolution.
y);
1427 for (
uint j = 0; j < camera_resolution.
y; j++) {
1428 for (
uint i = 0; i < camera_resolution.
x; i++) {
1430 float c = camera_depth.at(j * camera_resolution.
x + i);
1433 uint ii = camera_resolution.
x - i - 1;
1434 uint jj = camera_resolution.
y - j - 1;
1435 pixel_data.at(jj * camera_resolution.
x + ii) = pixel_color;
1439 writeJPEG(outfile.str(), camera_resolution.
x, camera_resolution.
y, pixel_data);
1443void 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) {
1445 if (cameras.find(cameralabel) == cameras.end()) {
1446 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1450 std::vector<uint> camera_UUIDs;
1451 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1453 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1455 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1456 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1457 int2 camera_resolution = cameras.at(cameralabel).resolution;
1459 std::string frame_str;
1461 frame_str = std::to_string(frame);
1464 std::string output_path = image_path;
1466 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1468 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1471 std::ostringstream outfile;
1472 outfile << output_path;
1475 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1477 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1481 std::ofstream label_file;
1482 if (append_label_file) {
1483 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1485 label_file.open(outfile.str());
1488 if (!label_file.is_open()) {
1489 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open file '" + outfile.str() +
"'.");
1492 std::map<int, vec4> pdata_bounds;
1494 for (
int j = 0; j < camera_resolution.
y; j++) {
1495 for (
int i = 0; i < camera_resolution.
x; i++) {
1496 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + i) - 1;
1504 context->
getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_ui);
1505 labeldata = labeldata_ui;
1508 context->
getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_i);
1509 labeldata = (
uint) labeldata_i;
1514 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1515 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1518 if (i < pdata_bounds[labeldata].x) {
1519 pdata_bounds[labeldata].x = i;
1521 if (i > pdata_bounds[labeldata].y) {
1522 pdata_bounds[labeldata].y = i;
1524 if (j < pdata_bounds[labeldata].z) {
1525 pdata_bounds[labeldata].z = j;
1527 if (j > pdata_bounds[labeldata].w) {
1528 pdata_bounds[labeldata].w = j;
1534 for (
auto box: pdata_bounds) {
1535 vec4 bbox = box.second;
1536 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1539 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
1540 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1549 if (cameras.find(cameralabel) == cameras.end()) {
1550 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1554 std::vector<uint> camera_UUIDs;
1555 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1557 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1559 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1560 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1561 int2 camera_resolution = cameras.at(cameralabel).resolution;
1563 std::string frame_str;
1565 frame_str = std::to_string(frame);
1568 std::string output_path = image_path;
1570 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1572 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1575 std::ostringstream outfile;
1576 outfile << output_path;
1579 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1581 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1585 std::ofstream label_file;
1586 if (append_label_file) {
1587 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1589 label_file.open(outfile.str());
1592 if (!label_file.is_open()) {
1593 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open file '" + outfile.str() +
"'.");
1596 std::map<int, vec4> pdata_bounds;
1598 for (
int j = 0; j < camera_resolution.
y; j++) {
1599 for (
int i = 0; i < camera_resolution.
x; i++) {
1600 uint ii = camera_resolution.
x - i - 1;
1601 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1618 context->
getObjectData(objID, object_data_label.c_str(), labeldata_ui);
1619 labeldata = labeldata_ui;
1622 context->
getObjectData(objID, object_data_label.c_str(), labeldata_i);
1623 labeldata = (
uint) labeldata_i;
1628 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1629 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1632 if (i < pdata_bounds[labeldata].x) {
1633 pdata_bounds[labeldata].x = i;
1635 if (i > pdata_bounds[labeldata].y) {
1636 pdata_bounds[labeldata].y = i;
1638 if (j < pdata_bounds[labeldata].z) {
1639 pdata_bounds[labeldata].z = j;
1641 if (j > pdata_bounds[labeldata].w) {
1642 pdata_bounds[labeldata].w = j;
1647 for (
auto box: pdata_bounds) {
1648 vec4 bbox = box.second;
1649 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1652 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
1653 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1659void 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) {
1660 writeImageBoundingBoxes(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1663void 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,
1664 const std::string &image_path) {
1666 if (cameras.find(cameralabel) == cameras.end()) {
1667 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1670 if (primitive_data_label.size() != object_class_ID.size()) {
1671 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
1675 std::vector<uint> camera_UUIDs;
1676 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1678 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1680 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1681 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1682 int2 camera_resolution = cameras.at(cameralabel).resolution;
1684 std::string output_path = image_path;
1686 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1688 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1691 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1693 std::ofstream label_file(outfile_txt);
1695 if (!label_file.is_open()) {
1696 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output bounding box file '" + outfile_txt +
"'.");
1700 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1703 for (
int j = 0; j < camera_resolution.
y; j++) {
1704 for (
int i = 0; i < camera_resolution.
x; i++) {
1705 uint ii = camera_resolution.
x - i - 1;
1706 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1710 for (
size_t label_idx = 0; label_idx < primitive_data_label.size(); label_idx++) {
1711 const std::string &data_label = primitive_data_label[label_idx];
1712 uint class_id = object_class_ID[label_idx];
1716 bool has_data =
false;
1722 labeldata = labeldata_ui;
1727 labeldata = (
uint) labeldata_i;
1732 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1734 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1735 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1738 if (i < pdata_bounds[key].x) {
1739 pdata_bounds[key].x = i;
1741 if (i > pdata_bounds[key].y) {
1742 pdata_bounds[key].y = i;
1744 if (j < pdata_bounds[key].z) {
1745 pdata_bounds[key].z = j;
1747 if (j > pdata_bounds[key].w) {
1748 pdata_bounds[key].w = j;
1757 for (
auto box: pdata_bounds) {
1758 uint class_id = box.first.first;
1759 vec4 bbox = box.second;
1760 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1763 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
1764 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1769 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1770 if (!classes_txt_stream.is_open()) {
1771 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output classes file '" + output_path + classes_txt_file +
".");
1773 for (
int i = 0; i < object_class_ID.size(); i++) {
1774 classes_txt_stream << object_class_ID.at(i) <<
" " << primitive_data_label.at(i) << std::endl;
1776 classes_txt_stream.close();
1780 const std::string &image_path) {
1781 writeImageBoundingBoxes_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1785 const std::string &image_path) {
1787 if (cameras.find(cameralabel) == cameras.end()) {
1788 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1791 if (object_data_label.size() != object_class_ID.size()) {
1792 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
1796 std::vector<uint> camera_UUIDs;
1797 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1799 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1801 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1802 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1803 int2 camera_resolution = cameras.at(cameralabel).resolution;
1805 std::string output_path = image_path;
1807 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1809 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1812 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1814 std::ofstream label_file(outfile_txt);
1816 if (!label_file.is_open()) {
1817 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output bounding box file '" + outfile_txt +
"'.");
1821 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1825 for (
int j = 0; j < camera_resolution.
y; j++) {
1826 for (
int i = 0; i < camera_resolution.
x; i++) {
1827 uint ii = camera_resolution.
x - i - 1;
1828 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1841 for (
size_t label_idx = 0; label_idx < object_data_label.size(); label_idx++) {
1842 const std::string &data_label = object_data_label[label_idx];
1843 uint class_id = object_class_ID[label_idx];
1847 bool has_data =
false;
1852 context->
getObjectData(objID, data_label.c_str(), labeldata_ui);
1853 labeldata = labeldata_ui;
1857 context->
getObjectData(objID, data_label.c_str(), labeldata_i);
1858 labeldata = (
uint) labeldata_i;
1863 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1865 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1866 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1869 if (i < pdata_bounds[key].x) {
1870 pdata_bounds[key].x = i;
1872 if (i > pdata_bounds[key].y) {
1873 pdata_bounds[key].y = i;
1875 if (j < pdata_bounds[key].z) {
1876 pdata_bounds[key].z = j;
1878 if (j > pdata_bounds[key].w) {
1879 pdata_bounds[key].w = j;
1887 for (
auto box: pdata_bounds) {
1888 uint class_id = box.first.first;
1889 vec4 bbox = box.second;
1890 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1893 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
1894 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1899 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1900 if (!classes_txt_stream.is_open()) {
1901 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output classes file '" + output_path + classes_txt_file +
".");
1903 for (
int i = 0; i < object_class_ID.size(); i++) {
1904 classes_txt_stream << object_class_ID.at(i) <<
" " << object_data_label.at(i) << std::endl;
1906 classes_txt_stream.close();
1910std::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) {
1911 nlohmann::json coco_json;
1915 std::ifstream existing_file(filename);
1916 if (existing_file.is_open()) {
1918 existing_file >> coco_json;
1919 }
catch (
const std::exception &e) {
1922 existing_file.close();
1927 if (coco_json.empty()) {
1928 coco_json[
"categories"] = nlohmann::json::array();
1929 coco_json[
"images"] = nlohmann::json::array();
1930 coco_json[
"annotations"] = nlohmann::json::array();
1934 std::filesystem::path image_path_obj(image_file);
1935 std::string filename_only = image_path_obj.filename().string();
1938 bool image_exists =
false;
1939 for (
const auto &img: coco_json[
"images"]) {
1940 if (img[
"file_name"] == filename_only) {
1941 image_id = img[
"id"];
1942 image_exists =
true;
1948 if (!image_exists) {
1950 int max_image_id = -1;
1951 for (
const auto &img: coco_json[
"images"]) {
1952 if (img[
"id"] > max_image_id) {
1953 max_image_id = img[
"id"];
1956 image_id = max_image_id + 1;
1959 nlohmann::json image_entry;
1960 image_entry[
"id"] = image_id;
1961 image_entry[
"file_name"] = filename_only;
1962 image_entry[
"height"] = camera_resolution.
y;
1963 image_entry[
"width"] = camera_resolution.
x;
1964 coco_json[
"images"].push_back(image_entry);
1967 return std::make_pair(coco_json, image_id);
1971nlohmann::json RadiationModel::initializeCOCOJson(
const std::string &filename,
bool append_file,
const std::string &cameralabel,
const helios::int2 &camera_resolution,
const std::string &image_file) {
1972 return initializeCOCOJsonWithImageId(filename, append_file, cameralabel, camera_resolution, image_file).first;
1976void RadiationModel::addCategoryToCOCO(nlohmann::json &coco_json,
const std::vector<uint> &object_class_ID,
const std::vector<std::string> &category_name) {
1977 if (object_class_ID.size() != category_name.size()) {
1978 helios_runtime_error(
"ERROR (RadiationModel::addCategoryToCOCO): The lengths of object_class_ID and category_name vectors must be the same.");
1981 for (
size_t i = 0; i < object_class_ID.size(); ++i) {
1982 bool category_exists =
false;
1983 for (
auto &cat: coco_json[
"categories"]) {
1984 if (cat[
"id"] == object_class_ID[i]) {
1985 category_exists =
true;
1989 if (!category_exists) {
1990 nlohmann::json category;
1991 category[
"id"] = object_class_ID[i];
1992 category[
"name"] = category_name[i];
1993 category[
"supercategory"] =
"none";
1994 coco_json[
"categories"].push_back(category);
2000void RadiationModel::writeCOCOJson(
const nlohmann::json &coco_json,
const std::string &filename) {
2001 std::ofstream json_file(filename);
2002 if (!json_file.is_open()) {
2007 json_file << coco_json.dump(2) << std::endl;
2012std::map<int, std::vector<std::vector<bool>>> RadiationModel::generateLabelMasks(
const std::string &cameralabel,
const std::string &data_label,
bool use_object_data) {
2013 std::vector<uint> camera_UUIDs;
2014 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2015 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
2016 std::vector<uint> pixel_UUIDs = camera_UUIDs;
2017 int2 camera_resolution = cameras.at(cameralabel).resolution;
2019 std::map<int, std::vector<std::vector<bool>>> label_masks;
2023 for (
int j = 0; j < camera_resolution.
y; j++) {
2024 for (
int i = 0; i < camera_resolution.
x; i++) {
2025 uint ii = camera_resolution.
x - i - 1;
2026 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
2030 bool has_data =
false;
2032 if (use_object_data) {
2037 if (datatype == HELIOS_TYPE_UINT) {
2039 context->
getObjectData(objID, data_label.c_str(), labeldata_ui);
2040 labeldata = labeldata_ui;
2042 }
else if (datatype == HELIOS_TYPE_INT) {
2044 context->
getObjectData(objID, data_label.c_str(), labeldata_i);
2045 labeldata = (
uint) labeldata_i;
2053 if (datatype == HELIOS_TYPE_UINT) {
2056 labeldata = labeldata_ui;
2058 }
else if (datatype == HELIOS_TYPE_INT) {
2061 labeldata = (
uint) labeldata_i;
2069 if (label_masks.find(labeldata) == label_masks.end()) {
2070 label_masks[labeldata] = std::vector<std::vector<bool>>(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2072 label_masks[labeldata][j][i] =
true;
2082std::pair<int, int> RadiationModel::findStartingBoundaryPixel(
const std::vector<std::vector<bool>> &mask,
const helios::int2 &camera_resolution) {
2083 for (
int j = 0; j < camera_resolution.
y; j++) {
2084 for (
int i = 0; i < camera_resolution.
x; i++) {
2087 for (
int di = -1; di <= 1; di++) {
2088 for (
int dj = -1; dj <= 1; dj++) {
2089 if (di == 0 && dj == 0)
2093 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
2105std::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) {
2106 std::vector<std::pair<int, int>> contour;
2109 int dx[] = {1, 1, 0, -1, -1, -1, 0, 1};
2110 int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
2112 int x = start_x, y = start_y;
2116 contour.push_back({x, y});
2119 int start_dir = (dir + 6) % 8;
2122 for (
int i = 0; i < 8; i++) {
2123 int check_dir = (start_dir + i) % 8;
2124 int nx = x + dx[check_dir];
2125 int ny = y + dy[check_dir];
2128 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.
y && mask[ny][nx]) {
2140 }
while (!(x == start_x && y == start_y) && contour.size() < camera_resolution.
x * camera_resolution.
y);
2146std::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) {
2147 std::vector<std::pair<int, int>> contour;
2148 std::set<std::pair<int, int>> visited_boundary;
2151 std::queue<std::pair<int, int>> boundary_queue;
2152 boundary_queue.push({start_x, start_y});
2153 visited_boundary.insert({start_x, start_y});
2155 while (!boundary_queue.empty()) {
2156 auto [x, y] = boundary_queue.front();
2157 boundary_queue.pop();
2158 contour.push_back({x, y});
2161 for (
int di = -1; di <= 1; di++) {
2162 for (
int dj = -1; dj <= 1; dj++) {
2163 if (di == 0 && dj == 0)
2168 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.
y && mask[ny][nx] && visited_boundary.find({nx, ny}) == visited_boundary.end()) {
2171 bool is_boundary =
false;
2172 for (
int ddi = -1; ddi <= 1; ddi++) {
2173 for (
int ddj = -1; ddj <= 1; ddj++) {
2174 if (ddi == 0 && ddj == 0)
2178 if (nnx < 0 || nnx >= camera_resolution.
x || nny < 0 || nny >= camera_resolution.
y || !mask[nny][nnx]) {
2188 boundary_queue.push({nx, ny});
2189 visited_boundary.insert({nx, ny});
2200std::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) {
2201 std::vector<std::map<std::string, std::vector<float>>> annotations;
2202 int annotation_id = 0;
2204 for (
const auto &label_pair: label_masks) {
2205 int label_value = label_pair.first;
2206 const auto &mask = label_pair.second;
2209 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2212 for (
int j = 0; j < camera_resolution.
y; j++) {
2213 for (
int i = 0; i < camera_resolution.
x; i++) {
2214 if (mask[j][i] && !visited[j][i]) {
2216 int boundary_i = i, boundary_j = j;
2217 bool is_boundary =
false;
2220 for (
int di = -1; di <= 1; di++) {
2221 for (
int dj = -1; dj <= 1; dj++) {
2224 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
2237 std::stack<std::pair<int, int>> stack;
2238 std::vector<std::pair<int, int>> component_pixels;
2240 visited[j][i] =
true;
2242 int min_x = i, max_x = i, min_y = j, max_y = j;
2245 while (!stack.empty()) {
2246 auto [ci, cj] = stack.top();
2249 component_pixels.push_back({ci, cj});
2251 min_x = std::min(min_x, ci);
2252 max_x = std::max(max_x, ci);
2253 min_y = std::min(min_y, cj);
2254 max_y = std::max(max_y, cj);
2257 for (
int di = -1; di <= 1; di++) {
2258 for (
int dj = -1; dj <= 1; dj++) {
2259 if (abs(di) + abs(dj) != 1)
2263 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2264 stack.push({ni, nj});
2265 visited[nj][ni] =
true;
2272 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2273 bool is_boundary_start =
false;
2275 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2276 is_boundary_start =
true;
2279 if (is_boundary_start) {
2281 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2284 if (contour.size() < 10) {
2285 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2288 if (contour.size() >= 3) {
2290 std::map<std::string, std::vector<float>> annotation;
2291 annotation[
"id"] = {(float) annotation_id++};
2292 annotation[
"image_id"] = {(float) image_id};
2293 annotation[
"category_id"] = {(float) object_class_ID};
2294 annotation[
"bbox"] = {(float) min_x, (
float) min_y, (float) (max_x - min_x), (float) (max_y - min_y)};
2295 annotation[
"area"] = {(float) area};
2296 annotation[
"iscrowd"] = {0.0f};
2299 std::vector<float> segmentation;
2300 for (
const auto &point: contour) {
2301 segmentation.push_back((
float) point.first);
2302 segmentation.push_back((
float) point.second);
2304 annotation[
"segmentation"] = segmentation;
2306 annotations.push_back(annotation);
2319 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2320 writeImageSegmentationMasks(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, data_attribute_labels, append_file);
2323void 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,
2324 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2326 if (cameras.find(cameralabel) == cameras.end()) {
2327 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Camera '" + cameralabel +
"' does not exist.");
2330 if (primitive_data_label.size() != object_class_ID.size()) {
2331 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
2335 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2337 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2342 for (
const auto &data_label: primitive_data_label) {
2343 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), data_label) == all_primitive_data.end()) {
2344 std::cerr <<
"WARNING (RadiationModel::writeImageSegmentationMasks): Primitive data label '" << data_label <<
"' does not exist in the context." << std::endl;
2349 if (!std::filesystem::exists(image_file)) {
2350 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Image file '" + image_file +
"' does not exist.");
2354 std::string validated_json_filename = json_filename;
2355 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2356 validated_json_filename +=
".json";
2360 std::string outfile = validated_json_filename;
2363 int2 camera_resolution = cameras.at(cameralabel).resolution;
2364 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2365 nlohmann::json coco_json = coco_json_pair.first;
2366 int image_id = coco_json_pair.second;
2367 addCategoryToCOCO(coco_json, object_class_ID, primitive_data_label);
2370 struct AttributeInfo {
2372 bool is_primitive_data;
2375 std::vector<AttributeInfo> attribute_info;
2377 if (!data_attribute_labels.empty()) {
2381 for (
const auto &attr_label: data_attribute_labels) {
2383 info.label = attr_label;
2384 info.exists =
false;
2386 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2387 info.is_primitive_data =
true;
2389 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2390 info.is_primitive_data =
false;
2395 attribute_info.push_back(info);
2400 bool use_attributes = !attribute_info.empty();
2403 std::vector<uint> pixel_UUIDs;
2404 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2405 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2408 for (
size_t i = 0; i < primitive_data_label.size(); ++i) {
2410 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, primitive_data_label[i],
false);
2413 std::vector<std::map<std::string, std::vector<float>>> annotations = generateAnnotationsFromMasks(label_masks, object_class_ID[i], camera_resolution, image_id);
2416 std::vector<std::map<std::string, double>> mean_attribute_values_per_component;
2417 if (use_attributes) {
2419 for (
const auto &label_pair: label_masks) {
2420 const auto &mask = label_pair.second;
2421 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2423 for (
int j = 0; j < camera_resolution.
y; j++) {
2424 for (
int i_px = 0; i_px < camera_resolution.
x; i_px++) {
2425 if (mask[j][i_px] && !visited[j][i_px]) {
2427 std::stack<std::pair<int, int>> stack;
2428 std::vector<std::pair<int, int>> component_pixels;
2429 stack.push({i_px, j});
2430 visited[j][i_px] =
true;
2432 while (!stack.empty()) {
2433 auto [ci, cj] = stack.top();
2435 component_pixels.push_back({ci, cj});
2438 for (
int di = -1; di <= 1; di++) {
2439 for (
int dj = -1; dj <= 1; dj++) {
2440 if (abs(di) + abs(dj) != 1)
2444 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2445 stack.push({ni, nj});
2446 visited[nj][ni] =
true;
2453 std::map<std::string, double> component_attributes;
2454 for (
const auto &attr: attribute_info) {
2458 for (
const auto &[px_i, px_j]: component_pixels) {
2459 uint ii = camera_resolution.
x - px_i - 1;
2460 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.
x + ii) - 1;
2464 bool has_value =
false;
2466 if (attr.is_primitive_data) {
2472 value =
static_cast<double>(val);
2477 value =
static_cast<double>(val);
2482 value =
static_cast<double>(val);
2496 value =
static_cast<double>(val);
2501 value =
static_cast<double>(val);
2506 value =
static_cast<double>(val);
2523 component_attributes[attr.label] =
sum / count;
2525 component_attributes[attr.label] = 0.0;
2529 mean_attribute_values_per_component.push_back(component_attributes);
2537 int max_annotation_id = -1;
2538 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2539 if (existing_ann[
"id"] > max_annotation_id) {
2540 max_annotation_id = existing_ann[
"id"];
2546 for (
const auto &ann: annotations) {
2547 nlohmann::json json_annotation;
2548 json_annotation[
"id"] = max_annotation_id + 1;
2549 json_annotation[
"image_id"] = (int) ann.at(
"image_id")[0];
2550 json_annotation[
"category_id"] = (int) ann.at(
"category_id")[0];
2552 const auto &bbox = ann.at(
"bbox");
2553 json_annotation[
"bbox"] = {(int) bbox[0], (
int) bbox[1], (int) bbox[2], (
int) bbox[3]};
2554 json_annotation[
"area"] = (int) ann.at(
"area")[0];
2556 const auto &seg = ann.at(
"segmentation");
2557 std::vector<int> segmentation_coords;
2558 for (
float coord: seg) {
2559 segmentation_coords.push_back((
int) coord);
2561 json_annotation[
"segmentation"] = {segmentation_coords};
2562 json_annotation[
"iscrowd"] = (int) ann.at(
"iscrowd")[0];
2565 if (use_attributes && ann_idx < mean_attribute_values_per_component.size()) {
2566 json_annotation[
"attributes"] = mean_attribute_values_per_component[ann_idx];
2569 coco_json[
"annotations"].push_back(json_annotation);
2570 max_annotation_id++;
2576 writeCOCOJson(coco_json, outfile);
2580 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2581 writeImageSegmentationMasks_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, data_attribute_labels, append_file);
2585 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2587 if (cameras.find(cameralabel) == cameras.end()) {
2588 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Camera '" + cameralabel +
"' does not exist.");
2591 if (object_data_label.size() != object_class_ID.size()) {
2592 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
2596 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2598 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2603 for (
const auto &data_label: object_data_label) {
2604 if (std::find(all_object_data.begin(), all_object_data.end(), data_label) == all_object_data.end()) {
2605 std::cerr <<
"WARNING (RadiationModel::writeImageSegmentationMasks_ObjectData): Object data label '" << data_label <<
"' does not exist in the context." << std::endl;
2610 if (!std::filesystem::exists(image_file)) {
2611 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Image file '" + image_file +
"' does not exist.");
2615 std::string validated_json_filename = json_filename;
2616 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2617 validated_json_filename +=
".json";
2621 std::string outfile = validated_json_filename;
2624 int2 camera_resolution = cameras.at(cameralabel).resolution;
2625 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2626 nlohmann::json coco_json = coco_json_pair.first;
2627 int image_id = coco_json_pair.second;
2628 addCategoryToCOCO(coco_json, object_class_ID, object_data_label);
2631 struct AttributeInfo {
2633 bool is_primitive_data;
2636 std::vector<AttributeInfo> attribute_info;
2638 if (!data_attribute_labels.empty()) {
2642 for (
const auto &attr_label: data_attribute_labels) {
2644 info.label = attr_label;
2645 info.exists =
false;
2647 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2648 info.is_primitive_data =
true;
2650 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2651 info.is_primitive_data =
false;
2656 attribute_info.push_back(info);
2661 bool use_attributes = !attribute_info.empty();
2664 std::vector<uint> pixel_UUIDs;
2665 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2666 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2669 for (
size_t i = 0; i < object_data_label.size(); ++i) {
2671 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, object_data_label[i],
true);
2674 int max_annotation_id = -1;
2675 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2676 if (existing_ann[
"id"] > max_annotation_id) {
2677 max_annotation_id = existing_ann[
"id"];
2683 for (
const auto &label_pair: label_masks) {
2684 const auto &mask = label_pair.second;
2687 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2690 for (
int j = 0; j < camera_resolution.
y; j++) {
2691 for (
int i_px = 0; i_px < camera_resolution.
x; i_px++) {
2692 if (mask[j][i_px] && !visited[j][i_px]) {
2694 int boundary_i = i_px, boundary_j = j;
2695 bool is_boundary =
false;
2698 for (
int di = -1; di <= 1; di++) {
2699 for (
int dj = -1; dj <= 1; dj++) {
2702 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
2715 std::stack<std::pair<int, int>> stack;
2716 std::vector<std::pair<int, int>> component_pixels;
2717 stack.push({i_px, j});
2718 visited[j][i_px] =
true;
2720 int min_x = i_px, max_x = i_px, min_y = j, max_y = j;
2723 while (!stack.empty()) {
2724 auto [ci, cj] = stack.top();
2727 component_pixels.push_back({ci, cj});
2729 min_x = std::min(min_x, ci);
2730 max_x = std::max(max_x, ci);
2731 min_y = std::min(min_y, cj);
2732 max_y = std::max(max_y, cj);
2735 for (
int di = -1; di <= 1; di++) {
2736 for (
int dj = -1; dj <= 1; dj++) {
2737 if (abs(di) + abs(dj) != 1)
2741 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2742 stack.push({ni, nj});
2743 visited[nj][ni] =
true;
2750 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2751 bool is_boundary_start =
false;
2753 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2754 is_boundary_start =
true;
2757 if (is_boundary_start) {
2759 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2762 if (contour.size() < 10) {
2763 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2766 if (contour.size() >= 3) {
2768 std::map<std::string, double> component_attributes;
2769 if (use_attributes) {
2770 for (
const auto &attr: attribute_info) {
2774 for (
const auto &[px_i, px_j]: component_pixels) {
2775 uint ii = camera_resolution.
x - px_i - 1;
2776 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.
x + ii) - 1;
2780 bool has_value =
false;
2782 if (attr.is_primitive_data) {
2788 value =
static_cast<double>(val);
2793 value =
static_cast<double>(val);
2798 value =
static_cast<double>(val);
2812 value =
static_cast<double>(val);
2817 value =
static_cast<double>(val);
2822 value =
static_cast<double>(val);
2839 component_attributes[attr.label] =
sum / count;
2841 component_attributes[attr.label] = 0.0;
2847 nlohmann::json json_annotation;
2848 json_annotation[
"id"] = max_annotation_id + 1;
2849 json_annotation[
"image_id"] = image_id;
2850 json_annotation[
"category_id"] = (int) object_class_ID[i];
2851 json_annotation[
"bbox"] = {min_x, min_y, max_x - min_x, max_y - min_y};
2852 json_annotation[
"area"] = area;
2853 json_annotation[
"iscrowd"] = 0;
2856 std::vector<int> segmentation_coords;
2857 for (
const auto &point: contour) {
2858 segmentation_coords.push_back(point.first);
2859 segmentation_coords.push_back(point.second);
2861 json_annotation[
"segmentation"] = {segmentation_coords};
2864 if (use_attributes) {
2865 json_annotation[
"attributes"] = component_attributes;
2868 coco_json[
"annotations"].push_back(json_annotation);
2869 max_annotation_id++;
2874 std::stack<std::pair<int, int>> stack;
2875 stack.push({i_px, j});
2876 visited[j][i_px] =
true;
2878 while (!stack.empty()) {
2879 auto [ci, cj] = stack.top();
2883 for (
int di = -1; di <= 1; di++) {
2884 for (
int dj = -1; dj <= 1; dj++) {
2885 if (abs(di) + abs(dj) != 1)
2889 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2890 stack.push({ni, nj});
2891 visited[nj][ni] =
true;
2904 writeCOCOJson(coco_json, outfile);
2908 for (
uint b = 0; b < bandlabels.size(); b++) {
2909 std::string bandlabel = bandlabels.at(b);
2911 std::string image_value_label =
"camera_" + cameralabel +
"_" + bandlabel;
2912 std::vector<float> cameradata;
2913 context->
getGlobalData(image_value_label.c_str(), cameradata);
2915 std::vector<uint> camera_UUIDs;
2916 std::string image_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2917 context->
getGlobalData(image_UUID_label.c_str(), camera_UUIDs);
2919 for (
uint i = 0; i < cameradata.size(); i++) {
2920 uint UUID = camera_UUIDs.at(i) - 1;
2922 cameradata.at(i) = padvalues.at(b);
2925 context->
setGlobalData(image_value_label.c_str(), cameradata);
2929void 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,
2930 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2932 if (cameras.find(originalcameralabel) == cameras.end()) {
2933 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2934 }
else if (radiation_sources.empty()) {
2935 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2939 if (!calibration_flag) {
2940 std::cout <<
"No color board added, use default color calibration." << std::endl;
2941 cameracalibration = &cameracalibration_;
2949 std::vector<std::string> cameraresplabels_cal(cameraresplabels_raw.size());
2951 for (
int iband = 0; iband < bandlabels.size(); iband++) {
2952 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + cameraresplabels_raw.at(iband);
2961 std::cout <<
"Camera response scale: " << camerascale << std::endl;
2966void RadiationModel::calibrateCamera(
const std::string &originalcameralabel,
const float scalefactor,
const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2968 if (cameras.find(originalcameralabel) == cameras.end()) {
2969 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2970 }
else if (radiation_sources.empty()) {
2971 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2975 if (!calibration_flag) {
2976 std::cout <<
"No color board added, use default color calibration." << std::endl;
2980 RadiationModel::setCameraCalibration(&cameracalibration_);
2985 std::vector<std::string> bandlabels = cameras.at(originalcameralabel).band_labels;
2988 std::vector<std::string> cameraresplabels_cal(cameras.at(originalcameralabel).band_spectral_response.size());
2989 std::vector<std::string> cameraresplabels_raw = cameraresplabels_cal;
2992 for (
auto &band: cameras.at(originalcameralabel).band_spectral_response) {
2993 cameraresplabels_raw.at(iband) = band.second;
2994 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + band.second;
2999 std::vector<std::string> sourcelabels(radiation_sources.size());
3001 for (
auto &source: radiation_sources) {
3002 if (source.source_spectrum.empty()) {
3003 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): A spectral distribution was not specified for source " + source.source_spectrum_label +
". Cannot perform camera calibration.");
3005 sourcelabels.at(isource) = source.source_spectrum_label;
3016 std::cout <<
"Camera response scale: " << camerascale << std::endl;
3021std::vector<helios::vec2> RadiationModel::generateGaussianCameraResponse(
float FWHM,
float mu,
float centrawavelength,
const helios::int2 &wavebandrange) {
3024 float sigma = FWHM / (2 * std::sqrt(2 * std::log(2)));
3026 size_t lenspectra = wavebandrange.
y - wavebandrange.
x;
3027 std::vector<helios::vec2> cameraresponse(lenspectra);
3030 for (
int i = 0; i < lenspectra; ++i) {
3031 cameraresponse.at(i).x = float(wavebandrange.
x + i);
3035 for (
size_t i = 0; i < lenspectra; ++i) {
3036 cameraresponse.at(i).y = centrawavelength * std::exp(-std::pow((cameraresponse.at(i).x - mu), 2) / (2 * std::pow(sigma, 2)));
3040 return cameraresponse;
3043void RadiationModel::applyCameraImageCorrections(
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,
3044 float contrast_adjustment) {
3046 if (cameras.find(cameralabel) == cameras.end()) {
3047 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): Camera '" + cameralabel +
"' does not exist.");
3050 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()) {
3051 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): One or more specified band labels do not exist for the camera pixel data.");
3055 if (camera_metadata.find(cameralabel) == camera_metadata.end()) {
3058 camera_metadata[cameralabel].image_processing.saturation_adjustment = saturation_adjustment;
3059 camera_metadata[cameralabel].image_processing.brightness_adjustment = brightness_adjustment;
3060 camera_metadata[cameralabel].image_processing.contrast_adjustment = contrast_adjustment;
3069 lens_flare.
apply(camera.pixel_data, camera.resolution);
3073 if (brightness_adjustment != 1.f || contrast_adjustment != 1.f) {
3074 camera.
adjustBrightnessContrast(red_band_label, green_band_label, blue_band_label, brightness_adjustment, contrast_adjustment);
3078 if (saturation_adjustment != 1.f) {
3079 camera.
adjustSaturation(red_band_label, green_band_label, blue_band_label, saturation_adjustment);
3083void 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,
3084 float contrast_adjustment,
float gain_adjustment) {
3085 applyCameraImageCorrections(cameralabel, red_band_label, green_band_label, blue_band_label, saturation_adjustment, brightness_adjustment, contrast_adjustment);
3090 float min_P = (std::numeric_limits<float>::max)();
3092 for (
const auto &[channel_label, data]: pixel_data) {
3093 for (
float v: data) {
3103 for (
auto &[channel_label, data]: pixel_data) {
3104 for (
float &v: data) {
3105 v = (v - min_P) / (max_P - min_P);
3113 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()) {
3114 helios_runtime_error(
"ERROR (RadiationModel::whiteBalance): One or more specified band labels do not exist for the camera pixel data.");
3118 auto &data_red = pixel_data.at(red_band_label);
3119 auto &data_green = pixel_data.at(green_band_label);
3120 auto &data_blue = pixel_data.at(blue_band_label);
3122 const std::size_t N = data_red.size();
3123 if (data_green.size() != N || data_blue.size() != N) {
3124 throw std::invalid_argument(
"All channels must have the same length");
3127 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
3134 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
3135 for (std::size_t i = 0; i < N; ++i) {
3136 acc_r += std::pow(data_red[i], p);
3137 acc_g += std::pow(data_green[i], p);
3138 acc_b += std::pow(data_blue[i], p);
3140 float mean_r_p = acc_r /
static_cast<float>(N);
3141 float mean_g_p = acc_g /
static_cast<float>(N);
3142 float mean_b_p = acc_b /
static_cast<float>(N);
3144 float M_R = std::pow(mean_r_p, 1.0f / p);
3145 float M_G = std::pow(mean_g_p, 1.0f / p);
3146 float M_B = std::pow(mean_b_p, 1.0f / p);
3149 const float eps = 1e-6f;
3150 if (M_R < eps || M_G < eps || M_B < eps) {
3151 throw std::runtime_error(
"Channel Minkowski mean too small");
3156 float M = (M_R + M_G + M_B) / 3.0f;
3167 for (std::size_t i = 0; i < N; ++i) {
3168 data_red[i] *= scale.
x;
3169 data_green[i] *= scale.
y;
3170 data_blue[i] *= scale.
z;
3177 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()) {
3178 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceGrayEdge): One or more specified band labels do not exist for the camera pixel data.");
3182 auto &data_red = pixel_data.at(red_band_label);
3183 auto &data_green = pixel_data.at(green_band_label);
3184 auto &data_blue = pixel_data.at(blue_band_label);
3186 const int width = resolution.
x;
3187 const int height = resolution.
y;
3188 const std::size_t N = width * height;
3191 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
3193 if (derivative_order < 1 || derivative_order > 2) {
3194 throw std::invalid_argument(
"Derivative order must be 1 or 2");
3198 std::vector<float> deriv_red(N, 0.0f);
3199 std::vector<float> deriv_green(N, 0.0f);
3200 std::vector<float> deriv_blue(N, 0.0f);
3202 if (derivative_order == 1) {
3204 for (
int y = 1; y < height - 1; ++y) {
3205 for (
int x = 1; x < width - 1; ++x) {
3206 int idx = y * width + x;
3209 float dx_r = (data_red[(y - 1) * width + (x + 1)] + 2 * data_red[y * width + (x + 1)] + data_red[(y + 1) * width + (x + 1)]) -
3210 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[y * width + (x - 1)] + data_red[(y + 1) * width + (x - 1)]) / 8.0f;
3211 float dy_r = (data_red[(y + 1) * width + (x - 1)] + 2 * data_red[(y + 1) * width + x] + data_red[(y + 1) * width + (x + 1)]) -
3212 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[(y - 1) * width + x] + data_red[(y - 1) * width + (x + 1)]) / 8.0f;
3213 deriv_red[idx] = std::sqrt(dx_r * dx_r + dy_r * dy_r);
3215 float dx_g = (data_green[(y - 1) * width + (x + 1)] + 2 * data_green[y * width + (x + 1)] + data_green[(y + 1) * width + (x + 1)]) -
3216 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[y * width + (x - 1)] + data_green[(y + 1) * width + (x - 1)]) / 8.0f;
3217 float dy_g = (data_green[(y + 1) * width + (x - 1)] + 2 * data_green[(y + 1) * width + x] + data_green[(y + 1) * width + (x + 1)]) -
3218 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[(y - 1) * width + x] + data_green[(y - 1) * width + (x + 1)]) / 8.0f;
3219 deriv_green[idx] = std::sqrt(dx_g * dx_g + dy_g * dy_g);
3221 float dx_b = (data_blue[(y - 1) * width + (x + 1)] + 2 * data_blue[y * width + (x + 1)] + data_blue[(y + 1) * width + (x + 1)]) -
3222 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[y * width + (x - 1)] + data_blue[(y + 1) * width + (x - 1)]) / 8.0f;
3223 float dy_b = (data_blue[(y + 1) * width + (x - 1)] + 2 * data_blue[(y + 1) * width + x] + data_blue[(y + 1) * width + (x + 1)]) -
3224 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[(y - 1) * width + x] + data_blue[(y - 1) * width + (x + 1)]) / 8.0f;
3225 deriv_blue[idx] = std::sqrt(dx_b * dx_b + dy_b * dy_b);
3230 for (
int y = 1; y < height - 1; ++y) {
3231 for (
int x = 1; x < width - 1; ++x) {
3232 int idx = y * width + x;
3234 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]);
3236 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]);
3238 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]);
3244 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
3245 int valid_pixels = 0;
3247 for (std::size_t i = 0; i < N; ++i) {
3248 if (deriv_red[i] > 0 || deriv_green[i] > 0 || deriv_blue[i] > 0) {
3249 acc_r += std::pow(deriv_red[i], p);
3250 acc_g += std::pow(deriv_green[i], p);
3251 acc_b += std::pow(deriv_blue[i], p);
3256 if (valid_pixels == 0) {
3258 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3262 float mean_r_p = acc_r /
static_cast<float>(valid_pixels);
3263 float mean_g_p = acc_g /
static_cast<float>(valid_pixels);
3264 float mean_b_p = acc_b /
static_cast<float>(valid_pixels);
3266 float M_R = std::pow(mean_r_p, 1.0f / p);
3267 float M_G = std::pow(mean_g_p, 1.0f / p);
3268 float M_B = std::pow(mean_b_p, 1.0f / p);
3271 const float eps = 1e-6f;
3272 if (M_R < eps || M_G < eps || M_B < eps) {
3274 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3279 float M = (M_R + M_G + M_B) / 3.0f;
3288 for (std::size_t i = 0; i < N; ++i) {
3289 data_red[i] *= scale.
x;
3290 data_green[i] *= scale.
y;
3291 data_blue[i] *= scale.
z;
3298 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()) {
3299 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceWhitePatch): One or more specified band labels do not exist for the camera pixel data.");
3303 if (percentile <= 0.0f || percentile > 1.0f) {
3304 throw std::invalid_argument(
"Percentile must be in range (0, 1]");
3307 auto &data_red = pixel_data.at(red_band_label);
3308 auto &data_green = pixel_data.at(green_band_label);
3309 auto &data_blue = pixel_data.at(blue_band_label);
3311 const std::size_t N = data_red.size();
3314 std::vector<float> sorted_red = data_red;
3315 std::vector<float> sorted_green = data_green;
3316 std::vector<float> sorted_blue = data_blue;
3318 std::size_t k =
static_cast<std::size_t
>(percentile * (N - 1));
3320 std::nth_element(sorted_red.begin(), sorted_red.begin() + k, sorted_red.end());
3321 std::nth_element(sorted_green.begin(), sorted_green.begin() + k, sorted_green.end());
3322 std::nth_element(sorted_blue.begin(), sorted_blue.begin() + k, sorted_blue.end());
3324 float white_r = sorted_red[k];
3325 float white_g = sorted_green[k];
3326 float white_b = sorted_blue[k];
3329 const float eps = 1e-6f;
3330 if (white_r < eps || white_g < eps || white_b < eps) {
3331 throw std::runtime_error(
"White patch values too small");
3335 for (std::size_t i = 0; i < N; ++i) {
3336 data_red[i] /= white_r;
3337 data_green[i] /= white_g;
3338 data_blue[i] /= white_b;
3346 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()) {
3347 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): One or more specified band labels do not exist for the camera pixel data.");
3352 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()) {
3353 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Spectral response data not found for one or more bands. Ensure camera spectral responses are properly initialized.");
3357 std::string red_response_id = band_spectral_response.at(red_band_label);
3358 std::string green_response_id = band_spectral_response.at(green_band_label);
3359 std::string blue_response_id = band_spectral_response.at(blue_band_label);
3362 if (red_response_id ==
"uniform" && green_response_id ==
"uniform" && blue_response_id ==
"uniform") {
3367 std::vector<helios::vec2> red_spectrum, green_spectrum, blue_spectrum;
3369 if (red_response_id !=
"uniform" && context->
doesGlobalDataExist(red_response_id.c_str())) {
3370 context->
getGlobalData(red_response_id.c_str(), red_spectrum);
3372 if (green_response_id !=
"uniform" && context->
doesGlobalDataExist(green_response_id.c_str())) {
3373 context->
getGlobalData(green_response_id.c_str(), green_spectrum);
3375 if (blue_response_id !=
"uniform" && context->
doesGlobalDataExist(blue_response_id.c_str())) {
3376 context->
getGlobalData(blue_response_id.c_str(), blue_spectrum);
3380 if (red_spectrum.empty() || green_spectrum.empty() || blue_spectrum.empty()) {
3381 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Could not retrieve spectral response curves for all bands from global data.");
3386 float red_integrated = 0.0f, green_integrated = 0.0f, blue_integrated = 0.0f;
3388 for (
size_t i = 1; i < red_spectrum.size(); ++i) {
3389 float dw = red_spectrum[i].x - red_spectrum[i - 1].x;
3390 red_integrated += 0.5f * (red_spectrum[i].y + red_spectrum[i - 1].y) * dw;
3392 for (
size_t i = 1; i < green_spectrum.size(); ++i) {
3393 float dw = green_spectrum[i].x - green_spectrum[i - 1].x;
3394 green_integrated += 0.5f * (green_spectrum[i].y + green_spectrum[i - 1].y) * dw;
3396 for (
size_t i = 1; i < blue_spectrum.size(); ++i) {
3397 float dw = blue_spectrum[i].x - blue_spectrum[i - 1].x;
3398 blue_integrated += 0.5f * (blue_spectrum[i].y + blue_spectrum[i - 1].y) * dw;
3402 if (red_integrated <= 0 || green_integrated <= 0 || blue_integrated <= 0) {
3403 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Invalid integrated spectral response (non-positive value). Check spectral response data.");
3410 float max_integrated = std::max({red_integrated, green_integrated, blue_integrated});
3413 white_balance_factors.
x = max_integrated / red_integrated;
3414 white_balance_factors.y = max_integrated / green_integrated;
3415 white_balance_factors.z = max_integrated / blue_integrated;
3419 auto &data_red = pixel_data.at(red_band_label);
3420 auto &data_green = pixel_data.at(green_band_label);
3421 auto &data_blue = pixel_data.at(blue_band_label);
3423 const std::size_t N = data_red.size();
3424 for (std::size_t i = 0; i < N; ++i) {
3425 data_red[i] *= white_balance_factors.x;
3426 data_green[i] *= white_balance_factors.y;
3427 data_blue[i] *= white_balance_factors.z;
3434 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()) {
3435 helios_runtime_error(
"ERROR (RadiationModel::reinhardToneMapping): One or more specified band labels do not exist for the camera pixel data.");
3439 const std::size_t N = resolution.
x * resolution.
y;
3440 constexpr float eps = 1e-6f;
3442 auto &data_red = pixel_data.at(red_band_label);
3443 auto &data_green = pixel_data.at(green_band_label);
3444 auto &data_blue = pixel_data.at(blue_band_label);
3445 for (std::size_t i = 0; i < N; ++i) {
3446 float R = data_red[i], G = data_green[i], B = data_blue[i];
3447 float L = luminance(
R, G, B);
3448 float s = (L > eps) ? (L / (1.0f + L)) / L : 0.0f;
3450 data_red[i] =
R * s;
3451 data_green[i] = G * s;
3452 data_blue[i] = B * s;
3456void RadiationCamera::applyGain(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float percentile) {
3459 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()) {
3460 helios_runtime_error(
"ERROR (RadiationModel::applyGain): One or more specified band labels do not exist for the camera pixel data.");
3464 const std::size_t N = resolution.
x * resolution.
y;
3466 auto &data_red = pixel_data.at(red_band_label);
3467 auto &data_green = pixel_data.at(green_band_label);
3468 auto &data_blue = pixel_data.at(blue_band_label);
3470 std::vector<float> luminance_pixel;
3471 luminance_pixel.reserve(N);
3472 for (std::size_t i = 0; i < N; ++i) {
3473 luminance_pixel.push_back(luminance(data_red[i], data_green[i], data_blue[i]));
3476 std::size_t k = std::size_t(percentile * (luminance_pixel.size() - 1));
3477 std::nth_element(luminance_pixel.begin(), luminance_pixel.begin() + k, luminance_pixel.end());
3478 float peak = luminance_pixel[k];
3479 float gain = (peak > 0.0f) ? 1.0f / peak : 1.0f;
3481 for (
auto &[channel, data]: pixel_data) {
3482 for (
float &v: data) {
3491 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()) {
3492 helios_runtime_error(
"ERROR (RadiationModel::globalHistogramEquilization): One or more specified band labels do not exist for the camera pixel data.");
3496 const size_t N = resolution.
x * resolution.
y;
3497 const float eps = 1e-6f;
3499 auto &data_red = pixel_data.at(red_band_label);
3500 auto &data_green = pixel_data.at(green_band_label);
3501 auto &data_blue = pixel_data.at(blue_band_label);
3504 std::vector<float> lum(N);
3505 std::vector<float> chroma_r(N), chroma_g(N), chroma_b(N);
3507 for (
size_t i = 0; i < N; ++i) {
3508 vec3 p(data_red[i], data_green[i], data_blue[i]);
3509 lum[i] = 0.2126f * p.
x + 0.7152f * p.
y + 0.0722f * p.
z;
3513 chroma_r[i] = p.
x / lum[i];
3514 chroma_g[i] = p.
y / lum[i];
3515 chroma_b[i] = p.
z / lum[i];
3525 std::vector<int> hist(B, 0);
3526 for (
float v: lum) {
3527 int b = int(std::clamp(v, 0.0f, 1.0f - eps) * B);
3528 if (b >= 0 && b < 2048) {
3532 std::vector<float> cdf(B);
3534 for (
int b = 0; b < B; ++b) {
3536 cdf[b] = float(acc) / float(N);
3540 for (
size_t i = 0; i < N; ++i) {
3542 if (lum[i] >= 1.0f) {
3543 data_red[i] = std::min(1.0f, data_red[i]);
3544 data_green[i] = std::min(1.0f, data_green[i]);
3545 data_blue[i] = std::min(1.0f, data_blue[i]);
3549 int b = int(std::clamp(lum[i], 0.0f, 1.0f - eps) * B);
3551 if (b < 0 || b >= 2048) {
3555 constexpr float k = 0.2f;
3556 constexpr float cs = 0.2f;
3559 float Ynew = (1.0f - k) * lum[i] + k * Yeq;
3562 float t = Ynew - 0.5f;
3563 Ynew = 0.5f + t * (1.0f + cs - 2.0f * cs * std::fabs(t));
3566 data_red[i] = Ynew * chroma_r[i];
3567 data_green[i] = Ynew * chroma_g[i];
3568 data_blue[i] = Ynew * chroma_b[i];
3572void 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) {
3574 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()) {
3575 helios_runtime_error(
"ERROR (RadiationModel::adjustSBC): One or more specified band labels do not exist for the camera pixel data.");
3579 constexpr float kRedW = 0.2126f;
3580 constexpr float kGreenW = 0.7152f;
3581 constexpr float kBlueW = 0.0722f;
3583 const size_t N = resolution.
x * resolution.
y;
3585 auto &data_red = pixel_data.at(red_band_label);
3586 auto &data_green = pixel_data.at(green_band_label);
3587 auto &data_blue = pixel_data.at(blue_band_label);
3589 for (
int i = 0; i < N; ++i) {
3591 helios::vec3 p(data_red[i], data_green[i], data_blue[i]);
3594 float Y = kRedW * p.
x + kGreenW * p.
y + kBlueW * p.
z;
3606 data_red[i] =
clamp(p.
x, 0.0f, 1.0f);
3607 data_green[i] =
clamp(p.
y, 0.0f, 1.0f);
3608 data_blue[i] =
clamp(p.
z, 0.0f, 1.0f);
3629 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()) {
3630 helios_runtime_error(
"ERROR (RadiationModel::gammaCompress): One or more specified band labels do not exist for the camera pixel data.");
3634 for (
float &v: pixel_data.at(red_band_label)) {
3637 for (
float &v: pixel_data.at(green_band_label)) {
3640 for (
float &v: pixel_data.at(blue_band_label)) {
3647void RadiationCamera::autoExposure(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float gain_multiplier) {
3649 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()) {
3650 helios_runtime_error(
"ERROR (RadiationModel::autoExposure): One or more specified band labels do not exist for the camera pixel data.");
3654 auto &data_red = pixel_data.at(red_band_label);
3655 auto &data_green = pixel_data.at(green_band_label);
3656 auto &data_blue = pixel_data.at(blue_band_label);
3658 const std::size_t N = data_red.size();
3661 std::vector<float> luminance_values(N);
3662 for (std::size_t i = 0; i < N; ++i) {
3663 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3667 std::vector<float> sorted_luminance = luminance_values;
3668 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3671 std::size_t p95_idx =
static_cast<std::size_t
>(0.95f * (N - 1));
3672 float p95_luminance = sorted_luminance[p95_idx];
3675 std::size_t median_idx = N / 2;
3676 float median_luminance = sorted_luminance[median_idx];
3680 float target_median = 0.18f;
3681 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3687 float final_gain = auto_gain * gain_multiplier;
3690 for (std::size_t i = 0; i < N; ++i) {
3691 data_red[i] *= final_gain;
3692 data_green[i] *= final_gain;
3693 data_blue[i] *= final_gain;
3699 if (pixel_data.empty()) {
3704 for (
const auto &band: band_labels) {
3705 if (pixel_data.find(band) == pixel_data.end()) {
3711 std::string exposure_mode = exposure;
3714 if (exposure_mode ==
"manual") {
3719 if (exposure_mode ==
"auto") {
3721 std::string cam_type;
3722 if (!camera_type.empty()) {
3723 cam_type = camera_type;
3726 cam_type = (band_labels.size() >= 3) ?
"rgb" :
"spectral";
3729 if (cam_type ==
"thermal") {
3732 }
else if (cam_type ==
"rgb" && band_labels.size() >= 3) {
3736 std::string red_band, green_band, blue_band;
3737 for (
const auto &band: band_labels) {
3738 if (band.find(
"red") != std::string::npos || band.find(
"Red") != std::string::npos || band.find(
"RED") != std::string::npos) {
3740 }
else if (band.find(
"green") != std::string::npos || band.find(
"Green") != std::string::npos || band.find(
"GREEN") != std::string::npos) {
3742 }
else if (band.find(
"blue") != std::string::npos || band.find(
"Blue") != std::string::npos || band.find(
"BLUE") != std::string::npos) {
3748 if (red_band.empty())
3749 red_band = band_labels[0];
3750 if (green_band.empty())
3751 green_band = band_labels[1];
3752 if (blue_band.empty())
3753 blue_band = band_labels[2];
3755 auto &data_red = pixel_data.at(red_band);
3756 auto &data_green = pixel_data.at(green_band);
3757 auto &data_blue = pixel_data.at(blue_band);
3759 const std::size_t N = data_red.size();
3762 std::vector<float> luminance_values(N);
3763 for (std::size_t i = 0; i < N; ++i) {
3764 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3768 std::vector<float> sorted_luminance = luminance_values;
3769 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3771 std::size_t median_idx = N / 2;
3772 float median_luminance = sorted_luminance[median_idx];
3775 float target_median = 0.18f;
3776 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3780 for (
auto &band_pair: pixel_data) {
3781 auto &data = band_pair.second;
3782 for (std::size_t i = 0; i < N; ++i) {
3783 data[i] *= auto_gain;
3787 }
else if (cam_type ==
"spectral") {
3799 for (
auto &band_pair: pixel_data) {
3800 auto &data = band_pair.second;
3801 const std::size_t N = data.size();
3803 std::vector<float> sorted_data = data;
3804 std::sort(sorted_data.begin(), sorted_data.end());
3806 const std::size_t p95_idx =
static_cast<std::size_t
>(0.95f * (N - 1));
3807 const float p95_value = sorted_data[p95_idx];
3813 const float target_p95 = 0.7f;
3814 const float band_gain = target_p95 / std::max(p95_value, 1e-6f);
3817 for (std::size_t i = 0; i < N; ++i) {
3818 data[i] *= band_gain;
3822 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown camera_type '" + cam_type +
"'. Must be 'rgb', 'spectral', or 'thermal'.");
3828 if (exposure_mode.substr(0, 3) ==
"ISO" || exposure_mode.substr(0, 3) ==
"iso") {
3832 iso_value = std::stoi(exposure_mode.substr(3));
3834 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Invalid ISO format '" + exposure_mode +
"'. Expected format: 'ISOXXX' (e.g., 'ISO100').");
3837 if (iso_value <= 0) {
3838 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): ISO value must be positive. Got: " + std::to_string(iso_value));
3842 if (lens_focal_length <= 0) {
3843 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): ISO mode requires lens_focal_length to be set. Camera '" + label +
"' has lens_focal_length = " + std::to_string(lens_focal_length) +
3844 ". Either set it explicitly or use 'auto' or 'manual' exposure mode.");
3849 float f_number = lens_focal_length / std::max(lens_diameter, 1e-6f);
3852 const float ref_iso = 100.0f;
3853 const float ref_shutter = 1.0f / 125.0f;
3854 const float ref_f_number = 2.8f;
3859 const float typical_scene_median = 10.0f;
3860 const float target_median = 0.0675f;
3864 float exposure = (float(iso_value) * shutter_speed) / (f_number * f_number);
3865 float ref_exposure = (ref_iso * ref_shutter) / (ref_f_number * ref_f_number);
3871 float ref_gain = target_median / typical_scene_median;
3872 float calibration_factor = ref_gain / ref_exposure;
3875 float exposure_multiplier = exposure * calibration_factor;
3879 const std::size_t N = pixel_data.begin()->second.size();
3880 for (
auto &band_pair: pixel_data) {
3881 auto &data = band_pair.second;
3882 for (std::size_t i = 0; i < N; ++i) {
3883 data[i] *= exposure_multiplier;
3890 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown exposure mode '" + exposure_mode +
"'. Must be 'auto', 'ISOXXX' (e.g., 'ISO100'), or 'manual'.");
3895 if (pixel_data.empty()) {
3900 for (
const auto &band: band_labels) {
3901 if (pixel_data.find(band) == pixel_data.end()) {
3907 std::string wb_mode = white_balance;
3910 if (wb_mode ==
"off") {
3915 if (band_labels.size() < 3) {
3920 if (wb_mode ==
"auto") {
3923 std::string red_band = band_labels[0];
3924 std::string green_band = band_labels[1];
3925 std::string blue_band = band_labels[2];
3929 }
catch (
const std::exception &e) {
3938 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraWhiteBalance): Unknown white_balance mode '" + wb_mode +
"'. Must be 'auto' or 'off'.");
3943 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()) {
3944 helios_runtime_error(
"ERROR (RadiationModel::adjustBrightnessContrast): One or more specified band labels do not exist for the camera pixel data.");
3948 auto &data_red = pixel_data.at(red_band_label);
3949 auto &data_green = pixel_data.at(green_band_label);
3950 auto &data_blue = pixel_data.at(blue_band_label);
3952 const std::size_t N = data_red.size();
3954 for (std::size_t i = 0; i < N; ++i) {
3956 float r = data_red[i] * brightness;
3957 float g = data_green[i] * brightness;
3958 float b = data_blue[i] * brightness;
3961 r = 0.5f + (r - 0.5f) * contrast;
3962 g = 0.5f + (g - 0.5f) * contrast;
3963 b = 0.5f + (b - 0.5f) * contrast;
3974 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()) {
3975 helios_runtime_error(
"ERROR (RadiationModel::adjustSaturation): One or more specified band labels do not exist for the camera pixel data.");
3979 auto &data_red = pixel_data.at(red_band_label);
3980 auto &data_green = pixel_data.at(green_band_label);
3981 auto &data_blue = pixel_data.at(blue_band_label);
3983 const std::size_t N = data_red.size();
3985 for (std::size_t i = 0; i < N; ++i) {
3986 float r = data_red[i];
3987 float g = data_green[i];
3988 float b = data_blue[i];
3991 float lum = luminance(r, g, b);
3994 data_red[i] = lum + saturation * (r - lum);
3995 data_green[i] = lum + saturation * (g - lum);
3996 data_blue[i] = lum + saturation * (b - lum);
4002std::string RadiationModel::detectLightingType()
const {
4003 if (radiation_sources.empty()) {
4007 bool has_sun =
false;
4008 bool has_artificial =
false;
4010 for (
const auto &source: radiation_sources) {
4011 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4013 }
else if (source.source_type == RADIATION_SOURCE_TYPE_SPHERE || source.source_type == RADIATION_SOURCE_TYPE_RECTANGLE || source.source_type == RADIATION_SOURCE_TYPE_DISK) {
4014 has_artificial =
true;
4018 if (has_sun && has_artificial) {
4020 }
else if (has_sun) {
4022 }
else if (has_artificial) {
4023 return "artificial";
4037 float tilt_angle_deg = -asin(direction.
z) * 180.0f /
M_PI;
4039 return tilt_angle_deg;
4044 if (cameras.find(camera_label) == cameras.end()) {
4045 helios_runtime_error(
"ERROR (RadiationModel::computeAgronomicProperties): Camera '" + camera_label +
"' does not exist.");
4048 const auto &cam = cameras.at(camera_label);
4060 std::vector<uint> pixel_UUIDs;
4061 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
4066 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
4069 std::map<std::string, std::set<int>> species_to_plantIDs;
4072 std::set<int> weed_plantIDs;
4075 std::set<int> all_plantIDs;
4078 std::map<std::string, std::map<int, float>> species_plant_heights;
4079 std::map<std::string, std::map<int, float>> species_plant_ages;
4080 std::map<std::string, std::map<int, std::string>> species_plant_stages;
4081 std::map<std::string, std::map<int, float>> species_plant_leaf_areas;
4082 std::map<std::string, std::map<int, int>> species_plant_pixel_counts;
4085 for (
uint j = 0; j < cam.resolution.y; j++) {
4086 for (
uint i = 0; i < cam.resolution.x; i++) {
4087 uint pixel_index = j * cam.resolution.x + i;
4089 if (pixel_index >= pixel_UUIDs.size()) {
4093 uint UUID_plus_one = pixel_UUIDs.at(pixel_index);
4094 if (UUID_plus_one == 0) {
4099 uint UUID = UUID_plus_one - 1;
4114 std::string plant_name;
4115 bool has_plant_name =
false;
4118 if (datatype == HELIOS_TYPE_STRING) {
4120 has_plant_name =
true;
4126 bool has_plantID =
false;
4129 if (datatype == HELIOS_TYPE_INT) {
4132 }
else if (datatype == HELIOS_TYPE_UINT) {
4135 plantID =
static_cast<int>(plantID_uint);
4141 std::string plant_type;
4142 bool has_plant_type =
false;
4145 if (datatype == HELIOS_TYPE_STRING) {
4147 has_plant_type =
true;
4152 float plant_height = 0.0f;
4153 bool has_plant_height =
false;
4156 if (datatype == HELIOS_TYPE_FLOAT) {
4157 context->
getObjectData(objID,
"plant_height", plant_height);
4158 has_plant_height =
true;
4164 bool has_age =
false;
4167 if (datatype == HELIOS_TYPE_FLOAT) {
4174 std::string phenology_stage;
4175 bool has_phenology_stage =
false;
4178 if (datatype == HELIOS_TYPE_STRING) {
4179 context->
getObjectData(objID,
"phenology_stage", phenology_stage);
4180 has_phenology_stage =
true;
4188 if (has_plant_name && has_plantID) {
4190 species_to_plantIDs[plant_name].insert(plantID);
4193 all_plantIDs.insert(plantID);
4196 if (has_plant_type && plant_type ==
"weed") {
4197 weed_plantIDs.insert(plantID);
4201 if (has_plant_height) {
4202 species_plant_heights[plant_name][plantID] = plant_height;
4205 species_plant_ages[plant_name][plantID] = age;
4207 if (has_phenology_stage) {
4208 species_plant_stages[plant_name][plantID] = phenology_stage;
4212 species_plant_leaf_areas[plant_name][plantID] += primitive_area;
4215 species_plant_pixel_counts[plant_name][plantID]++;
4221 if (species_to_plantIDs.empty()) {
4226 for (
const auto &species_pair: species_to_plantIDs) {
4228 props.
plant_count.push_back(
static_cast<int>(species_pair.second.size()));
4232 for (
const auto &species_pair: species_to_plantIDs) {
4233 const std::string &species = species_pair.first;
4234 const std::set<int> &plantIDs = species_pair.second;
4237 if (species_plant_heights.find(species) != species_plant_heights.end()) {
4238 float total_weighted_height = 0.0f;
4239 int total_pixels = 0;
4240 for (
int plantID: plantIDs) {
4241 if (species_plant_heights.at(species).find(plantID) != species_plant_heights.at(species).end()) {
4242 float height = species_plant_heights.at(species).at(plantID);
4243 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4244 total_weighted_height += height *
static_cast<float>(pixel_count);
4245 total_pixels += pixel_count;
4248 if (total_pixels > 0) {
4249 props.
plant_height_m.push_back(total_weighted_height /
static_cast<float>(total_pixels));
4258 if (species_plant_ages.find(species) != species_plant_ages.end()) {
4259 float total_weighted_age = 0.0f;
4260 int total_pixels = 0;
4261 for (
int plantID: plantIDs) {
4262 if (species_plant_ages.at(species).find(plantID) != species_plant_ages.at(species).end()) {
4263 float age = species_plant_ages.at(species).at(plantID);
4264 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4265 total_weighted_age += age *
static_cast<float>(pixel_count);
4266 total_pixels += pixel_count;
4269 if (total_pixels > 0) {
4270 props.
plant_age_days.push_back(total_weighted_age /
static_cast<float>(total_pixels));
4279 if (species_plant_stages.find(species) != species_plant_stages.end()) {
4280 std::map<std::string, int> stage_counts;
4281 for (
int plantID: plantIDs) {
4282 if (species_plant_stages.at(species).find(plantID) != species_plant_stages.at(species).end()) {
4283 std::string stage = species_plant_stages.at(species).at(plantID);
4284 stage_counts[stage]++;
4288 std::string mode_stage;
4290 for (
const auto &stage_pair: stage_counts) {
4291 if (stage_pair.second > max_count) {
4292 max_count = stage_pair.second;
4293 mode_stage = stage_pair.first;
4302 if (species_plant_leaf_areas.find(species) != species_plant_leaf_areas.end()) {
4303 float total_leaf_area = 0.0f;
4304 for (
int plantID: plantIDs) {
4305 if (species_plant_leaf_areas.at(species).find(plantID) != species_plant_leaf_areas.at(species).end()) {
4306 total_leaf_area += species_plant_leaf_areas.at(species).at(plantID);
4316 if (!all_plantIDs.empty()) {
4317 float weed_fraction =
static_cast<float>(weed_plantIDs.size()) /
static_cast<float>(all_plantIDs.size());
4318 float weed_percentage = weed_fraction * 100.0f;
4320 if (weed_percentage <= 20.0f) {
4322 }
else if (weed_percentage <= 40.0f) {
4330void RadiationModel::populateCameraMetadata(
const std::string &camera_label,
CameraMetadata &metadata)
const {
4332 if (cameras.find(camera_label) == cameras.end()) {
4333 helios_runtime_error(
"ERROR (RadiationModel::populateCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4336 const auto &cam = cameras.at(camera_label);
4339 metadata.camera_properties.
width = cam.resolution.x;
4340 metadata.camera_properties.
height = cam.resolution.y;
4341 metadata.camera_properties.
channels =
static_cast<int>(cam.band_labels.size());
4342 metadata.camera_properties.
type = cam.camera_type;
4346 metadata.camera_properties.
sensor_width = cam.sensor_width_mm;
4349 float VFOV_degrees = cam.HFOV_degrees / cam.FOV_aspect_ratio;
4352 metadata.camera_properties.
sensor_height = cam.sensor_width_mm / cam.FOV_aspect_ratio;
4361 float HFOV_rad = cam.HFOV_degrees *
M_PI / 180.0f;
4362 float optical_focal_length_mm = cam.sensor_width_mm / (2.0f * tan(HFOV_rad / 2.0f));
4363 metadata.camera_properties.
focal_length = optical_focal_length_mm;
4366 if (cam.lens_diameter > 0) {
4367 float lens_diameter_mm = cam.lens_diameter * 1000.0f;
4368 float f_number = optical_focal_length_mm / lens_diameter_mm;
4369 std::ostringstream aperture_str;
4370 aperture_str <<
"f/" << std::fixed << std::setprecision(1) << f_number;
4371 metadata.camera_properties.
aperture = aperture_str.str();
4373 metadata.camera_properties.
aperture =
"pinhole";
4377 metadata.camera_properties.
model = cam.model;
4380 metadata.camera_properties.
lens_make = cam.lens_make;
4381 metadata.camera_properties.
lens_model = cam.lens_model;
4385 metadata.camera_properties.
exposure = cam.exposure;
4386 metadata.camera_properties.
shutter_speed = cam.shutter_speed;
4389 metadata.camera_properties.
white_balance = cam.white_balance;
4392 metadata.camera_properties.
camera_zoom = cam.camera_zoom;
4404 std::ostringstream date_str;
4405 date_str << date.
year <<
"-" << std::setw(2) << std::setfill(
'0') << date.
month <<
"-" << std::setw(2) << std::setfill(
'0') << date.
day;
4406 metadata.acquisition_properties.
date = date_str.str();
4409 std::ostringstream time_str;
4410 time_str << std::setw(2) << std::setfill(
'0') << time.
hour <<
":" << std::setw(2) << std::setfill(
'0') << time.
minute <<
":" << std::setw(2) << std::setfill(
'0') << time.
second;
4411 metadata.acquisition_properties.
time = time_str.str();
4415 metadata.acquisition_properties.
camera_angle_deg = calculateCameraTiltAngle(cam.position, cam.lookat);
4416 metadata.acquisition_properties.
light_source = detectLightingType();
4419 computeAgronomicProperties(camera_label, metadata.agronomic_properties);
4427 if (cameras.find(camera_label) == cameras.end()) {
4428 helios_runtime_error(
"ERROR (RadiationModel::enableCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4433 if (camera_metadata.find(camera_label) != camera_metadata.end()) {
4434 saved_image_processing = camera_metadata.at(camera_label).image_processing;
4439 populateCameraMetadata(camera_label, metadata);
4442 metadata.image_processing = saved_image_processing;
4445 camera_metadata[camera_label] = metadata;
4446 metadata_enabled_cameras.insert(camera_label);
4451 for (
const auto &camera_label: camera_labels) {
4458 if (cameras.find(camera_label) == cameras.end()) {
4459 helios_runtime_error(
"ERROR (RadiationModel::getCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4464 populateCameraMetadata(camera_label, metadata);
4471 if (cameras.find(camera_label) == cameras.end()) {
4472 helios_runtime_error(
"ERROR (RadiationModel::setCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4475 camera_metadata[camera_label] = metadata;
4478std::string RadiationModel::writeCameraMetadataFile(
const std::string &camera_label,
const std::string &output_path)
const {
4480 if (camera_metadata.find(camera_label) == camera_metadata.end()) {
4481 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): No metadata set for camera '" + camera_label +
"'.");
4484 const auto &metadata = camera_metadata.at(camera_label);
4488 auto format_float = [](
float value,
int decimals) ->
double {
4489 std::ostringstream oss;
4490 oss << std::fixed << std::setprecision(decimals) << value;
4491 return std::stod(oss.str());
4496 j[
"path"] = metadata.
path;
4498 j[
"camera_properties"][
"height"] = metadata.camera_properties.
height;
4499 j[
"camera_properties"][
"width"] = metadata.camera_properties.
width;
4500 j[
"camera_properties"][
"channels"] = metadata.camera_properties.
channels;
4501 j[
"camera_properties"][
"type"] = metadata.camera_properties.
type;
4502 j[
"camera_properties"][
"focal_length"] = format_float(metadata.camera_properties.
focal_length, 2);
4503 j[
"camera_properties"][
"aperture"] = metadata.camera_properties.
aperture;
4504 j[
"camera_properties"][
"sensor_width"] = format_float(metadata.camera_properties.
sensor_width, 2);
4505 j[
"camera_properties"][
"sensor_height"] = format_float(metadata.camera_properties.
sensor_height, 2);
4506 j[
"camera_properties"][
"model"] = metadata.camera_properties.
model;
4509 if (!metadata.camera_properties.
lens_make.empty()) {
4510 j[
"camera_properties"][
"lens_make"] = metadata.camera_properties.
lens_make;
4512 if (!metadata.camera_properties.
lens_model.empty()) {
4513 j[
"camera_properties"][
"lens_model"] = metadata.camera_properties.
lens_model;
4516 j[
"camera_properties"][
"lens_specification"] = metadata.camera_properties.
lens_specification;
4520 j[
"camera_properties"][
"exposure"] = metadata.camera_properties.
exposure;
4521 j[
"camera_properties"][
"shutter_speed"] = format_float(metadata.camera_properties.
shutter_speed, 6);
4524 j[
"camera_properties"][
"white_balance"] = metadata.camera_properties.
white_balance;
4527 j[
"camera_properties"][
"zoom"] = format_float(metadata.camera_properties.
camera_zoom, 2);
4529 j[
"location_properties"][
"latitude"] = format_float(metadata.location_properties.
latitude, 6);
4530 j[
"location_properties"][
"longitude"] = format_float(metadata.location_properties.
longitude, 6);
4532 j[
"acquisition_properties"][
"date"] = metadata.acquisition_properties.
date;
4533 j[
"acquisition_properties"][
"time"] = metadata.acquisition_properties.
time;
4534 j[
"acquisition_properties"][
"UTC_offset"] = format_float(metadata.acquisition_properties.
UTC_offset, 1);
4535 j[
"acquisition_properties"][
"camera_height_m"] = format_float(metadata.acquisition_properties.
camera_height_m, 2);
4536 j[
"acquisition_properties"][
"camera_angle_deg"] = format_float(metadata.acquisition_properties.
camera_angle_deg, 2);
4537 j[
"acquisition_properties"][
"light_source"] = metadata.acquisition_properties.
light_source;
4540 const auto &img_proc = metadata.image_processing;
4541 j[
"image_processing"][
"exposure_gain"] = format_float(img_proc.exposure_gain, 4);
4542 j[
"image_processing"][
"white_balance_factors"] = {format_float(img_proc.white_balance_factors.x, 4),
4543 format_float(img_proc.white_balance_factors.y, 4),
4544 format_float(img_proc.white_balance_factors.z, 4)};
4545 j[
"image_processing"][
"saturation_adjustment"] = format_float(img_proc.saturation_adjustment, 2);
4546 j[
"image_processing"][
"brightness_adjustment"] = format_float(img_proc.brightness_adjustment, 2);
4547 j[
"image_processing"][
"contrast_adjustment"] = format_float(img_proc.contrast_adjustment, 2);
4548 j[
"image_processing"][
"color_space"] = img_proc.color_space;
4552 j[
"agronomic_properties"][
"plant_species"] = metadata.agronomic_properties.
plant_species;
4553 j[
"agronomic_properties"][
"plant_count"] = metadata.agronomic_properties.
plant_count;
4557 std::vector<double> formatted_heights;
4558 for (
float height: metadata.agronomic_properties.plant_height_m) {
4559 formatted_heights.push_back(format_float(height, 2));
4561 j[
"agronomic_properties"][
"plant_height_m"] = formatted_heights;
4565 std::vector<double> formatted_ages;
4566 for (
float age: metadata.agronomic_properties.plant_age_days) {
4567 formatted_ages.push_back(format_float(age, 1));
4569 j[
"agronomic_properties"][
"plant_age_days"] = formatted_ages;
4572 if (!metadata.agronomic_properties.
plant_stage.empty()) {
4573 j[
"agronomic_properties"][
"plant_stage"] = metadata.agronomic_properties.
plant_stage;
4576 if (!metadata.agronomic_properties.
leaf_area_m2.empty()) {
4577 std::vector<double> formatted_leaf_areas;
4578 for (
float area: metadata.agronomic_properties.leaf_area_m2) {
4579 formatted_leaf_areas.push_back(format_float(area, 4));
4581 j[
"agronomic_properties"][
"leaf_area_m2"] = formatted_leaf_areas;
4584 j[
"agronomic_properties"][
"weed_pressure"] = metadata.agronomic_properties.
weed_pressure;
4588 std::string json_filename = metadata.
path;
4589 size_t ext_pos = json_filename.find_last_of(
".");
4590 if (ext_pos != std::string::npos) {
4591 json_filename = json_filename.substr(0, ext_pos) +
".json";
4593 json_filename +=
".json";
4597 std::string json_path = output_path + json_filename;
4600 std::ofstream json_file(json_path);
4601 if (!json_file.is_open()) {
4602 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): Failed to open file '" + json_path +
"' for writing.");
4604 json_file << j.dump(2) << std::endl;
4611 if (cameras.find(camera_label) == cameras.end()) {
4612 helios_runtime_error(
"ERROR (RadiationModel::enableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4614 cameras.at(camera_label).lens_flare_enabled =
true;
4618 if (cameras.find(camera_label) == cameras.end()) {
4619 helios_runtime_error(
"ERROR (RadiationModel::disableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4621 cameras.at(camera_label).lens_flare_enabled =
false;
4625 if (cameras.find(camera_label) == cameras.end()) {
4626 helios_runtime_error(
"ERROR (RadiationModel::isCameraLensFlareEnabled): Camera '" + camera_label +
"' does not exist.");
4628 return cameras.at(camera_label).lens_flare_enabled;
4632 if (cameras.find(camera_label) == cameras.end()) {
4633 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4638 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): aperture_blade_count must be at least 3.");
4641 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): coating_efficiency must be in range [0.0, 1.0].");
4644 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_intensity must be non-negative.");
4647 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): starburst_intensity must be non-negative.");
4650 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): intensity_threshold must be in range [0.0, 1.0].");
4653 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_count must be at least 1.");
4656 cameras.at(camera_label).lens_flare_properties = properties;
4660 if (cameras.find(camera_label) == cameras.end()) {
4661 helios_runtime_error(
"ERROR (RadiationModel::getCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4663 return cameras.at(camera_label).lens_flare_properties;