26#include <unordered_set>
28using namespace helios;
40 directRayCount_default = 100;
41 diffuseRayCount_default = 1000;
43 diffuseFlux_default = -1.f;
45 minScatterEnergy_default = 0.1;
46 scatteringDepth_default = 0;
55 temperature_default = 300;
59 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/camera_spectral_library.xml").
string());
60 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/light_spectral_library.xml").
string());
61 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/soil_surface_spectral_library.xml").
string());
62 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/leaf_surface_spectral_library.xml").
string());
63 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/bark_surface_spectral_library.xml").
string());
64 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/fruit_surface_spectral_library.xml").
string());
65 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/solar_spectrum_ASTMG173.xml").
string());
66 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").
string());
67 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/DGK_DKK_colorboard.xml").
string());
71 backend->initialize();
74 std::string backend_name = backend->getBackendName();
75 std::cout <<
"Radiation model initialized with " << backend_name <<
" backend";
76 if (backend_name.find(
"Vulkan") != std::string::npos) {
77 std::cout <<
" - WARNING: radiation model may be slow depending on your GPU (NVIDIA+OptiX backend recommended)";
81 std::cout << std::endl;
90 directRayCount_default = 100;
91 diffuseRayCount_default = 1000;
92 diffuseFlux_default = -1.f;
93 minScatterEnergy_default = 0.1;
94 scatteringDepth_default = 0;
100 temperature_default = 300;
103 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/camera_spectral_library.xml").
string());
104 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/light_spectral_library.xml").
string());
105 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/soil_surface_spectral_library.xml").
string());
106 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/leaf_surface_spectral_library.xml").
string());
107 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/bark_surface_spectral_library.xml").
string());
108 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/fruit_surface_spectral_library.xml").
string());
109 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/solar_spectrum_ASTMG173.xml").
string());
110 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").
string());
111 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/DGK_DKK_colorboard.xml").
string());
120 model.backend = std::move(backend);
126 static bool checked =
false;
127 static bool available =
false;
135 const char *no_gpu = std::getenv(
"HELIOS_NO_GPU");
136 if (no_gpu && std::string(no_gpu) !=
"0") {
152 message_flag =
false;
161 return backend->getBackendName();
168 if (strcmp(label,
"reflectivity") == 0 || strcmp(label,
"transmissivity") == 0) {
169 output_prim_data.emplace_back(label);
171 std::cout <<
"WARNING (RadiationModel::optionalOutputPrimitiveData): unknown output primitive data " << label << std::endl;
177 helios_runtime_error(
"ERROR (RadiationModel::setDirectRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
179 radiation_bands.at(label).directRayCount = N;
184 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
186 radiation_bands.at(label).diffuseRayCount = N;
191 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationFlux): Cannot set flux value for band '" + label +
"' because it is not a valid band.");
193 radiation_bands.at(label).diffuseFlux = flux;
202 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationExtinctionCoeff): Cannot set diffuse extinction value for band '" + label +
"' because it is not a valid band.");
210 for (
int j = 0; j < N; j++) {
211 for (
int i = 0; i < N; i++) {
212 float theta = 0.5f *
M_PI / float(N) * (0.5f + float(i));
213 float phi = 2.f *
M_PI / float(N) * (0.5f + float(j));
218 if (psi <
M_PI / 180.f) {
219 fd = powf(
M_PI / 180.f, -K);
224 norm += fd * cosf(theta) * sinf(theta) *
M_PI / float(N * N);
229 radiation_bands.at(label).diffuseExtinction = K;
230 radiation_bands.at(label).diffusePeakDir = dir;
231 radiation_bands.at(label).diffuseDistNorm = 1.f / norm;
236 if (spectrum_integral < 0) {
237 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
238 }
else if (global_diffuse_spectrum.empty()) {
239 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
244 if (current_integral > 0) {
245 float scale_factor = spectrum_integral / current_integral;
246 for (
vec2 &wavelength: global_diffuse_spectrum) {
247 wavelength.y *= scale_factor;
252 for (
auto &band: radiation_bands) {
253 band.second.diffuse_spectrum = global_diffuse_spectrum;
256 radiativepropertiesneedupdate =
true;
261 if (spectrum_integral < 0) {
262 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
263 }
else if (global_diffuse_spectrum.empty()) {
264 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
268 float current_integral =
integrateSpectrum(global_diffuse_spectrum, wavelength1, wavelength2);
269 if (current_integral > 0) {
270 float scale_factor = spectrum_integral / current_integral;
271 for (
vec2 &wavelength: global_diffuse_spectrum) {
272 wavelength.y *= scale_factor;
277 for (
auto &band: radiation_bands) {
278 band.second.diffuse_spectrum = global_diffuse_spectrum;
281 radiativepropertiesneedupdate =
true;
286 if (spectrum_integral < 0) {
287 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
289 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
290 }
else if (radiation_bands.at(band_label).diffuse_spectrum.empty()) {
291 std::cerr <<
"WARNING (RadiationModel::setDiffuseSpectrumIntegral): Diffuse spectral distribution has not been set for radiation band '" + band_label +
"'. Cannot set its integral." << std::endl;
295 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum);
297 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
298 wavelength.y *= spectrum_integral / current_integral;
301 radiativepropertiesneedupdate =
true;
306 if (spectrum_integral < 0) {
307 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
309 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
312 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum, wavelength1, wavelength2);
314 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
315 wavelength.y *= spectrum_integral / current_integral;
318 radiativepropertiesneedupdate =
true;
323 if (radiation_bands.find(label) != radiation_bands.end()) {
324 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
328 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
331 if (!global_diffuse_spectrum.empty()) {
332 band.diffuse_spectrum = global_diffuse_spectrum;
335 radiation_bands.emplace(label, band);
338 for (
auto &source: radiation_sources) {
339 source.source_fluxes[label] = -1.f;
342 radiativepropertiesneedupdate =
true;
347 if (radiation_bands.find(label) != radiation_bands.end()) {
348 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
350 }
else if (wavelength1 > wavelength2) {
351 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The upper wavelength bound for a band must be greater than the lower bound.");
352 }
else if (wavelength2 - wavelength1 < 1) {
353 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The waveband range of a radiation band must be at least 1 nm.");
356 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
358 band.wavebandBounds =
make_vec2(wavelength1, wavelength2);
361 if (!global_diffuse_spectrum.empty()) {
362 band.diffuse_spectrum = global_diffuse_spectrum;
365 radiation_bands.emplace(label, band);
368 for (
auto &source: radiation_sources) {
369 source.source_fluxes[label] = -1.f;
372 radiativepropertiesneedupdate =
true;
378 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
381 vec2 waveBounds = radiation_bands.at(old_label).wavebandBounds;
389 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
393 band.label = new_label;
394 band.wavebandBounds =
make_vec2(wavelength_min, wavelength_max);
396 radiation_bands.emplace(new_label, band);
399 for (
auto &source: radiation_sources) {
400 source.source_fluxes[new_label] = source.source_fluxes.at(old_label);
403 radiativepropertiesneedupdate =
true;
407 if (radiation_bands.find(label) == radiation_bands.end()) {
417 helios_runtime_error(
"ERROR (RadiationModel::disableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
420 radiation_bands.at(label).emissionFlag =
false;
426 helios_runtime_error(
"ERROR (RadiationModel::enableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
429 radiation_bands.at(label).emissionFlag =
true;
446 float fp_round5(
float x) {
447 return std::round(x * 1e5f) * 1e-5f;
451bool RadiationModel::FluspectCacheKey::operator==(
const FluspectCacheKey &o)
const noexcept {
452 return biochem_label == o.biochem_label && excitation_step_nm == o.excitation_step_nm;
455std::size_t RadiationModel::FluspectCacheKeyHash::operator()(
const FluspectCacheKey &k)
const noexcept {
456 std::size_t h = std::hash<std::string>{}(k.biochem_label);
458 std::memcpy(&bits, &k.excitation_step_nm,
sizeof(bits));
459 h ^=
static_cast<std::size_t
>(bits) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
463void RadiationModel::ensureFluspectOptiparLoaded() {
464 if (fluspect_optipar_loaded) {
467 const std::filesystem::path p =
helios::resolveFilePath(
"plugins/radiation/spectral_data/fluspect_B_optipar.xml");
469 fluspect_optipar_loaded =
true;
477 if (!
context->doesPrimitiveDataExist(UUID,
"fluspect_spectrum")) {
480 std::string biochem_label;
481 context->getPrimitiveData(UUID,
"fluspect_spectrum", biochem_label);
485 FluspectCacheKey key{biochem_label, fp_round5(excitation_step_nm)};
486 auto it = fluspect_cache.find(key);
487 if (it != fluspect_cache.end()) {
493 if (!
context->doesGlobalDataExist(biochem_label.c_str())) {
494 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): primitive " + std::to_string(UUID) +
495 " has fluspect_spectrum = '" + biochem_label +
"' but that global data does not exist. "
496 "Either call LeafOptics::run() to author the biochemistry, or manually setGlobalData("
497 "\"" + biochem_label +
"\", std::vector<float>{Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe}).");
499 if (
context->getGlobalDataType(biochem_label.c_str()) != HELIOS_TYPE_FLOAT) {
500 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): global data '" + biochem_label +
501 "' is not a float vector — must be std::vector<float> with 11 elements.");
503 std::vector<float> biochem_vec;
504 context->getGlobalData(biochem_label.c_str(), biochem_vec);
505 if (biochem_vec.size() != 11) {
506 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): global data '" + biochem_label +
507 "' has " + std::to_string(biochem_vec.size()) +
" elements but must have exactly 11 "
508 "(Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe).");
512 biochem.
Cab = biochem_vec[0];
513 biochem.
Cca = biochem_vec[1];
514 biochem.
Cw = biochem_vec[2];
515 biochem.
Cdm = biochem_vec[3];
516 biochem.
Cs = biochem_vec[4];
517 biochem.
Cant = biochem_vec[5];
518 biochem.
Cp = biochem_vec[6];
519 biochem.
Cbc = biochem_vec[7];
520 biochem.
N = biochem_vec[8];
521 biochem.
V2Z = biochem_vec[9];
528 ensureFluspectOptiparLoaded();
530 auto [inserted_it, _] = fluspect_cache.emplace(key, std::move(kernel));
531 return &inserted_it->second;
534RadiationModel::ExcitationSet &RadiationModel::ensureExcitationSet(
float bin_width_nm,
uint scattering_depth) {
535 const float key = fp_round5(bin_width_nm);
536 auto it = excitation_sets.find(key);
537 if (it != excitation_sets.end()) {
542 if (scattering_depth > it->second.scattering_depth) {
543 it->second.scattering_depth = scattering_depth;
544 for (
const auto &bname : it->second.band_labels) {
552 set.bin_width_nm = bin_width_nm;
553 set.scattering_depth = scattering_depth;
554 constexpr float ex_min = 400.f;
555 constexpr float ex_max = 750.f;
559 const int n_bins =
static_cast<int>(std::ceil((ex_max - ex_min) / bin_width_nm));
560 set.band_labels.reserve(n_bins);
561 set.band_min_nm.reserve(n_bins);
562 set.band_max_nm.reserve(n_bins);
563 for (
int i = 0; i < n_bins; ++i) {
564 float wmin = ex_min + i * bin_width_nm;
565 float wmax = std::min(ex_max, wmin + bin_width_nm);
566 std::ostringstream oss;
567 oss <<
"_SIF_exc_" << bin_width_nm <<
"_" << wmin <<
"_" << wmax;
568 const std::string label = oss.str();
582 for (
uint sid = 0; sid < radiation_sources.size(); ++sid) {
583 const auto &src = radiation_sources.at(sid);
584 if (!src.source_spectrum.empty()) {
590 set.band_labels.push_back(label);
591 set.band_min_nm.push_back(wmin);
592 set.band_max_nm.push_back(wmax);
594 auto [inserted_it, _] = excitation_sets.emplace(key, std::move(set));
595 return inserted_it->second;
598void RadiationModel::populateExcitationAPAR(ExcitationSet &exc) {
602 exc.apar_buffer.clear();
603 const std::vector<uint> all_UUIDs =
context->getAllUUIDs();
604 const size_t n_bands = exc.band_labels.size();
605 for (
uint UUID : all_UUIDs) {
607 if (!
context->doesPrimitiveDataExist(UUID,
"fluspect_spectrum")) {
610 std::vector<float> row(n_bands, 0.f);
611 for (
size_t b = 0; b < n_bands; ++b) {
612 const std::string prop =
"radiation_flux_" + exc.band_labels[b];
613 if (
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
614 context->getPrimitiveData(UUID, prop.c_str(), row[b]);
617 exc.apar_buffer.emplace(UUID, std::move(row));
620 for (
uint UUID : all_UUIDs) {
621 for (
const auto &band_label : exc.band_labels) {
622 const std::string prop =
"radiation_flux_" + band_label;
623 if (
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
624 context->clearPrimitiveData(UUID, prop.c_str());
628 exc.populated =
true;
631void RadiationModel::runExcitationBands() {
639 for (
auto &kv : excitation_sets) {
640 ExcitationSet &exc = kv.second;
645 populateExcitationAPAR(exc);
649void RadiationModel::computeSIFEmission(
const std::string &emission_band) {
655 auto band_it = radiation_bands.find(emission_band);
656 if (band_it == radiation_bands.end()) {
657 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): band '" + emission_band +
"' does not exist.");
659 const float em_min = band_it->second.wavebandBounds.x;
660 const float em_max = band_it->second.wavebandBounds.y;
663 runExcitationBands();
665 auto &buf_top = sif_emission_buffer[emission_band];
666 auto &buf_bot = sif_emission_buffer_bottom[emission_band];
673 auto bw_it = sif_band_bin_width.find(emission_band);
674 if (bw_it == sif_band_bin_width.end()) {
675 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): band '" + emission_band +
"' is flagged as SIF but has no excitation bin width registered. This is an internal inconsistency.");
677 const float step = bw_it->second;
681 const ExcitationSet *matched =
nullptr;
682 for (
const auto &kv : excitation_sets) {
683 if (std::abs(kv.second.bin_width_nm - step) < 1e-5f) {
684 matched = &kv.second;
689 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): no excitation set found for bin width " + std::to_string(step) +
" nm (band '" + emission_band +
"').");
692 const std::vector<uint> all_UUIDs =
context->getAllUUIDs();
693 size_t n_applied = 0;
694 size_t n_skipped_no_biochem_has_etr = 0;
695 size_t n_skipped_has_biochem_no_etr = 0;
696 for (
uint UUID : all_UUIDs) {
697 const bool has_biochem =
context->doesPrimitiveDataExist(UUID,
"fluspect_spectrum");
698 const bool has_etr =
context->doesPrimitiveDataExist(UUID,
"electron_transport_ratio");
700 if (has_biochem) ++n_skipped_has_biochem_no_etr;
706 if (has_etr) ++n_skipped_no_biochem_has_etr;
710 float J_over_Jmax = 0.f;
711 context->getPrimitiveData(UUID,
"electron_transport_ratio", J_over_Jmax);
712 float T_leaf_K = 298.15f;
713 if (
context->doesPrimitiveDataExist(UUID,
"temperature")) {
714 context->getPrimitiveData(UUID,
"temperature", T_leaf_K);
715 if (T_leaf_K <= 0.f) T_leaf_K = 298.15f;
717 const float Phi_F = calculateFluorescenceYield(J_over_Jmax, T_leaf_K);
718 context->setPrimitiveData(UUID,
"fluorescence_yield", Phi_F);
725 std::string biochem_label;
726 context->getPrimitiveData(UUID,
"fluspect_spectrum", biochem_label);
727 if (
context->doesGlobalDataExist(biochem_label.c_str())) {
728 std::vector<float> biochem_vec;
729 context->getGlobalData(biochem_label.c_str(), biochem_vec);
730 if (biochem_vec.size() >= 11) fqe = biochem_vec[10];
733 const float phi_F_scaled = Phi_F * fqe;
741 const auto &wle = kernel->
wle;
742 const auto &wlf = kernel->
wlf;
744 auto apar_it = matched->apar_buffer.find(UUID);
745 if (apar_it == matched->apar_buffer.end())
continue;
746 const std::vector<float> &apar_bands = apar_it->second;
752 std::vector<double> F_top(wlf.size(), 0.0);
753 std::vector<double> F_bot(wlf.size(), 0.0);
754 for (
size_t b = 0; b < matched->band_labels.size(); ++b) {
755 const float band_center = 0.5f * (matched->band_min_nm[b] + matched->band_max_nm[b]);
758 float best_delta = std::numeric_limits<float>::infinity();
759 for (
size_t j = 0; j < wle.size(); ++j) {
760 const float d = std::abs(wle[j] - band_center);
761 if (d < best_delta) {
767 const double apar = apar_bands[b];
768 if (apar == 0.0)
continue;
769 for (
size_t i = 0; i < wlf.size(); ++i) {
770 F_top[i] += apar * kernel->
Mf[i][j_best];
771 F_bot[i] += apar * kernel->
Mb[i][j_best];
777 for (
size_t i = 0; i < wlf.size(); ++i) {
778 F_top[i] *= phi_F_scaled;
779 F_bot[i] *= phi_F_scaled;
783 auto integrate = [&](
const std::vector<double> &F) ->
double {
785 for (
size_t i = 0; i + 1 < wlf.size(); ++i) {
786 const float w0 = wlf[i];
787 const float w1 = wlf[i + 1];
788 if (w1 < em_min)
continue;
789 if (w0 > em_max)
break;
791 const double lo = std::max<double>(w0, em_min);
792 const double hi = std::min<double>(w1, em_max);
793 if (hi <= lo)
continue;
796 const double frac_lo = (lo - w0) / (w1 - w0);
797 const double frac_hi = (hi - w0) / (w1 - w0);
798 const double F_lo = F[i] + frac_lo * (F[i + 1] - F[i]);
799 const double F_hi = F[i] + frac_hi * (F[i + 1] - F[i]);
800 total += 0.5 * (F_lo + F_hi) * (hi - lo);
805 const double emit_top = integrate(F_top);
806 const double emit_bot = integrate(F_bot);
807 buf_top[UUID] =
static_cast<float>(emit_top);
808 buf_bot[UUID] =
static_cast<float>(emit_bot);
812 if (n_applied == 0 && message_flag) {
813 if (n_skipped_has_biochem_no_etr == 0 && n_skipped_no_biochem_has_etr == 0) {
814 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
815 <<
"' will emit zero — no primitives have both 'fluspect_spectrum' (leaf biochemistry "
816 "label) and 'electron_transport_ratio' (J/Jmax) primitive data. Call "
817 "LeafOptics::run() to author leaf biochemistry and PhotosynthesisModel::run() "
818 "with optionalOutputPrimitiveData(\"electron_transport_ratio\") before runBand()."
820 }
else if (n_skipped_has_biochem_no_etr > 0 && n_skipped_no_biochem_has_etr == 0) {
821 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
822 <<
"' will emit zero — " << n_skipped_has_biochem_no_etr <<
" primitives have "
823 "'fluspect_spectrum' but lack 'electron_transport_ratio'. Run PhotosynthesisModel::run() "
824 "with optionalOutputPrimitiveData(\"electron_transport_ratio\") before runBand()."
826 }
else if (n_skipped_no_biochem_has_etr > 0 && n_skipped_has_biochem_no_etr == 0) {
827 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
828 <<
"' will emit zero — " << n_skipped_no_biochem_has_etr <<
" primitives have "
829 "'electron_transport_ratio' but lack 'fluspect_spectrum'. Call LeafOptics::run() "
830 "to author leaf biochemistry for those primitives."
833 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
834 <<
"' will emit zero — " << n_skipped_has_biochem_no_etr <<
" primitives have "
835 "'fluspect_spectrum' but lack 'electron_transport_ratio', and "
836 << n_skipped_no_biochem_has_etr <<
" have 'electron_transport_ratio' but lack "
837 "'fluspect_spectrum'. No primitive has both required fields."
840 }
else if (n_applied > 0 && n_skipped_has_biochem_no_etr > 0 && message_flag) {
842 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): band '" << emission_band <<
"': "
843 << n_skipped_has_biochem_no_etr <<
" primitives with 'fluspect_spectrum' were silently "
844 "skipped because they lack 'electron_transport_ratio'. ("
845 << n_applied <<
" primitives emitted SIF normally.)"
854 if (emission_band_labels.empty()) {
855 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): emission_band_labels cannot be empty.");
857 for (
const auto &band : emission_band_labels) {
859 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): band '" + band +
"' does not exist. Add it with addRadiationBand() before calling addSIFCamera().");
864 sif_emission_bands.insert(band);
869 auto bw_it = sif_band_bin_width.find(band);
870 if (bw_it == sif_band_bin_width.end()) {
871 sif_band_bin_width[band] = camera_properties.excitation_bin_width_nm;
872 }
else if (std::abs(bw_it->second - camera_properties.excitation_bin_width_nm) > 1e-5f) {
873 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): emission band '" + band +
"' is already bound to excitation_bin_width_nm=" + std::to_string(bw_it->second) +
874 " by a prior SIF camera, but this camera's excitation_bin_width_nm=" + std::to_string(camera_properties.excitation_bin_width_nm) +
875 ". Each SIF emission band can be bound to only one excitation resolution. "
876 "Either use a separate band per camera or match excitation_bin_width_nm.");
879 if (camera_properties.excitation_bin_width_nm <= 0.f) {
880 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): excitation_bin_width_nm must be > 0.");
882 ensureExcitationSet(camera_properties.excitation_bin_width_nm, camera_properties.excitation_scattering_depth);
893 bool any_source_has_spectrum =
false;
894 for (
const auto &src : radiation_sources) {
895 if (!src.source_spectrum.empty()) {
896 any_source_has_spectrum =
true;
900 if (!any_source_has_spectrum) {
901 std::cerr <<
"WARNING (RadiationModel::addSIFCamera): Camera '" << camera_label
902 <<
"' added, but no radiation source has a spectrum set. Auto-generated excitation "
903 "bands will receive zero flux, so SIF emission from all leaves will be zero. "
904 "Call setSourceSpectrum(source_ID, \"solar_spectrum_direct_ASTMG173\") (or similar) "
905 "on at least one source before runBand()."
915 const auto all_UUIDs =
context->getAllUUIDs();
916 const size_t n_prims = all_UUIDs.size();
917 size_t n_with_biochem = 0;
918 for (
uint UUID : all_UUIDs) {
919 if (
context->doesPrimitiveDataExist(UUID,
"fluspect_spectrum")) {
923 if (n_prims > 0 && n_with_biochem == 0) {
924 std::cerr <<
"WARNING (RadiationModel::addSIFCamera): Camera '" << camera_label
925 <<
"' added to a scene with " << n_prims <<
" primitives, but none have "
926 "'fluspect_spectrum' primitive data. Helios cannot identify fluorescing leaves "
927 "without a biochemistry label. Call LeafOptics::run(UUIDs, properties, \"my_label\") "
928 "to author leaf biochemistry, or manually "
929 "setGlobalData(\"fluspect_biochem_<label>\", std::vector<float>{Cab, Cca, Cw, Cdm, "
930 "Cs, Cant, Cp, Cbc, N, V2Z, fqe}) and setPrimitiveData(UUIDs, \"fluspect_spectrum\", "
931 "\"fluspect_biochem_<label>\")."
937 addRadiationCamera(camera_label, emission_band_labels, position, lookat, camera_properties, antialiasing_samples);
940 sif_cameras.insert(camera_label);
947 addSIFCamera(camera_label, emission_band_labels, position, position + dir, camera_properties, antialiasing_samples);
951 return sif_cameras.find(camera_label) != sif_cameras.end();
954float RadiationModel::calculateFluorescenceYield(
float J_over_Jmax,
float T_leaf_K) {
959 constexpr float kF = 0.05f;
961 const float T_C = T_leaf_K - 273.15f;
962 const float kD = std::max(0.03f * T_C + 0.0773f, 0.87f);
968 }
else if (x < 0.6f) {
969 kN = 2.0f * (x - 0.2f) / 0.4f;
971 kN = 2.0f + 4.0f * (x - 0.6f) / 0.4f;
974 constexpr float Phi_P_max = 0.85f;
976 const float Phi_P =
helios::clamp(J_over_Jmax * Phi_P_max, 0.f, 0.84f);
977 const float kP = (kF + kD + kN) * Phi_P / (1.f - Phi_P);
979 return kF / (kF + kD + kP + kN);
994 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): Invalid collimated source direction. Direction vector should not have length of zero.");
997 uint Nsources = radiation_sources.size() + 1;
998 if (Nsources > 256) {
999 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): A maximum of 256 radiation sources are allowed.");
1002 bool warn_multiple_suns =
false;
1003 for (
auto &source: radiation_sources) {
1004 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1005 warn_multiple_suns =
true;
1008 if (warn_multiple_suns) {
1009 std::cerr <<
"WARNING (RadiationModel::addCollimatedRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
1015 for (
const auto &band: radiation_bands) {
1019 radiation_sources.emplace_back(collimated_source);
1021 radiativepropertiesneedupdate =
true;
1023 return Nsources - 1;
1029 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): Spherical radiation source radius must be positive.");
1032 uint Nsources = radiation_sources.size() + 1;
1033 if (Nsources > 256) {
1034 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
1040 for (
const auto &band: radiation_bands) {
1044 radiation_sources.emplace_back(sphere_source);
1046 uint sourceID = Nsources - 1;
1048 if (islightvisualizationenabled) {
1049 buildLightModelGeometry(sourceID);
1052 radiativepropertiesneedupdate =
true;
1067 uint Nsources = radiation_sources.size() + 1;
1068 if (Nsources > 256) {
1069 helios_runtime_error(
"ERROR (RadiationModel::addSunSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
1072 bool warn_multiple_suns =
false;
1073 for (
auto &source: radiation_sources) {
1074 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1075 warn_multiple_suns =
true;
1078 if (warn_multiple_suns) {
1079 std::cerr <<
"WARNING (RadiationModel::addSunSphereRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
1082 RadiationSource sphere_source(150e9 * sun_direction / sun_direction.
magnitude(), 150e9, 2.f * 695.5e6, sigma * powf(5700, 4) / 1288.437f);
1085 for (
const auto &band: radiation_bands) {
1089 radiation_sources.emplace_back(sphere_source);
1091 radiativepropertiesneedupdate =
true;
1093 return Nsources - 1;
1098 if (size.
x <= 0 || size.
y <= 0) {
1099 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): Radiation source size must be positive.");
1102 uint Nsources = radiation_sources.size() + 1;
1103 if (Nsources > 256) {
1104 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): A maximum of 256 radiation sources are allowed.");
1110 for (
const auto &band: radiation_bands) {
1114 radiation_sources.emplace_back(rectangle_source);
1116 uint sourceID = Nsources - 1;
1118 if (islightvisualizationenabled) {
1119 buildLightModelGeometry(sourceID);
1122 radiativepropertiesneedupdate =
true;
1130 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): Disk radiation source radius must be positive.");
1133 uint Nsources = radiation_sources.size() + 1;
1134 if (Nsources > 256) {
1135 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): A maximum of 256 radiation sources are allowed.");
1141 for (
const auto &band: radiation_bands) {
1145 radiation_sources.emplace_back(disk_source);
1147 uint sourceID = Nsources - 1;
1149 if (islightvisualizationenabled) {
1150 buildLightModelGeometry(sourceID);
1153 radiativepropertiesneedupdate =
true;
1160 if (sourceID >= radiation_sources.size()) {
1161 helios_runtime_error(
"ERROR (RadiationModel::deleteRadiationSource): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1164 radiation_sources.erase(radiation_sources.begin() + sourceID);
1166 radiativepropertiesneedupdate =
true;
1171 if (source_ID >= radiation_sources.size()) {
1172 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1173 }
else if (source_integral < 0) {
1174 helios_runtime_error(
"ERROR (RadiationModel::setSourceIntegral): Source integral must be non-negative.");
1177 float current_integral =
integrateSpectrum(radiation_sources.at(source_ID).source_spectrum);
1179 for (
vec2 &wavelength: radiation_sources.at(source_ID).source_spectrum) {
1180 wavelength.y *= source_integral / current_integral;
1186 if (source_ID >= radiation_sources.size()) {
1187 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1188 }
else if (source_integral < 0) {
1189 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source integral must be non-negative.");
1190 }
else if (radiation_sources.at(source_ID).source_spectrum.empty()) {
1191 std::cout <<
"WARNING (RadiationModel::setSourceSpectrumIntegral): Spectral distribution has not been set for radiation source. Cannot set its integral." << std::endl;
1197 float old_integral =
integrateSpectrum(source.source_spectrum, wavelength1, wavelength2);
1199 for (
vec2 &wavelength: source.source_spectrum) {
1200 wavelength.y *= source_integral / old_integral;
1207 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Cannot add set source flux for band '" + label +
"' because it is not a valid band.");
1208 }
else if (source_ID >= radiation_sources.size()) {
1209 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1210 }
else if (flux < 0) {
1211 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source flux must be non-negative.");
1214 radiation_sources.at(source_ID).source_fluxes[label] = flux * radiation_sources.at(source_ID).source_flux_scaling_factor;
1218 for (
auto ID: source_ID) {
1226 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get source flux for band '" + label +
"' because it is not a valid band.");
1227 }
else if (source_ID >= radiation_sources.size()) {
1228 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1229 }
else if (radiation_sources.at(source_ID).source_fluxes.find(label) == radiation_sources.at(source_ID).source_fluxes.end()) {
1230 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get flux for source #" + std::to_string(source_ID) +
" because radiative band '" + label +
"' does not exist.");
1235 if (!source.source_spectrum.empty() && source.source_fluxes.at(label) < 0.f) {
1236 vec2 wavebounds = radiation_bands.at(label).wavebandBounds;
1238 wavebounds =
make_vec2(source.source_spectrum.front().x, source.source_spectrum.back().x);
1240 return integrateSpectrum(source.source_spectrum, wavebounds.
x, wavebounds.
y) * source.source_flux_scaling_factor;
1241 }
else if (source.source_fluxes.at(label) < 0.f) {
1245 return source.source_fluxes.at(label);
1250 if (source_ID >= radiation_sources.size()) {
1251 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
1255 for (
auto s = 0; s < spectrum.size(); s++) {
1257 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
1258 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Wavelengths must increase monotonically.");
1261 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
1262 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Wavelength value of " + std::to_string(spectrum.at(s).x) +
" appears to be erroneous.");
1265 if (spectrum.at(s).y < 0) {
1266 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Flux value at wavelength of " + std::to_string(spectrum.at(s).x) +
" appears is negative.");
1270 radiation_sources.at(source_ID).source_spectrum = spectrum;
1272 radiativepropertiesneedupdate =
true;
1276 for (
auto ID: source_ID) {
1283 if (source_ID >= radiation_sources.size()) {
1284 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
1287 std::vector<vec2> spectrum = loadSpectralData(spectrum_label);
1289 radiation_sources.at(source_ID).source_spectrum = spectrum;
1290 radiation_sources.at(source_ID).source_spectrum_label = spectrum_label;
1291 radiation_sources.at(source_ID).source_spectrum_version =
context->getGlobalDataVersion(spectrum_label.c_str());
1293 radiativepropertiesneedupdate =
true;
1297 for (
auto ID: source_ID) {
1304 std::vector<vec2> spectrum;
1307 if (spectrum_label ==
"ASTMG173") {
1308 spectrum = loadSpectralData(
"solar_spectrum_diffuse_ASTMG173");
1309 global_diffuse_spectrum_label =
"solar_spectrum_diffuse_ASTMG173";
1311 spectrum = loadSpectralData(spectrum_label);
1312 global_diffuse_spectrum_label = spectrum_label;
1316 global_diffuse_spectrum = spectrum;
1317 global_diffuse_spectrum_version =
context->getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
1320 for (
auto &band_pair: radiation_bands) {
1321 band_pair.second.diffuse_spectrum = spectrum;
1324 radiativepropertiesneedupdate =
true;
1330 helios_runtime_error(
"ERROR (RadiationModel::getDiffuseFlux): Cannot get diffuse flux for band '" + band_label +
"' because it is not a valid band.");
1336 if (band.emissionFlag) {
1337 if (band.diffuseFlux >= 0.f) {
1338 return band.diffuseFlux;
1344 if (band.diffuseFlux >= 0.f) {
1345 return band.diffuseFlux;
1348 const std::vector<vec2> &spectrum = band.diffuse_spectrum;
1349 if (!spectrum.empty()) {
1350 vec2 wavebounds = band.wavebandBounds;
1352 wavebounds =
make_vec2(spectrum.front().x, spectrum.back().x);
1361 islightvisualizationenabled =
true;
1364 for (
int s = 0; s < radiation_sources.size(); s++) {
1365 buildLightModelGeometry(s);
1370 islightvisualizationenabled =
false;
1371 for (
auto &UUIDs: source_model_UUIDs) {
1372 context->deletePrimitive(UUIDs.second);
1377 iscameravisualizationenabled =
true;
1380 for (
auto &cam: cameras) {
1381 buildCameraModelGeometry(cam.first);
1386 iscameravisualizationenabled =
false;
1387 for (
auto &UUIDs: camera_model_UUIDs) {
1388 context->deletePrimitive(UUIDs.second);
1392void RadiationModel::buildLightModelGeometry(
uint sourceID) {
1394 assert(sourceID < radiation_sources.size());
1397 if (source.source_type == RADIATION_SOURCE_TYPE_SPHERE) {
1398 source_model_UUIDs[sourceID] =
context->loadOBJ(
"SphereLightSource.obj",
true);
1399 }
else if (source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1400 source_model_UUIDs[sourceID] =
context->loadOBJ(
"SphereLightSource.obj",
true);
1401 }
else if (source.source_type == RADIATION_SOURCE_TYPE_DISK) {
1402 source_model_UUIDs[sourceID] =
context->loadOBJ(
"DiskLightSource.obj",
true);
1403 context->scalePrimitive(source_model_UUIDs.at(sourceID),
make_vec3(source.source_width.x, source.source_width.y, 0.05f * source.source_width.x));
1404 std::vector<uint> UUIDs_arrow =
context->loadOBJ(
"Arrow.obj",
true);
1405 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
1406 context->scalePrimitive(UUIDs_arrow,
make_vec3(1, 1, 1) * 0.25f * source.source_width.x);
1407 }
else if (source.source_type == RADIATION_SOURCE_TYPE_RECTANGLE) {
1408 source_model_UUIDs[sourceID] =
context->loadOBJ(
"RectangularLightSource.obj",
true);
1409 context->scalePrimitive(source_model_UUIDs.at(sourceID),
make_vec3(source.source_width.x, source.source_width.y, fmin(0.05f * (source.source_width.x + source.source_width.y), 0.5f * fmin(source.source_width.x, source.source_width.y))));
1410 std::vector<uint> UUIDs_arrow =
context->loadOBJ(
"Arrow.obj",
true);
1411 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
1412 context->scalePrimitive(UUIDs_arrow,
make_vec3(1, 1, 1) * 0.15f * (source.source_width.x + source.source_width.y));
1417 if (source.source_type == RADIATION_SOURCE_TYPE_SPHERE) {
1418 context->scalePrimitive(source_model_UUIDs.at(sourceID),
make_vec3(source.source_width.x, source.source_width.x, source.source_width.x));
1419 context->translatePrimitive(source_model_UUIDs.at(sourceID), source.source_position);
1420 }
else if (source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1423 context->getDomainBoundingSphere(center, radius);
1424 context->scalePrimitive(source_model_UUIDs.at(sourceID),
make_vec3(1, 1, 1) * 0.1f * radius);
1425 vec3 sunvec = source.source_position;
1427 context->translatePrimitive(source_model_UUIDs.at(sourceID), center + sunvec * radius);
1429 context->rotatePrimitive(source_model_UUIDs.at(sourceID), source.source_rotation.x,
"x");
1430 context->rotatePrimitive(source_model_UUIDs.at(sourceID), source.source_rotation.y,
"y");
1431 context->rotatePrimitive(source_model_UUIDs.at(sourceID), source.source_rotation.z,
"z");
1432 context->translatePrimitive(source_model_UUIDs.at(sourceID), source.source_position);
1435 context->setPrimitiveData(source_model_UUIDs.at(sourceID),
"twosided_flag",
uint(3));
1438void RadiationModel::buildCameraModelGeometry(
const std::string &cameralabel) {
1440 assert(cameras.find(cameralabel) != cameras.end());
1444 vec3 viewvec = camera.lookat - camera.position;
1447 camera_model_UUIDs[cameralabel] =
context->loadOBJ(
"Camera.obj",
true);
1449 context->rotatePrimitive(camera_model_UUIDs.at(cameralabel), viewsph.
elevation,
"x");
1450 context->rotatePrimitive(camera_model_UUIDs.at(cameralabel), -viewsph.
azimuth,
"z");
1452 context->translatePrimitive(camera_model_UUIDs.at(cameralabel), camera.position);
1454 context->setPrimitiveData(camera_model_UUIDs.at(cameralabel),
"twosided_flag",
uint(3));
1457void RadiationModel::updateLightModelPosition(
uint sourceID,
const helios::vec3 &delta_position) {
1459 assert(sourceID < radiation_sources.size());
1463 if (source.source_type != RADIATION_SOURCE_TYPE_SPHERE && source.source_type != RADIATION_SOURCE_TYPE_DISK && source.source_type != RADIATION_SOURCE_TYPE_RECTANGLE) {
1467 context->translatePrimitive(source_model_UUIDs.at(sourceID), delta_position);
1470void RadiationModel::updateCameraModelPosition(
const std::string &cameralabel) {
1472 assert(cameras.find(cameralabel) != cameras.end());
1474 context->deletePrimitive(camera_model_UUIDs.at(cameralabel));
1475 buildCameraModelGeometry(cameralabel);
1480 if (source_ID >= radiation_sources.size()) {
1481 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1482 }
else if (object_spectrum.size() < 2) {
1483 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1484 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1485 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1488 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1491 int iend = (int) object_spectrum.size() - 1;
1492 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
1494 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
1497 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
1505 for (
auto i = istart; i < iend; i++) {
1507 float x0 = object_spectrum.at(i).x;
1508 float Esource0 =
interp1(source_spectrum, object_spectrum.at(i).x);
1509 float Eobject0 = object_spectrum.at(i).y;
1511 float x1 = object_spectrum.at(i + 1).x;
1512 float Eobject1 = object_spectrum.at(i + 1).y;
1513 float Esource1 =
interp1(source_spectrum, object_spectrum.at(i + 1).x);
1515 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
1516 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1524 if (object_spectrum.size() < 2) {
1525 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1526 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1527 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1531 int iend = (int) object_spectrum.size() - 1;
1532 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
1534 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
1537 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
1544 for (
auto i = istart; i < iend; i++) {
1545 float E0 = object_spectrum.at(i).y;
1546 float x0 = object_spectrum.at(i).x;
1547 float E1 = object_spectrum.at(i + 1).y;
1548 float x1 = object_spectrum.at(i + 1).x;
1549 E += (E0 + E1) * (x1 - x0) * 0.5f;
1556 float wavelength1 = object_spectrum.at(0).x;
1557 float wavelength2 = object_spectrum.at(object_spectrum.size() - 1).x;
1564 if (source_ID >= radiation_sources.size()) {
1565 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1566 }
else if (object_spectrum.size() < 2) {
1567 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1570 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1574 for (
auto i = 1; i < object_spectrum.size(); i++) {
1576 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
1579 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
1582 float x1 = object_spectrum.at(i).x;
1583 float Eobject1 = object_spectrum.at(i).y;
1584 float Esource1 =
interp1(source_spectrum, x1);
1585 float Ecamera1 =
interp1(camera_spectrum, x1);
1588 float x0 = object_spectrum.at(i - 1).x;
1589 float Eobject0 = object_spectrum.at(i - 1).y;
1590 float Esource0 =
interp1(source_spectrum, x0);
1591 float Ecamera0 =
interp1(camera_spectrum, x0);
1593 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
1594 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1603 if (object_spectrum.size() < 2) {
1604 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1609 for (
auto i = 1; i < object_spectrum.size(); i++) {
1611 if (object_spectrum.at(i).x <= camera_spectrum.front().x) {
1614 if (object_spectrum.at(i).x > camera_spectrum.back().x) {
1618 float x1 = object_spectrum.at(i).x;
1619 float Eobject1 = object_spectrum.at(i).y;
1620 float Ecamera1 =
interp1(camera_spectrum, x1);
1623 float x0 = object_spectrum.at(i - 1).x;
1624 float Eobject0 = object_spectrum.at(i - 1).y;
1625 float Ecamera0 =
interp1(camera_spectrum, x0);
1627 E += 0.5f * ((Eobject1 * Ecamera1) + (Eobject0 * Ecamera0)) * (x1 - x0);
1628 Etot += 0.5f * (Ecamera1 + Ecamera0) * (x1 - x0);
1636 if (source_ID >= radiation_sources.size()) {
1637 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1638 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1639 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1642 return integrateSpectrum(radiation_sources.at(source_ID).source_spectrum, wavelength1, wavelength2);
1647 std::vector<helios::vec2> spectrum = loadSpectralData(existing_global_data_label);
1650 s.y *= scale_factor;
1653 context->setGlobalData(new_global_data_label.c_str(), spectrum);
1658 std::vector<vec2> spectrum = loadSpectralData(global_data_label);
1660 for (
vec2 &s: spectrum) {
1661 s.y *= scale_factor;
1664 context->setGlobalData(global_data_label.c_str(), spectrum);
1669 scaleSpectrum(existing_global_data_label, new_global_data_label,
context->randu(minimum_scale_factor, maximum_scale_factor));
1673void RadiationModel::blendSpectra(
const std::string &new_spectrum_label,
const std::vector<std::string> &spectrum_labels,
const std::vector<float> &weights)
const {
1675 if (spectrum_labels.size() != weights.size()) {
1676 helios_runtime_error(
"ERROR (RadiationModel::blendSpectra): number of spectra and weights must be equal");
1677 }
else if (fabsf(
sum(weights) - 1.f) > 1e-5f) {
1681 std::vector<vec2> new_spectrum;
1682 uint spectrum_size = 0;
1684 std::vector<std::vector<vec2>> spectrum(spectrum_labels.size());
1686 uint lambda_start = 0;
1687 uint lambda_end = 0;
1688 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1690 spectrum.at(i) = loadSpectralData(spectrum_labels.at(i));
1693 lambda_start = spectrum.at(i).front().x;
1694 lambda_end = spectrum.at(i).back().x;
1696 if (spectrum.at(i).front().x > lambda_start) {
1697 lambda_start = spectrum.at(i).front().x;
1699 if (spectrum.at(i).back().x < lambda_end) {
1700 lambda_end = spectrum.at(i).back().x;
1705 spectrum_size = lambda_end - lambda_start + 1;
1706 new_spectrum.resize(spectrum_size);
1707 for (
uint j = 0; j < spectrum_size; j++) {
1708 new_spectrum.at(j) =
make_vec2(lambda_start + j, 0);
1712 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1713 for (
uint j = 0; j < spectrum.at(i).size(); j++) {
1715 if (spectrum.at(i).at(j).x >= lambda_start) {
1717 spectrum.at(i).erase(spectrum.at(i).begin(), spectrum.at(i).begin() + j);
1725 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1726 for (
int j = spectrum.at(i).size() - 1; j <= 0; j--) {
1728 if (spectrum.at(i).at(j).x <= lambda_end) {
1729 if (j < spectrum.at(i).size() - 1) {
1730 spectrum.at(i).erase(spectrum.at(i).begin() + j + 1, spectrum.at(i).end());
1737 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1738 for (
uint j = 0; j < spectrum_size; j++) {
1739 assert(new_spectrum.at(j).x == spectrum.at(i).at(j).x);
1740 new_spectrum.at(j).y += weights.at(i) * spectrum.at(i).at(j).y;
1744 context->setGlobalData(new_spectrum_label.c_str(), new_spectrum);
1749 std::vector<float> weights;
1750 weights.resize(spectrum_labels.size());
1751 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1752 weights.at(i) =
context->randu();
1754 float sum_weights =
sum(weights);
1755 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1756 weights.at(i) /= sum_weights;
1759 blendSpectra(new_spectrum_label, spectrum_labels, weights);
1763 const std::string &primitive_data_radprop_label) {
1766 if (spectra.size() != values.size()) {
1767 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' vector (size=" + std::to_string(spectra.size()) +
") and 'values' vector (size=" + std::to_string(values.size()) +
1768 ") must have the same length.");
1772 if (spectra.empty()) {
1773 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' and 'values' vectors cannot be empty.");
1777 if (primitive_UUIDs.empty()) {
1778 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_UUIDs' vector cannot be empty.");
1782 if (primitive_data_query_label.empty()) {
1783 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_query_label' cannot be empty.");
1786 if (primitive_data_radprop_label.empty()) {
1787 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_radprop_label' cannot be empty.");
1791 SpectrumInterpolationConfig *existing_config =
nullptr;
1792 for (
auto &config: spectrum_interpolation_configs) {
1793 if (config.query_data_label == primitive_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1794 existing_config = &config;
1799 if (existing_config !=
nullptr) {
1801 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1803 if (spectra_match) {
1805 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1808 existing_config->spectra_labels = spectra;
1809 existing_config->mapping_values = values;
1810 existing_config->primitive_UUIDs.clear();
1811 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1815 SpectrumInterpolationConfig config;
1816 config.primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1817 config.spectra_labels = spectra;
1818 config.mapping_values = values;
1819 config.query_data_label = primitive_data_query_label;
1820 config.target_data_label = primitive_data_radprop_label;
1822 spectrum_interpolation_configs.push_back(config);
1827 const std::string &primitive_data_radprop_label) {
1830 if (spectra.size() != values.size()) {
1831 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'spectra' vector (size=" + std::to_string(spectra.size()) +
") and 'values' vector (size=" + std::to_string(values.size()) +
") must have the same length.");
1835 if (spectra.empty()) {
1836 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'spectra' and 'values' vectors cannot be empty.");
1840 if (object_IDs.empty()) {
1841 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_IDs' vector cannot be empty.");
1845 if (object_data_query_label.empty()) {
1846 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_data_query_label' cannot be empty.");
1849 if (primitive_data_radprop_label.empty()) {
1850 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'primitive_data_radprop_label' cannot be empty.");
1854 SpectrumInterpolationConfig *existing_config =
nullptr;
1855 for (
auto &config: spectrum_interpolation_configs) {
1856 if (config.query_data_label == object_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1857 existing_config = &config;
1862 if (existing_config !=
nullptr) {
1864 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1866 if (spectra_match) {
1868 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1871 existing_config->spectra_labels = spectra;
1872 existing_config->mapping_values = values;
1873 existing_config->object_IDs.clear();
1874 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1878 SpectrumInterpolationConfig config;
1879 config.object_IDs.insert(object_IDs.begin(), object_IDs.end());
1880 config.spectra_labels = spectra;
1881 config.mapping_values = values;
1882 config.query_data_label = object_data_query_label;
1883 config.target_data_label = primitive_data_radprop_label;
1885 spectrum_interpolation_configs.push_back(config);
1891 if (source_ID >= radiation_sources.size()) {
1892 helios_runtime_error(
"ERROR (RadiationModel::setSourcePosition): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources.");
1895 vec3 old_position = radiation_sources.at(source_ID).source_position;
1897 if (radiation_sources.at(source_ID).source_type == RADIATION_SOURCE_TYPE_COLLIMATED) {
1898 radiation_sources.at(source_ID).source_position = position / position.
magnitude();
1900 radiation_sources.at(source_ID).source_position = position * radiation_sources.at(source_ID).source_position_scaling_factor;
1903 if (islightvisualizationenabled) {
1904 updateLightModelPosition(source_ID, radiation_sources.at(source_ID).source_position - old_position);
1913 if (source_ID >= radiation_sources.size()) {
1916 return radiation_sources.at(source_ID).source_position;
1922 helios_runtime_error(
"ERROR (RadiationModel::setScatteringDepth): Cannot set scattering depth for band '" + label +
"' because it is not a valid band.");
1924 radiation_bands.at(label).scatteringDepth = depth;
1930 helios_runtime_error(
"ERROR (setMinScatterEnergy): Cannot set minimum scattering energy for band '" + label +
"' because it is not a valid band.");
1932 radiation_bands.at(label).minScatterEnergy = energy;
1937 if (boundary ==
"x") {
1939 periodic_flag.
x = 1;
1941 }
else if (boundary ==
"y") {
1943 periodic_flag.
y = 1;
1945 }
else if (boundary ==
"xy") {
1947 periodic_flag.
x = 1;
1948 periodic_flag.
y = 1;
1952 std::cout <<
"WARNING (RadiationModel::enforcePeriodicBoundary()): unknown boundary of '" << boundary <<
"'. Possible choices are x, y, or xy." << std::endl;
1964 std::cout <<
"Updating geometry in radiation transport model..." << std::flush;
1968 buildGeometryData(UUIDs);
1975 backend->updateGeometry(geometry_data);
1976 backend->buildAccelerationStructure();
1978 radiativepropertiesneedupdate =
true;
1979 isgeometryinitialized =
true;
1982 std::cout <<
"done." << std::endl;
1986void RadiationModel::updateRadiativeProperties() {
1999 std::cout <<
"Updating radiative properties..." << std::flush;
2002 uint Nbands = radiation_bands.size();
2003 uint Nsources = radiation_sources.size();
2004 uint Ncameras = cameras.size();
2005 size_t Nobjects = primitiveID.size();
2006 size_t Nprimitives = context_UUIDs.size();
2008 scattering_iterations_needed.clear();
2009 for (
auto &band: radiation_bands) {
2010 scattering_iterations_needed[band.first] =
false;
2016 std::vector<std::string> band_labels;
2017 for (
auto &band: radiation_bands) {
2018 band_labels.push_back(band.first);
2027 size_t mat_size = (size_t)Nsources * Nprimitives * Nbands;
2028 material_data.
reflectivity.assign(mat_size, rho_default);
2032 material_data.
glass_n.assign(mat_size, 0.f);
2033 material_data.
glass_KL.assign(mat_size, 0.f);
2034 material_data.
is_glass.assign(mat_size, 0);
2037 size_t cam_size = (size_t)Nsources * Nprimitives * Nbands * Ncameras;
2049 std::vector<std::vector<std::vector<helios::vec2>>> camera_response_unique;
2050 camera_response_unique.resize(Ncameras);
2053 for (
const auto &camera: cameras) {
2055 camera_response_unique.at(cam).resize(Nbands);
2057 for (
uint b = 0; b < Nbands; b++) {
2059 if (camera.second.band_spectral_response.find(band_labels.at(b)) == camera.second.band_spectral_response.end()) {
2063 std::string camera_response = camera.second.band_spectral_response.at(band_labels.at(b));
2065 if (!camera_response.empty()) {
2067 if (!
context->doesGlobalDataExist(camera_response.c_str())) {
2068 if (camera_response !=
"uniform") {
2069 warnings.
addWarning(
"missing_camera_response",
"Camera spectral response \"" + camera_response +
"\" does not exist. Assuming a uniform spectral response.");
2071 }
else if (
context->getGlobalDataType(camera_response.c_str()) == helios::HELIOS_TYPE_VEC2) {
2073 std::vector<helios::vec2> data = loadSpectralData(camera_response.c_str());
2075 camera_response_unique.at(cam).at(b) = data;
2077 }
else if (
context->getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_VEC2 &&
context->getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_STRING) {
2078 camera_response.clear();
2079 warnings.
addWarning(
"camera_response_wrong_type",
"Camera spectral response \"" + camera_response +
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral response...");
2088 std::unordered_map<std::string, float> spectral_integration_cache;
2092 std::unordered_map<std::string, float> temp_spectral_cache;
2096 auto createCacheKey = [](
const std::string &spectrum_label,
uint source_id,
uint band_id,
uint camera_id,
const std::string &type) -> std::string {
2097 return spectrum_label +
"_" + std::to_string(source_id) +
"_" + std::to_string(band_id) +
"_" + std::to_string(camera_id) +
"_" + type;
2101 auto getCachedValue = [&](
const std::string &cache_key,
bool &found) ->
float {
2102 float result = 0.0f;
2110 auto cache_it = spectral_integration_cache.find(cache_key);
2111 if (cache_it != spectral_integration_cache.end()) {
2113 result = cache_it->second;
2122 auto setCachedValue = [&](
const std::string &cache_key,
float value) {
2127 spectral_integration_cache[cache_key] = value;
2134 auto cachedInterp1 = [&](
const std::vector<helios::vec2> &spectrum,
float wavelength,
const std::string &spectrum_id) ->
float {
2136 std::string cache_key =
"interp_" + spectrum_id +
"_" + std::to_string(wavelength);
2139 float cached_result = getCachedValue(cache_key, found);
2141 return cached_result;
2145 float result =
interp1(spectrum, wavelength);
2146 setCachedValue(cache_key, result);
2151 auto cachedIntegrateSpectrumWithSource = [&](
uint source_ID,
const std::vector<helios::vec2> &object_spectrum,
float wavelength1,
float wavelength2,
const std::string &object_spectrum_id) ->
float {
2152 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2 || wavelength1 >= wavelength2) {
2156 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
2157 std::string source_id =
"source_" + std::to_string(source_ID);
2160 int iend = (int) object_spectrum.size() - 1;
2161 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
2162 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
2165 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
2173 for (
auto i = istart; i < iend; i++) {
2174 float x0 = object_spectrum.at(i).x;
2175 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
2176 float Eobject0 = object_spectrum.at(i).y;
2178 float x1 = object_spectrum.at(i + 1).x;
2179 float Eobject1 = object_spectrum.at(i + 1).y;
2180 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
2182 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
2183 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
2186 return (Etot != 0.0f) ? E / Etot : 0.0f;
2190 auto cachedIntegrateSpectrumWithSourceAndCamera = [&](
uint source_ID,
const std::vector<helios::vec2> &object_spectrum,
const std::vector<helios::vec2> &camera_spectrum,
uint camera_index,
uint band_index,
2191 const std::string &object_spectrum_id) ->
float {
2192 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2) {
2196 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
2197 std::string source_id =
"source_" + std::to_string(source_ID);
2198 std::string camera_id =
"camera_" + std::to_string(camera_index) +
"_band_" + std::to_string(band_index);
2202 for (
auto i = 1; i < object_spectrum.size(); i++) {
2203 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
2206 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
2210 float x1 = object_spectrum.at(i).x;
2211 float Eobject1 = object_spectrum.at(i).y;
2212 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
2213 float Ecamera1 = cachedInterp1(camera_spectrum, x1, camera_id);
2215 float x0 = object_spectrum.at(i - 1).x;
2216 float Eobject0 = object_spectrum.at(i - 1).y;
2217 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
2218 float Ecamera0 = cachedInterp1(camera_spectrum, x0, camera_id);
2220 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
2221 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
2224 return (Etot != 0.0f) ? E / Etot : 0.0f;
2228 for (
const auto &config: spectrum_interpolation_configs) {
2230 for (
const auto &spectrum_label: config.spectra_labels) {
2231 if (!
context->doesGlobalDataExist(spectrum_label.c_str())) {
2232 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Spectral interpolation config references global data '" + spectrum_label +
"' which does not exist.");
2234 if (
context->getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2) {
2235 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Spectral interpolation config references global data '" + spectrum_label +
"' which must be of type HELIOS_TYPE_VEC2 (std::vector<helios::vec2>).");
2239 for (
uint uuid: config.primitive_UUIDs) {
2241 if (!
context->doesPrimitiveExist(uuid)) {
2246 if (
context->doesPrimitiveDataExist(uuid, config.query_data_label.c_str())) {
2248 if (
context->getPrimitiveDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
2249 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Primitive data '" + config.query_data_label +
"' for UUID " + std::to_string(uuid) +
" must be of type HELIOS_TYPE_FLOAT for spectral interpolation.");
2254 context->getPrimitiveData(uuid, config.query_data_label.c_str(), query_value);
2257 size_t nearest_idx = 0;
2258 float min_distance = std::abs(query_value - config.mapping_values[0]);
2259 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
2260 float distance = std::abs(query_value - config.mapping_values[i]);
2261 if (distance < min_distance) {
2262 min_distance = distance;
2268 context->setPrimitiveData(uuid, config.target_data_label.c_str(), config.spectra_labels[nearest_idx]);
2273 for (
uint objID: config.object_IDs) {
2275 if (!
context->doesObjectExist(objID)) {
2280 if (
context->doesObjectDataExist(objID, config.query_data_label.c_str())) {
2282 if (
context->getObjectDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
2283 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Object data '" + config.query_data_label +
"' for object ID " + std::to_string(objID) +
" must be of type HELIOS_TYPE_FLOAT for spectral interpolation.");
2288 context->getObjectData(objID, config.query_data_label.c_str(), query_value);
2291 size_t nearest_idx = 0;
2292 float min_distance = std::abs(query_value - config.mapping_values.at(0));
2293 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
2294 float distance = std::abs(query_value - config.mapping_values.at(i));
2295 if (distance < min_distance) {
2296 min_distance = distance;
2302 std::vector<uint> prim_uuids =
context->getObjectPrimitiveUUIDs(objID);
2303 context->setPrimitiveData(prim_uuids, config.target_data_label.c_str(), config.spectra_labels.at(nearest_idx));
2311 std::map<std::string, std::vector<helios::vec2>> surface_spectra_rho;
2312 std::map<std::string, std::vector<helios::vec2>> surface_spectra_tau;
2313 for (
size_t u = 0; u < Nprimitives; u++) {
2315 uint UUID = context_UUIDs.at(u);
2317 if (
context->doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
2318 if (
context->getPrimitiveDataType(
"reflectivity_spectrum") == HELIOS_TYPE_STRING) {
2319 std::string spectrum_label;
2320 context->getPrimitiveData(UUID,
"reflectivity_spectrum", spectrum_label);
2323 if (surface_spectra_rho.find(spectrum_label) == surface_spectra_rho.end()) {
2324 if (!
context->doesGlobalDataExist(spectrum_label.c_str())) {
2325 if (!spectrum_label.empty()) {
2326 warnings.
addWarning(
"missing_reflectivity_spectrum",
"Primitive spectral reflectivity \"" + spectrum_label +
"\" does not exist. Using default reflectivity of 0.");
2328 std::vector<helios::vec2> data;
2329 surface_spectra_rho.emplace(spectrum_label, data);
2330 }
else if (
context->getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
2332 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
2333 surface_spectra_rho.emplace(spectrum_label, data);
2335 }
else if (
context->getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 &&
context->getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
2336 spectrum_label.clear();
2337 warnings.
addWarning(
"reflectivity_spectrum_wrong_type",
"Object spectral reflectivity \"" + spectrum_label +
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral distribution...");
2343 if (
context->doesPrimitiveDataExist(UUID,
"transmissivity_spectrum")) {
2344 if (
context->getPrimitiveDataType(
"transmissivity_spectrum") == HELIOS_TYPE_STRING) {
2345 std::string spectrum_label;
2346 context->getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
2349 if (surface_spectra_tau.find(spectrum_label) == surface_spectra_tau.end()) {
2350 if (!
context->doesGlobalDataExist(spectrum_label.c_str())) {
2351 if (!spectrum_label.empty()) {
2352 warnings.
addWarning(
"missing_transmissivity_spectrum",
"Primitive spectral transmissivity \"" + spectrum_label +
"\" does not exist. Using default transmissivity of 0.");
2354 std::vector<helios::vec2> data;
2355 surface_spectra_tau.emplace(spectrum_label, data);
2356 }
else if (
context->getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
2358 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
2359 surface_spectra_tau.emplace(spectrum_label, data);
2361 }
else if (
context->getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 &&
context->getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
2362 spectrum_label.clear();
2363 warnings.
addWarning(
"transmissivity_spectrum_wrong_type",
"Object spectral transmissivity \"" + spectrum_label +
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral distribution...");
2371 std::map<std::string, std::vector<std::vector<float>>> rho_unique;
2372 std::map<std::string, std::vector<std::vector<float>>> tau_unique;
2374 std::map<std::string, std::vector<std::vector<std::vector<float>>>> rho_cam_unique;
2375 std::map<std::string, std::vector<std::vector<std::vector<float>>>> tau_cam_unique;
2377 std::vector<std::vector<float>> empty;
2378 empty.resize(Nbands);
2379 for (
uint b = 0; b < Nbands; b++) {
2380 empty.at(b).resize(Nsources, 0);
2382 std::vector<std::vector<std::vector<float>>> empty_cam;
2384 empty_cam.resize(Nbands);
2385 for (
uint b = 0; b < Nbands; b++) {
2386 empty_cam.at(b).resize(Nsources);
2387 for (
uint s = 0; s < Nsources; s++) {
2388 empty_cam.at(b).at(s).resize(Ncameras, 0);
2394 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_rho_vector(surface_spectra_rho.begin(), surface_spectra_rho.end());
2397 for (
const auto &spectrum: spectra_rho_vector) {
2398 rho_unique[spectrum.first] = empty;
2400 rho_cam_unique[spectrum.first] = empty_cam;
2406#pragma omp parallel for schedule(dynamic)
2408 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_rho_vector.size(); spectrum_idx++) {
2409 const auto &spectrum = spectra_rho_vector[spectrum_idx];
2411 for (
uint b = 0; b < Nbands; b++) {
2412 std::string band = band_labels.at(b);
2414 for (
uint s = 0; s < Nsources; s++) {
2417 auto band_it = radiation_bands.find(band);
2418 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
2419 if (!radiation_sources.at(s).source_spectrum.empty()) {
2420 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_source");
2422 float cached_result = getCachedValue(cache_key, found);
2424 rho_unique[spectrum.first][b][s] = cached_result;
2426 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
2427 setCachedValue(cache_key, result);
2428 rho_unique[spectrum.first][b][s] = result;
2432 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_no_source");
2434 float cached_result = getCachedValue(cache_key, found);
2436 rho_unique[spectrum.first][b][s] = cached_result;
2438 float result =
integrateSpectrum(spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y) / (band_it->second.wavebandBounds.y - band_it->second.wavebandBounds.x);
2439 setCachedValue(cache_key, result);
2440 rho_unique[spectrum.first][b][s] = result;
2446 rho_unique[spectrum.first][b][s] = rho_default;
2452 float rho_cam_sum_for_averaging = 0.f;
2453 for (
const auto &camera: cameras) {
2455 if (camera_response_unique.at(cam).at(b).empty()) {
2456 rho_cam_unique[spectrum.first][b][s][cam] = rho_unique[spectrum.first][b][s];
2460 if (!spectrum.second.empty()) {
2461 if (!radiation_sources.at(s).source_spectrum.empty()) {
2462 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_source");
2464 float cached_result = getCachedValue(cache_key, found);
2466 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2467 rho_cam_sum_for_averaging += cached_result;
2469 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
2470 setCachedValue(cache_key, result);
2471 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2472 rho_cam_sum_for_averaging += result;
2475 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_no_source");
2477 float cached_result = getCachedValue(cache_key, found);
2479 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2480 rho_cam_sum_for_averaging += cached_result;
2482 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
2483 setCachedValue(cache_key, result);
2484 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2485 rho_cam_sum_for_averaging += result;
2489 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = rho_default;
2499 if (rho_unique[spectrum.first][b][s] == rho_default && rho_cam_sum_for_averaging > 0 && cam > 0) {
2500 rho_unique[spectrum.first][b][s] = rho_cam_sum_for_averaging / float(cam);
2508 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_tau_vector(surface_spectra_tau.begin(), surface_spectra_tau.end());
2511 for (
const auto &spectrum: spectra_tau_vector) {
2512 tau_unique[spectrum.first] = empty;
2514 tau_cam_unique[spectrum.first] = empty_cam;
2520#pragma omp parallel for schedule(dynamic)
2522 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_tau_vector.size(); spectrum_idx++) {
2523 const auto &spectrum = spectra_tau_vector[spectrum_idx];
2525 for (
uint b = 0; b < Nbands; b++) {
2526 std::string band = band_labels.at(b);
2528 for (
uint s = 0; s < Nsources; s++) {
2531 auto band_it = radiation_bands.find(band);
2532 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
2533 if (!radiation_sources.at(s).source_spectrum.empty()) {
2534 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_source");
2536 float cached_result = getCachedValue(cache_key, found);
2538 tau_unique[spectrum.first][b][s] = cached_result;
2540 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
2541 setCachedValue(cache_key, result);
2542 tau_unique[spectrum.first][b][s] = result;
2545 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_no_source");
2547 float cached_result = getCachedValue(cache_key, found);
2549 tau_unique[spectrum.first][b][s] = cached_result;
2551 float result =
integrateSpectrum(spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y) / (band_it->second.wavebandBounds.y - band_it->second.wavebandBounds.x);
2552 setCachedValue(cache_key, result);
2553 tau_unique[spectrum.first][b][s] = result;
2557 tau_unique[spectrum.first][b][s] = tau_default;
2563 for (
const auto &camera: cameras) {
2565 if (camera_response_unique.at(cam).at(b).empty()) {
2567 tau_cam_unique[spectrum.first][b][s][cam] = tau_unique[spectrum.first][b][s];
2572 if (!spectrum.second.empty()) {
2573 if (!radiation_sources.at(s).source_spectrum.empty()) {
2574 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_source");
2576 float cached_result = getCachedValue(cache_key, found);
2578 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2580 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
2581 setCachedValue(cache_key, result);
2582 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2585 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_no_source");
2587 float cached_result = getCachedValue(cache_key, found);
2589 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2591 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
2592 setCachedValue(cache_key, result);
2593 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2597 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = tau_default;
2608 for (
size_t u = 0; u < Nprimitives; u++) {
2610 uint UUID = context_UUIDs.at(u);
2614 if (type == helios::PRIMITIVE_TYPE_VOXEL) {
2621 std::string spectrum_label;
2622 if (
context->doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
2623 if (
context->getPrimitiveDataType(
"reflectivity_spectrum") == HELIOS_TYPE_STRING) {
2624 context->getPrimitiveData(UUID,
"reflectivity_spectrum", spectrum_label);
2629 for (
const auto &band: band_labels) {
2632 prop =
"reflectivity_" + band;
2634 float rho_s = rho_default;
2635 if (
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
2636 context->getPrimitiveData(UUID, prop.c_str(), rho_s);
2639 for (
uint s = 0; s < Nsources; s++) {
2640 float &rho_val = material_data.
reflectivity[mat_idx(s, u, b)];
2643 if (rho_s != rho_default || spectrum_label.empty() || !
context->doesGlobalDataExist(spectrum_label.c_str()) || rho_unique.find(spectrum_label) == rho_unique.end()) {
2648 for (
uint cam = 0; cam < Ncameras; cam++) {
2655 rho_val = rho_unique.at(spectrum_label).at(b).at(s);
2658 for (
uint cam = 0; cam < Ncameras; cam++) {
2659 material_data.
reflectivity_cam[cam_idx(s, u, b, cam)] = rho_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2666 warnings.
addWarning(
"reflectivity_negative_clamped",
"Reflectivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2667 }
else if (rho_val > 1.f) {
2669 warnings.
addWarning(
"reflectivity_exceeded_clamped",
"Reflectivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2672 scattering_iterations_needed.at(band) =
true;
2674 for (
auto &odata: output_prim_data) {
2675 if (odata ==
"reflectivity") {
2676 context->setPrimitiveData(UUID, (
"reflectivity_" + std::to_string(s) +
"_" + band).c_str(), rho_val);
2686 spectrum_label.resize(0);
2687 if (
context->doesPrimitiveDataExist(UUID,
"transmissivity_spectrum")) {
2688 if (
context->getPrimitiveDataType(
"transmissivity_spectrum") == HELIOS_TYPE_STRING) {
2689 context->getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
2694 for (
const auto &band: band_labels) {
2697 prop =
"transmissivity_" + band;
2699 float tau_s = tau_default;
2700 if (
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
2701 context->getPrimitiveData(UUID, prop.c_str(), tau_s);
2704 for (
uint s = 0; s < Nsources; s++) {
2708 if (tau_s != tau_default || spectrum_label.empty() || !
context->doesGlobalDataExist(spectrum_label.c_str()) || tau_unique.find(spectrum_label) == tau_unique.end()) {
2713 for (
uint cam = 0; cam < Ncameras; cam++) {
2719 tau_val = tau_unique.at(spectrum_label).at(b).at(s);
2722 for (
uint cam = 0; cam < Ncameras; cam++) {
2723 material_data.
transmissivity_cam[cam_idx(s, u, b, cam)] = tau_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2730 warnings.
addWarning(
"transmissivity_negative_clamped",
"Transmissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2731 }
else if (tau_val > 1.f) {
2733 warnings.
addWarning(
"transmissivity_exceeded_clamped",
"Transmissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2736 scattering_iterations_needed.at(band) =
true;
2738 for (
auto &odata: output_prim_data) {
2739 if (odata ==
"transmissivity") {
2740 context->setPrimitiveData(UUID, (
"transmissivity_" + std::to_string(s) +
"_" + band).c_str(), tau_val);
2753 for (
const auto &band: band_labels) {
2755 prop =
"glass_n_" + band;
2756 if (!
context->doesPrimitiveDataExist(UUID, prop.c_str()) ||
context->getPrimitiveDataType(prop.c_str()) != HELIOS_TYPE_FLOAT) {
2762 context->getPrimitiveData(UUID, prop.c_str(), n_s);
2765 warnings.
addWarning(
"glass_n_invalid",
"Refractive index glass_n_" + band +
" must be >= 1. Ignoring glass material for this band.");
2771 std::string KL_prop =
"glass_KL_" + band;
2772 if (
context->doesPrimitiveDataExist(UUID, KL_prop.c_str()) &&
context->getPrimitiveDataType(KL_prop.c_str()) == HELIOS_TYPE_FLOAT) {
2773 context->getPrimitiveData(UUID, KL_prop.c_str(), KL_s);
2776 warnings.
addWarning(
"glass_KL_negative_clamped",
"Absorption glass_KL_" + band +
" cannot be less than 0. Clamping to 0.");
2781 if (
context->doesPrimitiveDataExist(UUID, (
"reflectivity_" + band).c_str()) ||
context->doesPrimitiveDataExist(UUID, (
"transmissivity_" + band).c_str())) {
2782 warnings.
addWarning(
"glass_overrides_constant",
"Primitive has both glass_n_" + band +
" and a constant reflectivity/transmissivity for band " + band +
2783 ". The glass (Fresnel+Bouguer) model takes precedence; the constant value is ignored.");
2786 for (
uint s = 0; s < Nsources; s++) {
2787 material_data.
is_glass[mat_idx(s, u, b)] = 1;
2788 material_data.
glass_n[mat_idx(s, u, b)] = n_s;
2789 material_data.
glass_KL[mat_idx(s, u, b)] = KL_s;
2794 scattering_iterations_needed.at(band) =
true;
2801 for (
const auto &band: band_labels) {
2803 prop =
"emissivity_" + band;
2805 if (
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
2806 context->getPrimitiveData(UUID, prop.c_str(), eps);
2813 warnings.
addWarning(
"emissivity_negative_clamped",
"Emissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2814 }
else if (eps > 1.f) {
2816 warnings.
addWarning(
"emissivity_exceeded_clamped",
"Emissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2819 scattering_iterations_needed.at(band) =
true;
2824 const bool is_sif_band = sif_emission_bands.count(band) > 0;
2826 for (
uint s = 0; s < Nsources; s++) {
2827 float &rho_val = material_data.
reflectivity[mat_idx(s, u, b)];
2832 if (material_data.
is_glass[mat_idx(s, u, b)] != 0) {
2843 if (tau_val + rho_val > 1.f) {
2844 helios_runtime_error(
"ERROR (RadiationModel): reflectivity and transmissivity must sum to less than or equal to 1 to ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
2845 ": tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
".");
2847 }
else if (radiation_bands.at(band).emissionFlag) {
2848 if (eps != 1.f && rho_val == 0 && tau_val == 0) {
2849 rho_val = 1.f - eps;
2850 }
else if (eps + tau_val + rho_val != 1.f && eps > 0.f) {
2851 helios_runtime_error(
"ERROR (RadiationModel): emissivity, transmissivity, and reflectivity must sum to 1 to ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
": eps=" +
2852 std::to_string(eps) +
", tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
". It is also possible that you forgot to disable emission for this band.");
2853 }
else if (radiation_bands.at(band).scatteringDepth == 0 && eps != 1.f) {
2858 }
else if (tau_val + rho_val > 1.f) {
2859 helios_runtime_error(
"ERROR (RadiationModel): transmissivity and reflectivity cannot sum to greater than 1 ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
": eps=" + std::to_string(eps) +
2860 ", tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
". It is also possible that you forgot to disable emission for this band.");
2872 bool specular_exponent_specified =
false;
2873 bool specular_scale_specified =
false;
2875 for (
size_t u = 0; u < Nprimitives; u++) {
2876 uint UUID = context_UUIDs.at(u);
2878 if (
context->doesPrimitiveDataExist(UUID,
"specular_exponent") &&
context->getPrimitiveDataType(
"specular_exponent") == HELIOS_TYPE_FLOAT) {
2881 specular_exponent_specified =
true;
2885 if (
context->doesPrimitiveDataExist(UUID,
"specular_scale") &&
context->getPrimitiveDataType(
"specular_scale") == HELIOS_TYPE_FLOAT) {
2888 specular_scale_specified =
true;
2894 if (specular_exponent_specified) {
2895 if (specular_scale_specified) {
2896 specular_reflection_mode = 2;
2898 specular_reflection_mode = 1;
2901 specular_reflection_mode = 0;
2904 backend->updateMaterials(material_data);
2906 radiativepropertiesneedupdate =
false;
2909 std::cout <<
"done\n";
2913 warnings.
report(std::cerr);
2916std::vector<float> RadiationModel::updateAtmosphericSkyModel(
const std::vector<std::string> &band_labels,
const RadiationCamera &camera) {
2921 size_t Nbands_launch = band_labels.size();
2922 std::vector<float> sky_base_radiances(Nbands_launch, 0.0f);
2926 bool has_atmospheric_data =
2927 context->doesGlobalDataExist(
"atmosphere_pressure_Pa") ||
context->doesGlobalDataExist(
"atmosphere_temperature_K") ||
context->doesGlobalDataExist(
"atmosphere_humidity_rel") ||
context->doesGlobalDataExist(
"atmosphere_turbidity");
2929 if (!has_atmospheric_data) {
2931 return sky_base_radiances;
2936 float pressure_Pa = 101325.f;
2937 float temperature_K = 300.f;
2938 float humidity_rel = 0.5f;
2939 float turbidity = 0.02f;
2941 if (
context->doesGlobalDataExist(
"atmosphere_pressure_Pa")) {
2942 context->getGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
2944 if (
context->doesGlobalDataExist(
"atmosphere_temperature_K")) {
2945 context->getGlobalData(
"atmosphere_temperature_K", temperature_K);
2947 if (
context->doesGlobalDataExist(
"atmosphere_humidity_rel")) {
2948 context->getGlobalData(
"atmosphere_humidity_rel", humidity_rel);
2950 if (
context->doesGlobalDataExist(
"atmosphere_turbidity")) {
2951 context->getGlobalData(
"atmosphere_turbidity", turbidity);
2955 int prague_valid = 0;
2956 if (
context->doesGlobalDataExist(
"prague_sky_valid")) {
2957 context->getGlobalData(
"prague_sky_valid", prague_valid);
2962 if (!radiation_sources.empty()) {
2963 sun_dir = radiation_sources[0].source_position;
2964 sun_dir.normalize();
2968 std::vector<helios::vec4> sky_params(Nbands_launch);
2971 bool use_prague_fallback = (prague_valid != 1);
2972 if (use_prague_fallback) {
2974 std::cerr <<
"WARNING (RadiationModel::updateAtmosphericSkyModel): "
2975 <<
"Prague sky model data not available in Context. "
2976 <<
"Using simple Rayleigh sky fallback. "
2977 <<
"Call SolarPosition::updatePragueSkyModel() for accurate sky radiance." << std::endl;
2981 std::vector<float> wavelengths;
2982 std::vector<float> L_zenith_spectrum;
2983 std::vector<float> circ_str_spectrum;
2984 std::vector<float> circ_width_spectrum;
2985 std::vector<float> horiz_bright_spectrum;
2986 std::vector<float> norm_spectrum;
2988 if (use_prague_fallback) {
2991 const int n_wavelengths = 40;
2992 wavelengths.resize(n_wavelengths);
2993 L_zenith_spectrum.resize(n_wavelengths);
2994 circ_str_spectrum.resize(n_wavelengths);
2995 circ_width_spectrum.resize(n_wavelengths);
2996 horiz_bright_spectrum.resize(n_wavelengths);
2997 norm_spectrum.resize(n_wavelengths);
2999 const float L_base = 0.4f;
3000 const float lambda_ref = 550.0f;
3002 for (
int i = 0; i < n_wavelengths; ++i) {
3003 float lambda = 360.0f + i * 10.0f;
3004 wavelengths[i] = lambda;
3007 float rayleigh_factor = std::pow(lambda_ref / lambda, 4.0f);
3008 L_zenith_spectrum[i] = L_base * rayleigh_factor;
3011 circ_str_spectrum[i] = 0.5f;
3012 circ_width_spectrum[i] = 20.0f;
3013 horiz_bright_spectrum[i] = 1.8f;
3014 norm_spectrum[i] = 0.7f;
3018 std::vector<float> spectral_params;
3019 context->getGlobalData(
"prague_sky_spectral_params", spectral_params);
3021 const int params_per_wavelength = 6;
3022 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
3025 wavelengths.resize(n_wavelengths);
3026 L_zenith_spectrum.resize(n_wavelengths);
3027 circ_str_spectrum.resize(n_wavelengths);
3028 circ_width_spectrum.resize(n_wavelengths);
3029 horiz_bright_spectrum.resize(n_wavelengths);
3030 norm_spectrum.resize(n_wavelengths);
3032 for (
int i = 0; i < n_wavelengths; ++i) {
3033 int base = i * params_per_wavelength;
3034 wavelengths[i] = spectral_params[base + 0];
3035 L_zenith_spectrum[i] = spectral_params[base + 1];
3036 circ_str_spectrum[i] = spectral_params[base + 2];
3037 circ_width_spectrum[i] = spectral_params[base + 3];
3038 horiz_bright_spectrum[i] = spectral_params[base + 4];
3039 norm_spectrum[i] = spectral_params[base + 5];
3044 for (
size_t b = 0; b < Nbands_launch; b++) {
3045 const std::string &band_label = band_labels[b];
3046 if (radiation_bands.find(band_label) == radiation_bands.end()) {
3053 if (band.emissionFlag) {
3058 std::string spectral_response_label =
"uniform";
3059 if (camera.band_spectral_response.find(band_label) != camera.band_spectral_response.end()) {
3060 spectral_response_label = camera.band_spectral_response.at(band_label);
3061 if (spectral_response_label.empty() ||
trim_whitespace(spectral_response_label).empty()) {
3062 spectral_response_label =
"uniform";
3067 std::vector<helios::vec2> camera_response;
3069 if (spectral_response_label ==
"uniform") {
3072 if (wavelength_range.
x <= 0.f || wavelength_range.
y <= 0.f) {
3073 bool bounds_inferred =
false;
3075 if (band_label ==
"red" || band_label ==
"R") {
3077 bounds_inferred =
true;
3078 }
else if (band_label ==
"green" || band_label ==
"G") {
3080 bounds_inferred =
true;
3081 }
else if (band_label ==
"blue" || band_label ==
"B") {
3083 bounds_inferred =
true;
3086 if (!bounds_inferred) {
3087 if (!band.diffuse_spectrum.empty()) {
3088 wavelength_range.
x = band.diffuse_spectrum.front().x;
3089 wavelength_range.
y = band.diffuse_spectrum.back().x;
3091 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera '" + camera.label +
"' band '" + band_label +
"' has uniform spectral response but no wavelength bounds set.");
3100 camera_response = loadSpectralData(spectral_response_label);
3102 if (camera_response.empty()) {
3103 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera spectral response '" + spectral_response_label +
"' not found for camera '" + camera.label +
"' band '" + band_label +
"'.");
3109 float integrated_L_zenith = integrateOverResponse(wavelengths, L_zenith_spectrum, camera_response);
3113 float integrated_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, camera_response);
3114 float integrated_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, camera_response);
3115 float integrated_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, camera_response);
3118 float integrated_norm = computeAngularNormalization(integrated_circ_str, integrated_circ_width, integrated_horiz_bright);
3123 float base_radiance_for_gpu = integrated_L_zenith / std::max(integrated_norm, 0.1f);
3125 sky_base_radiances[b] = base_radiance_for_gpu;
3126 sky_params[b] =
helios::make_vec4(integrated_circ_str, integrated_circ_width, integrated_horiz_bright, integrated_norm);
3130 return sky_base_radiances;
3133void RadiationModel::updatePragueParametersForGeneralDiffuse(
const std::vector<std::string> &band_labels) {
3139 int prague_valid = 0;
3140 if (!
context->doesGlobalDataExist(
"prague_sky_valid") || (
context->getGlobalData(
"prague_sky_valid", prague_valid), prague_valid != 1)) {
3146 std::vector<float> spectral_params;
3147 context->getGlobalData(
"prague_sky_spectral_params", spectral_params);
3150 const int params_per_wavelength = 6;
3151 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
3153 std::vector<float> wavelengths(n_wavelengths);
3154 std::vector<float> L_zenith_spectrum(n_wavelengths);
3155 std::vector<float> circ_str_spectrum(n_wavelengths);
3156 std::vector<float> circ_width_spectrum(n_wavelengths);
3157 std::vector<float> horiz_bright_spectrum(n_wavelengths);
3158 std::vector<float> norm_spectrum(n_wavelengths);
3160 for (
int i = 0; i < n_wavelengths; ++i) {
3161 int base = i * params_per_wavelength;
3162 wavelengths[i] = spectral_params[base + 0];
3163 L_zenith_spectrum[i] = spectral_params[base + 1];
3164 circ_str_spectrum[i] = spectral_params[base + 2];
3165 circ_width_spectrum[i] = spectral_params[base + 3];
3166 horiz_bright_spectrum[i] = spectral_params[base + 4];
3167 norm_spectrum[i] = spectral_params[base + 5];
3172 context->getGlobalData(
"prague_sky_sun_direction", sun_dir);
3175 for (
const auto &label: band_labels) {
3179 if (band.diffuseExtinction > 0.0f) {
3184 std::vector<helios::vec2> band_spectrum = band.diffuse_spectrum;
3185 if (band_spectrum.empty()) {
3187 float lambda_min = band.wavebandBounds.x;
3188 float lambda_max = band.wavebandBounds.y;
3189 if (lambda_min > 0 && lambda_max > lambda_min) {
3190 band_spectrum = {{lambda_min, 1.0f}, {lambda_max, 1.0f}};
3194 if (band_spectrum.empty()) {
3200 float int_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, band_spectrum);
3201 float int_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, band_spectrum);
3202 float int_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, band_spectrum);
3205 float int_norm = computeAngularNormalization(int_circ_str, int_circ_width, int_horiz_bright);
3208 band.diffusePragueParams =
helios::make_vec4(int_circ_str, int_circ_width, int_horiz_bright, int_norm);
3209 band.diffusePeakDir = sun_dir;
3213float RadiationModel::integrateOverResponse(
const std::vector<float> &wavelengths,
const std::vector<float> &values,
const std::vector<helios::vec2> &camera_response)
const {
3215 if (wavelengths.empty() || camera_response.empty()) {
3221 double integrated_radiance = 0.0;
3224 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
3225 float lambda1 = camera_response[i].x;
3226 float lambda2 = camera_response[i + 1].x;
3229 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
3233 float r1 = camera_response[i].y;
3234 float r2 = camera_response[i + 1].y;
3240 if (lambda1 <= wavelengths.front()) {
3241 v1 = values.front();
3242 }
else if (lambda1 >= wavelengths.back()) {
3245 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3246 size_t idx = std::distance(wavelengths.begin(), it);
3249 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3250 v1 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
3254 if (lambda2 <= wavelengths.front()) {
3255 v2 = values.front();
3256 }
else if (lambda2 >= wavelengths.back()) {
3259 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3260 size_t idx = std::distance(wavelengths.begin(), it);
3263 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3264 v2 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
3267 float dlambda = lambda2 - lambda1;
3271 integrated_radiance += 0.5 * (v1 * r1 + v2 * r2) * dlambda;
3275 return static_cast<float>(integrated_radiance);
3278float RadiationModel::weightedAverageOverResponse(
const std::vector<float> &wavelengths,
const std::vector<float> ¶m_values,
const std::vector<float> &weight_values,
const std::vector<helios::vec2> &camera_response)
const {
3280 if (wavelengths.empty() || camera_response.empty()) {
3286 double weighted_sum = 0.0;
3287 double total_weight = 0.0;
3289 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
3290 float lambda1 = camera_response[i].x;
3291 float lambda2 = camera_response[i + 1].x;
3293 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
3297 float r1 = camera_response[i].y;
3298 float r2 = camera_response[i + 1].y;
3302 if (lambda1 <= wavelengths.front()) {
3303 p1 = param_values.front();
3304 }
else if (lambda1 >= wavelengths.back()) {
3305 p1 = param_values.back();
3307 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3308 size_t idx = std::distance(wavelengths.begin(), it);
3311 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3312 p1 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
3315 if (lambda2 <= wavelengths.front()) {
3316 p2 = param_values.front();
3317 }
else if (lambda2 >= wavelengths.back()) {
3318 p2 = param_values.back();
3320 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3321 size_t idx = std::distance(wavelengths.begin(), it);
3324 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3325 p2 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
3330 if (lambda1 <= wavelengths.front()) {
3331 w1 = weight_values.front();
3332 }
else if (lambda1 >= wavelengths.back()) {
3333 w1 = weight_values.back();
3335 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3336 size_t idx = std::distance(wavelengths.begin(), it);
3339 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3340 w1 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
3343 if (lambda2 <= wavelengths.front()) {
3344 w2 = weight_values.front();
3345 }
else if (lambda2 >= wavelengths.back()) {
3346 w2 = weight_values.back();
3348 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3349 size_t idx = std::distance(wavelengths.begin(), it);
3352 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3353 w2 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
3356 float dlambda = lambda2 - lambda1;
3359 weighted_sum += 0.5 * (p1 * w1 * r1 + p2 * w2 * r2) * dlambda;
3360 total_weight += 0.5 * (w1 * r1 + w2 * r2) * dlambda;
3364 if (total_weight > 1e-10) {
3365 return static_cast<float>(weighted_sum / total_weight);
3370float RadiationModel::computeAngularNormalization(
float circ_str,
float circ_width,
float horiz_bright)
const {
3373 float integral = 0.0f;
3378 for (
int j = 0; j < N; ++j) {
3379 for (
int i = 0; i < N; ++i) {
3380 float theta = 0.5f * float(M_PI) * (i + 0.5f) / N;
3381 float phi = 2.0f * float(M_PI) * (j + 0.5f) / N;
3386 float cos_gamma = std::max(-1.0f, std::min(1.0f, dir.
x * sun_dir.
x + dir.
y * sun_dir.
y + dir.
z * sun_dir.
z));
3387 float gamma = std::acos(cos_gamma) * 180.0f / float(M_PI);
3390 float cos_theta = std::max(0.0f, dir.
z);
3391 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
3392 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
3394 float pattern = circ_term * horizon_term;
3397 integral += pattern * std::cos(theta) * std::sin(theta) * (float(M_PI) / (2.0f * N)) * (2.0f * float(M_PI) / N);
3401 return 1.0f / std::max(integral, 1e-10f);
3404std::vector<helios::vec2> RadiationModel::loadSpectralData(
const std::string &global_data_label)
const {
3406 std::vector<helios::vec2> spectrum;
3408 if (!
context->doesGlobalDataExist(global_data_label.c_str())) {
3411 bool data_found =
false;
3412 for (
const auto &file: spectral_library_files) {
3414 context->loadXML(file.c_str(),
true);
3421 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' could not be found.");
3425 if (
context->getGlobalDataType(global_data_label.c_str()) != HELIOS_TYPE_VEC2) {
3426 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is not of type HELIOS_TYPE_VEC2.");
3429 context->getGlobalData(global_data_label.c_str(), spectrum);
3432 if (spectrum.empty()) {
3433 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is empty.");
3435 for (
auto s = 0; s < spectrum.size(); s++) {
3437 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
3438 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Wavelengths must increase monotonically.");
3441 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
3442 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Wavelength value of " + std::to_string(spectrum.at(s).x) +
" appears to be erroneous.");
3445 if (spectrum.at(s).y < 0) {
3446 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Flux value at wavelength of " + std::to_string(spectrum.at(s).x) +
" appears is negative.");
3454 std::vector<std::string> labels{label};
3466 if (radiativepropertiesneedupdate) {
3467 for (
auto &kv : excitation_sets) {
3468 kv.second.populated =
false;
3487 std::vector<std::string> effective_label = label;
3488 std::vector<ExcitationSet *> piggybacked_sets;
3489 if (!excitation_sets.empty()) {
3490 bool label_has_sif_emission =
false;
3491 bool label_has_excitation_band =
false;
3492 for (
const auto &b : label) {
3493 if (sif_emission_bands.count(b) > 0) label_has_sif_emission =
true;
3494 if (b.size() >= 9 && b.compare(0, 9,
"_SIF_exc_") == 0) label_has_excitation_band =
true;
3496 if (!label_has_sif_emission && !label_has_excitation_band) {
3497 for (
auto &kv : excitation_sets) {
3498 ExcitationSet &exc = kv.second;
3499 if (exc.populated)
continue;
3500 for (
const auto &eb : exc.band_labels) {
3501 effective_label.push_back(eb);
3503 piggybacked_sets.push_back(&exc);
3510 std::vector<std::string> band_labels;
3511 for (
auto &band: radiation_bands) {
3512 if (std::find(effective_label.begin(), effective_label.end(), band.first) != effective_label.end()) {
3513 band_labels.push_back(band.first);
3518 if (
context->getPrimitiveCount() == 0) {
3519 std::cerr <<
"WARNING (RadiationModel::runBand): No geometry was added to the context. There is nothing to simulate...exiting." << std::endl;
3524 if (!isgeometryinitialized) {
3529 for (
const std::string &band: label) {
3531 helios_runtime_error(
"ERROR (RadiationModel::runBand): Cannot run band " + band +
" because it is not a valid band. Use addRadiationBand() function to add the band.");
3536 if (radiation_sources.empty()) {
3553 bool dispatch_has_sif_band =
false;
3554 for (
const auto &b : band_labels) {
3555 if (sif_emission_bands.count(b) > 0) {
3556 dispatch_has_sif_band =
true;
3560 if (dispatch_has_sif_band) {
3570 for (
const auto &b : band_labels) {
3571 if (sif_emission_bands.count(b) > 0) {
3572 auto &band = radiation_bands.at(b);
3573 if (!band.emissionFlag) {
3574 sif_warn.
addWarning(
"sif_emission_reenabled",
3575 "Band '" + b +
"' is bound to a SIF camera but has emission disabled. "
3576 "Re-enabling emission for this dispatch — SIF cameras require emission "
3577 "enabled so that Fluspect-B source flux is traced through the emission "
3578 "loop. Remove the disableEmission(\"" + b +
"\") call to silence this warning.");
3579 band.emissionFlag =
true;
3583 sif_warn.
report(std::cerr);
3591 runExcitationBands();
3592 for (
const auto &b : band_labels) {
3593 if (sif_emission_bands.count(b) > 0) {
3594 computeSIFEmission(b);
3600 for (
auto &source: radiation_sources) {
3601 if (!source.source_spectrum_label.empty() && source.source_spectrum_label !=
"none") {
3602 uint64_t current_version =
context->getGlobalDataVersion(source.source_spectrum_label.c_str());
3603 if (current_version != source.source_spectrum_version) {
3605 source.source_spectrum = loadSpectralData(source.source_spectrum_label);
3606 source.source_spectrum_version = current_version;
3607 radiativepropertiesneedupdate =
true;
3613 if (!global_diffuse_spectrum_label.empty() && global_diffuse_spectrum_label !=
"none") {
3614 uint64_t current_version =
context->getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
3615 if (current_version != global_diffuse_spectrum_version) {
3617 global_diffuse_spectrum = loadSpectralData(global_diffuse_spectrum_label);
3618 global_diffuse_spectrum_version = current_version;
3620 for (
auto &band_pair: radiation_bands) {
3621 band_pair.second.diffuse_spectrum = global_diffuse_spectrum;
3623 radiativepropertiesneedupdate =
true;
3627 if (radiativepropertiesneedupdate) {
3629 updateRadiativeProperties();
3633 buildMaterialData();
3634 backend->updateMaterials(material_data);
3639 backend->updateSources(source_data);
3642 size_t Nbands_launch = band_labels.size();
3643 size_t Nbands_global = radiation_bands.size();
3646 std::vector<char> band_launch_flag(Nbands_global);
3648 for (
auto &band: radiation_bands) {
3649 if (std::find(band_labels.begin(), band_labels.end(), band.first) != band_labels.end()) {
3650 band_launch_flag.at(bb) = 1;
3656 size_t Nobjects = primitiveID.size();
3657 size_t Nprimitives = context_UUIDs.size();
3658 uint Nsources = radiation_sources.size();
3659 uint Ncameras = cameras.size();
3665 std::vector<uint> scattering_depth(Nbands_launch);
3666 bool scatteringenabled =
false;
3667 for (
auto b = 0; b < Nbands_launch; b++) {
3668 scattering_depth.at(b) = radiation_bands.at(band_labels.at(b)).scatteringDepth;
3669 if (scattering_depth.at(b) > 0) {
3670 scatteringenabled =
true;
3681 for (
int b = 0; b < Nbands_launch; b++) {
3682 const std::string &bname = band_labels.at(b);
3683 const bool is_sif_excitation_band = bname.size() >= 9 && bname.compare(0, 9,
"_SIF_exc_") == 0;
3684 if (scattering_depth.at(b) == 0 && scattering_iterations_needed.at(bname) && !is_sif_excitation_band) {
3685 scattering_disabled_warnings.
addWarning(
"scattering_disabled_for_band",
3686 "Surface radiative properties for band " + bname +
3687 " are set to non-default values, but scattering iterations are disabled. Surface radiative properties will be ignored unless scattering depth is non-zero.");
3690 scattering_disabled_warnings.
report(std::cerr);
3693 std::vector<float> diffuse_flux(Nbands_launch);
3694 bool diffuseenabled =
false;
3695 for (
auto b = 0; b < Nbands_launch; b++) {
3697 if (diffuse_flux.at(b) > 0.f) {
3698 diffuseenabled =
true;
3704 std::vector<float> camera_sky_radiance(Nbands_launch, 0.0f);
3708 if (diffuseenabled) {
3709 updatePragueParametersForGeneralDiffuse(band_labels);
3713 std::vector<float> diffuse_extinction(Nbands_launch, 0);
3714 if (diffuseenabled) {
3715 for (
auto b = 0; b < Nbands_launch; b++) {
3716 diffuse_extinction.at(b) = radiation_bands.at(band_labels.at(b)).diffuseExtinction;
3722 std::vector<float> diffuse_dist_norm(Nbands_launch, 0);
3723 if (diffuseenabled) {
3724 for (
auto b = 0; b < Nbands_launch; b++) {
3725 diffuse_dist_norm.at(b) = radiation_bands.at(band_labels.at(b)).diffuseDistNorm;
3731 std::vector<helios::vec3> diffuse_peak_dir(Nbands_launch);
3732 if (diffuseenabled) {
3733 for (
auto b = 0; b < Nbands_launch; b++) {
3734 helios::vec3 peak_dir = radiation_bands.at(band_labels.at(b)).diffusePeakDir;
3742 std::vector<helios::vec4> prague_params(Nbands_launch);
3743 if (diffuseenabled) {
3744 for (
auto b = 0; b < Nbands_launch; b++) {
3745 const auto ¶ms = radiation_bands.at(band_labels.at(b)).diffusePragueParams;
3746 prague_params.at(b) =
helios::make_vec4(params.x, params.y, params.z, params.w);
3752 bool emissionenabled =
false;
3753 for (
auto b = 0; b < Nbands_launch; b++) {
3754 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
3755 emissionenabled =
true;
3760 size_t directRayCount = 0;
3761 for (
const auto &band: label) {
3762 if (radiation_bands.at(band).directRayCount > directRayCount) {
3763 directRayCount = radiation_bands.at(band).directRayCount;
3768 size_t diffuseRayCount = 0;
3769 for (
const auto &band: label) {
3770 if (radiation_bands.at(band).diffuseRayCount > diffuseRayCount) {
3771 diffuseRayCount = radiation_bands.at(band).diffuseRayCount;
3776 size_t scatteringDepth = 0;
3777 for (
const auto &band: label) {
3778 if (radiation_bands.at(band).scatteringDepth > scatteringDepth) {
3779 scatteringDepth = radiation_bands.at(band).scatteringDepth;
3784 backend->zeroRadiationBuffers(Nbands_launch);
3786 std::vector<float> TBS_top, TBS_bottom;
3787 TBS_top.resize(Nbands_launch * Nprimitives, 0);
3788 TBS_bottom = TBS_top;
3790 std::map<std::string, std::vector<std::vector<float>>> radiation_in_camera;
3792 size_t maxRays = 1024 * 1024 * 1024;
3798 bool rundirect =
false;
3799 for (
uint s = 0; s < Nsources; s++) {
3800 for (
uint b = 0; b < Nbands_launch; b++) {
3818 std::vector<std::vector<float>> source_flux_values(Nsources, std::vector<float>(Nbands_launch, 0.f));
3819 for (
uint s = 0; s < Nsources; s++) {
3820 for (
uint b = 0; b < Nbands_launch; b++) {
3821 source_flux_values.at(s).at(b) =
getSourceFlux(s, band_labels.at(b));
3824 backend->uploadSourceFluxes(
flatten(source_flux_values));
3827 if (Nsources > 0 && rundirect) {
3831 std::vector<std::vector<float>> fluxes;
3832 fluxes.resize(Nsources);
3833 std::vector<helios::vec3> positions(Nsources);
3834 std::vector<helios::vec2> widths(Nsources);
3835 std::vector<helios::vec3> rotations(Nsources);
3836 std::vector<uint> types(Nsources);
3839 for (
const auto &source: radiation_sources) {
3841 fluxes.at(s).resize(Nbands_launch);
3843 for (
auto b = 0; b < label.size(); b++) {
3847 positions.at(s) =
helios::make_vec3(source.source_position.x, source.source_position.y, source.source_position.z);
3849 rotations.at(s) =
helios::make_vec3(source.source_rotation.x, source.source_rotation.y, source.source_rotation.z);
3850 types.at(s) = source.source_type;
3856 backend->uploadSourceFluxes(
flatten(fluxes));
3864 std::vector<float> source_fluxes_cam;
3865 source_fluxes_cam.resize(Nsources * Nbands_launch * Ncameras, 1.0f);
3867 for (
uint s = 0; s < Nsources; s++) {
3871 for (
const auto &camera: cameras) {
3872 for (
uint b = 0; b < Nbands_launch; b++) {
3873 std::string band_label = band_labels.at(b);
3876 float weight = 1.0f;
3879 if (camera.second.band_spectral_response.find(band_label) != camera.second.band_spectral_response.end()) {
3880 std::string response_label = camera.second.band_spectral_response.at(band_label);
3882 if (!response_label.empty() && response_label !=
"uniform" &&
context->doesGlobalDataExist(response_label.c_str()) &&
context->getGlobalDataType(response_label.c_str()) ==
helios::HELIOS_TYPE_VEC2 &&
3883 source.source_spectrum.size() > 0) {
3886 std::vector<helios::vec2> camera_response;
3887 context->getGlobalData(response_label.c_str(), camera_response);
3890 helios::vec2 wavelength_range = radiation_bands.at(band_label).wavebandBounds;
3893 if (wavelength_range.
x == 0 && wavelength_range.
y == 0) {
3894 wavelength_range.
x = fmax(source.source_spectrum.front().x, camera_response.front().x);
3895 wavelength_range.
y = fmin(source.source_spectrum.back().x, camera_response.back().x);
3904 source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam] = weight;
3911 for (
uint s = 0; s < Nsources; s++) {
3912 source_data[s].fluxes_cam.clear();
3913 for (
uint b = 0; b < Nbands_launch; b++) {
3914 for (
uint cam = 0; cam < Ncameras; cam++) {
3915 source_data[s].fluxes_cam.push_back(source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam]);
3919 backend->updateSources(source_data);
3925 std::cout <<
"Performing primary direct radiation ray trace for bands ";
3926 for (
const auto &band: label) {
3927 std::cout << band <<
", ";
3929 std::cout <<
"..." << std::flush;
3936 params.rays_per_primitive = directRayCount;
3937 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3938 params.num_bands_global = Nbands_global;
3939 params.num_bands_launch = Nbands_launch;
3940 params.specular_reflection_enabled = specular_reflection_mode;
3943 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
3948 if (Ncameras > 0 && scatteringenabled) {
3949 backend->zeroCameraScatterBuffers(Nbands_launch);
3952 backend->launchDirectRays(params);
3955 std::cout <<
"done." << std::endl;
3963 std::vector<float> flux_top, flux_bottom;
3964 flux_top.resize(Nbands_launch * Nprimitives, 0);
3965 flux_bottom = flux_top;
3968 std::vector<float> scatter_top_cam;
3969 std::vector<float> scatter_bottom_cam;
3971 scatter_top_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3972 scatter_bottom_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3975 if (scatteringenabled && rundirect) {
3978 backend->getRadiationResults(scatter_results);
3989 backend->zeroCameraScatterBuffers(Nbands_launch);
3996 for (
size_t i = 0; i < Nprimitives; i++) {
3997 uint UUID = context_UUIDs.at(i);
3998 uint twosided =
context->getPrimitiveTwosidedFlag(UUID, 1);
4000 if (twosided == 0) {
4001 for (
size_t b = 0; b < Nbands_launch; b++) {
4002 size_t ind = rad_indexer(i, b);
4003 float total = flux_top[ind] + flux_bottom[ind];
4004 flux_top[ind] = total;
4005 flux_bottom[ind] = total;
4011 backend->uploadRadiationOut(flux_top, flux_bottom);
4012 backend->zeroScatterBuffers();
4017 if (emissionenabled || diffuseenabled) {
4020 if (emissionenabled) {
4022 float eps, temperature;
4027 for (
auto b = 0; b < Nbands_launch; b++) {
4029 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
4030 std::string prop =
"emissivity_" + band_labels.at(b);
4033 auto sif_band_it = sif_emission_buffer.find(band_labels.at(b));
4034 auto sif_band_bot_it = sif_emission_buffer_bottom.find(band_labels.at(b));
4035 const bool have_sif_band = (sif_band_it != sif_emission_buffer.end());
4036 const bool have_sif_band_bot = (sif_band_bot_it != sif_emission_buffer_bottom.end());
4037 for (
size_t u = 0; u < Nprimitives; u++) {
4039 size_t ind = emission_indexer(u, b);
4040 uint p = context_UUIDs.at(u);
4042 float sif_bottom_flux = 0.f;
4043 bool used_sif =
false;
4044 if (have_sif_band) {
4045 auto sif_uuid_it = sif_band_it->second.find(p);
4046 if (sif_uuid_it != sif_band_it->second.end()) {
4047 out_top = sif_uuid_it->second;
4051 if (used_sif && have_sif_band_bot) {
4052 auto sif_bot_it = sif_band_bot_it->second.find(p);
4053 if (sif_bot_it != sif_band_bot_it->second.end()) {
4054 sif_bottom_flux = sif_bot_it->second;
4061 if (have_sif_band) {
4064 if (
context->doesPrimitiveDataExist(p, prop.c_str())) {
4065 context->getPrimitiveData(p, prop.c_str(), eps);
4069 if (scattering_depth.at(b) == 0 && eps != 1.f) {
4072 if (
context->doesPrimitiveDataExist(p,
"temperature")) {
4073 context->getPrimitiveData(p,
"temperature", temperature);
4074 if (temperature < 0) {
4075 temperature = temperature_default;
4078 temperature = temperature_default;
4080 out_top = sigma * eps * pow(temperature, 4);
4083 flux_top.at(ind) += out_top;
4085 scatter_top_cam[ind] += out_top;
4088 uint twosided_flag =
context->getPrimitiveTwosidedFlag(p, 1);
4089 if (twosided_flag != 0) {
4093 flux_bottom.at(ind) += sif_bottom_flux;
4095 scatter_bottom_cam[ind] += sif_bottom_flux;
4098 flux_bottom.at(ind) += flux_top.at(ind);
4100 scatter_bottom_cam[ind] += out_top;
4112 backend->uploadCameraScatterBuffers(scatter_top_cam, scatter_bottom_cam);
4118 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4119 uint rays_per_primitive = n * n;
4122 std::cout <<
"Performing primary diffuse radiation ray trace for bands ";
4123 for (
const auto &band: label) {
4124 std::cout << band <<
" ";
4126 std::cout <<
"..." << std::flush;
4135 params.rays_per_primitive = rays_per_primitive;
4136 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4137 params.current_band = 0;
4138 params.num_bands_global = Nbands_global;
4139 params.num_bands_launch = Nbands_launch;
4140 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
4142 params.scattering_iteration = 0;
4143 params.max_scatters = scatteringDepth;
4152 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
4153 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
4154 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
4161 backend->launchDiffuseRays(params);
4165 backend->launchDiffuseRays(params);
4170 backend->getRadiationResults(primary_results);
4176 backend->zeroCameraScatterBuffers(Nbands_launch);
4180 std::cout <<
"done." << std::endl;
4187 if (scatteringenabled && (emissionenabled || diffuseenabled) && !rundirect) {
4188 backend->copyScatterToRadiation();
4189 backend->zeroScatterBuffers();
4192 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
4194 for (
auto b = 0; b < Nbands_launch; b++) {
4195 diffuse_flux.at(b) = 0.f;
4199 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4200 uint rays_per_primitive = n * n;
4204 std::vector<char> scatter_band_flags = band_launch_flag;
4206 for (s = 0; s < scatteringDepth; s++) {
4208 std::cout <<
"Performing scattering ray trace (iteration " << s + 1 <<
" of " << scatteringDepth <<
")..." << std::flush;
4212 int active_bands = 0;
4213 for (
uint b_global = 0; b_global < Nbands_global; b_global++) {
4215 if (scatter_band_flags.at(b_global) == 0) {
4220 const std::string &bname = band_labels.at(b);
4221 uint depth = radiation_bands.at(bname).scatteringDepth;
4222 if (s + 1 > depth) {
4229 const bool is_sif_excitation_band = bname.size() >= 9 && bname.compare(0, 9,
"_SIF_exc_") == 0;
4230 if (message_flag && !is_sif_excitation_band) {
4231 std::cout <<
"Skipping band " << bname <<
" for scattering launch " << s + 1 << std::endl;
4233 scatter_band_flags.at(b_global) = 0;
4242 if (s > 0 || (emissionenabled && rundirect)) {
4243 backend->copyScatterToRadiation();
4245 backend->zeroScatterBuffers();
4249 backend->getRadiationResults(scatter_results);
4258 params.rays_per_primitive = rays_per_primitive;
4259 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4260 params.current_band = 0;
4261 params.num_bands_global = Nbands_global;
4262 params.num_bands_launch = Nbands_launch;
4263 std::vector<bool> band_flags(scatter_band_flags.begin(), scatter_band_flags.end());
4265 params.scattering_iteration = s;
4266 params.max_scatters = scatteringDepth;
4273 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
4274 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
4275 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
4286 backend->launchDiffuseRays(params);
4290 backend->launchDiffuseRays(params);
4295 backend->getRadiationResults(post_launch);
4301 backend->zeroCameraScatterBuffers(Nbands_launch);
4305 std::cout <<
"\r \r" << std::flush;
4310 std::cout <<
"Performing scattering ray trace...done." << std::endl;
4320 if (Ncameras > 0 && scatteringenabled) {
4321 backend->uploadRadiationOut(scatter_top_cam, scatter_bottom_cam);
4326 vec3 sun_dir(0, 0, 1);
4327 std::vector<float> solar_radiances(Nbands_launch, 0.0f);
4328 bool has_sun_source =
false;
4330 for (
size_t s = 0; s < radiation_sources.size(); s++) {
4332 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4334 sun_dir = source.source_position;
4336 has_sun_source =
true;
4339 for (
size_t b = 0; b < Nbands_launch; b++) {
4342 if (source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4345 solar_radiances[b] = flux /
M_PI;
4349 const float solar_solid_angle = 6.74e-5f;
4350 solar_radiances[b] = flux / solar_solid_angle;
4357 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
4359 if (diffuseenabled) {
4360 for (
auto b = 0; b < Nbands_launch; b++) {
4365 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4369 if (!cameras.empty() && prague_params.size() == Nbands_launch) {
4371 std::vector<float> sky_for_backend = updateAtmosphericSkyModel(band_labels, cameras.begin()->second);
4375 std::vector<float> camera_diffuse_flux(Nbands_launch, 0.f);
4376 std::vector<uint32_t> band_emission_flag(Nbands_launch, 0u);
4377 for (
size_t b = 0; b < Nbands_launch; b++) {
4379 band_emission_flag[b] = radiation_bands.at(band_labels[b]).emissionFlag ? 1u : 0u;
4383 backend->updateSkyModel(prague_params, sky_for_backend, sun_dir, solar_radiances,
4384 has_sun_source ? 0.999989f : 0.0f,
4385 camera_diffuse_flux, band_emission_flag);
4389 for (
auto &camera: cameras) {
4397 bool camera_in_dispatch =
false;
4398 for (
const auto &camera_band: camera.second.band_labels) {
4399 if (std::find(band_labels.begin(), band_labels.end(), camera_band) != band_labels.end()) {
4400 camera_in_dispatch =
true;
4404 if (!camera_in_dispatch) {
4410 if (camera.second.antialiasing_samples > maxRays) {
4411 helios_runtime_error(
"ERROR (runBand): Camera '" + camera.second.label +
"' antialiasing samples (" + std::to_string(camera.second.antialiasing_samples) +
") exceeds OptiX maximum launch size (" + std::to_string(maxRays) +
4412 "). Reduce antialiasing samples.");
4416 std::vector<CameraTile> tiles = computeCameraTiles(camera.second, maxRays);
4418 if (message_flag && tiles.size() > 1) {
4419 std::cout <<
"Camera '" << camera.second.label <<
"' requires " << tiles.size() <<
" tiles" << std::endl;
4425 std::vector<float> cam_weights(Nsources * Nbands_launch, 1.0f);
4426 for (
uint s = 0; s < Nsources; s++) {
4427 for (
uint b = 0; b < Nbands_launch; b++) {
4428 if (!source_data[s].fluxes_cam.empty() && source_data[s].fluxes_cam.size() == Nbands_launch * Ncameras) {
4429 cam_weights[s * Nbands_launch + b] = source_data[s].fluxes_cam[b * Ncameras + cam];
4433 backend->uploadSourceFluxesCam(cam_weights);
4436 for (
size_t tile_idx = 0; tile_idx < tiles.size(); tile_idx++) {
4437 const auto &tile = tiles[tile_idx];
4440 helios::RayTracingLaunchParams params = buildCameraLaunchParams(camera.second, cam, camera.second.antialiasing_samples, tile.resolution, tile.offset);
4443 params.num_bands_launch = Nbands_launch;
4444 params.num_bands_global = Nbands_global;
4445 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4446 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
4451 if (tiles.size() == 1) {
4452 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"..." << std::flush;
4454 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << tiles.size() <<
")..." << std::flush;
4459 backend->launchCameraRays(params);
4462 if (tiles.size() > 1) {
4463 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
4465 std::cout <<
"done." << std::endl;
4470 if (message_flag && tiles.size() > 1) {
4471 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"...done." << std::endl;
4475 std::vector<float> radiation_camera;
4476 std::vector<uint> dummy_labels;
4477 std::vector<float> dummy_depths;
4478 backend->getCameraResults(radiation_camera, dummy_labels, dummy_depths, cam, camera.second.resolution);
4481 std::string camera_label = camera.second.label;
4483 for (
auto b = 0; b < Nbands_launch; b++) {
4485 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.x * camera.second.resolution.y);
4487 std::string data_label =
"camera_" + camera_label +
"_" + band_labels.at(b);
4489 for (
auto p = 0; p < camera.second.resolution.x * camera.second.resolution.y; p++) {
4490 camera.second.pixel_data.at(band_labels.at(b)).at(p) = radiation_camera.at(p * Nbands_launch + b);
4493 context->setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
4500 pixel_label_camera.antialiasing_samples = 1;
4501 std::vector<CameraTile> pixel_tiles = computeCameraTiles(pixel_label_camera, maxRays);
4504 backend->zeroCameraPixelBuffers(camera.second.resolution);
4507 for (
size_t tile_idx = 0; tile_idx < pixel_tiles.size(); tile_idx++) {
4508 const auto &tile = pixel_tiles[tile_idx];
4513 tile.resolution, tile.offset);
4517 if (pixel_tiles.size() == 1) {
4518 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"..." << std::flush;
4520 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << pixel_tiles.size() <<
")..." << std::flush;
4525 backend->launchPixelLabelRays(params);
4528 if (pixel_tiles.size() > 1) {
4529 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
4531 std::cout <<
"done." << std::endl;
4536 if (message_flag && pixel_tiles.size() > 1) {
4537 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"...done." << std::endl;
4541 std::vector<float> dummy_pixel_data;
4542 backend->getCameraResults(dummy_pixel_data, camera.second.pixel_label_UUID, camera.second.pixel_depth, cam, camera.second.resolution);
4549 std::string data_label =
"camera_" + camera_label +
"_pixel_UUID";
4550 context->setGlobalData(data_label.c_str(), camera.second.pixel_label_UUID);
4552 data_label =
"camera_" + camera_label +
"_pixel_depth";
4553 context->setGlobalData(data_label.c_str(), camera.second.pixel_depth);
4559 for (
auto &camera: cameras) {
4560 for (
auto b = 0; b < Nbands_launch; b++) {
4561 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.x * camera.second.resolution.y);
4563 std::string data_label =
"camera_" + camera.second.label +
"_" + band_labels.at(b);
4565 for (
auto p = 0; p < camera.second.resolution.x * camera.second.resolution.y; p++) {
4566 camera.second.pixel_data.at(band_labels.at(b)).at(p) = 0.f;
4568 context->setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
4575 for (
auto &camera: cameras) {
4576 camera.second.applyCameraExposure(
context);
4580 for (
auto &camera: cameras) {
4581 camera.second.applyCameraWhiteBalance(
context);
4588 backend->getRadiationResults(results);
4590 std::vector<float> radiation_flux_data = results.
radiation_in;
4596 std::vector<uint> UUIDs_context_all =
context->getAllUUIDs();
4601 for (
auto b = 0; b < Nbands_launch; b++) {
4603 std::string prop =
"radiation_flux_" + band_labels.at(b);
4604 std::vector<float>
R(Nprimitives);
4605 for (
size_t u = 0; u < Nprimitives; u++) {
4607 size_t ind = result_indexer(u, b);
4608 R.at(u) = radiation_flux_data.at(ind) + TBS_top.at(ind) + TBS_bottom.at(ind);
4610 context->setPrimitiveData(context_UUIDs, prop.c_str(),
R);
4612 if (UUIDs_context_all.size() != Nprimitives) {
4613 for (
uint UUID: UUIDs_context_all) {
4614 if (
context->doesPrimitiveExist(UUID) && !
context->doesPrimitiveDataExist(UUID, prop.c_str())) {
4615 context->setPrimitiveData(UUID, prop.c_str(), 0.f);
4625 for (ExcitationSet *exc : piggybacked_sets) {
4626 populateExcitationAPAR(*exc);
4633 backend->getRadiationResults(results);
4636 for (
size_t i = 0; i < results.
sky_energy.size(); i++) {
4644 std::vector<float> total_flux;
4645 total_flux.resize(
context->getPrimitiveCount(), 0.f);
4647 for (
const auto &band: radiation_bands) {
4649 std::string label = band.first;
4651 for (
size_t u = 0; u < context_UUIDs.size(); u++) {
4653 uint p = context_UUIDs.at(u);
4655 std::string str =
"radiation_flux_" + label;
4658 context->getPrimitiveData(p, str.c_str(),
R);
4659 total_flux.at(u) +=
R;
4669 vec3 dir = view_direction;
4673 float total_area = 0;
4674 for (std::size_t u = 0; u < primitiveID.size(); u++) {
4676 uint UUID = context_UUIDs.at(primitiveID.at(u));
4679 float area =
context->getPrimitiveArea(UUID);
4681 Gtheta += fabsf(normal * dir) * area;
4686 return Gtheta / total_area;
4690 float average_delta_e) {
4692 std::ofstream file(file_path);
4693 if (!file.is_open()) {
4694 helios_runtime_error(
"ERROR (RadiationModel::exportColorCorrectionMatrixXML): Failed to open file for writing: " + file_path);
4698 std::string matrix_type =
"3x3";
4699 if (matrix.size() == 4 || (matrix.size() >= 3 && matrix[0].size() == 4)) {
4700 matrix_type =
"4x3";
4704 file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
4705 file <<
"<!-- Camera Color Correction Matrix -->" << std::endl;
4706 file <<
"<!-- Source Image: " << source_image_path <<
" -->" << std::endl;
4707 file <<
"<!-- Camera Label: " << camera_label <<
" -->" << std::endl;
4708 file <<
"<!-- Colorboard Type: " << colorboard_type <<
" -->" << std::endl;
4709 if (average_delta_e >= 0.0f) {
4710 file <<
"<!-- Average Delta E: " << std::fixed << std::setprecision(2) << average_delta_e <<
" -->" << std::endl;
4712 file <<
"<!-- Matrix Type: " << matrix_type <<
" -->" << std::endl;
4713 file <<
"<!-- Generated: " << getCurrentDateTime() <<
" -->" << std::endl;
4716 file <<
"<helios>" << std::endl;
4717 file <<
" <ColorCorrectionMatrix camera_label=\"" << camera_label <<
"\" matrix_type=\"" << matrix_type <<
"\">" << std::endl;
4719 for (
size_t i = 0; i < matrix.size(); i++) {
4721 for (
size_t j = 0; j < matrix[i].size(); j++) {
4722 file << std::fixed << std::setprecision(6) << matrix[i][j];
4723 if (j < matrix[i].size() - 1) {
4727 file <<
"</row>" << std::endl;
4730 file <<
" </ColorCorrectionMatrix>" << std::endl;
4731 file <<
"</helios>" << std::endl;
4736std::string RadiationModel::getCurrentDateTime() {
4737 auto now = std::time(
nullptr);
4738 auto tm = *std::localtime(&now);
4739 std::stringstream ss;
4740 ss << std::put_time(&tm,
"%Y-%m-%d %H:%M:%S");
4746 std::ifstream file(file_path);
4747 if (!file.is_open()) {
4748 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Failed to open file for reading: " + file_path);
4751 std::vector<std::vector<float>> matrix;
4753 bool in_matrix =
false;
4754 std::string matrix_type =
"";
4756 while (std::getline(file, line)) {
4758 line.erase(0, line.find_first_not_of(
" \t"));
4759 line.erase(line.find_last_not_of(
" \t") + 1);
4762 if (line.find(
"<ColorCorrectionMatrix") != std::string::npos) {
4766 size_t camera_start = line.find(
"camera_label=\"");
4767 if (camera_start != std::string::npos) {
4769 size_t camera_end = line.find(
"\"", camera_start);
4770 if (camera_end != std::string::npos) {
4771 camera_label_out = line.substr(camera_start, camera_end - camera_start);
4776 size_t type_start = line.find(
"matrix_type=\"");
4777 if (type_start != std::string::npos) {
4779 size_t type_end = line.find(
"\"", type_start);
4780 if (type_end != std::string::npos) {
4781 matrix_type = line.substr(type_start, type_end - type_start);
4788 if (line.find(
"</ColorCorrectionMatrix>") != std::string::npos) {
4794 if (in_matrix && line.find(
"<row>") != std::string::npos && line.find(
"</row>") != std::string::npos) {
4796 size_t start = line.find(
"<row>") + 5;
4797 size_t end = line.find(
"</row>");
4798 std::string row_data = line.substr(start, end - start);
4801 std::vector<float> row;
4802 std::istringstream iss(row_data);
4804 while (iss >> value) {
4805 row.push_back(value);
4809 matrix.push_back(row);
4817 if (matrix.empty()) {
4818 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): No matrix data found in file: " + file_path);
4821 if (matrix.size() != 3) {
4822 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix size. Expected 3 rows, found " + std::to_string(matrix.size()) +
" rows in file: " + file_path);
4826 bool is_3x3 = (matrix[0].size() == 3 && matrix[1].size() == 3 && matrix[2].size() == 3);
4827 bool is_4x3 = (matrix[0].size() == 4 && matrix[1].size() == 4 && matrix[2].size() == 4);
4829 if (!is_3x3 && !is_4x3) {
4830 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix dimensions. All rows must have either 3 or 4 elements. File: " + file_path);
4834 if (!matrix_type.empty()) {
4835 if ((matrix_type ==
"3x3" && !is_3x3) || (matrix_type ==
"4x3" && !is_4x3)) {
4836 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Matrix type attribute ('" + matrix_type +
"') does not match actual matrix dimensions in file: " + file_path);
4843std::string
RadiationModel::autoCalibrateCameraImage(
const std::string &camera_label,
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
const std::string &output_file_path,
4847 if (cameras.find(camera_label) == cameras.end()) {
4848 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
4852 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
4853 if (!
context->doesGlobalDataExist(pixel_UUID_label.c_str())) {
4854 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Camera pixel UUID data '" + pixel_UUID_label +
"' does not exist for camera '" + camera_label +
"'. Make sure the radiation model has been run.");
4859 std::vector<std::string> colorboard_types;
4862 }
catch (
const std::exception &e) {
4863 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to detect colorboard types. " + std::string(e.what()));
4867 std::vector<CameraCalibration::LabColor> reference_lab_values;
4868 std::vector<std::string> colorboard_type_per_patch;
4870 for (
const auto &colorboard_type: colorboard_types) {
4871 std::vector<CameraCalibration::LabColor> current_reference_values;
4873 if (colorboard_type ==
"DGK") {
4875 }
else if (colorboard_type ==
"Calibrite") {
4877 }
else if (colorboard_type ==
"SpyderCHECKR") {
4880 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Unsupported colorboard type '" + colorboard_type +
"'.");
4884 reference_lab_values.insert(reference_lab_values.end(), current_reference_values.begin(), current_reference_values.end());
4887 for (
size_t i = 0; i < current_reference_values.size(); i++) {
4888 colorboard_type_per_patch.push_back(colorboard_type);
4893 std::vector<uint> pixel_UUIDs;
4894 context->getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
4895 int2 camera_resolution = cameras.at(camera_label).resolution;
4898 std::map<int, std::vector<std::vector<bool>>> patch_masks;
4899 int global_patch_idx = 0;
4901 for (
const auto &colorboard_type: colorboard_types) {
4903 int num_patches = 0;
4904 if (colorboard_type ==
"DGK") {
4906 }
else if (colorboard_type ==
"Calibrite" || colorboard_type ==
"SpyderCHECKR") {
4911 for (
int local_patch_idx = 0; local_patch_idx < num_patches; local_patch_idx++) {
4912 std::vector<std::vector<bool>> mask(camera_resolution.y, std::vector<bool>(camera_resolution.x,
false));
4915 for (
int y = 0; y < camera_resolution.y; y++) {
4916 for (
int x = 0; x < camera_resolution.x; x++) {
4917 int pixel_index = y * camera_resolution.x + x;
4918 uint pixel_UUID = pixel_UUIDs[pixel_index];
4920 if (pixel_UUID > 0) {
4924 std::string colorboard_data_label =
"colorboard_" + colorboard_type;
4925 if (
context->doesPrimitiveDataExist(pixel_UUID, colorboard_data_label.c_str())) {
4927 context->getPrimitiveData(pixel_UUID, colorboard_data_label.c_str(), patch_id);
4929 if ((
int) patch_id == local_patch_idx) {
4937 patch_masks[global_patch_idx] = mask;
4944 std::vector<float> red_data, green_data, blue_data;
4947 auto &camera_bands = cameras.at(camera_label).band_labels;
4948 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
4949 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
4951 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
4952 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
4954 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
4955 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
4959 red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
4960 green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
4961 blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
4964 float max_r = *std::max_element(red_data.begin(), red_data.end());
4965 float max_g = *std::max_element(green_data.begin(), green_data.end());
4966 float max_b = *std::max_element(blue_data.begin(), blue_data.end());
4969 float scale_factor = 1.0f;
4970 if (max_r > 1.0f || max_g > 1.0f || max_b > 1.0f) {
4971 scale_factor = 1.0f / std::max({max_r, max_g, max_b});
4973 for (
size_t i = 0; i < red_data.size(); i++) {
4974 red_data[i] *= scale_factor;
4975 green_data[i] *= scale_factor;
4976 blue_data[i] *= scale_factor;
4980 std::vector<helios::vec3> measured_rgb_values;
4981 int visible_patches = 0;
4983 for (
const auto &[patch_idx, mask]: patch_masks) {
4984 float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f;
4985 int pixel_count = 0;
4988 for (
int y = 0; y < camera_resolution.y; y++) {
4989 for (
int x = 0; x < camera_resolution.x; x++) {
4991 int pixel_index = y * camera_resolution.x + x;
4992 sum_r += red_data[pixel_index];
4993 sum_g += green_data[pixel_index];
4994 sum_b += blue_data[pixel_index];
5000 if (pixel_count > 10) {
5002 measured_rgb_values.push_back(avg_rgb);
5007 measured_rgb_values.push_back(
make_vec3(0, 0, 0));
5012 std::vector<CameraCalibration::LabColor> measured_lab_values;
5013 for (
const auto &rgb: measured_rgb_values) {
5014 if (rgb.magnitude() > 0) {
5015 measured_lab_values.push_back(calibration.
rgbToLab(rgb));
5020 std::vector<std::vector<float>> correction_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
5023 std::string algorithm_name;
5024 switch (algorithm) {
5026 algorithm_name =
"Diagonal scaling (white balance only)";
5029 algorithm_name =
"3x3 matrix with auto-fallback to diagonal";
5032 algorithm_name =
"3x3 matrix (forced)";
5036 if (measured_lab_values.size() >= 6 && reference_lab_values.size() >= 6) {
5038 std::vector<helios::vec3> target_rgb;
5040 for (
size_t i = 0; i < reference_lab_values.size(); i++) {
5043 target_rgb.push_back(ref_rgb);
5051 std::vector<helios::vec3> valid_measured, valid_target;
5052 std::vector<float> patch_weights;
5054 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), target_rgb.size()); i++) {
5055 if (measured_rgb_values[i].magnitude() > 0.01f) {
5056 valid_measured.push_back(measured_rgb_values[i]);
5057 valid_target.push_back(target_rgb[i]);
5060 float weight = 1.0f;
5063 if (i >= 18 && i <= 23) {
5073 else if (i == 14 || i == 13 || i == 12)
5077 else if (i == 3 || i == 10)
5082 float luminance = 0.299f * measured_rgb.
x + 0.587f * measured_rgb.
y + 0.114f * measured_rgb.
z;
5085 if (luminance > 0.6f)
5087 else if (luminance < 0.2f)
5096 patch_weights.push_back(weight);
5104 bool matrix_valid =
true;
5107 std::vector<float> lambda_values = {0.01f, 0.05f, 0.1f, 0.15f, 0.2f};
5108 int lambda_attempt = 0;
5110 while (lambda_attempt < lambda_values.size()) {
5111 float lambda = lambda_values[lambda_attempt];
5112 matrix_valid =
true;
5114 for (
int row = 0; row < 3; row++) {
5116 float ATA[3][3] = {{0}};
5119 for (
size_t i = 0; i < valid_measured.size(); i++) {
5120 float weight = patch_weights[i];
5122 float target_val = (row == 0) ? valid_target[i].x : (row == 1) ? valid_target[i].y : valid_target[i].z;
5125 ATA[0][0] += weight * m.
x * m.
x;
5126 ATA[0][1] += weight * m.
x * m.
y;
5127 ATA[0][2] += weight * m.
x * m.
z;
5128 ATA[1][0] += weight * m.
y * m.
x;
5129 ATA[1][1] += weight * m.
y * m.
y;
5130 ATA[1][2] += weight * m.
y * m.
z;
5131 ATA[2][0] += weight * m.
z * m.
x;
5132 ATA[2][1] += weight * m.
z * m.
y;
5133 ATA[2][2] += weight * m.
z * m.
z;
5135 ATb[0] += weight * m.
x * target_val;
5136 ATb[1] += weight * m.
y * target_val;
5137 ATb[2] += weight * m.
z * target_val;
5143 float diag_reg = lambda * 2.0f;
5144 float offdiag_reg = lambda * 0.5f;
5146 ATA[0][0] += diag_reg;
5147 ATA[1][1] += diag_reg;
5148 ATA[2][2] += diag_reg;
5151 ATA[0][1] += offdiag_reg;
5152 ATA[1][0] += offdiag_reg;
5153 ATA[0][2] += offdiag_reg;
5154 ATA[2][0] += offdiag_reg;
5155 ATA[1][2] += offdiag_reg;
5156 ATA[2][1] += offdiag_reg;
5159 float det = ATA[0][0] * (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) - ATA[0][1] * (ATA[1][0] * ATA[2][2] - ATA[1][2] * ATA[2][0]) + ATA[0][2] * (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]);
5161 if (fabs(det) < 1e-3f) {
5163 matrix_valid =
false;
5168 float inv_det = 1.0f / det;
5169 correction_matrix[row][0] = inv_det * (ATb[0] * (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) - ATb[1] * (ATA[0][1] * ATA[2][2] - ATA[0][2] * ATA[2][1]) + ATb[2] * (ATA[0][1] * ATA[1][2] - ATA[0][2] * ATA[1][1]));
5171 correction_matrix[row][1] = inv_det * (ATb[1] * (ATA[0][0] * ATA[2][2] - ATA[0][2] * ATA[2][0]) - ATb[0] * (ATA[1][0] * ATA[2][2] - ATA[1][2] * ATA[2][0]) + ATb[2] * (ATA[1][0] * ATA[0][2] - ATA[1][2] * ATA[0][0]));
5173 correction_matrix[row][2] = inv_det * (ATb[2] * (ATA[0][0] * ATA[1][1] - ATA[0][1] * ATA[1][0]) - ATb[0] * (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]) + ATb[1] * (ATA[0][0] * ATA[2][1] - ATA[0][1] * ATA[2][0]));
5178 bool elements_reasonable =
true;
5179 for (
int i = 0; i < 3; i++) {
5180 for (
int j = 0; j < 3; j++) {
5181 if (fabs(correction_matrix[i][j]) > 5.0f) {
5183 elements_reasonable =
false;
5188 if (!elements_reasonable)
5191 matrix_valid = elements_reasonable;
5201 if (!matrix_valid) {
5205 float total_weight = 0.0f;
5208 for (
size_t i = 0; i < valid_measured.size(); i++) {
5209 float weight = patch_weights[i];
5214 if (measured.
x > 0.01f && measured.
y > 0.01f && measured.
z > 0.01f) {
5217 weighted_correction.
x += weight * channel_correction.
x;
5218 weighted_correction.
y += weight * channel_correction.
y;
5219 weighted_correction.
z += weight * channel_correction.
z;
5220 total_weight += weight;
5224 if (total_weight > 0.1f) {
5226 correction_matrix[0][0] = weighted_correction.
x / total_weight;
5227 correction_matrix[1][1] = weighted_correction.
y / total_weight;
5228 correction_matrix[2][2] = weighted_correction.
z / total_weight;
5231 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5232 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5233 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5239 correction_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
5243 if (valid_measured.size() > 0 && patch_weights.size() == valid_measured.size()) {
5244 float total_weight = 0.0f;
5249 for (
size_t i = 0; i < valid_measured.size(); i++) {
5250 float weight = patch_weights[i];
5251 weighted_measured_avg = weighted_measured_avg + weight * valid_measured[i];
5252 weighted_target_avg = weighted_target_avg + weight * valid_target[i];
5253 total_weight += weight;
5256 if (total_weight > 0) {
5257 weighted_measured_avg = weighted_measured_avg / total_weight;
5258 weighted_target_avg = weighted_target_avg / total_weight;
5260 if (weighted_measured_avg.
x > 0.05f && weighted_measured_avg.
y > 0.05f && weighted_measured_avg.
z > 0.05f) {
5261 correction_matrix[0][0] = weighted_target_avg.
x / weighted_measured_avg.
x;
5262 correction_matrix[1][1] = weighted_target_avg.
y / weighted_measured_avg.
y;
5263 correction_matrix[2][2] = weighted_target_avg.
z / weighted_measured_avg.
z;
5266 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5267 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5268 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5272 size_t white_idx = 18;
5273 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
5274 helios::vec3 measured_white = measured_rgb_values[white_idx];
5276 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5277 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
5278 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
5279 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
5286 size_t white_idx = 18;
5287 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
5288 helios::vec3 measured_white = measured_rgb_values[white_idx];
5290 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5291 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
5292 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
5293 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
5300 size_t white_idx = 18;
5301 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size() && measured_rgb_values[white_idx].magnitude() > 0) {
5303 helios::vec3 measured_white = measured_rgb_values[white_idx];
5306 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5307 correction_matrix[0][0] = target_white.
x / measured_white.
x;
5308 correction_matrix[1][1] = target_white.
y / measured_white.
y;
5309 correction_matrix[2][2] = target_white.
z / measured_white.
z;
5312 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5313 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5314 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5318 std::cout <<
"Insufficient valid patches (" << valid_measured.size() <<
" available), using identity matrix" << std::endl;
5322 size_t white_idx = 18;
5323 if (white_idx < measured_rgb_values.size() && white_idx < reference_lab_values.size() && measured_rgb_values[white_idx].magnitude() > 0) {
5327 helios::vec3 measured_white = measured_rgb_values[white_idx];
5329 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5330 correction_matrix[0][0] = target_white.
x / measured_white.
x;
5331 correction_matrix[1][1] = target_white.
y / measured_white.
y;
5332 correction_matrix[2][2] = target_white.
z / measured_white.
z;
5335 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5336 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5337 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5341 std::cout <<
"Insufficient patches for correction (" << measured_lab_values.size() <<
" available), using identity matrix" << std::endl;
5345 if (print_quality_report) {
5346 std::cout <<
"\n========== COLOR CALIBRATION QUALITY REPORT ==========" << std::endl;
5347 std::cout <<
"Colorboard types: ";
5348 for (
size_t i = 0; i < colorboard_types.size(); i++) {
5349 std::cout << colorboard_types[i];
5350 if (i < colorboard_types.size() - 1) {
5354 std::cout << std::endl;
5355 std::cout <<
"Number of patches analyzed: " << visible_patches << std::endl;
5356 std::cout <<
"Algorithm used: " << algorithm_name << std::endl;
5359 bool is_diagonal_only =
true;
5360 for (
int i = 0; i < 3; i++) {
5361 for (
int j = 0; j < 3; j++) {
5362 if (i != j && fabs(correction_matrix[i][j]) > 1e-6f) {
5363 is_diagonal_only =
false;
5367 if (!is_diagonal_only)
5371 if (is_diagonal_only) {
5372 std::cout <<
"Color correction factors applied: R=" << correction_matrix[0][0] <<
", G=" << correction_matrix[1][1] <<
", B=" << correction_matrix[2][2] << std::endl;
5373 std::cout <<
"Matrix type: Diagonal (white balance only)" << std::endl;
5375 std::cout <<
"Full 3x3 color correction matrix applied:" << std::endl;
5376 for (
int i = 0; i < 3; i++) {
5377 std::cout <<
"[" << std::fixed << std::setprecision(4);
5378 for (
int j = 0; j < 3; j++) {
5379 std::cout << std::setw(8) << correction_matrix[i][j];
5383 std::cout <<
"]" << std::endl;
5385 std::cout <<
"Matrix type: Full 3x3 (corrects color casts and chromatic errors)" << std::endl;
5388 float det = correction_matrix[0][0] * (correction_matrix[1][1] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][1]) -
5389 correction_matrix[0][1] * (correction_matrix[1][0] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][0]) +
5390 correction_matrix[0][2] * (correction_matrix[1][0] * correction_matrix[2][1] - correction_matrix[1][1] * correction_matrix[2][0]);
5392 std::cout <<
"Matrix determinant: " << std::scientific << std::setprecision(3) << det << std::endl;
5393 if (fabs(det) > 0.1f) {
5394 std::cout <<
"Matrix conditioning: Good (well-conditioned)" << std::endl;
5395 }
else if (fabs(det) > 0.01f) {
5396 std::cout <<
"Matrix conditioning: Fair (moderately conditioned)" << std::endl;
5398 std::cout <<
"Matrix conditioning: Poor (ill-conditioned)" << std::endl;
5400 std::cout << std::fixed;
5404 double total_delta_e = 0.0;
5405 int valid_patches = 0;
5407 std::cout <<
"\nPer-patch analysis (after correction):" << std::endl;
5408 std::cout <<
"Patch | Corrected RGB | Reference RGB | Delta E " << std::endl;
5409 std::cout <<
"------|--------------------|--------------------|---------" << std::endl;
5411 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
5412 if (measured_rgb_values[i].magnitude() > 0) {
5415 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
5416 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
5417 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
5433 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
5435 std::cout << std::setw(5) << i <<
" | " << std::fixed << std::setprecision(3) <<
"(" << std::setw(5) << corrected_rgb.
x <<
"," << std::setw(5) << corrected_rgb.
y <<
"," << std::setw(5) << corrected_rgb.
z <<
") | ";
5438 std::cout <<
"(" << std::setw(5) << ref_rgb.
x <<
"," << std::setw(5) << ref_rgb.
y <<
"," << std::setw(5) << ref_rgb.
z <<
") | " << std::setw(7) << delta_E << std::endl;
5440 total_delta_e += delta_E;
5446 double mean_delta_e = total_delta_e / valid_patches;
5447 std::cout <<
"\n========== OVERALL CALIBRATION QUALITY ==========" << std::endl;
5448 std::cout <<
"Mean Delta E: " << std::fixed << std::setprecision(2) << mean_delta_e << std::endl;
5450 std::cout <<
"======================================================\n" << std::endl;
5454 std::vector<helios::RGBcolor> corrected_pixels;
5455 corrected_pixels.resize(red_data.size());
5458 for (
int j = 0; j < camera_resolution.y; j++) {
5459 for (
int i = 0; i < camera_resolution.x; i++) {
5461 int source_index = j * camera_resolution.x + i;
5462 float r = red_data[source_index];
5463 float g = green_data[source_index];
5464 float b = blue_data[source_index];
5467 float corrected_r = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
5468 float corrected_g = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
5469 float corrected_b = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
5478 uint ii = camera_resolution.x - i - 1;
5479 uint jj = camera_resolution.y - j - 1;
5480 uint dest_index = jj * camera_resolution.x + ii;
5482 corrected_pixels[dest_index] =
make_RGBcolor(corrected_r, corrected_g, corrected_b);
5487 std::string output_path = output_file_path;
5488 if (output_path.empty()) {
5489 output_path =
"auto_calibrated_" + camera_label +
".jpg";
5493 helios::writeJPEG(output_path, camera_resolution.x, camera_resolution.y, corrected_pixels);
5494 std::cout <<
"Wrote corrected image to: " << output_path << std::endl;
5495 }
catch (
const std::exception &e) {
5496 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to write corrected image. " + std::string(e.what()));
5500 if (!ccm_export_file_path.empty()) {
5503 double total_delta_e = 0.0;
5504 int valid_patches = 0;
5506 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
5507 if (measured_rgb_values[i].magnitude() > 0) {
5510 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
5511 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
5512 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
5521 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
5522 total_delta_e += delta_E;
5527 double mean_delta_e = (valid_patches > 0) ? (total_delta_e / valid_patches) : -1.0;
5531 std::string colorboard_types_str;
5532 for (
size_t i = 0; i < colorboard_types.size(); i++) {
5533 colorboard_types_str += colorboard_types[i];
5534 if (i < colorboard_types.size() - 1) {
5535 colorboard_types_str +=
", ";
5540 std::cout <<
"Exported color correction matrix to: " << ccm_export_file_path << std::endl;
5541 }
catch (
const std::exception &e) {
5542 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to export CCM to XML. " + std::string(e.what()));
5552 if (cameras.find(camera_label) == cameras.end()) {
5553 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
5557 auto &camera_bands = cameras.at(camera_label).band_labels;
5558 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
5559 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
5561 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
5562 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
5564 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
5565 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
5569 std::string loaded_camera_label;
5570 std::vector<std::vector<float>> correction_matrix;
5573 }
catch (
const std::exception &e) {
5574 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Failed to load CCM from XML file. " + std::string(e.what()));
5578 if (correction_matrix.size() != 3) {
5579 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Invalid matrix dimensions. Expected 3x3 or 4x3 matrix, got " + std::to_string(correction_matrix.size()) +
" rows.");
5582 bool is_3x3 = (correction_matrix[0].size() == 3);
5583 bool is_4x3 = (correction_matrix[0].size() == 4);
5585 if (!is_3x3 && !is_4x3) {
5586 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Invalid matrix dimensions. Expected 3x3 or 4x3 matrix, got " + std::to_string(correction_matrix.size()) +
"x" + std::to_string(correction_matrix[0].size()) +
5591 std::vector<float> &red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
5592 std::vector<float> &green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
5593 std::vector<float> &blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
5595 int2 camera_resolution = cameras.at(camera_label).resolution;
5596 size_t pixel_count = red_data.size();
5599 for (
size_t i = 0; i < pixel_count; i++) {
5600 float r = red_data[i];
5601 float g = green_data[i];
5602 float b = blue_data[i];
5607 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
5608 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
5609 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
5612 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b + correction_matrix[0][3];
5613 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b + correction_matrix[1][3];
5614 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b + correction_matrix[2][3];
5619 std::cout <<
"Applied color correction matrix from '" << ccm_file_path <<
"' to camera '" << camera_label <<
"'" << std::endl;
5620 std::cout <<
"Matrix type: " << (is_3x3 ?
"3x3" :
"4x3") <<
", processed " << pixel_count <<
" pixels" << std::endl;
5625 if (cameras.find(camera_label) == cameras.end()) {
5626 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Camera '" + camera_label +
"' does not exist.");
5629 auto &camera_pixel_data = cameras.at(camera_label).pixel_data;
5630 if (camera_pixel_data.find(band_label) == camera_pixel_data.end()) {
5631 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Band '" + band_label +
"' does not exist in camera '" + camera_label +
"'.");
5634 return camera_pixel_data.at(band_label);
5638 if (cameras.find(camera_label) == cameras.end()) {
5639 helios_runtime_error(
"ERROR (RadiationModel::setCameraPixelData): Camera '" + camera_label +
"' does not exist.");
5642 cameras.at(camera_label).pixel_data[band_label] = pixel_data;
5647void RadiationModel::queryBackendGPUMemory()
const {
5649 backend->queryGPUMemory();
5651 std::cout <<
"Backend not initialized - cannot query GPU memory." << std::endl;
5661 params.camera_position = camera.position;
5666 params.camera_focal_length = camera.focal_length;
5667 params.camera_lens_diameter = camera.lens_diameter;
5668 params.camera_fov_aspect = camera.FOV_aspect_ratio;
5671 params.camera_resolution = tile_resolution;
5672 params.camera_resolution_full = camera.resolution;
5673 params.camera_pixel_offset = tile_offset;
5674 params.antialiasing_samples = antialiasing_samples;
5675 params.camera_id = camera_id;
5678 float effective_HFOV = camera.HFOV_degrees / camera.camera_zoom;
5679 params.camera_HFOV = effective_HFOV *
M_PI / 180.0f;
5680 params.camera_viewplane_length = 0.5f / tanf(0.5f * effective_HFOV * M_PI / 180.f);
5683 float HFOV_rad = effective_HFOV *
M_PI / 180.f;
5684 float VFOV_rad = HFOV_rad / camera.FOV_aspect_ratio;
5685 float pixel_angle_h = HFOV_rad / float(camera.resolution.x);
5686 float pixel_angle_v = VFOV_rad / float(camera.resolution.y);
5687 params.camera_pixel_solid_angle = pixel_angle_h * pixel_angle_v;
5690 params.scattering_iteration = 0;
5693 params.specular_reflection_enabled = specular_reflection_mode;
5698std::vector<CameraTile> RadiationModel::computeCameraTiles(
const RadiationCamera &camera,
size_t maxRays) {
5700 std::vector<CameraTile> tiles;
5702 size_t total_rays = size_t(camera.antialiasing_samples) * size_t(camera.resolution.x) * size_t(camera.resolution.y);
5705 if (total_rays <= maxRays) {
5711 size_t rays_per_row = size_t(camera.antialiasing_samples) * size_t(camera.resolution.x);
5712 size_t max_rows_per_tile = floor(
float(maxRays) /
float(rays_per_row));
5714 if (max_rows_per_tile == 0) {
5716 size_t max_pixels_per_tile = floor(
float(maxRays) /
float(camera.antialiasing_samples));
5718 float aspect = float(camera.resolution.x) / float(camera.resolution.y);
5719 size_t tile_width = round(sqrt(max_pixels_per_tile * aspect));
5720 size_t tile_height = floor(
float(max_pixels_per_tile) /
float(tile_width));
5722 tile_width = std::min(tile_width,
size_t(camera.resolution.x));
5723 tile_height = std::min(tile_height,
size_t(camera.resolution.y));
5725 int Ntiles_x = ceil(
float(camera.resolution.x) /
float(tile_width));
5726 int Ntiles_y = ceil(
float(camera.resolution.y) /
float(tile_height));
5728 for (
int ty = 0; ty < Ntiles_y; ty++) {
5729 for (
int tx = 0; tx < Ntiles_x; tx++) {
5730 size_t offset_x = tx * tile_width;
5731 size_t offset_y = ty * tile_height;
5732 size_t width_this = std::min(tile_width, camera.resolution.x - offset_x);
5733 size_t height_this = std::min(tile_height, camera.resolution.y - offset_y);
5740 size_t rows_per_tile = std::min(max_rows_per_tile,
size_t(camera.resolution.y));
5741 int Ntiles = ceil(
float(camera.resolution.y) /
float(rows_per_tile));
5743 for (
int t = 0; t < Ntiles; t++) {
5744 size_t offset_y = t * rows_per_tile;
5745 size_t height_this = std::min(rows_per_tile, camera.resolution.y - offset_y);
5754void RadiationModel::buildGeometryData(
const std::vector<uint> &UUIDs) {
5759 std::vector<uint> valid_UUIDs;
5760 for (
uint UUID: UUIDs) {
5761 if (!
context->doesPrimitiveExist(UUID))
5764 float area =
context->getPrimitiveArea(UUID);
5765 uint parentID =
context->getPrimitiveParentObjectID(UUID);
5766 if ((area == 0 || std::isnan(area)) &&
context->getObjectType(parentID) != helios::OBJECT_TYPE_TILE) {
5769 valid_UUIDs.push_back(UUID);
5772 if (valid_UUIDs.empty()) {
5778 std::vector<uint> objID_all =
context->getUniquePrimitiveParentObjectIDs(valid_UUIDs,
true);
5779 std::vector<uint> primitive_UUIDs_ordered;
5780 std::unordered_set<uint> valid_set(valid_UUIDs.begin(), valid_UUIDs.end());
5782 for (
uint objID: objID_all) {
5783 std::vector<uint> prim_UUIDs =
context->getObjectPrimitiveUUIDs(objID);
5787 std::sort(prim_UUIDs.begin(), prim_UUIDs.end());
5789 for (
uint UUID: prim_UUIDs) {
5790 if (
context->doesPrimitiveExist(UUID) && valid_set.find(UUID) != valid_set.end()) {
5791 primitive_UUIDs_ordered.push_back(UUID);
5796 size_t Nprimitives = primitive_UUIDs_ordered.size();
5809 geometry_data.
object_IDs.resize(Nprimitives);
5834 uint current_objID = 0;
5835 uint last_parentID = 99999;
5837 std::vector<uint> primitiveID_indices;
5839 for (
size_t u = 0; u < Nprimitives; u++) {
5840 uint UUID = primitive_UUIDs_ordered[u];
5841 uint parentID =
context->getPrimitiveParentObjectID(UUID);
5843 if (last_parentID != parentID || parentID == 0 ||
context->getObjectType(parentID) == helios::OBJECT_TYPE_TILE) {
5844 primitiveID_indices.push_back(u);
5845 last_parentID = parentID;
5848 last_parentID = parentID;
5851 geometry_data.
object_IDs[u] = current_objID - 1;
5854 size_t Nobjects = primitiveID_indices.size();
5857 primitiveID = primitiveID_indices;
5861 std::vector<uint> primitiveID_for_backend(Nprimitives);
5862 for (
size_t i = 0; i < Nprimitives; i++) {
5863 primitiveID_for_backend[i] = primitive_UUIDs_ordered[i];
5870 size_t patch_idx = 0, tri_idx = 0, disk_idx = 0, voxel_idx = 0, bbox_idx = 0;
5874 for (
size_t prim_idx = 0; prim_idx < Nprimitives; prim_idx++) {
5875 uint UUID = primitive_UUIDs_ordered[prim_idx];
5879 uint parentID =
context->getPrimitiveParentObjectID(UUID);
5887 geometry_data.
twosided_flags[prim_idx] =
static_cast<char>(
context->getPrimitiveTwosidedFlag(UUID, 1));
5895 context->getPrimitiveTransformationMatrix(UUID, m);
5898 std::vector<vec3> verts =
context->getPrimitiveVertices(UUID);
5899 for (
const auto &v: verts) {
5907 }
else if (type == helios::PRIMITIVE_TYPE_PATCH) {
5910 context->getPrimitiveTransformationMatrix(UUID, m);
5913 std::vector<vec3> verts =
context->getPrimitiveVertices(UUID);
5914 for (
const auto &v: verts) {
5925 }
else if (type == helios::PRIMITIVE_TYPE_TRIANGLE) {
5928 context->getPrimitiveTransformationMatrix(UUID, m);
5931 std::vector<vec3> verts =
context->getPrimitiveVertices(UUID);
5932 for (
const auto &v: verts) {
5940 }
else if (type == helios::PRIMITIVE_TYPE_VOXEL) {
5943 context->getPrimitiveTransformationMatrix(UUID, m);
5946 std::vector<vec3> verts =
context->getPrimitiveVertices(UUID);
5947 for (
const auto &v: verts) {
5969 vec2 xbounds, ybounds, zbounds;
5970 context->getDomainBoundingBox(xbounds, ybounds, zbounds);
5973 if (periodic_flag.
x == 1 || periodic_flag.
y == 1) {
5974 if (!cameras.empty()) {
5975 for (
auto &camera: cameras) {
5976 vec3 camerapos = camera.second.position;
5977 if (camerapos.
x < xbounds.
x || camerapos.
x > xbounds.
y || camerapos.
y < ybounds.
x || camerapos.
y > ybounds.
y) {
5978 std::cout <<
"WARNING (RadiationModel::buildGeometryData): camera position is outside of the domain bounding box. Disabling periodic boundary conditions." << std::endl;
5979 periodic_flag.
x = 0;
5980 periodic_flag.
y = 0;
5984 if (camerapos.
z < zbounds.
x) {
5985 zbounds.
x = camerapos.
z;
5987 if (camerapos.
z > zbounds.
y) {
5988 zbounds.
y = camerapos.
z;
6005 uint bbox_UUID_base = max_UUID + 1;
6008 if (periodic_flag.
x == 1) {
6014 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
6022 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
6026 if (periodic_flag.
y == 1) {
6032 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
6040 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
6072 uint bbox_max_UUID = max_UUID;
6088 for (
size_t i = 0; i < geometry_data.
bbox_count; i++) {
6096void RadiationModel::buildTextureData() {
6104 geometry_data.
uv_data.clear();
6108 geometry_data.
mask_IDs.resize(Nobjects, -1);
6109 geometry_data.
uv_IDs.clear();
6110 geometry_data.
uv_IDs.resize(Nobjects, -1);
6113 std::map<std::string, int> texture_to_mask_idx;
6115 for (
size_t prim_idx = 0; prim_idx < Nobjects; prim_idx++) {
6119 std::string texture_file =
context->getPrimitiveTextureFile(UUID);
6120 if (texture_file.empty()) {
6125 if (!
context->primitiveTextureHasTransparencyChannel(UUID)) {
6131 auto cache_it = texture_to_mask_idx.find(texture_file);
6132 if (cache_it != texture_to_mask_idx.end()) {
6134 mask_idx = cache_it->second;
6137 const std::vector<std::vector<bool>> *trans_data =
context->getPrimitiveTextureTransparencyData(UUID);
6140 mask_idx =
static_cast<int>(geometry_data.
mask_sizes.size());
6141 texture_to_mask_idx[texture_file] = mask_idx;
6145 for (
int y = 0; y < tex_size.
y; y++) {
6146 for (
int x = 0; x < tex_size.
x; x++) {
6147 geometry_data.
mask_data.push_back((*trans_data)[y][x]);
6150 geometry_data.
mask_sizes.push_back(tex_size);
6153 geometry_data.
mask_IDs[prim_idx] = mask_idx;
6157 std::vector<helios::vec2> uvs =
context->getPrimitiveTextureUV(UUID);
6159 geometry_data.
uv_IDs[prim_idx] =
static_cast<int>(prim_idx);
6160 for (
const auto &uv: uvs) {
6161 geometry_data.
uv_data.push_back(uv);
6164 size_t start_idx = geometry_data.
uv_data.size() - uvs.size();
6165 while (geometry_data.
uv_data.size() - start_idx < 4) {
6166 geometry_data.
uv_data.push_back(uvs.back());
6173size_t RadiationModel::testBuildGeometryData() {
6174 buildGeometryData(
context->getAllUUIDs());
6178void RadiationModel::buildUUIDMapping() {
6182 uuid_to_position.clear();
6183 position_to_uuid.clear();
6189 uuid_to_position[UUID] = i;
6190 position_to_uuid.push_back(UUID);
6198static void validateAndCorrectMaterialProperties(
float &rho,
float &tau,
float eps,
bool emission_enabled,
uint scattering_depth,
const std::string &band_label,
uint UUID,
bool is_sif_band =
false,
6204 if (rho < 0.f || rho > 1.f) {
6206 warnings->
addWarning(
"material_property_clamping",
"Reflectivity out of range [0,1] for band " + band_label +
", primitive #" + std::to_string(UUID) +
": rho=" + std::to_string(rho) +
". Clamping to valid range.");
6208 rho = std::max(0.f, std::min(1.f, rho));
6211 if (tau < 0.f || tau > 1.f) {
6213 warnings->
addWarning(
"material_property_clamping",
"Transmissivity out of range [0,1] for band " + band_label +
", primitive #" + std::to_string(UUID) +
": tau=" + std::to_string(tau) +
". Clamping to valid range.");
6215 tau = std::max(0.f, std::min(1.f, tau));
6224 if (rho + tau > 1.f) {
6225 helios_runtime_error(std::string(
"ERROR (RadiationModel): reflectivity and transmissivity must sum to less than or equal to 1 to ensure energy conservation. Band ") + band_label +
", Primitive #" +
6226 std::to_string(UUID) +
": tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
".");
6232 if (emission_enabled) {
6234 if (scattering_depth == 0 && eps != 1.f) {
6235 if (warnings && (rho != 0.f || tau != 0.f)) {
6236 warnings->
addWarning(
"blackbody_override",
"Band " + band_label +
" has emission with scatteringDepth=0, " +
"enforcing blackbody behavior (eps=1, rho=0, tau=0) for primitive #" + std::to_string(UUID));
6242 else if (eps != 1.f && rho == 0 && tau == 0) {
6245 }
else if (std::abs(eps + rho + tau - 1.f) > 1e-5f && eps > 0.f) {
6247 helios_runtime_error(std::string(
"ERROR (RadiationModel): emissivity, transmissivity, and reflectivity ") +
"must sum to 1 to ensure energy conservation. Band " + band_label +
", Primitive #" + std::to_string(UUID) +
6248 ": eps=" + std::to_string(eps) +
", tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
". It is also possible that you forgot to disable emission for this band.");
6252 if (rho + tau > 1.f) {
6253 helios_runtime_error(std::string(
"ERROR (RadiationModel): transmissivity and reflectivity cannot sum to ") +
"greater than 1 to ensure energy conservation. Band " + band_label +
", Primitive #" + std::to_string(UUID) +
6254 ": eps=" + std::to_string(eps) +
", tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
". It is also possible that you forgot to disable emission for this band.");
6259void RadiationModel::buildMaterialData() {
6266 size_t Nbands = radiation_bands.size();
6267 size_t Nsources = radiation_sources.size();
6276 size_t total_size = Nsources * Nbands * Nprims;
6286 std::map<std::string, std::vector<helios::vec2>> unique_rho_spectra;
6287 std::map<std::string, std::vector<helios::vec2>> unique_tau_spectra;
6289 for (
size_t p = 0; p < Nprims; p++) {
6293 if (
context->doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
6294 std::string spectrum_label;
6295 context->getPrimitiveData(UUID,
"reflectivity_spectrum", spectrum_label);
6296 if (unique_rho_spectra.find(spectrum_label) == unique_rho_spectra.end()) {
6298 if (
context->doesGlobalDataExist(spectrum_label.c_str())) {
6299 unique_rho_spectra[spectrum_label] = loadSpectralData(spectrum_label);
6305 if (
context->doesPrimitiveDataExist(UUID,
"transmissivity_spectrum")) {
6306 std::string spectrum_label;
6307 context->getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
6308 if (unique_tau_spectra.find(spectrum_label) == unique_tau_spectra.end()) {
6310 if (
context->doesGlobalDataExist(spectrum_label.c_str())) {
6311 unique_tau_spectra[spectrum_label] = loadSpectralData(spectrum_label);
6319 for (
const auto &band_pair: radiation_bands) {
6320 std::string band_label = band_pair.second.label;
6322 for (
size_t s = 0; s < Nsources; s++) {
6323 for (
size_t p = 0; p < Nprims; p++) {
6328 size_t idx = mat_indexer(s, p, b_idx);
6331 float rho = rho_default;
6333 if (
context->doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
6335 std::string spectrum_label;
6336 context->getPrimitiveData(UUID,
"reflectivity_spectrum", spectrum_label);
6339 if (unique_rho_spectra.find(spectrum_label) != unique_rho_spectra.end()) {
6340 const std::vector<helios::vec2> &spectrum = unique_rho_spectra.at(spectrum_label);
6343 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
6348 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
6350 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
6351 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
6355 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
6356 if (!radiation_sources[s].source_spectrum.empty()) {
6368 std::string rho_label =
"reflectivity_" + band_label;
6369 if (
context->doesPrimitiveDataExist(UUID, rho_label.c_str())) {
6370 context->getPrimitiveData(UUID, rho_label.c_str(), rho);
6375 float tau = tau_default;
6377 if (
context->doesPrimitiveDataExist(UUID,
"transmissivity_spectrum")) {
6379 std::string spectrum_label;
6380 context->getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
6383 if (unique_tau_spectra.find(spectrum_label) != unique_tau_spectra.end()) {
6384 const std::vector<helios::vec2> &spectrum = unique_tau_spectra.at(spectrum_label);
6387 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
6392 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
6394 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
6395 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
6399 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
6400 if (!radiation_sources[s].source_spectrum.empty()) {
6412 std::string tau_label =
"transmissivity_" + band_label;
6413 if (
context->doesPrimitiveDataExist(UUID, tau_label.c_str())) {
6414 context->getPrimitiveData(UUID, tau_label.c_str(), tau);
6419 float eps = eps_default;
6420 std::string eps_label =
"emissivity_" + band_label;
6421 if (
context->doesPrimitiveDataExist(UUID, eps_label.c_str())) {
6422 context->getPrimitiveData(UUID, eps_label.c_str(), eps);
6429 const bool is_sif_band = sif_emission_bands.count(band_label) > 0;
6430 validateAndCorrectMaterialProperties(rho, tau, eps, band.emissionFlag, band.scatteringDepth, band_label, UUID, is_sif_band, &warnings);
6444 bool specular_exponent_specified =
false;
6445 bool specular_scale_specified =
false;
6447 for (
size_t p = 0; p < Nprims; p++) {
6450 if (
context->doesPrimitiveDataExist(UUID,
"specular_exponent") &&
context->getPrimitiveDataType(
"specular_exponent") == helios::HELIOS_TYPE_FLOAT) {
6453 specular_exponent_specified =
true;
6457 if (
context->doesPrimitiveDataExist(UUID,
"specular_scale") &&
context->getPrimitiveDataType(
"specular_scale") == helios::HELIOS_TYPE_FLOAT) {
6460 specular_scale_specified =
true;
6466 if (specular_exponent_specified) {
6467 if (specular_scale_specified) {
6468 specular_reflection_mode = 2;
6470 specular_reflection_mode = 1;
6473 specular_reflection_mode = 0;
6480void RadiationModel::buildSourceData() {
6483 source_data.clear();
6484 source_data.reserve(radiation_sources.size());
6486 for (
size_t s = 0; s < radiation_sources.size(); s++) {
6487 const auto &src = radiation_sources[s];
6489 backend_src.
position = src.source_position;
6490 backend_src.
rotation = src.source_rotation;
6491 backend_src.
width = src.source_width;
6492 backend_src.
type = src.source_type;
6495 backend_src.
fluxes.clear();
6497 for (
const auto &band_pair: radiation_bands) {
6498 std::string band_label = band_pair.second.label;
6501 backend_src.
fluxes.push_back(flux);
6505 source_data.push_back(backend_src);
6511 return backend.get();
6515 return geometry_data;
6519 return material_data;
6522std::vector<helios::RayTracingSource> &RadiationModel::getSourceData() {
6527void RadiationModel::testBuildAllBackendData() {
6528 buildGeometryData(
context->getAllUUIDs());
6529 buildMaterialData();