3#define DOCTEST_CONFIG_IMPLEMENT
5#include "doctest_utils.h"
11DOCTEST_TEST_CASE(
"LeafOpticsProperties Default Constructor") {
14 DOCTEST_CHECK(props.numberlayers == doctest::Approx(1.5f).epsilon(err_tol));
15 DOCTEST_CHECK(props.brownpigments == doctest::Approx(0.f).epsilon(err_tol));
16 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(30.f).epsilon(err_tol));
17 DOCTEST_CHECK(props.carotenoidcontent == doctest::Approx(7.f).epsilon(err_tol));
18 DOCTEST_CHECK(props.anthocyancontent == doctest::Approx(1.f).epsilon(err_tol));
19 DOCTEST_CHECK(props.watermass == doctest::Approx(0.015f).epsilon(err_tol));
20 DOCTEST_CHECK(props.drymass == doctest::Approx(0.09f).epsilon(err_tol));
21 DOCTEST_CHECK(props.protein == doctest::Approx(0.f).epsilon(err_tol));
22 DOCTEST_CHECK(props.carbonconstituents == doctest::Approx(0.f).epsilon(err_tol));
25DOCTEST_TEST_CASE(
"LeafOpticsProperties Parameterized Constructor") {
26 float chl = 50.0f, car = 10.0f, ant = 2.0f, water = 0.020f, dry = 0.08f, prot = 0.001f, carb = 0.005f;
29 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(chl).epsilon(err_tol));
30 DOCTEST_CHECK(props.carotenoidcontent == doctest::Approx(car).epsilon(err_tol));
31 DOCTEST_CHECK(props.anthocyancontent == doctest::Approx(ant).epsilon(err_tol));
32 DOCTEST_CHECK(props.watermass == doctest::Approx(water).epsilon(err_tol));
33 DOCTEST_CHECK(props.drymass == doctest::Approx(dry).epsilon(err_tol));
34 DOCTEST_CHECK(props.protein == doctest::Approx(prot).epsilon(err_tol));
35 DOCTEST_CHECK(props.carbonconstituents == doctest::Approx(carb).epsilon(err_tol));
38 DOCTEST_CHECK(props.numberlayers == doctest::Approx(1.5f).epsilon(err_tol));
39 DOCTEST_CHECK(props.brownpigments == doctest::Approx(0.f).epsilon(err_tol));
42DOCTEST_TEST_CASE(
"LeafOpticsProperties Edge Cases") {
45 DOCTEST_CHECK(zero_props.chlorophyllcontent == doctest::Approx(0.0f).epsilon(err_tol));
46 DOCTEST_CHECK(zero_props.carotenoidcontent == doctest::Approx(0.0f).epsilon(err_tol));
50 DOCTEST_CHECK(extreme_props.chlorophyllcontent == doctest::Approx(1000.0f).epsilon(err_tol));
51 DOCTEST_CHECK(extreme_props.protein == doctest::Approx(0.5f).epsilon(err_tol));
54DOCTEST_TEST_CASE(
"LeafOptics Constructor and Initialization") {
57 DOCTEST_CHECK_NOTHROW(
LeafOptics leafoptics(&context_test));
60 leafoptics.disableMessages();
74DOCTEST_TEST_CASE(
"LeafOptics Enable/Disable Messages") {
78 DOCTEST_CHECK_NOTHROW(leafoptics.enableMessages());
79 DOCTEST_CHECK_NOTHROW(leafoptics.disableMessages());
82DOCTEST_TEST_CASE(
"LeafOptics Basic Run with Label Only") {
85 leafoptics.disableMessages();
88 std::string label =
"test_basic";
90 DOCTEST_CHECK_NOTHROW(leafoptics.run(props, label));
93 std::string refl_label =
"leaf_reflectivity_" + label;
94 std::string trans_label =
"leaf_transmissivity_" + label;
100 std::vector<vec2> refl_data, trans_data;
101 DOCTEST_CHECK_NOTHROW(context_test.
getGlobalData(refl_label.c_str(), refl_data));
102 DOCTEST_CHECK_NOTHROW(context_test.
getGlobalData(trans_label.c_str(), trans_data));
104 DOCTEST_CHECK(refl_data.size() == 2101);
105 DOCTEST_CHECK(trans_data.size() == 2101);
108DOCTEST_TEST_CASE(
"LeafOptics Run with UUIDs") {
111 leafoptics.disableMessages();
114 std::vector<uint> UUIDs;
119 std::string label =
"test_uuids";
121 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, props, label));
124 std::string refl_label =
"leaf_reflectivity_" + label;
125 std::string trans_label =
"leaf_transmissivity_" + label;
131 for (
uint UUID: UUIDs) {
135 std::string refl_spectrum_label, trans_spectrum_label;
136 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"reflectivity_spectrum", refl_spectrum_label));
137 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"transmissivity_spectrum", trans_spectrum_label));
139 DOCTEST_CHECK(refl_spectrum_label == refl_label);
140 DOCTEST_CHECK(trans_spectrum_label == trans_label);
144DOCTEST_TEST_CASE(
"LeafOptics Run with Empty UUID Vector") {
147 leafoptics.disableMessages();
149 std::vector<uint> empty_UUIDs;
151 std::string label =
"test_empty";
153 DOCTEST_CHECK_NOTHROW(leafoptics.run(empty_UUIDs, props, label));
156 std::string refl_label =
"leaf_reflectivity_" + label;
157 std::string trans_label =
"leaf_transmissivity_" + label;
163DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - Default Properties") {
166 leafoptics.disableMessages();
169 std::vector<vec2> reflectivities, transmissivities;
171 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(props, reflectivities, transmissivities));
173 DOCTEST_CHECK(reflectivities.size() == 2101);
174 DOCTEST_CHECK(transmissivities.size() == 2101);
177 DOCTEST_CHECK(reflectivities[0].x == doctest::Approx(400.0f).epsilon(err_tol));
178 DOCTEST_CHECK(reflectivities.back().x == doctest::Approx(2500.0f).epsilon(err_tol));
179 DOCTEST_CHECK(transmissivities[0].x == doctest::Approx(400.0f).epsilon(err_tol));
180 DOCTEST_CHECK(transmissivities.back().x == doctest::Approx(2500.0f).epsilon(err_tol));
183 for (
const auto &refl: reflectivities) {
184 DOCTEST_CHECK(refl.y >= 0.0f);
185 DOCTEST_CHECK(refl.y <= 1.0f);
187 for (
const auto &trans: transmissivities) {
188 DOCTEST_CHECK(trans.y >= 0.0f);
189 DOCTEST_CHECK(trans.y <= 1.0f);
193 for (
size_t i = 0; i < reflectivities.size(); ++i) {
194 DOCTEST_CHECK(reflectivities[i].y + transmissivities[i].y <= 1.01f);
198DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - High Chlorophyll") {
201 leafoptics.disableMessages();
204 props.chlorophyllcontent = 80.0f;
205 props.carotenoidcontent = 15.0f;
207 std::vector<vec2> reflectivities, transmissivities;
208 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(props, reflectivities, transmissivities));
210 DOCTEST_CHECK(reflectivities.size() == 2101);
211 DOCTEST_CHECK(transmissivities.size() == 2101);
217DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - Autumn Leaves") {
220 leafoptics.disableMessages();
223 props.chlorophyllcontent = 5.0f;
224 props.carotenoidcontent = 20.0f;
225 props.anthocyancontent = 15.0f;
226 props.brownpigments = 0.5f;
228 std::vector<vec2> reflectivities, transmissivities;
229 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(props, reflectivities, transmissivities));
231 DOCTEST_CHECK(reflectivities.size() == 2101);
232 DOCTEST_CHECK(transmissivities.size() == 2101);
235DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - Prospect-PRO Mode") {
238 leafoptics.disableMessages();
241 props.protein = 0.002f;
242 props.carbonconstituents = 0.008f;
243 props.drymass = 0.0f;
245 std::vector<vec2> reflectivities, transmissivities;
246 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(props, reflectivities, transmissivities));
248 DOCTEST_CHECK(reflectivities.size() == 2101);
249 DOCTEST_CHECK(transmissivities.size() == 2101);
252DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - Extreme Values") {
255 leafoptics.disableMessages();
259 extreme_props.numberlayers = 5.0f;
260 extreme_props.chlorophyllcontent = 200.0f;
261 extreme_props.watermass = 0.1f;
263 std::vector<vec2> reflectivities, transmissivities;
264 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(extreme_props, reflectivities, transmissivities));
266 DOCTEST_CHECK(reflectivities.size() == 2101);
267 DOCTEST_CHECK(transmissivities.size() == 2101);
270 float avg_transmittance = 0.0f;
271 for (
const auto &trans: transmissivities) {
272 avg_transmittance += trans.y;
274 avg_transmittance /= transmissivities.size();
275 DOCTEST_CHECK(avg_transmittance < 0.1f);
278DOCTEST_TEST_CASE(
"LeafOptics GetLeafSpectra - Zero Values") {
281 leafoptics.disableMessages();
285 zero_props.chlorophyllcontent = 0.0f;
286 zero_props.carotenoidcontent = 0.0f;
287 zero_props.anthocyancontent = 0.0f;
288 zero_props.brownpigments = 0.0f;
290 std::vector<vec2> reflectivities, transmissivities;
291 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(zero_props, reflectivities, transmissivities));
293 DOCTEST_CHECK(reflectivities.size() == 2101);
294 DOCTEST_CHECK(transmissivities.size() == 2101);
297 float avg_transmittance = 0.0f;
298 for (
const auto &trans: transmissivities) {
299 avg_transmittance += trans.y;
301 avg_transmittance /= transmissivities.size();
302 DOCTEST_CHECK(avg_transmittance > 0.1f);
305DOCTEST_TEST_CASE(
"LeafOptics SetProperties") {
308 leafoptics.disableMessages();
311 std::vector<uint> UUIDs;
316 props.chlorophyllcontent = 45.0f;
317 props.carotenoidcontent = 12.0f;
318 props.anthocyancontent = 3.0f;
319 props.watermass = 0.018f;
320 props.drymass = 0.085f;
323 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
324 leafoptics.optionalOutputPrimitiveData(
"carotenoid");
325 leafoptics.optionalOutputPrimitiveData(
"anthocyanin");
326 leafoptics.optionalOutputPrimitiveData(
"water");
327 leafoptics.optionalOutputPrimitiveData(
"drymass");
329 DOCTEST_CHECK_NOTHROW(leafoptics.setProperties(UUIDs, props));
332 for (
uint UUID: UUIDs) {
333 float chl, car, ant, water, dry;
335 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"chlorophyll", chl));
336 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"carotenoid", car));
337 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"anthocyanin", ant));
341 DOCTEST_CHECK(chl == doctest::Approx(props.chlorophyllcontent).epsilon(err_tol));
342 DOCTEST_CHECK(car == doctest::Approx(props.carotenoidcontent).epsilon(err_tol));
343 DOCTEST_CHECK(ant == doctest::Approx(props.anthocyancontent).epsilon(err_tol));
344 DOCTEST_CHECK(water == doctest::Approx(props.watermass).epsilon(err_tol));
345 DOCTEST_CHECK(dry == doctest::Approx(props.drymass).epsilon(err_tol));
349DOCTEST_TEST_CASE(
"LeafOptics SetProperties with Brown Pigments") {
352 leafoptics.disableMessages();
354 std::vector<uint> UUIDs;
358 props.brownpigments = 0.3f;
361 leafoptics.optionalOutputPrimitiveData(
"brown");
363 DOCTEST_CHECK_NOTHROW(leafoptics.setProperties(UUIDs, props));
367 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"brown", brown));
368 DOCTEST_CHECK(brown == doctest::Approx(props.brownpigments).epsilon(err_tol));
371DOCTEST_TEST_CASE(
"LeafOptics SetProperties with Protein Mode") {
374 leafoptics.disableMessages();
376 std::vector<uint> UUIDs;
380 props.drymass = 0.0f;
381 props.protein = 0.001f;
382 props.carbonconstituents = 0.005f;
385 leafoptics.optionalOutputPrimitiveData(
"protein");
386 leafoptics.optionalOutputPrimitiveData(
"cellulose");
388 DOCTEST_CHECK_NOTHROW(leafoptics.setProperties(UUIDs, props));
391 float protein, carbon;
392 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"protein", protein));
393 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"cellulose", carbon));
395 DOCTEST_CHECK(protein == doctest::Approx(props.protein).epsilon(err_tol));
396 DOCTEST_CHECK(carbon == doctest::Approx(props.carbonconstituents).epsilon(err_tol));
399DOCTEST_TEST_CASE(
"LeafOptics SetProperties with Empty UUID Vector") {
402 leafoptics.disableMessages();
404 std::vector<uint> empty_UUIDs;
408 DOCTEST_CHECK_NOTHROW(leafoptics.setProperties(empty_UUIDs, props));
411DOCTEST_TEST_CASE(
"LeafOptics Different Label Formats") {
414 leafoptics.disableMessages();
419 DOCTEST_CHECK_NOTHROW(leafoptics.run(props,
""));
423 std::string special_label =
"test_123-abc.xyz";
424 DOCTEST_CHECK_NOTHROW(leafoptics.run(props, special_label));
425 std::string expected_refl =
"leaf_reflectivity_" + special_label;
429 std::string long_label(100,
'x');
430 DOCTEST_CHECK_NOTHROW(leafoptics.run(props, long_label));
431 std::string expected_long_refl =
"leaf_reflectivity_" + long_label;
435DOCTEST_TEST_CASE(
"LeafOptics Wavelength Range and Data Consistency") {
438 leafoptics.disableMessages();
441 std::vector<vec2> reflectivities, transmissivities;
443 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
446 for (
size_t i = 0; i < reflectivities.size(); ++i) {
447 DOCTEST_CHECK(reflectivities[i].x == doctest::Approx(transmissivities[i].x).epsilon(err_tol));
451 float wavelength_diff = reflectivities[i].x - reflectivities[i - 1].x;
452 DOCTEST_CHECK(wavelength_diff == doctest::Approx(1.0f).epsilon(err_tol));
457DOCTEST_TEST_CASE(
"LeafOptics Physical Realism Checks") {
460 leafoptics.disableMessages();
463 props.numberlayers = 2.0f;
464 props.chlorophyllcontent = 60.0f;
466 std::vector<vec2> reflectivities, transmissivities;
467 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
471 bool found_red_edge =
false;
472 for (
size_t i = 1; i < reflectivities.size() - 1; ++i) {
473 float wavelength = reflectivities[i].x;
474 if (wavelength >= 700.0f && wavelength <= 750.0f) {
475 float slope = reflectivities[i + 1].y - reflectivities[i - 1].y;
476 if (slope > 0.001f) {
477 found_red_edge =
true;
482 DOCTEST_CHECK(found_red_edge);
485 float visible_avg = 0.0f, nir_avg = 0.0f;
486 int visible_count = 0, nir_count = 0;
488 for (
const auto &refl: reflectivities) {
489 if (refl.x >= 400.0f && refl.x <= 700.0f) {
490 visible_avg += refl.y;
492 }
else if (refl.x >= 800.0f && refl.x <= 1200.0f) {
498 visible_avg /= visible_count;
499 nir_avg /= nir_count;
501 DOCTEST_CHECK(nir_avg > visible_avg);
504DOCTEST_TEST_CASE(
"LeafOptics Multiple Runs Data Consistency") {
507 leafoptics.disableMessages();
510 props.chlorophyllcontent = 40.0f;
512 std::vector<vec2> refl1, trans1, refl2, trans2;
515 leafoptics.getLeafSpectra(props, refl1, trans1);
516 leafoptics.getLeafSpectra(props, refl2, trans2);
519 DOCTEST_CHECK(refl1.size() == refl2.size());
520 DOCTEST_CHECK(trans1.size() == trans2.size());
522 for (
size_t i = 0; i < refl1.size(); ++i) {
523 DOCTEST_CHECK(refl1[i].x == doctest::Approx(refl2[i].x).epsilon(err_tol));
524 DOCTEST_CHECK(refl1[i].y == doctest::Approx(refl2[i].y).epsilon(err_tol));
525 DOCTEST_CHECK(trans1[i].x == doctest::Approx(trans2[i].x).epsilon(err_tol));
526 DOCTEST_CHECK(trans1[i].y == doctest::Approx(trans2[i].y).epsilon(err_tol));
530DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Basic Functionality") {
533 leafoptics.disableMessages();
536 std::vector<uint> UUIDs;
542 props.chlorophyllcontent = 45.0f;
543 props.carotenoidcontent = 12.0f;
544 props.anthocyancontent = 3.0f;
545 props.watermass = 0.018f;
546 props.drymass = 0.085f;
548 std::string label =
"test_get_props";
551 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
552 leafoptics.optionalOutputPrimitiveData(
"carotenoid");
553 leafoptics.optionalOutputPrimitiveData(
"anthocyanin");
554 leafoptics.optionalOutputPrimitiveData(
"water");
555 leafoptics.optionalOutputPrimitiveData(
"drymass");
558 leafoptics.run(UUIDs, props, label);
561 for (
uint UUID: UUIDs) {
573 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(UUIDs));
576 for (
uint UUID: UUIDs) {
577 float chl, car, ant, water, dry;
579 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"chlorophyll", chl));
580 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"carotenoid", car));
581 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"anthocyanin", ant));
585 DOCTEST_CHECK(chl == doctest::Approx(props.chlorophyllcontent).epsilon(err_tol));
586 DOCTEST_CHECK(car == doctest::Approx(props.carotenoidcontent).epsilon(err_tol));
587 DOCTEST_CHECK(ant == doctest::Approx(props.anthocyancontent).epsilon(err_tol));
588 DOCTEST_CHECK(water == doctest::Approx(props.watermass).epsilon(err_tol));
589 DOCTEST_CHECK(dry == doctest::Approx(props.drymass).epsilon(err_tol));
593DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Single UUID Overload") {
596 leafoptics.disableMessages();
601 props.chlorophyllcontent = 55.0f;
602 props.carotenoidcontent = 14.0f;
604 std::string label =
"test_single_uuid";
607 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
608 leafoptics.optionalOutputPrimitiveData(
"carotenoid");
611 leafoptics.run(std::vector<uint>{UUID}, props, label);
618 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(UUID));
622 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"chlorophyll", chl));
623 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"carotenoid", car));
625 DOCTEST_CHECK(chl == doctest::Approx(props.chlorophyllcontent).epsilon(err_tol));
626 DOCTEST_CHECK(car == doctest::Approx(props.carotenoidcontent).epsilon(err_tol));
629DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Non-LeafOptics Spectrum") {
632 leafoptics.disableMessages();
637 context_test.
setPrimitiveData(UUID,
"reflectivity_spectrum",
"custom_spectrum");
640 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(UUID));
646DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Missing Spectrum") {
649 leafoptics.disableMessages();
655 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(UUID));
661DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Unknown Label") {
664 leafoptics.disableMessages();
669 context_test.
setPrimitiveData(UUID,
"reflectivity_spectrum",
"leaf_reflectivity_unknown");
672 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(UUID));
677DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - With Brown Pigments") {
680 leafoptics.disableMessages();
685 props.chlorophyllcontent = 20.0f;
686 props.brownpigments = 0.4f;
688 std::string label =
"test_brown";
691 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
692 leafoptics.optionalOutputPrimitiveData(
"brown");
694 leafoptics.run(std::vector<uint>{UUID}, props, label);
701 leafoptics.getPropertiesFromSpectrum(UUID);
706 DOCTEST_CHECK(brown == doctest::Approx(props.brownpigments).epsilon(err_tol));
709DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - PROSPECT-PRO Mode") {
712 leafoptics.disableMessages();
717 props.chlorophyllcontent = 40.0f;
718 props.drymass = 0.0f;
719 props.protein = 0.002f;
720 props.carbonconstituents = 0.008f;
722 std::string label =
"test_pro_mode";
725 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
726 leafoptics.optionalOutputPrimitiveData(
"protein");
727 leafoptics.optionalOutputPrimitiveData(
"cellulose");
728 leafoptics.optionalOutputPrimitiveData(
"drymass");
730 leafoptics.run(std::vector<uint>{UUID}, props, label);
738 leafoptics.getPropertiesFromSpectrum(UUID);
741 float protein, carbon;
742 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"protein", protein));
743 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUID,
"cellulose", carbon));
745 DOCTEST_CHECK(protein == doctest::Approx(props.protein).epsilon(err_tol));
746 DOCTEST_CHECK(carbon == doctest::Approx(props.carbonconstituents).epsilon(err_tol));
752DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Multiple Spectra") {
755 leafoptics.disableMessages();
762 props1.chlorophyllcontent = 30.0f;
763 props1.carotenoidcontent = 8.0f;
766 props2.chlorophyllcontent = 60.0f;
767 props2.carotenoidcontent = 16.0f;
770 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
771 leafoptics.optionalOutputPrimitiveData(
"carotenoid");
774 leafoptics.run(std::vector<uint>{UUID1}, props1,
"spectrum1");
775 leafoptics.run(std::vector<uint>{UUID2}, props2,
"spectrum2");
782 leafoptics.getPropertiesFromSpectrum(std::vector<uint>{UUID1, UUID2});
789 DOCTEST_CHECK(chl1 == doctest::Approx(props1.chlorophyllcontent).epsilon(err_tol));
790 DOCTEST_CHECK(chl2 == doctest::Approx(props2.chlorophyllcontent).epsilon(err_tol));
793DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromSpectrum - Empty UUID Vector") {
796 leafoptics.disableMessages();
798 std::vector<uint> empty_UUIDs;
801 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromSpectrum(empty_UUIDs));
804DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - Default Species") {
807 leafoptics.disableMessages();
812 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"default", props));
815 DOCTEST_CHECK(props.numberlayers == doctest::Approx(1.5f).epsilon(err_tol));
816 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(30.0f).epsilon(err_tol));
817 DOCTEST_CHECK(props.carotenoidcontent == doctest::Approx(7.0f).epsilon(err_tol));
818 DOCTEST_CHECK(props.anthocyancontent == doctest::Approx(1.0f).epsilon(err_tol));
819 DOCTEST_CHECK(props.brownpigments == doctest::Approx(0.0f).epsilon(err_tol));
820 DOCTEST_CHECK(props.watermass == doctest::Approx(0.015f).epsilon(err_tol));
821 DOCTEST_CHECK(props.drymass == doctest::Approx(0.09f).epsilon(err_tol));
822 DOCTEST_CHECK(props.protein == doctest::Approx(0.0f).epsilon(err_tol));
823 DOCTEST_CHECK(props.carbonconstituents == doctest::Approx(0.0f).epsilon(err_tol));
826DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - Case Insensitivity") {
829 leafoptics.disableMessages();
834 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"default", props_lower));
835 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"Default", props_upper));
838 DOCTEST_CHECK(props_lower.chlorophyllcontent == doctest::Approx(props_upper.chlorophyllcontent).epsilon(err_tol));
839 DOCTEST_CHECK(props_lower.watermass == doctest::Approx(props_upper.watermass).epsilon(err_tol));
840 DOCTEST_CHECK(props_lower.drymass == doctest::Approx(props_upper.drymass).epsilon(err_tol));
843DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - Unknown Species") {
846 leafoptics.disableMessages();
851 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"UnknownSpecies", props));
854 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(30.0f).epsilon(err_tol));
855 DOCTEST_CHECK(props.watermass == doctest::Approx(0.015f).epsilon(err_tol));
856 DOCTEST_CHECK(props.drymass == doctest::Approx(0.09f).epsilon(err_tol));
859DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - Integration with Run") {
862 leafoptics.disableMessages();
865 std::vector<uint> UUIDs;
870 leafoptics.getPropertiesFromLibrary(
"default", props);
873 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
874 leafoptics.optionalOutputPrimitiveData(
"water");
875 leafoptics.optionalOutputPrimitiveData(
"drymass");
878 std::string label =
"test_library_integration";
879 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, props, label));
882 std::string refl_label =
"leaf_reflectivity_" + label;
883 std::string trans_label =
"leaf_transmissivity_" + label;
889 float chl, water, dry;
890 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"chlorophyll", chl));
891 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"water", water));
892 DOCTEST_CHECK_NOTHROW(context_test.
getPrimitiveData(UUIDs[0],
"drymass", dry));
894 DOCTEST_CHECK(chl == doctest::Approx(30.0f).epsilon(err_tol));
895 DOCTEST_CHECK(water == doctest::Approx(0.015f).epsilon(err_tol));
896 DOCTEST_CHECK(dry == doctest::Approx(0.09f).epsilon(err_tol));
899DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - LOPEX93 Species Library") {
902 leafoptics.disableMessages();
905 struct SpeciesTestData {
909 float expected_drymass;
912 std::vector<SpeciesTestData> species_data = {{
"garden_lettuce", 2.00517f, 30.2697f, 0.0052668f}, {
"alfalfa", 2.00758f, 43.6375f, 0.00473702f}, {
"corn", 1.59203f, 22.8664f, 0.00441283f},
913 {
"sunflower", 1.76358f, 54.0514f, 0.00644855f}, {
"english_walnut", 1.56274f, 55.9211f, 0.00583351f}, {
"rice", 1.67081f, 37.233f, 0.00484587f},
914 {
"soybean", 1.5375f, 46.4121f, 0.00292814f}, {
"wine_grape", 1.42673f, 50.918f, 0.00599315f}, {
"tomato", 1.40304f, 48.3467f, 0.00261571f}};
916 for (
const auto &species: species_data) {
918 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(species.name, props));
921 DOCTEST_CHECK(props.numberlayers == doctest::Approx(species.expected_N).epsilon(err_tol));
922 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(species.expected_Cab).epsilon(err_tol));
923 DOCTEST_CHECK(props.drymass == doctest::Approx(species.expected_drymass).epsilon(err_tol));
926 DOCTEST_CHECK(props.drymass > 0.0f);
927 DOCTEST_CHECK(props.protein == doctest::Approx(0.0f).epsilon(err_tol));
928 DOCTEST_CHECK(props.carbonconstituents == doctest::Approx(0.0f).epsilon(err_tol));
932DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - LOPEX93 Species Case Insensitivity") {
935 leafoptics.disableMessages();
940 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"corn", props_lower));
941 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"CORN", props_upper));
942 DOCTEST_CHECK_NOTHROW(leafoptics.getPropertiesFromLibrary(
"Corn", props_mixed));
945 DOCTEST_CHECK(props_lower.chlorophyllcontent == doctest::Approx(props_upper.chlorophyllcontent).epsilon(err_tol));
946 DOCTEST_CHECK(props_lower.chlorophyllcontent == doctest::Approx(props_mixed.chlorophyllcontent).epsilon(err_tol));
947 DOCTEST_CHECK(props_lower.drymass == doctest::Approx(props_upper.drymass).epsilon(err_tol));
950DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - LOPEX93 Complete Parameter Check") {
953 leafoptics.disableMessages();
957 leafoptics.getPropertiesFromLibrary(
"sunflower", props);
959 DOCTEST_CHECK(props.numberlayers == doctest::Approx(1.76358f).epsilon(err_tol));
960 DOCTEST_CHECK(props.chlorophyllcontent == doctest::Approx(54.0514f).epsilon(err_tol));
961 DOCTEST_CHECK(props.carotenoidcontent == doctest::Approx(12.9027f).epsilon(err_tol));
962 DOCTEST_CHECK(props.anthocyancontent == doctest::Approx(1.75194f).epsilon(err_tol));
963 DOCTEST_CHECK(props.brownpigments == doctest::Approx(0.0112026f).epsilon(err_tol));
964 DOCTEST_CHECK(props.watermass == doctest::Approx(0.0185557f).epsilon(err_tol));
965 DOCTEST_CHECK(props.drymass == doctest::Approx(0.00644855f).epsilon(err_tol));
966 DOCTEST_CHECK(props.protein == doctest::Approx(0.0f).epsilon(err_tol));
967 DOCTEST_CHECK(props.carbonconstituents == doctest::Approx(0.0f).epsilon(err_tol));
970DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - LOPEX93 Integration Test") {
973 leafoptics.disableMessages();
976 std::vector<uint> UUIDs;
982 leafoptics.getPropertiesFromLibrary(
"soybean", soybean_props);
985 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
988 std::string label =
"test_soybean";
989 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, soybean_props, label));
996 for (
uint UUID: UUIDs) {
999 DOCTEST_CHECK(chl == doctest::Approx(46.4121f).epsilon(err_tol));
1003DOCTEST_TEST_CASE(
"LeafOptics GetPropertiesFromLibrary - LOPEX93 Species Comparison") {
1006 leafoptics.disableMessages();
1009 leafoptics.getPropertiesFromLibrary(
"garden_lettuce", lettuce_props);
1010 leafoptics.getPropertiesFromLibrary(
"english_walnut", walnut_props);
1013 DOCTEST_CHECK(lettuce_props.chlorophyllcontent != doctest::Approx(walnut_props.chlorophyllcontent).epsilon(err_tol));
1014 DOCTEST_CHECK(lettuce_props.numberlayers != doctest::Approx(walnut_props.numberlayers).epsilon(err_tol));
1017 DOCTEST_CHECK(walnut_props.chlorophyllcontent > lettuce_props.chlorophyllcontent);
1020DOCTEST_TEST_CASE(
"LeafOptics optionalOutputPrimitiveData - No Output By Default") {
1023 leafoptics.disableMessages();
1026 std::vector<uint> UUIDs;
1030 props.chlorophyllcontent = 45.0f;
1031 props.carotenoidcontent = 12.0f;
1032 props.watermass = 0.018f;
1033 props.drymass = 0.085f;
1036 leafoptics.run(UUIDs, props,
"test_no_output");
1050DOCTEST_TEST_CASE(
"LeafOptics optionalOutputPrimitiveData - Selective Output") {
1053 leafoptics.disableMessages();
1055 std::vector<uint> UUIDs;
1059 props.chlorophyllcontent = 45.0f;
1060 props.carotenoidcontent = 12.0f;
1061 props.anthocyancontent = 3.0f;
1062 props.watermass = 0.018f;
1063 props.drymass = 0.085f;
1066 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
1067 leafoptics.optionalOutputPrimitiveData(
"water");
1069 leafoptics.run(UUIDs, props,
"test_selective");
1084 DOCTEST_CHECK(chl == doctest::Approx(props.chlorophyllcontent).epsilon(err_tol));
1085 DOCTEST_CHECK(water == doctest::Approx(props.watermass).epsilon(err_tol));
1088DOCTEST_TEST_CASE(
"LeafOptics optionalOutputPrimitiveData - Invalid Label Warning") {
1096 leafoptics.optionalOutputPrimitiveData(
"invalid_label");
1099 DOCTEST_CHECK(output.find(
"WARNING") != std::string::npos);
1100 DOCTEST_CHECK(output.find(
"invalid_label") != std::string::npos);
1103DOCTEST_TEST_CASE(
"LeafOptics optionalOutputPrimitiveData - All Valid Labels") {
1106 leafoptics.disableMessages();
1109 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"chlorophyll"));
1110 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"carotenoid"));
1111 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"anthocyanin"));
1112 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"brown"));
1113 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"water"));
1114 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"drymass"));
1115 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"protein"));
1116 DOCTEST_CHECK_NOTHROW(leafoptics.optionalOutputPrimitiveData(
"cellulose"));
1121DOCTEST_TEST_CASE(
"LeafOpticsProperties_Nauto Default Constructor") {
1125 DOCTEST_CHECK(params.f_photosynthetic == doctest::Approx(0.50f).epsilon(err_tol));
1126 DOCTEST_CHECK(params.N_to_Cab_coefficient == doctest::Approx(0.40f).epsilon(err_tol));
1127 DOCTEST_CHECK(params.Car_to_Cab_ratio == doctest::Approx(0.25f).epsilon(err_tol));
1130 DOCTEST_CHECK(params.numberlayers == doctest::Approx(1.5f).epsilon(err_tol));
1131 DOCTEST_CHECK(params.anthocyancontent == doctest::Approx(1.0f).epsilon(err_tol));
1132 DOCTEST_CHECK(params.brownpigments == doctest::Approx(0.0f).epsilon(err_tol));
1133 DOCTEST_CHECK(params.watermass == doctest::Approx(0.015f).epsilon(err_tol));
1134 DOCTEST_CHECK(params.drymass == doctest::Approx(0.006f).epsilon(err_tol));
1135 DOCTEST_CHECK(params.protein == doctest::Approx(0.0f).epsilon(err_tol));
1136 DOCTEST_CHECK(params.carbonconstituents == doctest::Approx(0.0f).epsilon(err_tol));
1139 DOCTEST_CHECK(params.num_bins == 20);
1140 DOCTEST_CHECK(params.reassignment_threshold == doctest::Approx(0.30f).epsilon(err_tol));
1141 DOCTEST_CHECK(params.min_reassignment_change == doctest::Approx(0.3f).epsilon(err_tol));
1144DOCTEST_TEST_CASE(
"Nitrogen Mode - First Call Creates Bins") {
1147 leafoptics.disableMessages();
1150 std::vector<uint> all_UUIDs;
1151 std::vector<uint> objIDs;
1153 for (
int i = 0; i < 5; i++) {
1155 objIDs.push_back(objID);
1158 float N_area = 1.0f + float(i) * 0.5f;
1159 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1163 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1167 params.num_bins = 3;
1170 DOCTEST_CHECK_NOTHROW(leafoptics.run(all_UUIDs, params));
1177 std::string refl_label;
1178 context_test.
getPrimitiveData(all_UUIDs[0],
"reflectivity_spectrum", refl_label);
1179 DOCTEST_CHECK(refl_label.find(
"leaf_reflectivity_Nauto_") == 0);
1182DOCTEST_TEST_CASE(
"Nitrogen Mode - Subsequent Call Reuses Bins") {
1185 leafoptics.disableMessages();
1188 std::vector<uint> all_UUIDs;
1189 std::vector<uint> objIDs;
1191 for (
int i = 0; i < 3; i++) {
1193 objIDs.push_back(objID);
1194 float N_area = 1.5f + float(i) * 0.3f;
1195 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1197 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1201 params.num_bins = 2;
1204 leafoptics.run(all_UUIDs, params);
1207 std::string initial_label;
1208 context_test.
getPrimitiveData(all_UUIDs[0],
"reflectivity_spectrum", initial_label);
1211 DOCTEST_CHECK_NOTHROW(leafoptics.run(all_UUIDs, params));
1214 std::string after_label;
1215 context_test.
getPrimitiveData(all_UUIDs[0],
"reflectivity_spectrum", after_label);
1216 DOCTEST_CHECK(initial_label == after_label);
1219DOCTEST_TEST_CASE(
"Nitrogen Mode - New Objects Assigned to Existing Bins") {
1222 leafoptics.disableMessages();
1225 std::vector<uint> all_UUIDs;
1226 for (
int i = 0; i < 3; i++) {
1228 float N_area = 1.5f + float(i) * 0.5f;
1229 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1231 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1235 params.num_bins = 2;
1238 leafoptics.run(all_UUIDs, params);
1242 context_test.
setObjectData(new_objID,
"leaf_nitrogen_gN_m2", 2.0f);
1244 all_UUIDs.insert(all_UUIDs.end(), new_UUIDs.begin(), new_UUIDs.end());
1247 DOCTEST_CHECK_NOTHROW(leafoptics.run(all_UUIDs, params));
1250 std::string refl_label;
1251 context_test.
getPrimitiveData(new_UUIDs[0],
"reflectivity_spectrum", refl_label);
1252 DOCTEST_CHECK(refl_label.find(
"leaf_reflectivity_Nauto_") == 0);
1255DOCTEST_TEST_CASE(
"Nitrogen Mode - Removed Objects Untracked") {
1258 leafoptics.disableMessages();
1261 std::vector<uint> all_UUIDs;
1262 std::vector<uint> objIDs;
1263 for (
int i = 0; i < 3; i++) {
1265 objIDs.push_back(objID);
1266 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 1.5f +
float(i) * 0.3f);
1268 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1272 params.num_bins = 2;
1275 leafoptics.run(all_UUIDs, params);
1278 std::vector<uint> reduced_UUIDs;
1279 for (
int i = 0; i < 2; i++) {
1281 reduced_UUIDs.insert(reduced_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1285 DOCTEST_CHECK_NOTHROW(leafoptics.run(reduced_UUIDs, params));
1288DOCTEST_TEST_CASE(
"Nitrogen Mode - Reassignment When N Changes Significantly") {
1291 leafoptics.disableMessages();
1294 std::vector<uint> all_UUIDs;
1295 std::vector<uint> objIDs;
1296 for (
int i = 0; i < 4; i++) {
1298 objIDs.push_back(objID);
1299 float N_area = 1.0f + float(i) * 1.0f;
1300 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1302 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1306 params.num_bins = 2;
1307 params.reassignment_threshold = 0.3f;
1308 params.min_reassignment_change = 0.3f;
1311 leafoptics.run(all_UUIDs, params);
1315 std::string initial_label;
1316 context_test.
getPrimitiveData(first_obj_UUIDs[0],
"reflectivity_spectrum", initial_label);
1319 context_test.
setObjectData(objIDs[0],
"leaf_nitrogen_gN_m2", 3.5f);
1322 leafoptics.run(all_UUIDs, params);
1324 std::string after_label;
1325 context_test.
getPrimitiveData(first_obj_UUIDs[0],
"reflectivity_spectrum", after_label);
1328 DOCTEST_CHECK(!after_label.empty());
1331DOCTEST_TEST_CASE(
"Nitrogen Mode - Missing Nitrogen Data Error") {
1334 leafoptics.disableMessages();
1343 DOCTEST_CHECK_THROWS(leafoptics.run(UUIDs, params));
1346DOCTEST_TEST_CASE(
"Nitrogen Mode - Primitives Without Parent Skipped") {
1349 leafoptics.disableMessages();
1356 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 2.0f);
1360 std::vector<uint> mixed_UUIDs = {standalone_UUID};
1361 mixed_UUIDs.insert(mixed_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1364 params.num_bins = 1;
1367 DOCTEST_CHECK_NOTHROW(leafoptics.run(mixed_UUIDs, params));
1370 std::string refl_label;
1371 context_test.
getPrimitiveData(obj_UUIDs[0],
"reflectivity_spectrum", refl_label);
1372 DOCTEST_CHECK(!refl_label.empty());
1375DOCTEST_TEST_CASE(
"Nitrogen Mode - updateNitrogenBasedSpectra Without Init Error") {
1378 leafoptics.disableMessages();
1381 DOCTEST_CHECK_THROWS(leafoptics.updateNitrogenBasedSpectra());
1384DOCTEST_TEST_CASE(
"Nitrogen Mode - updateNitrogenBasedSpectra After Init") {
1387 leafoptics.disableMessages();
1390 std::vector<uint> all_UUIDs;
1391 std::vector<uint> objIDs;
1392 for (
int i = 0; i < 3; i++) {
1394 objIDs.push_back(objID);
1395 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 1.5f +
float(i) * 0.5f);
1397 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1401 params.num_bins = 2;
1404 leafoptics.run(all_UUIDs, params);
1407 DOCTEST_CHECK_NOTHROW(leafoptics.updateNitrogenBasedSpectra());
1410DOCTEST_TEST_CASE(
"Nitrogen Mode - Spectrum Count Bounded by num_bins") {
1413 leafoptics.disableMessages();
1416 std::vector<uint> all_UUIDs;
1417 for (
int i = 0; i < 50; i++) {
1419 float N_area = 0.5f + float(i) * 0.1f;
1420 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", N_area);
1422 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1426 params.num_bins = 5;
1428 leafoptics.run(all_UUIDs, params);
1431 uint spectrum_count = 0;
1432 for (
uint i = 0; i < 100; i++) {
1433 std::string label =
"leaf_reflectivity_Nauto_" + std::to_string(i);
1439 DOCTEST_CHECK(spectrum_count <= params.num_bins);
1440 DOCTEST_CHECK(spectrum_count > 0);
1443DOCTEST_TEST_CASE(
"Nitrogen Mode - Empty UUID List") {
1446 leafoptics.disableMessages();
1448 std::vector<uint> empty_UUIDs;
1452 DOCTEST_CHECK_NOTHROW(leafoptics.run(empty_UUIDs, params));
1455DOCTEST_TEST_CASE(
"Nitrogen Mode - Single Object") {
1458 leafoptics.disableMessages();
1461 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 2.0f);
1465 params.num_bins = 5;
1467 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, params));
1473 std::string refl_label;
1474 context_test.
getPrimitiveData(UUIDs[0],
"reflectivity_spectrum", refl_label);
1475 DOCTEST_CHECK(refl_label ==
"leaf_reflectivity_Nauto_0");
1478DOCTEST_TEST_CASE(
"Nitrogen Mode - Chlorophyll Clamping Low") {
1481 leafoptics.disableMessages();
1485 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 0.1f);
1489 params.num_bins = 1;
1492 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, params));
1498DOCTEST_TEST_CASE(
"Nitrogen Mode - Chlorophyll Clamping High") {
1501 leafoptics.disableMessages();
1505 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 10.0f);
1509 params.num_bins = 1;
1512 DOCTEST_CHECK_NOTHROW(leafoptics.run(UUIDs, params));
1518DOCTEST_TEST_CASE(
"Nitrogen Mode - Chlorophyll Calculation Verification") {
1526 leafoptics.disableMessages();
1529 leafoptics.optionalOutputPrimitiveData(
"chlorophyll");
1530 leafoptics.optionalOutputPrimitiveData(
"carotenoid");
1533 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 2.0f);
1537 params.f_photosynthetic = 0.5f;
1538 params.N_to_Cab_coefficient = 0.4f;
1539 params.Car_to_Cab_ratio = 0.25f;
1540 params.num_bins = 1;
1542 leafoptics.run(UUIDs, params);
1545 leafoptics.getPropertiesFromSpectrum(UUIDs);
1550 DOCTEST_CHECK(chl == doctest::Approx(40.0f).epsilon(0.01f));
1555 DOCTEST_CHECK(car == doctest::Approx(10.0f).epsilon(0.01f));
1558DOCTEST_TEST_CASE(
"Nitrogen Mode - Hysteresis Prevents Oscillation") {
1564 leafoptics.disableMessages();
1567 std::vector<uint> all_UUIDs;
1568 std::vector<uint> objIDs;
1572 context_test.
setObjectData(obj0,
"leaf_nitrogen_gN_m2", 1.0f);
1573 objIDs.push_back(obj0);
1575 all_UUIDs.insert(all_UUIDs.end(), UUIDs0.begin(), UUIDs0.end());
1579 context_test.
setObjectData(obj1,
"leaf_nitrogen_gN_m2", 3.0f);
1580 objIDs.push_back(obj1);
1582 all_UUIDs.insert(all_UUIDs.end(), UUIDs1.begin(), UUIDs1.end());
1585 params.num_bins = 2;
1586 params.reassignment_threshold = 0.3f;
1587 params.min_reassignment_change = 0.3f;
1590 leafoptics.run(all_UUIDs, params);
1593 std::string initial_label;
1594 context_test.
getPrimitiveData(UUIDs0[0],
"reflectivity_spectrum", initial_label);
1599 context_test.
setObjectData(obj0,
"leaf_nitrogen_gN_m2", 1.5f);
1600 leafoptics.run(all_UUIDs, params);
1602 std::string after_small_change;
1603 context_test.
getPrimitiveData(UUIDs0[0],
"reflectivity_spectrum", after_small_change);
1610 leafoptics.run(all_UUIDs, params);
1611 std::string after_repeat;
1612 context_test.
getPrimitiveData(UUIDs0[0],
"reflectivity_spectrum", after_repeat);
1615 DOCTEST_CHECK(after_small_change == after_repeat);
1618DOCTEST_TEST_CASE(
"Nitrogen Mode - All Same Nitrogen Values") {
1624 leafoptics.disableMessages();
1626 std::vector<uint> all_UUIDs;
1627 const float same_nitrogen = 2.0f;
1630 for (
int i = 0; i < 10; i++) {
1632 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", same_nitrogen);
1634 all_UUIDs.insert(all_UUIDs.end(), obj_UUIDs.begin(), obj_UUIDs.end());
1638 params.num_bins = 5;
1640 leafoptics.run(all_UUIDs, params);
1643 uint spectrum_count = 0;
1644 for (
uint i = 0; i < 10; i++) {
1645 std::string label =
"leaf_reflectivity_Nauto_" + std::to_string(i);
1652 DOCTEST_CHECK(spectrum_count == 1);
1655 std::string first_label;
1656 context_test.
getPrimitiveData(all_UUIDs[0],
"reflectivity_spectrum", first_label);
1658 for (
uint UUID: all_UUIDs) {
1661 DOCTEST_CHECK(label == first_label);
1665DOCTEST_TEST_CASE(
"Nitrogen Mode - Verify Spectrum Physical Properties") {
1670 leafoptics.disableMessages();
1673 context_test.
setObjectData(objID,
"leaf_nitrogen_gN_m2", 2.0f);
1677 params.num_bins = 1;
1679 leafoptics.run(UUIDs, params);
1682 std::vector<vec2> refl_data, trans_data;
1683 context_test.
getGlobalData(
"leaf_reflectivity_Nauto_0", refl_data);
1684 context_test.
getGlobalData(
"leaf_transmissivity_Nauto_0", trans_data);
1687 DOCTEST_CHECK(refl_data.size() == 2101);
1688 DOCTEST_CHECK(trans_data.size() == 2101);
1691 for (
size_t i = 0; i < refl_data.size(); i++) {
1692 DOCTEST_CHECK(refl_data[i].y >= 0.0f);
1693 DOCTEST_CHECK(refl_data[i].y <= 1.0f);
1694 DOCTEST_CHECK(trans_data[i].y >= 0.0f);
1695 DOCTEST_CHECK(trans_data[i].y <= 1.0f);
1698 DOCTEST_CHECK(refl_data[i].y + trans_data[i].y <= 1.01f);
1702 DOCTEST_CHECK(refl_data.front().x == doctest::Approx(400.0f).epsilon(0.01f));
1703 DOCTEST_CHECK(refl_data.back().x == doctest::Approx(2500.0f).epsilon(0.01f));
1706DOCTEST_TEST_CASE(
"LeafOptics Zero Absorption - PROSPECT-PRO with no water") {
1719 leafoptics.disableMessages();
1722 props.chlorophyllcontent = 0.0f;
1723 props.carotenoidcontent = 0.0f;
1724 props.anthocyancontent = 0.0f;
1725 props.brownpigments = 0.0f;
1726 props.watermass = 0.0f;
1727 props.drymass = 0.0f;
1728 props.protein = 0.001f;
1729 props.carbonconstituents = 0.0f;
1731 std::vector<vec2> reflectivities, transmissivities;
1732 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
1734 DOCTEST_CHECK(reflectivities.size() == 2101);
1735 DOCTEST_CHECK(transmissivities.size() == 2101);
1738 for (
size_t i = 0; i < reflectivities.size(); i++) {
1739 DOCTEST_CHECK(std::isfinite(reflectivities.at(i).y));
1740 DOCTEST_CHECK(std::isfinite(transmissivities.at(i).y));
1741 DOCTEST_CHECK(reflectivities.at(i).y >= 0.0f);
1742 DOCTEST_CHECK(reflectivities.at(i).y <= 1.0f);
1743 DOCTEST_CHECK(transmissivities.at(i).y >= 0.0f);
1744 DOCTEST_CHECK(transmissivities.at(i).y <= 1.0f);
1745 DOCTEST_CHECK(reflectivities.at(i).y + transmissivities.at(i).y <= 1.01f);
1750 DOCTEST_CHECK(transmissivities.at(1000).y > 0.4f);
1751 DOCTEST_CHECK(reflectivities.at(1000).y + transmissivities.at(1000).y > 0.99f);
1754DOCTEST_TEST_CASE(
"LeafOptics Spectra Are Finite For All Library Species") {
1760 leafoptics.disableMessages();
1762 std::vector<std::string> species = {
"default",
"garden_lettuce",
"alfalfa",
"corn",
"sunflower",
"english_walnut",
"rice",
"soybean",
"wine_grape",
"tomato",
"common_bean",
"cowpea"};
1764 for (
const auto &name: species) {
1766 leafoptics.getPropertiesFromLibrary(name, props);
1768 std::vector<vec2> reflectivities, transmissivities;
1769 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
1771 DOCTEST_CHECK(reflectivities.size() == 2101);
1772 DOCTEST_CHECK(transmissivities.size() == 2101);
1774 for (
size_t i = 0; i < reflectivities.size(); i++) {
1775 DOCTEST_CHECK(std::isfinite(reflectivities.at(i).y));
1776 DOCTEST_CHECK(std::isfinite(transmissivities.at(i).y));
1777 DOCTEST_CHECK(reflectivities.at(i).y >= 0.0f);
1778 DOCTEST_CHECK(reflectivities.at(i).y <= 1.0f);
1779 DOCTEST_CHECK(transmissivities.at(i).y >= 0.0f);
1780 DOCTEST_CHECK(transmissivities.at(i).y <= 1.0f);
1785DOCTEST_TEST_CASE(
"LeafOptics Transmittance Continuity Across Branch Boundaries") {
1794 leafoptics.disableMessages();
1797 float previous_mean = -1.0f;
1798 for (
int step = 0; step <= 40; step++) {
1800 props.chlorophyllcontent = 5.0f +
static_cast<float>(step) * 2.5f;
1802 std::vector<vec2> reflectivities, transmissivities;
1803 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
1805 float mean_transmittance = 0.0f;
1806 for (
const auto &value: transmissivities) {
1807 DOCTEST_CHECK(std::isfinite(value.y));
1808 mean_transmittance += value.y;
1810 mean_transmittance /=
static_cast<float>(transmissivities.size());
1814 if (previous_mean >= 0.0f) {
1815 DOCTEST_CHECK(mean_transmittance <= previous_mean + 1e-4f);
1816 DOCTEST_CHECK(std::abs(mean_transmittance - previous_mean) < 0.05f);
1818 previous_mean = mean_transmittance;
1822DOCTEST_TEST_CASE(
"LeafOptics Output Vectors Are Overwritten Not Appended") {
1828 leafoptics.disableMessages();
1831 props.chlorophyllcontent = 40.0f;
1833 std::vector<vec2> reflectivities, transmissivities;
1834 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
1836 DOCTEST_CHECK(reflectivities.size() == 2101);
1837 DOCTEST_CHECK(transmissivities.size() == 2101);
1840 leafoptics.getLeafSpectra(props, reflectivities, transmissivities);
1842 DOCTEST_CHECK(reflectivities.size() == 2101);
1843 DOCTEST_CHECK(transmissivities.size() == 2101);
1846 DOCTEST_CHECK(reflectivities.front().x == doctest::Approx(400.0f).epsilon(0.01f));
1847 DOCTEST_CHECK(reflectivities.back().x == doctest::Approx(2500.0f).epsilon(0.01f));
1848 DOCTEST_CHECK(transmissivities.front().x == doctest::Approx(400.0f).epsilon(0.01f));
1849 DOCTEST_CHECK(transmissivities.back().x == doctest::Approx(2500.0f).epsilon(0.01f));
1852DOCTEST_TEST_CASE(
"LeafOptics Invalid Input Parameters Throw") {
1859 leafoptics.disableMessages();
1861 std::vector<vec2> reflectivities, transmissivities;
1867 zero_layers.numberlayers = 0.0f;
1868 DOCTEST_CHECK_THROWS(leafoptics.getLeafSpectra(zero_layers, reflectivities, transmissivities));
1871 sub_unit_layers.numberlayers = 0.5f;
1872 DOCTEST_CHECK_THROWS(leafoptics.getLeafSpectra(sub_unit_layers, reflectivities, transmissivities));
1875 negative_chlorophyll.chlorophyllcontent = -10.0f;
1876 DOCTEST_CHECK_THROWS(leafoptics.getLeafSpectra(negative_chlorophyll, reflectivities, transmissivities));
1879 negative_water.watermass = -0.01f;
1880 DOCTEST_CHECK_THROWS(leafoptics.getLeafSpectra(negative_water, reflectivities, transmissivities));
1885 single_layer.numberlayers = 1.0f;
1886 DOCTEST_CHECK_NOTHROW(leafoptics.getLeafSpectra(single_layer, reflectivities, transmissivities));
1887 for (
size_t i = 0; i < reflectivities.size(); i++) {
1888 DOCTEST_CHECK(std::isfinite(reflectivities.at(i).y));
1889 DOCTEST_CHECK(std::isfinite(transmissivities.at(i).y));
1893DOCTEST_TEST_CASE(
"LeafOptics PROSPECT Direct Entry Point Validates Inputs") {
1901 leafoptics.disableMessages();
1903 std::vector<float> reflectivities, transmissivities;
1909 DOCTEST_CHECK_THROWS(leafoptics.PROSPECT(0.0f, 30.f, 10.f, 0.f, 0.f, 0.01f, 0.005f, 0.f, 0.f, reflectivities, transmissivities));
1912 DOCTEST_CHECK_THROWS(leafoptics.PROSPECT(0.5f, 30.f, 10.f, 0.f, 0.f, 0.01f, 0.005f, 0.f, 0.f, reflectivities, transmissivities));
1915 DOCTEST_CHECK_THROWS(leafoptics.PROSPECT(1.5f, -10.f, 10.f, 0.f, 0.f, 0.01f, 0.005f, 0.f, 0.f, reflectivities, transmissivities));
1917 DOCTEST_CHECK_THROWS(leafoptics.PROSPECT(1.5f, 30.f, 10.f, 0.f, 0.f, -0.01f, 0.005f, 0.f, 0.f, reflectivities, transmissivities));
1921 DOCTEST_CHECK_NOTHROW(leafoptics.PROSPECT(1.5f, 30.f, 10.f, 0.f, 0.f, 0.01f, 0.005f, 0.f, 0.f, reflectivities, transmissivities));
1922 DOCTEST_CHECK(!reflectivities.empty());
1923 for (
size_t i = 0; i < reflectivities.size(); i++) {
1924 DOCTEST_CHECK(std::isfinite(reflectivities.at(i)));
1925 DOCTEST_CHECK(std::isfinite(transmissivities.at(i)));
1930 return helios::runDoctestWithValidation(argc, argv);