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.");
43 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;
47 RadiationCamera camera(camera_label, band_label, position, lookat, modified_properties, antialiasing_samples);
48 if (cameras.find(camera_label) == cameras.end()) {
49 cameras.emplace(camera_label, camera);
52 std::cout <<
"Camera with label " << camera_label <<
"already exists. Existing properties will be replaced by new inputs." << std::endl;
54 cameras.erase(camera_label);
55 cameras.emplace(camera_label, camera);
58 if (iscameravisualizationenabled) {
59 buildCameraModelGeometry(camera_label);
64 populateCameraMetadata(camera_label, metadata);
65 camera_metadata[camera_label] = metadata;
67 radiativepropertiesneedupdate =
true;
71 uint antialiasing_samples) {
74 addRadiationCamera(camera_label, band_label, position, lookat, camera_properties, antialiasing_samples);
78 if (cameras.find(camera_label) == cameras.end()) {
79 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Camera '" + camera_label +
"' does not exist.");
81 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Band '" + band_label +
"' does not exist.");
84 cameras.at(camera_label).band_spectral_response[band_label] = global_data;
86 radiativepropertiesneedupdate =
true;
91 if (cameras.find(camera_label) == cameras.end()) {
92 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Camera '" + camera_label +
"' does not exist.");
95 const auto &band_labels = cameras.at(camera_label).band_labels;
101 for (
const auto &band: band_labels) {
102 std::string response_spectrum = camera_library_name +
"_" + band;
104 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Band '" + band +
"' referenced in spectral library camera " + camera_library_name +
" does not exist for camera '" + camera_label +
"'.");
107 cameras.at(camera_label).band_spectral_response[band] = response_spectrum;
110 radiativepropertiesneedupdate =
true;
119 const std::vector<std::string> &custom_band_labels) {
125 pugi::xml_document xmldoc;
126 pugi::xml_parse_result result = xmldoc.load_file(library_path.string().c_str());
129 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Failed to load camera library file '" + library_path.string() +
"'. " + result.description());
132 pugi::xml_node helios_node = xmldoc.child(
"helios");
133 if (helios_node.empty()) {
134 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera library XML must have '<helios>' root tag.");
138 pugi::xml_node camera_node;
139 for (pugi::xml_node cam = helios_node.child(
"camera"); cam; cam = cam.next_sibling(
"camera")) {
140 std::string label = cam.attribute(
"label").value();
141 if (label == library_camera_label) {
147 if (camera_node.empty()) {
148 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera '" + library_camera_label +
"' not found in camera library.");
152 std::string manufacturer = camera_node.child(
"manufacturer").child_value();
153 std::string model = camera_node.child(
"model").child_value();
156 std::string camera_type = camera_node.child(
"type").child_value();
157 if (camera_type.empty()) {
158 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Missing required 'type' field for camera '" + library_camera_label +
"'.");
160 if (camera_type !=
"rgb" && camera_type !=
"spectral" && camera_type !=
"thermal") {
161 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid camera type '" + camera_type +
"' for camera '" + library_camera_label +
"'. Must be one of: 'rgb', 'spectral', or 'thermal'.");
164 float sensor_width_mm;
165 if (!
helios::parse_float(camera_node.child(
"sensor_width_mm").child_value(), sensor_width_mm)) {
166 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing sensor_width_mm for camera '" + library_camera_label +
"'.");
169 int resolution_width;
170 if (!
helios::parse_int(camera_node.child(
"resolution_width").child_value(), resolution_width)) {
171 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_width for camera '" + library_camera_label +
"'.");
174 int resolution_height;
175 if (!
helios::parse_int(camera_node.child(
"resolution_height").child_value(), resolution_height)) {
176 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_height for camera '" + library_camera_label +
"'.");
180 float focal_length_mm;
181 if (!
helios::parse_float(camera_node.child(
"focal_length_mm").child_value(), focal_length_mm)) {
182 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing focal_length_mm for camera '" + library_camera_label +
"'.");
185 float lens_diameter_mm;
186 if (!
helios::parse_float(camera_node.child(
"lens_diameter_mm").child_value(), lens_diameter_mm)) {
187 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing lens_diameter_mm for camera '" + library_camera_label +
"'.");
191 float focal_plane_distance_m = 2.0f;
192 if (camera_node.child(
"focal_plane_distance_m")) {
193 if (!
helios::parse_float(camera_node.child(
"focal_plane_distance_m").child_value(), focal_plane_distance_m)) {
194 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid focal_plane_distance_m for camera '" + library_camera_label +
"'.");
199 std::string lens_make = camera_node.child(
"lens_make").child_value();
200 std::string lens_model = camera_node.child(
"lens_model").child_value();
201 std::string lens_specification = camera_node.child(
"lens_specification").child_value();
204 std::string exposure_mode = camera_node.child(
"exposure").child_value();
205 if (exposure_mode.empty()) {
206 exposure_mode =
"auto";
210 float shutter_speed = 1.0f / 125.0f;
211 if (camera_node.child(
"shutter_speed")) {
212 if (!
helios::parse_float(camera_node.child(
"shutter_speed").child_value(), shutter_speed)) {
213 std::cerr <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Invalid shutter_speed for camera '" << library_camera_label <<
"'. Using default 1/125 second." << std::endl;
214 shutter_speed = 1.0f / 125.0f;
219 std::string white_balance_mode = camera_node.child(
"white_balance").child_value();
220 if (white_balance_mode.empty()) {
221 white_balance_mode =
"auto";
222 }
else if (white_balance_mode !=
"auto" && white_balance_mode !=
"off") {
223 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;
224 white_balance_mode =
"auto";
234 float HFOV_rad = 2.0f * atan(sensor_width_mm / (2.0f * focal_length_mm));
235 camera_properties.
HFOV = HFOV_rad * 180.0f /
M_PI;
244 camera_properties.
lens_diameter = lens_diameter_mm / 1000.0f;
250 camera_properties.
model = manufacturer +
" " + model;
258 camera_properties.
exposure = exposure_mode;
266 std::vector<std::string> xml_band_labels;
268 std::vector<std::pair<float, float>> spectral_wavelength_ranges;
270 for (pugi::xml_node spectral_node = camera_node.child(
"spectral_response"); spectral_node; spectral_node = spectral_node.next_sibling(
"spectral_response")) {
272 std::string xml_band_label = spectral_node.attribute(
"label").value();
273 if (xml_band_label.empty()) {
274 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): spectral_response node missing 'label' attribute for camera '" + library_camera_label +
"'.");
277 xml_band_labels.push_back(xml_band_label);
280 std::vector<helios::vec2> spectral_data;
281 std::string data_str = spectral_node.child_value();
283 if (!data_str.empty()) {
284 std::istringstream data_stream(data_str);
285 float wavelength, response;
286 while (data_stream >> wavelength >> response) {
291 if (spectral_data.empty()) {
292 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Empty spectral response data for band '" + xml_band_label +
"' in camera '" + library_camera_label +
"'.");
296 spectral_wavelength_ranges.emplace_back(spectral_data.front().x, spectral_data.back().x);
299 std::string global_data_label = library_camera_label +
"_" + xml_band_label;
300 context->
setGlobalData(global_data_label.c_str(), spectral_data);
303 if (xml_band_labels.empty()) {
304 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): No spectral response data found for camera '" + library_camera_label +
"'.");
308 std::vector<std::string> effective_band_labels;
309 if (!custom_band_labels.empty()) {
310 if (custom_band_labels.size() != xml_band_labels.size()) {
311 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 (" +
312 std::to_string(xml_band_labels.size()) +
") for camera '" + library_camera_label +
"'.");
314 effective_band_labels = custom_band_labels;
316 effective_band_labels = xml_band_labels;
320 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
321 const std::string &band_label = effective_band_labels[i];
323 float min_wavelength = spectral_wavelength_ranges[i].first;
324 float max_wavelength = spectral_wavelength_ranges[i].second;
333 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;
338 addRadiationCamera(camera_label, effective_band_labels, position, lookat, camera_properties, antialiasing_samples);
341 cameras.at(camera_label).camera_type = camera_type;
344 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
345 std::string global_data_label = library_camera_label +
"_" + xml_band_labels[i];
351 if (cameras.find(camera_label) == cameras.end()) {
352 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera '" + camera_label +
"' does not exist.");
353 }
else if (position == cameras.at(camera_label).lookat) {
354 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera position cannot be equal to the 'lookat' position.");
357 cameras.at(camera_label).position = position;
359 if (iscameravisualizationenabled) {
360 updateCameraModelPosition(camera_label);
366 if (cameras.find(camera_label) == cameras.end()) {
367 helios_runtime_error(
"ERROR (RadiationModel::getCameraPosition): Camera '" + camera_label +
"' does not exist.");
370 return cameras.at(camera_label).position;
374 if (cameras.find(camera_label) == cameras.end()) {
375 helios_runtime_error(
"ERROR (RadiationModel::setCameraLookat): Camera '" + camera_label +
"' does not exist.");
378 cameras.at(camera_label).lookat = lookat;
380 if (iscameravisualizationenabled) {
381 updateCameraModelPosition(camera_label);
387 if (cameras.find(camera_label) == cameras.end()) {
388 helios_runtime_error(
"ERROR (RadiationModel::getCameraLookat): Camera '" + camera_label +
"' does not exist.");
391 return cameras.at(camera_label).lookat;
395 if (cameras.find(camera_label) == cameras.end()) {
396 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
399 cameras.at(camera_label).lookat = cameras.at(camera_label).position + direction;
401 if (iscameravisualizationenabled) {
402 updateCameraModelPosition(camera_label);
408 if (cameras.find(camera_label) == cameras.end()) {
409 helios_runtime_error(
"ERROR (RadiationModel::getCameraOrientation): Camera '" + camera_label +
"' does not exist.");
412 return cart2sphere(cameras.at(camera_label).lookat - cameras.at(camera_label).position);
416 if (cameras.find(camera_label) == cameras.end()) {
417 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
420 cameras.at(camera_label).lookat = cameras.at(camera_label).position +
sphere2cart(direction);
422 if (iscameravisualizationenabled) {
423 updateCameraModelPosition(camera_label);
430 if (cameras.find(camera_label) == cameras.end()) {
431 helios_runtime_error(
"ERROR (RadiationModel::getCameraParameters): Camera '" + camera_label +
"' does not exist.");
435 const auto &camera = cameras.at(camera_label);
440 camera_properties.
HFOV = camera.HFOV_degrees;
445 camera_properties.
model = camera.model;
446 camera_properties.
lens_make = camera.lens_make;
447 camera_properties.
lens_model = camera.lens_model;
449 camera_properties.
exposure = camera.exposure;
452 camera_properties.
camera_zoom = camera.camera_zoom;
455 return camera_properties;
461 if (cameras.find(camera_label) == cameras.end()) {
462 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera '" + camera_label +
"' does not exist.");
467 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera resolution must be at least 1x1.");
468 }
else if (camera_properties.
HFOV <= 0 || camera_properties.
HFOV >= 180.f) {
469 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera horizontal field of view must be between 0 and 180 degrees.");
470 }
else if (camera_properties.
camera_zoom <= 0.0f) {
471 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.");
475 auto &camera = cameras.at(camera_label);
479 camera.HFOV_degrees = camera_properties.
HFOV;
484 camera.model = camera_properties.
model;
485 camera.exposure = camera_properties.
exposure;
488 camera.camera_zoom = camera_properties.
camera_zoom;
491 camera.FOV_aspect_ratio = float(camera.resolution.x) / float(camera.resolution.y);
494 radiativepropertiesneedupdate =
true;
497 if (iscameravisualizationenabled) {
498 updateCameraModelPosition(camera_label);
503 std::vector<std::string> labels(cameras.size());
505 for (
const auto &camera: cameras) {
506 labels.at(cam) = camera.second.label;
512std::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) {
515 if (cameras.find(camera) == cameras.end()) {
516 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
520 if (bands.size() != 1 && bands.size() != 3) {
521 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.");
524 std::vector<std::vector<float>> camera_data(bands.size());
527 for (
const auto &band: bands) {
530 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
531 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
535 camera_data.at(b) = cameras.at(camera).pixel_data.at(band);
542 bool is_rgb = (camera_data.size() == 3);
544 for (
auto &band_data: camera_data) {
545 for (
float &v: band_data) {
551 std::string frame_str;
553 frame_str = std::to_string(frame);
556 std::string output_path = image_path;
558 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
560 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
563 std::ostringstream outfile;
564 outfile << output_path;
567 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
569 outfile << camera <<
"_" << imagefile_base <<
".jpeg";
571 std::ofstream testfile(outfile.str());
573 if (!testfile.is_open()) {
574 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;
579 int2 camera_resolution = cameras.at(camera).resolution;
581 std::vector<RGBcolor> pixel_data_RGB(camera_resolution.
x * camera_resolution.
y);
584 for (
uint j = 0; j < camera_resolution.
y; j++) {
585 for (
uint i = 0; i < camera_resolution.
x; i++) {
586 if (camera_data.size() == 1) {
587 float c = camera_data.front().at(j * camera_resolution.
x + i);
590 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));
592 pixel_color.
scale(flux_to_pixel_conversion);
593 uint ii = camera_resolution.
x - i - 1;
594 uint jj = camera_resolution.
y - j - 1;
595 pixel_data_RGB.at(jj * camera_resolution.
x + ii) = pixel_color;
599 writeJPEG(outfile.str(), camera_resolution.
x, camera_resolution.
y, pixel_data_RGB);
601 std::string image_filepath = outfile.str();
604 if (metadata_enabled_cameras.find(camera) != metadata_enabled_cameras.end()) {
607 if (camera_metadata.find(camera) != camera_metadata.end()) {
608 saved_image_processing = camera_metadata.at(camera).image_processing;
613 populateCameraMetadata(camera, metadata);
616 metadata.image_processing = saved_image_processing;
619 metadata.image_processing.
color_space = is_rgb ?
"sRGB" :
"linear";
622 size_t last_slash = image_filepath.find_last_of(
"/\\");
623 std::string filename_only = (last_slash != std::string::npos) ? image_filepath.substr(last_slash + 1) : image_filepath;
624 metadata.
path = filename_only;
627 camera_metadata[camera] = metadata;
628 writeCameraMetadataFile(camera, output_path);
631 return image_filepath;
634std::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) {
637 for (
const std::string &band: bands) {
638 std::string global_data_label =
"camera_" + camera +
"_" + band;
639 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
640 std::cout <<
"ERROR (RadiationModel::writeNormCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
643 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;
646 std::vector<float> cameradata;
647 context->
getGlobalData(global_data_label.c_str(), cameradata);
648 for (
float val: cameradata) {
655 for (
const std::string &band: bands) {
656 std::string global_data_label =
"camera_" + camera +
"_" + band;
657 std::vector<float> cameradata;
658 context->
getGlobalData(global_data_label.c_str(), cameradata);
659 for (
float &val: cameradata) {
662 context->
setGlobalData(global_data_label.c_str(), cameradata);
671 if (cameras.find(camera) == cameras.end()) {
672 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
676 std::vector<float> camera_data;
679 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
680 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
684 std::string global_data_label =
"camera_" + camera +
"_" + band;
687 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;
691 context->
getGlobalData(global_data_label.c_str(), camera_data);
693 std::string frame_str;
695 frame_str = std::to_string(frame);
698 std::string output_path = image_path;
700 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
702 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
705 std::ostringstream outfile;
706 outfile << output_path;
709 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
711 outfile << camera <<
"_" << imagefile_base <<
".txt";
714 std::ofstream outfilestream(outfile.str());
716 if (!outfilestream.is_open()) {
717 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;
721 int2 camera_resolution = cameras.at(camera).resolution;
723 for (
int j = 0; j < camera_resolution.
y; j++) {
724 for (
int i = camera_resolution.
x - 1; i >= 0; i--) {
725 outfilestream << camera_data.at(j * camera_resolution.
x + i) <<
" ";
727 outfilestream <<
"\n";
730 outfilestream.close();
735 calibration_flag =
true;
739 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
741 std::vector<std::string> objectlabels;
742 vec2 wavelengthrange_c = wavelengthrange;
743 cameracalibration->
preprocessSpectra(sourcelabels_raw, cameraresponselabels, objectlabels, wavelengthrange_c);
747 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
751 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
753 cameraproperties.
exposure = calibratecamera.exposure;
754 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
757 std::string cameralabel =
"calibration";
758 std::map<uint, std::vector<vec2>> simulatedcolorboardspectra;
759 for (
uint UUID: UUIDs_target) {
760 simulatedcolorboardspectra.emplace(UUID, NULL);
763 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
767 std::vector<float> wavelengths;
769 int numberwavelengths = wavelengths.size();
771 for (
int iw = 0; iw < numberwavelengths; iw++) {
772 std::string wavelengthlabel = std::to_string(wavelengths.at(iw));
774 std::vector<std::string> sourcelabels;
775 for (std::string sourcelabel_raw: sourcelabels_raw) {
776 std::vector<vec2> icalsource;
777 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
778 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
779 icalsource.at(1).x += 1;
780 std::string sourcelable =
"Cal_source_" + sourcelabel_raw;
781 sourcelabels.push_back(sourcelable);
785 std::vector<vec2> icalcamera(2);
786 icalcamera.at(0).y = 1;
787 icalcamera.at(1).y = 1;
788 icalcamera.at(0).x = wavelengths.at(iw);
789 icalcamera.at(1).x = wavelengths.at(iw) + 1;
790 std::string camlable =
"Cal_cameraresponse";
793 for (
auto objectpair: cameracalibration->processedspectra.at(
"object")) {
794 std::vector<vec2> spectrum_obj;
795 spectrum_obj.push_back(objectpair.second.at(iw));
796 spectrum_obj.push_back(objectpair.second.at(iw));
797 spectrum_obj.at(1).x += 1;
798 context->
setGlobalData(objectpair.first.c_str(), spectrum_obj);
805 for (std::string sourcelabel_raw: sourcelabels_raw) {
819 std::vector<float> camera_data;
820 std::string global_data_label =
"camera_" + cameralabel +
"_" + wavelengthlabel;
821 context->
getGlobalData(global_data_label.c_str(), camera_data);
823 std::vector<uint> pixel_labels;
824 std::string global_data_label_UUID =
"camera_" + cameralabel +
"_pixel_UUID";
825 context->
getGlobalData(global_data_label_UUID.c_str(), pixel_labels);
827 for (
uint j = 0; j < calibratecamera.resolution.
y; j++) {
828 for (
uint i = 0; i < calibratecamera.resolution.
x; i++) {
829 float icdata = camera_data.at(j * calibratecamera.resolution.
x + i);
831 uint UUID = pixel_labels.at(j * calibratecamera.resolution.
x + i) - 1;
832 if (find(UUIDs_target.begin(), UUIDs_target.end(), UUID) != UUIDs_target.end()) {
833 if (simulatedcolorboardspectra.at(UUID).empty()) {
834 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
835 }
else if (simulatedcolorboardspectra.at(UUID).back().x == wavelengths.at(iw)) {
836 simulatedcolorboardspectra.at(UUID).back().y += icdata / float(numberwavelengths);
837 }
else if (simulatedcolorboardspectra.at(UUID).back().x != wavelengths.at(iw)) {
838 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
848 for (
uint UUID: UUIDs_colorbd) {
849 std::string colorboardspectra;
850 context->
getPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra);
851 context->
setPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra +
"_raw");
856 float fluxscale,
float diffusefactor,
uint scatteringdepth) {
858 float sources_fluxsum = 0;
859 std::vector<float> sources_fluxes;
860 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
861 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
865 sources_fluxsum += sources_fluxes.at(ID);
870 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
877 if (bandlabels.size() > 1) {
878 for (
int iband = 1; iband < bandlabels.size(); iband++) {
880 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
887 for (
int iband = 0; iband < bandlabels.size(); iband++) {
895void 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,
896 helios::vec2 wavelengthrange,
float fluxscale,
float diffusefactor,
uint scatteringdepth) {
898 float sources_fluxsum = 0;
899 std::vector<float> sources_fluxes;
900 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
901 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
905 sources_fluxsum += sources_fluxes.at(ID);
910 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
917 if (bandlabels.size() > 1) {
918 for (
int iband = 1; iband < bandlabels.size(); iband++) {
920 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
927 for (
int ic = 0; ic < cameralabels.size(); ic++) {
928 for (
int iband = 0; iband < bandlabels.size(); iband++) {
938float 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,
939 const std::vector<std::vector<float>> &truevalues) {
944 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
948 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
950 cameraproperties.
exposure = calibratecamera.exposure;
951 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
953 std::string cameralabel = orginalcameralabel +
"Scale";
965 if (cameras.find(cameralabel) == cameras.end()) {
966 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
970 std::vector<uint> camera_UUIDs;
971 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
973 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
975 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
976 std::vector<uint> pixel_UUIDs = camera_UUIDs;
977 int2 camera_resolution = cameras.at(cameralabel).resolution;
979 std::string frame_str;
981 frame_str = std::to_string(frame);
984 std::string output_path = image_path;
986 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
988 helios_runtime_error(
"ERROR(RadiationModel::writePrimitiveDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
991 std::ostringstream outfile;
992 outfile << output_path;
995 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
997 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1001 std::ofstream pixel_data(outfile.str());
1003 if (!pixel_data.is_open()) {
1004 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1007 bool empty_flag =
true;
1009 for (
uint j = 0; j < camera_resolution.
y; j++) {
1010 for (
uint i = 0; i < camera_resolution.
x; i++) {
1011 uint ii = camera_resolution.
x - i - 1;
1012 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1018 pixel_data << labeldata <<
" ";
1023 pixel_data << labeldata <<
" ";
1028 pixel_data << labeldata <<
" ";
1033 pixel_data << labeldata <<
" ";
1036 pixel_data << padvalue <<
" ";
1039 pixel_data << padvalue <<
" ";
1047 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;
1053 if (cameras.find(cameralabel) == cameras.end()) {
1054 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
1058 std::vector<uint> camera_UUIDs;
1059 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1061 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1063 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1064 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1065 int2 camera_resolution = cameras.at(cameralabel).resolution;
1067 std::string frame_str;
1069 frame_str = std::to_string(frame);
1072 std::string output_path = image_path;
1074 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1076 helios_runtime_error(
"ERROR(RadiationModel::writeObjectDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
1079 std::ostringstream outfile;
1080 outfile << output_path;
1083 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1085 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1089 std::ofstream pixel_data(outfile.str());
1091 if (!pixel_data.is_open()) {
1092 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1095 bool empty_flag =
true;
1097 for (
uint j = 0; j < camera_resolution.
y; j++) {
1098 for (
uint i = 0; i < camera_resolution.
x; i++) {
1099 uint ii = camera_resolution.
x - i - 1;
1100 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1102 pixel_data << padvalue <<
" ";
1110 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1111 pixel_data << labeldata <<
" ";
1115 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1116 pixel_data << labeldata <<
" ";
1120 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1121 pixel_data << labeldata <<
" ";
1125 context->
getObjectData(objID, object_data_label.c_str(), labeldata);
1126 pixel_data << labeldata <<
" ";
1129 pixel_data << padvalue <<
" ";
1132 pixel_data << padvalue <<
" ";
1140 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;
1146 if (cameras.find(cameralabel) == cameras.end()) {
1147 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Camera '" + cameralabel +
"' does not exist.");
1150 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1152 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1154 std::vector<float> camera_depth;
1155 context->
getGlobalData(global_data_label.c_str(), camera_depth);
1156 helios::vec3 camera_position = cameras.at(cameralabel).position;
1157 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1159 int2 camera_resolution = cameras.at(cameralabel).resolution;
1161 std::string frame_str;
1163 frame_str = std::to_string(frame);
1166 std::string output_path = image_path;
1168 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1170 helios_runtime_error(
"ERROR(RadiationModel::writeDepthImageData): Expected a directory path but got a file path for argument 'image_path'.");
1173 std::ostringstream outfile;
1174 outfile << output_path;
1177 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1179 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1183 std::ofstream pixel_data(outfile.str());
1185 if (!pixel_data.is_open()) {
1186 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Could not open file '" + outfile.str() +
"' for writing.");
1189 for (
int j = 0; j < camera_resolution.
y; j++) {
1190 for (
int i = camera_resolution.
x - 1; i >= 0; i--) {
1191 pixel_data << camera_depth.at(j * camera_resolution.
x + i) <<
" ";
1201 if (cameras.find(cameralabel) == cameras.end()) {
1202 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Camera '" + cameralabel +
"' does not exist.");
1205 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1207 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1209 std::vector<float> camera_depth;
1210 context->
getGlobalData(global_data_label.c_str(), camera_depth);
1211 helios::vec3 camera_position = cameras.at(cameralabel).position;
1212 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1214 int2 camera_resolution = cameras.at(cameralabel).resolution;
1216 std::string frame_str;
1218 frame_str = std::to_string(frame);
1221 std::string output_path = image_path;
1223 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1225 helios_runtime_error(
"ERROR(RadiationModel::writeNormDepthImage): Expected a directory path but got a file path for argument 'image_path'.");
1228 std::ostringstream outfile;
1229 outfile << output_path;
1232 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
1234 outfile << cameralabel <<
"_" << imagefile_base <<
".jpeg";
1237 float min_depth = 99999;
1238 for (
int i = 0; i < camera_depth.size(); i++) {
1239 if (camera_depth.at(i) < 0 || camera_depth.at(i) > max_depth) {
1240 camera_depth.at(i) = max_depth;
1242 if (camera_depth.at(i) < min_depth) {
1243 min_depth = camera_depth.at(i);
1246 for (
int i = 0; i < camera_depth.size(); i++) {
1247 camera_depth.at(i) = 1.f - (camera_depth.at(i) - min_depth) / (max_depth - min_depth);
1250 std::vector<RGBcolor> pixel_data(camera_resolution.
x * camera_resolution.
y);
1253 for (
uint j = 0; j < camera_resolution.
y; j++) {
1254 for (
uint i = 0; i < camera_resolution.
x; i++) {
1256 float c = camera_depth.at(j * camera_resolution.
x + i);
1259 uint ii = camera_resolution.
x - i - 1;
1260 uint jj = camera_resolution.
y - j - 1;
1261 pixel_data.at(jj * camera_resolution.
x + ii) = pixel_color;
1265 writeJPEG(outfile.str(), camera_resolution.
x, camera_resolution.
y, pixel_data);
1269void 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) {
1271 if (cameras.find(cameralabel) == cameras.end()) {
1272 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1276 std::vector<uint> camera_UUIDs;
1277 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1279 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1281 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1282 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1283 int2 camera_resolution = cameras.at(cameralabel).resolution;
1285 std::string frame_str;
1287 frame_str = std::to_string(frame);
1290 std::string output_path = image_path;
1292 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1294 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1297 std::ostringstream outfile;
1298 outfile << output_path;
1301 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1303 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1307 std::ofstream label_file;
1308 if (append_label_file) {
1309 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1311 label_file.open(outfile.str());
1314 if (!label_file.is_open()) {
1315 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open file '" + outfile.str() +
"'.");
1318 std::map<int, vec4> pdata_bounds;
1320 for (
int j = 0; j < camera_resolution.
y; j++) {
1321 for (
int i = 0; i < camera_resolution.
x; i++) {
1322 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + i) - 1;
1330 context->
getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_ui);
1331 labeldata = labeldata_ui;
1334 context->
getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_i);
1335 labeldata = (
uint) labeldata_i;
1340 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1341 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1344 if (i < pdata_bounds[labeldata].x) {
1345 pdata_bounds[labeldata].x = i;
1347 if (i > pdata_bounds[labeldata].y) {
1348 pdata_bounds[labeldata].y = i;
1350 if (j < pdata_bounds[labeldata].z) {
1351 pdata_bounds[labeldata].z = j;
1353 if (j > pdata_bounds[labeldata].w) {
1354 pdata_bounds[labeldata].w = j;
1360 for (
auto box: pdata_bounds) {
1361 vec4 bbox = box.second;
1362 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1365 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
1366 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1375 if (cameras.find(cameralabel) == cameras.end()) {
1376 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1380 std::vector<uint> camera_UUIDs;
1381 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1383 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1385 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1386 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1387 int2 camera_resolution = cameras.at(cameralabel).resolution;
1389 std::string frame_str;
1391 frame_str = std::to_string(frame);
1394 std::string output_path = image_path;
1396 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1398 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1401 std::ostringstream outfile;
1402 outfile << output_path;
1405 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1407 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1411 std::ofstream label_file;
1412 if (append_label_file) {
1413 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1415 label_file.open(outfile.str());
1418 if (!label_file.is_open()) {
1419 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open file '" + outfile.str() +
"'.");
1422 std::map<int, vec4> pdata_bounds;
1424 for (
int j = 0; j < camera_resolution.
y; j++) {
1425 for (
int i = 0; i < camera_resolution.
x; i++) {
1426 uint ii = camera_resolution.
x - i - 1;
1427 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1444 context->
getObjectData(objID, object_data_label.c_str(), labeldata_ui);
1445 labeldata = labeldata_ui;
1448 context->
getObjectData(objID, object_data_label.c_str(), labeldata_i);
1449 labeldata = (
uint) labeldata_i;
1454 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1455 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1458 if (i < pdata_bounds[labeldata].x) {
1459 pdata_bounds[labeldata].x = i;
1461 if (i > pdata_bounds[labeldata].y) {
1462 pdata_bounds[labeldata].y = i;
1464 if (j < pdata_bounds[labeldata].z) {
1465 pdata_bounds[labeldata].z = j;
1467 if (j > pdata_bounds[labeldata].w) {
1468 pdata_bounds[labeldata].w = j;
1473 for (
auto box: pdata_bounds) {
1474 vec4 bbox = box.second;
1475 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1478 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
1479 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1485void 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) {
1486 writeImageBoundingBoxes(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1489void 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,
1490 const std::string &image_path) {
1492 if (cameras.find(cameralabel) == cameras.end()) {
1493 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1496 if (primitive_data_label.size() != object_class_ID.size()) {
1497 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
1501 std::vector<uint> camera_UUIDs;
1502 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1504 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1506 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1507 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1508 int2 camera_resolution = cameras.at(cameralabel).resolution;
1510 std::string output_path = image_path;
1512 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1514 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1517 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1519 std::ofstream label_file(outfile_txt);
1521 if (!label_file.is_open()) {
1522 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output bounding box file '" + outfile_txt +
"'.");
1526 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1529 for (
int j = 0; j < camera_resolution.
y; j++) {
1530 for (
int i = 0; i < camera_resolution.
x; i++) {
1531 uint ii = camera_resolution.
x - i - 1;
1532 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1536 for (
size_t label_idx = 0; label_idx < primitive_data_label.size(); label_idx++) {
1537 const std::string &data_label = primitive_data_label[label_idx];
1538 uint class_id = object_class_ID[label_idx];
1542 bool has_data =
false;
1548 labeldata = labeldata_ui;
1553 labeldata = (
uint) labeldata_i;
1558 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1560 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1561 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1564 if (i < pdata_bounds[key].x) {
1565 pdata_bounds[key].x = i;
1567 if (i > pdata_bounds[key].y) {
1568 pdata_bounds[key].y = i;
1570 if (j < pdata_bounds[key].z) {
1571 pdata_bounds[key].z = j;
1573 if (j > pdata_bounds[key].w) {
1574 pdata_bounds[key].w = j;
1583 for (
auto box: pdata_bounds) {
1584 uint class_id = box.first.first;
1585 vec4 bbox = box.second;
1586 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1589 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
1590 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1595 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1596 if (!classes_txt_stream.is_open()) {
1597 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output classes file '" + output_path + classes_txt_file +
".");
1599 for (
int i = 0; i < object_class_ID.size(); i++) {
1600 classes_txt_stream << object_class_ID.at(i) <<
" " << primitive_data_label.at(i) << std::endl;
1602 classes_txt_stream.close();
1606 const std::string &image_path) {
1607 writeImageBoundingBoxes_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1611 const std::string &image_path) {
1613 if (cameras.find(cameralabel) == cameras.end()) {
1614 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1617 if (object_data_label.size() != object_class_ID.size()) {
1618 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
1622 std::vector<uint> camera_UUIDs;
1623 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1625 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1627 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1628 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1629 int2 camera_resolution = cameras.at(cameralabel).resolution;
1631 std::string output_path = image_path;
1633 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1635 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1638 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1640 std::ofstream label_file(outfile_txt);
1642 if (!label_file.is_open()) {
1643 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output bounding box file '" + outfile_txt +
"'.");
1647 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1651 for (
int j = 0; j < camera_resolution.
y; j++) {
1652 for (
int i = 0; i < camera_resolution.
x; i++) {
1653 uint ii = camera_resolution.
x - i - 1;
1654 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1667 for (
size_t label_idx = 0; label_idx < object_data_label.size(); label_idx++) {
1668 const std::string &data_label = object_data_label[label_idx];
1669 uint class_id = object_class_ID[label_idx];
1673 bool has_data =
false;
1678 context->
getObjectData(objID, data_label.c_str(), labeldata_ui);
1679 labeldata = labeldata_ui;
1683 context->
getObjectData(objID, data_label.c_str(), labeldata_i);
1684 labeldata = (
uint) labeldata_i;
1689 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1691 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1692 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1695 if (i < pdata_bounds[key].x) {
1696 pdata_bounds[key].x = i;
1698 if (i > pdata_bounds[key].y) {
1699 pdata_bounds[key].y = i;
1701 if (j < pdata_bounds[key].z) {
1702 pdata_bounds[key].z = j;
1704 if (j > pdata_bounds[key].w) {
1705 pdata_bounds[key].w = j;
1713 for (
auto box: pdata_bounds) {
1714 uint class_id = box.first.first;
1715 vec4 bbox = box.second;
1716 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1719 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
1720 << (bbox.
y - bbox.
x) /
float(camera_resolution.
x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.
y) << std::endl;
1725 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1726 if (!classes_txt_stream.is_open()) {
1727 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output classes file '" + output_path + classes_txt_file +
".");
1729 for (
int i = 0; i < object_class_ID.size(); i++) {
1730 classes_txt_stream << object_class_ID.at(i) <<
" " << object_data_label.at(i) << std::endl;
1732 classes_txt_stream.close();
1736std::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) {
1737 nlohmann::json coco_json;
1741 std::ifstream existing_file(filename);
1742 if (existing_file.is_open()) {
1744 existing_file >> coco_json;
1745 }
catch (
const std::exception &e) {
1748 existing_file.close();
1753 if (coco_json.empty()) {
1754 coco_json[
"categories"] = nlohmann::json::array();
1755 coco_json[
"images"] = nlohmann::json::array();
1756 coco_json[
"annotations"] = nlohmann::json::array();
1760 std::filesystem::path image_path_obj(image_file);
1761 std::string filename_only = image_path_obj.filename().string();
1764 bool image_exists =
false;
1765 for (
const auto &img: coco_json[
"images"]) {
1766 if (img[
"file_name"] == filename_only) {
1767 image_id = img[
"id"];
1768 image_exists =
true;
1774 if (!image_exists) {
1776 int max_image_id = -1;
1777 for (
const auto &img: coco_json[
"images"]) {
1778 if (img[
"id"] > max_image_id) {
1779 max_image_id = img[
"id"];
1782 image_id = max_image_id + 1;
1785 nlohmann::json image_entry;
1786 image_entry[
"id"] = image_id;
1787 image_entry[
"file_name"] = filename_only;
1788 image_entry[
"height"] = camera_resolution.
y;
1789 image_entry[
"width"] = camera_resolution.
x;
1790 coco_json[
"images"].push_back(image_entry);
1793 return std::make_pair(coco_json, image_id);
1797nlohmann::json RadiationModel::initializeCOCOJson(
const std::string &filename,
bool append_file,
const std::string &cameralabel,
const helios::int2 &camera_resolution,
const std::string &image_file) {
1798 return initializeCOCOJsonWithImageId(filename, append_file, cameralabel, camera_resolution, image_file).first;
1802void RadiationModel::addCategoryToCOCO(nlohmann::json &coco_json,
const std::vector<uint> &object_class_ID,
const std::vector<std::string> &category_name) {
1803 if (object_class_ID.size() != category_name.size()) {
1804 helios_runtime_error(
"ERROR (RadiationModel::addCategoryToCOCO): The lengths of object_class_ID and category_name vectors must be the same.");
1807 for (
size_t i = 0; i < object_class_ID.size(); ++i) {
1808 bool category_exists =
false;
1809 for (
auto &cat: coco_json[
"categories"]) {
1810 if (cat[
"id"] == object_class_ID[i]) {
1811 category_exists =
true;
1815 if (!category_exists) {
1816 nlohmann::json category;
1817 category[
"id"] = object_class_ID[i];
1818 category[
"name"] = category_name[i];
1819 category[
"supercategory"] =
"none";
1820 coco_json[
"categories"].push_back(category);
1826void RadiationModel::writeCOCOJson(
const nlohmann::json &coco_json,
const std::string &filename) {
1827 std::ofstream json_file(filename);
1828 if (!json_file.is_open()) {
1833 json_file << coco_json.dump(2) << std::endl;
1838std::map<int, std::vector<std::vector<bool>>> RadiationModel::generateLabelMasks(
const std::string &cameralabel,
const std::string &data_label,
bool use_object_data) {
1839 std::vector<uint> camera_UUIDs;
1840 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1841 context->
getGlobalData(global_data_label.c_str(), camera_UUIDs);
1842 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1843 int2 camera_resolution = cameras.at(cameralabel).resolution;
1845 std::map<int, std::vector<std::vector<bool>>> label_masks;
1849 for (
int j = 0; j < camera_resolution.
y; j++) {
1850 for (
int i = 0; i < camera_resolution.
x; i++) {
1851 uint ii = camera_resolution.
x - i - 1;
1852 uint UUID = pixel_UUIDs.at(j * camera_resolution.
x + ii) - 1;
1856 bool has_data =
false;
1858 if (use_object_data) {
1863 if (datatype == HELIOS_TYPE_UINT) {
1865 context->
getObjectData(objID, data_label.c_str(), labeldata_ui);
1866 labeldata = labeldata_ui;
1868 }
else if (datatype == HELIOS_TYPE_INT) {
1870 context->
getObjectData(objID, data_label.c_str(), labeldata_i);
1871 labeldata = (
uint) labeldata_i;
1879 if (datatype == HELIOS_TYPE_UINT) {
1882 labeldata = labeldata_ui;
1884 }
else if (datatype == HELIOS_TYPE_INT) {
1887 labeldata = (
uint) labeldata_i;
1895 if (label_masks.find(labeldata) == label_masks.end()) {
1896 label_masks[labeldata] = std::vector<std::vector<bool>>(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
1898 label_masks[labeldata][j][i] =
true;
1908std::pair<int, int> RadiationModel::findStartingBoundaryPixel(
const std::vector<std::vector<bool>> &mask,
const helios::int2 &camera_resolution) {
1909 for (
int j = 0; j < camera_resolution.
y; j++) {
1910 for (
int i = 0; i < camera_resolution.
x; i++) {
1913 for (
int di = -1; di <= 1; di++) {
1914 for (
int dj = -1; dj <= 1; dj++) {
1915 if (di == 0 && dj == 0)
1919 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
1931std::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) {
1932 std::vector<std::pair<int, int>> contour;
1935 int dx[] = {1, 1, 0, -1, -1, -1, 0, 1};
1936 int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
1938 int x = start_x, y = start_y;
1942 contour.push_back({x, y});
1945 int start_dir = (dir + 6) % 8;
1948 for (
int i = 0; i < 8; i++) {
1949 int check_dir = (start_dir + i) % 8;
1950 int nx = x + dx[check_dir];
1951 int ny = y + dy[check_dir];
1954 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.
y && mask[ny][nx]) {
1966 }
while (!(x == start_x && y == start_y) && contour.size() < camera_resolution.
x * camera_resolution.
y);
1972std::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) {
1973 std::vector<std::pair<int, int>> contour;
1974 std::set<std::pair<int, int>> visited_boundary;
1977 std::queue<std::pair<int, int>> boundary_queue;
1978 boundary_queue.push({start_x, start_y});
1979 visited_boundary.insert({start_x, start_y});
1981 while (!boundary_queue.empty()) {
1982 auto [x, y] = boundary_queue.front();
1983 boundary_queue.pop();
1984 contour.push_back({x, y});
1987 for (
int di = -1; di <= 1; di++) {
1988 for (
int dj = -1; dj <= 1; dj++) {
1989 if (di == 0 && dj == 0)
1994 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.
y && mask[ny][nx] && visited_boundary.find({nx, ny}) == visited_boundary.end()) {
1997 bool is_boundary =
false;
1998 for (
int ddi = -1; ddi <= 1; ddi++) {
1999 for (
int ddj = -1; ddj <= 1; ddj++) {
2000 if (ddi == 0 && ddj == 0)
2004 if (nnx < 0 || nnx >= camera_resolution.
x || nny < 0 || nny >= camera_resolution.
y || !mask[nny][nnx]) {
2014 boundary_queue.push({nx, ny});
2015 visited_boundary.insert({nx, ny});
2026std::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) {
2027 std::vector<std::map<std::string, std::vector<float>>> annotations;
2028 int annotation_id = 0;
2030 for (
const auto &label_pair: label_masks) {
2031 int label_value = label_pair.first;
2032 const auto &mask = label_pair.second;
2035 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2038 for (
int j = 0; j < camera_resolution.
y; j++) {
2039 for (
int i = 0; i < camera_resolution.
x; i++) {
2040 if (mask[j][i] && !visited[j][i]) {
2042 int boundary_i = i, boundary_j = j;
2043 bool is_boundary =
false;
2046 for (
int di = -1; di <= 1; di++) {
2047 for (
int dj = -1; dj <= 1; dj++) {
2050 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
2063 std::stack<std::pair<int, int>> stack;
2064 std::vector<std::pair<int, int>> component_pixels;
2066 visited[j][i] =
true;
2068 int min_x = i, max_x = i, min_y = j, max_y = j;
2071 while (!stack.empty()) {
2072 auto [ci, cj] = stack.top();
2075 component_pixels.push_back({ci, cj});
2077 min_x = std::min(min_x, ci);
2078 max_x = std::max(max_x, ci);
2079 min_y = std::min(min_y, cj);
2080 max_y = std::max(max_y, cj);
2083 for (
int di = -1; di <= 1; di++) {
2084 for (
int dj = -1; dj <= 1; dj++) {
2085 if (abs(di) + abs(dj) != 1)
2089 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2090 stack.push({ni, nj});
2091 visited[nj][ni] =
true;
2098 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2099 bool is_boundary_start =
false;
2101 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2102 is_boundary_start =
true;
2105 if (is_boundary_start) {
2107 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2110 if (contour.size() < 10) {
2111 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2114 if (contour.size() >= 3) {
2116 std::map<std::string, std::vector<float>> annotation;
2117 annotation[
"id"] = {(float) annotation_id++};
2118 annotation[
"image_id"] = {(float) image_id};
2119 annotation[
"category_id"] = {(float) object_class_ID};
2120 annotation[
"bbox"] = {(float) min_x, (
float) min_y, (float) (max_x - min_x), (float) (max_y - min_y)};
2121 annotation[
"area"] = {(float) area};
2122 annotation[
"iscrowd"] = {0.0f};
2125 std::vector<float> segmentation;
2126 for (
const auto &point: contour) {
2127 segmentation.push_back((
float) point.first);
2128 segmentation.push_back((
float) point.second);
2130 annotation[
"segmentation"] = segmentation;
2132 annotations.push_back(annotation);
2145 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2146 writeImageSegmentationMasks(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, data_attribute_labels, append_file);
2149void 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,
2150 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2152 if (cameras.find(cameralabel) == cameras.end()) {
2153 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Camera '" + cameralabel +
"' does not exist.");
2156 if (primitive_data_label.size() != object_class_ID.size()) {
2157 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
2161 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2163 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2168 for (
const auto &data_label: primitive_data_label) {
2169 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), data_label) == all_primitive_data.end()) {
2170 std::cerr <<
"WARNING (RadiationModel::writeImageSegmentationMasks): Primitive data label '" << data_label <<
"' does not exist in the context." << std::endl;
2175 if (!std::filesystem::exists(image_file)) {
2176 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Image file '" + image_file +
"' does not exist.");
2180 std::string validated_json_filename = json_filename;
2181 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2182 validated_json_filename +=
".json";
2186 std::string outfile = validated_json_filename;
2189 int2 camera_resolution = cameras.at(cameralabel).resolution;
2190 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2191 nlohmann::json coco_json = coco_json_pair.first;
2192 int image_id = coco_json_pair.second;
2193 addCategoryToCOCO(coco_json, object_class_ID, primitive_data_label);
2196 struct AttributeInfo {
2198 bool is_primitive_data;
2201 std::vector<AttributeInfo> attribute_info;
2203 if (!data_attribute_labels.empty()) {
2207 for (
const auto &attr_label: data_attribute_labels) {
2209 info.label = attr_label;
2210 info.exists =
false;
2212 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2213 info.is_primitive_data =
true;
2215 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2216 info.is_primitive_data =
false;
2221 attribute_info.push_back(info);
2226 bool use_attributes = !attribute_info.empty();
2229 std::vector<uint> pixel_UUIDs;
2230 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2231 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2234 for (
size_t i = 0; i < primitive_data_label.size(); ++i) {
2236 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, primitive_data_label[i],
false);
2239 std::vector<std::map<std::string, std::vector<float>>> annotations = generateAnnotationsFromMasks(label_masks, object_class_ID[i], camera_resolution, image_id);
2242 std::vector<std::map<std::string, double>> mean_attribute_values_per_component;
2243 if (use_attributes) {
2245 for (
const auto &label_pair: label_masks) {
2246 const auto &mask = label_pair.second;
2247 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2249 for (
int j = 0; j < camera_resolution.
y; j++) {
2250 for (
int i_px = 0; i_px < camera_resolution.
x; i_px++) {
2251 if (mask[j][i_px] && !visited[j][i_px]) {
2253 std::stack<std::pair<int, int>> stack;
2254 std::vector<std::pair<int, int>> component_pixels;
2255 stack.push({i_px, j});
2256 visited[j][i_px] =
true;
2258 while (!stack.empty()) {
2259 auto [ci, cj] = stack.top();
2261 component_pixels.push_back({ci, cj});
2264 for (
int di = -1; di <= 1; di++) {
2265 for (
int dj = -1; dj <= 1; dj++) {
2266 if (abs(di) + abs(dj) != 1)
2270 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2271 stack.push({ni, nj});
2272 visited[nj][ni] =
true;
2279 std::map<std::string, double> component_attributes;
2280 for (
const auto &attr: attribute_info) {
2284 for (
const auto &[px_i, px_j]: component_pixels) {
2285 uint ii = camera_resolution.
x - px_i - 1;
2286 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.
x + ii) - 1;
2290 bool has_value =
false;
2292 if (attr.is_primitive_data) {
2298 value =
static_cast<double>(val);
2303 value =
static_cast<double>(val);
2308 value =
static_cast<double>(val);
2322 value =
static_cast<double>(val);
2327 value =
static_cast<double>(val);
2332 value =
static_cast<double>(val);
2349 component_attributes[attr.label] =
sum / count;
2351 component_attributes[attr.label] = 0.0;
2355 mean_attribute_values_per_component.push_back(component_attributes);
2363 int max_annotation_id = -1;
2364 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2365 if (existing_ann[
"id"] > max_annotation_id) {
2366 max_annotation_id = existing_ann[
"id"];
2372 for (
const auto &ann: annotations) {
2373 nlohmann::json json_annotation;
2374 json_annotation[
"id"] = max_annotation_id + 1;
2375 json_annotation[
"image_id"] = (int) ann.at(
"image_id")[0];
2376 json_annotation[
"category_id"] = (int) ann.at(
"category_id")[0];
2378 const auto &bbox = ann.at(
"bbox");
2379 json_annotation[
"bbox"] = {(int) bbox[0], (
int) bbox[1], (int) bbox[2], (
int) bbox[3]};
2380 json_annotation[
"area"] = (int) ann.at(
"area")[0];
2382 const auto &seg = ann.at(
"segmentation");
2383 std::vector<int> segmentation_coords;
2384 for (
float coord: seg) {
2385 segmentation_coords.push_back((
int) coord);
2387 json_annotation[
"segmentation"] = {segmentation_coords};
2388 json_annotation[
"iscrowd"] = (int) ann.at(
"iscrowd")[0];
2391 if (use_attributes && ann_idx < mean_attribute_values_per_component.size()) {
2392 json_annotation[
"attributes"] = mean_attribute_values_per_component[ann_idx];
2395 coco_json[
"annotations"].push_back(json_annotation);
2396 max_annotation_id++;
2402 writeCOCOJson(coco_json, outfile);
2406 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2407 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);
2411 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2413 if (cameras.find(cameralabel) == cameras.end()) {
2414 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Camera '" + cameralabel +
"' does not exist.");
2417 if (object_data_label.size() != object_class_ID.size()) {
2418 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
2422 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2424 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2429 for (
const auto &data_label: object_data_label) {
2430 if (std::find(all_object_data.begin(), all_object_data.end(), data_label) == all_object_data.end()) {
2431 std::cerr <<
"WARNING (RadiationModel::writeImageSegmentationMasks_ObjectData): Object data label '" << data_label <<
"' does not exist in the context." << std::endl;
2436 if (!std::filesystem::exists(image_file)) {
2437 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Image file '" + image_file +
"' does not exist.");
2441 std::string validated_json_filename = json_filename;
2442 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2443 validated_json_filename +=
".json";
2447 std::string outfile = validated_json_filename;
2450 int2 camera_resolution = cameras.at(cameralabel).resolution;
2451 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2452 nlohmann::json coco_json = coco_json_pair.first;
2453 int image_id = coco_json_pair.second;
2454 addCategoryToCOCO(coco_json, object_class_ID, object_data_label);
2457 struct AttributeInfo {
2459 bool is_primitive_data;
2462 std::vector<AttributeInfo> attribute_info;
2464 if (!data_attribute_labels.empty()) {
2468 for (
const auto &attr_label: data_attribute_labels) {
2470 info.label = attr_label;
2471 info.exists =
false;
2473 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2474 info.is_primitive_data =
true;
2476 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2477 info.is_primitive_data =
false;
2482 attribute_info.push_back(info);
2487 bool use_attributes = !attribute_info.empty();
2490 std::vector<uint> pixel_UUIDs;
2491 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2492 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2495 for (
size_t i = 0; i < object_data_label.size(); ++i) {
2497 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, object_data_label[i],
true);
2500 int max_annotation_id = -1;
2501 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2502 if (existing_ann[
"id"] > max_annotation_id) {
2503 max_annotation_id = existing_ann[
"id"];
2509 for (
const auto &label_pair: label_masks) {
2510 const auto &mask = label_pair.second;
2513 std::vector<std::vector<bool>> visited(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
2516 for (
int j = 0; j < camera_resolution.
y; j++) {
2517 for (
int i_px = 0; i_px < camera_resolution.
x; i_px++) {
2518 if (mask[j][i_px] && !visited[j][i_px]) {
2520 int boundary_i = i_px, boundary_j = j;
2521 bool is_boundary =
false;
2524 for (
int di = -1; di <= 1; di++) {
2525 for (
int dj = -1; dj <= 1; dj++) {
2528 if (ni < 0 || ni >= camera_resolution.
x || nj < 0 || nj >= camera_resolution.
y || !mask[nj][ni]) {
2541 std::stack<std::pair<int, int>> stack;
2542 std::vector<std::pair<int, int>> component_pixels;
2543 stack.push({i_px, j});
2544 visited[j][i_px] =
true;
2546 int min_x = i_px, max_x = i_px, min_y = j, max_y = j;
2549 while (!stack.empty()) {
2550 auto [ci, cj] = stack.top();
2553 component_pixels.push_back({ci, cj});
2555 min_x = std::min(min_x, ci);
2556 max_x = std::max(max_x, ci);
2557 min_y = std::min(min_y, cj);
2558 max_y = std::max(max_y, cj);
2561 for (
int di = -1; di <= 1; di++) {
2562 for (
int dj = -1; dj <= 1; dj++) {
2563 if (abs(di) + abs(dj) != 1)
2567 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2568 stack.push({ni, nj});
2569 visited[nj][ni] =
true;
2576 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2577 bool is_boundary_start =
false;
2579 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2580 is_boundary_start =
true;
2583 if (is_boundary_start) {
2585 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2588 if (contour.size() < 10) {
2589 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2592 if (contour.size() >= 3) {
2594 std::map<std::string, double> component_attributes;
2595 if (use_attributes) {
2596 for (
const auto &attr: attribute_info) {
2600 for (
const auto &[px_i, px_j]: component_pixels) {
2601 uint ii = camera_resolution.
x - px_i - 1;
2602 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.
x + ii) - 1;
2606 bool has_value =
false;
2608 if (attr.is_primitive_data) {
2614 value =
static_cast<double>(val);
2619 value =
static_cast<double>(val);
2624 value =
static_cast<double>(val);
2638 value =
static_cast<double>(val);
2643 value =
static_cast<double>(val);
2648 value =
static_cast<double>(val);
2665 component_attributes[attr.label] =
sum / count;
2667 component_attributes[attr.label] = 0.0;
2673 nlohmann::json json_annotation;
2674 json_annotation[
"id"] = max_annotation_id + 1;
2675 json_annotation[
"image_id"] = image_id;
2676 json_annotation[
"category_id"] = (int) object_class_ID[i];
2677 json_annotation[
"bbox"] = {min_x, min_y, max_x - min_x, max_y - min_y};
2678 json_annotation[
"area"] = area;
2679 json_annotation[
"iscrowd"] = 0;
2682 std::vector<int> segmentation_coords;
2683 for (
const auto &point: contour) {
2684 segmentation_coords.push_back(point.first);
2685 segmentation_coords.push_back(point.second);
2687 json_annotation[
"segmentation"] = {segmentation_coords};
2690 if (use_attributes) {
2691 json_annotation[
"attributes"] = component_attributes;
2694 coco_json[
"annotations"].push_back(json_annotation);
2695 max_annotation_id++;
2700 std::stack<std::pair<int, int>> stack;
2701 stack.push({i_px, j});
2702 visited[j][i_px] =
true;
2704 while (!stack.empty()) {
2705 auto [ci, cj] = stack.top();
2709 for (
int di = -1; di <= 1; di++) {
2710 for (
int dj = -1; dj <= 1; dj++) {
2711 if (abs(di) + abs(dj) != 1)
2715 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.
y && mask[nj][ni] && !visited[nj][ni]) {
2716 stack.push({ni, nj});
2717 visited[nj][ni] =
true;
2730 writeCOCOJson(coco_json, outfile);
2734 for (
uint b = 0; b < bandlabels.size(); b++) {
2735 std::string bandlabel = bandlabels.at(b);
2737 std::string image_value_label =
"camera_" + cameralabel +
"_" + bandlabel;
2738 std::vector<float> cameradata;
2739 context->
getGlobalData(image_value_label.c_str(), cameradata);
2741 std::vector<uint> camera_UUIDs;
2742 std::string image_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2743 context->
getGlobalData(image_UUID_label.c_str(), camera_UUIDs);
2745 for (
uint i = 0; i < cameradata.size(); i++) {
2746 uint UUID = camera_UUIDs.at(i) - 1;
2748 cameradata.at(i) = padvalues.at(b);
2751 context->
setGlobalData(image_value_label.c_str(), cameradata);
2755void 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,
2756 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2758 if (cameras.find(originalcameralabel) == cameras.end()) {
2759 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2760 }
else if (radiation_sources.empty()) {
2761 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2765 if (!calibration_flag) {
2766 std::cout <<
"No color board added, use default color calibration." << std::endl;
2767 cameracalibration = &cameracalibration_;
2775 std::vector<std::string> cameraresplabels_cal(cameraresplabels_raw.size());
2777 for (
int iband = 0; iband < bandlabels.size(); iband++) {
2778 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + cameraresplabels_raw.at(iband);
2787 std::cout <<
"Camera response scale: " << camerascale << std::endl;
2792void RadiationModel::calibrateCamera(
const std::string &originalcameralabel,
const float scalefactor,
const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2794 if (cameras.find(originalcameralabel) == cameras.end()) {
2795 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2796 }
else if (radiation_sources.empty()) {
2797 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2801 if (!calibration_flag) {
2802 std::cout <<
"No color board added, use default color calibration." << std::endl;
2806 RadiationModel::setCameraCalibration(&cameracalibration_);
2811 std::vector<std::string> bandlabels = cameras.at(originalcameralabel).band_labels;
2814 std::vector<std::string> cameraresplabels_cal(cameras.at(originalcameralabel).band_spectral_response.size());
2815 std::vector<std::string> cameraresplabels_raw = cameraresplabels_cal;
2818 for (
auto &band: cameras.at(originalcameralabel).band_spectral_response) {
2819 cameraresplabels_raw.at(iband) = band.second;
2820 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + band.second;
2825 std::vector<std::string> sourcelabels(radiation_sources.size());
2827 for (
auto &source: radiation_sources) {
2828 if (source.source_spectrum.empty()) {
2829 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): A spectral distribution was not specified for source " + source.source_spectrum_label +
". Cannot perform camera calibration.");
2831 sourcelabels.at(isource) = source.source_spectrum_label;
2842 std::cout <<
"Camera response scale: " << camerascale << std::endl;
2847std::vector<helios::vec2> RadiationModel::generateGaussianCameraResponse(
float FWHM,
float mu,
float centrawavelength,
const helios::int2 &wavebandrange) {
2850 float sigma = FWHM / (2 * std::sqrt(2 * std::log(2)));
2852 size_t lenspectra = wavebandrange.
y - wavebandrange.
x;
2853 std::vector<helios::vec2> cameraresponse(lenspectra);
2856 for (
int i = 0; i < lenspectra; ++i) {
2857 cameraresponse.at(i).x = float(wavebandrange.
x + i);
2861 for (
size_t i = 0; i < lenspectra; ++i) {
2862 cameraresponse.at(i).y = centrawavelength * std::exp(-std::pow((cameraresponse.at(i).x - mu), 2) / (2 * std::pow(sigma, 2)));
2866 return cameraresponse;
2869void 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,
2870 float contrast_adjustment) {
2872 if (cameras.find(cameralabel) == cameras.end()) {
2873 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): Camera '" + cameralabel +
"' does not exist.");
2876 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()) {
2877 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): One or more specified band labels do not exist for the camera pixel data.");
2881 if (camera_metadata.find(cameralabel) == camera_metadata.end()) {
2884 camera_metadata[cameralabel].image_processing.saturation_adjustment = saturation_adjustment;
2885 camera_metadata[cameralabel].image_processing.brightness_adjustment = brightness_adjustment;
2886 camera_metadata[cameralabel].image_processing.contrast_adjustment = contrast_adjustment;
2895 lens_flare.
apply(camera.pixel_data, camera.resolution);
2899 if (brightness_adjustment != 1.f || contrast_adjustment != 1.f) {
2900 camera.
adjustBrightnessContrast(red_band_label, green_band_label, blue_band_label, brightness_adjustment, contrast_adjustment);
2904 if (saturation_adjustment != 1.f) {
2905 camera.
adjustSaturation(red_band_label, green_band_label, blue_band_label, saturation_adjustment);
2909void 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,
2910 float contrast_adjustment,
float gain_adjustment) {
2911 applyCameraImageCorrections(cameralabel, red_band_label, green_band_label, blue_band_label, saturation_adjustment, brightness_adjustment, contrast_adjustment);
2916 float min_P = (std::numeric_limits<float>::max)();
2918 for (
const auto &[channel_label, data]: pixel_data) {
2919 for (
float v: data) {
2929 for (
auto &[channel_label, data]: pixel_data) {
2930 for (
float &v: data) {
2931 v = (v - min_P) / (max_P - min_P);
2939 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()) {
2940 helios_runtime_error(
"ERROR (RadiationModel::whiteBalance): One or more specified band labels do not exist for the camera pixel data.");
2944 auto &data_red = pixel_data.at(red_band_label);
2945 auto &data_green = pixel_data.at(green_band_label);
2946 auto &data_blue = pixel_data.at(blue_band_label);
2948 const std::size_t N = data_red.size();
2949 if (data_green.size() != N || data_blue.size() != N) {
2950 throw std::invalid_argument(
"All channels must have the same length");
2953 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
2960 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
2961 for (std::size_t i = 0; i < N; ++i) {
2962 acc_r += std::pow(data_red[i], p);
2963 acc_g += std::pow(data_green[i], p);
2964 acc_b += std::pow(data_blue[i], p);
2966 float mean_r_p = acc_r /
static_cast<float>(N);
2967 float mean_g_p = acc_g /
static_cast<float>(N);
2968 float mean_b_p = acc_b /
static_cast<float>(N);
2970 float M_R = std::pow(mean_r_p, 1.0f / p);
2971 float M_G = std::pow(mean_g_p, 1.0f / p);
2972 float M_B = std::pow(mean_b_p, 1.0f / p);
2975 const float eps = 1e-6f;
2976 if (M_R < eps || M_G < eps || M_B < eps) {
2977 throw std::runtime_error(
"Channel Minkowski mean too small");
2982 float M = (M_R + M_G + M_B) / 3.0f;
2993 for (std::size_t i = 0; i < N; ++i) {
2994 data_red[i] *= scale.
x;
2995 data_green[i] *= scale.
y;
2996 data_blue[i] *= scale.
z;
3003 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()) {
3004 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceGrayEdge): One or more specified band labels do not exist for the camera pixel data.");
3008 auto &data_red = pixel_data.at(red_band_label);
3009 auto &data_green = pixel_data.at(green_band_label);
3010 auto &data_blue = pixel_data.at(blue_band_label);
3012 const int width = resolution.
x;
3013 const int height = resolution.
y;
3014 const std::size_t N = width * height;
3017 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
3019 if (derivative_order < 1 || derivative_order > 2) {
3020 throw std::invalid_argument(
"Derivative order must be 1 or 2");
3024 std::vector<float> deriv_red(N, 0.0f);
3025 std::vector<float> deriv_green(N, 0.0f);
3026 std::vector<float> deriv_blue(N, 0.0f);
3028 if (derivative_order == 1) {
3030 for (
int y = 1; y < height - 1; ++y) {
3031 for (
int x = 1; x < width - 1; ++x) {
3032 int idx = y * width + x;
3035 float dx_r = (data_red[(y - 1) * width + (x + 1)] + 2 * data_red[y * width + (x + 1)] + data_red[(y + 1) * width + (x + 1)]) -
3036 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[y * width + (x - 1)] + data_red[(y + 1) * width + (x - 1)]) / 8.0f;
3037 float dy_r = (data_red[(y + 1) * width + (x - 1)] + 2 * data_red[(y + 1) * width + x] + data_red[(y + 1) * width + (x + 1)]) -
3038 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[(y - 1) * width + x] + data_red[(y - 1) * width + (x + 1)]) / 8.0f;
3039 deriv_red[idx] = std::sqrt(dx_r * dx_r + dy_r * dy_r);
3041 float dx_g = (data_green[(y - 1) * width + (x + 1)] + 2 * data_green[y * width + (x + 1)] + data_green[(y + 1) * width + (x + 1)]) -
3042 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[y * width + (x - 1)] + data_green[(y + 1) * width + (x - 1)]) / 8.0f;
3043 float dy_g = (data_green[(y + 1) * width + (x - 1)] + 2 * data_green[(y + 1) * width + x] + data_green[(y + 1) * width + (x + 1)]) -
3044 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[(y - 1) * width + x] + data_green[(y - 1) * width + (x + 1)]) / 8.0f;
3045 deriv_green[idx] = std::sqrt(dx_g * dx_g + dy_g * dy_g);
3047 float dx_b = (data_blue[(y - 1) * width + (x + 1)] + 2 * data_blue[y * width + (x + 1)] + data_blue[(y + 1) * width + (x + 1)]) -
3048 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[y * width + (x - 1)] + data_blue[(y + 1) * width + (x - 1)]) / 8.0f;
3049 float dy_b = (data_blue[(y + 1) * width + (x - 1)] + 2 * data_blue[(y + 1) * width + x] + data_blue[(y + 1) * width + (x + 1)]) -
3050 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[(y - 1) * width + x] + data_blue[(y - 1) * width + (x + 1)]) / 8.0f;
3051 deriv_blue[idx] = std::sqrt(dx_b * dx_b + dy_b * dy_b);
3056 for (
int y = 1; y < height - 1; ++y) {
3057 for (
int x = 1; x < width - 1; ++x) {
3058 int idx = y * width + x;
3060 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]);
3062 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]);
3064 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]);
3070 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
3071 int valid_pixels = 0;
3073 for (std::size_t i = 0; i < N; ++i) {
3074 if (deriv_red[i] > 0 || deriv_green[i] > 0 || deriv_blue[i] > 0) {
3075 acc_r += std::pow(deriv_red[i], p);
3076 acc_g += std::pow(deriv_green[i], p);
3077 acc_b += std::pow(deriv_blue[i], p);
3082 if (valid_pixels == 0) {
3084 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3088 float mean_r_p = acc_r /
static_cast<float>(valid_pixels);
3089 float mean_g_p = acc_g /
static_cast<float>(valid_pixels);
3090 float mean_b_p = acc_b /
static_cast<float>(valid_pixels);
3092 float M_R = std::pow(mean_r_p, 1.0f / p);
3093 float M_G = std::pow(mean_g_p, 1.0f / p);
3094 float M_B = std::pow(mean_b_p, 1.0f / p);
3097 const float eps = 1e-6f;
3098 if (M_R < eps || M_G < eps || M_B < eps) {
3100 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3105 float M = (M_R + M_G + M_B) / 3.0f;
3114 for (std::size_t i = 0; i < N; ++i) {
3115 data_red[i] *= scale.
x;
3116 data_green[i] *= scale.
y;
3117 data_blue[i] *= scale.
z;
3124 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()) {
3125 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceWhitePatch): One or more specified band labels do not exist for the camera pixel data.");
3129 if (percentile <= 0.0f || percentile > 1.0f) {
3130 throw std::invalid_argument(
"Percentile must be in range (0, 1]");
3133 auto &data_red = pixel_data.at(red_band_label);
3134 auto &data_green = pixel_data.at(green_band_label);
3135 auto &data_blue = pixel_data.at(blue_band_label);
3137 const std::size_t N = data_red.size();
3140 std::vector<float> sorted_red = data_red;
3141 std::vector<float> sorted_green = data_green;
3142 std::vector<float> sorted_blue = data_blue;
3144 std::size_t k =
static_cast<std::size_t
>(percentile * (N - 1));
3146 std::nth_element(sorted_red.begin(), sorted_red.begin() + k, sorted_red.end());
3147 std::nth_element(sorted_green.begin(), sorted_green.begin() + k, sorted_green.end());
3148 std::nth_element(sorted_blue.begin(), sorted_blue.begin() + k, sorted_blue.end());
3150 float white_r = sorted_red[k];
3151 float white_g = sorted_green[k];
3152 float white_b = sorted_blue[k];
3155 const float eps = 1e-6f;
3156 if (white_r < eps || white_g < eps || white_b < eps) {
3157 throw std::runtime_error(
"White patch values too small");
3161 for (std::size_t i = 0; i < N; ++i) {
3162 data_red[i] /= white_r;
3163 data_green[i] /= white_g;
3164 data_blue[i] /= white_b;
3172 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()) {
3173 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): One or more specified band labels do not exist for the camera pixel data.");
3178 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()) {
3179 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Spectral response data not found for one or more bands. Ensure camera spectral responses are properly initialized.");
3183 std::string red_response_id = band_spectral_response.at(red_band_label);
3184 std::string green_response_id = band_spectral_response.at(green_band_label);
3185 std::string blue_response_id = band_spectral_response.at(blue_band_label);
3188 if (red_response_id ==
"uniform" && green_response_id ==
"uniform" && blue_response_id ==
"uniform") {
3193 std::vector<helios::vec2> red_spectrum, green_spectrum, blue_spectrum;
3195 if (red_response_id !=
"uniform" && context->
doesGlobalDataExist(red_response_id.c_str())) {
3196 context->
getGlobalData(red_response_id.c_str(), red_spectrum);
3198 if (green_response_id !=
"uniform" && context->
doesGlobalDataExist(green_response_id.c_str())) {
3199 context->
getGlobalData(green_response_id.c_str(), green_spectrum);
3201 if (blue_response_id !=
"uniform" && context->
doesGlobalDataExist(blue_response_id.c_str())) {
3202 context->
getGlobalData(blue_response_id.c_str(), blue_spectrum);
3206 if (red_spectrum.empty() || green_spectrum.empty() || blue_spectrum.empty()) {
3207 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Could not retrieve spectral response curves for all bands from global data.");
3212 float red_integrated = 0.0f, green_integrated = 0.0f, blue_integrated = 0.0f;
3214 for (
size_t i = 1; i < red_spectrum.size(); ++i) {
3215 float dw = red_spectrum[i].x - red_spectrum[i - 1].x;
3216 red_integrated += 0.5f * (red_spectrum[i].y + red_spectrum[i - 1].y) * dw;
3218 for (
size_t i = 1; i < green_spectrum.size(); ++i) {
3219 float dw = green_spectrum[i].x - green_spectrum[i - 1].x;
3220 green_integrated += 0.5f * (green_spectrum[i].y + green_spectrum[i - 1].y) * dw;
3222 for (
size_t i = 1; i < blue_spectrum.size(); ++i) {
3223 float dw = blue_spectrum[i].x - blue_spectrum[i - 1].x;
3224 blue_integrated += 0.5f * (blue_spectrum[i].y + blue_spectrum[i - 1].y) * dw;
3228 if (red_integrated <= 0 || green_integrated <= 0 || blue_integrated <= 0) {
3229 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Invalid integrated spectral response (non-positive value). Check spectral response data.");
3236 float max_integrated = std::max({red_integrated, green_integrated, blue_integrated});
3239 white_balance_factors.
x = max_integrated / red_integrated;
3240 white_balance_factors.y = max_integrated / green_integrated;
3241 white_balance_factors.z = max_integrated / blue_integrated;
3244 auto &data_red = pixel_data.at(red_band_label);
3245 auto &data_green = pixel_data.at(green_band_label);
3246 auto &data_blue = pixel_data.at(blue_band_label);
3248 const std::size_t N = data_red.size();
3249 for (std::size_t i = 0; i < N; ++i) {
3250 data_red[i] *= white_balance_factors.x;
3251 data_green[i] *= white_balance_factors.y;
3252 data_blue[i] *= white_balance_factors.z;
3259 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()) {
3260 helios_runtime_error(
"ERROR (RadiationModel::reinhardToneMapping): One or more specified band labels do not exist for the camera pixel data.");
3264 const std::size_t N = resolution.
x * resolution.
y;
3265 constexpr float eps = 1e-6f;
3267 auto &data_red = pixel_data.at(red_band_label);
3268 auto &data_green = pixel_data.at(green_band_label);
3269 auto &data_blue = pixel_data.at(blue_band_label);
3270 for (std::size_t i = 0; i < N; ++i) {
3271 float R = data_red[i], G = data_green[i], B = data_blue[i];
3272 float L = luminance(
R, G, B);
3273 float s = (L > eps) ? (L / (1.0f + L)) / L : 0.0f;
3275 data_red[i] =
R * s;
3276 data_green[i] = G * s;
3277 data_blue[i] = B * s;
3281void RadiationCamera::applyGain(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float percentile) {
3284 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()) {
3285 helios_runtime_error(
"ERROR (RadiationModel::applyGain): One or more specified band labels do not exist for the camera pixel data.");
3289 const std::size_t N = resolution.
x * resolution.
y;
3291 auto &data_red = pixel_data.at(red_band_label);
3292 auto &data_green = pixel_data.at(green_band_label);
3293 auto &data_blue = pixel_data.at(blue_band_label);
3295 std::vector<float> luminance_pixel;
3296 luminance_pixel.reserve(N);
3297 for (std::size_t i = 0; i < N; ++i) {
3298 luminance_pixel.push_back(luminance(data_red[i], data_green[i], data_blue[i]));
3301 std::size_t k = std::size_t(percentile * (luminance_pixel.size() - 1));
3302 std::nth_element(luminance_pixel.begin(), luminance_pixel.begin() + k, luminance_pixel.end());
3303 float peak = luminance_pixel[k];
3304 float gain = (peak > 0.0f) ? 1.0f / peak : 1.0f;
3306 for (
auto &[channel, data]: pixel_data) {
3307 for (
float &v: data) {
3316 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()) {
3317 helios_runtime_error(
"ERROR (RadiationModel::globalHistogramEquilization): One or more specified band labels do not exist for the camera pixel data.");
3321 const size_t N = resolution.
x * resolution.
y;
3322 const float eps = 1e-6f;
3324 auto &data_red = pixel_data.at(red_band_label);
3325 auto &data_green = pixel_data.at(green_band_label);
3326 auto &data_blue = pixel_data.at(blue_band_label);
3329 std::vector<float> lum(N);
3330 std::vector<float> chroma_r(N), chroma_g(N), chroma_b(N);
3332 for (
size_t i = 0; i < N; ++i) {
3333 vec3 p(data_red[i], data_green[i], data_blue[i]);
3334 lum[i] = 0.2126f * p.
x + 0.7152f * p.
y + 0.0722f * p.
z;
3338 chroma_r[i] = p.
x / lum[i];
3339 chroma_g[i] = p.
y / lum[i];
3340 chroma_b[i] = p.
z / lum[i];
3350 std::vector<int> hist(B, 0);
3351 for (
float v: lum) {
3352 int b = int(std::clamp(v, 0.0f, 1.0f - eps) * B);
3353 if (b >= 0 && b < 2048) {
3357 std::vector<float> cdf(B);
3359 for (
int b = 0; b < B; ++b) {
3361 cdf[b] = float(acc) / float(N);
3365 for (
size_t i = 0; i < N; ++i) {
3367 if (lum[i] >= 1.0f) {
3368 data_red[i] = std::min(1.0f, data_red[i]);
3369 data_green[i] = std::min(1.0f, data_green[i]);
3370 data_blue[i] = std::min(1.0f, data_blue[i]);
3374 int b = int(std::clamp(lum[i], 0.0f, 1.0f - eps) * B);
3376 if (b < 0 || b >= 2048) {
3380 constexpr float k = 0.2f;
3381 constexpr float cs = 0.2f;
3384 float Ynew = (1.0f - k) * lum[i] + k * Yeq;
3387 float t = Ynew - 0.5f;
3388 Ynew = 0.5f + t * (1.0f + cs - 2.0f * cs * std::fabs(t));
3391 data_red[i] = Ynew * chroma_r[i];
3392 data_green[i] = Ynew * chroma_g[i];
3393 data_blue[i] = Ynew * chroma_b[i];
3397void 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) {
3399 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()) {
3400 helios_runtime_error(
"ERROR (RadiationModel::adjustSBC): One or more specified band labels do not exist for the camera pixel data.");
3404 constexpr float kRedW = 0.2126f;
3405 constexpr float kGreenW = 0.7152f;
3406 constexpr float kBlueW = 0.0722f;
3408 const size_t N = resolution.
x * resolution.
y;
3410 auto &data_red = pixel_data.at(red_band_label);
3411 auto &data_green = pixel_data.at(green_band_label);
3412 auto &data_blue = pixel_data.at(blue_band_label);
3414 for (
int i = 0; i < N; ++i) {
3416 helios::vec3 p(data_red[i], data_green[i], data_blue[i]);
3419 float Y = kRedW * p.
x + kGreenW * p.
y + kBlueW * p.
z;
3431 data_red[i] =
clamp(p.
x, 0.0f, 1.0f);
3432 data_green[i] =
clamp(p.
y, 0.0f, 1.0f);
3433 data_blue[i] =
clamp(p.
z, 0.0f, 1.0f);
3454 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()) {
3455 helios_runtime_error(
"ERROR (RadiationModel::gammaCompress): One or more specified band labels do not exist for the camera pixel data.");
3459 for (
float &v: pixel_data.at(red_band_label)) {
3462 for (
float &v: pixel_data.at(green_band_label)) {
3465 for (
float &v: pixel_data.at(blue_band_label)) {
3472void RadiationCamera::autoExposure(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float gain_multiplier) {
3474 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()) {
3475 helios_runtime_error(
"ERROR (RadiationModel::autoExposure): One or more specified band labels do not exist for the camera pixel data.");
3479 auto &data_red = pixel_data.at(red_band_label);
3480 auto &data_green = pixel_data.at(green_band_label);
3481 auto &data_blue = pixel_data.at(blue_band_label);
3483 const std::size_t N = data_red.size();
3486 std::vector<float> luminance_values(N);
3487 for (std::size_t i = 0; i < N; ++i) {
3488 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3492 std::vector<float> sorted_luminance = luminance_values;
3493 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3496 std::size_t p95_idx =
static_cast<std::size_t
>(0.95f * (N - 1));
3497 float p95_luminance = sorted_luminance[p95_idx];
3500 std::size_t median_idx = N / 2;
3501 float median_luminance = sorted_luminance[median_idx];
3505 float target_median = 0.18f;
3506 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3512 float final_gain = auto_gain * gain_multiplier;
3515 for (std::size_t i = 0; i < N; ++i) {
3516 data_red[i] *= final_gain;
3517 data_green[i] *= final_gain;
3518 data_blue[i] *= final_gain;
3524 if (pixel_data.empty()) {
3529 for (
const auto &band: band_labels) {
3530 if (pixel_data.find(band) == pixel_data.end()) {
3536 std::string exposure_mode = exposure;
3539 if (exposure_mode ==
"manual") {
3544 if (exposure_mode ==
"auto") {
3546 std::string cam_type;
3547 if (!camera_type.empty()) {
3548 cam_type = camera_type;
3551 cam_type = (band_labels.size() >= 3) ?
"rgb" :
"spectral";
3554 if (cam_type ==
"thermal") {
3557 }
else if (cam_type ==
"rgb" && band_labels.size() >= 3) {
3561 std::string red_band, green_band, blue_band;
3562 for (
const auto &band: band_labels) {
3563 if (band.find(
"red") != std::string::npos || band.find(
"Red") != std::string::npos || band.find(
"RED") != std::string::npos) {
3565 }
else if (band.find(
"green") != std::string::npos || band.find(
"Green") != std::string::npos || band.find(
"GREEN") != std::string::npos) {
3567 }
else if (band.find(
"blue") != std::string::npos || band.find(
"Blue") != std::string::npos || band.find(
"BLUE") != std::string::npos) {
3573 if (red_band.empty())
3574 red_band = band_labels[0];
3575 if (green_band.empty())
3576 green_band = band_labels[1];
3577 if (blue_band.empty())
3578 blue_band = band_labels[2];
3580 auto &data_red = pixel_data.at(red_band);
3581 auto &data_green = pixel_data.at(green_band);
3582 auto &data_blue = pixel_data.at(blue_band);
3584 const std::size_t N = data_red.size();
3587 std::vector<float> luminance_values(N);
3588 for (std::size_t i = 0; i < N; ++i) {
3589 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3593 std::vector<float> sorted_luminance = luminance_values;
3594 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3596 std::size_t median_idx = N / 2;
3597 float median_luminance = sorted_luminance[median_idx];
3600 float target_median = 0.18f;
3601 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3604 for (
auto &band_pair: pixel_data) {
3605 auto &data = band_pair.second;
3606 for (std::size_t i = 0; i < N; ++i) {
3607 data[i] *= auto_gain;
3611 }
else if (cam_type ==
"spectral") {
3613 for (
auto &band_pair: pixel_data) {
3614 auto &data = band_pair.second;
3615 const std::size_t N = data.size();
3618 std::vector<float> sorted_data = data;
3619 std::sort(sorted_data.begin(), sorted_data.end());
3621 std::size_t median_idx = N / 2;
3622 float median_value = sorted_data[median_idx];
3625 float target_median = 0.18f;
3626 float band_gain = target_median / std::max(median_value, 1e-6f);
3629 for (std::size_t i = 0; i < N; ++i) {
3630 data[i] *= band_gain;
3634 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown camera_type '" + cam_type +
"'. Must be 'rgb', 'spectral', or 'thermal'.");
3640 if (exposure_mode.substr(0, 3) ==
"ISO" || exposure_mode.substr(0, 3) ==
"iso") {
3644 iso_value = std::stoi(exposure_mode.substr(3));
3646 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Invalid ISO format '" + exposure_mode +
"'. Expected format: 'ISOXXX' (e.g., 'ISO100').");
3649 if (iso_value <= 0) {
3650 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): ISO value must be positive. Got: " + std::to_string(iso_value));
3654 if (lens_focal_length <= 0) {
3655 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) +
3656 ". Either set it explicitly or use 'auto' or 'manual' exposure mode.");
3661 float f_number = lens_focal_length / std::max(lens_diameter, 1e-6f);
3664 const float ref_iso = 100.0f;
3665 const float ref_shutter = 1.0f / 125.0f;
3666 const float ref_f_number = 2.8f;
3671 const float typical_scene_median = 10.0f;
3672 const float target_median = 0.0675f;
3676 float exposure = (float(iso_value) * shutter_speed) / (f_number * f_number);
3677 float ref_exposure = (ref_iso * ref_shutter) / (ref_f_number * ref_f_number);
3683 float ref_gain = target_median / typical_scene_median;
3684 float calibration_factor = ref_gain / ref_exposure;
3687 float exposure_multiplier = exposure * calibration_factor;
3690 const std::size_t N = pixel_data.begin()->second.size();
3691 for (
auto &band_pair: pixel_data) {
3692 auto &data = band_pair.second;
3693 for (std::size_t i = 0; i < N; ++i) {
3694 data[i] *= exposure_multiplier;
3701 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown exposure mode '" + exposure_mode +
"'. Must be 'auto', 'ISOXXX' (e.g., 'ISO100'), or 'manual'.");
3706 if (pixel_data.empty()) {
3711 for (
const auto &band: band_labels) {
3712 if (pixel_data.find(band) == pixel_data.end()) {
3718 std::string wb_mode = white_balance;
3721 if (wb_mode ==
"off") {
3726 if (band_labels.size() < 3) {
3731 if (wb_mode ==
"auto") {
3734 std::string red_band = band_labels[0];
3735 std::string green_band = band_labels[1];
3736 std::string blue_band = band_labels[2];
3740 }
catch (
const std::exception &e) {
3749 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraWhiteBalance): Unknown white_balance mode '" + wb_mode +
"'. Must be 'auto' or 'off'.");
3754 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()) {
3755 helios_runtime_error(
"ERROR (RadiationModel::adjustBrightnessContrast): One or more specified band labels do not exist for the camera pixel data.");
3759 auto &data_red = pixel_data.at(red_band_label);
3760 auto &data_green = pixel_data.at(green_band_label);
3761 auto &data_blue = pixel_data.at(blue_band_label);
3763 const std::size_t N = data_red.size();
3765 for (std::size_t i = 0; i < N; ++i) {
3767 float r = data_red[i] * brightness;
3768 float g = data_green[i] * brightness;
3769 float b = data_blue[i] * brightness;
3772 r = 0.5f + (r - 0.5f) * contrast;
3773 g = 0.5f + (g - 0.5f) * contrast;
3774 b = 0.5f + (b - 0.5f) * contrast;
3785 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()) {
3786 helios_runtime_error(
"ERROR (RadiationModel::adjustSaturation): One or more specified band labels do not exist for the camera pixel data.");
3790 auto &data_red = pixel_data.at(red_band_label);
3791 auto &data_green = pixel_data.at(green_band_label);
3792 auto &data_blue = pixel_data.at(blue_band_label);
3794 const std::size_t N = data_red.size();
3796 for (std::size_t i = 0; i < N; ++i) {
3797 float r = data_red[i];
3798 float g = data_green[i];
3799 float b = data_blue[i];
3802 float lum = luminance(r, g, b);
3805 data_red[i] = lum + saturation * (r - lum);
3806 data_green[i] = lum + saturation * (g - lum);
3807 data_blue[i] = lum + saturation * (b - lum);
3813std::string RadiationModel::detectLightingType()
const {
3814 if (radiation_sources.empty()) {
3818 bool has_sun =
false;
3819 bool has_artificial =
false;
3821 for (
const auto &source: radiation_sources) {
3822 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
3824 }
else if (source.source_type == RADIATION_SOURCE_TYPE_SPHERE || source.source_type == RADIATION_SOURCE_TYPE_RECTANGLE || source.source_type == RADIATION_SOURCE_TYPE_DISK) {
3825 has_artificial =
true;
3829 if (has_sun && has_artificial) {
3831 }
else if (has_sun) {
3833 }
else if (has_artificial) {
3834 return "artificial";
3848 float tilt_angle_deg = -asin(direction.
z) * 180.0f /
M_PI;
3850 return tilt_angle_deg;
3855 if (cameras.find(camera_label) == cameras.end()) {
3856 helios_runtime_error(
"ERROR (RadiationModel::computeAgronomicProperties): Camera '" + camera_label +
"' does not exist.");
3859 const auto &cam = cameras.at(camera_label);
3871 std::vector<uint> pixel_UUIDs;
3872 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
3877 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
3880 std::map<std::string, std::set<int>> species_to_plantIDs;
3883 std::set<int> weed_plantIDs;
3886 std::set<int> all_plantIDs;
3889 std::map<std::string, std::map<int, float>> species_plant_heights;
3890 std::map<std::string, std::map<int, float>> species_plant_ages;
3891 std::map<std::string, std::map<int, std::string>> species_plant_stages;
3892 std::map<std::string, std::map<int, float>> species_plant_leaf_areas;
3893 std::map<std::string, std::map<int, int>> species_plant_pixel_counts;
3896 for (
uint j = 0; j < cam.resolution.y; j++) {
3897 for (
uint i = 0; i < cam.resolution.x; i++) {
3898 uint pixel_index = j * cam.resolution.x + i;
3900 if (pixel_index >= pixel_UUIDs.size()) {
3904 uint UUID_plus_one = pixel_UUIDs.at(pixel_index);
3905 if (UUID_plus_one == 0) {
3910 uint UUID = UUID_plus_one - 1;
3925 std::string plant_name;
3926 bool has_plant_name =
false;
3929 if (datatype == HELIOS_TYPE_STRING) {
3931 has_plant_name =
true;
3937 bool has_plantID =
false;
3940 if (datatype == HELIOS_TYPE_INT) {
3943 }
else if (datatype == HELIOS_TYPE_UINT) {
3946 plantID =
static_cast<int>(plantID_uint);
3952 std::string plant_type;
3953 bool has_plant_type =
false;
3956 if (datatype == HELIOS_TYPE_STRING) {
3958 has_plant_type =
true;
3963 float plant_height = 0.0f;
3964 bool has_plant_height =
false;
3967 if (datatype == HELIOS_TYPE_FLOAT) {
3968 context->
getObjectData(objID,
"plant_height", plant_height);
3969 has_plant_height =
true;
3975 bool has_age =
false;
3978 if (datatype == HELIOS_TYPE_FLOAT) {
3985 std::string phenology_stage;
3986 bool has_phenology_stage =
false;
3989 if (datatype == HELIOS_TYPE_STRING) {
3990 context->
getObjectData(objID,
"phenology_stage", phenology_stage);
3991 has_phenology_stage =
true;
3999 if (has_plant_name && has_plantID) {
4001 species_to_plantIDs[plant_name].insert(plantID);
4004 all_plantIDs.insert(plantID);
4007 if (has_plant_type && plant_type ==
"weed") {
4008 weed_plantIDs.insert(plantID);
4012 if (has_plant_height) {
4013 species_plant_heights[plant_name][plantID] = plant_height;
4016 species_plant_ages[plant_name][plantID] = age;
4018 if (has_phenology_stage) {
4019 species_plant_stages[plant_name][plantID] = phenology_stage;
4023 species_plant_leaf_areas[plant_name][plantID] += primitive_area;
4026 species_plant_pixel_counts[plant_name][plantID]++;
4032 if (species_to_plantIDs.empty()) {
4037 for (
const auto &species_pair: species_to_plantIDs) {
4039 props.
plant_count.push_back(
static_cast<int>(species_pair.second.size()));
4043 for (
const auto &species_pair: species_to_plantIDs) {
4044 const std::string &species = species_pair.first;
4045 const std::set<int> &plantIDs = species_pair.second;
4048 if (species_plant_heights.find(species) != species_plant_heights.end()) {
4049 float total_weighted_height = 0.0f;
4050 int total_pixels = 0;
4051 for (
int plantID: plantIDs) {
4052 if (species_plant_heights.at(species).find(plantID) != species_plant_heights.at(species).end()) {
4053 float height = species_plant_heights.at(species).at(plantID);
4054 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4055 total_weighted_height += height *
static_cast<float>(pixel_count);
4056 total_pixels += pixel_count;
4059 if (total_pixels > 0) {
4060 props.
plant_height_m.push_back(total_weighted_height /
static_cast<float>(total_pixels));
4069 if (species_plant_ages.find(species) != species_plant_ages.end()) {
4070 float total_weighted_age = 0.0f;
4071 int total_pixels = 0;
4072 for (
int plantID: plantIDs) {
4073 if (species_plant_ages.at(species).find(plantID) != species_plant_ages.at(species).end()) {
4074 float age = species_plant_ages.at(species).at(plantID);
4075 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4076 total_weighted_age += age *
static_cast<float>(pixel_count);
4077 total_pixels += pixel_count;
4080 if (total_pixels > 0) {
4081 props.
plant_age_days.push_back(total_weighted_age /
static_cast<float>(total_pixels));
4090 if (species_plant_stages.find(species) != species_plant_stages.end()) {
4091 std::map<std::string, int> stage_counts;
4092 for (
int plantID: plantIDs) {
4093 if (species_plant_stages.at(species).find(plantID) != species_plant_stages.at(species).end()) {
4094 std::string stage = species_plant_stages.at(species).at(plantID);
4095 stage_counts[stage]++;
4099 std::string mode_stage;
4101 for (
const auto &stage_pair: stage_counts) {
4102 if (stage_pair.second > max_count) {
4103 max_count = stage_pair.second;
4104 mode_stage = stage_pair.first;
4113 if (species_plant_leaf_areas.find(species) != species_plant_leaf_areas.end()) {
4114 float total_leaf_area = 0.0f;
4115 for (
int plantID: plantIDs) {
4116 if (species_plant_leaf_areas.at(species).find(plantID) != species_plant_leaf_areas.at(species).end()) {
4117 total_leaf_area += species_plant_leaf_areas.at(species).at(plantID);
4127 if (!all_plantIDs.empty()) {
4128 float weed_fraction =
static_cast<float>(weed_plantIDs.size()) /
static_cast<float>(all_plantIDs.size());
4129 float weed_percentage = weed_fraction * 100.0f;
4131 if (weed_percentage <= 20.0f) {
4133 }
else if (weed_percentage <= 40.0f) {
4141void RadiationModel::populateCameraMetadata(
const std::string &camera_label,
CameraMetadata &metadata)
const {
4143 if (cameras.find(camera_label) == cameras.end()) {
4144 helios_runtime_error(
"ERROR (RadiationModel::populateCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4147 const auto &cam = cameras.at(camera_label);
4150 metadata.camera_properties.
width = cam.resolution.x;
4151 metadata.camera_properties.
height = cam.resolution.y;
4152 metadata.camera_properties.
channels =
static_cast<int>(cam.band_labels.size());
4153 metadata.camera_properties.
type = cam.camera_type;
4157 metadata.camera_properties.
sensor_width = cam.sensor_width_mm;
4160 float VFOV_degrees = cam.HFOV_degrees / cam.FOV_aspect_ratio;
4163 metadata.camera_properties.
sensor_height = cam.sensor_width_mm / cam.FOV_aspect_ratio;
4172 float HFOV_rad = cam.HFOV_degrees *
M_PI / 180.0f;
4173 float optical_focal_length_mm = cam.sensor_width_mm / (2.0f * tan(HFOV_rad / 2.0f));
4174 metadata.camera_properties.
focal_length = optical_focal_length_mm;
4177 if (cam.lens_diameter > 0) {
4178 float lens_diameter_mm = cam.lens_diameter * 1000.0f;
4179 float f_number = optical_focal_length_mm / lens_diameter_mm;
4180 std::ostringstream aperture_str;
4181 aperture_str <<
"f/" << std::fixed << std::setprecision(1) << f_number;
4182 metadata.camera_properties.
aperture = aperture_str.str();
4184 metadata.camera_properties.
aperture =
"pinhole";
4188 metadata.camera_properties.
model = cam.model;
4191 metadata.camera_properties.
lens_make = cam.lens_make;
4192 metadata.camera_properties.
lens_model = cam.lens_model;
4196 metadata.camera_properties.
exposure = cam.exposure;
4197 metadata.camera_properties.
shutter_speed = cam.shutter_speed;
4200 metadata.camera_properties.
white_balance = cam.white_balance;
4203 metadata.camera_properties.
camera_zoom = cam.camera_zoom;
4215 std::ostringstream date_str;
4216 date_str << date.
year <<
"-" << std::setw(2) << std::setfill(
'0') << date.
month <<
"-" << std::setw(2) << std::setfill(
'0') << date.
day;
4217 metadata.acquisition_properties.
date = date_str.str();
4220 std::ostringstream time_str;
4221 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;
4222 metadata.acquisition_properties.
time = time_str.str();
4226 metadata.acquisition_properties.
camera_angle_deg = calculateCameraTiltAngle(cam.position, cam.lookat);
4227 metadata.acquisition_properties.
light_source = detectLightingType();
4230 computeAgronomicProperties(camera_label, metadata.agronomic_properties);
4238 if (cameras.find(camera_label) == cameras.end()) {
4239 helios_runtime_error(
"ERROR (RadiationModel::enableCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4244 if (camera_metadata.find(camera_label) != camera_metadata.end()) {
4245 saved_image_processing = camera_metadata.at(camera_label).image_processing;
4250 populateCameraMetadata(camera_label, metadata);
4253 metadata.image_processing = saved_image_processing;
4256 camera_metadata[camera_label] = metadata;
4257 metadata_enabled_cameras.insert(camera_label);
4262 for (
const auto &camera_label: camera_labels) {
4269 if (cameras.find(camera_label) == cameras.end()) {
4270 helios_runtime_error(
"ERROR (RadiationModel::getCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4275 populateCameraMetadata(camera_label, metadata);
4282 if (cameras.find(camera_label) == cameras.end()) {
4283 helios_runtime_error(
"ERROR (RadiationModel::setCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4286 camera_metadata[camera_label] = metadata;
4289std::string RadiationModel::writeCameraMetadataFile(
const std::string &camera_label,
const std::string &output_path)
const {
4291 if (camera_metadata.find(camera_label) == camera_metadata.end()) {
4292 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): No metadata set for camera '" + camera_label +
"'.");
4295 const auto &metadata = camera_metadata.at(camera_label);
4299 auto format_float = [](
float value,
int decimals) ->
double {
4300 std::ostringstream oss;
4301 oss << std::fixed << std::setprecision(decimals) << value;
4302 return std::stod(oss.str());
4307 j[
"path"] = metadata.
path;
4309 j[
"camera_properties"][
"height"] = metadata.camera_properties.
height;
4310 j[
"camera_properties"][
"width"] = metadata.camera_properties.
width;
4311 j[
"camera_properties"][
"channels"] = metadata.camera_properties.
channels;
4312 j[
"camera_properties"][
"type"] = metadata.camera_properties.
type;
4313 j[
"camera_properties"][
"focal_length"] = format_float(metadata.camera_properties.
focal_length, 2);
4314 j[
"camera_properties"][
"aperture"] = metadata.camera_properties.
aperture;
4315 j[
"camera_properties"][
"sensor_width"] = format_float(metadata.camera_properties.
sensor_width, 2);
4316 j[
"camera_properties"][
"sensor_height"] = format_float(metadata.camera_properties.
sensor_height, 2);
4317 j[
"camera_properties"][
"model"] = metadata.camera_properties.
model;
4320 if (!metadata.camera_properties.
lens_make.empty()) {
4321 j[
"camera_properties"][
"lens_make"] = metadata.camera_properties.
lens_make;
4323 if (!metadata.camera_properties.
lens_model.empty()) {
4324 j[
"camera_properties"][
"lens_model"] = metadata.camera_properties.
lens_model;
4327 j[
"camera_properties"][
"lens_specification"] = metadata.camera_properties.
lens_specification;
4331 j[
"camera_properties"][
"exposure"] = metadata.camera_properties.
exposure;
4332 j[
"camera_properties"][
"shutter_speed"] = format_float(metadata.camera_properties.
shutter_speed, 6);
4335 j[
"camera_properties"][
"white_balance"] = metadata.camera_properties.
white_balance;
4338 j[
"camera_properties"][
"zoom"] = format_float(metadata.camera_properties.
camera_zoom, 2);
4340 j[
"location_properties"][
"latitude"] = format_float(metadata.location_properties.
latitude, 6);
4341 j[
"location_properties"][
"longitude"] = format_float(metadata.location_properties.
longitude, 6);
4343 j[
"acquisition_properties"][
"date"] = metadata.acquisition_properties.
date;
4344 j[
"acquisition_properties"][
"time"] = metadata.acquisition_properties.
time;
4345 j[
"acquisition_properties"][
"UTC_offset"] = format_float(metadata.acquisition_properties.
UTC_offset, 1);
4346 j[
"acquisition_properties"][
"camera_height_m"] = format_float(metadata.acquisition_properties.
camera_height_m, 2);
4347 j[
"acquisition_properties"][
"camera_angle_deg"] = format_float(metadata.acquisition_properties.
camera_angle_deg, 2);
4348 j[
"acquisition_properties"][
"light_source"] = metadata.acquisition_properties.
light_source;
4351 const auto &img_proc = metadata.image_processing;
4352 j[
"image_processing"][
"saturation_adjustment"] = format_float(img_proc.saturation_adjustment, 2);
4353 j[
"image_processing"][
"brightness_adjustment"] = format_float(img_proc.brightness_adjustment, 2);
4354 j[
"image_processing"][
"contrast_adjustment"] = format_float(img_proc.contrast_adjustment, 2);
4355 j[
"image_processing"][
"color_space"] = img_proc.color_space;
4359 j[
"agronomic_properties"][
"plant_species"] = metadata.agronomic_properties.
plant_species;
4360 j[
"agronomic_properties"][
"plant_count"] = metadata.agronomic_properties.
plant_count;
4364 std::vector<double> formatted_heights;
4365 for (
float height: metadata.agronomic_properties.plant_height_m) {
4366 formatted_heights.push_back(format_float(height, 2));
4368 j[
"agronomic_properties"][
"plant_height_m"] = formatted_heights;
4372 std::vector<double> formatted_ages;
4373 for (
float age: metadata.agronomic_properties.plant_age_days) {
4374 formatted_ages.push_back(format_float(age, 1));
4376 j[
"agronomic_properties"][
"plant_age_days"] = formatted_ages;
4379 if (!metadata.agronomic_properties.
plant_stage.empty()) {
4380 j[
"agronomic_properties"][
"plant_stage"] = metadata.agronomic_properties.
plant_stage;
4383 if (!metadata.agronomic_properties.
leaf_area_m2.empty()) {
4384 std::vector<double> formatted_leaf_areas;
4385 for (
float area: metadata.agronomic_properties.leaf_area_m2) {
4386 formatted_leaf_areas.push_back(format_float(area, 4));
4388 j[
"agronomic_properties"][
"leaf_area_m2"] = formatted_leaf_areas;
4391 j[
"agronomic_properties"][
"weed_pressure"] = metadata.agronomic_properties.
weed_pressure;
4395 std::string json_filename = metadata.
path;
4396 size_t ext_pos = json_filename.find_last_of(
".");
4397 if (ext_pos != std::string::npos) {
4398 json_filename = json_filename.substr(0, ext_pos) +
".json";
4400 json_filename +=
".json";
4404 std::string json_path = output_path + json_filename;
4407 std::ofstream json_file(json_path);
4408 if (!json_file.is_open()) {
4409 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): Failed to open file '" + json_path +
"' for writing.");
4411 json_file << j.dump(2) << std::endl;
4418 if (cameras.find(camera_label) == cameras.end()) {
4419 helios_runtime_error(
"ERROR (RadiationModel::enableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4421 cameras.at(camera_label).lens_flare_enabled =
true;
4425 if (cameras.find(camera_label) == cameras.end()) {
4426 helios_runtime_error(
"ERROR (RadiationModel::disableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4428 cameras.at(camera_label).lens_flare_enabled =
false;
4432 if (cameras.find(camera_label) == cameras.end()) {
4433 helios_runtime_error(
"ERROR (RadiationModel::isCameraLensFlareEnabled): Camera '" + camera_label +
"' does not exist.");
4435 return cameras.at(camera_label).lens_flare_enabled;
4439 if (cameras.find(camera_label) == cameras.end()) {
4440 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4445 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): aperture_blade_count must be at least 3.");
4448 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): coating_efficiency must be in range [0.0, 1.0].");
4451 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_intensity must be non-negative.");
4454 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): starburst_intensity must be non-negative.");
4457 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): intensity_threshold must be in range [0.0, 1.0].");
4460 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_count must be at least 1.");
4463 cameras.at(camera_label).lens_flare_properties = properties;
4467 if (cameras.find(camera_label) == cameras.end()) {
4468 helios_runtime_error(
"ERROR (RadiationModel::getCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4470 return cameras.at(camera_label).lens_flare_properties;