18using namespace helios;
25 context->
loadXML(
"plugins/leafoptics/spectral_data/prospect_spectral_library.xml",
true);
28 std::vector<helios::vec2> data;
30 helios_runtime_error(
"Refraction index data was not loaded properly from the prospect_spectral_library.xml file.");
33 if (data.size() != nw) {
34 helios_runtime_error(
"Size of refraction index data loaded from the prospect_spectral_library.xml file was not correct.");
36 refractiveindex.resize(nw);
37 wave_length.resize(nw);
38 for (
int i = 0; i < nw; i++) {
39 refractiveindex.at(i) = data.at(i).y;
40 wave_length.at(i) = data.at(i).x;
44 helios_runtime_error(
"Chlorophyll absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
47 if (data.size() != nw) {
48 helios_runtime_error(
"Size of chlorophyll absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
50 absorption_chlorophyll.resize(nw);
51 for (
int i = 0; i < nw; i++) {
52 absorption_chlorophyll.at(i) = data.at(i).y;
57 helios_runtime_error(
"Carotenoid absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
60 if (data.size() != nw) {
61 helios_runtime_error(
"Size of carotenoid absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
63 absorption_carotenoid.resize(nw);
64 for (
int i = 0; i < nw; i++) {
65 absorption_carotenoid.at(i) = data.at(i).y;
70 helios_runtime_error(
"Anothocyanin absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
73 if (data.size() != nw) {
74 helios_runtime_error(
"Size of anthocyanin absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
76 absorption_anthocyanin.resize(nw);
77 for (
int i = 0; i < nw; i++) {
78 absorption_anthocyanin.at(i) = data.at(i).y;
83 helios_runtime_error(
"Brown pigment absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
86 if (data.size() != nw) {
87 helios_runtime_error(
"Size of brown pigment absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
89 absorption_brown.resize(nw);
90 for (
int i = 0; i < nw; i++) {
91 absorption_brown.at(i) = data.at(i).y;
96 helios_runtime_error(
"Water absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
99 if (data.size() != nw) {
100 helios_runtime_error(
"Size of water absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
102 absorption_water.resize(nw);
103 for (
int i = 0; i < nw; i++) {
104 absorption_water.at(i) = data.at(i).y;
109 helios_runtime_error(
"Dry mass absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
112 if (data.size() != nw) {
113 helios_runtime_error(
"Size of dry mass absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
115 absorption_drymass.resize(nw);
116 for (
int i = 0; i < nw; i++) {
117 absorption_drymass.at(i) = data.at(i).y;
122 helios_runtime_error(
"Protein absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
125 if (data.size() != nw) {
126 helios_runtime_error(
"Size of protein absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
128 absorption_protein.resize(nw);
129 for (
int i = 0; i < nw; i++) {
130 absorption_protein.at(i) = data.at(i).y;
135 helios_runtime_error(
"Carbon constituent absorption spectral data was not loaded properly from the prospect_spectral_library.xml file.");
137 context->
getGlobalData(
"absorption_carbonconstituents", data);
138 if (data.size() != nw) {
139 helios_runtime_error(
"Size of carbon constituent absorption spectral data loaded from the prospect_spectral_library.xml file was not correct.");
141 absorption_carbonconstituents.resize(nw);
142 for (
int i = 0; i < nw; i++) {
143 absorption_carbonconstituents.at(i) = data.at(i).y;
146 LeafOptics::surface(40.0, R_spec_normal);
150 LeafOptics::surface(90.0, R_spec_diffuse);
154 initializeSpeciesLibrary();
157void LeafOptics::initializeSpeciesLibrary() {
165 props.numberlayers = 1.5f;
166 props.chlorophyllcontent = 30.0f;
167 props.carotenoidcontent = 7.0f;
168 props.anthocyancontent = 1.0f;
169 props.brownpigments = 0.0f;
170 props.watermass = 0.015f;
171 props.drymass = 0.09f;
172 props.protein = 0.0f;
173 props.carbonconstituents = 0.0f;
174 species_library[
"default"] = props;
178 props.numberlayers = 2.00517f;
179 props.chlorophyllcontent = 30.2697f;
180 props.carotenoidcontent = 6.9869f;
181 props.anthocyancontent = 1.35975f;
182 props.brownpigments = 0.107067f;
183 props.watermass = 0.0281985f;
184 props.drymass = 0.0052668f;
185 props.protein = 0.0f;
186 props.carbonconstituents = 0.0f;
187 species_library[
"garden_lettuce"] = props;
191 props.numberlayers = 2.00758f;
192 props.chlorophyllcontent = 43.6375f;
193 props.carotenoidcontent = 10.3145f;
194 props.anthocyancontent = 1.33894f;
195 props.brownpigments = 0.0f;
196 props.watermass = 0.0189936f;
197 props.drymass = 0.00473702f;
198 props.protein = 0.0f;
199 props.carbonconstituents = 0.0f;
200 species_library[
"alfalfa"] = props;
204 props.numberlayers = 1.59203f;
205 props.chlorophyllcontent = 22.8664f;
206 props.carotenoidcontent = 3.9745f;
207 props.anthocyancontent = 0.0f;
208 props.brownpigments = 0.72677f;
209 props.watermass = 0.0149645f;
210 props.drymass = 0.00441283f;
211 props.protein = 0.0f;
212 props.carbonconstituents = 0.0f;
213 species_library[
"corn"] = props;
217 props.numberlayers = 1.76358f;
218 props.chlorophyllcontent = 54.0514f;
219 props.carotenoidcontent = 12.9027f;
220 props.anthocyancontent = 1.75194f;
221 props.brownpigments = 0.0112026f;
222 props.watermass = 0.0185557f;
223 props.drymass = 0.00644855f;
224 props.protein = 0.0f;
225 props.carbonconstituents = 0.0f;
226 species_library[
"sunflower"] = props;
230 props.numberlayers = 1.56274f;
231 props.chlorophyllcontent = 55.9211f;
232 props.carotenoidcontent = 12.4596f;
233 props.anthocyancontent = 1.73981f;
234 props.brownpigments = 0.0f;
235 props.watermass = 0.0127743f;
236 props.drymass = 0.00583351f;
237 props.protein = 0.0f;
238 props.carbonconstituents = 0.0f;
239 species_library[
"english_walnut"] = props;
243 props.numberlayers = 1.67081f;
244 props.chlorophyllcontent = 37.233f;
245 props.carotenoidcontent = 9.98756f;
246 props.anthocyancontent = 0.0f;
247 props.brownpigments = 0.0275106f;
248 props.watermass = 0.0100962f;
249 props.drymass = 0.00484587f;
250 props.protein = 0.0f;
251 props.carbonconstituents = 0.0f;
252 species_library[
"rice"] = props;
256 props.numberlayers = 1.5375f;
257 props.chlorophyllcontent = 46.4121f;
258 props.carotenoidcontent = 12.1394f;
259 props.anthocyancontent = 0.648353f;
260 props.brownpigments = 0.0f;
261 props.watermass = 0.0101049f;
262 props.drymass = 0.00292814f;
263 props.protein = 0.0f;
264 props.carbonconstituents = 0.0f;
265 species_library[
"soybean"] = props;
269 props.numberlayers = 1.42673f;
270 props.chlorophyllcontent = 50.918f;
271 props.carotenoidcontent = 12.5466f;
272 props.anthocyancontent = 1.43905f;
273 props.brownpigments = 0.0798702f;
274 props.watermass = 0.010922f;
275 props.drymass = 0.00599315f;
276 props.protein = 0.0f;
277 props.carbonconstituents = 0.0f;
278 species_library[
"wine_grape"] = props;
282 props.numberlayers = 1.40304f;
283 props.chlorophyllcontent = 48.3467f;
284 props.carotenoidcontent = 11.604f;
285 props.anthocyancontent = 1.45113f;
286 props.brownpigments = 0.0f;
287 props.watermass = 0.0155627f;
288 props.drymass = 0.00261571f;
289 props.protein = 0.0f;
290 props.carbonconstituents = 0.0f;
291 species_library[
"tomato"] = props;
295 props.numberlayers = 1.44041f;
296 props.chlorophyllcontent = 42.3619f;
297 props.carotenoidcontent = 15.6263f;
298 props.anthocyancontent = 0.844536f;
299 props.brownpigments = 0.0f;
300 props.watermass = 0.0150048f;
301 props.drymass = 0.00196285f;
302 props.protein = 0.0f;
303 props.carbonconstituents = 0.0f;
304 species_library[
"common_bean"] = props;
308 props.numberlayers = 1.22669f;
309 props.chlorophyllcontent = 61.5204f;
310 props.carotenoidcontent = 25.6171f;
311 props.anthocyancontent = 2.51899f;
312 props.brownpigments = 0.0f;
313 props.watermass = 0.0221158f;
314 props.drymass = 0.00108066f;
315 props.protein = 0.0f;
316 props.carbonconstituents = 0.0f;
317 species_library[
"cowpea"] = props;
321 std::vector<vec2> reflectivities_fit;
322 std::vector<vec2> transmissivities_fit;
323 getLeafSpectra(leafproperties, reflectivities_fit, transmissivities_fit);
325 std::string leaf_reflectivity_label =
"leaf_reflectivity_" + label;
326 std::string leaf_transmissivity_label =
"leaf_transmissivity_" + label;
327 context->
setGlobalData(leaf_reflectivity_label.c_str(), reflectivities_fit);
328 context->
setGlobalData(leaf_transmissivity_label.c_str(), transmissivities_fit);
330 context->
setPrimitiveData(UUIDs,
"reflectivity_spectrum", leaf_reflectivity_label);
331 context->
setPrimitiveData(UUIDs,
"transmissivity_spectrum", leaf_transmissivity_label);
341 std::string fluspect_biochem_label =
"fluspect_biochem_" + label;
342 std::vector<float> fluspect_biochem = {
343 leafproperties.chlorophyllcontent,
344 leafproperties.carotenoidcontent,
345 leafproperties.watermass,
346 leafproperties.drymass,
347 leafproperties.brownpigments,
348 leafproperties.anthocyancontent,
349 leafproperties.protein,
350 leafproperties.carbonconstituents,
351 leafproperties.numberlayers,
355 context->
setGlobalData(fluspect_biochem_label.c_str(), fluspect_biochem);
356 context->
setPrimitiveData(UUIDs,
"fluspect_spectrum", fluspect_biochem_label);
359 spectrum_parameters_map[label] = leafproperties;
363 std::vector<vec2> reflectivities_fit;
364 std::vector<vec2> transmissivities_fit;
365 getLeafSpectra(leafproperties, reflectivities_fit, transmissivities_fit);
367 std::string leaf_reflectivity_label =
"leaf_reflectivity_" + label;
368 std::string leaf_transmissivity_label =
"leaf_transmissivity_" + label;
369 context->
setGlobalData(leaf_reflectivity_label.c_str(), reflectivities_fit);
370 context->
setGlobalData(leaf_transmissivity_label.c_str(), transmissivities_fit);
375 std::string fluspect_biochem_label =
"fluspect_biochem_" + label;
376 std::vector<float> fluspect_biochem = {
377 leafproperties.chlorophyllcontent, leafproperties.carotenoidcontent, leafproperties.watermass,
378 leafproperties.drymass, leafproperties.brownpigments, leafproperties.anthocyancontent,
379 leafproperties.protein, leafproperties.carbonconstituents, leafproperties.numberlayers,
380 leafproperties.
V2Z, leafproperties.
fqe,
382 context->
setGlobalData(fluspect_biochem_label.c_str(), fluspect_biochem);
385 spectrum_parameters_map[label] = leafproperties;
389void LeafOptics::PROSPECT(
float numberlayers,
float Chlorophyllcontent,
float carotenoidcontent,
float anthocyancontent,
float brownpigments,
float watermass,
float drymass,
float protein,
float carbonconstituents,
390 std::vector<float> &reflectivities_fit, std::vector<float> &transmissivities_fit)
394 double tau, ralf, r12, talf, t12, t21, r21, denom, Ta, Ra, t, r;
400 validation_properties.numberlayers = numberlayers;
401 validation_properties.chlorophyllcontent = Chlorophyllcontent;
402 validation_properties.carotenoidcontent = carotenoidcontent;
403 validation_properties.anthocyancontent = anthocyancontent;
404 validation_properties.brownpigments = brownpigments;
405 validation_properties.watermass = watermass;
406 validation_properties.drymass = drymass;
407 validation_properties.protein = protein;
408 validation_properties.carbonconstituents = carbonconstituents;
409 validateProperties(validation_properties);
413 reflectivities_fit.clear();
414 transmissivities_fit.clear();
415 reflectivities_fit.reserve(nw);
416 transmissivities_fit.reserve(nw);
419 for (
int i = 0; i < LeafOptics::nw; i++) {
421 k = (Chlorophyllcontent * absorption_chlorophyll.at(i) + carotenoidcontent * absorption_carotenoid.at(i) + anthocyancontent * absorption_anthocyanin.at(i) + brownpigments * absorption_brown.at(i) + watermass * absorption_water.at(i) +
422 drymass * absorption_drymass.at(i) + protein * absorption_protein.at(i) + carbonconstituents * absorption_carbonconstituents.at(i)) /
426 tau = transmittance(k);
428 ralf = R_spec_normal.at(i);
430 r12 = R_spec_diffuse.at(i);
437 t21 = t12 / (refractiveindex.at(i) * refractiveindex.at(i));
442 denom = 1 - r21 * r21 * tau * tau;
443 Ta = talf * tau * t21 / denom;
444 Ra = ralf + r21 * tau * Ta;
448 t = t12 * tau * t21 / denom;
449 r = r12 + r21 * tau * t;
452 double D, rq, tq, a, b, bNm1, bN2, a2, Rsub, Tsub;
453 D = sqrt((1. + r + t) * (1. + r - t) * (1. - r + t) * (1. - r - t));
456 a = (1. + rq - tq + D) / (2 * r);
457 b = (1. - rq + tq + D) / (2 * t);
458 bNm1 = std::pow(b, (numberlayers - 1));
461 denom = a2 * bN2 - 1.;
462 Rsub = a * (bN2 - 1.) / denom;
463 Tsub = bNm1 * (a2 - 1.) / denom;
469 if ((r + t) >= 1.0) {
470 Tsub = t / (t + (1. - t) * (numberlayers - 1));
476 denom = 1 - Rsub * r;
477 transmissivities_fit.push_back(Ta * Tsub / denom);
478 reflectivities_fit.push_back(Ra + Ta * Rsub * t / denom);
488 if (leafproperties.numberlayers < 1.f) {
490 "ERROR (LeafOptics): the leaf structure parameter 'numberlayers' (N) was " + std::to_string(leafproperties.numberlayers) +
491 ", but PROSPECT requires numberlayers >= 1 because it counts the number of elementary layers in the leaf. Set numberlayers to 1 for a single compact layer, or a larger value (typically 1.0-3.0) for a more scattering leaf.");
496 auto check_non_negative = [](
float value,
const char *name,
const char *units) {
498 helios_runtime_error(
"ERROR (LeafOptics): the leaf constituent '" + std::string(name) +
"' was " + std::to_string(value) +
" " + units +
", but constituent contents cannot be negative. Set it to zero to disable this constituent.");
502 check_non_negative(leafproperties.chlorophyllcontent,
"chlorophyllcontent",
"ug/cm^2");
503 check_non_negative(leafproperties.carotenoidcontent,
"carotenoidcontent",
"ug/cm^2");
504 check_non_negative(leafproperties.anthocyancontent,
"anthocyancontent",
"ug/cm^2");
505 check_non_negative(leafproperties.brownpigments,
"brownpigments",
"(unitless)");
506 check_non_negative(leafproperties.watermass,
"watermass",
"g/cm^2");
507 check_non_negative(leafproperties.drymass,
"drymass",
"g/cm^2");
508 check_non_negative(leafproperties.protein,
"protein",
"g/cm^2");
509 check_non_negative(leafproperties.carbonconstituents,
"carbonconstituents",
"g/cm^2");
514 std::vector<float> reflectivities_fit_y, transmissivities_fit_y;
516 validateProperties(leafproperties);
520 reflectivities_fit.clear();
521 transmissivities_fit.clear();
522 reflectivities_fit.reserve(nw);
523 transmissivities_fit.reserve(nw);
525 float numberlayers = leafproperties.numberlayers;
526 float chlorophyllcontent = leafproperties.chlorophyllcontent;
527 float carotenoidcontent = leafproperties.carotenoidcontent;
528 float anthocyancontent = leafproperties.anthocyancontent;
529 float brownpigments = leafproperties.brownpigments;
530 float watermass = leafproperties.watermass;
531 float drymass = leafproperties.drymass;
532 float protein = leafproperties.protein;
533 float carbonconstituents = leafproperties.carbonconstituents;
535 if (protein == 0 && carbonconstituents == 0) {
536 if (drymass == 0 && message_flag) {
537 std::cerr <<
"Warning: No leaf mass given" << std::endl;
542 PROSPECT(numberlayers, chlorophyllcontent, carotenoidcontent, anthocyancontent, brownpigments, watermass, drymass, protein, carbonconstituents, reflectivities_fit_y, transmissivities_fit_y);
545 for (
int iwave = 0; iwave < nw; iwave++) {
546 reflectivities_fit.push_back(
make_vec2(iwave + 400, reflectivities_fit_y.at(iwave)));
547 transmissivities_fit.push_back(
make_vec2(iwave + 400, transmissivities_fit_y.at(iwave)));
551void LeafOptics::surface(
float degree, std::vector<float> &reflectivities)
556 double rad2degree = 57.2958;
558 double n2, np, nm, a, k, sinvalue, b1, b2, b, b3, a3, ts, tp1, tp2, tp3, tp4, tp5, tp, tav;
559 for (
int i = 0; i < LeafOptics::nw; i++) {
560 double n = refractiveindex.at(i);
564 a = (n + 1) * (n + 1) / 2;
565 k = -(n2 - 1) * (n2 - 1) / 4;
566 sinvalue = sin(degree / rad2degree);
568 b1 = sqrt((sinvalue * sinvalue - np / 2) * (sinvalue * sinvalue - np / 2) + k);
572 b2 = sinvalue * sinvalue - np / 2;
576 ts = (k * k / (6 * b3) + k / b - b / 2) - (k * k / (6 * a3) + k / a - a / 2);
577 tp1 = -2 * n2 * (b - a) / (np * np);
578 tp2 = -2 * n2 * np * log(b / a) / (nm * nm);
579 tp3 = n2 * (1 / b - 1 / a) / 2;
580 tp4 = 16 * n2 * n2 * (n2 * n2 + 1) * log((2 * np * b - nm * nm) / (2 * np * a - nm * nm)) / (np * np * np * nm * nm);
581 tp5 = 16 * n2 * n2 * n2 * (1 / (2 * np * b - nm * nm) - 1 / (2 * np * a - nm * nm)) / (np * np * np);
582 tp = tp1 + tp2 + tp3 + tp4 + tp5;
583 tav = (ts + tp) / (2 * sinvalue * sinvalue);
584 reflectivities.push_back(1.0 - tav);
589float LeafOptics::transmittance(
double k) {
605 yy = (((((((((((((((-3.60311230482612224e-13L * xx + 3.46348526554087424e-12L) * xx - 2.99627399604128973e-11L) * xx + 2.57747807106988589e-10L) * xx - 2.09330568435488303e-9L) * xx + 1.59501329936987818e-8L) * xx - 1.13717900285428895e-7L) *
607 7.55292885309152956e-7L) *
609 4.64980751480619431e-6L) *
611 2.63830365675408129e-5L) *
613 1.37089870978830576e-4L) *
615 6.47686503728103400e-4L) *
617 2.76060141343627983e-3L) *
619 1.05306034687449505e-2L) *
621 3.57191348753631956e-2L) *
623 1.07774527938978692e-1L) *
625 2.96997075145080963e-1L;
626 yy = (yy * xx + 8.64664716763387311e-1L) * xx + 7.42047691268006429e-1L;
628 tau = (1.0 - k) * exp(-k) + k * k * yy;
632 xx = 14.5 / (k + 3.25) - 1.0;
633 yy = (((((((((((((((-1.62806570868460749e-12L * xx - 8.95400579318284288e-13L) * xx - 4.08352702838151578e-12L) * xx - 1.45132988248537498e-11L) * xx - 8.35086918940757852e-11L) * xx - 2.13638678953766289e-10L) * xx -
634 1.10302431467069770e-9L) *
636 3.67128915633455484e-9L) *
638 1.66980544304104726e-8L) *
640 6.11774386401295125e-8L) *
642 2.70306163610271497e-7L) *
644 1.05565006992891261e-6L) *
646 4.72090467203711484e-6L) *
648 1.95076375089955937e-5L) *
650 9.16450482931221453e-5L) *
652 4.05892130452128677e-4L) *
654 2.14213055000334718e-3L;
655 yy = ((yy * xx - 1.06374875116569657e-2L) * xx - 8.50699154984571871e-2L) * xx + 9.23755307807784058e-1L;
656 yy = exp(-k) * yy / k;
657 tau = (1.0 - k) * exp(-k) + k * k * yy;
666 for (
const auto &data: output_prim_data) {
667 if (data ==
"chlorophyll") {
668 context->
setPrimitiveData(UUIDs,
"chlorophyll", leafproperties.chlorophyllcontent);
669 }
else if (data ==
"carotenoid") {
670 context->
setPrimitiveData(UUIDs,
"carotenoid", leafproperties.carotenoidcontent);
671 }
else if (data ==
"anthocyanin") {
672 context->
setPrimitiveData(UUIDs,
"anthocyanin", leafproperties.anthocyancontent);
673 }
else if (data ==
"brown" && leafproperties.brownpigments > 0.0) {
675 }
else if (data ==
"water") {
677 }
else if (data ==
"drymass" && leafproperties.drymass > 0.0) {
679 }
else if (data ==
"protein" && leafproperties.drymass == 0.0) {
681 }
else if (data ==
"cellulose" && leafproperties.drymass == 0.0) {
682 context->
setPrimitiveData(UUIDs,
"cellulose", leafproperties.carbonconstituents);
688 const std::string prefix =
"leaf_reflectivity_";
690 for (
uint UUID: UUIDs) {
697 std::string spectrum_label;
701 if (spectrum_label.find(prefix) != 0) {
706 std::string user_label = spectrum_label.substr(prefix.length());
709 auto it = spectrum_parameters_map.find(user_label);
710 if (it == spectrum_parameters_map.end()) {
718 for (
const auto &data: output_prim_data) {
719 if (data ==
"chlorophyll") {
721 }
else if (data ==
"carotenoid") {
723 }
else if (data ==
"anthocyanin") {
725 }
else if (data ==
"brown" && props.brownpigments > 0.0) {
727 }
else if (data ==
"water") {
729 }
else if (data ==
"drymass" && props.drymass > 0.0) {
731 }
else if (data ==
"protein" && props.drymass == 0.0) {
733 }
else if (data ==
"cellulose" && props.drymass == 0.0) {
746 std::string species_lower = species;
747 std::transform(species_lower.begin(), species_lower.end(), species_lower.begin(), ::tolower);
750 auto it = species_library.find(species_lower);
751 if (it != species_library.end()) {
753 leafproperties = it->second;
755 std::cout <<
"Setting Leaf Optics Properties to species: " << species << std::endl;
760 std::cerr <<
"WARNING (LeafOptics): unknown species \"" << species <<
"\". Using default properties." << std::endl;
762 leafproperties = species_library[
"default"];
767 message_flag =
false;
775 if (strcmp(label,
"chlorophyll") == 0 || strcmp(label,
"carotenoid") == 0 || strcmp(label,
"anthocyanin") == 0 || strcmp(label,
"brown") == 0 || strcmp(label,
"water") == 0 || strcmp(label,
"drymass") == 0 || strcmp(label,
"protein") == 0 ||
776 strcmp(label,
"cellulose") == 0) {
777 output_prim_data.emplace_back(label);
780 std::cout <<
"WARNING (LeafOptics::optionalOutputPrimitiveData): unknown output primitive data " << label << std::endl;
793 float N_area_ug_cm2 = N_area_gN_m2 * 100.0f;
794 props.chlorophyllcontent = N_area_ug_cm2 * params.f_photosynthetic * params.N_to_Cab_coefficient;
797 props.chlorophyllcontent = std::max(5.0f, std::min(80.0f, props.chlorophyllcontent));
800 props.carotenoidcontent = props.chlorophyllcontent * params.Car_to_Cab_ratio;
803 props.numberlayers = params.numberlayers;
804 props.anthocyancontent = params.anthocyancontent;
805 props.brownpigments = params.brownpigments;
806 props.watermass = params.watermass;
807 props.drymass = params.drymass;
808 props.protein = params.protein;
809 props.carbonconstituents = params.carbonconstituents;
810 props.
V2Z = params.V2Z;
811 props.
fqe = params.fqe;
816std::map<uint, std::vector<uint>> LeafOptics::groupPrimitivesByObject(
const std::vector<uint> &UUIDs) {
817 std::map<uint, std::vector<uint>> object_groups;
822 for (
uint UUID: UUIDs) {
824 warnings.
addWarning(
"primitive_does_not_exist",
"Primitive UUID " + std::to_string(UUID) +
" does not exist, skipping.");
832 warnings.
addWarning(
"primitive_no_parent_object",
"Primitive UUID " + std::to_string(UUID) +
" has no parent object, skipping.");
836 object_groups[objID].push_back(UUID);
839 warnings.
report(std::cerr);
841 return object_groups;
844void LeafOptics::createAdaptiveBins(
const std::vector<float> &nitrogen_values) {
845 nitrogen_bins.clear();
847 if (nitrogen_values.empty()) {
852 std::vector<float> sorted_N = nitrogen_values;
853 std::sort(sorted_N.begin(), sorted_N.end());
855 uint count = sorted_N.size();
859 if (count < target_bins) {
864 std::vector<float> bin_centers;
865 for (
uint i = 0; i < target_bins; i++) {
867 uint idx = (i * count / target_bins) + (count / (2 * target_bins));
871 float center = sorted_N[idx];
874 bool is_duplicate =
false;
875 for (
float existing: bin_centers) {
876 if (std::abs(center - existing) < 0.001f) {
882 bin_centers.push_back(center);
887 for (
uint i = 0; i < bin_centers.size(); i++) {
889 bin.N_center = bin_centers[i];
890 bin.spectrum_label =
"Nauto_" + std::to_string(i);
894 run(props, bin.spectrum_label);
896 nitrogen_bins.push_back(bin);
900 std::cout <<
"LeafOptics: Created " << nitrogen_bins.size() <<
" nitrogen-based spectrum bins." << std::endl;
904uint LeafOptics::findNearestBin(
float N_value) {
905 if (nitrogen_bins.empty()) {
906 helios_runtime_error(
"ERROR (LeafOptics::findNearestBin): No nitrogen bins have been created.");
910 float best_distance = std::abs(N_value - nitrogen_bins[0].N_center);
912 for (
uint i = 1; i < nitrogen_bins.size(); i++) {
913 float distance = std::abs(N_value - nitrogen_bins[i].N_center);
914 if (distance < best_distance) {
915 best_distance = distance;
923bool LeafOptics::shouldReassign(
float current_N,
uint current_bin) {
924 if (current_bin >= nitrogen_bins.size()) {
928 float bin_center = nitrogen_bins[current_bin].N_center;
929 float absolute_change = std::abs(current_N - bin_center);
937 float relative_change = (bin_center > 0.0f) ? (absolute_change / bin_center) : 0.0f;
941bool LeafOptics::isSignificantImprovement(
float current_N,
uint old_bin,
uint new_bin) {
942 if (old_bin >= nitrogen_bins.size() || new_bin >= nitrogen_bins.size()) {
946 float old_distance = std::abs(current_N - nitrogen_bins[old_bin].N_center);
947 float new_distance = std::abs(current_N - nitrogen_bins[new_bin].N_center);
953void LeafOptics::assignSpectrumToPrimitives(
const std::vector<uint> &UUIDs,
uint bin_index) {
954 if (bin_index >= nitrogen_bins.size()) {
955 helios_runtime_error(
"ERROR (LeafOptics::assignSpectrumToPrimitives): Invalid bin index " + std::to_string(bin_index));
958 const std::string &label = nitrogen_bins[bin_index].spectrum_label;
959 std::string refl_label =
"leaf_reflectivity_" + label;
960 std::string trans_label =
"leaf_transmissivity_" + label;
968 std::string fluspect_label =
"fluspect_biochem_" + label;
972 if (!output_prim_data.empty()) {
974 LeafOpticsProperties props = computePropertiesFromNitrogen(nitrogen_bins[bin_index].N_center, nitrogen_params);
976 for (
const auto &data: output_prim_data) {
977 if (data ==
"chlorophyll") {
979 }
else if (data ==
"carotenoid") {
981 }
else if (data ==
"anthocyanin") {
983 }
else if (data ==
"brown" && props.brownpigments > 0.0) {
985 }
else if (data ==
"water") {
987 }
else if (data ==
"drymass" && props.drymass > 0.0) {
989 }
else if (data ==
"protein" && props.drymass == 0.0) {
991 }
else if (data ==
"cellulose" && props.drymass == 0.0) {
1001 std::cout <<
"LeafOptics: Empty UUID list provided to nitrogen mode, nothing to do." << std::endl;
1007 std::map<uint, std::vector<uint>> object_groups = groupPrimitivesByObject(UUIDs);
1009 if (object_groups.empty()) {
1011 std::cerr <<
"WARNING (LeafOptics::run): No valid objects found for nitrogen-based leaf optics." << std::endl;
1017 std::vector<float> nitrogen_values;
1018 std::map<uint, float> object_nitrogen;
1020 for (
const auto &pair: object_groups) {
1021 uint objID = pair.first;
1025 helios_runtime_error(
"ERROR (LeafOptics::run): Object " + std::to_string(objID) +
" does not have 'leaf_nitrogen_gN_m2' data. Enable PlantArchitecture nitrogen model first.");
1029 context->
getObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1030 nitrogen_values.push_back(N_area);
1031 object_nitrogen[objID] = N_area;
1034 if (!nitrogen_mode_active) {
1036 nitrogen_params = params;
1039 createAdaptiveBins(nitrogen_values);
1042 for (
const auto &pair: object_groups) {
1043 uint objID = pair.first;
1044 const std::vector<uint> &obj_UUIDs = pair.second;
1045 float N_area = object_nitrogen[objID];
1047 uint best_bin = findNearestBin(N_area);
1048 assignSpectrumToPrimitives(obj_UUIDs, best_bin);
1051 ObjectAssignment assignment;
1052 assignment.bin_index = best_bin;
1053 assignment.N_at_assignment = N_area;
1054 assignment.primitive_UUIDs = obj_UUIDs;
1055 object_assignments[objID] = assignment;
1058 for (
uint UUID: obj_UUIDs) {
1059 primitive_to_object[UUID] = objID;
1063 nitrogen_mode_active =
true;
1066 std::cout <<
"LeafOptics: Nitrogen mode initialized with " << object_assignments.size() <<
" leaf objects assigned to " << nitrogen_bins.size() <<
" spectrum bins." << std::endl;
1073 std::set<uint> current_objects;
1074 for (
const auto &pair: object_groups) {
1075 current_objects.insert(pair.first);
1079 std::set<uint> tracked_objects;
1080 for (
const auto &pair: object_assignments) {
1081 tracked_objects.insert(pair.first);
1085 std::vector<uint> removed_objects;
1086 for (
uint objID: tracked_objects) {
1087 if (current_objects.find(objID) == current_objects.end()) {
1088 removed_objects.push_back(objID);
1093 for (
uint objID: removed_objects) {
1094 const ObjectAssignment &assignment = object_assignments[objID];
1095 for (
uint UUID: assignment.primitive_UUIDs) {
1096 primitive_to_object.erase(UUID);
1098 object_assignments.erase(objID);
1102 for (
const auto &pair: object_groups) {
1103 uint objID = pair.first;
1104 const std::vector<uint> &obj_UUIDs = pair.second;
1105 float N_area = object_nitrogen[objID];
1107 if (tracked_objects.find(objID) == tracked_objects.end()) {
1109 uint best_bin = findNearestBin(N_area);
1110 assignSpectrumToPrimitives(obj_UUIDs, best_bin);
1112 ObjectAssignment assignment;
1113 assignment.bin_index = best_bin;
1114 assignment.N_at_assignment = N_area;
1115 assignment.primitive_UUIDs = obj_UUIDs;
1116 object_assignments[objID] = assignment;
1118 for (
uint UUID: obj_UUIDs) {
1119 primitive_to_object[UUID] = objID;
1124 ObjectAssignment &assignment = object_assignments[objID];
1127 std::set<uint> prev_UUIDs(assignment.primitive_UUIDs.begin(), assignment.primitive_UUIDs.end());
1128 std::vector<uint> new_UUIDs;
1129 for (
uint UUID: obj_UUIDs) {
1130 if (prev_UUIDs.find(UUID) == prev_UUIDs.end()) {
1131 new_UUIDs.push_back(UUID);
1136 assignment.primitive_UUIDs = obj_UUIDs;
1137 for (
uint UUID: obj_UUIDs) {
1138 primitive_to_object[UUID] = objID;
1142 if (!new_UUIDs.empty()) {
1143 uint best_bin = findNearestBin(N_area);
1144 assignSpectrumToPrimitives(new_UUIDs, best_bin);
1145 assignment.bin_index = best_bin;
1146 assignment.N_at_assignment = N_area;
1147 }
else if (shouldReassign(N_area, assignment.bin_index)) {
1148 uint best_bin = findNearestBin(N_area);
1150 if (best_bin != assignment.bin_index && isSignificantImprovement(N_area, assignment.bin_index, best_bin)) {
1151 assignSpectrumToPrimitives(obj_UUIDs, best_bin);
1152 assignment.bin_index = best_bin;
1153 assignment.N_at_assignment = N_area;
1162 if (!nitrogen_mode_active) {
1163 helios_runtime_error(
"ERROR (LeafOptics::updateNitrogenBasedSpectra): Nitrogen mode is not active. "
1164 "Call run() with LeafOpticsProperties_Nauto first to initialize nitrogen mode.");
1170 for (
auto &pair: object_assignments) {
1171 uint objID = pair.first;
1172 ObjectAssignment &assignment = pair.second;
1176 warnings.
addWarning(
"missing_nitrogen_data",
"Object " + std::to_string(objID) +
" no longer has 'leaf_nitrogen_gN_m2' data, skipping.");
1181 context->
getObjectData(objID,
"leaf_nitrogen_gN_m2", current_N);
1184 if (shouldReassign(current_N, assignment.bin_index)) {
1185 uint best_bin = findNearestBin(current_N);
1187 if (best_bin != assignment.bin_index && isSignificantImprovement(current_N, assignment.bin_index, best_bin)) {
1188 assignSpectrumToPrimitives(assignment.primitive_UUIDs, best_bin);
1189 assignment.bin_index = best_bin;
1190 assignment.N_at_assignment = current_N;
1195 warnings.
report(std::cerr);