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.");
34 }
else if (camera_properties.camera_resolution.x <= 0 || camera_properties.camera_resolution.y <= 0) {
35 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCamera): Camera resolution must be at least 1x1.");
36 }
else if (camera_properties.HFOV < 0 || camera_properties.HFOV > 180.f) {
37 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCamera): Camera horizontal field of view must be between 0 and 180 degrees.");
47 for (
const auto &band : band_label) {
48 auto it = radiation_bands.find(band);
49 if (it != radiation_bands.end() && it->second.scatteringDepth == 0) {
50 warnings.
addWarning(
"camera_band_zero_scattering_depth",
51 "Camera '" + camera_label +
"' is bound to band '" + band +
52 "' which has scatteringDepth == 0. Camera pixels for this band will be "
53 "zero because camera ray tracing relies on scattered flux. Call "
54 "setScatteringDepth(\"" + band +
"\", >=1) before runBand().");
57 warnings.
report(std::cerr);
62 if (camera_properties.FOV_aspect_ratio != 0.f) {
63 std::cerr <<
"WARNING (RadiationModel::addRadiationCamera): FOV_aspect_ratio is deprecated and will be ignored. The value is auto-calculated from camera_resolution to ensure square pixels." << std::endl;
65 modified_properties.
FOV_aspect_ratio = float(camera_properties.camera_resolution.x) / float(camera_properties.camera_resolution.y);
67 RadiationCamera camera(camera_label, band_label, position, lookat, modified_properties, antialiasing_samples);
68 if (cameras.find(camera_label) == cameras.end()) {
69 cameras.emplace(camera_label, camera);
72 std::cout <<
"Camera with label " << camera_label <<
"already exists. Existing properties will be replaced by new inputs." << std::endl;
74 cameras.erase(camera_label);
75 cameras.emplace(camera_label, camera);
78 if (iscameravisualizationenabled) {
79 buildCameraModelGeometry(camera_label);
84 populateCameraMetadata(camera_label, metadata);
85 camera_metadata[camera_label] = metadata;
87 radiativepropertiesneedupdate =
true;
91 uint antialiasing_samples) {
94 addRadiationCamera(camera_label, band_label, position, lookat, camera_properties, antialiasing_samples);
98 if (cameras.find(camera_label) == cameras.end()) {
99 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Camera '" + camera_label +
"' does not exist.");
101 helios_runtime_error(
"ERROR (setCameraSpectralResponse): Band '" + band_label +
"' does not exist.");
104 cameras.at(camera_label).band_spectral_response[band_label] = global_data;
106 radiativepropertiesneedupdate =
true;
111 if (cameras.find(camera_label) == cameras.end()) {
112 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Camera '" + camera_label +
"' does not exist.");
115 const auto &band_labels = cameras.at(camera_label).band_labels;
117 if (!
context->doesGlobalDataExist(
"spectral_library_loaded")) {
121 for (
const auto &band: band_labels) {
122 std::string response_spectrum = camera_library_name +
"_" + band;
124 helios_runtime_error(
"ERROR (setCameraSpectralResponseFromLibrary): Band '" + band +
"' referenced in spectral library camera " + camera_library_name +
" does not exist for camera '" + camera_label +
"'.");
127 cameras.at(camera_label).band_spectral_response[band] = response_spectrum;
130 radiativepropertiesneedupdate =
true;
139 const std::vector<std::string> &custom_band_labels) {
145 pugi::xml_document xmldoc;
146 pugi::xml_parse_result result = xmldoc.load_file(library_path.string().c_str());
149 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Failed to load camera library file '" + library_path.string() +
"'. " + result.description());
152 pugi::xml_node helios_node = xmldoc.child(
"helios");
153 if (helios_node.empty()) {
154 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera library XML must have '<helios>' root tag.");
158 pugi::xml_node camera_node;
159 for (pugi::xml_node cam = helios_node.child(
"camera"); cam; cam = cam.next_sibling(
"camera")) {
160 std::string label = cam.attribute(
"label").value();
161 if (label == library_camera_label) {
167 if (camera_node.empty()) {
168 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Camera '" + library_camera_label +
"' not found in camera library.");
172 std::string manufacturer = camera_node.child(
"manufacturer").child_value();
173 std::string model = camera_node.child(
"model").child_value();
176 std::string camera_type = camera_node.child(
"type").child_value();
177 if (camera_type.empty()) {
178 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Missing required 'type' field for camera '" + library_camera_label +
"'.");
180 if (camera_type !=
"rgb" && camera_type !=
"spectral" && camera_type !=
"thermal") {
181 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid camera type '" + camera_type +
"' for camera '" + library_camera_label +
"'. Must be one of: 'rgb', 'spectral', or 'thermal'.");
184 float sensor_width_mm;
185 if (!
helios::parse_float(camera_node.child(
"sensor_width_mm").child_value(), sensor_width_mm)) {
186 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing sensor_width_mm for camera '" + library_camera_label +
"'.");
189 int resolution_width;
190 if (!
helios::parse_int(camera_node.child(
"resolution_width").child_value(), resolution_width)) {
191 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_width for camera '" + library_camera_label +
"'.");
194 int resolution_height;
195 if (!
helios::parse_int(camera_node.child(
"resolution_height").child_value(), resolution_height)) {
196 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing resolution_height for camera '" + library_camera_label +
"'.");
200 float focal_length_mm;
201 if (!
helios::parse_float(camera_node.child(
"focal_length_mm").child_value(), focal_length_mm)) {
202 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing focal_length_mm for camera '" + library_camera_label +
"'.");
205 float lens_diameter_mm;
206 if (!
helios::parse_float(camera_node.child(
"lens_diameter_mm").child_value(), lens_diameter_mm)) {
207 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid or missing lens_diameter_mm for camera '" + library_camera_label +
"'.");
211 float focal_plane_distance_m = 2.0f;
212 if (camera_node.child(
"focal_plane_distance_m")) {
213 if (!
helios::parse_float(camera_node.child(
"focal_plane_distance_m").child_value(), focal_plane_distance_m)) {
214 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Invalid focal_plane_distance_m for camera '" + library_camera_label +
"'.");
219 std::string lens_make = camera_node.child(
"lens_make").child_value();
220 std::string lens_model = camera_node.child(
"lens_model").child_value();
221 std::string lens_specification = camera_node.child(
"lens_specification").child_value();
224 std::string exposure_mode = camera_node.child(
"exposure").child_value();
225 if (exposure_mode.empty()) {
226 exposure_mode =
"auto";
230 float shutter_speed = 1.0f / 125.0f;
231 if (camera_node.child(
"shutter_speed")) {
232 if (!
helios::parse_float(camera_node.child(
"shutter_speed").child_value(), shutter_speed)) {
233 std::cerr <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Invalid shutter_speed for camera '" << library_camera_label <<
"'. Using default 1/125 second." << std::endl;
234 shutter_speed = 1.0f / 125.0f;
239 std::string white_balance_mode = camera_node.child(
"white_balance").child_value();
240 if (white_balance_mode.empty()) {
241 white_balance_mode =
"auto";
242 }
else if (white_balance_mode !=
"auto" && white_balance_mode !=
"off") {
243 std::cerr <<
"WARNING (RadiationModel::addRadiationCameraFromLibrary): Invalid white_balance mode '" << white_balance_mode <<
"' for camera '" << library_camera_label <<
"'. Must be 'auto' or 'off'. Using default 'auto'." << std::endl;
244 white_balance_mode =
"auto";
249 camera_properties.camera_resolution =
helios::make_int2(resolution_width, resolution_height);
250 camera_properties.sensor_width_mm = sensor_width_mm;
254 float HFOV_rad = 2.0f * atan(sensor_width_mm / (2.0f * focal_length_mm));
255 camera_properties.HFOV = HFOV_rad * 180.0f /
M_PI;
258 camera_properties.focal_plane_distance = focal_plane_distance_m;
261 camera_properties.lens_focal_length = focal_length_mm / 1000.0f;
264 camera_properties.lens_diameter = lens_diameter_mm / 1000.0f;
267 camera_properties.FOV_aspect_ratio = 0.0f;
272 camera_properties.manufacturer = manufacturer;
273 camera_properties.model = manufacturer +
" " + model;
276 camera_properties.lens_make = lens_make;
277 camera_properties.lens_model = lens_model;
278 camera_properties.lens_specification = lens_specification;
281 camera_properties.exposure = exposure_mode;
282 camera_properties.shutter_speed = shutter_speed;
285 camera_properties.white_balance = white_balance_mode;
289 std::vector<std::string> xml_band_labels;
291 std::vector<std::pair<float, float>> spectral_wavelength_ranges;
293 for (pugi::xml_node spectral_node = camera_node.child(
"spectral_response"); spectral_node; spectral_node = spectral_node.next_sibling(
"spectral_response")) {
295 std::string xml_band_label = spectral_node.attribute(
"label").value();
296 if (xml_band_label.empty()) {
297 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): spectral_response node missing 'label' attribute for camera '" + library_camera_label +
"'.");
300 xml_band_labels.push_back(xml_band_label);
303 std::vector<helios::vec2> spectral_data;
304 std::string data_str = spectral_node.child_value();
306 if (!data_str.empty()) {
307 std::istringstream data_stream(data_str);
308 float wavelength, response;
309 while (data_stream >> wavelength >> response) {
314 if (spectral_data.empty()) {
315 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): Empty spectral response data for band '" + xml_band_label +
"' in camera '" + library_camera_label +
"'.");
319 spectral_wavelength_ranges.emplace_back(spectral_data.front().x, spectral_data.back().x);
322 std::string global_data_label = library_camera_label +
"_" + xml_band_label;
323 context->setGlobalData(global_data_label.c_str(), spectral_data);
326 if (xml_band_labels.empty()) {
327 helios_runtime_error(
"ERROR (RadiationModel::addRadiationCameraFromLibrary): No spectral response data found for camera '" + library_camera_label +
"'.");
331 std::vector<std::string> effective_band_labels;
332 if (!custom_band_labels.empty()) {
333 if (custom_band_labels.size() != xml_band_labels.size()) {
334 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 (" +
335 std::to_string(xml_band_labels.size()) +
") for camera '" + library_camera_label +
"'.");
337 effective_band_labels = custom_band_labels;
339 effective_band_labels = xml_band_labels;
343 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
344 const std::string &band_label = effective_band_labels[i];
346 float min_wavelength = spectral_wavelength_ranges[i].first;
347 float max_wavelength = spectral_wavelength_ranges[i].second;
356 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;
361 addRadiationCamera(camera_label, effective_band_labels, position, lookat, camera_properties, antialiasing_samples);
364 cameras.at(camera_label).camera_type = camera_type;
367 for (
size_t i = 0; i < effective_band_labels.size(); i++) {
368 std::string global_data_label = library_camera_label +
"_" + xml_band_labels[i];
374 if (cameras.find(camera_label) == cameras.end()) {
375 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera '" + camera_label +
"' does not exist.");
376 }
else if (position == cameras.at(camera_label).lookat) {
377 helios_runtime_error(
"ERROR (RadiationModel::setCameraPosition): Camera position cannot be equal to the 'lookat' position.");
380 cameras.at(camera_label).position = position;
382 if (iscameravisualizationenabled) {
383 updateCameraModelPosition(camera_label);
389 if (cameras.find(camera_label) == cameras.end()) {
390 helios_runtime_error(
"ERROR (RadiationModel::getCameraPosition): Camera '" + camera_label +
"' does not exist.");
393 return cameras.at(camera_label).position;
397 if (cameras.find(camera_label) == cameras.end()) {
398 helios_runtime_error(
"ERROR (RadiationModel::setCameraLookat): Camera '" + camera_label +
"' does not exist.");
401 cameras.at(camera_label).lookat = lookat;
403 if (iscameravisualizationenabled) {
404 updateCameraModelPosition(camera_label);
410 if (cameras.find(camera_label) == cameras.end()) {
411 helios_runtime_error(
"ERROR (RadiationModel::getCameraLookat): Camera '" + camera_label +
"' does not exist.");
414 return cameras.at(camera_label).lookat;
418 if (cameras.find(camera_label) == cameras.end()) {
419 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
422 cameras.at(camera_label).lookat = cameras.at(camera_label).position + direction;
424 if (iscameravisualizationenabled) {
425 updateCameraModelPosition(camera_label);
431 if (cameras.find(camera_label) == cameras.end()) {
432 helios_runtime_error(
"ERROR (RadiationModel::getCameraOrientation): Camera '" + camera_label +
"' does not exist.");
435 return cart2sphere(cameras.at(camera_label).lookat - cameras.at(camera_label).position);
439 if (cameras.find(camera_label) == cameras.end()) {
440 helios_runtime_error(
"ERROR (RadiationModel::setCameraOrientation): Camera '" + camera_label +
"' does not exist.");
443 cameras.at(camera_label).lookat = cameras.at(camera_label).position +
sphere2cart(direction);
445 if (iscameravisualizationenabled) {
446 updateCameraModelPosition(camera_label);
453 if (cameras.find(camera_label) == cameras.end()) {
454 helios_runtime_error(
"ERROR (RadiationModel::getCameraParameters): Camera '" + camera_label +
"' does not exist.");
458 const auto &camera = cameras.at(camera_label);
462 camera_properties.camera_resolution = camera.resolution;
463 camera_properties.HFOV = camera.HFOV_degrees;
464 camera_properties.lens_diameter = camera.lens_diameter;
465 camera_properties.focal_plane_distance = camera.focal_length;
466 camera_properties.lens_focal_length = camera.lens_focal_length;
467 camera_properties.sensor_width_mm = camera.sensor_width_mm;
468 camera_properties.manufacturer = camera.manufacturer;
469 camera_properties.model = camera.model;
470 camera_properties.lens_make = camera.lens_make;
471 camera_properties.lens_model = camera.lens_model;
472 camera_properties.lens_specification = camera.lens_specification;
473 camera_properties.exposure = camera.exposure;
474 camera_properties.shutter_speed = camera.shutter_speed;
475 camera_properties.white_balance = camera.white_balance;
476 camera_properties.camera_zoom = camera.camera_zoom;
477 camera_properties.FOV_aspect_ratio = camera.FOV_aspect_ratio;
479 return camera_properties;
485 if (cameras.find(camera_label) == cameras.end()) {
486 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera '" + camera_label +
"' does not exist.");
490 if (camera_properties.camera_resolution.x <= 0 || camera_properties.camera_resolution.y <= 0) {
491 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera resolution must be at least 1x1.");
492 }
else if (camera_properties.HFOV <= 0 || camera_properties.HFOV >= 180.f) {
493 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): Camera horizontal field of view must be between 0 and 180 degrees.");
494 }
else if (camera_properties.camera_zoom <= 0.0f) {
495 helios_runtime_error(
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.");
499 auto &camera = cameras.at(camera_label);
502 camera.resolution = camera_properties.camera_resolution;
503 camera.HFOV_degrees = camera_properties.HFOV;
504 camera.lens_diameter = camera_properties.lens_diameter;
505 camera.focal_length = camera_properties.focal_plane_distance;
506 camera.lens_focal_length = camera_properties.lens_focal_length;
507 camera.sensor_width_mm = camera_properties.sensor_width_mm;
508 camera.manufacturer = camera_properties.manufacturer;
509 camera.model = camera_properties.model;
510 camera.exposure = camera_properties.exposure;
511 camera.shutter_speed = camera_properties.shutter_speed;
512 camera.white_balance = camera_properties.white_balance;
513 camera.camera_zoom = camera_properties.camera_zoom;
516 camera.FOV_aspect_ratio = float(camera.resolution.x) / float(camera.resolution.y);
519 radiativepropertiesneedupdate =
true;
522 if (iscameravisualizationenabled) {
523 updateCameraModelPosition(camera_label);
528 std::vector<std::string> labels(cameras.size());
530 for (
const auto &camera: cameras) {
531 labels.at(cam) = camera.second.label;
537std::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) {
540 if (cameras.find(camera) == cameras.end()) {
541 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
545 if (bands.size() != 1 && bands.size() != 3) {
546 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.");
549 std::vector<std::vector<float>> camera_data(bands.size());
552 for (
const auto &band: bands) {
555 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
556 std::cout <<
"ERROR (RadiationModel::writeCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
560 camera_data.at(b) = cameras.at(camera).pixel_data.at(band);
567 bool is_rgb = (camera_data.size() == 3);
569 for (
auto &band_data: camera_data) {
570 for (
float &v: band_data) {
576 std::string frame_str;
578 frame_str = std::to_string(frame);
581 std::string output_path = image_path;
583 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
585 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
588 std::ostringstream outfile;
589 outfile << output_path;
592 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
594 outfile << camera <<
"_" << imagefile_base <<
".jpeg";
596 std::ofstream testfile(outfile.str());
598 if (!testfile.is_open()) {
599 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;
604 int2 camera_resolution = cameras.at(camera).resolution;
606 std::vector<RGBcolor> pixel_data_RGB(camera_resolution.x * camera_resolution.y);
609 for (
uint j = 0; j < camera_resolution.y; j++) {
610 for (
uint i = 0; i < camera_resolution.x; i++) {
611 if (camera_data.size() == 1) {
612 float c = camera_data.front().at(j * camera_resolution.x + i);
615 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));
617 pixel_color.
scale(flux_to_pixel_conversion);
618 uint ii = camera_resolution.x - i - 1;
619 uint jj = camera_resolution.y - j - 1;
620 pixel_data_RGB.at(jj * camera_resolution.x + ii) = pixel_color;
625 populateImageEXIF(camera, exif);
626 writeJPEG(outfile.str(), camera_resolution.x, camera_resolution.y, pixel_data_RGB, exif);
628 std::string image_filepath = outfile.str();
631 if (metadata_enabled_cameras.find(camera) != metadata_enabled_cameras.end()) {
634 if (camera_metadata.find(camera) != camera_metadata.end()) {
635 saved_image_processing = camera_metadata.at(camera).image_processing;
640 populateCameraMetadata(camera, metadata);
643 metadata.image_processing = saved_image_processing;
646 metadata.image_processing.
exposure_gain = cameras.at(camera).applied_exposure_gain;
650 metadata.image_processing.
color_space = is_rgb ?
"sRGB" :
"linear";
653 size_t last_slash = image_filepath.find_last_of(
"/\\");
654 std::string filename_only = (last_slash != std::string::npos) ? image_filepath.substr(last_slash + 1) : image_filepath;
655 metadata.
path = filename_only;
658 camera_metadata[camera] = metadata;
659 writeCameraMetadataFile(camera, output_path);
662 return image_filepath;
665std::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) {
668 for (
const std::string &band: bands) {
669 std::string global_data_label =
"camera_" + camera +
"_" + band;
670 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
671 std::cout <<
"ERROR (RadiationModel::writeNormCameraImage): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
673 }
else if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
674 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;
677 std::vector<float> cameradata;
678 context->getGlobalData(global_data_label.c_str(), cameradata);
679 for (
float val: cameradata) {
686 for (
const std::string &band: bands) {
687 std::string global_data_label =
"camera_" + camera +
"_" + band;
688 std::vector<float> cameradata;
689 context->getGlobalData(global_data_label.c_str(), cameradata);
690 for (
float &val: cameradata) {
693 context->setGlobalData(global_data_label.c_str(), cameradata);
702 if (cameras.find(camera) == cameras.end()) {
703 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera with label " << camera <<
" does not exist. Skipping image write for this camera." << std::endl;
707 std::vector<float> camera_data;
710 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
711 std::cout <<
"ERROR (RadiationModel::writeCameraImageData): camera " << camera <<
" band with label " << band <<
" does not exist. Skipping image write for this camera." << std::endl;
715 std::string global_data_label =
"camera_" + camera +
"_" + band;
717 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
718 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;
722 context->getGlobalData(global_data_label.c_str(), camera_data);
724 std::string frame_str;
726 frame_str = std::to_string(frame);
729 std::string output_path = image_path;
731 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
733 helios_runtime_error(
"ERROR(RadiationModel::writeCameraImage): Expected a directory path but got a file path for argument 'image_path'.");
736 std::ostringstream outfile;
737 outfile << output_path;
740 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
742 outfile << camera <<
"_" << imagefile_base <<
".txt";
745 std::ofstream outfilestream(outfile.str());
747 if (!outfilestream.is_open()) {
748 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;
752 int2 camera_resolution = cameras.at(camera).resolution;
754 for (
int j = 0; j < camera_resolution.y; j++) {
755 for (
int i = camera_resolution.x - 1; i >= 0; i--) {
756 outfilestream << camera_data.at(j * camera_resolution.x + i) <<
" ";
758 outfilestream <<
"\n";
761 outfilestream.close();
766 if (cameras.find(camera) == cameras.end()) {
767 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' does not exist.");
770 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
771 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' band with label '" + band +
"' does not exist.");
774 std::string global_data_label =
"camera_" + camera +
"_" + band;
776 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
777 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Image data for camera '" + camera +
"', band '" + band +
"' has not been created. Did you run the radiation model?");
780 std::vector<float> camera_data;
781 context->getGlobalData(global_data_label.c_str(), camera_data);
783 std::string output_path = image_path;
785 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
787 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
790 std::ostringstream outfile;
791 outfile << output_path;
793 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
795 outfile << camera <<
"_" << imagefile_base <<
".exr";
798 int2 camera_resolution = cameras.at(camera).resolution;
801 std::vector<float> flipped_data(camera_resolution.x * camera_resolution.y);
802 for (
int j = 0; j < camera_resolution.y; j++) {
803 for (
int i = 0; i < camera_resolution.x; i++) {
804 int ii = camera_resolution.x - i - 1;
805 flipped_data[j * camera_resolution.x + i] = camera_data[j * camera_resolution.x + ii];
809 helios::writeEXR(outfile.str(), camera_resolution.x, camera_resolution.y, flipped_data);
814 if (cameras.find(camera) == cameras.end()) {
815 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' does not exist.");
818 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): 'bands' vector is empty.");
821 int2 camera_resolution = cameras.at(camera).resolution;
822 size_t num_pixels = camera_resolution.x * camera_resolution.y;
824 std::vector<std::vector<float>> channel_data(bands.size());
825 std::vector<std::string> channel_names(bands.size());
827 for (
size_t b = 0; b < bands.size(); b++) {
828 const std::string &band = bands[b];
830 if (std::find(cameras.at(camera).band_labels.begin(), cameras.at(camera).band_labels.end(), band) == cameras.at(camera).band_labels.end()) {
831 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Camera '" + camera +
"' band with label '" + band +
"' does not exist.");
834 std::string global_data_label =
"camera_" + camera +
"_" + band;
835 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
836 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Image data for camera '" + camera +
"', band '" + band +
"' has not been created. Did you run the radiation model?");
839 std::vector<float> raw_data;
840 context->getGlobalData(global_data_label.c_str(), raw_data);
843 channel_data[b].resize(num_pixels);
844 for (
int j = 0; j < camera_resolution.y; j++) {
845 for (
int i = 0; i < camera_resolution.x; i++) {
846 int ii = camera_resolution.x - i - 1;
847 channel_data[b][j * camera_resolution.x + i] = raw_data[j * camera_resolution.x + ii];
851 channel_names[b] = band;
854 std::string output_path = image_path;
856 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
858 helios_runtime_error(
"ERROR (RadiationModel::writeCameraImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
861 std::ostringstream outfile;
862 outfile << output_path;
864 outfile << camera <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
866 outfile << camera <<
"_" << imagefile_base <<
".exr";
869 helios::writeEXR(outfile.str(), camera_resolution.x, camera_resolution.y, channel_data, channel_names);
874 calibration_flag =
true;
878 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
880 std::vector<std::string> objectlabels;
881 vec2 wavelengthrange_c = wavelengthrange;
882 cameracalibration->
preprocessSpectra(sourcelabels_raw, cameraresponselabels, objectlabels, wavelengthrange_c);
886 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
890 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
892 cameraproperties.
exposure = calibratecamera.exposure;
893 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
896 std::string cameralabel =
"calibration";
897 std::map<uint, std::vector<vec2>> simulatedcolorboardspectra;
898 for (
uint UUID: UUIDs_target) {
899 simulatedcolorboardspectra.emplace(UUID, NULL);
902 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
906 std::vector<float> wavelengths;
907 context->getGlobalData(
"wavelengths", wavelengths);
908 int numberwavelengths = wavelengths.size();
910 for (
int iw = 0; iw < numberwavelengths; iw++) {
911 std::string wavelengthlabel = std::to_string(wavelengths.at(iw));
913 std::vector<std::string> sourcelabels;
914 for (std::string sourcelabel_raw: sourcelabels_raw) {
915 std::vector<vec2> icalsource;
916 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
917 icalsource.push_back(cameracalibration->processedspectra.at(
"source").at(sourcelabel_raw).at(iw));
918 icalsource.at(1).x += 1;
919 std::string sourcelable =
"Cal_source_" + sourcelabel_raw;
920 sourcelabels.push_back(sourcelable);
921 context->setGlobalData(sourcelable.c_str(), icalsource);
924 std::vector<vec2> icalcamera(2);
925 icalcamera.at(0).y = 1;
926 icalcamera.at(1).y = 1;
927 icalcamera.at(0).x = wavelengths.at(iw);
928 icalcamera.at(1).x = wavelengths.at(iw) + 1;
929 std::string camlable =
"Cal_cameraresponse";
930 context->setGlobalData(camlable.c_str(), icalcamera);
932 for (
auto objectpair: cameracalibration->processedspectra.at(
"object")) {
933 std::vector<vec2> spectrum_obj;
934 spectrum_obj.push_back(objectpair.second.at(iw));
935 spectrum_obj.push_back(objectpair.second.at(iw));
936 spectrum_obj.at(1).x += 1;
937 context->setGlobalData(objectpair.first.c_str(), spectrum_obj);
944 for (std::string sourcelabel_raw: sourcelabels_raw) {
958 std::vector<float> camera_data;
959 std::string global_data_label =
"camera_" + cameralabel +
"_" + wavelengthlabel;
960 context->getGlobalData(global_data_label.c_str(), camera_data);
962 std::vector<uint> pixel_labels;
963 std::string global_data_label_UUID =
"camera_" + cameralabel +
"_pixel_UUID";
964 context->getGlobalData(global_data_label_UUID.c_str(), pixel_labels);
966 for (
uint j = 0; j < calibratecamera.resolution.
y; j++) {
967 for (
uint i = 0; i < calibratecamera.resolution.
x; i++) {
968 float icdata = camera_data.at(j * calibratecamera.resolution.
x + i);
970 uint UUID = pixel_labels.at(j * calibratecamera.resolution.
x + i) - 1;
971 if (find(UUIDs_target.begin(), UUIDs_target.end(), UUID) != UUIDs_target.end()) {
972 if (simulatedcolorboardspectra.at(UUID).empty()) {
973 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
974 }
else if (simulatedcolorboardspectra.at(UUID).back().x == wavelengths.at(iw)) {
975 simulatedcolorboardspectra.at(UUID).back().y += icdata / float(numberwavelengths);
976 }
else if (simulatedcolorboardspectra.at(UUID).back().x != wavelengths.at(iw)) {
977 simulatedcolorboardspectra.at(UUID).push_back(
make_vec2(wavelengths.at(iw), icdata /
float(numberwavelengths)));
987 for (
uint UUID: UUIDs_colorbd) {
988 std::string colorboardspectra;
989 context->getPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra);
990 context->setPrimitiveData(UUID,
"reflectivity_spectrum", colorboardspectra +
"_raw");
995 float fluxscale,
float diffusefactor,
uint scatteringdepth) {
997 float sources_fluxsum = 0;
998 std::vector<float> sources_fluxes;
999 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
1000 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
1004 sources_fluxsum += sources_fluxes.at(ID);
1009 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1016 if (bandlabels.size() > 1) {
1017 for (
int iband = 1; iband < bandlabels.size(); iband++) {
1019 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1026 for (
int iband = 0; iband < bandlabels.size(); iband++) {
1034void 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,
1035 helios::vec2 wavelengthrange,
float fluxscale,
float diffusefactor,
uint scatteringdepth) {
1037 float sources_fluxsum = 0;
1038 std::vector<float> sources_fluxes;
1039 for (
uint ID = 0; ID < sourcelabels.size(); ID++) {
1040 std::vector<vec2> Source_spectrum = loadSpectralData(sourcelabels.at(ID).c_str());
1044 sources_fluxsum += sources_fluxes.at(ID);
1049 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1056 if (bandlabels.size() > 1) {
1057 for (
int iband = 1; iband < bandlabels.size(); iband++) {
1059 for (
uint ID = 0; ID < radiation_sources.size(); ID++) {
1066 for (
int ic = 0; ic < cameralabels.size(); ic++) {
1067 for (
int iband = 0; iband < bandlabels.size(); iband++) {
1077float 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,
1078 const std::vector<std::vector<float>> &truevalues) {
1083 cameraproperties.
HFOV = calibratecamera.HFOV_degrees;
1087 cameraproperties.
lens_diameter = calibratecamera.lens_diameter;
1089 cameraproperties.
exposure = calibratecamera.exposure;
1090 cameraproperties.
shutter_speed = calibratecamera.shutter_speed;
1092 std::string cameralabel = orginalcameralabel +
"Scale";
1104 if (cameras.find(cameralabel) == cameras.end()) {
1105 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
1109 std::vector<uint> camera_UUIDs;
1110 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1111 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1112 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1114 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1115 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1116 int2 camera_resolution = cameras.at(cameralabel).resolution;
1118 std::string frame_str;
1120 frame_str = std::to_string(frame);
1123 std::string output_path = image_path;
1125 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1127 helios_runtime_error(
"ERROR(RadiationModel::writePrimitiveDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
1130 std::ostringstream outfile;
1131 outfile << output_path;
1134 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1136 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1140 std::ofstream pixel_data(outfile.str());
1142 if (!pixel_data.is_open()) {
1143 helios_runtime_error(
"ERROR (RadiationModel::writePrimitiveDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1146 bool empty_flag =
true;
1148 for (
uint j = 0; j < camera_resolution.y; j++) {
1149 for (
uint i = 0; i < camera_resolution.x; i++) {
1150 uint ii = camera_resolution.x - i - 1;
1151 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1152 if (
context->doesPrimitiveExist(UUID) &&
context->doesPrimitiveDataExist(UUID, primitive_data_label.c_str())) {
1156 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
1157 pixel_data << labeldata <<
" ";
1161 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
1162 pixel_data << labeldata <<
" ";
1166 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
1167 pixel_data << labeldata <<
" ";
1171 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata);
1172 pixel_data << labeldata <<
" ";
1175 pixel_data << padvalue <<
" ";
1178 pixel_data << padvalue <<
" ";
1186 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;
1192 if (cameras.find(cameralabel) == cameras.end()) {
1193 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Camera '" + cameralabel +
"' does not exist.");
1197 std::vector<uint> camera_UUIDs;
1198 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1199 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1200 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1202 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1203 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1204 int2 camera_resolution = cameras.at(cameralabel).resolution;
1206 std::string frame_str;
1208 frame_str = std::to_string(frame);
1211 std::string output_path = image_path;
1213 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1215 helios_runtime_error(
"ERROR(RadiationModel::writeObjectDataLabelMap): Expected a directory path but got a file path for argument 'image_path'.");
1218 std::ostringstream outfile;
1219 outfile << output_path;
1222 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1224 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1228 std::ofstream pixel_data(outfile.str());
1230 if (!pixel_data.is_open()) {
1231 helios_runtime_error(
"ERROR (RadiationModel::writeObjectDataLabelMap): Could not open file '" + outfile.str() +
"' for writing.");
1234 bool empty_flag =
true;
1236 for (
uint j = 0; j < camera_resolution.y; j++) {
1237 for (
uint i = 0; i < camera_resolution.x; i++) {
1238 uint ii = camera_resolution.x - i - 1;
1239 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1240 if (!
context->doesPrimitiveExist(UUID)) {
1241 pixel_data << padvalue <<
" ";
1244 uint objID =
context->getPrimitiveParentObjectID(UUID);
1245 if (
context->doesObjectExist(objID) &&
context->doesObjectDataExist(objID, object_data_label.c_str())) {
1249 context->getObjectData(objID, object_data_label.c_str(), labeldata);
1250 pixel_data << labeldata <<
" ";
1254 context->getObjectData(objID, object_data_label.c_str(), labeldata);
1255 pixel_data << labeldata <<
" ";
1259 context->getObjectData(objID, object_data_label.c_str(), labeldata);
1260 pixel_data << labeldata <<
" ";
1264 context->getObjectData(objID, object_data_label.c_str(), labeldata);
1265 pixel_data << labeldata <<
" ";
1268 pixel_data << padvalue <<
" ";
1271 pixel_data << padvalue <<
" ";
1279 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;
1285 if (cameras.find(cameralabel) == cameras.end()) {
1286 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Camera '" + cameralabel +
"' does not exist.");
1289 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1290 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1291 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1293 std::vector<float> camera_depth;
1294 context->getGlobalData(global_data_label.c_str(), camera_depth);
1295 helios::vec3 camera_position = cameras.at(cameralabel).position;
1296 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1298 int2 camera_resolution = cameras.at(cameralabel).resolution;
1300 std::string frame_str;
1302 frame_str = std::to_string(frame);
1305 std::string output_path = image_path;
1307 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1309 helios_runtime_error(
"ERROR(RadiationModel::writeDepthImageData): Expected a directory path but got a file path for argument 'image_path'.");
1312 std::ostringstream outfile;
1313 outfile << output_path;
1316 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1318 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1322 std::ofstream pixel_data(outfile.str());
1324 if (!pixel_data.is_open()) {
1325 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageData): Could not open file '" + outfile.str() +
"' for writing.");
1328 for (
int j = 0; j < camera_resolution.y; j++) {
1329 for (
int i = camera_resolution.x - 1; i >= 0; i--) {
1330 pixel_data << camera_depth.at(j * camera_resolution.x + i) <<
" ";
1340 if (cameras.find(cameralabel) == cameras.end()) {
1341 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Camera '" + cameralabel +
"' does not exist.");
1344 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1345 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1346 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1348 std::vector<float> camera_depth;
1349 context->getGlobalData(global_data_label.c_str(), camera_depth);
1351 int2 camera_resolution = cameras.at(cameralabel).resolution;
1353 std::string output_path = image_path;
1355 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1357 helios_runtime_error(
"ERROR (RadiationModel::writeDepthImageDataEXR): Expected a directory path but got a file path for argument 'image_path'.");
1360 std::ostringstream outfile;
1361 outfile << output_path;
1363 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame <<
".exr";
1365 outfile << cameralabel <<
"_" << imagefile_base <<
".exr";
1369 std::vector<float> flipped_data(camera_resolution.x * camera_resolution.y);
1370 for (
int j = 0; j < camera_resolution.y; j++) {
1371 for (
int i = 0; i < camera_resolution.x; i++) {
1372 int ii = camera_resolution.x - i - 1;
1373 flipped_data[j * camera_resolution.x + i] = camera_depth[j * camera_resolution.x + ii];
1377 helios::writeEXR(outfile.str(), camera_resolution.x, camera_resolution.y, flipped_data);
1382 if (cameras.find(cameralabel) == cameras.end()) {
1383 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Camera '" + cameralabel +
"' does not exist.");
1386 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_depth";
1387 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1388 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Depth data for camera '" + cameralabel +
"' does not exist. Was the radiation model run for the camera?");
1390 std::vector<float> camera_depth;
1391 context->getGlobalData(global_data_label.c_str(), camera_depth);
1392 helios::vec3 camera_position = cameras.at(cameralabel).position;
1393 helios::vec3 camera_lookat = cameras.at(cameralabel).lookat;
1395 int2 camera_resolution = cameras.at(cameralabel).resolution;
1397 std::string frame_str;
1399 frame_str = std::to_string(frame);
1402 std::string output_path = image_path;
1404 helios_runtime_error(
"ERROR (RadiationModel::writeNormDepthImage): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1406 helios_runtime_error(
"ERROR(RadiationModel::writeNormDepthImage): Expected a directory path but got a file path for argument 'image_path'.");
1409 std::ostringstream outfile;
1410 outfile << output_path;
1413 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".jpeg";
1415 outfile << cameralabel <<
"_" << imagefile_base <<
".jpeg";
1418 float min_depth = 99999;
1419 for (
int i = 0; i < camera_depth.size(); i++) {
1420 if (camera_depth.at(i) < 0 || camera_depth.at(i) > max_depth) {
1421 camera_depth.at(i) = max_depth;
1423 if (camera_depth.at(i) < min_depth) {
1424 min_depth = camera_depth.at(i);
1427 for (
int i = 0; i < camera_depth.size(); i++) {
1428 camera_depth.at(i) = 1.f - (camera_depth.at(i) - min_depth) / (max_depth - min_depth);
1431 std::vector<RGBcolor> pixel_data(camera_resolution.x * camera_resolution.y);
1434 for (
uint j = 0; j < camera_resolution.y; j++) {
1435 for (
uint i = 0; i < camera_resolution.x; i++) {
1437 float c = camera_depth.at(j * camera_resolution.x + i);
1440 uint ii = camera_resolution.x - i - 1;
1441 uint jj = camera_resolution.y - j - 1;
1442 pixel_data.at(jj * camera_resolution.x + ii) = pixel_color;
1446 writeJPEG(outfile.str(), camera_resolution.x, camera_resolution.y, pixel_data);
1450void 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) {
1452 if (cameras.find(cameralabel) == cameras.end()) {
1453 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1457 std::vector<uint> camera_UUIDs;
1458 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1459 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1460 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1462 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1463 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1464 int2 camera_resolution = cameras.at(cameralabel).resolution;
1466 std::string frame_str;
1468 frame_str = std::to_string(frame);
1471 std::string output_path = image_path;
1473 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1475 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1478 std::ostringstream outfile;
1479 outfile << output_path;
1482 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1484 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1488 std::ofstream label_file;
1489 if (append_label_file) {
1490 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1492 label_file.open(outfile.str());
1495 if (!label_file.is_open()) {
1496 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open file '" + outfile.str() +
"'.");
1499 std::map<int, vec4> pdata_bounds;
1501 for (
int j = 0; j < camera_resolution.y; j++) {
1502 for (
int i = 0; i < camera_resolution.x; i++) {
1503 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + i) - 1;
1504 if (
context->doesPrimitiveExist(UUID) &&
context->doesPrimitiveDataExist(UUID, primitive_data_label.c_str())) {
1511 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_ui);
1512 labeldata = labeldata_ui;
1515 context->getPrimitiveData(UUID, primitive_data_label.c_str(), labeldata_i);
1516 labeldata = (
uint) labeldata_i;
1521 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1522 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1525 if (i < pdata_bounds[labeldata].x) {
1526 pdata_bounds[labeldata].x = i;
1528 if (i > pdata_bounds[labeldata].y) {
1529 pdata_bounds[labeldata].y = i;
1531 if (j < pdata_bounds[labeldata].z) {
1532 pdata_bounds[labeldata].z = j;
1534 if (j > pdata_bounds[labeldata].w) {
1535 pdata_bounds[labeldata].w = j;
1541 for (
auto box: pdata_bounds) {
1542 vec4 bbox = box.second;
1543 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1546 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
1547 << (bbox.
y - bbox.
x) /
float(camera_resolution.x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.y) << std::endl;
1556 if (cameras.find(cameralabel) == cameras.end()) {
1557 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1561 std::vector<uint> camera_UUIDs;
1562 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1563 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1564 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1566 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1567 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1568 int2 camera_resolution = cameras.at(cameralabel).resolution;
1570 std::string frame_str;
1572 frame_str = std::to_string(frame);
1575 std::string output_path = image_path;
1577 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1579 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1582 std::ostringstream outfile;
1583 outfile << output_path;
1586 outfile << cameralabel <<
"_" << imagefile_base <<
"_" << std::setw(5) << std::setfill(
'0') << frame_str <<
".txt";
1588 outfile << cameralabel <<
"_" << imagefile_base <<
".txt";
1592 std::ofstream label_file;
1593 if (append_label_file) {
1594 label_file.open(outfile.str(), std::ios::out | std::ios::app);
1596 label_file.open(outfile.str());
1599 if (!label_file.is_open()) {
1600 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open file '" + outfile.str() +
"'.");
1603 std::map<int, vec4> pdata_bounds;
1605 for (
int j = 0; j < camera_resolution.y; j++) {
1606 for (
int i = 0; i < camera_resolution.x; i++) {
1607 uint ii = camera_resolution.x - i - 1;
1608 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1610 if (!
context->doesPrimitiveExist(UUID)) {
1614 uint objID =
context->getPrimitiveParentObjectID(UUID);
1616 if (!
context->doesObjectExist(objID) || !
context->doesObjectDataExist(objID, object_data_label.c_str())) {
1625 context->getObjectData(objID, object_data_label.c_str(), labeldata_ui);
1626 labeldata = labeldata_ui;
1629 context->getObjectData(objID, object_data_label.c_str(), labeldata_i);
1630 labeldata = (
uint) labeldata_i;
1635 if (pdata_bounds.find(labeldata) == pdata_bounds.end()) {
1636 pdata_bounds[labeldata] =
make_vec4(1e6, -1, 1e6, -1);
1639 if (i < pdata_bounds[labeldata].x) {
1640 pdata_bounds[labeldata].x = i;
1642 if (i > pdata_bounds[labeldata].y) {
1643 pdata_bounds[labeldata].y = i;
1645 if (j < pdata_bounds[labeldata].z) {
1646 pdata_bounds[labeldata].z = j;
1648 if (j > pdata_bounds[labeldata].w) {
1649 pdata_bounds[labeldata].w = j;
1654 for (
auto box: pdata_bounds) {
1655 vec4 bbox = box.second;
1656 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1659 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
1660 << (bbox.
y - bbox.
x) /
float(camera_resolution.x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.y) << std::endl;
1666void 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) {
1667 writeImageBoundingBoxes(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1670void 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,
1671 const std::string &image_path) {
1673 if (cameras.find(cameralabel) == cameras.end()) {
1674 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Camera '" + cameralabel +
"' does not exist.");
1677 if (primitive_data_label.size() != object_class_ID.size()) {
1678 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
1682 std::vector<uint> camera_UUIDs;
1683 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1684 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1685 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1687 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1688 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1689 int2 camera_resolution = cameras.at(cameralabel).resolution;
1691 std::string output_path = image_path;
1693 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1695 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes): Expected a directory path but got a file path for argument 'image_path'.");
1698 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1700 std::ofstream label_file(outfile_txt);
1702 if (!label_file.is_open()) {
1703 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output bounding box file '" + outfile_txt +
"'.");
1707 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1710 for (
int j = 0; j < camera_resolution.y; j++) {
1711 for (
int i = 0; i < camera_resolution.x; i++) {
1712 uint ii = camera_resolution.x - i - 1;
1713 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1715 if (
context->doesPrimitiveExist(UUID)) {
1717 for (
size_t label_idx = 0; label_idx < primitive_data_label.size(); label_idx++) {
1718 const std::string &data_label = primitive_data_label[label_idx];
1719 uint class_id = object_class_ID[label_idx];
1721 if (
context->doesPrimitiveDataExist(UUID, data_label.c_str())) {
1723 bool has_data =
false;
1728 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_ui);
1729 labeldata = labeldata_ui;
1733 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_i);
1734 labeldata = (
uint) labeldata_i;
1739 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1741 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1742 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1745 if (i < pdata_bounds[key].x) {
1746 pdata_bounds[key].x = i;
1748 if (i > pdata_bounds[key].y) {
1749 pdata_bounds[key].y = i;
1751 if (j < pdata_bounds[key].z) {
1752 pdata_bounds[key].z = j;
1754 if (j > pdata_bounds[key].w) {
1755 pdata_bounds[key].w = j;
1764 for (
auto box: pdata_bounds) {
1765 uint class_id = box.first.first;
1766 vec4 bbox = box.second;
1767 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1770 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
1771 << (bbox.
y - bbox.
x) /
float(camera_resolution.x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.y) << std::endl;
1776 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1777 if (!classes_txt_stream.is_open()) {
1778 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes): Could not open output classes file '" + output_path + classes_txt_file +
".");
1780 for (
int i = 0; i < object_class_ID.size(); i++) {
1781 classes_txt_stream << object_class_ID.at(i) <<
" " << primitive_data_label.at(i) << std::endl;
1783 classes_txt_stream.close();
1787 const std::string &image_path) {
1788 writeImageBoundingBoxes_ObjectData(cameralabel, std::vector<std::string>{object_data_label}, std::vector<uint>{object_class_ID}, image_file, classes_txt_file, image_path);
1792 const std::string &image_path) {
1794 if (cameras.find(cameralabel) == cameras.end()) {
1795 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Camera '" + cameralabel +
"' does not exist.");
1798 if (object_data_label.size() != object_class_ID.size()) {
1799 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
1803 std::vector<uint> camera_UUIDs;
1804 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
1805 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
1806 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
1808 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
1809 std::vector<uint> pixel_UUIDs = camera_UUIDs;
1810 int2 camera_resolution = cameras.at(cameralabel).resolution;
1812 std::string output_path = image_path;
1814 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Invalid image output directory '" + image_path +
"'. Check that the path exists and that you have write permission.");
1816 helios_runtime_error(
"ERROR(RadiationModel::writeImageBoundingBoxes_ObjectData): Expected a directory path but got a file path for argument 'image_path'.");
1819 std::string outfile_txt = output_path + std::filesystem::path(image_file).stem().string() +
".txt";
1821 std::ofstream label_file(outfile_txt);
1823 if (!label_file.is_open()) {
1824 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output bounding box file '" + outfile_txt +
"'.");
1828 std::map<std::pair<uint, uint>,
vec4> pdata_bounds;
1832 for (
int j = 0; j < camera_resolution.y; j++) {
1833 for (
int i = 0; i < camera_resolution.x; i++) {
1834 uint ii = camera_resolution.x - i - 1;
1835 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
1837 if (!
context->doesPrimitiveExist(UUID)) {
1841 uint objID =
context->getPrimitiveParentObjectID(UUID);
1843 if (!
context->doesObjectExist(objID)) {
1848 for (
size_t label_idx = 0; label_idx < object_data_label.size(); label_idx++) {
1849 const std::string &data_label = object_data_label[label_idx];
1850 uint class_id = object_class_ID[label_idx];
1852 if (
context->doesObjectDataExist(objID, data_label.c_str())) {
1854 bool has_data =
false;
1859 context->getObjectData(objID, data_label.c_str(), labeldata_ui);
1860 labeldata = labeldata_ui;
1864 context->getObjectData(objID, data_label.c_str(), labeldata_i);
1865 labeldata = (
uint) labeldata_i;
1870 std::pair<uint, uint> key = std::make_pair(class_id, labeldata);
1872 if (pdata_bounds.find(key) == pdata_bounds.end()) {
1873 pdata_bounds[key] =
make_vec4(1e6, -1, 1e6, -1);
1876 if (i < pdata_bounds[key].x) {
1877 pdata_bounds[key].x = i;
1879 if (i > pdata_bounds[key].y) {
1880 pdata_bounds[key].y = i;
1882 if (j < pdata_bounds[key].z) {
1883 pdata_bounds[key].z = j;
1885 if (j > pdata_bounds[key].w) {
1886 pdata_bounds[key].w = j;
1894 for (
auto box: pdata_bounds) {
1895 uint class_id = box.first.first;
1896 vec4 bbox = box.second;
1897 if (bbox.
x == bbox.
y || bbox.
z == bbox.
w) {
1900 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
1901 << (bbox.
y - bbox.
x) /
float(camera_resolution.x) <<
" " << (bbox.
w - bbox.
z) /
float(camera_resolution.y) << std::endl;
1906 std::ofstream classes_txt_stream(output_path + classes_txt_file);
1907 if (!classes_txt_stream.is_open()) {
1908 helios_runtime_error(
"ERROR (RadiationModel::writeImageBoundingBoxes_ObjectData): Could not open output classes file '" + output_path + classes_txt_file +
".");
1910 for (
int i = 0; i < object_class_ID.size(); i++) {
1911 classes_txt_stream << object_class_ID.at(i) <<
" " << object_data_label.at(i) << std::endl;
1913 classes_txt_stream.close();
1917std::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) {
1918 nlohmann::json coco_json;
1922 std::ifstream existing_file(filename);
1923 if (existing_file.is_open()) {
1925 existing_file >> coco_json;
1926 }
catch (
const std::exception &e) {
1929 existing_file.close();
1934 if (coco_json.empty()) {
1935 coco_json[
"categories"] = nlohmann::json::array();
1936 coco_json[
"images"] = nlohmann::json::array();
1937 coco_json[
"annotations"] = nlohmann::json::array();
1941 std::filesystem::path image_path_obj(image_file);
1942 std::string filename_only = image_path_obj.filename().string();
1945 bool image_exists =
false;
1946 for (
const auto &img: coco_json[
"images"]) {
1947 if (img[
"file_name"] == filename_only) {
1948 image_id = img[
"id"];
1949 image_exists =
true;
1955 if (!image_exists) {
1957 int max_image_id = -1;
1958 for (
const auto &img: coco_json[
"images"]) {
1959 if (img[
"id"] > max_image_id) {
1960 max_image_id = img[
"id"];
1963 image_id = max_image_id + 1;
1966 nlohmann::json image_entry;
1967 image_entry[
"id"] = image_id;
1968 image_entry[
"file_name"] = filename_only;
1969 image_entry[
"height"] = camera_resolution.y;
1970 image_entry[
"width"] = camera_resolution.x;
1971 coco_json[
"images"].push_back(image_entry);
1974 return std::make_pair(coco_json, image_id);
1978nlohmann::json RadiationModel::initializeCOCOJson(
const std::string &filename,
bool append_file,
const std::string &cameralabel,
const helios::int2 &camera_resolution,
const std::string &image_file) {
1979 return initializeCOCOJsonWithImageId(filename, append_file, cameralabel, camera_resolution, image_file).first;
1983void RadiationModel::addCategoryToCOCO(nlohmann::json &coco_json,
const std::vector<uint> &object_class_ID,
const std::vector<std::string> &category_name) {
1984 if (object_class_ID.size() != category_name.size()) {
1985 helios_runtime_error(
"ERROR (RadiationModel::addCategoryToCOCO): The lengths of object_class_ID and category_name vectors must be the same.");
1988 for (
size_t i = 0; i < object_class_ID.size(); ++i) {
1989 bool category_exists =
false;
1990 for (
auto &cat: coco_json[
"categories"]) {
1991 if (cat[
"id"] == object_class_ID[i]) {
1992 category_exists =
true;
1996 if (!category_exists) {
1997 nlohmann::json category;
1998 category[
"id"] = object_class_ID[i];
1999 category[
"name"] = category_name[i];
2000 category[
"supercategory"] =
"none";
2001 coco_json[
"categories"].push_back(category);
2007void RadiationModel::writeCOCOJson(
const nlohmann::json &coco_json,
const std::string &filename) {
2008 std::ofstream json_file(filename);
2009 if (!json_file.is_open()) {
2014 json_file << coco_json.dump(2) << std::endl;
2019std::map<int, std::vector<std::vector<bool>>> RadiationModel::generateLabelMasks(
const std::string &cameralabel,
const std::string &data_label,
bool use_object_data) {
2020 std::vector<uint> camera_UUIDs;
2021 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2022 context->getGlobalData(global_data_label.c_str(), camera_UUIDs);
2023 std::vector<uint> pixel_UUIDs = camera_UUIDs;
2024 int2 camera_resolution = cameras.at(cameralabel).resolution;
2026 std::map<int, std::vector<std::vector<bool>>> label_masks;
2030 for (
int j = 0; j < camera_resolution.y; j++) {
2031 for (
int i = 0; i < camera_resolution.x; i++) {
2032 uint ii = camera_resolution.x - i - 1;
2033 uint UUID = pixel_UUIDs.at(j * camera_resolution.x + ii) - 1;
2035 if (
context->doesPrimitiveExist(UUID)) {
2037 bool has_data =
false;
2039 if (use_object_data) {
2041 uint objID =
context->getPrimitiveParentObjectID(UUID);
2042 if (objID != 0 &&
context->doesObjectDataExist(objID, data_label.c_str())) {
2044 if (datatype == HELIOS_TYPE_UINT) {
2046 context->getObjectData(objID, data_label.c_str(), labeldata_ui);
2047 labeldata = labeldata_ui;
2049 }
else if (datatype == HELIOS_TYPE_INT) {
2051 context->getObjectData(objID, data_label.c_str(), labeldata_i);
2052 labeldata = (
uint) labeldata_i;
2058 if (
context->doesPrimitiveDataExist(UUID, data_label.c_str())) {
2060 if (datatype == HELIOS_TYPE_UINT) {
2062 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_ui);
2063 labeldata = labeldata_ui;
2065 }
else if (datatype == HELIOS_TYPE_INT) {
2067 context->getPrimitiveData(UUID, data_label.c_str(), labeldata_i);
2068 labeldata = (
uint) labeldata_i;
2076 if (label_masks.find(labeldata) == label_masks.end()) {
2077 label_masks[labeldata] = std::vector<std::vector<bool>>(camera_resolution.y, std::vector<bool>(camera_resolution.x,
false));
2079 label_masks[labeldata][j][i] =
true;
2089std::pair<int, int> RadiationModel::findStartingBoundaryPixel(
const std::vector<std::vector<bool>> &mask,
const helios::int2 &camera_resolution) {
2090 for (
int j = 0; j < camera_resolution.y; j++) {
2091 for (
int i = 0; i < camera_resolution.x; i++) {
2094 for (
int di = -1; di <= 1; di++) {
2095 for (
int dj = -1; dj <= 1; dj++) {
2096 if (di == 0 && dj == 0)
2100 if (ni < 0 || ni >= camera_resolution.x || nj < 0 || nj >= camera_resolution.y || !mask[nj][ni]) {
2112std::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) {
2113 std::vector<std::pair<int, int>> contour;
2116 int dx[] = {1, 1, 0, -1, -1, -1, 0, 1};
2117 int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
2119 int x = start_x, y = start_y;
2123 contour.push_back({x, y});
2126 int start_dir = (dir + 6) % 8;
2129 for (
int i = 0; i < 8; i++) {
2130 int check_dir = (start_dir + i) % 8;
2131 int nx = x + dx[check_dir];
2132 int ny = y + dy[check_dir];
2135 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.y && mask[ny][nx]) {
2147 }
while (!(x == start_x && y == start_y) && contour.size() < camera_resolution.x * camera_resolution.y);
2153std::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) {
2154 std::vector<std::pair<int, int>> contour;
2155 std::set<std::pair<int, int>> visited_boundary;
2158 std::queue<std::pair<int, int>> boundary_queue;
2159 boundary_queue.push({start_x, start_y});
2160 visited_boundary.insert({start_x, start_y});
2162 while (!boundary_queue.empty()) {
2163 auto [x, y] = boundary_queue.front();
2164 boundary_queue.pop();
2165 contour.push_back({x, y});
2168 for (
int di = -1; di <= 1; di++) {
2169 for (
int dj = -1; dj <= 1; dj++) {
2170 if (di == 0 && dj == 0)
2175 if (nx >= 0 && nx < camera_resolution.x && ny >= 0 && ny < camera_resolution.y && mask[ny][nx] && visited_boundary.find({nx, ny}) == visited_boundary.end()) {
2178 bool is_boundary =
false;
2179 for (
int ddi = -1; ddi <= 1; ddi++) {
2180 for (
int ddj = -1; ddj <= 1; ddj++) {
2181 if (ddi == 0 && ddj == 0)
2185 if (nnx < 0 || nnx >= camera_resolution.x || nny < 0 || nny >= camera_resolution.y || !mask[nny][nnx]) {
2195 boundary_queue.push({nx, ny});
2196 visited_boundary.insert({nx, ny});
2207std::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) {
2208 std::vector<std::map<std::string, std::vector<float>>> annotations;
2209 int annotation_id = 0;
2211 for (
const auto &label_pair: label_masks) {
2212 int label_value = label_pair.first;
2213 const auto &mask = label_pair.second;
2216 std::vector<std::vector<bool>> visited(camera_resolution.y, std::vector<bool>(camera_resolution.x,
false));
2219 for (
int j = 0; j < camera_resolution.y; j++) {
2220 for (
int i = 0; i < camera_resolution.x; i++) {
2221 if (mask[j][i] && !visited[j][i]) {
2223 int boundary_i = i, boundary_j = j;
2224 bool is_boundary =
false;
2227 for (
int di = -1; di <= 1; di++) {
2228 for (
int dj = -1; dj <= 1; dj++) {
2231 if (ni < 0 || ni >= camera_resolution.x || nj < 0 || nj >= camera_resolution.y || !mask[nj][ni]) {
2244 std::stack<std::pair<int, int>> stack;
2245 std::vector<std::pair<int, int>> component_pixels;
2247 visited[j][i] =
true;
2249 int min_x = i, max_x = i, min_y = j, max_y = j;
2252 while (!stack.empty()) {
2253 auto [ci, cj] = stack.top();
2256 component_pixels.push_back({ci, cj});
2258 min_x = std::min(min_x, ci);
2259 max_x = std::max(max_x, ci);
2260 min_y = std::min(min_y, cj);
2261 max_y = std::max(max_y, 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 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2280 bool is_boundary_start =
false;
2282 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2283 is_boundary_start =
true;
2286 if (is_boundary_start) {
2288 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2291 if (contour.size() < 10) {
2292 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2295 if (contour.size() >= 3) {
2297 std::map<std::string, std::vector<float>> annotation;
2298 annotation[
"id"] = {(float) annotation_id++};
2299 annotation[
"image_id"] = {(float) image_id};
2300 annotation[
"category_id"] = {(float) object_class_ID};
2301 annotation[
"bbox"] = {(float) min_x, (
float) min_y, (float) (max_x - min_x), (float) (max_y - min_y)};
2302 annotation[
"area"] = {(float) area};
2303 annotation[
"iscrowd"] = {0.0f};
2306 std::vector<float> segmentation;
2307 for (
const auto &point: contour) {
2308 segmentation.push_back((
float) point.first);
2309 segmentation.push_back((
float) point.second);
2311 annotation[
"segmentation"] = segmentation;
2313 annotations.push_back(annotation);
2326 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2327 writeImageSegmentationMasks(cameralabel, std::vector<std::string>{primitive_data_label}, std::vector<uint>{object_class_ID}, json_filename, image_file, data_attribute_labels, append_file);
2330void 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,
2331 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2333 if (cameras.find(cameralabel) == cameras.end()) {
2334 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Camera '" + cameralabel +
"' does not exist.");
2337 if (primitive_data_label.size() != object_class_ID.size()) {
2338 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): The lengths of primitive_data_label and object_class_ID vectors must be the same.");
2342 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2343 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
2344 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2348 std::vector<std::string> all_primitive_data =
context->listAllPrimitiveDataLabels();
2350 for (
const auto &data_label: primitive_data_label) {
2351 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), data_label) == all_primitive_data.end()) {
2352 missing_label_warnings.
addWarning(
"missing_primitive_data_label",
"Primitive data label '" + data_label +
"' does not exist in the context.");
2355 missing_label_warnings.
report(std::cerr);
2358 if (!std::filesystem::exists(image_file)) {
2359 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks): Image file '" + image_file +
"' does not exist.");
2363 std::string validated_json_filename = json_filename;
2364 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2365 validated_json_filename +=
".json";
2369 std::string outfile = validated_json_filename;
2372 int2 camera_resolution = cameras.at(cameralabel).resolution;
2373 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2374 nlohmann::json coco_json = coco_json_pair.first;
2375 int image_id = coco_json_pair.second;
2376 addCategoryToCOCO(coco_json, object_class_ID, primitive_data_label);
2379 struct AttributeInfo {
2381 bool is_primitive_data;
2384 std::vector<AttributeInfo> attribute_info;
2386 if (!data_attribute_labels.empty()) {
2387 std::vector<std::string> all_primitive_data =
context->listAllPrimitiveDataLabels();
2388 std::vector<std::string> all_object_data =
context->listAllObjectDataLabels();
2390 for (
const auto &attr_label: data_attribute_labels) {
2392 info.label = attr_label;
2393 info.exists =
false;
2395 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2396 info.is_primitive_data =
true;
2398 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2399 info.is_primitive_data =
false;
2404 attribute_info.push_back(info);
2409 bool use_attributes = !attribute_info.empty();
2412 std::vector<uint> pixel_UUIDs;
2413 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2414 context->getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2417 for (
size_t i = 0; i < primitive_data_label.size(); ++i) {
2419 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, primitive_data_label[i],
false);
2422 std::vector<std::map<std::string, std::vector<float>>> annotations = generateAnnotationsFromMasks(label_masks, object_class_ID[i], camera_resolution, image_id);
2425 std::vector<std::map<std::string, double>> mean_attribute_values_per_component;
2426 if (use_attributes) {
2428 for (
const auto &label_pair: label_masks) {
2429 const auto &mask = label_pair.second;
2430 std::vector<std::vector<bool>> visited(camera_resolution.y, std::vector<bool>(camera_resolution.x,
false));
2432 for (
int j = 0; j < camera_resolution.y; j++) {
2433 for (
int i_px = 0; i_px < camera_resolution.x; i_px++) {
2434 if (mask[j][i_px] && !visited[j][i_px]) {
2436 std::stack<std::pair<int, int>> stack;
2437 std::vector<std::pair<int, int>> component_pixels;
2438 stack.push({i_px, j});
2439 visited[j][i_px] =
true;
2441 while (!stack.empty()) {
2442 auto [ci, cj] = stack.top();
2444 component_pixels.push_back({ci, cj});
2447 for (
int di = -1; di <= 1; di++) {
2448 for (
int dj = -1; dj <= 1; dj++) {
2449 if (abs(di) + abs(dj) != 1)
2453 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.y && mask[nj][ni] && !visited[nj][ni]) {
2454 stack.push({ni, nj});
2455 visited[nj][ni] =
true;
2462 std::map<std::string, double> component_attributes;
2463 for (
const auto &attr: attribute_info) {
2467 for (
const auto &[px_i, px_j]: component_pixels) {
2468 uint ii = camera_resolution.x - px_i - 1;
2469 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.x + ii) - 1;
2471 if (
context->doesPrimitiveExist(UUID)) {
2473 bool has_value =
false;
2475 if (attr.is_primitive_data) {
2476 if (
context->doesPrimitiveDataExist(UUID, attr.label.c_str())) {
2480 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2481 value =
static_cast<double>(val);
2485 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2486 value =
static_cast<double>(val);
2490 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2491 value =
static_cast<double>(val);
2494 context->getPrimitiveData(UUID, attr.label.c_str(), value);
2499 uint objID =
context->getPrimitiveParentObjectID(UUID);
2500 if (objID != 0 &&
context->doesObjectDataExist(objID, attr.label.c_str())) {
2504 context->getObjectData(objID, attr.label.c_str(), val);
2505 value =
static_cast<double>(val);
2509 context->getObjectData(objID, attr.label.c_str(), val);
2510 value =
static_cast<double>(val);
2514 context->getObjectData(objID, attr.label.c_str(), val);
2515 value =
static_cast<double>(val);
2518 context->getObjectData(objID, attr.label.c_str(), value);
2532 component_attributes[attr.label] =
sum / count;
2534 component_attributes[attr.label] = 0.0;
2538 mean_attribute_values_per_component.push_back(component_attributes);
2546 int max_annotation_id = -1;
2547 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2548 if (existing_ann[
"id"] > max_annotation_id) {
2549 max_annotation_id = existing_ann[
"id"];
2555 for (
const auto &ann: annotations) {
2556 nlohmann::json json_annotation;
2557 json_annotation[
"id"] = max_annotation_id + 1;
2558 json_annotation[
"image_id"] = (int) ann.at(
"image_id")[0];
2559 json_annotation[
"category_id"] = (int) ann.at(
"category_id")[0];
2561 const auto &bbox = ann.at(
"bbox");
2562 json_annotation[
"bbox"] = {(int) bbox[0], (
int) bbox[1], (int) bbox[2], (
int) bbox[3]};
2563 json_annotation[
"area"] = (int) ann.at(
"area")[0];
2565 const auto &seg = ann.at(
"segmentation");
2566 std::vector<int> segmentation_coords;
2567 for (
float coord: seg) {
2568 segmentation_coords.push_back((
int) coord);
2570 json_annotation[
"segmentation"] = {segmentation_coords};
2571 json_annotation[
"iscrowd"] = (int) ann.at(
"iscrowd")[0];
2574 if (use_attributes && ann_idx < mean_attribute_values_per_component.size()) {
2575 json_annotation[
"attributes"] = mean_attribute_values_per_component[ann_idx];
2578 coco_json[
"annotations"].push_back(json_annotation);
2579 max_annotation_id++;
2585 writeCOCOJson(coco_json, outfile);
2589 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2590 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);
2594 const std::vector<std::string> &data_attribute_labels,
bool append_file) {
2596 if (cameras.find(cameralabel) == cameras.end()) {
2597 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Camera '" + cameralabel +
"' does not exist.");
2600 if (object_data_label.size() != object_class_ID.size()) {
2601 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): The lengths of object_data_label and object_class_ID vectors must be the same.");
2605 std::string global_data_label =
"camera_" + cameralabel +
"_pixel_UUID";
2606 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
2607 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Pixel labels for camera '" + cameralabel +
"' do not exist. Was the radiation model run to generate labels?");
2611 std::vector<std::string> all_object_data =
context->listAllObjectDataLabels();
2613 for (
const auto &data_label: object_data_label) {
2614 if (std::find(all_object_data.begin(), all_object_data.end(), data_label) == all_object_data.end()) {
2615 missing_label_warnings.
addWarning(
"missing_object_data_label",
"Object data label '" + data_label +
"' does not exist in the context.");
2618 missing_label_warnings.
report(std::cerr);
2621 if (!std::filesystem::exists(image_file)) {
2622 helios_runtime_error(
"ERROR (RadiationModel::writeImageSegmentationMasks_ObjectData): Image file '" + image_file +
"' does not exist.");
2626 std::string validated_json_filename = json_filename;
2627 if (validated_json_filename.length() < 5 || validated_json_filename.substr(validated_json_filename.length() - 5) !=
".json") {
2628 validated_json_filename +=
".json";
2632 std::string outfile = validated_json_filename;
2635 int2 camera_resolution = cameras.at(cameralabel).resolution;
2636 auto coco_json_pair = initializeCOCOJsonWithImageId(outfile, append_file, cameralabel, camera_resolution, image_file);
2637 nlohmann::json coco_json = coco_json_pair.first;
2638 int image_id = coco_json_pair.second;
2639 addCategoryToCOCO(coco_json, object_class_ID, object_data_label);
2642 struct AttributeInfo {
2644 bool is_primitive_data;
2647 std::vector<AttributeInfo> attribute_info;
2649 if (!data_attribute_labels.empty()) {
2650 std::vector<std::string> all_primitive_data =
context->listAllPrimitiveDataLabels();
2651 std::vector<std::string> all_object_data =
context->listAllObjectDataLabels();
2653 for (
const auto &attr_label: data_attribute_labels) {
2655 info.label = attr_label;
2656 info.exists =
false;
2658 if (std::find(all_primitive_data.begin(), all_primitive_data.end(), attr_label) != all_primitive_data.end()) {
2659 info.is_primitive_data =
true;
2661 }
else if (std::find(all_object_data.begin(), all_object_data.end(), attr_label) != all_object_data.end()) {
2662 info.is_primitive_data =
false;
2667 attribute_info.push_back(info);
2672 bool use_attributes = !attribute_info.empty();
2675 std::vector<uint> pixel_UUIDs;
2676 std::string pixel_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2677 context->getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
2680 for (
size_t i = 0; i < object_data_label.size(); ++i) {
2682 std::map<int, std::vector<std::vector<bool>>> label_masks = generateLabelMasks(cameralabel, object_data_label[i],
true);
2685 int max_annotation_id = -1;
2686 for (
const auto &existing_ann: coco_json[
"annotations"]) {
2687 if (existing_ann[
"id"] > max_annotation_id) {
2688 max_annotation_id = existing_ann[
"id"];
2694 for (
const auto &label_pair: label_masks) {
2695 const auto &mask = label_pair.second;
2698 std::vector<std::vector<bool>> visited(camera_resolution.y, std::vector<bool>(camera_resolution.x,
false));
2701 for (
int j = 0; j < camera_resolution.y; j++) {
2702 for (
int i_px = 0; i_px < camera_resolution.x; i_px++) {
2703 if (mask[j][i_px] && !visited[j][i_px]) {
2705 int boundary_i = i_px, boundary_j = j;
2706 bool is_boundary =
false;
2709 for (
int di = -1; di <= 1; di++) {
2710 for (
int dj = -1; dj <= 1; dj++) {
2713 if (ni < 0 || ni >= camera_resolution.x || nj < 0 || nj >= camera_resolution.y || !mask[nj][ni]) {
2726 std::stack<std::pair<int, int>> stack;
2727 std::vector<std::pair<int, int>> component_pixels;
2728 stack.push({i_px, j});
2729 visited[j][i_px] =
true;
2731 int min_x = i_px, max_x = i_px, min_y = j, max_y = j;
2734 while (!stack.empty()) {
2735 auto [ci, cj] = stack.top();
2738 component_pixels.push_back({ci, cj});
2740 min_x = std::min(min_x, ci);
2741 max_x = std::max(max_x, ci);
2742 min_y = std::min(min_y, cj);
2743 max_y = std::max(max_y, cj);
2746 for (
int di = -1; di <= 1; di++) {
2747 for (
int dj = -1; dj <= 1; dj++) {
2748 if (abs(di) + abs(dj) != 1)
2752 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.y && mask[nj][ni] && !visited[nj][ni]) {
2753 stack.push({ni, nj});
2754 visited[nj][ni] =
true;
2761 auto start_pixel = findStartingBoundaryPixel(mask, camera_resolution);
2762 bool is_boundary_start =
false;
2764 if (start_pixel.first >= min_x && start_pixel.first <= max_x && start_pixel.second >= min_y && start_pixel.second <= max_y) {
2765 is_boundary_start =
true;
2768 if (is_boundary_start) {
2770 auto contour = traceBoundaryMoore(mask, start_pixel.first, start_pixel.second, camera_resolution);
2773 if (contour.size() < 10) {
2774 contour = traceBoundarySimple(mask, start_pixel.first, start_pixel.second, camera_resolution);
2777 if (contour.size() >= 3) {
2779 std::map<std::string, double> component_attributes;
2780 if (use_attributes) {
2781 for (
const auto &attr: attribute_info) {
2785 for (
const auto &[px_i, px_j]: component_pixels) {
2786 uint ii = camera_resolution.x - px_i - 1;
2787 uint UUID = pixel_UUIDs.at(px_j * camera_resolution.x + ii) - 1;
2789 if (
context->doesPrimitiveExist(UUID)) {
2791 bool has_value =
false;
2793 if (attr.is_primitive_data) {
2794 if (
context->doesPrimitiveDataExist(UUID, attr.label.c_str())) {
2798 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2799 value =
static_cast<double>(val);
2803 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2804 value =
static_cast<double>(val);
2808 context->getPrimitiveData(UUID, attr.label.c_str(), val);
2809 value =
static_cast<double>(val);
2812 context->getPrimitiveData(UUID, attr.label.c_str(), value);
2817 uint objID =
context->getPrimitiveParentObjectID(UUID);
2818 if (objID != 0 &&
context->doesObjectDataExist(objID, attr.label.c_str())) {
2822 context->getObjectData(objID, attr.label.c_str(), val);
2823 value =
static_cast<double>(val);
2827 context->getObjectData(objID, attr.label.c_str(), val);
2828 value =
static_cast<double>(val);
2832 context->getObjectData(objID, attr.label.c_str(), val);
2833 value =
static_cast<double>(val);
2836 context->getObjectData(objID, attr.label.c_str(), value);
2850 component_attributes[attr.label] =
sum / count;
2852 component_attributes[attr.label] = 0.0;
2858 nlohmann::json json_annotation;
2859 json_annotation[
"id"] = max_annotation_id + 1;
2860 json_annotation[
"image_id"] = image_id;
2861 json_annotation[
"category_id"] = (int) object_class_ID[i];
2862 json_annotation[
"bbox"] = {min_x, min_y, max_x - min_x, max_y - min_y};
2863 json_annotation[
"area"] = area;
2864 json_annotation[
"iscrowd"] = 0;
2867 std::vector<int> segmentation_coords;
2868 for (
const auto &point: contour) {
2869 segmentation_coords.push_back(point.first);
2870 segmentation_coords.push_back(point.second);
2872 json_annotation[
"segmentation"] = {segmentation_coords};
2875 if (use_attributes) {
2876 json_annotation[
"attributes"] = component_attributes;
2879 coco_json[
"annotations"].push_back(json_annotation);
2880 max_annotation_id++;
2885 std::stack<std::pair<int, int>> stack;
2886 stack.push({i_px, j});
2887 visited[j][i_px] =
true;
2889 while (!stack.empty()) {
2890 auto [ci, cj] = stack.top();
2894 for (
int di = -1; di <= 1; di++) {
2895 for (
int dj = -1; dj <= 1; dj++) {
2896 if (abs(di) + abs(dj) != 1)
2900 if (ni >= 0 && ni < camera_resolution.x && nj >= 0 && nj < camera_resolution.y && mask[nj][ni] && !visited[nj][ni]) {
2901 stack.push({ni, nj});
2902 visited[nj][ni] =
true;
2915 writeCOCOJson(coco_json, outfile);
2919 for (
uint b = 0; b < bandlabels.size(); b++) {
2920 std::string bandlabel = bandlabels.at(b);
2922 std::string image_value_label =
"camera_" + cameralabel +
"_" + bandlabel;
2923 std::vector<float> cameradata;
2924 context->getGlobalData(image_value_label.c_str(), cameradata);
2926 std::vector<uint> camera_UUIDs;
2927 std::string image_UUID_label =
"camera_" + cameralabel +
"_pixel_UUID";
2928 context->getGlobalData(image_UUID_label.c_str(), camera_UUIDs);
2930 for (
uint i = 0; i < cameradata.size(); i++) {
2931 uint UUID = camera_UUIDs.at(i) - 1;
2932 if (!
context->doesPrimitiveExist(UUID)) {
2933 cameradata.at(i) = padvalues.at(b);
2936 context->setGlobalData(image_value_label.c_str(), cameradata);
2940void 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,
2941 const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2943 if (cameras.find(originalcameralabel) == cameras.end()) {
2944 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2945 }
else if (radiation_sources.empty()) {
2946 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2950 if (!calibration_flag) {
2951 std::cout <<
"No color board added, use default color calibration." << std::endl;
2952 cameracalibration = &cameracalibration_;
2960 std::vector<std::string> cameraresplabels_cal(cameraresplabels_raw.size());
2962 for (
int iband = 0; iband < bandlabels.size(); iband++) {
2963 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + cameraresplabels_raw.at(iband);
2972 std::cout <<
"Camera response scale: " << camerascale << std::endl;
2977void RadiationModel::calibrateCamera(
const std::string &originalcameralabel,
const float scalefactor,
const std::vector<std::vector<float>> &truevalues,
const std::string &calibratedmark) {
2979 if (cameras.find(originalcameralabel) == cameras.end()) {
2980 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): Camera " + originalcameralabel +
" does not exist.");
2981 }
else if (radiation_sources.empty()) {
2982 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): No radiation sources were added to the radiation model. Cannot perform calibration.");
2986 if (!calibration_flag) {
2987 std::cout <<
"No color board added, use default color calibration." << std::endl;
2991 RadiationModel::setCameraCalibration(&cameracalibration_);
2996 std::vector<std::string> bandlabels = cameras.at(originalcameralabel).band_labels;
2999 std::vector<std::string> cameraresplabels_cal(cameras.at(originalcameralabel).band_spectral_response.size());
3000 std::vector<std::string> cameraresplabels_raw = cameraresplabels_cal;
3003 for (
auto &band: cameras.at(originalcameralabel).band_spectral_response) {
3004 cameraresplabels_raw.at(iband) = band.second;
3005 cameraresplabels_cal.at(iband) = calibratedmark +
"_" + band.second;
3010 std::vector<std::string> sourcelabels(radiation_sources.size());
3012 for (
auto &source: radiation_sources) {
3013 if (source.source_spectrum.empty()) {
3014 helios_runtime_error(
"ERROR (RadiationModel::calibrateCamera): A spectral distribution was not specified for source " + source.source_spectrum_label +
". Cannot perform camera calibration.");
3016 sourcelabels.at(isource) = source.source_spectrum_label;
3027 std::cout <<
"Camera response scale: " << camerascale << std::endl;
3032std::vector<helios::vec2> RadiationModel::generateGaussianCameraResponse(
float FWHM,
float mu,
float centrawavelength,
const helios::int2 &wavebandrange) {
3035 float sigma = FWHM / (2 * std::sqrt(2 * std::log(2)));
3037 size_t lenspectra = wavebandrange.
y - wavebandrange.
x;
3038 std::vector<helios::vec2> cameraresponse(lenspectra);
3041 for (
int i = 0; i < lenspectra; ++i) {
3042 cameraresponse.at(i).x = float(wavebandrange.
x + i);
3046 for (
size_t i = 0; i < lenspectra; ++i) {
3047 cameraresponse.at(i).y = centrawavelength * std::exp(-std::pow((cameraresponse.at(i).x - mu), 2) / (2 * std::pow(sigma, 2)));
3051 return cameraresponse;
3054void 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,
3055 float contrast_adjustment) {
3057 if (cameras.find(cameralabel) == cameras.end()) {
3058 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): Camera '" + cameralabel +
"' does not exist.");
3061 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()) {
3062 helios_runtime_error(
"ERROR (RadiationModel::applyCameraImageCorrections): One or more specified band labels do not exist for the camera pixel data.");
3066 if (camera_metadata.find(cameralabel) == camera_metadata.end()) {
3069 camera_metadata[cameralabel].image_processing.saturation_adjustment = saturation_adjustment;
3070 camera_metadata[cameralabel].image_processing.brightness_adjustment = brightness_adjustment;
3071 camera_metadata[cameralabel].image_processing.contrast_adjustment = contrast_adjustment;
3078 if (camera.lens_flare_enabled) {
3079 LensFlare lens_flare(camera.lens_flare_properties, camera.resolution);
3080 lens_flare.
apply(camera.pixel_data, camera.resolution);
3084 if (brightness_adjustment != 1.f || contrast_adjustment != 1.f) {
3085 camera.adjustBrightnessContrast(red_band_label, green_band_label, blue_band_label, brightness_adjustment, contrast_adjustment);
3089 if (saturation_adjustment != 1.f) {
3090 camera.adjustSaturation(red_band_label, green_band_label, blue_band_label, saturation_adjustment);
3094void 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,
3095 float contrast_adjustment,
float gain_adjustment) {
3096 applyCameraImageCorrections(cameralabel, red_band_label, green_band_label, blue_band_label, saturation_adjustment, brightness_adjustment, contrast_adjustment);
3101 float min_P = (std::numeric_limits<float>::max)();
3103 for (
const auto &[channel_label, data]: pixel_data) {
3104 for (
float v: data) {
3114 for (
auto &[channel_label, data]: pixel_data) {
3115 for (
float &v: data) {
3116 v = (v - min_P) / (max_P - min_P);
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::whiteBalance): One or more specified band labels do not exist for the camera pixel data.");
3129 auto &data_red = pixel_data.at(red_band_label);
3130 auto &data_green = pixel_data.at(green_band_label);
3131 auto &data_blue = pixel_data.at(blue_band_label);
3133 const std::size_t N = data_red.size();
3134 if (data_green.size() != N || data_blue.size() != N) {
3135 throw std::invalid_argument(
"All channels must have the same length");
3138 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
3145 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
3146 for (std::size_t i = 0; i < N; ++i) {
3147 acc_r += std::pow(data_red[i], p);
3148 acc_g += std::pow(data_green[i], p);
3149 acc_b += std::pow(data_blue[i], p);
3151 float mean_r_p = acc_r /
static_cast<float>(N);
3152 float mean_g_p = acc_g /
static_cast<float>(N);
3153 float mean_b_p = acc_b /
static_cast<float>(N);
3155 float M_R = std::pow(mean_r_p, 1.0f / p);
3156 float M_G = std::pow(mean_g_p, 1.0f / p);
3157 float M_B = std::pow(mean_b_p, 1.0f / p);
3160 const float eps = 1e-6f;
3161 if (M_R < eps || M_G < eps || M_B < eps) {
3162 throw std::runtime_error(
"Channel Minkowski mean too small");
3167 float M = (M_R + M_G + M_B) / 3.0f;
3178 for (std::size_t i = 0; i < N; ++i) {
3179 data_red[i] *= scale.
x;
3180 data_green[i] *= scale.
y;
3181 data_blue[i] *= scale.
z;
3188 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()) {
3189 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceGrayEdge): One or more specified band labels do not exist for the camera pixel data.");
3193 auto &data_red = pixel_data.at(red_band_label);
3194 auto &data_green = pixel_data.at(green_band_label);
3195 auto &data_blue = pixel_data.at(blue_band_label);
3197 const int width = resolution.
x;
3198 const int height = resolution.
y;
3199 const std::size_t N = width * height;
3202 throw std::invalid_argument(
"Minkowski exponent p must satisfy p >= 1");
3204 if (derivative_order < 1 || derivative_order > 2) {
3205 throw std::invalid_argument(
"Derivative order must be 1 or 2");
3209 std::vector<float> deriv_red(N, 0.0f);
3210 std::vector<float> deriv_green(N, 0.0f);
3211 std::vector<float> deriv_blue(N, 0.0f);
3213 if (derivative_order == 1) {
3215 for (
int y = 1; y < height - 1; ++y) {
3216 for (
int x = 1; x < width - 1; ++x) {
3217 int idx = y * width + x;
3220 float dx_r = (data_red[(y - 1) * width + (x + 1)] + 2 * data_red[y * width + (x + 1)] + data_red[(y + 1) * width + (x + 1)]) -
3221 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[y * width + (x - 1)] + data_red[(y + 1) * width + (x - 1)]) / 8.0f;
3222 float dy_r = (data_red[(y + 1) * width + (x - 1)] + 2 * data_red[(y + 1) * width + x] + data_red[(y + 1) * width + (x + 1)]) -
3223 (data_red[(y - 1) * width + (x - 1)] + 2 * data_red[(y - 1) * width + x] + data_red[(y - 1) * width + (x + 1)]) / 8.0f;
3224 deriv_red[idx] = std::sqrt(dx_r * dx_r + dy_r * dy_r);
3226 float dx_g = (data_green[(y - 1) * width + (x + 1)] + 2 * data_green[y * width + (x + 1)] + data_green[(y + 1) * width + (x + 1)]) -
3227 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[y * width + (x - 1)] + data_green[(y + 1) * width + (x - 1)]) / 8.0f;
3228 float dy_g = (data_green[(y + 1) * width + (x - 1)] + 2 * data_green[(y + 1) * width + x] + data_green[(y + 1) * width + (x + 1)]) -
3229 (data_green[(y - 1) * width + (x - 1)] + 2 * data_green[(y - 1) * width + x] + data_green[(y - 1) * width + (x + 1)]) / 8.0f;
3230 deriv_green[idx] = std::sqrt(dx_g * dx_g + dy_g * dy_g);
3232 float dx_b = (data_blue[(y - 1) * width + (x + 1)] + 2 * data_blue[y * width + (x + 1)] + data_blue[(y + 1) * width + (x + 1)]) -
3233 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[y * width + (x - 1)] + data_blue[(y + 1) * width + (x - 1)]) / 8.0f;
3234 float dy_b = (data_blue[(y + 1) * width + (x - 1)] + 2 * data_blue[(y + 1) * width + x] + data_blue[(y + 1) * width + (x + 1)]) -
3235 (data_blue[(y - 1) * width + (x - 1)] + 2 * data_blue[(y - 1) * width + x] + data_blue[(y - 1) * width + (x + 1)]) / 8.0f;
3236 deriv_blue[idx] = std::sqrt(dx_b * dx_b + dy_b * dy_b);
3241 for (
int y = 1; y < height - 1; ++y) {
3242 for (
int x = 1; x < width - 1; ++x) {
3243 int idx = y * width + x;
3245 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]);
3247 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]);
3249 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]);
3255 float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f;
3256 int valid_pixels = 0;
3258 for (std::size_t i = 0; i < N; ++i) {
3259 if (deriv_red[i] > 0 || deriv_green[i] > 0 || deriv_blue[i] > 0) {
3260 acc_r += std::pow(deriv_red[i], p);
3261 acc_g += std::pow(deriv_green[i], p);
3262 acc_b += std::pow(deriv_blue[i], p);
3267 if (valid_pixels == 0) {
3269 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3273 float mean_r_p = acc_r /
static_cast<float>(valid_pixels);
3274 float mean_g_p = acc_g /
static_cast<float>(valid_pixels);
3275 float mean_b_p = acc_b /
static_cast<float>(valid_pixels);
3277 float M_R = std::pow(mean_r_p, 1.0f / p);
3278 float M_G = std::pow(mean_g_p, 1.0f / p);
3279 float M_B = std::pow(mean_b_p, 1.0f / p);
3282 const float eps = 1e-6f;
3283 if (M_R < eps || M_G < eps || M_B < eps) {
3285 whiteBalance(red_band_label, green_band_label, blue_band_label, p);
3290 float M = (M_R + M_G + M_B) / 3.0f;
3299 for (std::size_t i = 0; i < N; ++i) {
3300 data_red[i] *= scale.
x;
3301 data_green[i] *= scale.
y;
3302 data_blue[i] *= scale.
z;
3309 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()) {
3310 helios_runtime_error(
"ERROR (RadiationModel::whiteBalanceWhitePatch): One or more specified band labels do not exist for the camera pixel data.");
3314 if (percentile <= 0.0f || percentile > 1.0f) {
3315 throw std::invalid_argument(
"Percentile must be in range (0, 1]");
3318 auto &data_red = pixel_data.at(red_band_label);
3319 auto &data_green = pixel_data.at(green_band_label);
3320 auto &data_blue = pixel_data.at(blue_band_label);
3322 const std::size_t N = data_red.size();
3325 std::vector<float> sorted_red = data_red;
3326 std::vector<float> sorted_green = data_green;
3327 std::vector<float> sorted_blue = data_blue;
3329 std::size_t k =
static_cast<std::size_t
>(percentile * (N - 1));
3331 std::nth_element(sorted_red.begin(), sorted_red.begin() + k, sorted_red.end());
3332 std::nth_element(sorted_green.begin(), sorted_green.begin() + k, sorted_green.end());
3333 std::nth_element(sorted_blue.begin(), sorted_blue.begin() + k, sorted_blue.end());
3335 float white_r = sorted_red[k];
3336 float white_g = sorted_green[k];
3337 float white_b = sorted_blue[k];
3340 const float eps = 1e-6f;
3341 if (white_r < eps || white_g < eps || white_b < eps) {
3342 throw std::runtime_error(
"White patch values too small");
3346 for (std::size_t i = 0; i < N; ++i) {
3347 data_red[i] /= white_r;
3348 data_green[i] /= white_g;
3349 data_blue[i] /= white_b;
3357 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()) {
3358 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): One or more specified band labels do not exist for the camera pixel data.");
3363 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()) {
3364 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Spectral response data not found for one or more bands. Ensure camera spectral responses are properly initialized.");
3368 std::string red_response_id = band_spectral_response.at(red_band_label);
3369 std::string green_response_id = band_spectral_response.at(green_band_label);
3370 std::string blue_response_id = band_spectral_response.at(blue_band_label);
3373 if (red_response_id ==
"uniform" && green_response_id ==
"uniform" && blue_response_id ==
"uniform") {
3378 std::vector<helios::vec2> red_spectrum, green_spectrum, blue_spectrum;
3380 if (red_response_id !=
"uniform" &&
context->doesGlobalDataExist(red_response_id.c_str())) {
3381 context->getGlobalData(red_response_id.c_str(), red_spectrum);
3383 if (green_response_id !=
"uniform" &&
context->doesGlobalDataExist(green_response_id.c_str())) {
3384 context->getGlobalData(green_response_id.c_str(), green_spectrum);
3386 if (blue_response_id !=
"uniform" &&
context->doesGlobalDataExist(blue_response_id.c_str())) {
3387 context->getGlobalData(blue_response_id.c_str(), blue_spectrum);
3391 if (red_spectrum.empty() || green_spectrum.empty() || blue_spectrum.empty()) {
3392 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Could not retrieve spectral response curves for all bands from global data.");
3397 float red_integrated = 0.0f, green_integrated = 0.0f, blue_integrated = 0.0f;
3399 for (
size_t i = 1; i < red_spectrum.size(); ++i) {
3400 float dw = red_spectrum[i].x - red_spectrum[i - 1].x;
3401 red_integrated += 0.5f * (red_spectrum[i].y + red_spectrum[i - 1].y) * dw;
3403 for (
size_t i = 1; i < green_spectrum.size(); ++i) {
3404 float dw = green_spectrum[i].x - green_spectrum[i - 1].x;
3405 green_integrated += 0.5f * (green_spectrum[i].y + green_spectrum[i - 1].y) * dw;
3407 for (
size_t i = 1; i < blue_spectrum.size(); ++i) {
3408 float dw = blue_spectrum[i].x - blue_spectrum[i - 1].x;
3409 blue_integrated += 0.5f * (blue_spectrum[i].y + blue_spectrum[i - 1].y) * dw;
3413 if (red_integrated <= 0 || green_integrated <= 0 || blue_integrated <= 0) {
3414 helios_runtime_error(
"ERROR (RadiationCamera::whiteBalanceSpectral): Invalid integrated spectral response (non-positive value). Check spectral response data.");
3421 float max_integrated = std::max({red_integrated, green_integrated, blue_integrated});
3424 white_balance_factors.
x = max_integrated / red_integrated;
3425 white_balance_factors.y = max_integrated / green_integrated;
3426 white_balance_factors.z = max_integrated / blue_integrated;
3430 auto &data_red = pixel_data.at(red_band_label);
3431 auto &data_green = pixel_data.at(green_band_label);
3432 auto &data_blue = pixel_data.at(blue_band_label);
3434 const std::size_t N = data_red.size();
3435 for (std::size_t i = 0; i < N; ++i) {
3436 data_red[i] *= white_balance_factors.x;
3437 data_green[i] *= white_balance_factors.y;
3438 data_blue[i] *= white_balance_factors.z;
3445 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()) {
3446 helios_runtime_error(
"ERROR (RadiationModel::reinhardToneMapping): One or more specified band labels do not exist for the camera pixel data.");
3450 const std::size_t N = resolution.
x * resolution.
y;
3451 constexpr float eps = 1e-6f;
3453 auto &data_red = pixel_data.at(red_band_label);
3454 auto &data_green = pixel_data.at(green_band_label);
3455 auto &data_blue = pixel_data.at(blue_band_label);
3456 for (std::size_t i = 0; i < N; ++i) {
3457 float R = data_red[i], G = data_green[i], B = data_blue[i];
3458 float L = luminance(
R, G, B);
3459 float s = (L > eps) ? (L / (1.0f + L)) / L : 0.0f;
3461 data_red[i] =
R * s;
3462 data_green[i] = G * s;
3463 data_blue[i] = B * s;
3467void RadiationCamera::applyGain(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float percentile) {
3470 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()) {
3471 helios_runtime_error(
"ERROR (RadiationModel::applyGain): One or more specified band labels do not exist for the camera pixel data.");
3475 const std::size_t N = resolution.
x * resolution.
y;
3477 auto &data_red = pixel_data.at(red_band_label);
3478 auto &data_green = pixel_data.at(green_band_label);
3479 auto &data_blue = pixel_data.at(blue_band_label);
3481 std::vector<float> luminance_pixel;
3482 luminance_pixel.reserve(N);
3483 for (std::size_t i = 0; i < N; ++i) {
3484 luminance_pixel.push_back(luminance(data_red[i], data_green[i], data_blue[i]));
3487 std::size_t k = std::size_t(percentile * (luminance_pixel.size() - 1));
3488 std::nth_element(luminance_pixel.begin(), luminance_pixel.begin() + k, luminance_pixel.end());
3489 float peak = luminance_pixel[k];
3490 float gain = (peak > 0.0f) ? 1.0f / peak : 1.0f;
3492 for (
auto &[channel, data]: pixel_data) {
3493 for (
float &v: data) {
3502 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()) {
3503 helios_runtime_error(
"ERROR (RadiationModel::globalHistogramEquilization): One or more specified band labels do not exist for the camera pixel data.");
3507 const size_t N = resolution.
x * resolution.
y;
3508 const float eps = 1e-6f;
3510 auto &data_red = pixel_data.at(red_band_label);
3511 auto &data_green = pixel_data.at(green_band_label);
3512 auto &data_blue = pixel_data.at(blue_band_label);
3515 std::vector<float> lum(N);
3516 std::vector<float> chroma_r(N), chroma_g(N), chroma_b(N);
3518 for (
size_t i = 0; i < N; ++i) {
3519 vec3 p(data_red[i], data_green[i], data_blue[i]);
3520 lum[i] = 0.2126f * p.
x + 0.7152f * p.
y + 0.0722f * p.
z;
3524 chroma_r[i] = p.
x / lum[i];
3525 chroma_g[i] = p.
y / lum[i];
3526 chroma_b[i] = p.
z / lum[i];
3536 std::vector<int> hist(B, 0);
3537 for (
float v: lum) {
3538 int b = int(std::clamp(v, 0.0f, 1.0f - eps) * B);
3539 if (b >= 0 && b < 2048) {
3543 std::vector<float> cdf(B);
3545 for (
int b = 0; b < B; ++b) {
3547 cdf[b] = float(acc) / float(N);
3551 for (
size_t i = 0; i < N; ++i) {
3553 if (lum[i] >= 1.0f) {
3554 data_red[i] = std::min(1.0f, data_red[i]);
3555 data_green[i] = std::min(1.0f, data_green[i]);
3556 data_blue[i] = std::min(1.0f, data_blue[i]);
3560 int b = int(std::clamp(lum[i], 0.0f, 1.0f - eps) * B);
3562 if (b < 0 || b >= 2048) {
3566 constexpr float k = 0.2f;
3567 constexpr float cs = 0.2f;
3570 float Ynew = (1.0f - k) * lum[i] + k * Yeq;
3573 float t = Ynew - 0.5f;
3574 Ynew = 0.5f + t * (1.0f + cs - 2.0f * cs * std::fabs(t));
3577 data_red[i] = Ynew * chroma_r[i];
3578 data_green[i] = Ynew * chroma_g[i];
3579 data_blue[i] = Ynew * chroma_b[i];
3583void 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) {
3585 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()) {
3586 helios_runtime_error(
"ERROR (RadiationModel::adjustSBC): One or more specified band labels do not exist for the camera pixel data.");
3590 constexpr float kRedW = 0.2126f;
3591 constexpr float kGreenW = 0.7152f;
3592 constexpr float kBlueW = 0.0722f;
3594 const size_t N = resolution.
x * resolution.
y;
3596 auto &data_red = pixel_data.at(red_band_label);
3597 auto &data_green = pixel_data.at(green_band_label);
3598 auto &data_blue = pixel_data.at(blue_band_label);
3600 for (
int i = 0; i < N; ++i) {
3602 helios::vec3 p(data_red[i], data_green[i], data_blue[i]);
3605 float Y = kRedW * p.
x + kGreenW * p.
y + kBlueW * p.
z;
3617 data_red[i] =
clamp(p.
x, 0.0f, 1.0f);
3618 data_green[i] =
clamp(p.
y, 0.0f, 1.0f);
3619 data_blue[i] =
clamp(p.
z, 0.0f, 1.0f);
3640 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()) {
3641 helios_runtime_error(
"ERROR (RadiationModel::gammaCompress): One or more specified band labels do not exist for the camera pixel data.");
3645 for (
float &v: pixel_data.at(red_band_label)) {
3648 for (
float &v: pixel_data.at(green_band_label)) {
3651 for (
float &v: pixel_data.at(blue_band_label)) {
3658void RadiationCamera::autoExposure(
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
float gain_multiplier) {
3660 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()) {
3661 helios_runtime_error(
"ERROR (RadiationModel::autoExposure): One or more specified band labels do not exist for the camera pixel data.");
3665 auto &data_red = pixel_data.at(red_band_label);
3666 auto &data_green = pixel_data.at(green_band_label);
3667 auto &data_blue = pixel_data.at(blue_band_label);
3669 const std::size_t N = data_red.size();
3672 std::vector<float> luminance_values(N);
3673 for (std::size_t i = 0; i < N; ++i) {
3674 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3678 std::vector<float> sorted_luminance = luminance_values;
3679 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3682 std::size_t p95_idx =
static_cast<std::size_t
>(0.95f * (N - 1));
3683 float p95_luminance = sorted_luminance[p95_idx];
3686 std::size_t median_idx = N / 2;
3687 float median_luminance = sorted_luminance[median_idx];
3691 float target_median = 0.18f;
3692 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3698 float final_gain = auto_gain * gain_multiplier;
3701 for (std::size_t i = 0; i < N; ++i) {
3702 data_red[i] *= final_gain;
3703 data_green[i] *= final_gain;
3704 data_blue[i] *= final_gain;
3710 if (pixel_data.empty()) {
3715 for (
const auto &band: band_labels) {
3716 if (pixel_data.find(band) == pixel_data.end()) {
3722 std::string exposure_mode = exposure;
3725 if (exposure_mode ==
"manual") {
3730 if (exposure_mode ==
"auto") {
3732 std::string cam_type;
3733 if (!camera_type.empty()) {
3734 cam_type = camera_type;
3737 cam_type = (band_labels.size() >= 3) ?
"rgb" :
"spectral";
3740 if (cam_type ==
"thermal") {
3743 }
else if (cam_type ==
"rgb" && band_labels.size() >= 3) {
3747 std::string red_band, green_band, blue_band;
3748 for (
const auto &band: band_labels) {
3749 if (band.find(
"red") != std::string::npos || band.find(
"Red") != std::string::npos || band.find(
"RED") != std::string::npos) {
3751 }
else if (band.find(
"green") != std::string::npos || band.find(
"Green") != std::string::npos || band.find(
"GREEN") != std::string::npos) {
3753 }
else if (band.find(
"blue") != std::string::npos || band.find(
"Blue") != std::string::npos || band.find(
"BLUE") != std::string::npos) {
3759 if (red_band.empty())
3760 red_band = band_labels[0];
3761 if (green_band.empty())
3762 green_band = band_labels[1];
3763 if (blue_band.empty())
3764 blue_band = band_labels[2];
3766 auto &data_red = pixel_data.at(red_band);
3767 auto &data_green = pixel_data.at(green_band);
3768 auto &data_blue = pixel_data.at(blue_band);
3770 const std::size_t N = data_red.size();
3773 std::vector<float> luminance_values(N);
3774 for (std::size_t i = 0; i < N; ++i) {
3775 luminance_values[i] = luminance(data_red[i], data_green[i], data_blue[i]);
3779 std::vector<float> sorted_luminance = luminance_values;
3780 std::sort(sorted_luminance.begin(), sorted_luminance.end());
3782 std::size_t median_idx = N / 2;
3783 float median_luminance = sorted_luminance[median_idx];
3786 float target_median = 0.18f;
3787 float auto_gain = target_median / std::max(median_luminance, 1e-6f);
3791 for (
auto &band_pair: pixel_data) {
3792 auto &data = band_pair.second;
3793 for (std::size_t i = 0; i < N; ++i) {
3794 data[i] *= auto_gain;
3798 }
else if (cam_type ==
"spectral") {
3821 for (
auto &band_pair: pixel_data) {
3822 auto &data = band_pair.second;
3823 const std::size_t N = data.size();
3825 std::vector<float> sorted_data = data;
3826 std::sort(sorted_data.begin(), sorted_data.end());
3828 const float peak_value = sorted_data.back();
3832 const float signal_floor = 1e-4f * peak_value;
3833 const std::size_t first_signal =
static_cast<std::size_t
>(std::upper_bound(sorted_data.begin(), sorted_data.end(), signal_floor) - sorted_data.begin());
3836 if (first_signal >= N) {
3838 p95_value = peak_value;
3840 const std::size_t signal_count = N - first_signal;
3841 const std::size_t p95_idx = first_signal +
static_cast<std::size_t
>(0.95f * (signal_count - 1));
3842 p95_value = sorted_data[p95_idx];
3849 const float target_p95 = 0.7f;
3850 const float band_gain = target_p95 / std::max(p95_value, 1e-6f);
3853 for (std::size_t i = 0; i < N; ++i) {
3854 data[i] *= band_gain;
3858 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown camera_type '" + cam_type +
"'. Must be 'rgb', 'spectral', or 'thermal'.");
3864 if (exposure_mode.substr(0, 3) ==
"ISO" || exposure_mode.substr(0, 3) ==
"iso") {
3868 iso_value = std::stoi(exposure_mode.substr(3));
3870 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Invalid ISO format '" + exposure_mode +
"'. Expected format: 'ISOXXX' (e.g., 'ISO100').");
3873 if (iso_value <= 0) {
3874 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): ISO value must be positive. Got: " + std::to_string(iso_value));
3878 if (lens_focal_length <= 0) {
3879 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) +
3880 ". Either set it explicitly or use 'auto' or 'manual' exposure mode.");
3885 float f_number = lens_focal_length / std::max(lens_diameter, 1e-6f);
3888 const float ref_iso = 100.0f;
3889 const float ref_shutter = 1.0f / 125.0f;
3890 const float ref_f_number = 2.8f;
3895 const float typical_scene_median = 10.0f;
3896 const float target_median = 0.0675f;
3900 float exposure = (float(iso_value) * shutter_speed) / (f_number * f_number);
3901 float ref_exposure = (ref_iso * ref_shutter) / (ref_f_number * ref_f_number);
3907 float ref_gain = target_median / typical_scene_median;
3908 float calibration_factor = ref_gain / ref_exposure;
3911 float exposure_multiplier = exposure * calibration_factor;
3915 const std::size_t N = pixel_data.begin()->second.size();
3916 for (
auto &band_pair: pixel_data) {
3917 auto &data = band_pair.second;
3918 for (std::size_t i = 0; i < N; ++i) {
3919 data[i] *= exposure_multiplier;
3926 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraExposure): Unknown exposure mode '" + exposure_mode +
"'. Must be 'auto', 'ISOXXX' (e.g., 'ISO100'), or 'manual'.");
3931 if (pixel_data.empty()) {
3936 for (
const auto &band: band_labels) {
3937 if (pixel_data.find(band) == pixel_data.end()) {
3943 std::string wb_mode = white_balance;
3946 if (wb_mode ==
"off") {
3951 if (band_labels.size() < 3) {
3956 if (wb_mode ==
"auto") {
3959 std::string red_band = band_labels[0];
3960 std::string green_band = band_labels[1];
3961 std::string blue_band = band_labels[2];
3965 }
catch (
const std::exception &e) {
3974 helios_runtime_error(
"ERROR (RadiationCamera::applyCameraWhiteBalance): Unknown white_balance mode '" + wb_mode +
"'. Must be 'auto' or 'off'.");
3979 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()) {
3980 helios_runtime_error(
"ERROR (RadiationModel::adjustBrightnessContrast): One or more specified band labels do not exist for the camera pixel data.");
3984 auto &data_red = pixel_data.at(red_band_label);
3985 auto &data_green = pixel_data.at(green_band_label);
3986 auto &data_blue = pixel_data.at(blue_band_label);
3988 const std::size_t N = data_red.size();
3990 for (std::size_t i = 0; i < N; ++i) {
3992 float r = data_red[i] * brightness;
3993 float g = data_green[i] * brightness;
3994 float b = data_blue[i] * brightness;
3997 r = 0.5f + (r - 0.5f) * contrast;
3998 g = 0.5f + (g - 0.5f) * contrast;
3999 b = 0.5f + (b - 0.5f) * contrast;
4010 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()) {
4011 helios_runtime_error(
"ERROR (RadiationModel::adjustSaturation): One or more specified band labels do not exist for the camera pixel data.");
4015 auto &data_red = pixel_data.at(red_band_label);
4016 auto &data_green = pixel_data.at(green_band_label);
4017 auto &data_blue = pixel_data.at(blue_band_label);
4019 const std::size_t N = data_red.size();
4021 for (std::size_t i = 0; i < N; ++i) {
4022 float r = data_red[i];
4023 float g = data_green[i];
4024 float b = data_blue[i];
4027 float lum = luminance(r, g, b);
4030 data_red[i] = lum + saturation * (r - lum);
4031 data_green[i] = lum + saturation * (g - lum);
4032 data_blue[i] = lum + saturation * (b - lum);
4038std::string RadiationModel::detectLightingType()
const {
4039 if (radiation_sources.empty()) {
4043 bool has_sun =
false;
4044 bool has_artificial =
false;
4046 for (
const auto &source: radiation_sources) {
4047 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4049 }
else if (source.source_type == RADIATION_SOURCE_TYPE_SPHERE || source.source_type == RADIATION_SOURCE_TYPE_RECTANGLE || source.source_type == RADIATION_SOURCE_TYPE_DISK) {
4050 has_artificial =
true;
4054 if (has_sun && has_artificial) {
4056 }
else if (has_sun) {
4058 }
else if (has_artificial) {
4059 return "artificial";
4073 float tilt_angle_deg = -asin(direction.
z) * 180.0f /
M_PI;
4075 return tilt_angle_deg;
4080 if (cameras.find(camera_label) == cameras.end()) {
4081 helios_runtime_error(
"ERROR (RadiationModel::computeAgronomicProperties): Camera '" + camera_label +
"' does not exist.");
4084 const auto &cam = cameras.at(camera_label);
4096 std::vector<uint> pixel_UUIDs;
4097 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
4098 if (!
context->doesGlobalDataExist(pixel_UUID_label.c_str())) {
4102 context->getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
4105 std::map<std::string, std::set<int>> species_to_plantIDs;
4108 std::set<int> weed_plantIDs;
4111 std::set<int> all_plantIDs;
4114 std::map<std::string, std::map<int, float>> species_plant_heights;
4115 std::map<std::string, std::map<int, float>> species_plant_ages;
4116 std::map<std::string, std::map<int, std::string>> species_plant_stages;
4117 std::map<std::string, std::map<int, float>> species_plant_leaf_areas;
4118 std::map<std::string, std::map<int, int>> species_plant_pixel_counts;
4121 for (
uint j = 0; j < cam.resolution.y; j++) {
4122 for (
uint i = 0; i < cam.resolution.x; i++) {
4123 uint pixel_index = j * cam.resolution.x + i;
4125 if (pixel_index >= pixel_UUIDs.size()) {
4129 uint UUID_plus_one = pixel_UUIDs.at(pixel_index);
4130 if (UUID_plus_one == 0) {
4135 uint UUID = UUID_plus_one - 1;
4138 if (!
context->doesPrimitiveExist(UUID)) {
4143 uint objID =
context->getPrimitiveParentObjectID(UUID);
4150 std::string plant_name;
4151 bool has_plant_name =
false;
4152 if (
context->doesObjectDataExist(objID,
"plant_name")) {
4154 if (datatype == HELIOS_TYPE_STRING) {
4155 context->getObjectData(objID,
"plant_name", plant_name);
4156 has_plant_name =
true;
4162 bool has_plantID =
false;
4163 if (
context->doesObjectDataExist(objID,
"plantID")) {
4165 if (datatype == HELIOS_TYPE_INT) {
4166 context->getObjectData(objID,
"plantID", plantID);
4168 }
else if (datatype == HELIOS_TYPE_UINT) {
4170 context->getObjectData(objID,
"plantID", plantID_uint);
4171 plantID =
static_cast<int>(plantID_uint);
4177 std::string plant_type;
4178 bool has_plant_type =
false;
4179 if (
context->doesObjectDataExist(objID,
"plant_type")) {
4181 if (datatype == HELIOS_TYPE_STRING) {
4182 context->getObjectData(objID,
"plant_type", plant_type);
4183 has_plant_type =
true;
4188 float plant_height = 0.0f;
4189 bool has_plant_height =
false;
4190 if (
context->doesObjectDataExist(objID,
"plant_height")) {
4192 if (datatype == HELIOS_TYPE_FLOAT) {
4193 context->getObjectData(objID,
"plant_height", plant_height);
4194 has_plant_height =
true;
4200 bool has_age =
false;
4201 if (
context->doesObjectDataExist(objID,
"age")) {
4203 if (datatype == HELIOS_TYPE_FLOAT) {
4204 context->getObjectData(objID,
"age", age);
4210 std::string phenology_stage;
4211 bool has_phenology_stage =
false;
4212 if (
context->doesObjectDataExist(objID,
"phenology_stage")) {
4214 if (datatype == HELIOS_TYPE_STRING) {
4215 context->getObjectData(objID,
"phenology_stage", phenology_stage);
4216 has_phenology_stage =
true;
4221 float primitive_area =
context->getPrimitiveArea(UUID);
4224 if (has_plant_name && has_plantID) {
4226 species_to_plantIDs[plant_name].insert(plantID);
4229 all_plantIDs.insert(plantID);
4232 if (has_plant_type && plant_type ==
"weed") {
4233 weed_plantIDs.insert(plantID);
4237 if (has_plant_height) {
4238 species_plant_heights[plant_name][plantID] = plant_height;
4241 species_plant_ages[plant_name][plantID] = age;
4243 if (has_phenology_stage) {
4244 species_plant_stages[plant_name][plantID] = phenology_stage;
4248 species_plant_leaf_areas[plant_name][plantID] += primitive_area;
4251 species_plant_pixel_counts[plant_name][plantID]++;
4257 if (species_to_plantIDs.empty()) {
4262 for (
const auto &species_pair: species_to_plantIDs) {
4264 props.
plant_count.push_back(
static_cast<int>(species_pair.second.size()));
4268 for (
const auto &species_pair: species_to_plantIDs) {
4269 const std::string &species = species_pair.first;
4270 const std::set<int> &plantIDs = species_pair.second;
4273 if (species_plant_heights.find(species) != species_plant_heights.end()) {
4274 float total_weighted_height = 0.0f;
4275 int total_pixels = 0;
4276 for (
int plantID: plantIDs) {
4277 if (species_plant_heights.at(species).find(plantID) != species_plant_heights.at(species).end()) {
4278 float height = species_plant_heights.at(species).at(plantID);
4279 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4280 total_weighted_height += height *
static_cast<float>(pixel_count);
4281 total_pixels += pixel_count;
4284 if (total_pixels > 0) {
4285 props.
plant_height_m.push_back(total_weighted_height /
static_cast<float>(total_pixels));
4294 if (species_plant_ages.find(species) != species_plant_ages.end()) {
4295 float total_weighted_age = 0.0f;
4296 int total_pixels = 0;
4297 for (
int plantID: plantIDs) {
4298 if (species_plant_ages.at(species).find(plantID) != species_plant_ages.at(species).end()) {
4299 float age = species_plant_ages.at(species).at(plantID);
4300 int pixel_count = species_plant_pixel_counts.at(species).at(plantID);
4301 total_weighted_age += age *
static_cast<float>(pixel_count);
4302 total_pixels += pixel_count;
4305 if (total_pixels > 0) {
4306 props.
plant_age_days.push_back(total_weighted_age /
static_cast<float>(total_pixels));
4315 if (species_plant_stages.find(species) != species_plant_stages.end()) {
4316 std::map<std::string, int> stage_counts;
4317 for (
int plantID: plantIDs) {
4318 if (species_plant_stages.at(species).find(plantID) != species_plant_stages.at(species).end()) {
4319 std::string stage = species_plant_stages.at(species).at(plantID);
4320 stage_counts[stage]++;
4324 std::string mode_stage;
4326 for (
const auto &stage_pair: stage_counts) {
4327 if (stage_pair.second > max_count) {
4328 max_count = stage_pair.second;
4329 mode_stage = stage_pair.first;
4338 if (species_plant_leaf_areas.find(species) != species_plant_leaf_areas.end()) {
4339 float total_leaf_area = 0.0f;
4340 for (
int plantID: plantIDs) {
4341 if (species_plant_leaf_areas.at(species).find(plantID) != species_plant_leaf_areas.at(species).end()) {
4342 total_leaf_area += species_plant_leaf_areas.at(species).at(plantID);
4352 if (!all_plantIDs.empty()) {
4353 float weed_fraction =
static_cast<float>(weed_plantIDs.size()) /
static_cast<float>(all_plantIDs.size());
4354 float weed_percentage = weed_fraction * 100.0f;
4356 if (weed_percentage <= 20.0f) {
4358 }
else if (weed_percentage <= 40.0f) {
4366void RadiationModel::populateCameraMetadata(
const std::string &camera_label,
CameraMetadata &metadata)
const {
4368 if (cameras.find(camera_label) == cameras.end()) {
4369 helios_runtime_error(
"ERROR (RadiationModel::populateCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4372 const auto &cam = cameras.at(camera_label);
4375 metadata.camera_properties.
width = cam.resolution.x;
4376 metadata.camera_properties.
height = cam.resolution.y;
4377 metadata.camera_properties.
channels =
static_cast<int>(cam.band_labels.size());
4378 metadata.camera_properties.
type = cam.camera_type;
4382 metadata.camera_properties.
sensor_width = cam.sensor_width_mm;
4385 float VFOV_degrees = cam.HFOV_degrees / cam.FOV_aspect_ratio;
4388 metadata.camera_properties.
sensor_height = cam.sensor_width_mm / cam.FOV_aspect_ratio;
4397 float HFOV_rad = cam.HFOV_degrees *
M_PI / 180.0f;
4398 float optical_focal_length_mm = cam.sensor_width_mm / (2.0f * tan(HFOV_rad / 2.0f));
4399 metadata.camera_properties.
focal_length = optical_focal_length_mm;
4402 if (cam.lens_diameter > 0) {
4403 float lens_diameter_mm = cam.lens_diameter * 1000.0f;
4404 float f_number = optical_focal_length_mm / lens_diameter_mm;
4405 std::ostringstream aperture_str;
4406 aperture_str <<
"f/" << std::fixed << std::setprecision(1) << f_number;
4407 metadata.camera_properties.
aperture = aperture_str.str();
4409 metadata.camera_properties.
aperture =
"pinhole";
4413 metadata.camera_properties.
model = cam.model;
4416 metadata.camera_properties.
lens_make = cam.lens_make;
4417 metadata.camera_properties.
lens_model = cam.lens_model;
4421 metadata.camera_properties.
exposure = cam.exposure;
4422 metadata.camera_properties.
shutter_speed = cam.shutter_speed;
4425 metadata.camera_properties.
white_balance = cam.white_balance;
4428 metadata.camera_properties.
camera_zoom = cam.camera_zoom;
4440 std::ostringstream date_str;
4441 date_str << date.year <<
"-" << std::setw(2) << std::setfill(
'0') << date.month <<
"-" << std::setw(2) << std::setfill(
'0') << date.day;
4442 metadata.acquisition_properties.
date = date_str.str();
4445 std::ostringstream time_str;
4446 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;
4447 metadata.acquisition_properties.
time = time_str.str();
4451 metadata.acquisition_properties.
camera_angle_deg = calculateCameraTiltAngle(cam.position, cam.lookat);
4452 metadata.acquisition_properties.
light_source = detectLightingType();
4455 computeAgronomicProperties(camera_label, metadata.agronomic_properties);
4463 if (cameras.find(camera_label) == cameras.end()) {
4464 helios_runtime_error(
"ERROR (RadiationModel::enableCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4469 if (camera_metadata.find(camera_label) != camera_metadata.end()) {
4470 saved_image_processing = camera_metadata.at(camera_label).image_processing;
4475 populateCameraMetadata(camera_label, metadata);
4478 metadata.image_processing = saved_image_processing;
4481 camera_metadata[camera_label] = metadata;
4482 metadata_enabled_cameras.insert(camera_label);
4487 for (
const auto &camera_label: camera_labels) {
4494 if (cameras.find(camera_label) == cameras.end()) {
4495 helios_runtime_error(
"ERROR (RadiationModel::getCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4500 populateCameraMetadata(camera_label, metadata);
4507 if (cameras.find(camera_label) == cameras.end()) {
4508 helios_runtime_error(
"ERROR (RadiationModel::setCameraMetadata): Camera '" + camera_label +
"' does not exist.");
4511 camera_metadata[camera_label] = metadata;
4514std::string RadiationModel::writeCameraMetadataFile(
const std::string &camera_label,
const std::string &output_path)
const {
4516 if (camera_metadata.find(camera_label) == camera_metadata.end()) {
4517 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): No metadata set for camera '" + camera_label +
"'.");
4520 const auto &metadata = camera_metadata.at(camera_label);
4524 auto format_float = [](
float value,
int decimals) ->
double {
4525 std::ostringstream oss;
4526 oss << std::fixed << std::setprecision(decimals) << value;
4527 return std::stod(oss.str());
4532 j[
"path"] = metadata.
path;
4534 j[
"camera_properties"][
"height"] = metadata.camera_properties.
height;
4535 j[
"camera_properties"][
"width"] = metadata.camera_properties.
width;
4536 j[
"camera_properties"][
"channels"] = metadata.camera_properties.
channels;
4537 j[
"camera_properties"][
"type"] = metadata.camera_properties.
type;
4538 j[
"camera_properties"][
"focal_length"] = format_float(metadata.camera_properties.
focal_length, 2);
4539 j[
"camera_properties"][
"aperture"] = metadata.camera_properties.
aperture;
4540 j[
"camera_properties"][
"sensor_width"] = format_float(metadata.camera_properties.
sensor_width, 2);
4541 j[
"camera_properties"][
"sensor_height"] = format_float(metadata.camera_properties.
sensor_height, 2);
4542 j[
"camera_properties"][
"model"] = metadata.camera_properties.
model;
4545 if (!metadata.camera_properties.
lens_make.empty()) {
4546 j[
"camera_properties"][
"lens_make"] = metadata.camera_properties.
lens_make;
4548 if (!metadata.camera_properties.
lens_model.empty()) {
4549 j[
"camera_properties"][
"lens_model"] = metadata.camera_properties.
lens_model;
4552 j[
"camera_properties"][
"lens_specification"] = metadata.camera_properties.
lens_specification;
4556 j[
"camera_properties"][
"exposure"] = metadata.camera_properties.
exposure;
4557 j[
"camera_properties"][
"shutter_speed"] = format_float(metadata.camera_properties.
shutter_speed, 6);
4560 j[
"camera_properties"][
"white_balance"] = metadata.camera_properties.
white_balance;
4563 j[
"camera_properties"][
"zoom"] = format_float(metadata.camera_properties.
camera_zoom, 2);
4565 j[
"location_properties"][
"latitude"] = format_float(metadata.location_properties.
latitude, 6);
4566 j[
"location_properties"][
"longitude"] = format_float(metadata.location_properties.
longitude, 6);
4568 j[
"acquisition_properties"][
"date"] = metadata.acquisition_properties.
date;
4569 j[
"acquisition_properties"][
"time"] = metadata.acquisition_properties.
time;
4570 j[
"acquisition_properties"][
"UTC_offset"] = format_float(metadata.acquisition_properties.
UTC_offset, 1);
4571 j[
"acquisition_properties"][
"camera_height_m"] = format_float(metadata.acquisition_properties.
camera_height_m, 2);
4572 j[
"acquisition_properties"][
"camera_angle_deg"] = format_float(metadata.acquisition_properties.
camera_angle_deg, 2);
4573 j[
"acquisition_properties"][
"light_source"] = metadata.acquisition_properties.
light_source;
4576 const auto &img_proc = metadata.image_processing;
4577 j[
"image_processing"][
"exposure_gain"] = format_float(img_proc.exposure_gain, 4);
4578 j[
"image_processing"][
"white_balance_factors"] = {format_float(img_proc.white_balance_factors.x, 4),
4579 format_float(img_proc.white_balance_factors.y, 4),
4580 format_float(img_proc.white_balance_factors.z, 4)};
4581 j[
"image_processing"][
"saturation_adjustment"] = format_float(img_proc.saturation_adjustment, 2);
4582 j[
"image_processing"][
"brightness_adjustment"] = format_float(img_proc.brightness_adjustment, 2);
4583 j[
"image_processing"][
"contrast_adjustment"] = format_float(img_proc.contrast_adjustment, 2);
4584 j[
"image_processing"][
"color_space"] = img_proc.color_space;
4588 j[
"agronomic_properties"][
"plant_species"] = metadata.agronomic_properties.
plant_species;
4589 j[
"agronomic_properties"][
"plant_count"] = metadata.agronomic_properties.
plant_count;
4593 std::vector<double> formatted_heights;
4594 for (
float height: metadata.agronomic_properties.plant_height_m) {
4595 formatted_heights.push_back(format_float(height, 2));
4597 j[
"agronomic_properties"][
"plant_height_m"] = formatted_heights;
4601 std::vector<double> formatted_ages;
4602 for (
float age: metadata.agronomic_properties.plant_age_days) {
4603 formatted_ages.push_back(format_float(age, 1));
4605 j[
"agronomic_properties"][
"plant_age_days"] = formatted_ages;
4608 if (!metadata.agronomic_properties.
plant_stage.empty()) {
4609 j[
"agronomic_properties"][
"plant_stage"] = metadata.agronomic_properties.
plant_stage;
4612 if (!metadata.agronomic_properties.
leaf_area_m2.empty()) {
4613 std::vector<double> formatted_leaf_areas;
4614 for (
float area: metadata.agronomic_properties.leaf_area_m2) {
4615 formatted_leaf_areas.push_back(format_float(area, 4));
4617 j[
"agronomic_properties"][
"leaf_area_m2"] = formatted_leaf_areas;
4620 j[
"agronomic_properties"][
"weed_pressure"] = metadata.agronomic_properties.
weed_pressure;
4624 std::string json_filename = metadata.
path;
4625 size_t ext_pos = json_filename.find_last_of(
".");
4626 if (ext_pos != std::string::npos) {
4627 json_filename = json_filename.substr(0, ext_pos) +
".json";
4629 json_filename +=
".json";
4633 std::string json_path = output_path + json_filename;
4636 std::ofstream json_file(json_path);
4637 if (!json_file.is_open()) {
4638 helios_runtime_error(
"ERROR (RadiationModel::writeCameraMetadataFile): Failed to open file '" + json_path +
"' for writing.");
4640 json_file << j.dump(2) << std::endl;
4647 if (cameras.find(camera_label) == cameras.end()) {
4648 helios_runtime_error(
"ERROR (RadiationModel::enableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4650 cameras.at(camera_label).lens_flare_enabled =
true;
4654 if (cameras.find(camera_label) == cameras.end()) {
4655 helios_runtime_error(
"ERROR (RadiationModel::disableCameraLensFlare): Camera '" + camera_label +
"' does not exist.");
4657 cameras.at(camera_label).lens_flare_enabled =
false;
4661 if (cameras.find(camera_label) == cameras.end()) {
4662 helios_runtime_error(
"ERROR (RadiationModel::isCameraLensFlareEnabled): Camera '" + camera_label +
"' does not exist.");
4664 return cameras.at(camera_label).lens_flare_enabled;
4668 if (cameras.find(camera_label) == cameras.end()) {
4669 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4673 if (properties.aperture_blade_count < 3) {
4674 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): aperture_blade_count must be at least 3.");
4676 if (properties.coating_efficiency < 0.0f || properties.coating_efficiency > 1.0f) {
4677 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): coating_efficiency must be in range [0.0, 1.0].");
4679 if (properties.ghost_intensity < 0.0f) {
4680 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_intensity must be non-negative.");
4682 if (properties.starburst_intensity < 0.0f) {
4683 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): starburst_intensity must be non-negative.");
4685 if (properties.intensity_threshold < 0.0f || properties.intensity_threshold > 1.0f) {
4686 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): intensity_threshold must be in range [0.0, 1.0].");
4688 if (properties.ghost_count < 1) {
4689 helios_runtime_error(
"ERROR (RadiationModel::setCameraLensFlareProperties): ghost_count must be at least 1.");
4692 cameras.at(camera_label).lens_flare_properties = properties;
4696 if (cameras.find(camera_label) == cameras.end()) {
4697 helios_runtime_error(
"ERROR (RadiationModel::getCameraLensFlareProperties): Camera '" + camera_label +
"' does not exist.");
4699 return cameras.at(camera_label).lens_flare_properties;