10#ifdef HELIOS_HAVE_VULKAN
14#define DOCTEST_CONFIG_IMPLEMENT
16#include "doctest_utils.h"
18using namespace helios;
36#ifdef HELIOS_HAVE_VULKAN
48#if defined(HELIOS_HAVE_OPTIX8) || (defined(HELIOS_HAVE_OPTIX) && !defined(FORCE_VULKAN_BACKEND))
51#elif defined(HELIOS_HAVE_VULKAN)
53 VulkanDevice *device = TestVulkanDeviceManager::getSharedDevice();
57 auto backend = std::make_unique<VulkanComputeBackend>(device);
58 backend->initialize();
71 return helios::runDoctestWithValidation(argc, argv);
74DOCTEST_TEST_CASE(
"Backend Identification") {
75 std::string compiled_backends;
76#ifdef HELIOS_HAVE_OPTIX8
77 compiled_backends +=
"OptiX8 ";
79#ifdef HELIOS_HAVE_OPTIX
80 compiled_backends +=
"OptiX6 ";
82#ifdef HELIOS_HAVE_VULKAN
83 compiled_backends +=
"Vulkan ";
85 if (compiled_backends.empty()) compiled_backends =
"(none)";
86 DOCTEST_MESSAGE(
"Compiled backends: " << compiled_backends);
89 DOCTEST_MESSAGE(
"GPU available: " << std::string(gpu_available ?
"yes" :
"no"));
93 RadiationModel model = RadiationModelTestHelper::createWithSharedDevice(&context);
98DOCTEST_TEST_CASE(
"BufferIndexing Correctness") {
103 DOCTEST_CHECK(indexer(0, 0) == 0);
104 DOCTEST_CHECK(indexer(0, 1) == 1);
105 DOCTEST_CHECK(indexer(0, 4) == 4);
106 DOCTEST_CHECK(indexer(1, 0) == 5);
107 DOCTEST_CHECK(indexer(1, 1) == 6);
108 DOCTEST_CHECK(indexer(9, 4) == 49);
111 for (
size_t i = 0; i < 10; i++) {
112 for (
size_t j = 0; j < 5; j++) {
113 size_t manual = i * 5 + j;
114 size_t indexed = indexer(i, j);
115 DOCTEST_CHECK(manual == indexed);
124 DOCTEST_CHECK(indexer(0, 0, 0) == 0);
125 DOCTEST_CHECK(indexer(0, 0, 1) == 1);
126 DOCTEST_CHECK(indexer(0, 0, 3) == 3);
127 DOCTEST_CHECK(indexer(0, 1, 0) == 4);
128 DOCTEST_CHECK(indexer(0, 2, 0) == 8);
129 DOCTEST_CHECK(indexer(1, 0, 0) == 12);
130 DOCTEST_CHECK(indexer(1, 2, 3) == 23);
133 for (
size_t i = 0; i < 2; i++) {
134 for (
size_t j = 0; j < 3; j++) {
135 for (
size_t k = 0; k < 4; k++) {
136 size_t manual = i * 3 * 4 + j * 4 + k;
137 size_t indexed = indexer(i, j, k);
138 DOCTEST_CHECK(manual == indexed);
148 DOCTEST_CHECK(indexer(0, 0, 0, 0) == 0);
149 DOCTEST_CHECK(indexer(0, 0, 0, 1) == 1);
150 DOCTEST_CHECK(indexer(0, 0, 1, 0) == 2);
151 DOCTEST_CHECK(indexer(0, 1, 0, 0) == 4);
152 DOCTEST_CHECK(indexer(1, 0, 0, 0) == 8);
153 DOCTEST_CHECK(indexer(1, 1, 1, 1) == 15);
156 for (
size_t i = 0; i < 2; i++) {
157 for (
size_t j = 0; j < 2; j++) {
158 for (
size_t k = 0; k < 2; k++) {
159 for (
size_t l = 0; l < 2; l++) {
160 size_t manual = i * 2 * 2 * 2 + j * 2 * 2 + k * 2 + l;
161 size_t indexed = indexer(i, j, k, l);
162 DOCTEST_CHECK(manual == indexed);
171 const size_t Nsources = 5;
172 const size_t Nprimitives = 100;
173 const size_t Nbands = 20;
174 const size_t Ncameras = 3;
179 DOCTEST_CHECK(mat_indexer(0, 0, 0) == 0);
180 DOCTEST_CHECK(mat_indexer(0, 0, 1) == 1);
181 DOCTEST_CHECK(mat_indexer(0, 1, 0) == 20);
182 DOCTEST_CHECK(mat_indexer(1, 0, 0) == 2000);
185 for (
size_t s = 0; s < Nsources; s++) {
186 for (
size_t p = 0; p < Nprimitives; p++) {
187 for (
size_t b = 0; b < Nbands; b++) {
188 size_t manual = s * Nprimitives * Nbands + p * Nbands + b;
189 size_t indexed = mat_indexer(s, p, b);
190 DOCTEST_CHECK(manual == indexed);
198 for (
size_t s = 0; s < 2; s++) {
199 for (
size_t p = 0; p < 10; p++) {
200 for (
size_t b = 0; b < Nbands; b++) {
201 for (
size_t c = 0; c < Ncameras; c++) {
202 size_t manual = s * Nprimitives * Nbands * Ncameras + p * Nbands * Ncameras + b * Ncameras + c;
203 size_t indexed = cam_mat_indexer(s, p, b, c);
204 DOCTEST_CHECK(manual == indexed);
212GPU_TEST_CASE(
"RadiationModel Simple Direct") {
219 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
237 float error = fabsf(flux - 1000.0f) / 1000.0f;
238 DOCTEST_CHECK(error <= 0.01);
241GPU_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Squares") {
242 float error_threshold = 0.005;
245 uint Ndiffuse_1 = 100000;
246 uint Ndirect_1 = 5000;
249 float sigma = 5.6703744E-8;
251 float shortwave_exact_0 = 0.7f * Qs;
252 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
253 float longwave_exact_0 = 0.f;
254 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
267 float shortwave_rho = 0.3f;
270 RadiationModel radiationmodel_1 = RadiationModelTestHelper::createWithSharedDevice(&context_1);
290 float longwave_model_0 = 0.f;
291 float longwave_model_1 = 0.f;
292 float shortwave_model_0 = 0.f;
293 float shortwave_model_1 = 0.f;
296 for (
int r = 0; r < Nensemble; r++) {
297 std::vector<std::string> bands{
"LW",
"SW"};
298 radiationmodel_1.
runBand(bands);
302 longwave_model_0 +=
R / float(Nensemble);
305 longwave_model_1 +=
R / float(Nensemble);
309 shortwave_model_0 +=
R / float(Nensemble);
312 shortwave_model_1 +=
R / float(Nensemble);
315 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
316 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
317 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
319 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
320 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
322 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
323 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
326GPU_TEST_CASE(
"RadiationModel Black Parallel Rectangles") {
327 float error_threshold = 0.005;
330 uint Ndiffuse_2 = 50000;
342 2.0f / float(
M_PI * X * Y) * (logf(std::sqrt((1.f + X2) * (1.f + Y2) / (1.f + X2 + Y2))) + X * std::sqrt(1.f + Y2) * atanf(X / std::sqrt(1.f + Y2)) + Y * std::sqrt(1.f + X2) * atanf(Y / std::sqrt(1.f + X2)) - X * atanf(X) - Y * atanf(Y));
344 float shortwave_exact_0 = (1.f - F12);
345 float shortwave_exact_1 = (1.f - F12);
355 RadiationModel radiationmodel_2 = RadiationModelTestHelper::createWithSharedDevice(&context_2);
367 float shortwave_model_0 = 0.f;
368 float shortwave_model_1 = 0.f;
371 for (
int r = 0; r < Nensemble; r++) {
372 radiationmodel_2.
runBand(
"SW");
375 shortwave_model_0 +=
R / float(Nensemble);
377 shortwave_model_1 +=
R / float(Nensemble);
380 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
381 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
383 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
384 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
387GPU_TEST_CASE(
"RadiationModel Gray Parallel Rectangles") {
388 float error_threshold = 0.005;
390 float sigma = 5.6703744E-8;
392 uint Ndiffuse_3 = 100000;
395 float longwave_rho = 0.4;
411 2.0f / float(
M_PI * X * Y) * (logf(std::sqrt((1.f + X2) * (1.f + Y2) / (1.f + X2 + Y2))) + X * std::sqrt(1.f + Y2) * atanf(X / std::sqrt(1.f + Y2)) + Y * std::sqrt(1.f + X2) * atanf(Y / std::sqrt(1.f + X2)) - X * atanf(X) - Y * atanf(Y));
413 float longwave_exact_0 = (eps * (1.f / eps - 1.f) * F12 * sigma * (powf(T1, 4) - F12 * powf(T0, 4)) + sigma * (powf(T0, 4) - F12 * powf(T1, 4))) / (1.f / eps - (1.f / eps - 1.f) * F12 * eps * (1 / eps - 1) * F12) - eps * sigma * powf(T0, 4);
414 float longwave_exact_1 = fabsf(eps * ((1 / eps - 1) * F12 * (longwave_exact_0 + eps * sigma * powf(T0, 4)) + sigma * (powf(T1, 4) - F12 * powf(T0, 4))) - eps * sigma * powf(T1, 4));
415 longwave_exact_0 = fabsf(longwave_exact_0);
433 RadiationModel radiationmodel_3 = RadiationModelTestHelper::createWithSharedDevice(&context_3);
445 float longwave_model_0 = 0.f;
446 float longwave_model_1 = 0.f;
449 for (
int r = 0; r < Nensemble; r++) {
450 radiationmodel_3.
runBand(
"LW");
453 longwave_model_0 +=
R / float(Nensemble);
455 longwave_model_1 +=
R / float(Nensemble);
458 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
459 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
461 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
462 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
465GPU_TEST_CASE(
"RadiationModel Sphere Source") {
466 float error_threshold = 0.005;
469 uint Ndirect_4 = 10000;
479 float F12 = 0.25f / float(
M_PI) * atanf(sqrtf(1.f / (D1 * D1 + D2 * D2 + D1 * D1 * D2 * D2)));
481 float shortwave_exact_0 = 4.0f * float(
M_PI) * r * r * F12 / (l1 * l2);
486 RadiationModel radiationmodel_4 = RadiationModelTestHelper::createWithSharedDevice(&context_4);
500 float shortwave_model_0 = 0.f;
503 for (
int i = 0; i < Nensemble; i++) {
504 radiationmodel_4.
runBand(
"SW");
507 shortwave_model_0 +=
R / float(Nensemble);
510 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
512 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
515GPU_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Sub-Triangles") {
516 float error_threshold = 0.005;
518 float sigma = 5.6703744E-8;
522 uint Ndiffuse_5 = 100000;
523 uint Ndirect_5 = 5000;
525 float shortwave_exact_0 = 0.7f * Qs;
526 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
527 float longwave_exact_0 = 0.f;
528 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
543 float shortwave_rho = 0.3f;
553 RadiationModel radiationmodel_5 = RadiationModelTestHelper::createWithSharedDevice(&context_5);
573 float longwave_model_0 = 0.f;
574 float longwave_model_1 = 0.f;
575 float shortwave_model_0 = 0.f;
576 float shortwave_model_1 = 0.f;
579 for (
int i = 0; i < Nensemble; i++) {
580 std::vector<std::string> bands{
"SW",
"LW"};
581 radiationmodel_5.
runBand(bands);
585 longwave_model_0 += 0.5f *
R / float(Nensemble);
587 longwave_model_0 += 0.5f *
R / float(Nensemble);
590 longwave_model_1 += 0.5f *
R / float(Nensemble);
592 longwave_model_1 += 0.5f *
R / float(Nensemble);
596 shortwave_model_0 += 0.5f *
R / float(Nensemble);
598 shortwave_model_0 += 0.5f *
R / float(Nensemble);
601 shortwave_model_1 += 0.5f *
R / float(Nensemble);
603 shortwave_model_1 += 0.5f *
R / float(Nensemble);
606 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
607 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
608 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
610 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
611 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
613 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
614 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
617GPU_TEST_CASE(
"RadiationModel Parallel Disks Texture Masked Patches") {
618 float error_threshold = 0.005;
620 float sigma = 5.6703744E-8;
622 uint Ndirect_6 = 1000;
623 uint Ndiffuse_6 = 500000;
625 float shortwave_rho = 0.3;
631 float A1 =
M_PI * r1 * r1;
632 float A2 =
M_PI * r2 * r2;
637 float X = 1.f + (1.f + R2 * R2) / (R1 * R1);
638 float F12 = 0.5f * (X - std::sqrt(X * X - 4.f * powf(R2 / R1, 2)));
640 float shortwave_exact_0 = (A1 - A2) / A1 * (1.f - shortwave_rho);
641 float shortwave_exact_1 = (A1 - A2) / A1 * F12 * A1 / A2 * shortwave_rho;
642 float longwave_exact_0 = sigma * powf(300.f, 4) * F12;
643 float longwave_exact_1 = sigma * powf(300.f, 4) * F12 * A1 / A2;
661 RadiationModel radiationmodel_6 = RadiationModelTestHelper::createWithSharedDevice(&context_6);
683 float shortwave_model_0 = 0;
684 float shortwave_model_1 = 0;
685 float longwave_model_0 = 0;
686 float longwave_model_1 = 0;
689 for (
uint i = 0; i < Nensemble; i++) {
690 radiationmodel_6.
runBand(
"SW");
691 radiationmodel_6.
runBand(
"LW");
694 shortwave_model_0 +=
R / float(Nensemble);
697 shortwave_model_1 +=
R / float(Nensemble);
700 longwave_model_0 +=
R / float(Nensemble);
703 longwave_model_1 +=
R / float(Nensemble);
706 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
707 float shortwave_error_1 = fabsf(shortwave_exact_1 - shortwave_model_1) / fabsf(shortwave_exact_1);
708 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
709 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
711 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
712 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
713 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
714 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
717GPU_TEST_CASE(
"RadiationModel Second Law Equilibrium Test") {
718 float error_threshold = 0.005;
719 float sigma = 5.6703744E-8;
721 uint Ndiffuse_7 = 50000;
730 uint objID_7 = context_7.
addBoxObject(
make_vec3(0, 0, 0),
make_vec3(10, 10, 10),
make_int3(5, 5, 5), RGB::black,
true);
740 RadiationModel radiationmodel_7 = RadiationModelTestHelper::createWithSharedDevice(&context_7);
751 radiationmodel_7.
runBand(
"LW");
754 float flux_err = 0.f;
755 for (
int p = 0; p < UUIDt.size(); p++) {
758 flux_err += fabsf(
R - eps1_7 * sigma * powf(300, 4)) / (eps1_7 * sigma * powf(300, 4)) /
float(UUIDt.size());
761 DOCTEST_CHECK(flux_err <= error_threshold);
764 for (
uint p: UUIDt) {
766 if (context_7.
randu() < 0.5f) {
776 radiationmodel_7.
runBand(
"LW");
779 for (
int p = 0; p < UUIDt.size(); p++) {
784 flux_err += fabsf(
R - emissivity * sigma * powf(300, 4)) / (emissivity * sigma * powf(300, 4)) /
float(UUIDt.size());
787 DOCTEST_CHECK(flux_err <= error_threshold);
790GPU_TEST_CASE(
"RadiationModel Texture Mapping") {
791 float error_threshold = 0.005;
795 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context_8);
825 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
826 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
842 for (
uint p: UUIDs1) {
853 bool test_8b_pass =
true;
854 for (
uint p = 0; p < UUIDs1.size(); p++) {
857 if (fabs(
R - 1.f) > error_threshold) {
858 test_8b_pass =
false;
862 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
863 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
864 DOCTEST_CHECK(test_8b_pass);
878 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
879 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
893 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
894 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
899 UUID1 = context_8.
addTriangle(p1 +
make_vec3(-0.5f * sz.x, -0.5f * sz.y, 0), p1 +
make_vec3(0.5f * sz.x, 0.5f * sz.y, 0.f), p1 +
make_vec3(-0.5f * sz.x, 0.5f * sz.y, 0.f),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 1),
909 DOCTEST_CHECK(fabs(F0 - 0.5 - 0.5 * (1.f - 0.25f *
M_PI)) <= error_threshold);
910 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
917 uint UUID2 = context_8.
addTriangle(p1 +
make_vec3(-0.5f * sz.x, -0.5f * sz.y, 0), p1 +
make_vec3(0.5f * sz.x, -0.5f * sz.y, 0), p1 +
make_vec3(0.5f * sz.x, 0.5f * sz.y, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
929 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
930 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
931 DOCTEST_CHECK(fabsf(F2 - 1.f) <= error_threshold);
940 UUID1 = context_8.
addTriangle(p0 +
make_vec3(-0.5f * sz.x, -0.5f * sz.y, 0), p0 +
make_vec3(0.5f * sz.x, 0.5f * sz.y, 0), p0 +
make_vec3(-0.5f * sz.x, 0.5f * sz.y, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 1),
942 UUID2 = context_8.
addTriangle(p0 +
make_vec3(-0.5f * sz.x, -0.5f * sz.y, 0), p0 +
make_vec3(0.5f * sz.x, -0.5f * sz.y, 0), p0 +
make_vec3(0.5f * sz.x, 0.5f * sz.y, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
953 DOCTEST_CHECK(fabsf(F1) <= error_threshold);
954 DOCTEST_CHECK(fabsf(F2) <= error_threshold);
955 DOCTEST_CHECK(fabsf(F0 - 1.f) <= error_threshold);
958GPU_TEST_CASE(
"RadiationModel Homogeneous Canopy of Patches") {
959 float error_threshold = 0.005;
960 float sigma = 5.6703744E-8;
962 uint Ndirect_9 = 1000;
963 uint Ndiffuse_9 = 5000;
969 float w_leaf_9 = 0.075;
971 int Nleaves = (int) lroundf(LAI_9 * D_9 * D_9 / w_leaf_9 / w_leaf_9);
975 std::vector<uint> UUIDs_leaf, UUIDs_inc;
977 for (
int i = 0; i < Nleaves; i++) {
978 vec3 position((-0.5f + context_9.
randu()) * D_9, (-0.5f + context_9.
randu()) * D_9, 0.5f * w_leaf_9 + context_9.
randu() * h_9);
982 if (fabsf(position.x) <= 0.5 * D_inc_9 && fabsf(position.y) <= 0.5 * D_inc_9) {
983 UUIDs_inc.push_back(UUID);
990 RadiationModel radiation_9 = RadiationModelTestHelper::createWithSharedDevice(&context_9);
996 float theta_s = 0.2 *
M_PI;
998 radiation_9.
setSourceFlux(ID,
"direct", 1.f / cosf(theta_s));
1007 radiation_9.
runBand(
"direct");
1008 radiation_9.
runBand(
"diffuse");
1010 float intercepted_leaf_direct = 0.f;
1011 float intercepted_leaf_diffuse = 0.f;
1012 for (
uint i: UUIDs_inc) {
1016 intercepted_leaf_direct += flux * area / D_inc_9 / D_inc_9;
1018 intercepted_leaf_diffuse += flux * area / D_inc_9 / D_inc_9;
1021 float intercepted_ground_direct = 0.f;
1022 float intercepted_ground_diffuse = 0.f;
1023 for (
uint i: UUIDs_ground) {
1030 if (fabsf(position.
x) <= 0.5 * D_inc_9 && fabsf(position.
y) <= 0.5 * D_inc_9) {
1031 intercepted_ground_direct += flux_dir * area / D_inc_9 / D_inc_9;
1032 intercepted_ground_diffuse += flux_diff * area / D_inc_9 / D_inc_9;
1036 intercepted_ground_direct = 1.f - intercepted_ground_direct;
1037 intercepted_ground_diffuse = 1.f - intercepted_ground_diffuse;
1040 float dtheta = 0.5f * float(
M_PI) / float(N);
1042 float intercepted_theoretical_diffuse = 0.f;
1043 for (
int i = 0; i < N; i++) {
1044 float theta = (float(i) + 0.5f) * dtheta;
1045 intercepted_theoretical_diffuse += 2.f * (1.f - expf(-0.5f * LAI_9 / cosf(theta))) * cosf(theta) * sinf(theta) * dtheta;
1048 float intercepted_theoretical_direct = 1.f - expf(-0.5f * LAI_9 / cosf(theta_s));
1050 DOCTEST_CHECK(fabsf(intercepted_ground_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
1051 DOCTEST_CHECK(fabsf(intercepted_leaf_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
1052 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
1053 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
1056GPU_TEST_CASE(
"RadiationModel Gas-filled Furnace") {
1057 float error_threshold = 0.005;
1058 float sigma = 5.6703744E-8;
1060 float Rref_10 = 33000.f;
1061 uint Ndiffuse_10 = 10000;
1067 float Tw_10 = 1273.f;
1068 float Tm_10 = 1773.f;
1070 float kappa_10 = 0.1f;
1071 float eps_m_10 = 1.f;
1072 float w_patch_10 = 0.01;
1074 int Npatches_10 = (int) lroundf(2.f * kappa_10 * w_10 * h_10 * d_10 / w_patch_10 / w_patch_10);
1078 std::vector<uint> UUIDs_box = context_10.
addBox(
make_vec3(0, 0, 0),
make_vec3(d_10, w_10, h_10),
make_int3(round(d_10 / w_patch_10), round(w_10 / w_patch_10), round(h_10 / w_patch_10)), RGB::green,
true);
1083 std::vector<uint> UUIDs_patches;
1085 for (
int i = 0; i < Npatches_10; i++) {
1086 float x = -0.5f * d_10 + 0.5f * w_patch_10 + (d_10 - 2 * w_patch_10) * context_10.
randu();
1087 float y = -0.5f * w_10 + 0.5f * w_patch_10 + (w_10 - 2 * w_patch_10) * context_10.
randu();
1088 float z = -0.5f * h_10 + 0.5f * w_patch_10 + (h_10 - 2 * w_patch_10) * context_10.
randu();
1090 float theta = acosf(1.f - context_10.
randu());
1091 float phi = 2.f * float(
M_PI) * context_10.
randu();
1097 context_10.
setPrimitiveData(UUIDs_patches,
"reflectivity_LW", 1.f - eps_m_10);
1099 RadiationModel radiation_10 = RadiationModelTestHelper::createWithSharedDevice(&context_10);
1111 for (
uint i: UUIDs_box) {
1116 R_wall += flux * area;
1118 R_wall = R_wall / A_wall - sigma * powf(Tw_10, 4);
1120 DOCTEST_CHECK(fabsf(R_wall - Rref_10) / Rref_10 <= error_threshold);
1123GPU_TEST_CASE(
"RadiationModel Purely Scattering Medium Between Infinite Plates") {
1124 float error_threshold = 0.005;
1125 float sigma = 5.6703744E-8;
1131 float Tw1_11 = 300.f;
1132 float Tw2_11 = 400.f;
1134 float epsw1_11 = 0.8f;
1135 float epsw2_11 = 0.5f;
1137 float omega_11 = 1.f;
1138 float tauL_11 = 0.1f;
1140 float Psi2_exact = 0.427;
1142 float w_patch_11 = 0.05;
1144 float beta = tauL_11 / h_11;
1146 int Nleaves_11 = (int) lroundf(2.f * beta * W_11 * W_11 * h_11 / w_patch_11 / w_patch_11);
1165 std::vector<uint> UUIDs_patches_11;
1167 for (
int i = 0; i < Nleaves_11; i++) {
1168 float x = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1169 float y = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1170 float z = -0.5f * h_11 + 0.5f * w_patch_11 + (h_11 - w_patch_11) * context_11.
randu();
1172 float theta = acosf(1.f - context_11.
randu());
1173 float phi = 2.f * float(
M_PI) * context_11.
randu();
1178 context_11.
setPrimitiveData(UUIDs_patches_11,
"emissivity_LW", 1.f - omega_11);
1179 context_11.
setPrimitiveData(UUIDs_patches_11,
"reflectivity_LW", omega_11);
1181 RadiationModel radiation_11 = RadiationModelTestHelper::createWithSharedDevice(&context_11);
1192 float A_wall2 = 0.f;
1193 for (
int i = 0; i < UUIDs_1.size(); i++) {
1196 if (fabsf(position.
x) < 0.5 * w_11 && fabsf(position.
y) < 0.5 * w_11) {
1201 R_wall2 += flux * area;
1206 R_wall2 = (R_wall2 / A_wall2 - epsw2_11 * sigma * pow(Tw2_11, 4)) / (sigma * (pow(Tw1_11, 4) - pow(Tw2_11, 4)));
1208 DOCTEST_CHECK(fabsf(R_wall2 - Psi2_exact) <= 10.f * error_threshold);
1211GPU_TEST_CASE(
"RadiationModel Homogeneous Canopy with Periodic Boundaries") {
1212 float error_threshold = 0.005;
1214 uint Ndirect_12 = 1000;
1215 uint Ndiffuse_12 = 5000;
1220 float w_leaf_12 = 0.05;
1222 int Nleaves_12 = round(LAI_12 * D_12 * D_12 / w_leaf_12 / w_leaf_12);
1226 std::vector<uint> UUIDs_leaf_12;
1228 for (
int i = 0; i < Nleaves_12; i++) {
1229 vec3 position((-0.5 + context_12.
randu()) * D_12, (-0.5 + context_12.
randu()) * D_12, 0.5 * w_leaf_12 + context_12.
randu() * h_12);
1233 UUIDs_leaf_12.push_back(UUID);
1239 RadiationModel radiation_12 = RadiationModelTestHelper::createWithSharedDevice(&context_12);
1245 float theta_s = 0.2 *
M_PI;
1247 radiation_12.
setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1258 radiation_12.
runBand(
"direct");
1259 radiation_12.
runBand(
"diffuse");
1261 float intercepted_leaf_direct_12 = 0.f;
1262 float intercepted_leaf_diffuse_12 = 0.f;
1263 for (
int i = 0; i < UUIDs_leaf_12.size(); i++) {
1266 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_direct", flux);
1267 intercepted_leaf_direct_12 += flux * area / D_12 / D_12;
1268 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_diffuse", flux);
1269 intercepted_leaf_diffuse_12 += flux * area / D_12 / D_12;
1272 float intercepted_ground_direct_12 = 0.f;
1273 float intercepted_ground_diffuse_12 = 0.f;
1274 for (
int i = 0; i < UUIDs_ground_12.size(); i++) {
1277 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_direct", flux_dir);
1279 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_diffuse", flux_diff);
1281 intercepted_ground_direct_12 += flux_dir * area / D_12 / D_12;
1282 intercepted_ground_diffuse_12 += flux_diff * area / D_12 / D_12;
1285 intercepted_ground_direct_12 = 1.f - intercepted_ground_direct_12;
1286 intercepted_ground_diffuse_12 = 1.f - intercepted_ground_diffuse_12;
1289 float dtheta = 0.5 *
M_PI / float(N);
1291 float intercepted_theoretical_diffuse_12 = 0.f;
1292 for (
int i = 0; i < N; i++) {
1293 float theta = (i + 0.5f) * dtheta;
1294 intercepted_theoretical_diffuse_12 += 2.f * (1.f - exp(-0.5 * LAI_12 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1297 float intercepted_theoretical_direct_12 = 1.f - exp(-0.5 * LAI_12 / cos(theta_s));
1299 DOCTEST_CHECK(fabsf(intercepted_ground_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1300 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1301 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1302 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1305GPU_TEST_CASE(
"RayTracingGeometry validation with periodic boundaries") {
1313 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
1325 DOCTEST_CHECK(
true);
1328GPU_TEST_CASE(
"RadiationModel Texture-masked Tile Objects with Periodic Boundaries") {
1329 float error_threshold = 0.005;
1331 uint Ndirect_13 = 1000;
1332 uint Ndiffuse_13 = 5000;
1337 float w_leaf_13 = 0.05;
1345 for (
uint p = 0; p < UUIDs_ptype.size(); p++) {
1349 int Nleaves_13 = round(LAI_13 * D_13 * D_13 / A_leaf);
1351 std::vector<uint> UUIDs_leaf_13;
1353 for (
int i = 0; i < Nleaves_13; i++) {
1354 vec3 position((-0.5 + context_13.
randu()) * D_13, (-0.5 + context_13.
randu()) * D_13, 0.5 * w_leaf_13 + context_13.
randu() * h_13);
1359 context_13.
rotateObject(objID, -rotation.elevation,
"y");
1364 UUIDs_leaf_13.insert(UUIDs_leaf_13.end(), UUIDs.begin(), UUIDs.end());
1372 RadiationModel radiation_13 = RadiationModelTestHelper::createWithSharedDevice(&context_13);
1378 float theta_s = 0.2 *
M_PI;
1380 radiation_13.
setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1391 radiation_13.
runBand(
"direct");
1392 radiation_13.
runBand(
"diffuse");
1394 float intercepted_leaf_direct_13 = 0.f;
1395 float intercepted_leaf_diffuse_13 = 0.f;
1396 for (
int i = 0; i < UUIDs_leaf_13.size(); i++) {
1399 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_direct", flux);
1400 intercepted_leaf_direct_13 += flux * area / D_13 / D_13;
1401 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_diffuse", flux);
1402 intercepted_leaf_diffuse_13 += flux * area / D_13 / D_13;
1405 float intercepted_ground_direct_13 = 0.f;
1406 float intercepted_ground_diffuse_13 = 0.f;
1407 for (
int i = 0; i < UUIDs_ground_13.size(); i++) {
1410 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_direct", flux_dir);
1412 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_diffuse", flux_diff);
1414 intercepted_ground_direct_13 += flux_dir * area / D_13 / D_13;
1415 intercepted_ground_diffuse_13 += flux_diff * area / D_13 / D_13;
1418 intercepted_ground_direct_13 = 1.f - intercepted_ground_direct_13;
1419 intercepted_ground_diffuse_13 = 1.f - intercepted_ground_diffuse_13;
1422 float dtheta = 0.5 *
M_PI / float(N);
1424 float intercepted_theoretical_diffuse_13 = 0.f;
1425 for (
int i = 0; i < N; i++) {
1426 float theta = (i + 0.5f) * dtheta;
1427 intercepted_theoretical_diffuse_13 += 2.f * (1.f - exp(-0.5 * LAI_13 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1430 float intercepted_theoretical_direct_13 = 1.f - exp(-0.5 * LAI_13 / cos(theta_s));
1432 DOCTEST_CHECK(fabsf(intercepted_ground_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1433 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1434 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_13 - intercepted_theoretical_diffuse_13) <= 2.f * error_threshold);
1435 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_13 - intercepted_theoretical_diffuse_13) <= 4.f * error_threshold);
1438GPU_TEST_CASE(
"RadiationModel Anisotropic Diffuse Radiation Horizontal Patch") {
1439 float error_threshold = 0.005;
1441 uint Ndiffuse_14 = 50000;
1445 std::vector<float> K_14;
1446 K_14.push_back(0.f);
1447 K_14.push_back(0.25f);
1448 K_14.push_back(1.f);
1450 std::vector<float> thetas_14;
1451 thetas_14.push_back(0.f);
1452 thetas_14.push_back(0.25 *
M_PI);
1457 RadiationModel radiation_14 = RadiationModelTestHelper::createWithSharedDevice(&context_14);
1467 for (
int t = 0; t < thetas_14.size(); t++) {
1468 for (
int k = 0; k < K_14.size(); k++) {
1470 radiation_14.
runBand(
"diffuse");
1475 DOCTEST_CHECK(fabsf(Rdiff - 1.f) <= 2.f * error_threshold);
1480GPU_TEST_CASE(
"RadiationModel Prague Sky Diffuse Radiation Normalization") {
1481 float error_threshold = 0.015;
1483 uint Ndiffuse_prague = 100000;
1490 std::vector<std::vector<float>> prague_test_conditions;
1493 std::vector<float> clear_sky;
1494 clear_sky.push_back(3.0f);
1495 clear_sky.push_back(15.0f);
1496 clear_sky.push_back(1.5f);
1497 prague_test_conditions.push_back(clear_sky);
1500 std::vector<float> turbid_sky;
1501 turbid_sky.push_back(8.0f);
1502 turbid_sky.push_back(10.0f);
1503 turbid_sky.push_back(2.5f);
1504 prague_test_conditions.push_back(turbid_sky);
1507 std::vector<float> overcast_sky;
1508 overcast_sky.push_back(0.5f);
1509 overcast_sky.push_back(30.0f);
1510 overcast_sky.push_back(1.2f);
1511 prague_test_conditions.push_back(overcast_sky);
1516 RadiationModel radiation_prague = RadiationModelTestHelper::createWithSharedDevice(&context_prague);
1527 std::vector<helios::vec2> diffuse_spectrum_prague = {{400, 1.0}, {550, 1.0}, {700, 1.0}};
1528 context_prague.
setGlobalData(
"prague_test_diffuse_spectrum", diffuse_spectrum_prague);
1536 context_prague.
setGlobalData(
"prague_sky_visibility_km", 50.0f);
1537 context_prague.
setGlobalData(
"prague_sky_ground_albedo", 0.2f);
1539 for (
size_t cond = 0; cond < prague_test_conditions.size(); cond++) {
1540 float circ_str = prague_test_conditions[cond][0];
1541 float circ_width = prague_test_conditions[cond][1];
1542 float horiz_bright = prague_test_conditions[cond][2];
1546 float integral = 0.0f;
1548 for (
int j = 0; j < N; ++j) {
1549 for (
int i = 0; i < N; ++i) {
1550 float theta = 0.5f *
M_PI * (i + 0.5f) / N;
1551 float phi = 2.0f *
M_PI * (j + 0.5f) / N;
1555 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));
1556 float gamma = std::acos(cos_gamma) * 180.0f /
M_PI;
1559 float cos_theta = std::max(0.0f, dir.z);
1560 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
1561 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
1562 float pattern = circ_term * horizon_term;
1564 integral += pattern * std::cos(theta) * std::sin(theta) * (
M_PI / (2.0f * N)) * (2.0f *
M_PI / N);
1567 float normalization = 1.0f / std::max(integral, 1e-10f);
1570 std::vector<float> prague_params;
1571 prague_params.push_back(550.0f);
1572 prague_params.push_back(0.1f);
1573 prague_params.push_back(circ_str);
1574 prague_params.push_back(circ_width);
1575 prague_params.push_back(horiz_bright);
1576 prague_params.push_back(normalization);
1578 context_prague.
setGlobalData(
"prague_sky_spectral_params", prague_params);
1581 radiation_prague.
runBand(
"diffuse");
1585 context_prague.
getPrimitiveData(UUID_prague,
"radiation_flux_diffuse", Rdiff_prague);
1589 DOCTEST_CHECK(fabsf(Rdiff_prague - 1.f) <= 2.f * error_threshold);
1593GPU_TEST_CASE(
"RadiationModel Prague Sky Angular Distribution") {
1615 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
1624 std::vector<helios::vec2> diffuse_spectrum = {{400, 1.0}, {550, 1.0}, {700, 1.0}};
1625 context.
setGlobalData(
"prague_angular_test_spectrum", diffuse_spectrum);
1641 std::vector<float> prague_params;
1642 prague_params.push_back(550.0f);
1643 prague_params.push_back(0.1f);
1644 prague_params.push_back(8.0f);
1645 prague_params.push_back(10.0f);
1646 prague_params.push_back(1.0f);
1647 prague_params.push_back(0.0f);
1648 context.
setGlobalData(
"prague_sky_spectral_params", prague_params);
1658 DOCTEST_CHECK(flux < 0.97f);
1659 DOCTEST_CHECK(flux > 0.70f);
1662GPU_TEST_CASE(
"RadiationModel Disk Radiation Source Above Circular Element") {
1663 float error_threshold = 0.005;
1665 uint Ndirect_15 = 10000;
1672 RadiationModel radiation_15 = RadiationModelTestHelper::createWithSharedDevice(&context_15);
1685 radiation_15.
runBand(
"light");
1690 float R1_15 = r1_15 / a_15;
1691 float R2_15 = r2_15 / a_15;
1692 float X_15 = 1.f + (1.f + R2_15 * R2_15) / (R1_15 * R1_15);
1693 float F12_exact_15 = 0.5f * (X_15 - sqrtf(X_15 * X_15 - 4.f * powf(R2_15 / R1_15, 2)));
1695 DOCTEST_CHECK(fabs(F12_15 - F12_exact_15 * r1_15 * r1_15 / r2_15 / r2_15) <= 2.f * error_threshold);
1698GPU_TEST_CASE(
"RadiationModel Rectangular Radiation Source Above Patch") {
1699 float error_threshold = 0.01;
1701 uint Ndirect_16 = 50000;
1708 RadiationModel radiation_16 = RadiationModelTestHelper::createWithSharedDevice(&context_16);
1721 radiation_16.
runBand(
"light");
1726 float X_16 = a_16 / c_16;
1727 float Y_16 = b_16 / c_16;
1728 float X2_16 = X_16 * X_16;
1729 float Y2_16 = Y_16 * Y_16;
1731 float F12_exact_16 = 2.0f / float(
M_PI * X_16 * Y_16) *
1732 (logf(std::sqrt((1.f + X2_16) * (1.f + Y2_16) / (1.f + X2_16 + Y2_16))) + X_16 * std::sqrt(1.f + Y2_16) * atanf(X_16 / std::sqrt(1.f + Y2_16)) + Y_16 * std::sqrt(1.f + X2_16) * atanf(Y_16 / std::sqrt(1.f + X2_16)) -
1733 X_16 * atanf(X_16) - Y_16 * atanf(Y_16));
1735 DOCTEST_CHECK(fabs(F12_16 - F12_exact_16) <= error_threshold);
1738GPU_TEST_CASE(
"RadiationModel ROMC Camera Test Verification") {
1740 float sunzenithd = 30;
1741 float reflectivityleaf = 0.02;
1742 float transmissivityleaf = 0.01;
1743 std::string bandname =
"RED";
1745 float viewazimuth = 0;
1746 float heightscene = 30.f;
1747 float rangescene = 100.f;
1748 std::vector<float> viewangles = {-75, 0, 36};
1749 float sunazimuth = 0;
1751 std::vector<float> referencevalues = {21.f, 71.6f, 87.2f};
1754 std::vector<std::vector<float>> CSpositions = {{-24.8302, 11.6110, 15.6210}, {-38.3380, -9.06342, 17.6094}, {-5.26569, 18.9618, 17.2535}, {-27.4794, -32.0266, 15.9146},
1755 {33.5709, -6.31039, 14.5332}, {11.9126, 8.32062, 12.1220}, {32.4756, -26.9023, 16.3684}};
1757 for (
int w = -1; w < 2; w++) {
1759 for (
auto &CSposition: CSpositions) {
1760 vec3 transpos = movew +
make_vec3(CSposition.at(0), CSposition.at(1), CSposition.at(2));
1762 std::vector<uint> iCUUIDsn = cameracalibration.readROMCCanopy();
1765 context_17.
setPrimitiveData(iCUUIDsn,
"reflectivity_spectrum",
"leaf_reflectivity");
1766 context_17.
setPrimitiveData(iCUUIDsn,
"transmissivity_spectrum",
"leaf_transmissivity");
1771 std::vector<helios::vec2> leafspectrarho(2200);
1772 std::vector<helios::vec2> leafspectratau(2200);
1773 std::vector<helios::vec2> sourceintensity(2200);
1774 for (
int i = 0; i < leafspectrarho.size(); i++) {
1775 leafspectrarho.at(i).x = float(301 + i);
1776 leafspectrarho.at(i).y = reflectivityleaf;
1777 leafspectratau.at(i).x = float(301 + i);
1778 leafspectratau.at(i).y = transmissivityleaf;
1779 sourceintensity.at(i).x = float(301 + i);
1780 sourceintensity.at(i).y = 1;
1782 context_17.
setGlobalData(
"leaf_reflectivity", leafspectrarho);
1783 context_17.
setGlobalData(
"leaf_transmissivity", leafspectratau);
1784 context_17.
setGlobalData(
"camera_response", sourceintensity);
1785 context_17.
setGlobalData(
"source_intensity", sourceintensity);
1789 std::vector<std::string> cameralabels;
1790 RadiationModel radiation_17 = RadiationModelTestHelper::createWithSharedDevice(&context_17);
1792 for (
float viewangle: viewangles) {
1795 vec3 camera_position = 100000 * camerarotation + camera_lookat;
1800 cameraproperties.
HFOV = 0.02864786f * 2.f;
1803 std::string cameralabel =
"ROMC" + std::to_string(viewangle);
1804 radiation_17.
addRadiationCamera(cameralabel, {bandname}, camera_position, camera_lookat, cameraproperties, 60);
1805 cameralabels.push_back(cameralabel);
1817 for (
const auto &cameralabel: cameralabels) {
1821 radiation_17.
runBand(bandname);
1824 std::vector<float> camera_data;
1825 std::vector<uint> camera_UUID;
1827 for (
int i = 0; i < cameralabels.size(); i++) {
1828 std::string global_data_label =
"camera_" + cameralabels.at(i) +
"_" + bandname;
1829 std::string global_UUID =
"camera_" + cameralabels.at(i) +
"_pixel_UUID";
1830 context_17.
getGlobalData(global_data_label.c_str(), camera_data);
1832 float camera_all_data = 0;
1833 int filtered_count = 0, uuid_zero_count = 0, uuid_invalid_count = 0;
1834 float unfiltered_sum = 0, uuid_zero_sum = 0;
1835 for (
int v = 0; v < camera_data.size(); v++) {
1836 if (camera_data.at(v) > 0) {
1837 unfiltered_sum += camera_data.at(v);
1838 uint raw_uuid = camera_UUID.at(v);
1839 if (raw_uuid == 0) {
1841 uuid_zero_sum += camera_data.at(v);
1843 uint iUUID = raw_uuid - 1;
1845 camera_all_data += camera_data.at(v);
1848 uuid_invalid_count++;
1853 cameravalue = std::abs(referencevalues.at(i) - camera_all_data);
1854 DOCTEST_CHECK(cameravalue <= 1.5f);
1858GPU_TEST_CASE(
"RadiationModel Spectral Integration and Interpolation Tests") {
1861 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
1866 std::vector<helios::vec2> test_spectrum;
1867 test_spectrum.push_back(
make_vec2(400, 0.1f));
1868 test_spectrum.push_back(
make_vec2(500, 0.5f));
1869 test_spectrum.push_back(
make_vec2(600, 0.3f));
1870 test_spectrum.push_back(
make_vec2(700, 0.2f));
1875 float expected_integral = (0.1f + 0.5f) * 100.0f * 0.5f + (0.5f + 0.3f) * 100.0f * 0.5f + (0.3f + 0.2f) * 100.0f * 0.5f;
1876 DOCTEST_CHECK(std::abs(full_integral - expected_integral) < 1e-5f);
1882 DOCTEST_CHECK(std::abs(partial_integral - full_integral) < 1e-5f);
1887 std::vector<helios::vec2> source_spectrum;
1888 source_spectrum.push_back(
make_vec2(400, 1.0f));
1889 source_spectrum.push_back(
make_vec2(500, 2.0f));
1890 source_spectrum.push_back(
make_vec2(600, 1.5f));
1891 source_spectrum.push_back(
make_vec2(700, 0.5f));
1893 std::vector<helios::vec2> surface_spectrum;
1894 surface_spectrum.push_back(
make_vec2(400, 0.2f));
1895 surface_spectrum.push_back(
make_vec2(500, 0.6f));
1896 surface_spectrum.push_back(
make_vec2(600, 0.4f));
1897 surface_spectrum.push_back(
make_vec2(700, 0.1f));
1902 float integrated_product = radiation.
integrateSpectrum(source_ID, surface_spectrum, 400, 700);
1905 DOCTEST_CHECK(integrated_product > 0.0f);
1906 DOCTEST_CHECK(integrated_product <= 1.0f);
1911 std::vector<helios::vec2> surface_spectrum;
1912 surface_spectrum.push_back(
make_vec2(400, 0.3f));
1913 surface_spectrum.push_back(
make_vec2(500, 0.7f));
1914 surface_spectrum.push_back(
make_vec2(600, 0.5f));
1915 surface_spectrum.push_back(
make_vec2(700, 0.2f));
1917 std::vector<helios::vec2> camera_response;
1918 camera_response.push_back(
make_vec2(400, 0.1f));
1919 camera_response.push_back(
make_vec2(500, 0.8f));
1920 camera_response.push_back(
make_vec2(600, 0.9f));
1921 camera_response.push_back(
make_vec2(700, 0.3f));
1923 float camera_integrated = radiation.
integrateSpectrum(surface_spectrum, camera_response);
1924 DOCTEST_CHECK(camera_integrated >= 0.0f);
1925 DOCTEST_CHECK(camera_integrated <= 1.0f);
1929GPU_TEST_CASE(
"RadiationModel Spectral Radiative Properties Setting and Validation") {
1932 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
1941 std::vector<helios::vec2> leaf_reflectivity;
1942 leaf_reflectivity.push_back(
make_vec2(400, 0.05f));
1943 leaf_reflectivity.push_back(
make_vec2(500, 0.10f));
1944 leaf_reflectivity.push_back(
make_vec2(600, 0.08f));
1945 leaf_reflectivity.push_back(
make_vec2(700, 0.45f));
1946 leaf_reflectivity.push_back(
make_vec2(800, 0.50f));
1948 std::vector<helios::vec2> leaf_transmissivity;
1949 leaf_transmissivity.push_back(
make_vec2(400, 0.02f));
1950 leaf_transmissivity.push_back(
make_vec2(500, 0.05f));
1951 leaf_transmissivity.push_back(
make_vec2(600, 0.04f));
1952 leaf_transmissivity.push_back(
make_vec2(700, 0.40f));
1953 leaf_transmissivity.push_back(
make_vec2(800, 0.45f));
1955 context.
setGlobalData(
"test_leaf_reflectivity", leaf_reflectivity);
1956 context.
setGlobalData(
"test_leaf_transmissivity", leaf_transmissivity);
1959 context.
setPrimitiveData(patch_UUID,
"reflectivity_spectrum",
"test_leaf_reflectivity");
1960 context.
setPrimitiveData(patch_UUID,
"transmissivity_spectrum",
"test_leaf_transmissivity");
1963 std::string refl_spectrum_label;
1964 context.
getPrimitiveData(patch_UUID,
"reflectivity_spectrum", refl_spectrum_label);
1965 DOCTEST_CHECK(refl_spectrum_label ==
"test_leaf_reflectivity");
1967 std::string trans_spectrum_label;
1968 context.
getPrimitiveData(patch_UUID,
"transmissivity_spectrum", trans_spectrum_label);
1969 DOCTEST_CHECK(trans_spectrum_label ==
"test_leaf_transmissivity");
1972 std::vector<helios::vec2> retrieved_refl;
1973 context.
getGlobalData(
"test_leaf_reflectivity", retrieved_refl);
1974 DOCTEST_CHECK(retrieved_refl.size() == leaf_reflectivity.size());
1976 for (
size_t i = 0; i < retrieved_refl.size(); i++) {
1977 DOCTEST_CHECK(std::abs(retrieved_refl[i].x - leaf_reflectivity[i].x) < 1e-5f);
1978 DOCTEST_CHECK(std::abs(retrieved_refl[i].y - leaf_reflectivity[i].y) < 1e-5f);
1988 std::vector<helios::vec2> solar_spectrum;
1989 solar_spectrum.push_back(
make_vec2(400, 1.5f));
1990 solar_spectrum.push_back(
make_vec2(500, 2.0f));
1991 solar_spectrum.push_back(
make_vec2(600, 1.8f));
1992 solar_spectrum.push_back(
make_vec2(700, 1.2f));
1993 solar_spectrum.push_back(
make_vec2(800, 1.0f));
1994 solar_spectrum.push_back(
make_vec2(900, 0.8f));
2013 DOCTEST_CHECK(has_refl_spectrum);
2014 DOCTEST_CHECK(has_trans_spectrum);
2019 std::vector<helios::vec2> rgb_red_response;
2020 rgb_red_response.push_back(
make_vec2(400, 0.0f));
2021 rgb_red_response.push_back(
make_vec2(500, 0.1f));
2022 rgb_red_response.push_back(
make_vec2(600, 0.6f));
2023 rgb_red_response.push_back(
make_vec2(700, 0.9f));
2024 rgb_red_response.push_back(
make_vec2(800, 0.1f));
2026 context.
setGlobalData(
"rgb_red_response", rgb_red_response);
2030 camera_properties.
HFOV = 45.0f *
M_PI / 180.0f;
2042 DOCTEST_CHECK(
true);
2046GPU_TEST_CASE(
"RadiationModel Spectral Edge Cases and Error Handling") {
2049 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2054 std::vector<helios::vec2> empty_spectrum;
2057 bool caught_error =
false;
2061 caught_error =
true;
2063 DOCTEST_CHECK(caught_error);
2068 std::vector<helios::vec2> single_point;
2069 single_point.push_back(
make_vec2(550, 0.5f));
2071 bool caught_error =
false;
2075 caught_error =
true;
2077 DOCTEST_CHECK(caught_error);
2082 std::vector<helios::vec2> test_spectrum;
2083 test_spectrum.push_back(
make_vec2(400, 0.2f));
2084 test_spectrum.push_back(
make_vec2(600, 0.8f));
2085 test_spectrum.push_back(
make_vec2(800, 0.3f));
2087 bool caught_error =
false;
2092 caught_error =
true;
2094 DOCTEST_CHECK(caught_error);
2096 caught_error =
false;
2101 caught_error =
true;
2103 DOCTEST_CHECK(caught_error);
2108 std::vector<helios::vec2> non_monotonic;
2109 non_monotonic.push_back(
make_vec2(500, 0.3f));
2110 non_monotonic.push_back(
make_vec2(400, 0.5f));
2111 non_monotonic.push_back(
make_vec2(600, 0.2f));
2115 bool function_completed =
true;
2117 context.
setGlobalData(
"non_monotonic_spectrum", non_monotonic);
2119 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"non_monotonic_spectrum");
2124 function_completed =
false;
2127 DOCTEST_CHECK(function_completed);
2132 std::vector<helios::vec2> limited_spectrum;
2133 limited_spectrum.push_back(
make_vec2(500, 0.3f));
2134 limited_spectrum.push_back(
make_vec2(600, 0.7f));
2137 float extended_integral = radiation.
integrateSpectrum(limited_spectrum, 400, 800);
2138 float limited_integral = radiation.
integrateSpectrum(limited_spectrum, 500, 600);
2141 DOCTEST_CHECK(extended_integral == 0.0f);
2142 DOCTEST_CHECK(limited_integral > 0.0f);
2146GPU_TEST_CASE(
"RadiationModel Spectral Caching and Performance Validation") {
2149 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2155 std::vector<helios::vec2> common_spectrum;
2156 common_spectrum.push_back(
make_vec2(400, 0.1f));
2157 common_spectrum.push_back(
make_vec2(500, 0.5f));
2158 common_spectrum.push_back(
make_vec2(600, 0.3f));
2159 common_spectrum.push_back(
make_vec2(700, 0.2f));
2161 context.
setGlobalData(
"common_leaf_spectrum", common_spectrum);
2164 std::vector<uint> patch_UUIDs;
2165 for (
int i = 0; i < 10; i++) {
2167 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"common_leaf_spectrum");
2168 context.
setPrimitiveData(patch,
"transmissivity_spectrum",
"common_leaf_spectrum");
2169 patch_UUIDs.push_back(patch);
2181 auto start_time = std::chrono::high_resolution_clock::now();
2183 auto end_time = std::chrono::high_resolution_clock::now();
2185 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
2188 DOCTEST_CHECK(duration.count() < 10000000);
2191 for (
uint patch_UUID: patch_UUIDs) {
2194 DOCTEST_CHECK(has_spectrum);
2199GPU_TEST_CASE(
"RadiationModel Spectral Library Integration") {
2202 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2211 bool library_available =
false;
2213 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"leaf_reflectivity");
2216 library_available =
false;
2219 if (library_available) {
2224 std::string spectrum_label;
2226 DOCTEST_CHECK(spectrum_label ==
"leaf_reflectivity");
2229 DOCTEST_CHECK(
true);
2234GPU_TEST_CASE(
"RadiationModel Multi-Spectrum Primitive Assignment") {
2237 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2241 std::vector<helios::vec2> red_spectrum;
2242 red_spectrum.push_back(
make_vec2(400, 0.1f));
2243 red_spectrum.push_back(
make_vec2(500, 0.1f));
2244 red_spectrum.push_back(
make_vec2(600, 0.8f));
2245 red_spectrum.push_back(
make_vec2(700, 0.9f));
2247 std::vector<helios::vec2> green_spectrum;
2248 green_spectrum.push_back(
make_vec2(400, 0.1f));
2249 green_spectrum.push_back(
make_vec2(500, 0.8f));
2250 green_spectrum.push_back(
make_vec2(600, 0.9f));
2251 green_spectrum.push_back(
make_vec2(700, 0.1f));
2253 std::vector<helios::vec2> blue_spectrum;
2254 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2255 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2256 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2257 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2265 std::vector<uint> red_patches, green_patches, blue_patches;
2268 for (
int i = 0; i < 5; i++) {
2271 red_patches.push_back(patch);
2275 for (
int i = 0; i < 5; i++) {
2277 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2278 green_patches.push_back(patch);
2282 for (
int i = 0; i < 5; i++) {
2285 blue_patches.push_back(patch);
2300 std::vector<helios::vec2> uniform_spectrum;
2301 uniform_spectrum.push_back(
make_vec2(300, 1.0f));
2302 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2313 std::vector<helios::vec2> camera_spectrum;
2314 camera_spectrum.push_back(
make_vec2(400, 0.3f));
2315 camera_spectrum.push_back(
make_vec2(500, 0.9f));
2316 camera_spectrum.push_back(
make_vec2(600, 0.8f));
2317 camera_spectrum.push_back(
make_vec2(700, 0.2f));
2321 std::vector<helios::vec2> camera_spectrum2;
2322 camera_spectrum2.push_back(
make_vec2(400, 0.2f));
2323 camera_spectrum2.push_back(
make_vec2(500, 0.3f));
2324 camera_spectrum2.push_back(
make_vec2(600, 0.8f));
2325 camera_spectrum2.push_back(
make_vec2(700, 0.9f));
2326 context.
setGlobalData(
"camera2_spectrum", camera_spectrum2);
2328 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2331 camera_props.
HFOV = 2.0f;
2360 float red_patch_R_flux = 0, red_patch_G_flux = 0, red_patch_B_flux = 0;
2361 for (
uint patch: red_patches) {
2362 float flux_R, flux_G, flux_B;
2366 red_patch_R_flux += flux_R;
2367 red_patch_G_flux += flux_G;
2368 red_patch_B_flux += flux_B;
2370 red_patch_R_flux /= red_patches.size();
2371 red_patch_G_flux /= red_patches.size();
2372 red_patch_B_flux /= red_patches.size();
2375 float green_patch_R_flux = 0, green_patch_G_flux = 0, green_patch_B_flux = 0;
2376 for (
uint patch: green_patches) {
2377 float flux_R, flux_G, flux_B;
2381 green_patch_R_flux += flux_R;
2382 green_patch_G_flux += flux_G;
2383 green_patch_B_flux += flux_B;
2385 green_patch_R_flux /= green_patches.size();
2386 green_patch_G_flux /= green_patches.size();
2387 green_patch_B_flux /= green_patches.size();
2390 float blue_patch_R_flux = 0, blue_patch_G_flux = 0, blue_patch_B_flux = 0;
2391 for (
uint patch: blue_patches) {
2392 float flux_R, flux_G, flux_B;
2396 blue_patch_R_flux += flux_R;
2397 blue_patch_G_flux += flux_G;
2398 blue_patch_B_flux += flux_B;
2400 blue_patch_R_flux /= blue_patches.size();
2401 blue_patch_G_flux /= blue_patches.size();
2402 blue_patch_B_flux /= blue_patches.size();
2406 DOCTEST_CHECK(red_patch_R_flux < red_patch_G_flux);
2407 DOCTEST_CHECK(red_patch_R_flux < red_patch_B_flux);
2410 DOCTEST_CHECK(green_patch_G_flux < green_patch_R_flux);
2411 DOCTEST_CHECK(green_patch_G_flux < green_patch_B_flux);
2414 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_R_flux);
2415 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_G_flux);
2418 for (
uint i = 1; i < red_patches.size(); i++) {
2419 float flux_R_0, flux_R_i;
2422 DOCTEST_CHECK(std::abs(flux_R_0 - flux_R_i) / flux_R_0 < 0.15f);
2426GPU_TEST_CASE(
"RadiationModel Band-Specific Camera Spectral Response") {
2429 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2434 std::vector<helios::vec2> red_spectrum;
2435 red_spectrum.push_back(
make_vec2(400, 0.1f));
2436 red_spectrum.push_back(
make_vec2(500, 0.1f));
2437 red_spectrum.push_back(
make_vec2(600, 0.8f));
2438 red_spectrum.push_back(
make_vec2(700, 0.9f));
2442 std::vector<helios::vec2> green_spectrum;
2443 green_spectrum.push_back(
make_vec2(400, 0.1f));
2444 green_spectrum.push_back(
make_vec2(500, 0.8f));
2445 green_spectrum.push_back(
make_vec2(600, 0.9f));
2446 green_spectrum.push_back(
make_vec2(700, 0.1f));
2450 std::vector<helios::vec2> blue_spectrum;
2451 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2452 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2453 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2454 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2458 std::vector<uint> red_patches, green_patches, blue_patches, white_patches;
2461 for (
int i = 0; i < 2; i++) {
2464 red_patches.push_back(patch);
2468 for (
int i = 0; i < 2; i++) {
2470 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2471 green_patches.push_back(patch);
2475 for (
int i = 0; i < 2; i++) {
2478 blue_patches.push_back(patch);
2482 for (
int i = 0; i < 2; i++) {
2484 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"white_spectrum");
2485 white_patches.push_back(patch);
2500 std::vector<helios::vec2> uniform_spectrum;
2501 uniform_spectrum.push_back(
make_vec2(350, 1.0f));
2502 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2510 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2513 camera_props.
HFOV = 2.0f;
2516 std::vector<helios::vec2> cam1_R_spectrum;
2517 cam1_R_spectrum.push_back(
make_vec2(600, 1.0f));
2518 cam1_R_spectrum.push_back(
make_vec2(700, 1.0f));
2521 std::vector<helios::vec2> cam1_G_spectrum;
2522 cam1_G_spectrum.push_back(
make_vec2(500, 0.05f));
2523 cam1_G_spectrum.push_back(
make_vec2(600, 0.05f));
2526 std::vector<helios::vec2> cam1_B_spectrum;
2527 cam1_B_spectrum.push_back(
make_vec2(400, 0.05f));
2528 cam1_B_spectrum.push_back(
make_vec2(500, 0.05f));
2537 std::vector<helios::vec2> cam2_R_spectrum;
2538 cam2_R_spectrum.push_back(
make_vec2(600, 0.05f));
2539 cam2_R_spectrum.push_back(
make_vec2(700, 0.05f));
2542 std::vector<helios::vec2> cam2_G_spectrum;
2543 cam2_G_spectrum.push_back(
make_vec2(500, 0.3f));
2544 cam2_G_spectrum.push_back(
make_vec2(600, 0.3f));
2547 std::vector<helios::vec2> cam2_B_spectrum;
2548 cam2_B_spectrum.push_back(
make_vec2(400, 1.0f));
2549 cam2_B_spectrum.push_back(
make_vec2(500, 1.0f));
2574 uint red_patch = red_patches[0];
2575 float red_flux_R, red_flux_G, red_flux_B;
2580 uint green_patch = green_patches[0];
2581 float green_flux_R, green_flux_G, green_flux_B;
2586 uint blue_patch = blue_patches[0];
2587 float blue_flux_R, blue_flux_G, blue_flux_B;
2621GPU_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary") {
2624 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2628 vec3 position(0, 0, 5);
2629 vec3 lookat(0, 0, 0);
2639 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2653 DOCTEST_CHECK(context.
getGlobalDataType(
"Canon_20D_green") == HELIOS_TYPE_VEC2);
2657 std::vector<vec2> red_response;
2659 DOCTEST_CHECK(red_response.size() == 33);
2662 DOCTEST_CHECK(red_response.front().x == 400.0f);
2663 DOCTEST_CHECK(red_response.back().x == 720.0f);
2683 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2684 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2685 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2689 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2690 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2691 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2694 std::vector<std::string> available_cameras = {
"Canon_20D",
"Nikon_D700",
"Nikon_D50",
"iPhone11",
"iPhone12ProMAX"};
2696 for (
const auto &cam_name: available_cameras) {
2697 if (cam_name !=
"Canon_20D" && cam_name !=
"iPhone11") {
2698 std::string label =
"cam" + std::to_string(cam_count++);
2705GPU_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary with custom band labels") {
2708 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2711 vec3 position(0, 0, 5);
2712 vec3 lookat(0, 0, 0);
2715 std::vector<std::string> custom_labels = {
"R_custom",
"G_custom",
"B_custom"};
2725 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam_custom") != cameras.end());
2743 std::vector<vec2> red_response;
2745 DOCTEST_CHECK(red_response.size() == 33);
2750 std::vector<std::string> wrong_size = {
"A",
"B"};
2751 DOCTEST_CHECK_THROWS_AS(radiation.
addRadiationCameraFromLibrary(
"cam_fail",
"Canon_20D", position, lookat, 1, wrong_size), std::runtime_error);
2756 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
2773 RadiationModel radiation3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
2776 std::vector<std::string> custom_labels2 = {
"NIR",
"VIS",
"UV"};
2795GPU_TEST_CASE(
"RadiationModel - updateCameraParameters") {
2798 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2807 vec3 position(0, 0, 5);
2808 vec3 lookat(0, 0, 0);
2811 initial_props.
HFOV = 45.0f;
2815 initial_props.
model =
"TestCamera";
2817 std::vector<std::string> bands = {
"red",
"green",
"blue"};
2822 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2827 updated_props.
HFOV = 60.0f;
2831 updated_props.
model =
"UpdatedCamera";
2838 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2839 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2840 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2843 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2844 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2845 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2853 DOCTEST_CHECK_THROWS_AS(radiation.
updateCameraParameters(
"nonexistent_camera", props), std::runtime_error);
2860 invalid_props.
HFOV = 45.0f;
2869 invalid_props.
HFOV = 45.0f;
2878 invalid_props.
HFOV = 0.0f;
2887 invalid_props.
HFOV = 180.0f;
2896 invalid_props.
HFOV = 200.0f;
2905 invalid_props.
HFOV = -10.0f;
2914 edge_props.
HFOV = 0.001f;
2922 edge_props.
HFOV = 179.999f;
2935 nonsquare_props.
HFOV = 70.0f;
2943 pinhole_props.
HFOV = 45.0f;
2949 for (
int i = 0; i < 5; i++) {
2952 multi_update_props.
HFOV = 45.0f + i * 5.0f;
2958 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2961GPU_TEST_CASE(
"RadiationModel - getCameraParameters") {
2964 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
2973 vec3 position(1, 2, 3);
2974 vec3 lookat(0, 0, 0);
2977 initial_props.
HFOV = 60.0f;
2981 initial_props.
model =
"TestCameraModel";
2983 std::vector<std::string> bands = {
"red",
"green",
"blue"};
2990 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(initial_props.
HFOV).epsilon(0.001));
2994 DOCTEST_CHECK(retrieved_props.
model == initial_props.
model);
2998 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3003 updated_props.
HFOV = 75.0f;
3007 updated_props.
model =
"UpdatedModel";
3014 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(updated_props.
HFOV).epsilon(0.001));
3018 DOCTEST_CHECK(retrieved_props.
model == updated_props.
model);
3022 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3027 DOCTEST_CHECK_THROWS_AS(radiation.
getCameraParameters(
"nonexistent_camera"), std::runtime_error);
3038 DOCTEST_CHECK(captured.empty());
3045 DOCTEST_CHECK(roundtrip_props.
HFOV == doctest::Approx(roundtrip_props2.
HFOV).epsilon(0.001));
3049 DOCTEST_CHECK(roundtrip_props.
model == roundtrip_props2.
model);
3055 nonsquare_props.
HFOV = 90.0f;
3059 expected_aspect = 1280.0f / 720.0f;
3060 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3065 pinhole_props.
HFOV = 45.0f;
3071 DOCTEST_CHECK(retrieved_props.
lens_diameter == doctest::Approx(0.0f).epsilon(0.001));
3076 cam2_props.
HFOV = 50.0f;
3077 cam2_props.
model =
"Camera2Model";
3082 cam3_props.
HFOV = 70.0f;
3083 cam3_props.
model =
"Camera3Model";
3092 DOCTEST_CHECK(check_cam2.
HFOV == doctest::Approx(50.0f).epsilon(0.001));
3093 DOCTEST_CHECK(check_cam2.
model ==
"Camera2Model");
3097 DOCTEST_CHECK(check_cam3.
HFOV == doctest::Approx(70.0f).epsilon(0.001));
3098 DOCTEST_CHECK(check_cam3.
model ==
"Camera3Model");
3101DOCTEST_TEST_CASE(
"CameraCalibration Basic Functionality") {
3106 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
3107 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
3110 std::vector<uint> all_colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
3111 DOCTEST_CHECK(all_colorboard_UUIDs.size() == 24);
3114 std::vector<uint> all_UUIDs = context.
getAllUUIDs();
3115 DOCTEST_CHECK(all_UUIDs.size() >= 24);
3118 int patches_with_reflectivity = 0;
3119 for (
uint UUID: calibrite_UUIDs) {
3121 patches_with_reflectivity++;
3124 DOCTEST_CHECK(patches_with_reflectivity == 24);
3128 std::vector<uint> spyder_UUIDs = calibration2.addSpyderCHECKRColorboard(
make_vec3(0.5, 0.5, 0.001), 0.05);
3129 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
3132 patches_with_reflectivity = 0;
3133 for (
uint UUID: spyder_UUIDs) {
3135 patches_with_reflectivity++;
3138 DOCTEST_CHECK(patches_with_reflectivity == 24);
3141 std::vector<helios::vec2> test_spectrum;
3142 test_spectrum.push_back(
make_vec2(400.0f, 0.1f));
3143 test_spectrum.push_back(
make_vec2(500.0f, 0.5f));
3144 test_spectrum.push_back(
make_vec2(600.0f, 0.8f));
3145 test_spectrum.push_back(
make_vec2(700.0f, 0.3f));
3148 bool write_success = calibration.writeSpectralXMLfile(
"test_spectrum.xml",
"Test spectrum",
"test_label", &test_spectrum);
3149 DOCTEST_CHECK(write_success ==
true);
3152 std::remove(
"test_spectrum.xml");
3155DOCTEST_TEST_CASE(
"CameraCalibration DGK Integration") {
3166 std::vector<uint> colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
3168 DOCTEST_CHECK(colorboard_UUIDs.size() == 0);
3171 std::vector<uint> test_patches;
3172 for (
int i = 0; i < 18; i++) {
3174 test_patches.push_back(patch);
3180 std::vector<uint> all_UUIDs = context.
getAllUUIDs();
3181 DOCTEST_CHECK(all_UUIDs.size() >= 18);
3184 int dgk_labeled_patches = 0;
3185 for (
uint UUID: test_patches) {
3187 dgk_labeled_patches++;
3190 DOCTEST_CHECK(dgk_labeled_patches == 18);
3196DOCTEST_TEST_CASE(
"CameraCalibration Multiple Colorboards") {
3201 std::vector<uint> dgk_UUIDs = calibration.addDGKColorboard(
make_vec3(0, 0, 0.001), 0.05);
3202 DOCTEST_CHECK(dgk_UUIDs.size() == 18);
3204 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0.5, 0, 0.001), 0.05);
3205 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
3207 std::vector<uint> spyder_UUIDs = calibration.addSpyderCHECKRColorboard(
make_vec3(1.0, 0, 0.001), 0.05);
3208 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
3211 std::vector<uint> all_UUIDs = calibration.getAllColorBoardUUIDs();
3212 DOCTEST_CHECK(all_UUIDs.size() == 66);
3215 std::vector<std::string> detected_types = calibration.detectColorBoardTypes();
3216 DOCTEST_CHECK(detected_types.size() == 3);
3217 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"DGK") != detected_types.end());
3218 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"Calibrite") != detected_types.end());
3219 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"SpyderCHECKR") != detected_types.end());
3223 std::vector<uint> dgk_UUIDs_2;
3226 dgk_UUIDs_2 = calibration.addDGKColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
3228 DOCTEST_CHECK(dgk_UUIDs_2.size() == 18);
3231 std::vector<uint> all_UUIDs_2 = calibration.getAllColorBoardUUIDs();
3232 DOCTEST_CHECK(all_UUIDs_2.size() == 66);
3235 int dgk_labeled = 0, calibrite_labeled = 0, spyder_labeled = 0;
3236 std::vector<uint> context_UUIDs = context.
getAllUUIDs();
3237 for (
uint UUID: context_UUIDs) {
3242 calibrite_labeled++;
3248 DOCTEST_CHECK(dgk_labeled == 18);
3249 DOCTEST_CHECK(calibrite_labeled == 24);
3250 DOCTEST_CHECK(spyder_labeled == 24);
3253GPU_TEST_CASE(
"RadiationModel CCM Export and Import") {
3255 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
3259 std::vector<std::string> band_labels = {
"red",
"green",
"blue"};
3260 std::string camera_label =
"test_camera";
3266 camera_properties.
HFOV = 45.0f;
3274 size_t pixel_count = resolution.
x * resolution.
y;
3275 std::vector<float> red_data(pixel_count, 0.8f);
3276 std::vector<float> green_data(pixel_count, 0.6f);
3277 std::vector<float> blue_data(pixel_count, 0.4f);
3287 std::vector<std::vector<float>> test_matrix = {{1.2f, -0.1f, 0.05f}, {-0.08f, 1.15f, 0.02f}, {0.03f, -0.12f, 1.18f}};
3289 std::string ccm_file_path =
"test_ccm_3x3.xml";
3295 std::ifstream test_file(ccm_file_path);
3296 DOCTEST_CHECK(test_file.good());
3300 std::string loaded_camera_label;
3304 DOCTEST_CHECK(loaded_camera_label == camera_label);
3305 DOCTEST_CHECK(loaded_matrix.size() == 3);
3306 DOCTEST_CHECK(loaded_matrix[0].size() == 3);
3309 for (
size_t i = 0; i < 3; i++) {
3310 for (
size_t j = 0; j < 3; j++) {
3311 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix[i][j]) < 1e-5f);
3316 std::remove(ccm_file_path.c_str());
3322 std::vector<std::vector<float>> test_matrix_4x3 = {{1.1f, -0.05f, 0.02f, 0.01f}, {-0.04f, 1.08f, 0.01f, -0.005f}, {0.02f, -0.06f, 1.12f, 0.008f}};
3324 std::string ccm_file_path =
"test_ccm_4x3.xml";
3330 std::string loaded_camera_label;
3333 DOCTEST_CHECK(loaded_camera_label == camera_label);
3334 DOCTEST_CHECK(loaded_matrix.size() == 3);
3335 DOCTEST_CHECK(loaded_matrix[0].size() == 4);
3338 for (
size_t i = 0; i < 3; i++) {
3339 for (
size_t j = 0; j < 4; j++) {
3340 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix_4x3[i][j]) < 1e-5f);
3345 std::remove(ccm_file_path.c_str());
3351 std::vector<std::vector<float>> test_matrix = {{1.1f, -0.05f, 0.02f}, {-0.03f, 1.08f, 0.01f}, {0.01f, -0.04f, 1.12f}};
3353 std::string ccm_file_path =
"test_apply_ccm_3x3.xml";
3357 std::vector<float> initial_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3358 std::vector<float> initial_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3359 std::vector<float> initial_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3365 std::vector<float> corrected_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3366 std::vector<float> corrected_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3367 std::vector<float> corrected_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3371 float expected_red = test_matrix[0][0] * initial_red[0] + test_matrix[0][1] * initial_green[0] + test_matrix[0][2] * initial_blue[0];
3372 float expected_green = test_matrix[1][0] * initial_red[0] + test_matrix[1][1] * initial_green[0] + test_matrix[1][2] * initial_blue[0];
3373 float expected_blue = test_matrix[2][0] * initial_red[0] + test_matrix[2][1] * initial_green[0] + test_matrix[2][2] * initial_blue[0];
3375 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3376 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3377 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3380 std::remove(ccm_file_path.c_str());
3386 std::vector<std::vector<float>> test_matrix = {{1.05f, -0.02f, 0.01f, 0.005f}, {-0.01f, 1.03f, 0.005f, -0.002f}, {0.005f, -0.015f, 1.08f, 0.003f}};
3388 std::string ccm_file_path =
"test_apply_ccm_4x3.xml";
3392 std::fill(red_data.begin(), red_data.end(), 0.7f);
3393 std::fill(green_data.begin(), green_data.end(), 0.5f);
3394 std::fill(blue_data.begin(), blue_data.end(), 0.3f);
3404 std::vector<float> corrected_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3405 std::vector<float> corrected_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3406 std::vector<float> corrected_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3409 float expected_red = test_matrix[0][0] * 0.7f + test_matrix[0][1] * 0.5f + test_matrix[0][2] * 0.3f + test_matrix[0][3];
3410 float expected_green = test_matrix[1][0] * 0.7f + test_matrix[1][1] * 0.5f + test_matrix[1][2] * 0.3f + test_matrix[1][3];
3411 float expected_blue = test_matrix[2][0] * 0.7f + test_matrix[2][1] * 0.5f + test_matrix[2][2] * 0.3f + test_matrix[2][3];
3413 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3414 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3415 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3418 std::remove(ccm_file_path.c_str());
3422GPU_TEST_CASE(
"RadiationModel CCM Error Handling") {
3424 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
3428 std::string camera_label;
3429 bool exception_thrown =
false;
3432 }
catch (
const std::runtime_error &e) {
3433 exception_thrown =
true;
3434 std::string error_msg(e.what());
3435 DOCTEST_CHECK(error_msg.find(
"Failed to open file for reading") != std::string::npos);
3437 DOCTEST_CHECK(exception_thrown);
3442 std::string malformed_ccm_path =
"malformed_ccm.xml";
3443 std::ofstream malformed_file(malformed_ccm_path);
3444 malformed_file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3445 malformed_file <<
"<helios>\n";
3446 malformed_file <<
" <InvalidTag>\n";
3447 malformed_file <<
" <row>1.0 0.0 0.0</row>\n";
3448 malformed_file <<
" </InvalidTag>\n";
3449 malformed_file <<
"</helios>\n";
3450 malformed_file.close();
3452 std::string camera_label;
3453 bool exception_thrown =
false;
3456 }
catch (
const std::runtime_error &e) {
3457 exception_thrown =
true;
3458 std::string error_msg(e.what());
3459 DOCTEST_CHECK(error_msg.find(
"No matrix data found") != std::string::npos);
3461 DOCTEST_CHECK(exception_thrown);
3463 std::remove(malformed_ccm_path.c_str());
3468 std::string ccm_file_path =
"test_error_ccm.xml";
3469 std::vector<std::vector<float>> identity_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
3473 bool exception_thrown =
false;
3476 }
catch (
const std::runtime_error &e) {
3477 exception_thrown =
true;
3478 std::string error_msg(e.what());
3479 DOCTEST_CHECK(error_msg.find(
"Camera 'nonexistent_camera' does not exist") != std::string::npos);
3481 DOCTEST_CHECK(exception_thrown);
3483 std::remove(ccm_file_path.c_str());
3487GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation from Primitive Data") {
3490 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
3494 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3495 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3496 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3517 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3518 std::vector<uint> uuids = {uuid0, uuid1, uuid2, uuid3, uuid4};
3519 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3520 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3529 radiationmodel.
runBand(
"PAR");
3532 std::string assigned_spectrum;
3533 context.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
3534 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3536 context.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
3537 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3539 context.
getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
3540 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3542 context.
getPrimitiveData(uuid3,
"reflectivity_spectrum", assigned_spectrum);
3543 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3545 context.
getPrimitiveData(uuid4,
"reflectivity_spectrum", assigned_spectrum);
3546 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3550 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3552 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
3564 std::vector<uint> uuids = {uuid_a, uuid_b};
3565 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3566 std::vector<float> values = {0.0f, 10.0f};
3574 radiationmodel2.
runBand(
"PAR");
3576 std::string assigned_spectrum;
3577 context2.
getPrimitiveData(uuid_a,
"transmissivity_spectrum", assigned_spectrum);
3578 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3580 context2.
getPrimitiveData(uuid_b,
"transmissivity_spectrum", assigned_spectrum);
3581 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3585 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3587 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
3595 std::vector<uint> uuids = {uuid};
3596 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3597 std::vector<float> values = {0.0f};
3599 bool exception_thrown =
false;
3602 }
catch (
const std::runtime_error &e) {
3603 exception_thrown =
true;
3604 std::string error_msg(e.what());
3605 DOCTEST_CHECK(error_msg.find(
"must have the same length") != std::string::npos);
3607 DOCTEST_CHECK(exception_thrown);
3611 DOCTEST_SUBCASE(
"Error: empty vectors") {
3613 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
3618 std::vector<uint> uuids = {uuid};
3619 std::vector<std::string> spectra;
3620 std::vector<float> values;
3622 bool exception_thrown =
false;
3625 }
catch (
const std::runtime_error &e) {
3626 exception_thrown =
true;
3627 std::string error_msg(e.what());
3628 DOCTEST_CHECK(error_msg.find(
"cannot be empty") != std::string::npos);
3630 DOCTEST_CHECK(exception_thrown);
3634 DOCTEST_SUBCASE(
"Error: invalid global data label") {
3636 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
3642 std::vector<uint> uuids = {uuid};
3643 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
3644 std::vector<float> values = {0.0f};
3655 bool exception_thrown =
false;
3657 radiationmodel5.
runBand(
"PAR");
3658 }
catch (
const std::runtime_error &e) {
3659 exception_thrown =
true;
3660 std::string error_msg(e.what());
3661 DOCTEST_CHECK(error_msg.find(
"does not exist") != std::string::npos);
3663 DOCTEST_CHECK(exception_thrown);
3667 DOCTEST_SUBCASE(
"Error: wrong global data type") {
3669 RadiationModel radiationmodel6 = RadiationModelTestHelper::createWithSharedDevice(&context6);
3677 std::vector<uint> uuids = {uuid};
3678 std::vector<std::string> spectra = {
"wrong_type"};
3679 std::vector<float> values = {0.0f};
3690 bool exception_thrown =
false;
3692 radiationmodel6.
runBand(
"PAR");
3693 }
catch (
const std::runtime_error &e) {
3694 exception_thrown =
true;
3695 std::string error_msg(e.what());
3696 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_VEC2") != std::string::npos);
3698 DOCTEST_CHECK(exception_thrown);
3702 DOCTEST_SUBCASE(
"Invalid UUID is silently skipped") {
3704 RadiationModel radiationmodel7 = RadiationModelTestHelper::createWithSharedDevice(&context7);
3712 std::vector<uint> uuids = {valid_uuid, 99999};
3713 std::vector<std::string> spectra = {
"spec"};
3714 std::vector<float> values = {0.0f};
3725 radiationmodel7.
runBand(
"PAR");
3728 std::string assigned_spectrum;
3729 context7.
getPrimitiveData(valid_uuid,
"reflectivity_spectrum", assigned_spectrum);
3730 DOCTEST_CHECK(assigned_spectrum ==
"spec");
3734 DOCTEST_SUBCASE(
"Error: wrong primitive data type for query") {
3736 RadiationModel radiationmodel8 = RadiationModelTestHelper::createWithSharedDevice(&context8);
3744 std::vector<uint> uuids = {uuid};
3745 std::vector<std::string> spectra = {
"spec"};
3746 std::vector<float> values = {0.0f};
3757 bool exception_thrown =
false;
3759 radiationmodel8.
runBand(
"PAR");
3760 }
catch (
const std::runtime_error &e) {
3761 exception_thrown =
true;
3762 std::string error_msg(e.what());
3763 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_FLOAT") != std::string::npos);
3765 DOCTEST_CHECK(exception_thrown);
3769 DOCTEST_SUBCASE(
"Primitive without query data is skipped") {
3771 RadiationModel radiationmodel9 = RadiationModelTestHelper::createWithSharedDevice(&context9);
3783 std::vector<uint> uuids = {uuid_with_data, uuid_without_data};
3784 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3785 std::vector<float> values = {0.0f, 10.0f};
3793 radiationmodel9.
runBand(
"PAR");
3796 std::string assigned_spectrum;
3797 context9.
getPrimitiveData(uuid_with_data,
"reflectivity_spectrum", assigned_spectrum);
3798 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
3803 context9.
getPrimitiveData(uuid_without_data,
"reflectivity_spectrum", assigned_spectrum);
3809GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation from Object Data") {
3812 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
3816 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3817 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3818 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3839 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3840 std::vector<uint> obj_ids = {obj0, obj1, obj2, obj3, obj4};
3841 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3842 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3851 radiationmodel.
runBand(
"PAR");
3854 std::string assigned_spectrum;
3856 for (
uint uuid: prim_uuids0) {
3857 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3858 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3862 for (
uint uuid: prim_uuids1) {
3863 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3864 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3868 for (
uint uuid: prim_uuids2) {
3869 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3870 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3874 for (
uint uuid: prim_uuids3) {
3875 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3876 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3880 for (
uint uuid: prim_uuids4) {
3881 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3882 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3887 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3889 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
3901 std::vector<uint> obj_ids = {obj_a, obj_b};
3902 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3903 std::vector<float> values = {0.0f, 10.0f};
3911 radiationmodel2.
runBand(
"PAR");
3913 std::string assigned_spectrum;
3915 for (
uint uuid: prim_uuids_a) {
3916 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3917 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3921 for (
uint uuid: prim_uuids_b) {
3922 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3923 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3928 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3930 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
3934 std::vector<uint> obj_ids = {obj_test};
3935 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3936 std::vector<float> values = {0.0f};
3938 bool caught_error =
false;
3941 }
catch (
const std::exception &e) {
3942 caught_error =
true;
3944 DOCTEST_CHECK(caught_error);
3948 DOCTEST_SUBCASE(
"Error: empty spectra vector") {
3950 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
3954 std::vector<uint> obj_ids = {obj_test};
3955 std::vector<std::string> spectra;
3956 std::vector<float> values;
3958 bool caught_error =
false;
3961 }
catch (
const std::exception &e) {
3962 caught_error =
true;
3964 DOCTEST_CHECK(caught_error);
3968 DOCTEST_SUBCASE(
"Error: empty object_IDs vector") {
3970 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
3973 std::vector<uint> obj_ids;
3974 std::vector<std::string> spectra = {
"spec1"};
3975 std::vector<float> values = {0.0f};
3977 bool caught_error =
false;
3980 }
catch (
const std::exception &e) {
3981 caught_error =
true;
3983 DOCTEST_CHECK(caught_error);
3987 DOCTEST_SUBCASE(
"Error: empty query label") {
3989 RadiationModel radiationmodel6 = RadiationModelTestHelper::createWithSharedDevice(&context6);
3993 std::vector<uint> obj_ids = {obj_test};
3994 std::vector<std::string> spectra = {
"spec1"};
3995 std::vector<float> values = {0.0f};
3997 bool caught_error =
false;
4000 }
catch (
const std::exception &e) {
4001 caught_error =
true;
4003 DOCTEST_CHECK(caught_error);
4007 DOCTEST_SUBCASE(
"Error: empty target label") {
4009 RadiationModel radiationmodel7 = RadiationModelTestHelper::createWithSharedDevice(&context7);
4013 std::vector<uint> obj_ids = {obj_test};
4014 std::vector<std::string> spectra = {
"spec1"};
4015 std::vector<float> values = {0.0f};
4017 bool caught_error =
false;
4020 }
catch (
const std::exception &e) {
4021 caught_error =
true;
4023 DOCTEST_CHECK(caught_error);
4027 DOCTEST_SUBCASE(
"Graceful skip: object without query data") {
4029 RadiationModel radiationmodel8 = RadiationModelTestHelper::createWithSharedDevice(&context8);
4040 std::vector<uint> obj_ids = {obj_with_data, obj_without_data};
4041 std::vector<std::string> spectra = {
"spec1"};
4042 std::vector<float> values = {5.0f};
4050 radiationmodel8.
runBand(
"PAR");
4052 std::string assigned_spectrum;
4054 for (
uint uuid: prim_uuids_with) {
4055 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4056 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4061 for (
uint uuid: prim_uuids_without) {
4063 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4069 DOCTEST_SUBCASE(
"Graceful skip: invalid object ID") {
4071 RadiationModel radiationmodel9 = RadiationModelTestHelper::createWithSharedDevice(&context9);
4082 std::vector<uint> obj_ids = {obj_valid, obj_to_delete};
4083 std::vector<std::string> spectra = {
"spec1"};
4084 std::vector<float> values = {5.0f};
4095 radiationmodel9.
runBand(
"PAR");
4097 std::string assigned_spectrum;
4099 for (
uint uuid: prim_uuids_valid) {
4100 context9.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4101 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4106 DOCTEST_SUBCASE(
"Error: wrong object data type") {
4108 RadiationModel radiationmodel10 = RadiationModelTestHelper::createWithSharedDevice(&context10);
4116 std::vector<uint> obj_ids = {obj_test};
4117 std::vector<std::string> spectra = {
"spec1"};
4118 std::vector<float> values = {5.0f};
4127 bool caught_error =
false;
4129 radiationmodel10.
runBand(
"PAR");
4130 }
catch (
const std::exception &e) {
4131 caught_error =
true;
4133 DOCTEST_CHECK(caught_error);
4137 DOCTEST_SUBCASE(
"Error: invalid global data") {
4139 RadiationModel radiationmodel11 = RadiationModelTestHelper::createWithSharedDevice(&context11);
4145 std::vector<uint> obj_ids = {obj_test};
4146 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
4147 std::vector<float> values = {5.0f};
4156 bool caught_error =
false;
4158 radiationmodel11.
runBand(
"PAR");
4159 }
catch (
const std::exception &e) {
4160 caught_error =
true;
4162 DOCTEST_CHECK(caught_error);
4166 DOCTEST_SUBCASE(
"Error: wrong global data type") {
4168 RadiationModel radiationmodel12 = RadiationModelTestHelper::createWithSharedDevice(&context12);
4176 std::vector<uint> obj_ids = {obj_test};
4177 std::vector<std::string> spectra = {
"wrong_type"};
4178 std::vector<float> values = {5.0f};
4187 bool caught_error =
false;
4189 radiationmodel12.
runBand(
"PAR");
4190 }
catch (
const std::exception &e) {
4191 caught_error =
true;
4193 DOCTEST_CHECK(caught_error);
4197GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation - Duplicate Handling") {
4200 DOCTEST_SUBCASE(
"Primitive: Merge duplicates with matching spectra") {
4202 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
4205 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4206 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4228 radiationmodel.
runBand(
"PAR");
4231 std::string assigned_spectrum;
4232 context.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4233 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4235 context.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4236 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4238 context.
getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
4239 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4243 DOCTEST_SUBCASE(
"Primitive: Replace config with different spectra") {
4245 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
4248 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4249 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4250 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4271 radiationmodel2.
runBand(
"PAR");
4274 std::string assigned_spectrum;
4275 context2.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4276 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4278 context2.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4279 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4283 DOCTEST_SUBCASE(
"Object: Merge duplicates with matching spectra") {
4285 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
4288 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4289 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4311 radiationmodel3.
runBand(
"PAR");
4314 std::string assigned_spectrum;
4316 for (
uint uuid: prim_uuids0) {
4317 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4318 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4322 for (
uint uuid: prim_uuids1) {
4323 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4324 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4328 for (
uint uuid: prim_uuids2) {
4329 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4330 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4335 DOCTEST_SUBCASE(
"Object: Replace config with different spectra") {
4337 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
4340 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4341 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4342 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4363 radiationmodel4.
runBand(
"PAR");
4366 std::string assigned_spectrum;
4368 for (
uint uuid: prim_uuids0) {
4369 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4370 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4374 for (
uint uuid: prim_uuids1) {
4375 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4376 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4381 DOCTEST_SUBCASE(
"Primitive: Separate configs for different labels") {
4383 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
4386 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4387 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4404 radiationmodel5.
runBand(
"PAR");
4407 std::string assigned_spectrum_rho;
4408 std::string assigned_spectrum_tau;
4409 context5.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum_rho);
4410 context5.
getPrimitiveData(uuid0,
"transmissivity_spectrum", assigned_spectrum_tau);
4412 DOCTEST_CHECK(assigned_spectrum_rho ==
"spec1");
4413 DOCTEST_CHECK(assigned_spectrum_tau ==
"spec2");
4417GPU_TEST_CASE(
"RadiationModel - Camera Metadata Export") {
4425 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
4442 DOCTEST_SUBCASE(
"Auto-populate metadata with custom sensor size") {
4448 camera_props.
HFOV = 45.0f;
4460 DOCTEST_CHECK(metadata.camera_properties.
width == 512);
4461 DOCTEST_CHECK(metadata.camera_properties.
height == 512);
4462 DOCTEST_CHECK(metadata.camera_properties.
channels == 3);
4465 DOCTEST_CHECK(metadata.camera_properties.
sensor_width == 24.0f);
4466 DOCTEST_CHECK(metadata.camera_properties.
sensor_height == doctest::Approx(24.0f).epsilon(0.01));
4469 float expected_focal_length = 24.0f / (2.0f * tan(45.0f *
M_PI / 180.0f / 2.0f));
4470 DOCTEST_CHECK(metadata.camera_properties.
focal_length == doctest::Approx(expected_focal_length).epsilon(0.01));
4473 float lens_diameter_mm = 0.05f * 1000.0f;
4474 float expected_f_number = expected_focal_length / lens_diameter_mm;
4475 std::ostringstream expected_aperture;
4476 expected_aperture <<
"f/" << std::fixed << std::setprecision(1) << expected_f_number;
4477 DOCTEST_CHECK(metadata.camera_properties.
aperture == expected_aperture.str());
4480 DOCTEST_CHECK(metadata.camera_properties.
model ==
"generic");
4483 DOCTEST_CHECK(metadata.location_properties.
latitude == doctest::Approx(34.0522).epsilon(0.0001));
4484 DOCTEST_CHECK(metadata.location_properties.
longitude == doctest::Approx(-118.2437).epsilon(0.0001));
4487 DOCTEST_CHECK(metadata.acquisition_properties.
date ==
"2025-09-30");
4488 DOCTEST_CHECK(metadata.acquisition_properties.
time ==
"10:30:00");
4489 DOCTEST_CHECK(metadata.acquisition_properties.
UTC_offset == 8.0f);
4490 DOCTEST_CHECK(metadata.acquisition_properties.
camera_height_m == 2.0f);
4495 DOCTEST_CHECK(metadata.acquisition_properties.
camera_angle_deg == doctest::Approx(21.8).epsilon(0.5));
4498 DOCTEST_CHECK(metadata.acquisition_properties.
light_source ==
"sunlight");
4501 DOCTEST_CHECK(metadata.
path ==
"");
4504 DOCTEST_SUBCASE(
"Pinhole camera aperture") {
4509 camera_props.
HFOV = 30.0f;
4519 DOCTEST_CHECK(metadata.camera_properties.
aperture ==
"pinhole");
4522 DOCTEST_SUBCASE(
"Light source detection") {
4525 camera_props.
HFOV = 20.0f;
4529 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
4537 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"none");
4544 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"sunlight");
4551 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"mixed");
4554 DOCTEST_SUBCASE(
"Set metadata and automatic JSON export") {
4557 camera_props.
HFOV = 35.0f;
4560 radiationmodel.
addRadiationCamera(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -3, 1.5),
make_vec3(0, 0, 0), camera_props, 1);
4567 radiationmodel.
runBand(
"RGB_R");
4568 radiationmodel.
runBand(
"RGB_G");
4569 radiationmodel.
runBand(
"RGB_B");
4572 std::string image_path = radiationmodel.
writeCameraImage(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_metadata");
4575 DOCTEST_CHECK(!image_path.empty());
4576 DOCTEST_CHECK(image_path.find(
".jpeg") != std::string::npos);
4579 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4580 std::ifstream json_file(json_path);
4581 DOCTEST_CHECK(json_file.is_open());
4583 if (json_file.is_open()) {
4590 DOCTEST_CHECK(j.contains(
"path"));
4591 DOCTEST_CHECK(j.contains(
"camera_properties"));
4592 DOCTEST_CHECK(j.contains(
"location_properties"));
4593 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
4596 DOCTEST_CHECK(j[
"camera_properties"].contains(
"height"));
4597 DOCTEST_CHECK(j[
"camera_properties"].contains(
"width"));
4598 DOCTEST_CHECK(j[
"camera_properties"].contains(
"channels"));
4599 DOCTEST_CHECK(j[
"camera_properties"].contains(
"focal_length"));
4600 DOCTEST_CHECK(j[
"camera_properties"].contains(
"aperture"));
4601 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_width"));
4602 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_height"));
4603 DOCTEST_CHECK(j[
"camera_properties"].contains(
"model"));
4606 DOCTEST_CHECK(j[
"camera_properties"][
"width"] == 256);
4607 DOCTEST_CHECK(j[
"camera_properties"][
"height"] == 256);
4608 DOCTEST_CHECK(j[
"camera_properties"][
"channels"] == 3);
4609 DOCTEST_CHECK(j[
"camera_properties"][
"model"] ==
"generic");
4612 size_t last_slash = image_path.find_last_of(
"/\\");
4613 std::string expected_filename = (last_slash != std::string::npos) ? image_path.substr(last_slash + 1) : image_path;
4614 DOCTEST_CHECK(j[
"path"] == expected_filename);
4617 std::remove(image_path.c_str());
4618 std::remove(json_path.c_str());
4622 DOCTEST_SUBCASE(
"Manual metadata population") {
4630 metadata.camera_properties.
width = 128;
4631 metadata.camera_properties.
height = 128;
4632 metadata.camera_properties.
channels = 1;
4634 metadata.camera_properties.
aperture =
"f/1.8";
4637 metadata.camera_properties.
model =
"Nikon D700";
4639 metadata.location_properties.
latitude = 40.0f;
4640 metadata.location_properties.
longitude = -75.0f;
4642 metadata.acquisition_properties.
date =
"2025-01-01";
4643 metadata.acquisition_properties.
time =
"12:00:00";
4644 metadata.acquisition_properties.
UTC_offset = 5.0f;
4647 metadata.acquisition_properties.
light_source =
"artificial";
4654 DOCTEST_CHECK(
true);
4657 DOCTEST_SUBCASE(
"Enable metadata for multiple cameras with vector") {
4660 camera_props.
HFOV = 30.0f;
4663 radiationmodel.
addRadiationCamera(
"camera_A", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4664 radiationmodel.
addRadiationCamera(
"camera_B", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(2, 0, 1),
make_vec3(0, 0, 0), camera_props, 1);
4665 radiationmodel.
addRadiationCamera(
"camera_C", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, 2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4668 std::vector<std::string> camera_labels = {
"camera_A",
"camera_B",
"camera_C"};
4673 radiationmodel.
runBand(
"RGB_R");
4674 radiationmodel.
runBand(
"RGB_G");
4675 radiationmodel.
runBand(
"RGB_B");
4678 std::vector<std::string> image_paths;
4679 std::vector<std::string> json_paths;
4681 for (
const auto &label: camera_labels) {
4682 std::string image_path = radiationmodel.
writeCameraImage(label, {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_vector");
4683 DOCTEST_CHECK(!image_path.empty());
4685 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4686 std::ifstream json_file(json_path);
4687 DOCTEST_CHECK(json_file.is_open());
4690 image_paths.push_back(image_path);
4691 json_paths.push_back(json_path);
4695 for (
size_t i = 0; i < image_paths.size(); i++) {
4696 std::remove(image_paths[i].c_str());
4697 std::remove(json_paths[i].c_str());
4701 DOCTEST_SUBCASE(
"applyCameraImageCorrections stores parameters in metadata") {
4707 camera_props.
HFOV = 45.0f;
4710 radiationmodel.
addRadiationCamera(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4717 radiationmodel.
runBand(
"RGB_R");
4718 radiationmodel.
runBand(
"RGB_G");
4719 radiationmodel.
runBand(
"RGB_B");
4722 float saturation = 1.5f;
4723 float brightness = 1.2f;
4724 float contrast = 1.1f;
4728 std::string image_path = radiationmodel.
writeCameraImage(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_corrections");
4730 DOCTEST_CHECK(!image_path.empty());
4733 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4734 std::ifstream json_file(json_path);
4735 DOCTEST_CHECK(json_file.is_open());
4737 if (json_file.is_open()) {
4743 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
4744 DOCTEST_CHECK(j.contains(
"image_processing"));
4747 auto &img_proc = j[
"image_processing"];
4748 DOCTEST_CHECK(img_proc.contains(
"saturation_adjustment"));
4749 DOCTEST_CHECK(img_proc.contains(
"brightness_adjustment"));
4750 DOCTEST_CHECK(img_proc.contains(
"contrast_adjustment"));
4751 DOCTEST_CHECK(img_proc.contains(
"color_space"));
4753 DOCTEST_CHECK(img_proc[
"saturation_adjustment"].get<double>() == doctest::Approx(saturation).epsilon(0.01));
4754 DOCTEST_CHECK(img_proc[
"brightness_adjustment"].get<double>() == doctest::Approx(brightness).epsilon(0.01));
4755 DOCTEST_CHECK(img_proc[
"contrast_adjustment"].get<double>() == doctest::Approx(contrast).epsilon(0.01));
4756 DOCTEST_CHECK(img_proc[
"color_space"].get<std::string>() ==
"sRGB");
4759 std::remove(image_path.c_str());
4760 std::remove(json_path.c_str());
4765GPU_TEST_CASE(
"RadiationModel - Camera Metadata Agronomic Properties") {
4773 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
4792 DOCTEST_SUBCASE(
"Agronomic properties with multiple species and weeds") {
4796 context.
setObjectData(bean_obj_1,
"plant_name", std::string(
"bean"));
4798 context.
setObjectData(bean_obj_1,
"plant_type", std::string(
"crop"));
4801 context.
setObjectData(bean_obj_1,
"phenology_stage", std::string(
"flowering"));
4805 context.
setObjectData(bean_obj_2,
"plant_name", std::string(
"bean"));
4807 context.
setObjectData(bean_obj_2,
"plant_type", std::string(
"crop"));
4810 context.
setObjectData(bean_obj_2,
"phenology_stage", std::string(
"flowering"));
4814 context.
setObjectData(bean_obj_3,
"plant_name", std::string(
"bean"));
4816 context.
setObjectData(bean_obj_3,
"plant_type", std::string(
"crop"));
4819 context.
setObjectData(bean_obj_3,
"phenology_stage", std::string(
"flowering"));
4824 context.
setObjectData(weed_obj_1,
"plant_name", std::string(
"pigweed"));
4826 context.
setObjectData(weed_obj_1,
"plant_type", std::string(
"weed"));
4829 context.
setObjectData(weed_obj_1,
"phenology_stage", std::string(
"vegetative"));
4830 context.
setObjectData(weed_obj_1,
"reflectivity_SW", 0.25f);
4833 context.
setObjectData(weed_obj_2,
"plant_name", std::string(
"pigweed"));
4835 context.
setObjectData(weed_obj_2,
"plant_type", std::string(
"weed"));
4838 context.
setObjectData(weed_obj_2,
"phenology_stage", std::string(
"vegetative"));
4839 context.
setObjectData(weed_obj_2,
"reflectivity_SW", 0.25f);
4844 camera_props.
HFOV = 60.0f;
4847 radiationmodel.
addRadiationCamera(
"test_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0.5, 0.25, 3.0),
make_vec3(0.5, 0.25, 0), camera_props, 1);
4851 radiationmodel.
runBand(
"RGB_R");
4852 radiationmodel.
runBand(
"RGB_G");
4853 radiationmodel.
runBand(
"RGB_B");
4859 DOCTEST_CHECK(!metadata.agronomic_properties.
plant_species.empty());
4860 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.size() == 2);
4864 int pigweed_idx = -1;
4865 for (
size_t i = 0; i < metadata.agronomic_properties.
plant_species.size(); i++) {
4866 if (metadata.agronomic_properties.
plant_species[i] ==
"bean") {
4867 bean_idx =
static_cast<int>(i);
4868 }
else if (metadata.agronomic_properties.
plant_species[i] ==
"pigweed") {
4869 pigweed_idx =
static_cast<int>(i);
4873 DOCTEST_CHECK(bean_idx >= 0);
4874 DOCTEST_CHECK(pigweed_idx >= 0);
4877 if (bean_idx >= 0) {
4878 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[bean_idx] == 3);
4880 if (pigweed_idx >= 0) {
4881 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[pigweed_idx] == 2);
4885 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"moderate");
4888 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m.size() == 2);
4889 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days.size() == 2);
4890 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage.size() == 2);
4891 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2.size() == 2);
4896 if (bean_idx >= 0) {
4897 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] > 0.40f);
4898 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] < 0.52f);
4900 if (pigweed_idx >= 0) {
4901 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] > 0.28f);
4902 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] < 0.37f);
4908 if (bean_idx >= 0) {
4909 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] > 27.0f);
4910 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] < 33.0f);
4912 if (pigweed_idx >= 0) {
4913 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] > 14.0f);
4914 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] < 19.0f);
4920 if (bean_idx >= 0) {
4921 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[bean_idx] ==
"flowering");
4923 if (pigweed_idx >= 0) {
4924 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[pigweed_idx] ==
"vegetative");
4928 if (bean_idx >= 0) {
4929 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[bean_idx] > 0.0f);
4931 if (pigweed_idx >= 0) {
4932 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[pigweed_idx] > 0.0f);
4936 DOCTEST_SUBCASE(
"Agronomic properties with low weed pressure") {
4938 for (
int i = 0; i < 10; i++) {
4940 context.
setObjectData(crop_obj,
"plant_name", std::string(
"soybean"));
4942 context.
setObjectData(crop_obj,
"plant_type", std::string(
"crop"));
4947 context.
setObjectData(weed_obj,
"plant_name", std::string(
"lambsquarter"));
4949 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
4954 camera_props.
HFOV = 90.0f;
4959 radiationmodel.
runBand(
"RGB_R");
4965 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"low");
4968 DOCTEST_SUBCASE(
"Agronomic properties with high weed pressure") {
4971 context.
setObjectData(crop_obj_1,
"plant_name", std::string(
"corn"));
4973 context.
setObjectData(crop_obj_1,
"plant_type", std::string(
"crop"));
4977 context.
setObjectData(crop_obj_2,
"plant_name", std::string(
"corn"));
4979 context.
setObjectData(crop_obj_2,
"plant_type", std::string(
"crop"));
4982 for (
int i = 0; i < 3; i++) {
4984 context.
setObjectData(weed_obj,
"plant_name", std::string(
"foxtail"));
4986 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
4992 camera_props.
HFOV = 60.0f;
4997 radiationmodel.
runBand(
"RGB_R");
5003 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"high");
5006 DOCTEST_SUBCASE(
"Agronomic properties with no plant data") {
5009 for (
const auto &uuid: patch_UUIDs) {
5015 camera_props.
HFOV = 45.0f;
5020 radiationmodel.
runBand(
"RGB_R");
5026 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.empty());
5027 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count.empty());
5028 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"");
5031 DOCTEST_SUBCASE(
"Agronomic properties JSON export") {
5034 context.
setObjectData(bean_obj,
"plant_name", std::string(
"bean"));
5036 context.
setObjectData(bean_obj,
"plant_type", std::string(
"crop"));
5040 context.
setObjectData(weed_obj,
"plant_name", std::string(
"weed"));
5042 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
5047 camera_props.
HFOV = 50.0f;
5049 radiationmodel.
addRadiationCamera(
"json_export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0.25, 0, 2.0),
make_vec3(0.25, 0, 0), camera_props, 1);
5052 radiationmodel.
runBand(
"RGB_R");
5053 radiationmodel.
runBand(
"RGB_G");
5054 radiationmodel.
runBand(
"RGB_B");
5060 std::string image_path = radiationmodel.
writeCameraImage(
"json_export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_agronomic");
5066 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
5067 std::ifstream json_file(json_path);
5068 DOCTEST_CHECK(json_file.is_open());
5070 if (json_file.is_open()) {
5076 DOCTEST_CHECK(j.contains(
"agronomic_properties"));
5078 if (j.contains(
"agronomic_properties")) {
5079 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_species"));
5080 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_count"));
5081 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"weed_pressure"));
5084 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_species"].is_array());
5085 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_count"].is_array());
5086 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"].is_string());
5089 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"] ==
"high");
5093 std::remove(image_path.c_str());
5094 std::remove(json_path.c_str());
5099GPU_TEST_CASE(
"RadiationModel - FOV_aspect_ratio Deprecation") {
5104 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
5110 DOCTEST_SUBCASE(
"Default FOV_aspect_ratio is auto-calculated") {
5114 camera_props.
HFOV = 45.0f;
5118 std::string stderr_output;
5124 DOCTEST_CHECK(stderr_output.empty());
5129 DOCTEST_CHECK(std::abs(expected_aspect - 1.333333f) < 0.0001f);
5132 DOCTEST_SUBCASE(
"Explicit FOV_aspect_ratio triggers deprecation warning") {
5136 camera_props.
HFOV = 50.0f;
5140 std::string stderr_output;
5146 DOCTEST_CHECK(stderr_output.find(
"WARNING") != std::string::npos);
5147 DOCTEST_CHECK(stderr_output.find(
"FOV_aspect_ratio") != std::string::npos);
5148 DOCTEST_CHECK(stderr_output.find(
"deprecated") != std::string::npos);
5149 DOCTEST_CHECK(stderr_output.find(
"auto-calculated") != std::string::npos);
5152 DOCTEST_SUBCASE(
"Auto-calculated value ensures square pixels") {
5154 std::vector<helios::int2> resolutions = {
5161 for (
const auto &resolution: resolutions) {
5164 camera_props.
HFOV = 60.0f;
5167 std::string camera_label =
"camera_" + std::to_string(resolution.
x) +
"x" + std::to_string(resolution.
y);
5170 std::string stderr_output;
5176 DOCTEST_CHECK(stderr_output.empty());
5181GPU_TEST_CASE(
"RadiationModel Atmospheric Sky Model for Camera") {
5192 float pressure_Pa = 95000.f;
5193 float temperature_K = 285.f;
5194 float humidity_rel = 0.6f;
5195 float turbidity = 0.08f;
5197 context.
setGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
5198 context.
setGlobalData(
"atmosphere_temperature_K", temperature_K);
5199 context.
setGlobalData(
"atmosphere_humidity_rel", humidity_rel);
5202 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
5205 DOCTEST_SUBCASE(
"Sky model requires wavelength bounds with uniform response") {
5221 camera_props.
HFOV = 60.0f;
5228 bool threw_error =
false;
5232 radiationmodel.
runBand(
"VIS");
5233 }
catch (std::runtime_error &e) {
5234 std::string error_msg = e.what();
5235 threw_error = (error_msg.find(
"wavelength bounds") != std::string::npos);
5238 DOCTEST_CHECK(threw_error);
5241 DOCTEST_SUBCASE(
"Sky model computed with camera and wavelength bounds") {
5260 camera_props.
HFOV = 60.0f;
5269 radiationmodel.
runBand(
"VIS");
5273 DOCTEST_CHECK(
true);
5276 DOCTEST_SUBCASE(
"Atmospheric parameters do not cause errors") {
5287 camera_props.
HFOV = 45.0f;
5291 radiationmodel.
runBand(
"VIS");
5295 float high_turbidity = 0.3f;
5296 context.
setGlobalData(
"atmosphere_turbidity", high_turbidity);
5298 radiationmodel.
runBand(
"VIS");
5301 DOCTEST_CHECK(
true);
5305GPU_TEST_CASE(
"RadiationModel - Camera White Balance") {
5308 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
5326 DOCTEST_SUBCASE(
"Default white_balance is 'auto'") {
5331 camera_props.
HFOV = 45.0f;
5336 radiationmodel.
addRadiationCamera(
"test_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5343 DOCTEST_SUBCASE(
"White balance mode 'off' preserves raw data") {
5348 camera_props.
HFOV = 45.0f;
5351 radiationmodel.
addRadiationCamera(
"camera_wb_off", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5355 radiationmodel.
runBand(
"RGB_R");
5356 radiationmodel.
runBand(
"RGB_G");
5357 radiationmodel.
runBand(
"RGB_B");
5361 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5364 DOCTEST_CHECK(
true);
5367 DOCTEST_SUBCASE(
"White balance mode 'auto' applies correction") {
5372 camera_props.
HFOV = 45.0f;
5375 radiationmodel.
addRadiationCamera(
"camera_wb_auto", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5379 radiationmodel.
runBand(
"RGB_R");
5380 radiationmodel.
runBand(
"RGB_G");
5381 radiationmodel.
runBand(
"RGB_B");
5385 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5388 DOCTEST_CHECK(
true);
5391 DOCTEST_SUBCASE(
"Single-channel camera skips white balance") {
5396 camera_props.
HFOV = 45.0f;
5403 radiationmodel.
runBand(
"RGB_R");
5407 DOCTEST_CHECK(metadata.camera_properties.
channels == 1);
5408 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5411 DOCTEST_CHECK(
true);
5414 DOCTEST_SUBCASE(
"Update camera white_balance parameter") {
5419 camera_props.
HFOV = 45.0f;
5422 radiationmodel.
addRadiationCamera(
"camera_update", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5435 radiationmodel.
runBand(
"RGB_R");
5436 radiationmodel.
runBand(
"RGB_G");
5437 radiationmodel.
runBand(
"RGB_B");
5441 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5444 DOCTEST_SUBCASE(
"CameraProperties equality includes white_balance") {
5452 DOCTEST_CHECK(props1 == props2);
5458 DOCTEST_CHECK(props1 != props2);
5462GPU_TEST_CASE(
"RadiationModel setDiffuseSpectrum and emission band behavior") {
5464 using namespace helios;
5469 std::vector<vec2> test_spectrum;
5470 test_spectrum.emplace_back(400.f, 1.0f);
5471 test_spectrum.emplace_back(500.f, 1.5f);
5472 test_spectrum.emplace_back(600.f, 1.0f);
5473 test_spectrum.emplace_back(700.f, 0.5f);
5476 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5479 DOCTEST_SUBCASE(
"setDiffuseSpectrum applies to all bands") {
5498 DOCTEST_CHECK(flux1 > 0.f);
5499 DOCTEST_CHECK(flux2 > 0.f);
5500 DOCTEST_CHECK(flux3 > 0.f);
5503 DOCTEST_SUBCASE(
"getDiffuseFlux returns 0 for emission-enabled bands with spectrum") {
5512 DOCTEST_CHECK(flux == 0.f);
5515 DOCTEST_SUBCASE(
"getDiffuseFlux returns manual flux for emission-enabled bands") {
5523 float manual_flux = 100.f;
5528 DOCTEST_CHECK(flux == manual_flux);
5531 DOCTEST_SUBCASE(
"Manual flux overrides spectrum for non-emission bands") {
5541 DOCTEST_CHECK(spectrum_flux > 0.f);
5544 float manual_flux = 999.f;
5548 DOCTEST_CHECK(flux == manual_flux);
5551 DOCTEST_SUBCASE(
"setDiffuseSpectrum with no bands does not error") {
5555 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5560 DOCTEST_CHECK(
true);
5563 DOCTEST_SUBCASE(
"setDiffuseSpectrum before bands are added applies to later bands") {
5567 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5585 DOCTEST_CHECK(flux1 > 0.f);
5586 DOCTEST_CHECK(flux2 > 0.f);
5589 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral scales global spectrum before bands are added") {
5593 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5598 float target_integral = 850.f;
5610 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5613 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral with wavelength bounds scales global spectrum") {
5617 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5622 float target_integral = 500.f;
5631 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5634 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral applies to existing bands") {
5640 float target_integral = 1000.f;
5644 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5650GPU_TEST_CASE(
"Radiation - Prague Context data fallback behavior") {
5652 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5669 camera_props.
HFOV = 45.0f;
5671 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
5678GPU_TEST_CASE(
"Radiation - Prague Context data integration end-to-end") {
5680 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5685 std::vector<float> spectral_params(225 * 6);
5686 for (
int i = 0; i < 225; ++i) {
5687 float wavelength = 360.0f + i * 5.0f;
5691 float rayleigh_factor = std::pow(550.0f / wavelength, 4.0f);
5693 spectral_params[base + 0] = wavelength;
5694 spectral_params[base + 1] = 0.3f * rayleigh_factor;
5695 spectral_params[base + 2] = 2.0f;
5696 spectral_params[base + 3] = 15.0f;
5697 spectral_params[base + 4] = 2.0f;
5698 spectral_params[base + 5] = 0.8f;
5701 context.
setGlobalData(
"prague_sky_spectral_params", spectral_params);
5709 DOCTEST_CHECK_NOTHROW(context.
getGlobalData(
"prague_sky_valid", valid));
5710 DOCTEST_CHECK(valid == 1);
5712 std::vector<float> read_params;
5713 DOCTEST_CHECK_NOTHROW(context.
getGlobalData(
"prague_sky_spectral_params", read_params));
5714 DOCTEST_CHECK(read_params.size() == 225 * 6);
5730 camera_props.
HFOV = 45.0f;
5732 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
5738GPU_TEST_CASE(
"RadiationModel Automatic Spectrum Update Detection") {
5741 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5745 std::vector<helios::vec2> direct_spectrum_v1 = {{300, 1.0}, {400, 2.0}, {500, 3.0}, {700, 2.0}, {800, 1.0}};
5746 context.
setGlobalData(
"test_direct_spectrum", direct_spectrum_v1);
5749 std::vector<helios::vec2> diffuse_spectrum_v1 = {{300, 0.5}, {400, 1.0}, {500, 1.5}, {700, 1.0}, {800, 0.5}};
5750 context.
setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v1);
5768 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"PAR"));
5772 DOCTEST_CHECK(flux_v1 > 0.0f);
5775 std::vector<helios::vec2> direct_spectrum_v2 = {{300, 2.0}, {400, 4.0}, {500, 6.0}, {700, 4.0}, {800, 2.0}};
5776 context.
setGlobalData(
"test_direct_spectrum", direct_spectrum_v2);
5779 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"PAR"));
5785 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.9f);
5786 DOCTEST_CHECK(flux_v2 < flux_v1 * 2.1f);
5789 std::vector<helios::vec2> diffuse_spectrum_v2 = {{300, 1.5}, {400, 3.0}, {500, 4.5}, {700, 3.0}, {800, 1.5}};
5790 context.
setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v2);
5793 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"PAR"));
5800 DOCTEST_CHECK(flux_v3 >= flux_v2 * 0.99f);
5803GPU_TEST_CASE(
"RadiationModel Multiple Sources Same Spectrum Update") {
5806 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5810 std::vector<helios::vec2> shared_spectrum = {{300, 1.0}, {800, 1.0}};
5817 for (
int i = 0; i < 3; i++) {
5829 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"test"));
5833 DOCTEST_CHECK(flux_v1 > 0.0f);
5836 std::vector<helios::vec2> updated_spectrum = {{300, 2.0}, {800, 2.0}};
5840 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"test"));
5846 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.8f);
5849GPU_TEST_CASE(
"RadiationModel No Update When Spectrum Unchanged") {
5852 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5856 std::vector<helios::vec2> spectrum = {{300, 1.0}, {800, 1.0}};
5867 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"test"));
5871 DOCTEST_CHECK_NOTHROW(radiation.
runBand(
"test"));
5875 DOCTEST_CHECK(flux > 0.0f);
5878DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties default camera_zoom") {
5883DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties equality with camera_zoom") {
5887 DOCTEST_CHECK(props1 == props2);
5890 DOCTEST_CHECK(props1 != props2);
5893 DOCTEST_CHECK(props1 == props2);
5896GPU_TEST_CASE(
"RadiationModel - camera_zoom validation in updateCameraParameters") {
5898 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5906 std::vector<std::string> bands = {
"R"};
5913 DOCTEST_CHECK_THROWS_WITH_AS(radiation.
updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
5917 DOCTEST_CHECK_THROWS_WITH_AS(radiation.
updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
5920GPU_TEST_CASE(
"RadiationModel - camera_zoom parameter get/set") {
5922 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5930 std::vector<std::string> bands = {
"R",
"G",
"B"};
5935 DOCTEST_CHECK(retrieved.
HFOV == 60.0f);
5938GPU_TEST_CASE(
"RadiationModel - update camera_zoom") {
5940 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5948 std::vector<std::string> bands = {
"R"};
5958 DOCTEST_CHECK(final_props.
HFOV == 45.0f);
5960GPU_TEST_CASE(
"Lens Flare - Enable/Disable API") {
5962 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5967 camera_props.
HFOV = 45.0f;
5968 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
5987GPU_TEST_CASE(
"Lens Flare - Properties API") {
5989 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
5994 camera_props.
HFOV = 45.0f;
5995 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6001 DOCTEST_CHECK(default_props.
ghost_intensity == doctest::Approx(1.0f));
6020 DOCTEST_CHECK(retrieved_props.
ghost_intensity == doctest::Approx(0.5f));
6029 invalid_props = default_props;
6034 invalid_props = default_props;
6039 invalid_props = default_props;
6044 invalid_props = default_props;
6049 invalid_props = default_props;
6054 invalid_props = default_props;
6059GPU_TEST_CASE(
"Lens Flare - Application to Camera Image") {
6061 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
6109 camera_props.
HFOV = 60.0f;
6111 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6123 radiation.
runBand({
"red",
"green",
"blue"});
6130 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"test_camera") != all_labels.end());
6133GPU_TEST_CASE(
"Lens Flare - Disabled Does Nothing") {
6135 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
6173 camera_props.
HFOV = 45.0f;
6174 radiation.
addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6178 radiation.
runBand({
"red",
"green",
"blue"});
6185GPU_TEST_CASE(
"RadiationModel - Camera Sphere Source Rendering") {
6187 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
6201 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6207 camera_props.
HFOV = 45.0f;
6212 radiation.
runBand(
"test_band");
6215 DOCTEST_REQUIRE(!pixel_data.empty());
6218 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6221GPU_TEST_CASE(
"RadiationModel - Camera Rectangle Source Rendering") {
6223 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
6237 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6243 camera_props.
HFOV = 45.0f;
6248 radiation.
runBand(
"test_band");
6251 DOCTEST_REQUIRE(!pixel_data.empty());
6254 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6257GPU_TEST_CASE(
"RadiationModel - Camera Disk Source Rendering") {
6259 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
6273 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6279 camera_props.
HFOV = 45.0f;
6284 radiation.
runBand(
"test_band");
6287 DOCTEST_REQUIRE(!pixel_data.empty());
6290 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6293GPU_TEST_CASE(
"RadiationModel - Camera Pixel UUID Indexing Validation") {
6311 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6316 cam_props.
HFOV = 90;
6336 std::string test_file =
"test_cam_test_camera_indexing_00000.txt";
6340 std::ifstream label_file(test_file);
6341 DOCTEST_REQUIRE_MESSAGE(label_file.is_open(),
"Could not open label map file");
6343 std::vector<float> labels;
6345 while (label_file >> val) {
6346 labels.push_back(val);
6350 DOCTEST_REQUIRE_EQ(labels.size(), 64 * 64);
6355 int left_votes = 0, center_votes = 0, right_votes = 0;
6356 for (
int j = 26; j < 38; j++) {
6357 for (
int i = 20; i < 25; i++) {
6358 float label = labels[j * 64 + i];
6361 else if (label == 2.0f)
6363 else if (label == 3.0f)
6369 DOCTEST_CHECK_MESSAGE(left_votes > right_votes,
"Left region should see world-left patch (ID=1), not world-right (ID=3)");
6370 DOCTEST_CHECK_MESSAGE(left_votes > center_votes,
"Left region should predominantly see left patch");
6373 left_votes = center_votes = right_votes = 0;
6374 for (
int j = 26; j < 38; j++) {
6375 for (
int i = 39; i < 44; i++) {
6376 float label = labels[j * 64 + i];
6379 else if (label == 2.0f)
6381 else if (label == 3.0f)
6387 DOCTEST_CHECK_MESSAGE(right_votes > left_votes,
"Right region should see world-right patch (ID=3), not world-left (ID=1)");
6388 DOCTEST_CHECK_MESSAGE(right_votes > center_votes,
"Right region should predominantly see right patch");
6391 std::remove(test_file.c_str());
6394GPU_TEST_CASE(
"RadiationModel - Pixel Labeling with Fine Tessellation") {
6402 float camera_height = 20.0f;
6403 float HFOV_degrees = 45.0f;
6406 float ground_size = 2.0f * camera_height * tanf(HFOV_degrees *
M_PI / 180.0f / 2.0f);
6414 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6419 cam_props.
HFOV = HFOV_degrees;
6439 std::string test_file =
"test_cam_test_fine_tessellation_00000.txt";
6442 std::ifstream label_file(test_file);
6443 DOCTEST_REQUIRE(label_file.is_open());
6445 std::vector<float> labels;
6447 while (label_file >> val) {
6448 labels.push_back(val);
6453 int valid_count = 0;
6455 for (
float label: labels) {
6456 if (std::isnan(label)) {
6458 }
else if (label == 42.0f) {
6463 float valid_percentage = 100.0f * valid_count / labels.size();
6466 DOCTEST_CHECK_MESSAGE(valid_percentage > 95.0f,
"Pixel labeling with fine tessellation should have >95% valid hits, got " << valid_percentage <<
"%");
6469 std::remove(test_file.c_str());
6472GPU_TEST_CASE(
"RadiationModel - runBand Invalid Band Error Handling") {
6475 DOCTEST_SUBCASE(
"Single invalid band") {
6477 RadiationModel radiation1 = RadiationModelTestHelper::createWithSharedDevice(&context1);
6487 bool exception_thrown =
false;
6488 std::string error_message;
6490 radiation1.
runBand(
"INVALID_BAND");
6491 }
catch (
const std::runtime_error &e) {
6492 exception_thrown =
true;
6493 error_message = e.what();
6494 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6495 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6496 }
catch (
const std::out_of_range &e) {
6498 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6500 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band");
6504 DOCTEST_SUBCASE(
"Mixed valid and invalid bands") {
6506 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
6518 std::vector<std::string> bands = {
"PAR",
"INVALID_BAND",
"NIR"};
6519 bool exception_thrown =
false;
6520 std::string error_message;
6523 }
catch (
const std::runtime_error &e) {
6524 exception_thrown =
true;
6525 error_message = e.what();
6526 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6527 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6528 }
catch (
const std::out_of_range &e) {
6530 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6532 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band in vector");
6536 DOCTEST_SUBCASE(
"All invalid bands") {
6538 RadiationModel radiation3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
6548 std::vector<std::string> bands = {
"INVALID1",
"INVALID2"};
6549 bool exception_thrown =
false;
6550 std::string error_message;
6553 }
catch (
const std::runtime_error &e) {
6554 exception_thrown =
true;
6555 error_message = e.what();
6557 bool found_invalid = error_message.find(
"INVALID1") != std::string::npos || error_message.find(
"INVALID2") != std::string::npos;
6558 DOCTEST_CHECK(found_invalid);
6559 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6560 }
catch (
const std::out_of_range &e) {
6562 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6564 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for all invalid bands");
6568GPU_TEST_CASE(
"RadiationModel - Segmentation Mask to Image Coordinate Alignment") {
6587 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6592 cam_props.
HFOV = 60;
6609 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"test_alignment",
"./");
6614 std::ifstream json_file(
"test_alignment_masks.json");
6615 DOCTEST_REQUIRE(json_file.is_open());
6617 std::stringstream buffer;
6618 buffer << json_file.rdbuf();
6621 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6624 std::vector<uint> pixel_UUIDs;
6625 context.
getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs);
6628 for (
const auto &ann: coco_json[
"annotations"]) {
6629 int bbox_x = ann[
"bbox"][0];
6630 int bbox_y = ann[
"bbox"][1];
6631 int bbox_w = ann[
"bbox"][2];
6632 int bbox_h = ann[
"bbox"][3];
6635 std::vector<int> seg_coords = ann[
"segmentation"][0];
6638 int sample_x = bbox_x + bbox_w / 2;
6639 int sample_y = bbox_y + bbox_h / 2;
6640 uint sample_UUID = pixel_UUIDs.at(sample_y * 128 + sample_x) - 1;
6647 int min_x = 128, max_x = 0, min_y = 128, max_y = 0;
6648 bool found_any =
false;
6650 for (
int j = 0; j < 128; j++) {
6651 for (
int i = 0; i < 128; i++) {
6652 uint UUID = pixel_UUIDs.at(j * 128 + i) - 1;
6653 if (UUID == sample_UUID) {
6654 min_x = std::min(min_x, i);
6655 max_x = std::max(max_x, i);
6656 min_y = std::min(min_y, j);
6657 max_y = std::max(max_y, j);
6665 DOCTEST_CHECK_MESSAGE(bbox_x <= min_x + 2,
"Bbox x-min should match or slightly exceed actual pixels");
6666 DOCTEST_CHECK_MESSAGE(bbox_x + bbox_w >= max_x - 2,
"Bbox x-max should match or slightly exceed actual pixels");
6667 DOCTEST_CHECK_MESSAGE(bbox_y <= min_y + 2,
"Bbox y-min should match or slightly exceed actual pixels");
6668 DOCTEST_CHECK_MESSAGE(bbox_y + bbox_h >= max_y - 2,
"Bbox y-max should match or slightly exceed actual pixels");
6673 std::remove(
"test_alignment_masks.json");
6674 std::remove(image_file.c_str());
6677GPU_TEST_CASE(
"RadiationModel - Mask Spatial Ordering Matches Image") {
6694 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6699 cam_props.
HFOV = 70;
6715 std::vector<uint> pixel_UUIDs_check;
6716 context.
getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs_check);
6718 for (
uint uuid: pixel_UUIDs_check) {
6725 DOCTEST_INFO(
"Pixels hitting patches with patch_id: " << patch_hits);
6726 DOCTEST_REQUIRE_MESSAGE(patch_hits > 0,
"Camera should hit at least some patches");
6729 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"spatial_test",
"./");
6733 std::ifstream json_file(
"spatial_test_masks.json");
6734 DOCTEST_REQUIRE(json_file.is_open());
6736 std::stringstream buffer;
6737 buffer << json_file.rdbuf();
6740 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6743 DOCTEST_INFO(
"Number of annotations: " << coco_json[
"annotations"].size());
6746 std::map<int, int> patch_center_x;
6748 for (
const auto &ann: coco_json[
"annotations"]) {
6749 int cat_id = ann[
"category_id"];
6750 int bbox_x = ann[
"bbox"][0];
6751 int bbox_w = ann[
"bbox"][2];
6752 int center_x = bbox_x + bbox_w / 2;
6758 patch_center_x[center_x] = center_x;
6762 DOCTEST_CHECK_EQ(coco_json[
"annotations"].size(), 3);
6765 std::vector<int> centers;
6766 for (
const auto &ann: coco_json[
"annotations"]) {
6767 int bbox_x = ann[
"bbox"][0];
6768 int bbox_w = ann[
"bbox"][2];
6769 centers.push_back(bbox_x + bbox_w / 2);
6771 std::sort(centers.begin(), centers.end());
6774 if (centers.size() == 3) {
6775 DOCTEST_CHECK_MESSAGE(centers[0] < centers[1],
"Left patch should be left of center patch");
6776 DOCTEST_CHECK_MESSAGE(centers[1] < centers[2],
"Center patch should be left of right patch");
6779 int spacing1 = centers[1] - centers[0];
6780 int spacing2 = centers[2] - centers[1];
6781 DOCTEST_CHECK_MESSAGE(spacing1 > 5,
"Patches should be visibly separated in x");
6782 DOCTEST_CHECK_MESSAGE(spacing2 > 5,
"Patches should be visibly separated in x");
6783 DOCTEST_CHECK_MESSAGE(abs(spacing1 - spacing2) < spacing1 * 0.5,
"Spacing should be roughly uniform");
6787 std::remove(
"spatial_test_masks.json");
6788 std::remove(image_file.c_str());
6791GPU_TEST_CASE(
"RadiationModel - Data Label Maps Match Segmentation Mask Coordinates") {
6817 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6822 cam_props.
HFOV = 90;
6838 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"coord_match_test",
"./");
6844 std::ifstream prim_file(
"test_cam_coord_match_primdata_00000.txt");
6845 DOCTEST_REQUIRE(prim_file.is_open());
6846 std::vector<float> prim_labels;
6848 while (prim_file >> val) {
6849 prim_labels.push_back(val);
6852 DOCTEST_REQUIRE_EQ(prim_labels.size(), 64 * 64);
6855 std::ifstream obj_file(
"test_cam_coord_match_objdata_00000.txt");
6856 DOCTEST_REQUIRE(obj_file.is_open());
6857 std::vector<float> obj_labels;
6858 while (obj_file >> val) {
6859 obj_labels.push_back(val);
6862 DOCTEST_REQUIRE_EQ(obj_labels.size(), 64 * 64);
6865 std::ifstream json_file(
"./coord_match_masks.json");
6866 DOCTEST_REQUIRE(json_file.is_open());
6867 std::stringstream buffer;
6868 buffer << json_file.rdbuf();
6870 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6874 int total_annotations = coco_json[
"annotations"].size();
6875 DOCTEST_REQUIRE_MESSAGE(total_annotations == 3,
"Should have 3 annotations, got " << total_annotations);
6883 std::vector<std::tuple<int, int, int, int, int>> ann_data;
6884 for (
size_t idx = 0; idx < coco_json[
"annotations"].size(); idx++) {
6885 const auto &ann = coco_json[
"annotations"][idx];
6886 ann_data.push_back({ann[
"bbox"][0].get<
int>(), ann[
"bbox"][1].get<int>(), ann[
"bbox"][2].get<
int>(), ann[
"bbox"][3].get<int>(),
static_cast<int>(idx)});
6888 std::sort(ann_data.begin(), ann_data.end());
6892 std::vector<uint> expected_obj_ids = {100, 200, 300};
6894 for (
size_t i = 0; i < ann_data.size(); i++) {
6895 auto [bbox_x, bbox_y, bbox_w, bbox_h, ann_idx] = ann_data[i];
6896 uint expected_obj_value = expected_obj_ids[i];
6899 int correct_value_count = 0;
6900 int total_pixels = 0;
6902 for (
int dy = 0; dy < bbox_h; dy++) {
6903 for (
int dx = 0; dx < bbox_w; dx++) {
6904 int px = bbox_x + dx;
6905 int py = bbox_y + dy;
6907 if (px < 0 || px >= 64 || py < 0 || py >= 64)
6910 float obj_value = obj_labels[py * 64 + px];
6913 if (fabs(obj_value - expected_obj_value) < 1.0f) {
6914 correct_value_count++;
6920 float match_percentage = 100.0f * correct_value_count / total_pixels;
6923 int center_x = bbox_x + bbox_w / 2;
6924 int center_y = bbox_y + bbox_h / 2;
6925 float sample_actual_value = obj_labels[center_y * 64 + center_x];
6928 DOCTEST_CHECK_MESSAGE(match_percentage > 80.0f,
"At least 80% of bbox pixels should have CORRECT data value in label map. "
6929 "If this fails, label map coordinates are flipped relative to mask. Got "
6930 << match_percentage <<
"%");
6934 std::remove(
"test_cam_coord_match_primdata_00000.txt");
6935 std::remove(
"test_cam_coord_match_objdata_00000.txt");
6936 std::remove(
"./coord_match_masks.json");
6937 std::remove(image_file.c_str());
6940GPU_TEST_CASE(
"RadiationModel - Pixel Label UUID Mapping With Non-Sequential Object Ordering") {
6956 std::vector<uint> obj_patch_UUIDs;
6957 for (
int i = 0; i < 9; i++) {
6958 float x = -1.5f + (i % 3) * 0.5f;
6959 float y = (i / 3) * 0.5f - 0.5f;
6970 DOCTEST_REQUIRE_GT(orphan_UUID, obj_patch_UUIDs.back());
6977 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
6982 cam_props.
HFOV = 90;
7000 std::ifstream label_file(
"test_cam_uuid_mapping_test_00000.txt");
7001 DOCTEST_REQUIRE(label_file.is_open());
7002 std::vector<float> labels;
7004 while (label_file >> val) {
7005 labels.push_back(val);
7008 DOCTEST_REQUIRE_EQ(labels.size(), 64u * 64u);
7013 int left_polymesh = 0, left_orphan = 0;
7014 int right_polymesh = 0, right_orphan = 0;
7016 for (
int j = 0; j < 64; j++) {
7017 for (
int i = 0; i < 64; i++) {
7018 float label = labels[j * 64 + i];
7019 if (std::isnan(label))
continue;
7021 uint label_uint =
static_cast<uint>(label);
7022 bool is_left = (i < 32);
7025 if (label_uint == 1u) left_polymesh++;
7026 else if (label_uint == 2u) left_orphan++;
7028 if (label_uint == 1u) right_polymesh++;
7029 else if (label_uint == 2u) right_orphan++;
7036 DOCTEST_CHECK_MESSAGE(left_polymesh > 0,
"Polymesh patches (element_type=1) should appear on the left side of the image");
7037 DOCTEST_CHECK_MESSAGE(right_orphan > 0,
"Orphan patch (element_type=2) should appear on the right side of the image");
7038 DOCTEST_CHECK_MESSAGE(left_orphan == 0,
7039 "No orphan labels should appear on the left side (got " << left_orphan <<
" — indicates UUID mapping error)");
7040 DOCTEST_CHECK_MESSAGE(right_polymesh == 0,
7041 "No polymesh labels should appear on the right side (got " << right_polymesh <<
" — indicates UUID mapping error)");
7043 std::remove(
"test_cam_uuid_mapping_test_00000.txt");
7046GPU_TEST_CASE(
"Material Backend Migration - Spectrum Interpolation Integration") {
7050 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
7054 std::vector<helios::vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
7055 std::vector<helios::vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
7065 std::vector<uint> uuids = {uuid};
7066 std::vector<std::string> spectra = {
"rho_young",
"rho_old"};
7067 std::vector<float> values = {0.0f, 10.0f};
7081 radiationmodel.
runBand(
"PAR");
7084 std::string assigned_spectrum;
7086 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
7087 DOCTEST_CHECK(assigned_spectrum ==
"rho_old");
7090GPU_TEST_CASE(
"Material Backend Migration - Camera Weighted Materials") {
7094 RadiationModel radiationmodel = RadiationModelTestHelper::createWithSharedDevice(&context);
7098 std::vector<helios::vec2> object_spectrum = {{400, 0.1}, {500, 0.3}, {600, 0.5}, {700, 0.7}};
7102 std::vector<helios::vec2> camera_response = {{400, 0.2}, {500, 0.8}, {600, 0.8}, {700, 0.2}};
7106 std::vector<helios::vec2> source_spectrum = {{400, 0.8}, {500, 1.0}, {600, 1.0}, {700, 0.9}};
7111 context.
setPrimitiveData(uuid,
"reflectivity_spectrum", std::string(
"object_rho"));
7126 cam_props.
HFOV = 45.f;
7130 std::vector<std::string> band_labels = {
"VIS"};
7131 radiationmodel.
addRadiationCamera(
"test_cam", band_labels, helios::make_vec3(0, -5, 0), helios::make_vec3(0, 0, 0), cam_props, 1);
7136 radiationmodel.
runBand(
"VIS");
7143 std::vector<float> camera_data;
7145 DOCTEST_CHECK(camera_data.size() == 100);
7149GPU_TEST_CASE(
"RadiationModel - Specular Reflection Camera Rendering") {
7154 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
7162 std::vector<helios::vec2> reflectivity = {
make_vec2(400, 0.05f),
make_vec2(700, 0.05f)};
7166 std::vector<helios::vec2> zero_transmissivity = {
make_vec2(400, 0.0f),
make_vec2(700, 0.0f)};
7167 context.
setGlobalData(
"zero_transmissivity", zero_transmissivity);
7168 context.
setPrimitiveData(UUID,
"transmissivity_spectrum",
"zero_transmissivity");
7188 cam_props.
HFOV = 30.0f;
7189 radiation.
addRadiationCamera(
"test_cam", {
"SUN"}, camera_pos, camera_lookat, cam_props, 100);
7191 std::vector<helios::vec2> camera_response = {
make_vec2(400, 1.0f),
make_vec2(700, 1.0f)};
7203 std::vector<float> pixels_no_specular;
7204 context.
getGlobalData(
"camera_test_cam_SUN", pixels_no_specular);
7206 float sum_no_specular = 0.0f;
7207 for (
float p: pixels_no_specular) {
7208 sum_no_specular += p;
7210 float avg_no_specular = sum_no_specular / (float)pixels_no_specular.size();
7220 std::vector<float> pixels_with_specular;
7221 context.
getGlobalData(
"camera_test_cam_SUN", pixels_with_specular);
7223 float sum_with_specular = 0.0f;
7224 for (
float p: pixels_with_specular) {
7225 sum_with_specular += p;
7227 float avg_with_specular = sum_with_specular / (float)pixels_with_specular.size();
7229 float difference = avg_with_specular - avg_no_specular;
7231 DOCTEST_MESSAGE(
"No specular avg: " << avg_no_specular <<
", With specular avg: " << avg_with_specular <<
", Difference: " << difference);
7234 DOCTEST_CHECK_MESSAGE(difference > 5.0f,
"Specular exponent should increase camera intensity. "
7236 << avg_no_specular <<
", With specular: " << avg_with_specular <<
", Difference: " << difference);
7239GPU_TEST_CASE(
"RadiationModel - Specular Reflection Multiple Cameras") {
7245 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
7252 std::vector<helios::vec2> reflectivity = {
make_vec2(400, 0.05f),
make_vec2(700, 0.05f)};
7256 std::vector<helios::vec2> zero_transmissivity = {
make_vec2(400, 0.0f),
make_vec2(700, 0.0f)};
7257 context.
setGlobalData(
"zero_transmissivity", zero_transmissivity);
7258 context.
setPrimitiveData(UUID,
"transmissivity_spectrum",
"zero_transmissivity");
7276 cam_props.
HFOV = 30.0f;
7278 std::vector<helios::vec2> camera_response = {
make_vec2(400, 1.0f),
make_vec2(700, 1.0f)};
7296 std::vector<float> pixels_A, pixels_B;
7300 float sum_A = 0.0f, sum_B = 0.0f;
7301 for (
float p : pixels_A) sum_A += p;
7302 for (
float p : pixels_B) sum_B += p;
7303 float avg_A = sum_A / (float)pixels_A.size();
7304 float avg_B = sum_B / (float)pixels_B.size();
7307 float diffuse_baseline = 15.0f;
7309 DOCTEST_MESSAGE(
"Camera A avg: " << avg_A <<
", Camera B avg: " << avg_B <<
", Diffuse baseline ~" << diffuse_baseline);
7312 DOCTEST_CHECK_MESSAGE(avg_A > diffuse_baseline * 2.0f,
"Camera A should show specular highlight. avg_A: " << avg_A);
7313 DOCTEST_CHECK_MESSAGE(avg_B > diffuse_baseline * 2.0f,
"Camera B should show specular highlight. avg_B: " << avg_B);
7316 float ratio = (avg_A > avg_B) ? avg_B / avg_A : avg_A / avg_B;
7317 DOCTEST_CHECK_MESSAGE(ratio > 0.8f,
"Both cameras should see similar specular intensity. "
7318 "avg_A: " << avg_A <<
", avg_B: " << avg_B <<
", ratio: " << ratio);
7321GPU_TEST_CASE(
"RadiationModel More Than 4 Simultaneous Radiation Bands") {
7326 const int Nbands = 6;
7327 const float error_threshold = 0.005f;
7328 const uint Ndirect = 10000;
7331 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
7340 std::vector<std::string> band_names;
7341 std::vector<float> expected_flux;
7343 for (
int b = 0; b < Nbands; b++) {
7344 std::string name =
"band_" + std::to_string(b);
7345 band_names.push_back(name);
7346 float flux = float(b + 1) * 100.f;
7347 expected_flux.push_back(flux);
7356 radiation.
runBand(band_names);
7358 for (
int b = 0; b < Nbands; b++) {
7360 context.
getPrimitiveData(UUID, (
"radiation_flux_" + band_names[b]).c_str(), measured);
7361 float rel_error = std::abs(measured - expected_flux[b]) / expected_flux[b];
7362 DOCTEST_CHECK_MESSAGE(rel_error <= error_threshold,
"Band " << band_names[b] <<
": expected " << expected_flux[b] <<
", got " << measured <<
" (error " << rel_error <<
")");
7370GPU_TEST_CASE(
"RadiationModel - Camera triangle vs patch rendering parity") {
7378 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
7383 float reflectivity = 0.5f;
7409 cam_props.
HFOV = 60.0f;
7420 DOCTEST_REQUIRE(pixel_data.size() == 64 * 64);
7428 float patch_sum = 0;
7429 int patch_count = 0;
7434 for (
int j = 24; j < 40; j++) {
7435 for (
int i = 40; i < 56; i++) {
7436 patch_sum += pixel_data[j * 64 + i];
7442 for (
int j = 24; j < 40; j++) {
7443 for (
int i = 8; i < 24; i++) {
7444 tri_sum += pixel_data[j * 64 + i];
7449 float patch_avg = patch_sum / float(patch_count);
7450 float tri_avg = tri_sum / float(tri_count);
7453 DOCTEST_CHECK_MESSAGE(patch_avg > 0.0f,
7454 "Patch region average radiance should be > 0, got " << patch_avg);
7455 DOCTEST_CHECK_MESSAGE(tri_avg > 0.0f,
7456 "Triangle region average radiance should be > 0, got " << tri_avg);
7460 if (patch_avg > 0.0f && tri_avg > 0.0f) {
7461 float ratio = tri_avg / patch_avg;
7462 DOCTEST_CHECK_MESSAGE(ratio > 0.1f,
7463 "Triangle/patch radiance ratio too low: " << ratio
7464 <<
" (tri_avg=" << tri_avg <<
", patch_avg=" << patch_avg <<
")");
7465 DOCTEST_CHECK_MESSAGE(ratio < 10.0f,
7466 "Triangle/patch radiance ratio too high: " << ratio
7467 <<
" (tri_avg=" << tri_avg <<
", patch_avg=" << patch_avg <<
")");
7471GPU_TEST_CASE(
"RadiationModel - Multi-tile camera rendering consistency") {
7482 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context);
7516 cam_pos, cam_lookat, 100);
7523 radiation.
runBand({
"red",
"green",
"blue"});
7530 DOCTEST_REQUIRE(W == 3024);
7531 DOCTEST_REQUIRE(H == 4032);
7534 DOCTEST_REQUIRE(pixel_red.size() == (
size_t)W * H);
7544 float tile1_sum = 0;
7545 int tile1_count = 0;
7546 for (
int j = 500; j < 600; j++) {
7547 for (
int i = W / 4; i < 3 * W / 4; i += 10) {
7548 tile1_sum += pixel_red[j * W + i];
7554 float tile2_sum = 0;
7555 int tile2_count = 0;
7556 for (
int j = 3600; j < 3700; j++) {
7557 for (
int i = W / 4; i < 3 * W / 4; i += 10) {
7558 tile2_sum += pixel_red[j * W + i];
7563 float tile1_avg = tile1_sum / float(tile1_count);
7564 float tile2_avg = tile2_sum / float(tile2_count);
7567 DOCTEST_CHECK_MESSAGE(tile1_avg > 0.0f,
7568 "Tile 1 (rows 500-600) average radiance should be > 0, got " << tile1_avg);
7569 DOCTEST_CHECK_MESSAGE(tile2_avg > 0.0f,
7570 "Tile 2 (rows 3600-3700) average radiance should be > 0, got " << tile2_avg);
7573 if (tile1_avg > 0.0f && tile2_avg > 0.0f) {
7574 float ratio = tile2_avg / tile1_avg;
7575 DOCTEST_CHECK_MESSAGE(ratio > 0.1f,
7576 "Tile 2/tile 1 radiance ratio too low: " << ratio
7577 <<
" (tile1=" << tile1_avg <<
", tile2=" << tile2_avg <<
")");
7578 DOCTEST_CHECK_MESSAGE(ratio < 10.0f,
7579 "Tile 2/tile 1 radiance ratio too high: " << ratio
7580 <<
" (tile1=" << tile1_avg <<
", tile2=" << tile2_avg <<
")");
7597 struct FluspectReferenceConfig {
7598 float Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, fqe, V2Z;
7599 float wle_step, wlf_step;
7602 FluspectReferenceConfig load_fluspect_reference_config() {
7603 FluspectReferenceConfig c{};
7604 const std::filesystem::path p =
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_config.csv");
7606 DOCTEST_REQUIRE_MESSAGE(f.good(),
"Failed to open " << p.string());
7608 while (std::getline(f, line)) {
7609 if (line.empty() || line[0] ==
'#' || line.substr(0, 3) ==
"key")
continue;
7610 const auto comma = line.find(
',');
7611 if (comma == std::string::npos)
continue;
7612 const std::string key = line.substr(0, comma);
7613 const float val = std::stof(line.substr(comma + 1));
7614 if (key ==
"Cab") c.Cab = val;
7615 else if (key ==
"Cca") c.Cca = val;
7616 else if (key ==
"Cw") c.Cw = val;
7617 else if (key ==
"Cdm") c.Cdm = val;
7618 else if (key ==
"Cs") c.Cs = val;
7619 else if (key ==
"Cant") c.Cant = val;
7620 else if (key ==
"Cp") c.Cp = val;
7621 else if (key ==
"Cbc") c.Cbc = val;
7622 else if (key ==
"N") c.N = val;
7623 else if (key ==
"fqe") c.fqe = val;
7624 else if (key ==
"V2Z") c.V2Z = val;
7625 else if (key ==
"wle_step") c.wle_step = val;
7626 else if (key ==
"wlf_step") c.wlf_step = val;
7637 std::vector<std::vector<double>> load_matrix_csv(
const std::filesystem::path &p,
7638 std::vector<float> &wle_out,
7639 std::vector<float> &wlf_out) {
7641 DOCTEST_REQUIRE_MESSAGE(f.good(),
"Failed to open " << p.string());
7645 std::vector<std::vector<double>> M;
7648 while (std::getline(f, line)) {
7649 if (line.empty() || line[0] ==
'#')
continue;
7653 std::stringstream hs(line);
7656 while (std::getline(hs, cell,
',')) {
7657 if (first) { first =
false;
continue; }
7658 wle_out.push_back(std::stof(cell));
7661 while (std::getline(f, line)) {
7662 if (line.empty() || line[0] ==
'#')
continue;
7663 std::stringstream ds(line);
7664 std::vector<double> row;
7665 bool is_first =
true;
7666 while (std::getline(ds, cell,
',')) {
7668 wlf_out.push_back(std::stof(cell));
7672 row.push_back(std::stod(cell));
7674 if (!row.empty()) M.push_back(std::move(row));
7681DOCTEST_TEST_CASE(
"SIF V&V Tier 1 (v2): Fluspect-B C++ port matches MATLAB reference") {
7684 const std::filesystem::path optipar_xml =
helios::resolveFilePath(
"plugins/radiation/spectral_data/fluspect_B_optipar.xml");
7686 DOCTEST_CHECK(optipar.wavelengths_nm.size() > 1000);
7689 const FluspectReferenceConfig cfg = load_fluspect_reference_config();
7690 std::vector<float> ref_wle, ref_wlf;
7691 const auto Mf_ref = load_matrix_csv(
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_Mf.csv"), ref_wle, ref_wlf);
7692 std::vector<float> ref_wle2, ref_wlf2;
7693 const auto Mb_ref = load_matrix_csv(
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_Mb.csv"), ref_wle2, ref_wlf2);
7694 DOCTEST_REQUIRE(ref_wle == ref_wle2);
7695 DOCTEST_REQUIRE(ref_wlf == ref_wlf2);
7696 DOCTEST_REQUIRE(Mf_ref.size() == ref_wlf.size());
7700 biochem.
Cab = cfg.Cab;
7701 biochem.
Cca = cfg.Cca;
7702 biochem.
Cw = cfg.Cw;
7703 biochem.
Cdm = cfg.Cdm;
7704 biochem.
Cs = cfg.Cs;
7705 biochem.
Cant = cfg.Cant;
7706 biochem.
Cp = cfg.Cp;
7707 biochem.
Cbc = cfg.Cbc;
7709 biochem.
fqe = cfg.fqe;
7710 biochem.
V2Z = cfg.V2Z;
7714 DOCTEST_REQUIRE(out.
wle.size() == ref_wle.size());
7715 DOCTEST_REQUIRE(out.
wlf.size() == ref_wlf.size());
7716 for (
size_t j = 0; j < out.
wle.size(); ++j) {
7717 DOCTEST_CHECK(out.
wle[j] == doctest::Approx(ref_wle[j]).epsilon(1e-5));
7719 for (
size_t i = 0; i < out.
wlf.size(); ++i) {
7720 DOCTEST_CHECK(out.
wlf[i] == doctest::Approx(ref_wlf[i]).epsilon(1e-5));
7728 size_t n_total = 0, n_checked_Mf = 0, n_checked_Mb = 0;
7729 double max_rel_err_Mf = 0.0, max_rel_err_Mb = 0.0;
7730 for (
size_t i = 0; i < out.
wlf.size(); ++i) {
7731 for (
size_t j = 0; j < out.
wle.size(); ++j) {
7732 const double cf = out.
Mf[i][j];
7733 const double cb = out.
Mb[i][j];
7734 const double rf = Mf_ref[i][j];
7735 const double rb = Mb_ref[i][j];
7736 if (std::abs(rf) > 1e-10) {
7737 max_rel_err_Mf = std::max(max_rel_err_Mf, std::abs(cf - rf) / std::abs(rf));
7740 if (std::abs(rb) > 1e-10) {
7741 max_rel_err_Mb = std::max(max_rel_err_Mb, std::abs(cb - rb) / std::abs(rb));
7747 DOCTEST_MESSAGE(
"Fluspect Mf max relative error: " << max_rel_err_Mf);
7748 DOCTEST_MESSAGE(
"Fluspect Mb max relative error: " << max_rel_err_Mb);
7749 DOCTEST_MESSAGE(
"Elements compared: Mf=" << n_checked_Mf <<
"/" << n_total
7750 <<
" Mb=" << n_checked_Mb <<
"/" << n_total
7751 <<
" (rest below 1e-10 magnitude floor — anti-Stokes wavelengths)");
7754 DOCTEST_CHECK(n_checked_Mf > 0.9 * n_total);
7755 DOCTEST_CHECK(n_checked_Mb > 0.9 * n_total);
7756 DOCTEST_CHECK(max_rel_err_Mf < 1e-4);
7757 DOCTEST_CHECK(max_rel_err_Mb < 1e-4);
7773 void sif_stamp_biochem(
Context &ctx,
const std::vector<uint> &UUIDs,
const std::string &label,
7774 float Cab = 40.f,
float Cca = 10.f,
float Cw = 0.009f,
float Cdm = 0.012f,
7775 float Cs = 0.f,
float Cant = 1.f,
float Cp = 0.f,
float Cbc = 0.f,
7776 float N = 1.5f,
float V2Z = 0.f,
float fqe = 1.f) {
7777 const std::string full_label =
"fluspect_biochem_" + label;
7778 std::vector<float> biochem = {Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe};
7801GPU_TEST_CASE(
"SIF V&V Tier 2 (v2): full pipeline with solar source + SIF camera") {
7807 sif_stamp_biochem(ctx, {leaf},
"t2");
7814 const float sensor_side = 1.f;
7819 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
7843 cam_props.
HFOV = 30.f;
7845 radiation.
addSIFCamera(
"sif_cam", {
"SIF_red",
"SIF_farred"},
7849 DOCTEST_CHECK(!radiation.
isSIFCamera(
"nonexistent"));
7852 const std::vector<std::string> sif_bands = {
"SIF_red",
"SIF_farred"};
7859 DOCTEST_CHECK(phi_f > 0.f);
7860 DOCTEST_CHECK(phi_f < 0.1f);
7861 DOCTEST_MESSAGE(
"Leaf Phi_F = " << phi_f);
7866 float flux_red = 0.f, flux_farred = 0.f;
7869 DOCTEST_MESSAGE(
"Sensor F_red=" << flux_red <<
" F_farred=" << flux_farred
7870 <<
" ratio=" << (flux_red / std::max(flux_farred, 1e-12f)));
7871 DOCTEST_CHECK(std::isfinite(flux_red));
7872 DOCTEST_CHECK(std::isfinite(flux_farred));
7873 DOCTEST_CHECK(flux_red > 0.f);
7874 DOCTEST_CHECK(flux_farred > 0.f);
7880 DOCTEST_CHECK(pixels_red.size() == 16 * 16);
7881 DOCTEST_CHECK(pixels_farred.size() == 16 * 16);
7882 float max_pixel_red = 0.f, max_pixel_farred = 0.f;
7883 for (
float v : pixels_red) {
7884 DOCTEST_CHECK(std::isfinite(v));
7885 DOCTEST_CHECK(v >= 0.f);
7886 if (v > max_pixel_red) max_pixel_red = v;
7888 for (
float v : pixels_farred) {
7889 DOCTEST_CHECK(std::isfinite(v));
7890 DOCTEST_CHECK(v >= 0.f);
7891 if (v > max_pixel_farred) max_pixel_farred = v;
7893 DOCTEST_CHECK(max_pixel_red > 0.f);
7894 DOCTEST_CHECK(max_pixel_farred > 0.f);
7915GPU_TEST_CASE(
"SIF V&V Tier 3 (v2): multi-camera pipeline with distinct excitation resolutions") {
7921 sif_stamp_biochem(ctx, {leaf},
"t3");
7926 const float sensor_side = 1.f;
7931 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
7938 for (
const auto &b : {
"SIF_red_fine",
"SIF_red_coarse",
"SIF_farred_fine"}) {
7961 radiation.
addSIFCamera(
"cam_a", {
"SIF_red_fine",
"SIF_farred_fine"},
7975 DOCTEST_CHECK(!radiation.
isSIFCamera(
"nonexistent"));
7980 regular_props.
HFOV = 20.f;
7983 DOCTEST_CHECK(!radiation.
isSIFCamera(
"regular_cam"));
7989 DOCTEST_CHECK(radiation.
doesBandExist(
"_SIF_exc_10_400_410"));
7990 DOCTEST_CHECK(radiation.
doesBandExist(
"_SIF_exc_10_740_750"));
7991 DOCTEST_CHECK(radiation.
doesBandExist(
"_SIF_exc_25_400_425"));
7992 DOCTEST_CHECK(radiation.
doesBandExist(
"_SIF_exc_25_725_750"));
7997 DOCTEST_CHECK_THROWS_AS(
7998 radiation.
addSIFCamera(
"cam_conflict", {
"SIF_red_fine"},
8000 std::runtime_error);
8007 const std::vector<std::string> sif_bands_list = {
"SIF_red_fine",
"SIF_red_coarse",
"SIF_farred_fine"};
8008 radiation.
runBand(sif_bands_list);
8010 float flux_red_fine = 0.f, flux_red_coarse = 0.f, flux_farred_fine = 0.f;
8011 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_red_fine", flux_red_fine);
8012 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_red_coarse", flux_red_coarse);
8013 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_farred_fine", flux_farred_fine);
8014 DOCTEST_MESSAGE(
"Sensor F_red_fine=" << flux_red_fine
8015 <<
" F_red_coarse=" << flux_red_coarse
8016 <<
" F_farred_fine=" << flux_farred_fine);
8017 DOCTEST_CHECK(flux_red_fine > 0.f);
8018 DOCTEST_CHECK(flux_red_coarse > 0.f);
8019 DOCTEST_CHECK(flux_farred_fine > 0.f);
8027 DOCTEST_CHECK(flux_red_coarse > 0.33f * flux_red_fine);
8028 DOCTEST_CHECK(flux_red_coarse < 3.f * flux_red_fine);
8040GPU_TEST_CASE(
"SIF warnings: no source spectrum set") {
8043 sif_stamp_biochem(ctx, {leaf},
"warn_no_spec");
8045 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8054 cam_props.
HFOV = 20.f;
8057 std::string captured;
8060 radiation.
addSIFCamera(
"warn_cam_no_spectrum", {
"SIF_red"},
8064 DOCTEST_CHECK(captured.find(
"no radiation source has a spectrum set") != std::string::npos);
8067GPU_TEST_CASE(
"SIF warnings: no fluspect_spectrum on any primitive") {
8073 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8080 cam_props.
HFOV = 20.f;
8083 std::string captured;
8090 DOCTEST_CHECK(captured.find(
"fluspect_spectrum") != std::string::npos);
8093GPU_TEST_CASE(
"SIF warnings: leaves have biochemistry but lack electron_transport_ratio at runtime") {
8100 sif_stamp_biochem(ctx, {leaf},
"warn_no_etr");
8103 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8104 radiation.addRadiationBand(
"SIF_red", 680.f, 700.f);
8105 radiation.setScatteringDepth(
"SIF_red", 1);
8106 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8107 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8111 cam_props.
HFOV = 20.f;
8116 std::string setup_captured;
8119 radiation.addSIFCamera(
"warn_no_etr", {
"SIF_red"},
8123 DOCTEST_CHECK(setup_captured.find(
"electron_transport_ratio") == std::string::npos);
8126 std::string runtime_captured;
8129 radiation.updateGeometry();
8130 const std::vector<std::string> sif_bands = {
"SIF_red"};
8131 radiation.runBand(sif_bands);
8134 DOCTEST_CHECK(runtime_captured.find(
"electron_transport_ratio") != std::string::npos);
8137GPU_TEST_CASE(
"SIF warnings: camera bound to band with scattering depth 0") {
8141 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8148 cam_props.
HFOV = 20.f;
8150 std::string captured;
8157 DOCTEST_CHECK(captured.find(
"scatteringDepth == 0") != std::string::npos);
8158 DOCTEST_CHECK(captured.find(
"Camera pixels for this band will be zero") != std::string::npos);
8166GPU_TEST_CASE(
"SIF: non-blackbody leaf optics (rho+tau<1) should not trip ε+ρ+τ=1 check") {
8170 sif_stamp_biochem(ctx, {leaf},
"conserv_test");
8180 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8190 cam_props.
HFOV = 20.f;
8195 const std::vector<std::string> sif_bands = {
"SIF_farred"};
8196 DOCTEST_CHECK_NOTHROW(radiation.
runBand(sif_bands));
8202GPU_TEST_CASE(
"SIF: disabled emission on a SIF band is soft-overridden with warning") {
8206 sif_stamp_biochem(ctx, {leaf},
"override_test");
8210 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8220 cam_props.
HFOV = 20.f;
8228 std::string captured;
8232 const std::vector<std::string> sif_bands = {
"SIF_farred"};
8236 DOCTEST_CHECK(captured.find(
"has emission disabled") != std::string::npos);
8237 DOCTEST_CHECK(captured.find(
"Re-enabling emission") != std::string::npos);
8244GPU_TEST_CASE(
"SIF: excitation_scattering_depth propagates to excitation bands and suppresses per-band warning spam") {
8247 sif_stamp_biochem(ctx, {leaf},
"depth_test");
8253 RadiationModel radiation_a = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8270 for (
float wmin = 400.f; wmin < 750.f; wmin += 50.f) {
8271 const float wmax = std::min(750.f, wmin + 50.f);
8272 std::ostringstream oss;
8273 oss <<
"_SIF_exc_50_" << wmin <<
"_" << wmax;
8274 const std::string label = oss.str();
8279 std::string captured;
8283 const std::vector<std::string> sif_bands = {
"SIF_red"};
8284 radiation_a.
runBand(sif_bands);
8289 const bool exc_warning_absent = (captured.find(
"_SIF_exc_") == std::string::npos) ||
8290 (captured.find(
"scattering iterations are disabled") == std::string::npos);
8291 DOCTEST_CHECK(exc_warning_absent);
8301 sif_stamp_biochem(ctx2, {leaf2},
"depth_test_2");
8304 RadiationModel radiation_b = RadiationModelTestHelper::createWithSharedDevice(&ctx2);
8322 const std::vector<std::string> sif_bands_b = {
"SIF_red"};
8323 DOCTEST_CHECK_NOTHROW(radiation_b.
runBand(sif_bands_b));