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"));
94 DOCTEST_MESSAGE(
"Active backend: " << model.getBackendName());
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") {
216 context.setPrimitiveData(patch,
"twosided_flag",
uint(0));
217 context.setPrimitiveData(patch,
"reflectivity_SW", 0.0f);
220 radiation.disableMessages();
223 radiation.addRadiationBand(
"SW");
224 radiation.disableEmission(
"SW");
225 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
226 radiation.setSourceFlux(sun,
"SW", 1000.0f);
227 radiation.setDirectRayCount(
"SW", 10000);
228 radiation.setScatteringDepth(
"SW", 0);
230 radiation.updateGeometry();
231 radiation.runBand(
"SW");
234 context.getPrimitiveData(patch,
"radiation_flux_SW", flux);
237 float error = fabsf(flux - 1000.0f) / 1000.0f;
238 DOCTEST_CHECK(error <= 0.01);
241GPU_TEST_CASE(
"RadiationModel Multi-Band Stale Backend Geometry") {
256 radiation.disableMessages();
258 radiation.addRadiationBand(
"PAR");
259 radiation.addRadiationBand(
"NIR");
260 radiation.addRadiationBand(
"LW");
262 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
263 radiation.setSourceFlux(sun,
"PAR", 500.0f);
264 radiation.setSourceFlux(sun,
"NIR", 500.0f);
267 radiation.updateGeometry();
274 DOCTEST_CHECK_NOTHROW(radiation.runBand({
"PAR",
"NIR",
"LW"}));
277GPU_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Squares") {
278 float error_threshold = 0.005;
281 uint Ndiffuse_1 = 100000;
282 uint Ndirect_1 = 5000;
285 float sigma = 5.6703744E-8;
287 float shortwave_exact_0 = 0.7f * Qs;
288 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
289 float longwave_exact_0 = 0.f;
290 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
303 float shortwave_rho = 0.3f;
306 RadiationModel radiationmodel_1 = RadiationModelTestHelper::createWithSharedDevice(&context_1);
326 float longwave_model_0 = 0.f;
327 float longwave_model_1 = 0.f;
328 float shortwave_model_0 = 0.f;
329 float shortwave_model_1 = 0.f;
332 for (
int r = 0; r < Nensemble; r++) {
333 std::vector<std::string> bands{
"LW",
"SW"};
334 radiationmodel_1.
runBand(bands);
338 longwave_model_0 +=
R / float(Nensemble);
341 longwave_model_1 +=
R / float(Nensemble);
345 shortwave_model_0 +=
R / float(Nensemble);
348 shortwave_model_1 +=
R / float(Nensemble);
351 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
352 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
353 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
355 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
356 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
358 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
359 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
362GPU_TEST_CASE(
"RadiationModel Black Parallel Rectangles") {
363 float error_threshold = 0.005;
366 uint Ndiffuse_2 = 50000;
378 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));
380 float shortwave_exact_0 = (1.f - F12);
381 float shortwave_exact_1 = (1.f - F12);
391 RadiationModel radiationmodel_2 = RadiationModelTestHelper::createWithSharedDevice(&context_2);
403 float shortwave_model_0 = 0.f;
404 float shortwave_model_1 = 0.f;
407 for (
int r = 0; r < Nensemble; r++) {
408 radiationmodel_2.
runBand(
"SW");
411 shortwave_model_0 +=
R / float(Nensemble);
413 shortwave_model_1 +=
R / float(Nensemble);
416 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
417 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
419 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
420 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
423GPU_TEST_CASE(
"RadiationModel Gray Parallel Rectangles") {
424 float error_threshold = 0.005;
426 float sigma = 5.6703744E-8;
428 uint Ndiffuse_3 = 100000;
431 float longwave_rho = 0.4;
447 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));
449 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);
450 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));
451 longwave_exact_0 = fabsf(longwave_exact_0);
469 RadiationModel radiationmodel_3 = RadiationModelTestHelper::createWithSharedDevice(&context_3);
481 float longwave_model_0 = 0.f;
482 float longwave_model_1 = 0.f;
485 for (
int r = 0; r < Nensemble; r++) {
486 radiationmodel_3.
runBand(
"LW");
489 longwave_model_0 +=
R / float(Nensemble);
491 longwave_model_1 +=
R / float(Nensemble);
494 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
495 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
497 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
498 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
501GPU_TEST_CASE(
"RadiationModel Sphere Source") {
502 float error_threshold = 0.005;
505 uint Ndirect_4 = 10000;
515 float F12 = 0.25f / float(M_PI) * atanf(sqrtf(1.f / (D1 * D1 + D2 * D2 + D1 * D1 * D2 * D2)));
517 float shortwave_exact_0 = 4.0f * float(M_PI) * r * r * F12 / (l1 * l2);
522 RadiationModel radiationmodel_4 = RadiationModelTestHelper::createWithSharedDevice(&context_4);
536 float shortwave_model_0 = 0.f;
539 for (
int i = 0; i < Nensemble; i++) {
540 radiationmodel_4.
runBand(
"SW");
543 shortwave_model_0 +=
R / float(Nensemble);
546 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
548 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
551GPU_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Sub-Triangles") {
552 float error_threshold = 0.005;
554 float sigma = 5.6703744E-8;
558 uint Ndiffuse_5 = 100000;
559 uint Ndirect_5 = 5000;
561 float shortwave_exact_0 = 0.7f * Qs;
562 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
563 float longwave_exact_0 = 0.f;
564 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
579 float shortwave_rho = 0.3f;
589 RadiationModel radiationmodel_5 = RadiationModelTestHelper::createWithSharedDevice(&context_5);
609 float longwave_model_0 = 0.f;
610 float longwave_model_1 = 0.f;
611 float shortwave_model_0 = 0.f;
612 float shortwave_model_1 = 0.f;
615 for (
int i = 0; i < Nensemble; i++) {
616 std::vector<std::string> bands{
"SW",
"LW"};
617 radiationmodel_5.
runBand(bands);
621 longwave_model_0 += 0.5f *
R / float(Nensemble);
623 longwave_model_0 += 0.5f *
R / float(Nensemble);
626 longwave_model_1 += 0.5f *
R / float(Nensemble);
628 longwave_model_1 += 0.5f *
R / float(Nensemble);
632 shortwave_model_0 += 0.5f *
R / float(Nensemble);
634 shortwave_model_0 += 0.5f *
R / float(Nensemble);
637 shortwave_model_1 += 0.5f *
R / float(Nensemble);
639 shortwave_model_1 += 0.5f *
R / float(Nensemble);
642 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
643 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
644 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
646 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
647 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
649 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
650 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
653GPU_TEST_CASE(
"RadiationModel Parallel Disks Texture Masked Patches") {
654 float error_threshold = 0.005;
656 float sigma = 5.6703744E-8;
658 uint Ndirect_6 = 1000;
659 uint Ndiffuse_6 = 500000;
661 float shortwave_rho = 0.3;
667 float A1 =
M_PI * r1 * r1;
668 float A2 =
M_PI * r2 * r2;
673 float X = 1.f + (1.f + R2 * R2) / (R1 * R1);
674 float F12 = 0.5f * (X - std::sqrt(X * X - 4.f * powf(R2 / R1, 2)));
676 float shortwave_exact_0 = (A1 - A2) / A1 * (1.f - shortwave_rho);
677 float shortwave_exact_1 = (A1 - A2) / A1 * F12 * A1 / A2 * shortwave_rho;
678 float longwave_exact_0 = sigma * powf(300.f, 4) * F12;
679 float longwave_exact_1 = sigma * powf(300.f, 4) * F12 * A1 / A2;
697 RadiationModel radiationmodel_6 = RadiationModelTestHelper::createWithSharedDevice(&context_6);
719 float shortwave_model_0 = 0;
720 float shortwave_model_1 = 0;
721 float longwave_model_0 = 0;
722 float longwave_model_1 = 0;
725 for (
uint i = 0; i < Nensemble; i++) {
726 radiationmodel_6.
runBand(
"SW");
727 radiationmodel_6.
runBand(
"LW");
730 shortwave_model_0 +=
R / float(Nensemble);
733 shortwave_model_1 +=
R / float(Nensemble);
736 longwave_model_0 +=
R / float(Nensemble);
739 longwave_model_1 +=
R / float(Nensemble);
742 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
743 float shortwave_error_1 = fabsf(shortwave_exact_1 - shortwave_model_1) / fabsf(shortwave_exact_1);
744 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
745 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
747 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
748 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
749 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
750 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
753GPU_TEST_CASE(
"RadiationModel Second Law Equilibrium Test") {
754 float error_threshold = 0.005;
755 float sigma = 5.6703744E-8;
757 uint Ndiffuse_7 = 50000;
766 uint objID_7 = context_7.
addBoxObject(
make_vec3(0, 0, 0),
make_vec3(10, 10, 10),
make_int3(5, 5, 5), RGB::black,
true);
776 RadiationModel radiationmodel_7 = RadiationModelTestHelper::createWithSharedDevice(&context_7);
787 radiationmodel_7.
runBand(
"LW");
790 float flux_err = 0.f;
791 for (
int p = 0; p < UUIDt.size(); p++) {
794 flux_err += fabsf(
R - eps1_7 * sigma * powf(300, 4)) / (eps1_7 * sigma * powf(300, 4)) /
float(UUIDt.size());
797 DOCTEST_CHECK(flux_err <= error_threshold);
800 for (
uint p: UUIDt) {
802 if (context_7.
randu() < 0.5f) {
812 radiationmodel_7.
runBand(
"LW");
815 for (
int p = 0; p < UUIDt.size(); p++) {
820 flux_err += fabsf(
R - emissivity * sigma * powf(300, 4)) / (emissivity * sigma * powf(300, 4)) /
float(UUIDt.size());
823 DOCTEST_CHECK(flux_err <= error_threshold);
826GPU_TEST_CASE(
"RadiationModel Texture Mapping") {
827 float error_threshold = 0.005;
831 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&context_8);
833 uint source = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
835 radiation.addRadiationBand(
"SW");
837 radiation.setDirectRayCount(
"SW", 10000);
838 radiation.disableEmission(
"SW");
839 radiation.disableMessages();
841 radiation.setSourceFlux(source,
"SW", 1.f);
853 radiation.updateGeometry();
855 radiation.runBand(
"SW");
861 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f * M_PI)) <= error_threshold);
862 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
870 radiation.updateGeometry();
872 radiation.runBand(
"SW");
878 for (
uint p: UUIDs1) {
889 bool test_8b_pass =
true;
890 for (
uint p = 0; p < UUIDs1.size(); p++) {
893 if (fabs(
R - 1.f) > error_threshold) {
894 test_8b_pass =
false;
898 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f * M_PI)) <= error_threshold);
899 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
900 DOCTEST_CHECK(test_8b_pass);
907 radiation.updateGeometry();
909 radiation.runBand(
"SW");
914 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
915 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
922 radiation.updateGeometry();
924 radiation.runBand(
"SW");
929 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f * M_PI)) <= error_threshold);
930 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
935 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),
938 radiation.updateGeometry();
940 radiation.runBand(
"SW");
945 DOCTEST_CHECK(fabs(F0 - 0.5 - 0.5 * (1.f - 0.25f * M_PI)) <= error_threshold);
946 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
953 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),
956 radiation.updateGeometry();
958 radiation.runBand(
"SW");
965 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
966 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
967 DOCTEST_CHECK(fabsf(F2 - 1.f) <= error_threshold);
976 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),
978 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),
981 radiation.updateGeometry();
983 radiation.runBand(
"SW");
989 DOCTEST_CHECK(fabsf(F1) <= error_threshold);
990 DOCTEST_CHECK(fabsf(F2) <= error_threshold);
991 DOCTEST_CHECK(fabsf(F0 - 1.f) <= error_threshold);
994GPU_TEST_CASE(
"RadiationModel Homogeneous Canopy of Patches") {
995 float error_threshold = 0.005;
996 float sigma = 5.6703744E-8;
998 uint Ndirect_9 = 1000;
999 uint Ndiffuse_9 = 5000;
1005 float w_leaf_9 = 0.075;
1007 int Nleaves = (int) lroundf(LAI_9 * D_9 * D_9 / w_leaf_9 / w_leaf_9);
1011 std::vector<uint> UUIDs_leaf, UUIDs_inc;
1013 for (
int i = 0; i < Nleaves; i++) {
1014 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);
1018 if (fabsf(position.x) <= 0.5 * D_inc_9 && fabsf(position.y) <= 0.5 * D_inc_9) {
1019 UUIDs_inc.push_back(UUID);
1026 RadiationModel radiation_9 = RadiationModelTestHelper::createWithSharedDevice(&context_9);
1032 float theta_s = 0.2 *
M_PI;
1034 radiation_9.
setSourceFlux(ID,
"direct", 1.f / cosf(theta_s));
1043 radiation_9.
runBand(
"direct");
1044 radiation_9.
runBand(
"diffuse");
1046 float intercepted_leaf_direct = 0.f;
1047 float intercepted_leaf_diffuse = 0.f;
1048 for (
uint i: UUIDs_inc) {
1052 intercepted_leaf_direct += flux * area / D_inc_9 / D_inc_9;
1054 intercepted_leaf_diffuse += flux * area / D_inc_9 / D_inc_9;
1057 float intercepted_ground_direct = 0.f;
1058 float intercepted_ground_diffuse = 0.f;
1059 for (
uint i: UUIDs_ground) {
1066 if (fabsf(position.
x) <= 0.5 * D_inc_9 && fabsf(position.
y) <= 0.5 * D_inc_9) {
1067 intercepted_ground_direct += flux_dir * area / D_inc_9 / D_inc_9;
1068 intercepted_ground_diffuse += flux_diff * area / D_inc_9 / D_inc_9;
1072 intercepted_ground_direct = 1.f - intercepted_ground_direct;
1073 intercepted_ground_diffuse = 1.f - intercepted_ground_diffuse;
1076 float dtheta = 0.5f * float(M_PI) / float(N);
1078 float intercepted_theoretical_diffuse = 0.f;
1079 for (
int i = 0; i < N; i++) {
1080 float theta = (float(i) + 0.5f) * dtheta;
1081 intercepted_theoretical_diffuse += 2.f * (1.f - expf(-0.5f * LAI_9 / cosf(theta))) * cosf(theta) * sinf(theta) * dtheta;
1084 float intercepted_theoretical_direct = 1.f - expf(-0.5f * LAI_9 / cosf(theta_s));
1086 DOCTEST_CHECK(fabsf(intercepted_ground_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
1087 DOCTEST_CHECK(fabsf(intercepted_leaf_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
1088 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
1089 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
1092GPU_TEST_CASE(
"RadiationModel Gas-filled Furnace") {
1093 float error_threshold = 0.005;
1094 float sigma = 5.6703744E-8;
1096 float Rref_10 = 33000.f;
1097 uint Ndiffuse_10 = 10000;
1103 float Tw_10 = 1273.f;
1104 float Tm_10 = 1773.f;
1106 float kappa_10 = 0.1f;
1107 float eps_m_10 = 1.f;
1108 float w_patch_10 = 0.01;
1110 int Npatches_10 = (int) lroundf(2.f * kappa_10 * w_10 * h_10 * d_10 / w_patch_10 / w_patch_10);
1114 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);
1119 std::vector<uint> UUIDs_patches;
1121 for (
int i = 0; i < Npatches_10; i++) {
1122 float x = -0.5f * d_10 + 0.5f * w_patch_10 + (d_10 - 2 * w_patch_10) * context_10.
randu();
1123 float y = -0.5f * w_10 + 0.5f * w_patch_10 + (w_10 - 2 * w_patch_10) * context_10.
randu();
1124 float z = -0.5f * h_10 + 0.5f * w_patch_10 + (h_10 - 2 * w_patch_10) * context_10.
randu();
1126 float theta = acosf(1.f - context_10.
randu());
1127 float phi = 2.f * float(M_PI) * context_10.
randu();
1133 context_10.
setPrimitiveData(UUIDs_patches,
"reflectivity_LW", 1.f - eps_m_10);
1135 RadiationModel radiation_10 = RadiationModelTestHelper::createWithSharedDevice(&context_10);
1147 for (
uint i: UUIDs_box) {
1152 R_wall += flux * area;
1154 R_wall = R_wall / A_wall - sigma * powf(Tw_10, 4);
1156 DOCTEST_CHECK(fabsf(R_wall - Rref_10) / Rref_10 <= error_threshold);
1159GPU_TEST_CASE(
"RadiationModel Purely Scattering Medium Between Infinite Plates") {
1160 float error_threshold = 0.005;
1161 float sigma = 5.6703744E-8;
1167 float Tw1_11 = 300.f;
1168 float Tw2_11 = 400.f;
1170 float epsw1_11 = 0.8f;
1171 float epsw2_11 = 0.5f;
1173 float omega_11 = 1.f;
1174 float tauL_11 = 0.1f;
1176 float Psi2_exact = 0.427;
1178 float w_patch_11 = 0.05;
1180 float beta = tauL_11 / h_11;
1182 int Nleaves_11 = (int) lroundf(2.f * beta * W_11 * W_11 * h_11 / w_patch_11 / w_patch_11);
1201 std::vector<uint> UUIDs_patches_11;
1203 for (
int i = 0; i < Nleaves_11; i++) {
1204 float x = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1205 float y = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1206 float z = -0.5f * h_11 + 0.5f * w_patch_11 + (h_11 - w_patch_11) * context_11.
randu();
1208 float theta = acosf(1.f - context_11.
randu());
1209 float phi = 2.f * float(M_PI) * context_11.
randu();
1214 context_11.
setPrimitiveData(UUIDs_patches_11,
"emissivity_LW", 1.f - omega_11);
1215 context_11.
setPrimitiveData(UUIDs_patches_11,
"reflectivity_LW", omega_11);
1217 RadiationModel radiation_11 = RadiationModelTestHelper::createWithSharedDevice(&context_11);
1228 float A_wall2 = 0.f;
1229 for (
int i = 0; i < UUIDs_1.size(); i++) {
1232 if (fabsf(position.
x) < 0.5 * w_11 && fabsf(position.
y) < 0.5 * w_11) {
1237 R_wall2 += flux * area;
1242 R_wall2 = (R_wall2 / A_wall2 - epsw2_11 * sigma * pow(Tw2_11, 4)) / (sigma * (pow(Tw1_11, 4) - pow(Tw2_11, 4)));
1244 DOCTEST_CHECK(fabsf(R_wall2 - Psi2_exact) <= 10.f * error_threshold);
1247GPU_TEST_CASE(
"RadiationModel Homogeneous Canopy with Periodic Boundaries") {
1248 float error_threshold = 0.005;
1250 uint Ndirect_12 = 1000;
1251 uint Ndiffuse_12 = 5000;
1256 float w_leaf_12 = 0.05;
1258 int Nleaves_12 = round(LAI_12 * D_12 * D_12 / w_leaf_12 / w_leaf_12);
1262 std::vector<uint> UUIDs_leaf_12;
1264 for (
int i = 0; i < Nleaves_12; i++) {
1265 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);
1269 UUIDs_leaf_12.push_back(UUID);
1275 RadiationModel radiation_12 = RadiationModelTestHelper::createWithSharedDevice(&context_12);
1281 float theta_s = 0.2 *
M_PI;
1283 radiation_12.
setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1294 radiation_12.
runBand(
"direct");
1295 radiation_12.
runBand(
"diffuse");
1297 float intercepted_leaf_direct_12 = 0.f;
1298 float intercepted_leaf_diffuse_12 = 0.f;
1299 for (
int i = 0; i < UUIDs_leaf_12.size(); i++) {
1302 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_direct", flux);
1303 intercepted_leaf_direct_12 += flux * area / D_12 / D_12;
1304 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_diffuse", flux);
1305 intercepted_leaf_diffuse_12 += flux * area / D_12 / D_12;
1308 float intercepted_ground_direct_12 = 0.f;
1309 float intercepted_ground_diffuse_12 = 0.f;
1310 for (
int i = 0; i < UUIDs_ground_12.size(); i++) {
1313 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_direct", flux_dir);
1315 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_diffuse", flux_diff);
1317 intercepted_ground_direct_12 += flux_dir * area / D_12 / D_12;
1318 intercepted_ground_diffuse_12 += flux_diff * area / D_12 / D_12;
1321 intercepted_ground_direct_12 = 1.f - intercepted_ground_direct_12;
1322 intercepted_ground_diffuse_12 = 1.f - intercepted_ground_diffuse_12;
1325 float dtheta = 0.5 *
M_PI / float(N);
1327 float intercepted_theoretical_diffuse_12 = 0.f;
1328 for (
int i = 0; i < N; i++) {
1329 float theta = (i + 0.5f) * dtheta;
1330 intercepted_theoretical_diffuse_12 += 2.f * (1.f - exp(-0.5 * LAI_12 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1333 float intercepted_theoretical_direct_12 = 1.f - exp(-0.5 * LAI_12 / cos(theta_s));
1335 DOCTEST_CHECK(fabsf(intercepted_ground_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1336 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1337 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1338 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1341GPU_TEST_CASE(
"RayTracingGeometry validation with periodic boundaries") {
1350 radiation.disableMessages();
1351 radiation.addRadiationBand(
"PAR");
1352 radiation.disableEmission(
"PAR");
1353 radiation.setDiffuseRayCount(
"PAR", 100);
1354 radiation.setScatteringDepth(
"PAR", 0);
1355 radiation.enforcePeriodicBoundary(
"xy");
1359 radiation.updateGeometry();
1361 DOCTEST_CHECK(
true);
1364GPU_TEST_CASE(
"RadiationModel Texture-masked Tile Objects with Periodic Boundaries") {
1365 float error_threshold = 0.005;
1367 uint Ndirect_13 = 1000;
1368 uint Ndiffuse_13 = 5000;
1373 float w_leaf_13 = 0.05;
1381 for (
uint p = 0; p < UUIDs_ptype.size(); p++) {
1385 int Nleaves_13 = round(LAI_13 * D_13 * D_13 / A_leaf);
1387 std::vector<uint> UUIDs_leaf_13;
1389 for (
int i = 0; i < Nleaves_13; i++) {
1390 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);
1395 context_13.
rotateObject(objID, -rotation.elevation,
"y");
1400 UUIDs_leaf_13.insert(UUIDs_leaf_13.end(), UUIDs.begin(), UUIDs.end());
1408 RadiationModel radiation_13 = RadiationModelTestHelper::createWithSharedDevice(&context_13);
1414 float theta_s = 0.2 *
M_PI;
1416 radiation_13.
setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1427 radiation_13.
runBand(
"direct");
1428 radiation_13.
runBand(
"diffuse");
1430 float intercepted_leaf_direct_13 = 0.f;
1431 float intercepted_leaf_diffuse_13 = 0.f;
1432 for (
int i = 0; i < UUIDs_leaf_13.size(); i++) {
1435 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_direct", flux);
1436 intercepted_leaf_direct_13 += flux * area / D_13 / D_13;
1437 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_diffuse", flux);
1438 intercepted_leaf_diffuse_13 += flux * area / D_13 / D_13;
1441 float intercepted_ground_direct_13 = 0.f;
1442 float intercepted_ground_diffuse_13 = 0.f;
1443 for (
int i = 0; i < UUIDs_ground_13.size(); i++) {
1446 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_direct", flux_dir);
1448 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_diffuse", flux_diff);
1450 intercepted_ground_direct_13 += flux_dir * area / D_13 / D_13;
1451 intercepted_ground_diffuse_13 += flux_diff * area / D_13 / D_13;
1454 intercepted_ground_direct_13 = 1.f - intercepted_ground_direct_13;
1455 intercepted_ground_diffuse_13 = 1.f - intercepted_ground_diffuse_13;
1458 float dtheta = 0.5 *
M_PI / float(N);
1460 float intercepted_theoretical_diffuse_13 = 0.f;
1461 for (
int i = 0; i < N; i++) {
1462 float theta = (i + 0.5f) * dtheta;
1463 intercepted_theoretical_diffuse_13 += 2.f * (1.f - exp(-0.5 * LAI_13 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1466 float intercepted_theoretical_direct_13 = 1.f - exp(-0.5 * LAI_13 / cos(theta_s));
1468 DOCTEST_CHECK(fabsf(intercepted_ground_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1469 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1470 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_13 - intercepted_theoretical_diffuse_13) <= 2.f * error_threshold);
1471 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_13 - intercepted_theoretical_diffuse_13) <= 4.f * error_threshold);
1474GPU_TEST_CASE(
"RadiationModel Anisotropic Diffuse Radiation Horizontal Patch") {
1475 float error_threshold = 0.005;
1477 uint Ndiffuse_14 = 50000;
1481 std::vector<float> K_14;
1482 K_14.push_back(0.f);
1483 K_14.push_back(0.25f);
1484 K_14.push_back(1.f);
1486 std::vector<float> thetas_14;
1487 thetas_14.push_back(0.f);
1488 thetas_14.push_back(0.25 * M_PI);
1493 RadiationModel radiation_14 = RadiationModelTestHelper::createWithSharedDevice(&context_14);
1503 for (
int t = 0; t < thetas_14.size(); t++) {
1504 for (
int k = 0; k < K_14.size(); k++) {
1506 radiation_14.
runBand(
"diffuse");
1511 DOCTEST_CHECK(fabsf(Rdiff - 1.f) <= 2.f * error_threshold);
1516GPU_TEST_CASE(
"RadiationModel Prague Sky Diffuse Radiation Normalization") {
1517 float error_threshold = 0.015;
1519 uint Ndiffuse_prague = 100000;
1526 std::vector<std::vector<float>> prague_test_conditions;
1529 std::vector<float> clear_sky;
1530 clear_sky.push_back(3.0f);
1531 clear_sky.push_back(15.0f);
1532 clear_sky.push_back(1.5f);
1533 prague_test_conditions.push_back(clear_sky);
1536 std::vector<float> turbid_sky;
1537 turbid_sky.push_back(8.0f);
1538 turbid_sky.push_back(10.0f);
1539 turbid_sky.push_back(2.5f);
1540 prague_test_conditions.push_back(turbid_sky);
1543 std::vector<float> overcast_sky;
1544 overcast_sky.push_back(0.5f);
1545 overcast_sky.push_back(30.0f);
1546 overcast_sky.push_back(1.2f);
1547 prague_test_conditions.push_back(overcast_sky);
1552 RadiationModel radiation_prague = RadiationModelTestHelper::createWithSharedDevice(&context_prague);
1563 std::vector<helios::vec2> diffuse_spectrum_prague = {{400, 1.0}, {550, 1.0}, {700, 1.0}};
1564 context_prague.
setGlobalData(
"prague_test_diffuse_spectrum", diffuse_spectrum_prague);
1572 context_prague.
setGlobalData(
"prague_sky_visibility_km", 50.0f);
1573 context_prague.
setGlobalData(
"prague_sky_ground_albedo", 0.2f);
1575 for (
size_t cond = 0; cond < prague_test_conditions.size(); cond++) {
1576 float circ_str = prague_test_conditions[cond][0];
1577 float circ_width = prague_test_conditions[cond][1];
1578 float horiz_bright = prague_test_conditions[cond][2];
1582 float integral = 0.0f;
1584 for (
int j = 0; j < N; ++j) {
1585 for (
int i = 0; i < N; ++i) {
1586 float theta = 0.5f *
M_PI * (i + 0.5f) / N;
1587 float phi = 2.0f *
M_PI * (j + 0.5f) / N;
1591 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));
1592 float gamma = std::acos(cos_gamma) * 180.0f /
M_PI;
1595 float cos_theta = std::max(0.0f, dir.z);
1596 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
1597 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
1598 float pattern = circ_term * horizon_term;
1600 integral += pattern * std::cos(theta) * std::sin(theta) * (
M_PI / (2.0f * N)) * (2.0f *
M_PI / N);
1603 float normalization = 1.0f / std::max(integral, 1e-10f);
1606 std::vector<float> prague_params;
1607 prague_params.push_back(550.0f);
1608 prague_params.push_back(0.1f);
1609 prague_params.push_back(circ_str);
1610 prague_params.push_back(circ_width);
1611 prague_params.push_back(horiz_bright);
1612 prague_params.push_back(normalization);
1614 context_prague.
setGlobalData(
"prague_sky_spectral_params", prague_params);
1617 radiation_prague.
runBand(
"diffuse");
1621 context_prague.
getPrimitiveData(UUID_prague,
"radiation_flux_diffuse", Rdiff_prague);
1625 DOCTEST_CHECK(fabsf(Rdiff_prague - 1.f) <= 2.f * error_threshold);
1629GPU_TEST_CASE(
"RadiationModel Prague Sky Angular Distribution") {
1649 context.setPrimitiveData(UUID,
"twosided_flag",
uint(0));
1652 radiation.disableMessages();
1654 radiation.addRadiationBand(
"diffuse");
1655 radiation.disableEmission(
"diffuse");
1656 radiation.setDiffuseRayCount(
"diffuse", 200000);
1657 radiation.setDiffuseRadiationFlux(
"diffuse", 1.f);
1660 std::vector<helios::vec2> diffuse_spectrum = {{400, 1.0}, {550, 1.0}, {700, 1.0}};
1661 context.setGlobalData(
"prague_angular_test_spectrum", diffuse_spectrum);
1662 radiation.setDiffuseSpectrum(
"prague_angular_test_spectrum");
1664 radiation.updateGeometry();
1670 context.setGlobalData(
"prague_sky_valid", 1);
1671 context.setGlobalData(
"prague_sky_sun_direction", sun_dir);
1672 context.setGlobalData(
"prague_sky_visibility_km", 50.0f);
1673 context.setGlobalData(
"prague_sky_ground_albedo", 0.2f);
1677 std::vector<float> prague_params;
1678 prague_params.push_back(550.0f);
1679 prague_params.push_back(0.1f);
1680 prague_params.push_back(8.0f);
1681 prague_params.push_back(10.0f);
1682 prague_params.push_back(1.0f);
1683 prague_params.push_back(0.0f);
1684 context.setGlobalData(
"prague_sky_spectral_params", prague_params);
1686 radiation.runBand(
"diffuse");
1689 context.getPrimitiveData(UUID,
"radiation_flux_diffuse", flux);
1694 DOCTEST_CHECK(flux < 0.97f);
1695 DOCTEST_CHECK(flux > 0.70f);
1698GPU_TEST_CASE(
"RadiationModel Disk Radiation Source Above Circular Element") {
1699 float error_threshold = 0.005;
1701 uint Ndirect_15 = 10000;
1708 RadiationModel radiation_15 = RadiationModelTestHelper::createWithSharedDevice(&context_15);
1721 radiation_15.
runBand(
"light");
1726 float R1_15 = r1_15 / a_15;
1727 float R2_15 = r2_15 / a_15;
1728 float X_15 = 1.f + (1.f + R2_15 * R2_15) / (R1_15 * R1_15);
1729 float F12_exact_15 = 0.5f * (X_15 - sqrtf(X_15 * X_15 - 4.f * powf(R2_15 / R1_15, 2)));
1731 DOCTEST_CHECK(fabs(F12_15 - F12_exact_15 * r1_15 * r1_15 / r2_15 / r2_15) <= 2.f * error_threshold);
1734GPU_TEST_CASE(
"RadiationModel Rectangular Radiation Source Above Patch") {
1735 float error_threshold = 0.01;
1737 uint Ndirect_16 = 50000;
1744 RadiationModel radiation_16 = RadiationModelTestHelper::createWithSharedDevice(&context_16);
1757 radiation_16.
runBand(
"light");
1762 float X_16 = a_16 / c_16;
1763 float Y_16 = b_16 / c_16;
1764 float X2_16 = X_16 * X_16;
1765 float Y2_16 = Y_16 * Y_16;
1767 float F12_exact_16 = 2.0f / float(M_PI * X_16 * Y_16) *
1768 (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)) -
1769 X_16 * atanf(X_16) - Y_16 * atanf(Y_16));
1771 DOCTEST_CHECK(fabs(F12_16 - F12_exact_16) <= error_threshold);
1774GPU_TEST_CASE(
"RadiationModel ROMC Camera Test Verification") {
1776 float sunzenithd = 30;
1777 float reflectivityleaf = 0.02;
1778 float transmissivityleaf = 0.01;
1779 std::string bandname =
"RED";
1781 float viewazimuth = 0;
1782 float heightscene = 30.f;
1783 float rangescene = 100.f;
1784 std::vector<float> viewangles = {-75, 0, 36};
1785 float sunazimuth = 0;
1787 std::vector<float> referencevalues = {21.f, 71.6f, 87.2f};
1790 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},
1791 {33.5709, -6.31039, 14.5332}, {11.9126, 8.32062, 12.1220}, {32.4756, -26.9023, 16.3684}};
1793 for (
int w = -1; w < 2; w++) {
1795 for (
auto &CSposition: CSpositions) {
1796 vec3 transpos = movew +
make_vec3(CSposition.at(0), CSposition.at(1), CSposition.at(2));
1798 std::vector<uint> iCUUIDsn = cameracalibration.readROMCCanopy();
1801 context_17.
setPrimitiveData(iCUUIDsn,
"reflectivity_spectrum",
"leaf_reflectivity");
1802 context_17.
setPrimitiveData(iCUUIDsn,
"transmissivity_spectrum",
"leaf_transmissivity");
1807 std::vector<helios::vec2> leafspectrarho(2200);
1808 std::vector<helios::vec2> leafspectratau(2200);
1809 std::vector<helios::vec2> sourceintensity(2200);
1810 for (
int i = 0; i < leafspectrarho.size(); i++) {
1811 leafspectrarho.at(i).x = float(301 + i);
1812 leafspectrarho.at(i).y = reflectivityleaf;
1813 leafspectratau.at(i).x = float(301 + i);
1814 leafspectratau.at(i).y = transmissivityleaf;
1815 sourceintensity.at(i).x = float(301 + i);
1816 sourceintensity.at(i).y = 1;
1818 context_17.
setGlobalData(
"leaf_reflectivity", leafspectrarho);
1819 context_17.
setGlobalData(
"leaf_transmissivity", leafspectratau);
1820 context_17.
setGlobalData(
"camera_response", sourceintensity);
1821 context_17.
setGlobalData(
"source_intensity", sourceintensity);
1825 std::vector<std::string> cameralabels;
1826 RadiationModel radiation_17 = RadiationModelTestHelper::createWithSharedDevice(&context_17);
1828 for (
float viewangle: viewangles) {
1831 vec3 camera_position = 100000 * camerarotation + camera_lookat;
1836 cameraproperties.
HFOV = 0.02864786f * 2.f;
1839 std::string cameralabel =
"ROMC" + std::to_string(viewangle);
1840 radiation_17.
addRadiationCamera(cameralabel, {bandname}, camera_position, camera_lookat, cameraproperties, 60);
1841 cameralabels.push_back(cameralabel);
1853 for (
const auto &cameralabel: cameralabels) {
1857 radiation_17.
runBand(bandname);
1860 std::vector<float> camera_data;
1861 std::vector<uint> camera_UUID;
1863 for (
int i = 0; i < cameralabels.size(); i++) {
1864 std::string global_data_label =
"camera_" + cameralabels.at(i) +
"_" + bandname;
1865 std::string global_UUID =
"camera_" + cameralabels.at(i) +
"_pixel_UUID";
1866 context_17.
getGlobalData(global_data_label.c_str(), camera_data);
1868 float camera_all_data = 0;
1869 int filtered_count = 0, uuid_zero_count = 0, uuid_invalid_count = 0;
1870 float unfiltered_sum = 0, uuid_zero_sum = 0;
1871 for (
int v = 0; v < camera_data.size(); v++) {
1872 if (camera_data.at(v) > 0) {
1873 unfiltered_sum += camera_data.at(v);
1874 uint raw_uuid = camera_UUID.at(v);
1875 if (raw_uuid == 0) {
1877 uuid_zero_sum += camera_data.at(v);
1879 uint iUUID = raw_uuid - 1;
1881 camera_all_data += camera_data.at(v);
1884 uuid_invalid_count++;
1889 cameravalue = std::abs(referencevalues.at(i) - camera_all_data);
1890 DOCTEST_CHECK(cameravalue <= 1.5f);
1894GPU_TEST_CASE(
"RadiationModel Spectral Integration and Interpolation Tests") {
1898 radiation.disableMessages();
1902 std::vector<helios::vec2> test_spectrum;
1903 test_spectrum.push_back(
make_vec2(400, 0.1f));
1904 test_spectrum.push_back(
make_vec2(500, 0.5f));
1905 test_spectrum.push_back(
make_vec2(600, 0.3f));
1906 test_spectrum.push_back(
make_vec2(700, 0.2f));
1909 float full_integral = radiation.integrateSpectrum(test_spectrum);
1911 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;
1912 DOCTEST_CHECK(std::abs(full_integral - expected_integral) < 1e-5f);
1916 float partial_integral = radiation.integrateSpectrum(test_spectrum, 450, 650);
1918 DOCTEST_CHECK(std::abs(partial_integral - full_integral) < 1e-5f);
1923 std::vector<helios::vec2> source_spectrum;
1924 source_spectrum.push_back(
make_vec2(400, 1.0f));
1925 source_spectrum.push_back(
make_vec2(500, 2.0f));
1926 source_spectrum.push_back(
make_vec2(600, 1.5f));
1927 source_spectrum.push_back(
make_vec2(700, 0.5f));
1929 std::vector<helios::vec2> surface_spectrum;
1930 surface_spectrum.push_back(
make_vec2(400, 0.2f));
1931 surface_spectrum.push_back(
make_vec2(500, 0.6f));
1932 surface_spectrum.push_back(
make_vec2(600, 0.4f));
1933 surface_spectrum.push_back(
make_vec2(700, 0.1f));
1936 radiation.setSourceSpectrum(source_ID, source_spectrum);
1938 float integrated_product = radiation.integrateSpectrum(source_ID, surface_spectrum, 400, 700);
1941 DOCTEST_CHECK(integrated_product > 0.0f);
1942 DOCTEST_CHECK(integrated_product <= 1.0f);
1947 std::vector<helios::vec2> surface_spectrum;
1948 surface_spectrum.push_back(
make_vec2(400, 0.3f));
1949 surface_spectrum.push_back(
make_vec2(500, 0.7f));
1950 surface_spectrum.push_back(
make_vec2(600, 0.5f));
1951 surface_spectrum.push_back(
make_vec2(700, 0.2f));
1953 std::vector<helios::vec2> camera_response;
1954 camera_response.push_back(
make_vec2(400, 0.1f));
1955 camera_response.push_back(
make_vec2(500, 0.8f));
1956 camera_response.push_back(
make_vec2(600, 0.9f));
1957 camera_response.push_back(
make_vec2(700, 0.3f));
1959 float camera_integrated = radiation.integrateSpectrum(surface_spectrum, camera_response);
1960 DOCTEST_CHECK(camera_integrated >= 0.0f);
1961 DOCTEST_CHECK(camera_integrated <= 1.0f);
1965GPU_TEST_CASE(
"RadiationModel Spectral Radiative Properties Setting and Validation") {
1969 radiation.disableMessages();
1977 std::vector<helios::vec2> leaf_reflectivity;
1978 leaf_reflectivity.push_back(
make_vec2(400, 0.05f));
1979 leaf_reflectivity.push_back(
make_vec2(500, 0.10f));
1980 leaf_reflectivity.push_back(
make_vec2(600, 0.08f));
1981 leaf_reflectivity.push_back(
make_vec2(700, 0.45f));
1982 leaf_reflectivity.push_back(
make_vec2(800, 0.50f));
1984 std::vector<helios::vec2> leaf_transmissivity;
1985 leaf_transmissivity.push_back(
make_vec2(400, 0.02f));
1986 leaf_transmissivity.push_back(
make_vec2(500, 0.05f));
1987 leaf_transmissivity.push_back(
make_vec2(600, 0.04f));
1988 leaf_transmissivity.push_back(
make_vec2(700, 0.40f));
1989 leaf_transmissivity.push_back(
make_vec2(800, 0.45f));
1991 context.setGlobalData(
"test_leaf_reflectivity", leaf_reflectivity);
1992 context.setGlobalData(
"test_leaf_transmissivity", leaf_transmissivity);
1995 context.setPrimitiveData(patch_UUID,
"reflectivity_spectrum",
"test_leaf_reflectivity");
1996 context.setPrimitiveData(patch_UUID,
"transmissivity_spectrum",
"test_leaf_transmissivity");
1999 std::string refl_spectrum_label;
2000 context.getPrimitiveData(patch_UUID,
"reflectivity_spectrum", refl_spectrum_label);
2001 DOCTEST_CHECK(refl_spectrum_label ==
"test_leaf_reflectivity");
2003 std::string trans_spectrum_label;
2004 context.getPrimitiveData(patch_UUID,
"transmissivity_spectrum", trans_spectrum_label);
2005 DOCTEST_CHECK(trans_spectrum_label ==
"test_leaf_transmissivity");
2008 std::vector<helios::vec2> retrieved_refl;
2009 context.getGlobalData(
"test_leaf_reflectivity", retrieved_refl);
2010 DOCTEST_CHECK(retrieved_refl.size() == leaf_reflectivity.size());
2012 for (
size_t i = 0; i < retrieved_refl.size(); i++) {
2013 DOCTEST_CHECK(std::abs(retrieved_refl[i].x - leaf_reflectivity[i].x) < 1e-5f);
2014 DOCTEST_CHECK(std::abs(retrieved_refl[i].y - leaf_reflectivity[i].y) < 1e-5f);
2020 radiation.addRadiationBand(
"VIS", 400, 700);
2021 radiation.addRadiationBand(
"NIR", 700, 900);
2024 std::vector<helios::vec2> solar_spectrum;
2025 solar_spectrum.push_back(
make_vec2(400, 1.5f));
2026 solar_spectrum.push_back(
make_vec2(500, 2.0f));
2027 solar_spectrum.push_back(
make_vec2(600, 1.8f));
2028 solar_spectrum.push_back(
make_vec2(700, 1.2f));
2029 solar_spectrum.push_back(
make_vec2(800, 1.0f));
2030 solar_spectrum.push_back(
make_vec2(900, 0.8f));
2033 radiation.setSourceSpectrum(sun_source, solar_spectrum);
2035 radiation.setScatteringDepth(
"VIS", 0);
2036 radiation.setScatteringDepth(
"NIR", 0);
2037 radiation.disableEmission(
"VIS");
2038 radiation.disableEmission(
"NIR");
2041 radiation.updateGeometry();
2045 bool has_refl_spectrum =
context.doesPrimitiveDataExist(patch_UUID,
"reflectivity_spectrum");
2046 bool has_trans_spectrum =
context.doesPrimitiveDataExist(patch_UUID,
"transmissivity_spectrum");
2049 DOCTEST_CHECK(has_refl_spectrum);
2050 DOCTEST_CHECK(has_trans_spectrum);
2055 std::vector<helios::vec2> rgb_red_response;
2056 rgb_red_response.push_back(
make_vec2(400, 0.0f));
2057 rgb_red_response.push_back(
make_vec2(500, 0.1f));
2058 rgb_red_response.push_back(
make_vec2(600, 0.6f));
2059 rgb_red_response.push_back(
make_vec2(700, 0.9f));
2060 rgb_red_response.push_back(
make_vec2(800, 0.1f));
2062 context.setGlobalData(
"rgb_red_response", rgb_red_response);
2065 camera_properties.camera_resolution =
make_int2(10, 10);
2066 camera_properties.HFOV = 45.0f *
M_PI / 180.0f;
2068 radiation.addRadiationCamera(
"test_camera", {
"VIS"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_properties, 1);
2070 radiation.setCameraSpectralResponse(
"test_camera",
"VIS",
"rgb_red_response");
2074 radiation.updateGeometry();
2078 DOCTEST_CHECK(
true);
2082GPU_TEST_CASE(
"RadiationModel Spectral Edge Cases and Error Handling") {
2086 radiation.disableMessages();
2090 std::vector<helios::vec2> empty_spectrum;
2093 bool caught_error =
false;
2095 float integral = radiation.integrateSpectrum(empty_spectrum);
2097 caught_error =
true;
2099 DOCTEST_CHECK(caught_error);
2104 std::vector<helios::vec2> single_point;
2105 single_point.push_back(
make_vec2(550, 0.5f));
2107 bool caught_error =
false;
2109 float integral = radiation.integrateSpectrum(single_point);
2111 caught_error =
true;
2113 DOCTEST_CHECK(caught_error);
2118 std::vector<helios::vec2> test_spectrum;
2119 test_spectrum.push_back(
make_vec2(400, 0.2f));
2120 test_spectrum.push_back(
make_vec2(600, 0.8f));
2121 test_spectrum.push_back(
make_vec2(800, 0.3f));
2123 bool caught_error =
false;
2126 float integral = radiation.integrateSpectrum(test_spectrum, 700, 500);
2128 caught_error =
true;
2130 DOCTEST_CHECK(caught_error);
2132 caught_error =
false;
2135 float integral = radiation.integrateSpectrum(test_spectrum, 600, 600);
2137 caught_error =
true;
2139 DOCTEST_CHECK(caught_error);
2144 std::vector<helios::vec2> non_monotonic;
2145 non_monotonic.push_back(
make_vec2(500, 0.3f));
2146 non_monotonic.push_back(
make_vec2(400, 0.5f));
2147 non_monotonic.push_back(
make_vec2(600, 0.2f));
2151 bool function_completed =
true;
2153 context.setGlobalData(
"non_monotonic_spectrum", non_monotonic);
2155 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"non_monotonic_spectrum");
2157 radiation.addRadiationBand(
"test", 400, 700);
2158 radiation.updateGeometry();
2160 function_completed =
false;
2163 DOCTEST_CHECK(function_completed);
2168 std::vector<helios::vec2> limited_spectrum;
2169 limited_spectrum.push_back(
make_vec2(500, 0.3f));
2170 limited_spectrum.push_back(
make_vec2(600, 0.7f));
2173 float extended_integral = radiation.integrateSpectrum(limited_spectrum, 400, 800);
2174 float limited_integral = radiation.integrateSpectrum(limited_spectrum, 500, 600);
2177 DOCTEST_CHECK(extended_integral == 0.0f);
2178 DOCTEST_CHECK(limited_integral > 0.0f);
2182GPU_TEST_CASE(
"RadiationModel Spectral Caching and Performance Validation") {
2186 radiation.disableMessages();
2191 std::vector<helios::vec2> common_spectrum;
2192 common_spectrum.push_back(
make_vec2(400, 0.1f));
2193 common_spectrum.push_back(
make_vec2(500, 0.5f));
2194 common_spectrum.push_back(
make_vec2(600, 0.3f));
2195 common_spectrum.push_back(
make_vec2(700, 0.2f));
2197 context.setGlobalData(
"common_leaf_spectrum", common_spectrum);
2200 std::vector<uint> patch_UUIDs;
2201 for (
int i = 0; i < 10; i++) {
2203 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"common_leaf_spectrum");
2204 context.setPrimitiveData(patch,
"transmissivity_spectrum",
"common_leaf_spectrum");
2205 patch_UUIDs.push_back(patch);
2209 radiation.addRadiationBand(
"test_band", 400, 700);
2211 radiation.setSourceSpectrum(source, common_spectrum);
2213 radiation.disableEmission(
"test_band");
2214 radiation.setScatteringDepth(
"test_band", 0);
2217 auto start_time = std::chrono::high_resolution_clock::now();
2218 radiation.updateGeometry();
2219 auto end_time = std::chrono::high_resolution_clock::now();
2221 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
2224 DOCTEST_CHECK(duration.count() < 10000000);
2227 for (
uint patch_UUID: patch_UUIDs) {
2229 bool has_spectrum =
context.doesPrimitiveDataExist(patch_UUID,
"reflectivity_spectrum");
2230 DOCTEST_CHECK(has_spectrum);
2235GPU_TEST_CASE(
"RadiationModel Spectral Library Integration") {
2239 radiation.disableMessages();
2247 bool library_available =
false;
2249 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"leaf_reflectivity");
2250 library_available =
context.doesGlobalDataExist(
"leaf_reflectivity");
2252 library_available =
false;
2255 if (library_available) {
2257 radiation.addRadiationBand(
"test", 400, 800);
2258 radiation.updateGeometry();
2260 std::string spectrum_label;
2261 context.getPrimitiveData(patch,
"reflectivity_spectrum", spectrum_label);
2262 DOCTEST_CHECK(spectrum_label ==
"leaf_reflectivity");
2265 DOCTEST_CHECK(
true);
2270GPU_TEST_CASE(
"RadiationModel Multi-Spectrum Primitive Assignment") {
2274 radiation.disableMessages();
2277 std::vector<helios::vec2> red_spectrum;
2278 red_spectrum.push_back(
make_vec2(400, 0.1f));
2279 red_spectrum.push_back(
make_vec2(500, 0.1f));
2280 red_spectrum.push_back(
make_vec2(600, 0.8f));
2281 red_spectrum.push_back(
make_vec2(700, 0.9f));
2283 std::vector<helios::vec2> green_spectrum;
2284 green_spectrum.push_back(
make_vec2(400, 0.1f));
2285 green_spectrum.push_back(
make_vec2(500, 0.8f));
2286 green_spectrum.push_back(
make_vec2(600, 0.9f));
2287 green_spectrum.push_back(
make_vec2(700, 0.1f));
2289 std::vector<helios::vec2> blue_spectrum;
2290 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2291 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2292 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2293 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2296 context.setGlobalData(
"red_spectrum", red_spectrum);
2297 context.setGlobalData(
"green_spectrum", green_spectrum);
2298 context.setGlobalData(
"blue_spectrum", blue_spectrum);
2301 std::vector<uint> red_patches, green_patches, blue_patches;
2304 for (
int i = 0; i < 5; i++) {
2306 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"red_spectrum");
2307 red_patches.push_back(patch);
2311 for (
int i = 0; i < 5; i++) {
2313 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2314 green_patches.push_back(patch);
2318 for (
int i = 0; i < 5; i++) {
2320 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"blue_spectrum");
2321 blue_patches.push_back(patch);
2325 radiation.addRadiationBand(
"R", 600, 700);
2326 radiation.addRadiationBand(
"G", 500, 600);
2327 radiation.addRadiationBand(
"B", 400, 500);
2330 radiation.setDiffuseRayCount(
"R", 10000);
2331 radiation.setDiffuseRayCount(
"G", 10000);
2332 radiation.setDiffuseRayCount(
"B", 10000);
2336 std::vector<helios::vec2> uniform_spectrum;
2337 uniform_spectrum.push_back(
make_vec2(300, 1.0f));
2338 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2339 radiation.setSourceSpectrum(source, uniform_spectrum);
2340 radiation.setSourceFlux(source,
"R", 1000.0f);
2341 radiation.setSourceFlux(source,
"G", 1000.0f);
2342 radiation.setSourceFlux(source,
"B", 1000.0f);
2343 radiation.setDirectRayCount(
"R", 1000);
2344 radiation.setDirectRayCount(
"G", 1000);
2345 radiation.setDirectRayCount(
"B", 1000);
2349 std::vector<helios::vec2> camera_spectrum;
2350 camera_spectrum.push_back(
make_vec2(400, 0.3f));
2351 camera_spectrum.push_back(
make_vec2(500, 0.9f));
2352 camera_spectrum.push_back(
make_vec2(600, 0.8f));
2353 camera_spectrum.push_back(
make_vec2(700, 0.2f));
2354 context.setGlobalData(
"camera1_spectrum", camera_spectrum);
2357 std::vector<helios::vec2> camera_spectrum2;
2358 camera_spectrum2.push_back(
make_vec2(400, 0.2f));
2359 camera_spectrum2.push_back(
make_vec2(500, 0.3f));
2360 camera_spectrum2.push_back(
make_vec2(600, 0.8f));
2361 camera_spectrum2.push_back(
make_vec2(700, 0.9f));
2362 context.setGlobalData(
"camera2_spectrum", camera_spectrum2);
2364 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2367 camera_props.
HFOV = 2.0f;
2369 radiation.addRadiationCamera(
"camera1", band_labels,
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2370 radiation.setCameraSpectralResponse(
"camera1",
"R",
"camera1_spectrum");
2371 radiation.setCameraSpectralResponse(
"camera1",
"G",
"camera1_spectrum");
2372 radiation.setCameraSpectralResponse(
"camera1",
"B",
"camera1_spectrum");
2374 radiation.addRadiationCamera(
"camera2", band_labels,
make_vec3(5, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2375 radiation.setCameraSpectralResponse(
"camera2",
"R",
"camera2_spectrum");
2376 radiation.setCameraSpectralResponse(
"camera2",
"G",
"camera2_spectrum");
2377 radiation.setCameraSpectralResponse(
"camera2",
"B",
"camera2_spectrum");
2379 radiation.disableEmission(
"R");
2380 radiation.disableEmission(
"G");
2381 radiation.disableEmission(
"B");
2382 radiation.setScatteringDepth(
"R", 1);
2383 radiation.setScatteringDepth(
"G", 1);
2384 radiation.setScatteringDepth(
"B", 1);
2387 radiation.updateGeometry();
2390 radiation.runBand(
"R");
2391 radiation.runBand(
"G");
2392 radiation.runBand(
"B");
2396 float red_patch_R_flux = 0, red_patch_G_flux = 0, red_patch_B_flux = 0;
2397 for (
uint patch: red_patches) {
2398 float flux_R, flux_G, flux_B;
2399 context.getPrimitiveData(patch,
"radiation_flux_R", flux_R);
2400 context.getPrimitiveData(patch,
"radiation_flux_G", flux_G);
2401 context.getPrimitiveData(patch,
"radiation_flux_B", flux_B);
2402 red_patch_R_flux += flux_R;
2403 red_patch_G_flux += flux_G;
2404 red_patch_B_flux += flux_B;
2406 red_patch_R_flux /= red_patches.size();
2407 red_patch_G_flux /= red_patches.size();
2408 red_patch_B_flux /= red_patches.size();
2411 float green_patch_R_flux = 0, green_patch_G_flux = 0, green_patch_B_flux = 0;
2412 for (
uint patch: green_patches) {
2413 float flux_R, flux_G, flux_B;
2414 context.getPrimitiveData(patch,
"radiation_flux_R", flux_R);
2415 context.getPrimitiveData(patch,
"radiation_flux_G", flux_G);
2416 context.getPrimitiveData(patch,
"radiation_flux_B", flux_B);
2417 green_patch_R_flux += flux_R;
2418 green_patch_G_flux += flux_G;
2419 green_patch_B_flux += flux_B;
2421 green_patch_R_flux /= green_patches.size();
2422 green_patch_G_flux /= green_patches.size();
2423 green_patch_B_flux /= green_patches.size();
2426 float blue_patch_R_flux = 0, blue_patch_G_flux = 0, blue_patch_B_flux = 0;
2427 for (
uint patch: blue_patches) {
2428 float flux_R, flux_G, flux_B;
2429 context.getPrimitiveData(patch,
"radiation_flux_R", flux_R);
2430 context.getPrimitiveData(patch,
"radiation_flux_G", flux_G);
2431 context.getPrimitiveData(patch,
"radiation_flux_B", flux_B);
2432 blue_patch_R_flux += flux_R;
2433 blue_patch_G_flux += flux_G;
2434 blue_patch_B_flux += flux_B;
2436 blue_patch_R_flux /= blue_patches.size();
2437 blue_patch_G_flux /= blue_patches.size();
2438 blue_patch_B_flux /= blue_patches.size();
2442 DOCTEST_CHECK(red_patch_R_flux < red_patch_G_flux);
2443 DOCTEST_CHECK(red_patch_R_flux < red_patch_B_flux);
2446 DOCTEST_CHECK(green_patch_G_flux < green_patch_R_flux);
2447 DOCTEST_CHECK(green_patch_G_flux < green_patch_B_flux);
2450 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_R_flux);
2451 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_G_flux);
2454 for (
uint i = 1; i < red_patches.size(); i++) {
2455 float flux_R_0, flux_R_i;
2456 context.getPrimitiveData(red_patches[0],
"radiation_flux_R", flux_R_0);
2457 context.getPrimitiveData(red_patches[i],
"radiation_flux_R", flux_R_i);
2458 DOCTEST_CHECK(std::abs(flux_R_0 - flux_R_i) / flux_R_0 < 0.15f);
2462GPU_TEST_CASE(
"RadiationModel Band-Specific Camera Spectral Response") {
2466 radiation.disableMessages();
2470 std::vector<helios::vec2> red_spectrum;
2471 red_spectrum.push_back(
make_vec2(400, 0.1f));
2472 red_spectrum.push_back(
make_vec2(500, 0.1f));
2473 red_spectrum.push_back(
make_vec2(600, 0.8f));
2474 red_spectrum.push_back(
make_vec2(700, 0.9f));
2475 context.setGlobalData(
"red_spectrum", red_spectrum);
2478 std::vector<helios::vec2> green_spectrum;
2479 green_spectrum.push_back(
make_vec2(400, 0.1f));
2480 green_spectrum.push_back(
make_vec2(500, 0.8f));
2481 green_spectrum.push_back(
make_vec2(600, 0.9f));
2482 green_spectrum.push_back(
make_vec2(700, 0.1f));
2483 context.setGlobalData(
"green_spectrum", green_spectrum);
2486 std::vector<helios::vec2> blue_spectrum;
2487 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2488 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2489 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2490 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2491 context.setGlobalData(
"blue_spectrum", blue_spectrum);
2494 std::vector<uint> red_patches, green_patches, blue_patches, white_patches;
2497 for (
int i = 0; i < 2; i++) {
2499 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"red_spectrum");
2500 red_patches.push_back(patch);
2504 for (
int i = 0; i < 2; i++) {
2506 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2507 green_patches.push_back(patch);
2511 for (
int i = 0; i < 2; i++) {
2513 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"blue_spectrum");
2514 blue_patches.push_back(patch);
2518 for (
int i = 0; i < 2; i++) {
2520 context.setPrimitiveData(patch,
"reflectivity_spectrum",
"white_spectrum");
2521 white_patches.push_back(patch);
2525 radiation.addRadiationBand(
"R", 600, 700);
2526 radiation.addRadiationBand(
"G", 500, 600);
2527 radiation.addRadiationBand(
"B", 400, 500);
2530 radiation.setDiffuseRayCount(
"R", 10000);
2531 radiation.setDiffuseRayCount(
"G", 10000);
2532 radiation.setDiffuseRayCount(
"B", 10000);
2536 std::vector<helios::vec2> uniform_spectrum;
2537 uniform_spectrum.push_back(
make_vec2(350, 1.0f));
2538 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2539 radiation.setSourceSpectrum(source, uniform_spectrum);
2540 radiation.setSourceFlux(source,
"R", 1000.0f);
2541 radiation.setSourceFlux(source,
"G", 1000.0f);
2542 radiation.setSourceFlux(source,
"B", 1000.0f);
2546 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2549 camera_props.
HFOV = 2.0f;
2552 std::vector<helios::vec2> cam1_R_spectrum;
2553 cam1_R_spectrum.push_back(
make_vec2(600, 1.0f));
2554 cam1_R_spectrum.push_back(
make_vec2(700, 1.0f));
2555 context.setGlobalData(
"cam1_R_spectrum", cam1_R_spectrum);
2557 std::vector<helios::vec2> cam1_G_spectrum;
2558 cam1_G_spectrum.push_back(
make_vec2(500, 0.05f));
2559 cam1_G_spectrum.push_back(
make_vec2(600, 0.05f));
2560 context.setGlobalData(
"cam1_G_spectrum", cam1_G_spectrum);
2562 std::vector<helios::vec2> cam1_B_spectrum;
2563 cam1_B_spectrum.push_back(
make_vec2(400, 0.05f));
2564 cam1_B_spectrum.push_back(
make_vec2(500, 0.05f));
2565 context.setGlobalData(
"cam1_B_spectrum", cam1_B_spectrum);
2567 radiation.addRadiationCamera(
"camera1", band_labels,
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2568 radiation.setCameraSpectralResponse(
"camera1",
"R",
"cam1_R_spectrum");
2569 radiation.setCameraSpectralResponse(
"camera1",
"G",
"cam1_G_spectrum");
2570 radiation.setCameraSpectralResponse(
"camera1",
"B",
"cam1_B_spectrum");
2573 std::vector<helios::vec2> cam2_R_spectrum;
2574 cam2_R_spectrum.push_back(
make_vec2(600, 0.05f));
2575 cam2_R_spectrum.push_back(
make_vec2(700, 0.05f));
2576 context.setGlobalData(
"cam2_R_spectrum", cam2_R_spectrum);
2578 std::vector<helios::vec2> cam2_G_spectrum;
2579 cam2_G_spectrum.push_back(
make_vec2(500, 0.3f));
2580 cam2_G_spectrum.push_back(
make_vec2(600, 0.3f));
2581 context.setGlobalData(
"cam2_G_spectrum", cam2_G_spectrum);
2583 std::vector<helios::vec2> cam2_B_spectrum;
2584 cam2_B_spectrum.push_back(
make_vec2(400, 1.0f));
2585 cam2_B_spectrum.push_back(
make_vec2(500, 1.0f));
2586 context.setGlobalData(
"cam2_B_spectrum", cam2_B_spectrum);
2588 radiation.addRadiationCamera(
"camera2", band_labels,
make_vec3(5, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2589 radiation.setCameraSpectralResponse(
"camera2",
"R",
"cam2_R_spectrum");
2590 radiation.setCameraSpectralResponse(
"camera2",
"G",
"cam2_G_spectrum");
2591 radiation.setCameraSpectralResponse(
"camera2",
"B",
"cam2_B_spectrum");
2593 radiation.disableEmission(
"R");
2594 radiation.disableEmission(
"G");
2595 radiation.disableEmission(
"B");
2596 radiation.setScatteringDepth(
"R", 1);
2597 radiation.setScatteringDepth(
"G", 1);
2598 radiation.setScatteringDepth(
"B", 1);
2602 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
2605 radiation.runBand(
"R");
2606 radiation.runBand(
"G");
2607 radiation.runBand(
"B");
2610 uint red_patch = red_patches[0];
2611 float red_flux_R, red_flux_G, red_flux_B;
2612 context.getPrimitiveData(red_patch,
"radiation_flux_R", red_flux_R);
2613 context.getPrimitiveData(red_patch,
"radiation_flux_G", red_flux_G);
2614 context.getPrimitiveData(red_patch,
"radiation_flux_B", red_flux_B);
2616 uint green_patch = green_patches[0];
2617 float green_flux_R, green_flux_G, green_flux_B;
2618 context.getPrimitiveData(green_patch,
"radiation_flux_R", green_flux_R);
2619 context.getPrimitiveData(green_patch,
"radiation_flux_G", green_flux_G);
2620 context.getPrimitiveData(green_patch,
"radiation_flux_B", green_flux_B);
2622 uint blue_patch = blue_patches[0];
2623 float blue_flux_R, blue_flux_G, blue_flux_B;
2624 context.getPrimitiveData(blue_patch,
"radiation_flux_R", blue_flux_R);
2625 context.getPrimitiveData(blue_patch,
"radiation_flux_G", blue_flux_G);
2626 context.getPrimitiveData(blue_patch,
"radiation_flux_B", blue_flux_B);
2657GPU_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary") {
2661 radiation.disableMessages();
2664 vec3 position(0, 0, 5);
2665 vec3 lookat(0, 0, 0);
2670 radiation.addRadiationCameraFromLibrary(
"cam1",
"Canon_20D", position, lookat, 1);
2674 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2675 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2678 DOCTEST_CHECK(radiation.doesBandExist(
"red"));
2679 DOCTEST_CHECK(radiation.doesBandExist(
"green"));
2680 DOCTEST_CHECK(radiation.doesBandExist(
"blue"));
2683 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_red"));
2684 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_green"));
2685 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_blue"));
2688 DOCTEST_CHECK(
context.getGlobalDataType(
"Canon_20D_red") == HELIOS_TYPE_VEC2);
2689 DOCTEST_CHECK(
context.getGlobalDataType(
"Canon_20D_green") == HELIOS_TYPE_VEC2);
2690 DOCTEST_CHECK(
context.getGlobalDataType(
"Canon_20D_blue") == HELIOS_TYPE_VEC2);
2693 std::vector<vec2> red_response;
2694 context.getGlobalData(
"Canon_20D_red", red_response);
2695 DOCTEST_CHECK(red_response.size() == 33);
2698 DOCTEST_CHECK(red_response.front().x == 400.0f);
2699 DOCTEST_CHECK(red_response.back().x == 720.0f);
2703 radiation.addRadiationCameraFromLibrary(
"cam2",
"iPhone11", position, lookat, 1);
2704 DOCTEST_CHECK(std::find(radiation.getAllCameraLabels().begin(), radiation.getAllCameraLabels().end(),
"cam2") != radiation.getAllCameraLabels().end());
2707 DOCTEST_CHECK(
context.doesGlobalDataExist(
"iPhone11_red"));
2708 DOCTEST_CHECK(
context.doesGlobalDataExist(
"iPhone11_green"));
2709 DOCTEST_CHECK(
context.doesGlobalDataExist(
"iPhone11_blue"));
2714 DOCTEST_CHECK_THROWS_AS(radiation.addRadiationCameraFromLibrary(
"cam3",
"InvalidCamera", position, lookat, 1), std::runtime_error);
2718 vec3 cam_pos = radiation.getCameraPosition(
"cam1");
2719 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2720 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2721 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2724 vec3 cam_lookat = radiation.getCameraLookat(
"cam1");
2725 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2726 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2727 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2730 std::vector<std::string> available_cameras = {
"Canon_20D",
"Nikon_D700",
"Nikon_D50",
"iPhone11",
"iPhone12ProMAX"};
2732 for (
const auto &cam_name: available_cameras) {
2733 if (cam_name !=
"Canon_20D" && cam_name !=
"iPhone11") {
2734 std::string label =
"cam" + std::to_string(cam_count++);
2735 radiation.addRadiationCameraFromLibrary(label, cam_name, position, lookat, 1);
2736 DOCTEST_CHECK(std::find(radiation.getAllCameraLabels().begin(), radiation.getAllCameraLabels().end(), label) != radiation.getAllCameraLabels().end());
2741GPU_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary with custom band labels") {
2745 radiation.disableMessages();
2747 vec3 position(0, 0, 5);
2748 vec3 lookat(0, 0, 0);
2751 std::vector<std::string> custom_labels = {
"R_custom",
"G_custom",
"B_custom"};
2756 radiation.addRadiationCameraFromLibrary(
"cam_custom",
"Canon_20D", position, lookat, 1, custom_labels);
2760 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2761 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam_custom") != cameras.end());
2764 DOCTEST_CHECK(radiation.doesBandExist(
"R_custom"));
2765 DOCTEST_CHECK(radiation.doesBandExist(
"G_custom"));
2766 DOCTEST_CHECK(radiation.doesBandExist(
"B_custom"));
2769 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"red"));
2770 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"green"));
2771 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"blue"));
2774 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_red"));
2775 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_green"));
2776 DOCTEST_CHECK(
context.doesGlobalDataExist(
"Canon_20D_blue"));
2779 std::vector<vec2> red_response;
2780 context.getGlobalData(
"Canon_20D_red", red_response);
2781 DOCTEST_CHECK(red_response.size() == 33);
2786 std::vector<std::string> wrong_size = {
"A",
"B"};
2787 DOCTEST_CHECK_THROWS_AS(radiation.addRadiationCameraFromLibrary(
"cam_fail",
"Canon_20D", position, lookat, 1, wrong_size), std::runtime_error);
2792 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
2809 RadiationModel radiation3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
2812 std::vector<std::string> custom_labels2 = {
"NIR",
"VIS",
"UV"};
2831GPU_TEST_CASE(
"RadiationModel - updateCameraParameters") {
2835 radiation.disableMessages();
2838 radiation.addRadiationBand(
"red");
2839 radiation.addRadiationBand(
"green");
2840 radiation.addRadiationBand(
"blue");
2843 vec3 position(0, 0, 5);
2844 vec3 lookat(0, 0, 0);
2847 initial_props.
HFOV = 45.0f;
2851 initial_props.
model =
"TestCamera";
2853 std::vector<std::string> bands = {
"red",
"green",
"blue"};
2854 radiation.addRadiationCamera(
"cam1", bands, position, lookat, initial_props, 1);
2857 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2858 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2863 updated_props.
HFOV = 60.0f;
2867 updated_props.
model =
"UpdatedCamera";
2870 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", updated_props));
2873 vec3 cam_pos = radiation.getCameraPosition(
"cam1");
2874 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2875 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2876 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2878 vec3 cam_lookat = radiation.getCameraLookat(
"cam1");
2879 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2880 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2881 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2889 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"nonexistent_camera", props), std::runtime_error);
2896 invalid_props.
HFOV = 45.0f;
2898 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2905 invalid_props.
HFOV = 45.0f;
2907 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2914 invalid_props.
HFOV = 0.0f;
2916 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2923 invalid_props.
HFOV = 180.0f;
2925 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2932 invalid_props.
HFOV = 200.0f;
2934 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2941 invalid_props.
HFOV = -10.0f;
2943 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2950 edge_props.
HFOV = 0.001f;
2951 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", edge_props));
2958 edge_props.
HFOV = 179.999f;
2959 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", edge_props));
2963 DOCTEST_CHECK(radiation.doesBandExist(
"red"));
2964 DOCTEST_CHECK(radiation.doesBandExist(
"green"));
2965 DOCTEST_CHECK(radiation.doesBandExist(
"blue"));
2971 nonsquare_props.
HFOV = 70.0f;
2972 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", nonsquare_props));
2979 pinhole_props.
HFOV = 45.0f;
2981 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", pinhole_props));
2985 for (
int i = 0; i < 5; i++) {
2988 multi_update_props.
HFOV = 45.0f + i * 5.0f;
2989 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", multi_update_props));
2993 cameras = radiation.getAllCameraLabels();
2994 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2997GPU_TEST_CASE(
"RadiationModel - getCameraParameters") {
3001 radiation.disableMessages();
3004 radiation.addRadiationBand(
"red");
3005 radiation.addRadiationBand(
"green");
3006 radiation.addRadiationBand(
"blue");
3009 vec3 position(1, 2, 3);
3010 vec3 lookat(0, 0, 0);
3013 initial_props.
HFOV = 60.0f;
3017 initial_props.
model =
"TestCameraModel";
3019 std::vector<std::string> bands = {
"red",
"green",
"blue"};
3020 radiation.addRadiationCamera(
"test_cam", bands, position, lookat, initial_props, 1);
3023 CameraProperties retrieved_props = radiation.getCameraParameters(
"test_cam");
3026 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(initial_props.
HFOV).epsilon(0.001));
3030 DOCTEST_CHECK(retrieved_props.
model == initial_props.
model);
3034 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3039 updated_props.
HFOV = 75.0f;
3043 updated_props.
model =
"UpdatedModel";
3045 radiation.updateCameraParameters(
"test_cam", updated_props);
3046 retrieved_props = radiation.getCameraParameters(
"test_cam");
3050 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(updated_props.
HFOV).epsilon(0.001));
3054 DOCTEST_CHECK(retrieved_props.
model == updated_props.
model);
3058 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3063 DOCTEST_CHECK_THROWS_AS(radiation.getCameraParameters(
"nonexistent_camera"), std::runtime_error);
3067 CameraProperties roundtrip_props = radiation.getCameraParameters(
"test_cam");
3072 radiation.updateCameraParameters(
"test_cam", roundtrip_props);
3074 DOCTEST_CHECK(captured.empty());
3077 CameraProperties roundtrip_props2 = radiation.getCameraParameters(
"test_cam");
3081 DOCTEST_CHECK(roundtrip_props.
HFOV == doctest::Approx(roundtrip_props2.
HFOV).epsilon(0.001));
3085 DOCTEST_CHECK(roundtrip_props.
model == roundtrip_props2.
model);
3091 nonsquare_props.
HFOV = 90.0f;
3092 radiation.updateCameraParameters(
"test_cam", nonsquare_props);
3093 retrieved_props = radiation.getCameraParameters(
"test_cam");
3095 expected_aspect = 1280.0f / 720.0f;
3096 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
3101 pinhole_props.
HFOV = 45.0f;
3104 radiation.updateCameraParameters(
"test_cam", pinhole_props);
3105 retrieved_props = radiation.getCameraParameters(
"test_cam");
3107 DOCTEST_CHECK(retrieved_props.
lens_diameter == doctest::Approx(0.0f).epsilon(0.001));
3112 cam2_props.
HFOV = 50.0f;
3113 cam2_props.
model =
"Camera2Model";
3114 radiation.addRadiationCamera(
"test_cam2", bands, position, lookat, cam2_props, 1);
3118 cam3_props.
HFOV = 70.0f;
3119 cam3_props.
model =
"Camera3Model";
3120 radiation.addRadiationCamera(
"test_cam3", bands, position, lookat, cam3_props, 1);
3128 DOCTEST_CHECK(check_cam2.
HFOV == doctest::Approx(50.0f).epsilon(0.001));
3129 DOCTEST_CHECK(check_cam2.
model ==
"Camera2Model");
3133 DOCTEST_CHECK(check_cam3.
HFOV == doctest::Approx(70.0f).epsilon(0.001));
3134 DOCTEST_CHECK(check_cam3.
model ==
"Camera3Model");
3137DOCTEST_TEST_CASE(
"CameraCalibration Basic Functionality") {
3142 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
3143 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
3146 std::vector<uint> all_colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
3147 DOCTEST_CHECK(all_colorboard_UUIDs.size() == 24);
3150 std::vector<uint> all_UUIDs =
context.getAllUUIDs();
3151 DOCTEST_CHECK(all_UUIDs.size() >= 24);
3154 int patches_with_reflectivity = 0;
3155 for (
uint UUID: calibrite_UUIDs) {
3156 if (
context.doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
3157 patches_with_reflectivity++;
3160 DOCTEST_CHECK(patches_with_reflectivity == 24);
3164 std::vector<uint> spyder_UUIDs = calibration2.addSpyderCHECKRColorboard(
make_vec3(0.5, 0.5, 0.001), 0.05);
3165 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
3168 patches_with_reflectivity = 0;
3169 for (
uint UUID: spyder_UUIDs) {
3170 if (
context.doesPrimitiveDataExist(UUID,
"reflectivity_spectrum")) {
3171 patches_with_reflectivity++;
3174 DOCTEST_CHECK(patches_with_reflectivity == 24);
3177 std::vector<helios::vec2> test_spectrum;
3178 test_spectrum.push_back(
make_vec2(400.0f, 0.1f));
3179 test_spectrum.push_back(
make_vec2(500.0f, 0.5f));
3180 test_spectrum.push_back(
make_vec2(600.0f, 0.8f));
3181 test_spectrum.push_back(
make_vec2(700.0f, 0.3f));
3184 bool write_success = calibration.writeSpectralXMLfile(
"test_spectrum.xml",
"Test spectrum",
"test_label", &test_spectrum);
3185 DOCTEST_CHECK(write_success ==
true);
3188 std::remove(
"test_spectrum.xml");
3191DOCTEST_TEST_CASE(
"CameraCalibration DGK Integration") {
3202 std::vector<uint> colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
3204 DOCTEST_CHECK(colorboard_UUIDs.size() == 0);
3207 std::vector<uint> test_patches;
3208 for (
int i = 0; i < 18; i++) {
3210 test_patches.push_back(patch);
3212 context.setPrimitiveData(patch,
"colorboard_DGK",
uint(i));
3216 std::vector<uint> all_UUIDs =
context.getAllUUIDs();
3217 DOCTEST_CHECK(all_UUIDs.size() >= 18);
3220 int dgk_labeled_patches = 0;
3221 for (
uint UUID: test_patches) {
3222 if (
context.doesPrimitiveDataExist(UUID,
"colorboard_DGK")) {
3223 dgk_labeled_patches++;
3226 DOCTEST_CHECK(dgk_labeled_patches == 18);
3232DOCTEST_TEST_CASE(
"CameraCalibration Multiple Colorboards") {
3237 std::vector<uint> dgk_UUIDs = calibration.addDGKColorboard(
make_vec3(0, 0, 0.001), 0.05);
3238 DOCTEST_CHECK(dgk_UUIDs.size() == 18);
3240 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0.5, 0, 0.001), 0.05);
3241 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
3243 std::vector<uint> spyder_UUIDs = calibration.addSpyderCHECKRColorboard(
make_vec3(1.0, 0, 0.001), 0.05);
3244 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
3247 std::vector<uint> all_UUIDs = calibration.getAllColorBoardUUIDs();
3248 DOCTEST_CHECK(all_UUIDs.size() == 66);
3251 std::vector<std::string> detected_types = calibration.detectColorBoardTypes();
3252 DOCTEST_CHECK(detected_types.size() == 3);
3253 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"DGK") != detected_types.end());
3254 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"Calibrite") != detected_types.end());
3255 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"SpyderCHECKR") != detected_types.end());
3259 std::vector<uint> dgk_UUIDs_2;
3262 dgk_UUIDs_2 = calibration.addDGKColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
3264 DOCTEST_CHECK(dgk_UUIDs_2.size() == 18);
3267 std::vector<uint> all_UUIDs_2 = calibration.getAllColorBoardUUIDs();
3268 DOCTEST_CHECK(all_UUIDs_2.size() == 66);
3271 int dgk_labeled = 0, calibrite_labeled = 0, spyder_labeled = 0;
3272 std::vector<uint> context_UUIDs =
context.getAllUUIDs();
3273 for (
uint UUID: context_UUIDs) {
3274 if (
context.doesPrimitiveDataExist(UUID,
"colorboard_DGK")) {
3277 if (
context.doesPrimitiveDataExist(UUID,
"colorboard_Calibrite")) {
3278 calibrite_labeled++;
3280 if (
context.doesPrimitiveDataExist(UUID,
"colorboard_SpyderCHECKR")) {
3284 DOCTEST_CHECK(dgk_labeled == 18);
3285 DOCTEST_CHECK(calibrite_labeled == 24);
3286 DOCTEST_CHECK(spyder_labeled == 24);
3289GPU_TEST_CASE(
"RadiationModel CCM Export and Import") {
3295 std::vector<std::string> band_labels = {
"red",
"green",
"blue"};
3296 std::string camera_label =
"test_camera";
3301 camera_properties.camera_resolution = resolution;
3302 camera_properties.HFOV = 45.0f;
3304 camera_properties.focal_plane_distance = 1.0f;
3305 camera_properties.lens_diameter = 0.0f;
3310 size_t pixel_count = resolution.x * resolution.y;
3311 std::vector<float> red_data(pixel_count, 0.8f);
3312 std::vector<float> green_data(pixel_count, 0.6f);
3313 std::vector<float> blue_data(pixel_count, 0.4f);
3323 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}};
3325 std::string ccm_file_path =
"test_ccm_3x3.xml";
3331 std::ifstream test_file(ccm_file_path);
3332 DOCTEST_CHECK(test_file.good());
3336 std::string loaded_camera_label;
3340 DOCTEST_CHECK(loaded_camera_label == camera_label);
3341 DOCTEST_CHECK(loaded_matrix.size() == 3);
3342 DOCTEST_CHECK(loaded_matrix[0].size() == 3);
3345 for (
size_t i = 0; i < 3; i++) {
3346 for (
size_t j = 0; j < 3; j++) {
3347 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix[i][j]) < 1e-5f);
3352 std::remove(ccm_file_path.c_str());
3358 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}};
3360 std::string ccm_file_path =
"test_ccm_4x3.xml";
3366 std::string loaded_camera_label;
3369 DOCTEST_CHECK(loaded_camera_label == camera_label);
3370 DOCTEST_CHECK(loaded_matrix.size() == 3);
3371 DOCTEST_CHECK(loaded_matrix[0].size() == 4);
3374 for (
size_t i = 0; i < 3; i++) {
3375 for (
size_t j = 0; j < 4; j++) {
3376 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix_4x3[i][j]) < 1e-5f);
3381 std::remove(ccm_file_path.c_str());
3387 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}};
3389 std::string ccm_file_path =
"test_apply_ccm_3x3.xml";
3393 std::vector<float> initial_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3394 std::vector<float> initial_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3395 std::vector<float> initial_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3401 std::vector<float> corrected_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3402 std::vector<float> corrected_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3403 std::vector<float> corrected_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3407 float expected_red = test_matrix[0][0] * initial_red[0] + test_matrix[0][1] * initial_green[0] + test_matrix[0][2] * initial_blue[0];
3408 float expected_green = test_matrix[1][0] * initial_red[0] + test_matrix[1][1] * initial_green[0] + test_matrix[1][2] * initial_blue[0];
3409 float expected_blue = test_matrix[2][0] * initial_red[0] + test_matrix[2][1] * initial_green[0] + test_matrix[2][2] * initial_blue[0];
3411 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3412 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3413 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3416 std::remove(ccm_file_path.c_str());
3422 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}};
3424 std::string ccm_file_path =
"test_apply_ccm_4x3.xml";
3428 std::fill(red_data.begin(), red_data.end(), 0.7f);
3429 std::fill(green_data.begin(), green_data.end(), 0.5f);
3430 std::fill(blue_data.begin(), blue_data.end(), 0.3f);
3440 std::vector<float> corrected_red = radiationmodel.
getCameraPixelData(camera_label,
"red");
3441 std::vector<float> corrected_green = radiationmodel.
getCameraPixelData(camera_label,
"green");
3442 std::vector<float> corrected_blue = radiationmodel.
getCameraPixelData(camera_label,
"blue");
3445 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];
3446 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];
3447 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];
3449 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3450 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3451 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3454 std::remove(ccm_file_path.c_str());
3458GPU_TEST_CASE(
"RadiationModel CCM Error Handling") {
3464 std::string camera_label;
3465 bool exception_thrown =
false;
3468 }
catch (
const std::runtime_error &e) {
3469 exception_thrown =
true;
3470 std::string error_msg(e.what());
3471 DOCTEST_CHECK(error_msg.find(
"Failed to open file for reading") != std::string::npos);
3473 DOCTEST_CHECK(exception_thrown);
3478 std::string malformed_ccm_path =
"malformed_ccm.xml";
3479 std::ofstream malformed_file(malformed_ccm_path);
3480 malformed_file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3481 malformed_file <<
"<helios>\n";
3482 malformed_file <<
" <InvalidTag>\n";
3483 malformed_file <<
" <row>1.0 0.0 0.0</row>\n";
3484 malformed_file <<
" </InvalidTag>\n";
3485 malformed_file <<
"</helios>\n";
3486 malformed_file.close();
3488 std::string camera_label;
3489 bool exception_thrown =
false;
3492 }
catch (
const std::runtime_error &e) {
3493 exception_thrown =
true;
3494 std::string error_msg(e.what());
3495 DOCTEST_CHECK(error_msg.find(
"No matrix data found") != std::string::npos);
3497 DOCTEST_CHECK(exception_thrown);
3499 std::remove(malformed_ccm_path.c_str());
3504 std::string ccm_file_path =
"test_error_ccm.xml";
3505 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}};
3509 bool exception_thrown =
false;
3512 }
catch (
const std::runtime_error &e) {
3513 exception_thrown =
true;
3514 std::string error_msg(e.what());
3515 DOCTEST_CHECK(error_msg.find(
"Camera 'nonexistent_camera' does not exist") != std::string::npos);
3517 DOCTEST_CHECK(exception_thrown);
3519 std::remove(ccm_file_path.c_str());
3523GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation from Primitive Data") {
3530 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3531 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3532 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3534 context.setGlobalData(
"spectrum_age_0", spectrum_young);
3535 context.setGlobalData(
"spectrum_age_5", spectrum_mature);
3536 context.setGlobalData(
"spectrum_age_10", spectrum_old);
3546 context.setPrimitiveData(uuid0,
"age", 0.0f);
3547 context.setPrimitiveData(uuid1,
"age", 2.0f);
3548 context.setPrimitiveData(uuid2,
"age", 5.0f);
3549 context.setPrimitiveData(uuid3,
"age", 8.0f);
3550 context.setPrimitiveData(uuid4,
"age", 12.0f);
3553 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3554 std::vector<uint> uuids = {uuid0, uuid1, uuid2, uuid3, uuid4};
3555 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3556 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3565 radiationmodel.
runBand(
"PAR");
3568 std::string assigned_spectrum;
3569 context.getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
3570 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3572 context.getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
3573 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3575 context.getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
3576 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3578 context.getPrimitiveData(uuid3,
"reflectivity_spectrum", assigned_spectrum);
3579 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3581 context.getPrimitiveData(uuid4,
"reflectivity_spectrum", assigned_spectrum);
3582 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3586 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3588 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
3600 std::vector<uint> uuids = {uuid_a, uuid_b};
3601 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3602 std::vector<float> values = {0.0f, 10.0f};
3610 radiationmodel2.
runBand(
"PAR");
3612 std::string assigned_spectrum;
3613 context2.
getPrimitiveData(uuid_a,
"transmissivity_spectrum", assigned_spectrum);
3614 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3616 context2.
getPrimitiveData(uuid_b,
"transmissivity_spectrum", assigned_spectrum);
3617 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3621 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3623 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
3631 std::vector<uint> uuids = {uuid};
3632 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3633 std::vector<float> values = {0.0f};
3635 bool exception_thrown =
false;
3638 }
catch (
const std::runtime_error &e) {
3639 exception_thrown =
true;
3640 std::string error_msg(e.what());
3641 DOCTEST_CHECK(error_msg.find(
"must have the same length") != std::string::npos);
3643 DOCTEST_CHECK(exception_thrown);
3647 DOCTEST_SUBCASE(
"Error: empty vectors") {
3649 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
3654 std::vector<uint> uuids = {uuid};
3655 std::vector<std::string> spectra;
3656 std::vector<float> values;
3658 bool exception_thrown =
false;
3661 }
catch (
const std::runtime_error &e) {
3662 exception_thrown =
true;
3663 std::string error_msg(e.what());
3664 DOCTEST_CHECK(error_msg.find(
"cannot be empty") != std::string::npos);
3666 DOCTEST_CHECK(exception_thrown);
3670 DOCTEST_SUBCASE(
"Error: invalid global data label") {
3672 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
3678 std::vector<uint> uuids = {uuid};
3679 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
3680 std::vector<float> values = {0.0f};
3691 bool exception_thrown =
false;
3693 radiationmodel5.
runBand(
"PAR");
3694 }
catch (
const std::runtime_error &e) {
3695 exception_thrown =
true;
3696 std::string error_msg(e.what());
3697 DOCTEST_CHECK(error_msg.find(
"does not exist") != std::string::npos);
3699 DOCTEST_CHECK(exception_thrown);
3703 DOCTEST_SUBCASE(
"Error: wrong global data type") {
3705 RadiationModel radiationmodel6 = RadiationModelTestHelper::createWithSharedDevice(&context6);
3713 std::vector<uint> uuids = {uuid};
3714 std::vector<std::string> spectra = {
"wrong_type"};
3715 std::vector<float> values = {0.0f};
3726 bool exception_thrown =
false;
3728 radiationmodel6.
runBand(
"PAR");
3729 }
catch (
const std::runtime_error &e) {
3730 exception_thrown =
true;
3731 std::string error_msg(e.what());
3732 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_VEC2") != std::string::npos);
3734 DOCTEST_CHECK(exception_thrown);
3738 DOCTEST_SUBCASE(
"Invalid UUID is silently skipped") {
3740 RadiationModel radiationmodel7 = RadiationModelTestHelper::createWithSharedDevice(&context7);
3748 std::vector<uint> uuids = {valid_uuid, 99999};
3749 std::vector<std::string> spectra = {
"spec"};
3750 std::vector<float> values = {0.0f};
3761 radiationmodel7.
runBand(
"PAR");
3764 std::string assigned_spectrum;
3765 context7.
getPrimitiveData(valid_uuid,
"reflectivity_spectrum", assigned_spectrum);
3766 DOCTEST_CHECK(assigned_spectrum ==
"spec");
3770 DOCTEST_SUBCASE(
"Error: wrong primitive data type for query") {
3772 RadiationModel radiationmodel8 = RadiationModelTestHelper::createWithSharedDevice(&context8);
3780 std::vector<uint> uuids = {uuid};
3781 std::vector<std::string> spectra = {
"spec"};
3782 std::vector<float> values = {0.0f};
3793 bool exception_thrown =
false;
3795 radiationmodel8.
runBand(
"PAR");
3796 }
catch (
const std::runtime_error &e) {
3797 exception_thrown =
true;
3798 std::string error_msg(e.what());
3799 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_FLOAT") != std::string::npos);
3801 DOCTEST_CHECK(exception_thrown);
3805 DOCTEST_SUBCASE(
"Primitive without query data is skipped") {
3807 RadiationModel radiationmodel9 = RadiationModelTestHelper::createWithSharedDevice(&context9);
3819 std::vector<uint> uuids = {uuid_with_data, uuid_without_data};
3820 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3821 std::vector<float> values = {0.0f, 10.0f};
3829 radiationmodel9.
runBand(
"PAR");
3832 std::string assigned_spectrum;
3833 context9.
getPrimitiveData(uuid_with_data,
"reflectivity_spectrum", assigned_spectrum);
3834 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
3839 context9.
getPrimitiveData(uuid_without_data,
"reflectivity_spectrum", assigned_spectrum);
3845GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation from Object Data") {
3852 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3853 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3854 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3856 context.setGlobalData(
"spectrum_age_0", spectrum_young);
3857 context.setGlobalData(
"spectrum_age_5", spectrum_mature);
3858 context.setGlobalData(
"spectrum_age_10", spectrum_old);
3868 context.setObjectData(obj0,
"age", 0.0f);
3869 context.setObjectData(obj1,
"age", 2.0f);
3870 context.setObjectData(obj2,
"age", 5.0f);
3871 context.setObjectData(obj3,
"age", 8.0f);
3872 context.setObjectData(obj4,
"age", 12.0f);
3875 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3876 std::vector<uint> obj_ids = {obj0, obj1, obj2, obj3, obj4};
3877 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3878 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3887 radiationmodel.
runBand(
"PAR");
3890 std::string assigned_spectrum;
3891 std::vector<uint> prim_uuids0 =
context.getObjectPrimitiveUUIDs(obj0);
3892 for (
uint uuid: prim_uuids0) {
3893 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3894 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3897 std::vector<uint> prim_uuids1 =
context.getObjectPrimitiveUUIDs(obj1);
3898 for (
uint uuid: prim_uuids1) {
3899 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3900 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3903 std::vector<uint> prim_uuids2 =
context.getObjectPrimitiveUUIDs(obj2);
3904 for (
uint uuid: prim_uuids2) {
3905 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3906 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3909 std::vector<uint> prim_uuids3 =
context.getObjectPrimitiveUUIDs(obj3);
3910 for (
uint uuid: prim_uuids3) {
3911 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3912 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3915 std::vector<uint> prim_uuids4 =
context.getObjectPrimitiveUUIDs(obj4);
3916 for (
uint uuid: prim_uuids4) {
3917 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3918 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3923 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3925 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
3937 std::vector<uint> obj_ids = {obj_a, obj_b};
3938 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3939 std::vector<float> values = {0.0f, 10.0f};
3947 radiationmodel2.
runBand(
"PAR");
3949 std::string assigned_spectrum;
3951 for (
uint uuid: prim_uuids_a) {
3952 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3953 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3957 for (
uint uuid: prim_uuids_b) {
3958 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3959 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3964 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3966 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
3970 std::vector<uint> obj_ids = {obj_test};
3971 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3972 std::vector<float> values = {0.0f};
3974 bool caught_error =
false;
3977 }
catch (
const std::exception &e) {
3978 caught_error =
true;
3980 DOCTEST_CHECK(caught_error);
3984 DOCTEST_SUBCASE(
"Error: empty spectra vector") {
3986 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
3990 std::vector<uint> obj_ids = {obj_test};
3991 std::vector<std::string> spectra;
3992 std::vector<float> values;
3994 bool caught_error =
false;
3997 }
catch (
const std::exception &e) {
3998 caught_error =
true;
4000 DOCTEST_CHECK(caught_error);
4004 DOCTEST_SUBCASE(
"Error: empty object_IDs vector") {
4006 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
4009 std::vector<uint> obj_ids;
4010 std::vector<std::string> spectra = {
"spec1"};
4011 std::vector<float> values = {0.0f};
4013 bool caught_error =
false;
4016 }
catch (
const std::exception &e) {
4017 caught_error =
true;
4019 DOCTEST_CHECK(caught_error);
4023 DOCTEST_SUBCASE(
"Error: empty query label") {
4025 RadiationModel radiationmodel6 = RadiationModelTestHelper::createWithSharedDevice(&context6);
4029 std::vector<uint> obj_ids = {obj_test};
4030 std::vector<std::string> spectra = {
"spec1"};
4031 std::vector<float> values = {0.0f};
4033 bool caught_error =
false;
4036 }
catch (
const std::exception &e) {
4037 caught_error =
true;
4039 DOCTEST_CHECK(caught_error);
4043 DOCTEST_SUBCASE(
"Error: empty target label") {
4045 RadiationModel radiationmodel7 = RadiationModelTestHelper::createWithSharedDevice(&context7);
4049 std::vector<uint> obj_ids = {obj_test};
4050 std::vector<std::string> spectra = {
"spec1"};
4051 std::vector<float> values = {0.0f};
4053 bool caught_error =
false;
4056 }
catch (
const std::exception &e) {
4057 caught_error =
true;
4059 DOCTEST_CHECK(caught_error);
4063 DOCTEST_SUBCASE(
"Graceful skip: object without query data") {
4065 RadiationModel radiationmodel8 = RadiationModelTestHelper::createWithSharedDevice(&context8);
4076 std::vector<uint> obj_ids = {obj_with_data, obj_without_data};
4077 std::vector<std::string> spectra = {
"spec1"};
4078 std::vector<float> values = {5.0f};
4086 radiationmodel8.
runBand(
"PAR");
4088 std::string assigned_spectrum;
4090 for (
uint uuid: prim_uuids_with) {
4091 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4092 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4097 for (
uint uuid: prim_uuids_without) {
4099 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4105 DOCTEST_SUBCASE(
"Graceful skip: invalid object ID") {
4107 RadiationModel radiationmodel9 = RadiationModelTestHelper::createWithSharedDevice(&context9);
4118 std::vector<uint> obj_ids = {obj_valid, obj_to_delete};
4119 std::vector<std::string> spectra = {
"spec1"};
4120 std::vector<float> values = {5.0f};
4131 radiationmodel9.
runBand(
"PAR");
4133 std::string assigned_spectrum;
4135 for (
uint uuid: prim_uuids_valid) {
4136 context9.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4137 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4142 DOCTEST_SUBCASE(
"Error: wrong object data type") {
4144 RadiationModel radiationmodel10 = RadiationModelTestHelper::createWithSharedDevice(&context10);
4152 std::vector<uint> obj_ids = {obj_test};
4153 std::vector<std::string> spectra = {
"spec1"};
4154 std::vector<float> values = {5.0f};
4163 bool caught_error =
false;
4165 radiationmodel10.
runBand(
"PAR");
4166 }
catch (
const std::exception &e) {
4167 caught_error =
true;
4169 DOCTEST_CHECK(caught_error);
4173 DOCTEST_SUBCASE(
"Error: invalid global data") {
4175 RadiationModel radiationmodel11 = RadiationModelTestHelper::createWithSharedDevice(&context11);
4181 std::vector<uint> obj_ids = {obj_test};
4182 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
4183 std::vector<float> values = {5.0f};
4192 bool caught_error =
false;
4194 radiationmodel11.
runBand(
"PAR");
4195 }
catch (
const std::exception &e) {
4196 caught_error =
true;
4198 DOCTEST_CHECK(caught_error);
4202 DOCTEST_SUBCASE(
"Error: wrong global data type") {
4204 RadiationModel radiationmodel12 = RadiationModelTestHelper::createWithSharedDevice(&context12);
4212 std::vector<uint> obj_ids = {obj_test};
4213 std::vector<std::string> spectra = {
"wrong_type"};
4214 std::vector<float> values = {5.0f};
4223 bool caught_error =
false;
4225 radiationmodel12.
runBand(
"PAR");
4226 }
catch (
const std::exception &e) {
4227 caught_error =
true;
4229 DOCTEST_CHECK(caught_error);
4233GPU_TEST_CASE(
"RadiationModel Spectrum Interpolation - Duplicate Handling") {
4236 DOCTEST_SUBCASE(
"Primitive: Merge duplicates with matching spectra") {
4241 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4242 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4243 context.setGlobalData(
"spec1", spectrum1);
4244 context.setGlobalData(
"spec2", spectrum2);
4250 context.setPrimitiveData(uuid0,
"age", 1.0f);
4251 context.setPrimitiveData(uuid1,
"age", 1.0f);
4252 context.setPrimitiveData(uuid2,
"age", 9.0f);
4264 radiationmodel.
runBand(
"PAR");
4267 std::string assigned_spectrum;
4268 context.getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4269 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4271 context.getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4272 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4274 context.getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
4275 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4279 DOCTEST_SUBCASE(
"Primitive: Replace config with different spectra") {
4281 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
4284 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4285 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4286 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4307 radiationmodel2.
runBand(
"PAR");
4310 std::string assigned_spectrum;
4311 context2.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4312 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4314 context2.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4315 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4319 DOCTEST_SUBCASE(
"Object: Merge duplicates with matching spectra") {
4321 RadiationModel radiationmodel3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
4324 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4325 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4347 radiationmodel3.
runBand(
"PAR");
4350 std::string assigned_spectrum;
4352 for (
uint uuid: prim_uuids0) {
4353 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4354 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4358 for (
uint uuid: prim_uuids1) {
4359 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4360 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4364 for (
uint uuid: prim_uuids2) {
4365 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4366 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4371 DOCTEST_SUBCASE(
"Object: Replace config with different spectra") {
4373 RadiationModel radiationmodel4 = RadiationModelTestHelper::createWithSharedDevice(&context4);
4376 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4377 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4378 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4399 radiationmodel4.
runBand(
"PAR");
4402 std::string assigned_spectrum;
4404 for (
uint uuid: prim_uuids0) {
4405 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4406 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4410 for (
uint uuid: prim_uuids1) {
4411 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4412 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4417 DOCTEST_SUBCASE(
"Primitive: Separate configs for different labels") {
4419 RadiationModel radiationmodel5 = RadiationModelTestHelper::createWithSharedDevice(&context5);
4422 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4423 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4440 radiationmodel5.
runBand(
"PAR");
4443 std::string assigned_spectrum_rho;
4444 std::string assigned_spectrum_tau;
4445 context5.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum_rho);
4446 context5.
getPrimitiveData(uuid0,
"transmissivity_spectrum", assigned_spectrum_tau);
4448 DOCTEST_CHECK(assigned_spectrum_rho ==
"spec1");
4449 DOCTEST_CHECK(assigned_spectrum_tau ==
"spec2");
4453GPU_TEST_CASE(
"RadiationModel - Camera Metadata Export") {
4466 context.setPrimitiveData(uuid,
"reflectivity_SW", 0.5f);
4478 DOCTEST_SUBCASE(
"Auto-populate metadata with custom sensor size") {
4484 camera_props.
HFOV = 45.0f;
4496 DOCTEST_CHECK(metadata.camera_properties.
width == 512);
4497 DOCTEST_CHECK(metadata.camera_properties.
height == 512);
4498 DOCTEST_CHECK(metadata.camera_properties.
channels == 3);
4501 DOCTEST_CHECK(metadata.camera_properties.
sensor_width == 24.0f);
4502 DOCTEST_CHECK(metadata.camera_properties.
sensor_height == doctest::Approx(24.0f).epsilon(0.01));
4505 float expected_focal_length = 24.0f / (2.0f * tan(45.0f * M_PI / 180.0f / 2.0f));
4506 DOCTEST_CHECK(metadata.camera_properties.
focal_length == doctest::Approx(expected_focal_length).epsilon(0.01));
4509 float lens_diameter_mm = 0.05f * 1000.0f;
4510 float expected_f_number = expected_focal_length / lens_diameter_mm;
4511 std::ostringstream expected_aperture;
4512 expected_aperture <<
"f/" << std::fixed << std::setprecision(1) << expected_f_number;
4513 DOCTEST_CHECK(metadata.camera_properties.
aperture == expected_aperture.str());
4516 DOCTEST_CHECK(metadata.camera_properties.
model ==
"generic");
4519 DOCTEST_CHECK(metadata.location_properties.
latitude == doctest::Approx(34.0522).epsilon(0.0001));
4520 DOCTEST_CHECK(metadata.location_properties.
longitude == doctest::Approx(-118.2437).epsilon(0.0001));
4523 DOCTEST_CHECK(metadata.acquisition_properties.
date ==
"2025-09-30");
4524 DOCTEST_CHECK(metadata.acquisition_properties.
time ==
"10:30:00");
4525 DOCTEST_CHECK(metadata.acquisition_properties.
UTC_offset == 8.0f);
4526 DOCTEST_CHECK(metadata.acquisition_properties.
camera_height_m == 2.0f);
4531 DOCTEST_CHECK(metadata.acquisition_properties.
camera_angle_deg == doctest::Approx(21.8).epsilon(0.5));
4534 DOCTEST_CHECK(metadata.acquisition_properties.
light_source ==
"sunlight");
4537 DOCTEST_CHECK(metadata.
path ==
"");
4540 DOCTEST_SUBCASE(
"Pinhole camera aperture") {
4545 camera_props.
HFOV = 30.0f;
4555 DOCTEST_CHECK(metadata.camera_properties.
aperture ==
"pinhole");
4558 DOCTEST_SUBCASE(
"Light source detection") {
4561 camera_props.
HFOV = 20.0f;
4565 RadiationModel radiationmodel2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
4573 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"none");
4580 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"sunlight");
4587 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"mixed");
4590 DOCTEST_SUBCASE(
"Set metadata and automatic JSON export") {
4593 camera_props.
HFOV = 35.0f;
4596 radiationmodel.
addRadiationCamera(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -3, 1.5),
make_vec3(0, 0, 0), camera_props, 1);
4603 radiationmodel.
runBand(
"RGB_R");
4604 radiationmodel.
runBand(
"RGB_G");
4605 radiationmodel.
runBand(
"RGB_B");
4608 std::string image_path = radiationmodel.
writeCameraImage(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_metadata");
4611 DOCTEST_CHECK(!image_path.empty());
4612 DOCTEST_CHECK(image_path.find(
".jpeg") != std::string::npos);
4615 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4616 std::ifstream json_file(json_path);
4617 DOCTEST_CHECK(json_file.is_open());
4619 if (json_file.is_open()) {
4626 DOCTEST_CHECK(j.contains(
"path"));
4627 DOCTEST_CHECK(j.contains(
"camera_properties"));
4628 DOCTEST_CHECK(j.contains(
"location_properties"));
4629 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
4632 DOCTEST_CHECK(j[
"camera_properties"].contains(
"height"));
4633 DOCTEST_CHECK(j[
"camera_properties"].contains(
"width"));
4634 DOCTEST_CHECK(j[
"camera_properties"].contains(
"channels"));
4635 DOCTEST_CHECK(j[
"camera_properties"].contains(
"focal_length"));
4636 DOCTEST_CHECK(j[
"camera_properties"].contains(
"aperture"));
4637 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_width"));
4638 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_height"));
4639 DOCTEST_CHECK(j[
"camera_properties"].contains(
"model"));
4642 DOCTEST_CHECK(j[
"camera_properties"][
"width"] == 256);
4643 DOCTEST_CHECK(j[
"camera_properties"][
"height"] == 256);
4644 DOCTEST_CHECK(j[
"camera_properties"][
"channels"] == 3);
4645 DOCTEST_CHECK(j[
"camera_properties"][
"model"] ==
"generic");
4648 size_t last_slash = image_path.find_last_of(
"/\\");
4649 std::string expected_filename = (last_slash != std::string::npos) ? image_path.substr(last_slash + 1) : image_path;
4650 DOCTEST_CHECK(j[
"path"] == expected_filename);
4653 std::remove(image_path.c_str());
4654 std::remove(json_path.c_str());
4658 DOCTEST_SUBCASE(
"Embedded EXIF and XMP in written JPEG") {
4668 camera_props.
HFOV = 35.0f;
4675 radiationmodel.
runBand(
"RGB_R");
4676 radiationmodel.
runBand(
"RGB_G");
4677 radiationmodel.
runBand(
"RGB_B");
4680 "exif_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_exif");
4681 DOCTEST_REQUIRE(!image_path.empty());
4684 std::ifstream jpeg_in(image_path, std::ios::binary);
4685 DOCTEST_REQUIRE(jpeg_in.is_open());
4686 std::vector<unsigned char> bytes((std::istreambuf_iterator<char>(jpeg_in)), std::istreambuf_iterator<char>());
4689 DOCTEST_REQUIRE(bytes.size() > 20);
4690 DOCTEST_CHECK(bytes[0] == 0xFF);
4691 DOCTEST_CHECK(bytes[1] == 0xD8);
4693 bool found_exif =
false;
4694 bool found_xmp =
false;
4695 bool found_helios =
false;
4696 char gps_lon_ref = 0;
4697 char gps_lat_ref = 0;
4700 while (i + 4 < bytes.size()) {
4701 if (bytes[i] != 0xFF)
break;
4702 const unsigned char marker = bytes[i + 1];
4703 if (marker == 0xD9 || marker == 0xDA)
break;
4704 const size_t seg_len = (
static_cast<size_t>(bytes[i + 2]) << 8) |
static_cast<size_t>(bytes[i + 3]);
4705 if (seg_len < 2 || i + 2 + seg_len > bytes.size())
break;
4706 if (marker == 0xE1) {
4707 const size_t payload_off = i + 4;
4708 const size_t payload_len = seg_len - 2;
4709 if (payload_len >= 6 && bytes[payload_off + 0] ==
'E' && bytes[payload_off + 1] ==
'x' &&
4710 bytes[payload_off + 2] ==
'i' && bytes[payload_off + 3] ==
'f') {
4713 const std::string exif_str(bytes.begin() + payload_off, bytes.begin() + payload_off + payload_len);
4714 if (exif_str.find(
"Helios") != std::string::npos) found_helios =
true;
4717 const size_t tiff_base = payload_off + 6;
4718 const size_t ifd0_off = tiff_base + 8;
4719 if (ifd0_off + 2 <= bytes.size()) {
4720 const uint16_t n0 =
static_cast<uint16_t
>(bytes[ifd0_off]) |
4721 (
static_cast<uint16_t
>(bytes[ifd0_off + 1]) << 8);
4722 uint32_t gps_off = 0;
4723 for (uint16_t k = 0; k < n0; ++k) {
4724 const size_t e = ifd0_off + 2 +
static_cast<size_t>(k) * 12;
4725 if (e + 12 > bytes.size())
break;
4726 const uint16_t tag =
static_cast<uint16_t
>(bytes[e]) |
4727 (
static_cast<uint16_t
>(bytes[e + 1]) << 8);
4728 if (tag == 0x8825) {
4729 gps_off =
static_cast<uint32_t
>(bytes[e + 8]) |
4730 (
static_cast<uint32_t
>(bytes[e + 9]) << 8) |
4731 (
static_cast<uint32_t
>(bytes[e + 10]) << 16) |
4732 (
static_cast<uint32_t
>(bytes[e + 11]) << 24);
4737 const size_t gps_abs = tiff_base + gps_off;
4738 if (gps_abs + 2 <= bytes.size()) {
4739 const uint16_t ng =
static_cast<uint16_t
>(bytes[gps_abs]) |
4740 (
static_cast<uint16_t
>(bytes[gps_abs + 1]) << 8);
4741 for (uint16_t k = 0; k < ng; ++k) {
4742 const size_t e = gps_abs + 2 +
static_cast<size_t>(k) * 12;
4743 if (e + 12 > bytes.size())
break;
4744 const uint16_t tag =
static_cast<uint16_t
>(bytes[e]) |
4745 (
static_cast<uint16_t
>(bytes[e + 1]) << 8);
4746 if (tag == 0x0001) gps_lat_ref =
static_cast<char>(bytes[e + 8]);
4747 else if (tag == 0x0003) gps_lon_ref =
static_cast<char>(bytes[e + 8]);
4752 }
else if (payload_len >= 29) {
4753 static const char NS_ID[] =
"http://ns.adobe.com/xap/1.0/";
4755 for (
size_t k = 0; k < 28; ++k) {
4756 if (bytes[payload_off + k] !=
static_cast<unsigned char>(NS_ID[k])) {
4763 const std::string xmp_str(bytes.begin() + payload_off,
4764 bytes.begin() + payload_off + payload_len);
4765 DOCTEST_CHECK(xmp_str.find(
"Camera:Yaw") != std::string::npos);
4769 i = i + 2 + seg_len;
4772 DOCTEST_CHECK(found_exif);
4773 DOCTEST_CHECK(found_xmp);
4774 DOCTEST_CHECK(found_helios);
4777 DOCTEST_CHECK(gps_lat_ref ==
'N');
4778 DOCTEST_CHECK(gps_lon_ref ==
'W');
4780 std::remove(image_path.c_str());
4783 DOCTEST_SUBCASE(
"Library camera writes correct EXIF Make and Model") {
4794 radiationmodel.
runBand(
"red");
4795 radiationmodel.
runBand(
"green");
4796 radiationmodel.
runBand(
"blue");
4799 "canon_lib_cam", {
"red",
"green",
"blue"},
"test_library_exif");
4800 DOCTEST_REQUIRE(!image_path.empty());
4802 std::ifstream jpeg_in(image_path, std::ios::binary);
4803 DOCTEST_REQUIRE(jpeg_in.is_open());
4804 std::vector<unsigned char> bytes((std::istreambuf_iterator<char>(jpeg_in)), std::istreambuf_iterator<char>());
4808 DOCTEST_REQUIRE(bytes.size() > 20);
4809 std::string make_value, model_value, lens_make_value, lens_model_value;
4811 while (i + 4 < bytes.size()) {
4812 if (bytes[i] != 0xFF)
break;
4813 const unsigned char marker = bytes[i + 1];
4814 if (marker == 0xD9 || marker == 0xDA)
break;
4815 const size_t seg_len = (
static_cast<size_t>(bytes[i + 2]) << 8) |
static_cast<size_t>(bytes[i + 3]);
4816 if (seg_len < 2 || i + 2 + seg_len > bytes.size())
break;
4817 if (marker == 0xE1) {
4818 const size_t payload_off = i + 4;
4819 const size_t payload_len = seg_len - 2;
4820 if (payload_len >= 6 && bytes[payload_off + 0] ==
'E' && bytes[payload_off + 1] ==
'x' &&
4821 bytes[payload_off + 2] ==
'i' && bytes[payload_off + 3] ==
'f') {
4822 const size_t tiff_base = payload_off + 6;
4823 const size_t ifd0_off = tiff_base + 8;
4826 auto readAsciiAt = [&](
size_t ifd_off, uint16_t target_tag) -> std::string {
4827 if (ifd_off + 2 > bytes.size())
return std::string();
4828 const uint16_t n =
static_cast<uint16_t
>(bytes[ifd_off]) |
4829 (
static_cast<uint16_t
>(bytes[ifd_off + 1]) << 8);
4830 for (uint16_t k = 0; k < n; ++k) {
4831 const size_t e = ifd_off + 2 +
static_cast<size_t>(k) * 12;
4832 if (e + 12 > bytes.size())
break;
4833 const uint16_t tag =
static_cast<uint16_t
>(bytes[e]) |
4834 (
static_cast<uint16_t
>(bytes[e + 1]) << 8);
4835 if (tag != target_tag)
continue;
4836 const uint16_t type =
static_cast<uint16_t
>(bytes[e + 2]) |
4837 (
static_cast<uint16_t
>(bytes[e + 3]) << 8);
4838 if (type != 2 )
return std::string();
4839 const uint32_t count =
static_cast<uint32_t
>(bytes[e + 4]) |
4840 (
static_cast<uint32_t
>(bytes[e + 5]) << 8) |
4841 (
static_cast<uint32_t
>(bytes[e + 6]) << 16) |
4842 (
static_cast<uint32_t
>(bytes[e + 7]) << 24);
4847 const uint32_t off_in_tiff =
static_cast<uint32_t
>(bytes[e + 8]) |
4848 (
static_cast<uint32_t
>(bytes[e + 9]) << 8) |
4849 (
static_cast<uint32_t
>(bytes[e + 10]) << 16) |
4850 (
static_cast<uint32_t
>(bytes[e + 11]) << 24);
4851 value_off = tiff_base + off_in_tiff;
4854 const size_t str_len = (count > 0) ? (count - 1) : 0;
4855 if (value_off + str_len > bytes.size())
return std::string();
4856 return std::string(bytes.begin() + value_off, bytes.begin() + value_off + str_len);
4858 return std::string();
4861 make_value = readAsciiAt(ifd0_off, 0x010F);
4862 model_value = readAsciiAt(ifd0_off, 0x0110);
4865 if (ifd0_off + 2 <= bytes.size()) {
4866 const uint16_t n0 =
static_cast<uint16_t
>(bytes[ifd0_off]) |
4867 (
static_cast<uint16_t
>(bytes[ifd0_off + 1]) << 8);
4868 uint32_t exif_sub_off = 0;
4869 for (uint16_t k = 0; k < n0; ++k) {
4870 const size_t e = ifd0_off + 2 +
static_cast<size_t>(k) * 12;
4871 if (e + 12 > bytes.size())
break;
4872 const uint16_t tag =
static_cast<uint16_t
>(bytes[e]) |
4873 (
static_cast<uint16_t
>(bytes[e + 1]) << 8);
4874 if (tag == 0x8769) {
4875 exif_sub_off =
static_cast<uint32_t
>(bytes[e + 8]) |
4876 (
static_cast<uint32_t
>(bytes[e + 9]) << 8) |
4877 (
static_cast<uint32_t
>(bytes[e + 10]) << 16) |
4878 (
static_cast<uint32_t
>(bytes[e + 11]) << 24);
4882 if (exif_sub_off > 0) {
4883 lens_make_value = readAsciiAt(tiff_base + exif_sub_off, 0xA433);
4884 lens_model_value = readAsciiAt(tiff_base + exif_sub_off, 0xA434);
4890 i = i + 2 + seg_len;
4895 DOCTEST_CHECK(make_value ==
"Canon");
4896 DOCTEST_CHECK(model_value ==
"EOS 20D");
4898 DOCTEST_CHECK(lens_make_value ==
"Canon");
4899 DOCTEST_CHECK(lens_model_value ==
"Canon EF-S 18-55mm f/3.5-5.6");
4901 std::remove(image_path.c_str());
4902 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4903 std::remove(json_path.c_str());
4906 DOCTEST_SUBCASE(
"Manual metadata population") {
4914 metadata.camera_properties.
width = 128;
4915 metadata.camera_properties.
height = 128;
4916 metadata.camera_properties.
channels = 1;
4918 metadata.camera_properties.
aperture =
"f/1.8";
4921 metadata.camera_properties.
model =
"Nikon D700";
4923 metadata.location_properties.
latitude = 40.0f;
4924 metadata.location_properties.
longitude = -75.0f;
4926 metadata.acquisition_properties.
date =
"2025-01-01";
4927 metadata.acquisition_properties.
time =
"12:00:00";
4928 metadata.acquisition_properties.
UTC_offset = 5.0f;
4931 metadata.acquisition_properties.
light_source =
"artificial";
4938 DOCTEST_CHECK(
true);
4941 DOCTEST_SUBCASE(
"Enable metadata for multiple cameras with vector") {
4944 camera_props.
HFOV = 30.0f;
4947 radiationmodel.
addRadiationCamera(
"camera_A", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4948 radiationmodel.
addRadiationCamera(
"camera_B", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(2, 0, 1),
make_vec3(0, 0, 0), camera_props, 1);
4949 radiationmodel.
addRadiationCamera(
"camera_C", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, 2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4952 std::vector<std::string> camera_labels = {
"camera_A",
"camera_B",
"camera_C"};
4957 radiationmodel.
runBand(
"RGB_R");
4958 radiationmodel.
runBand(
"RGB_G");
4959 radiationmodel.
runBand(
"RGB_B");
4962 std::vector<std::string> image_paths;
4963 std::vector<std::string> json_paths;
4965 for (
const auto &label: camera_labels) {
4966 std::string image_path = radiationmodel.
writeCameraImage(label, {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_vector");
4967 DOCTEST_CHECK(!image_path.empty());
4969 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4970 std::ifstream json_file(json_path);
4971 DOCTEST_CHECK(json_file.is_open());
4974 image_paths.push_back(image_path);
4975 json_paths.push_back(json_path);
4979 for (
size_t i = 0; i < image_paths.size(); i++) {
4980 std::remove(image_paths[i].c_str());
4981 std::remove(json_paths[i].c_str());
4985 DOCTEST_SUBCASE(
"applyCameraImageCorrections stores parameters in metadata") {
4991 camera_props.
HFOV = 45.0f;
4994 radiationmodel.
addRadiationCamera(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
5001 radiationmodel.
runBand(
"RGB_R");
5002 radiationmodel.
runBand(
"RGB_G");
5003 radiationmodel.
runBand(
"RGB_B");
5006 float saturation = 1.5f;
5007 float brightness = 1.2f;
5008 float contrast = 1.1f;
5012 std::string image_path = radiationmodel.
writeCameraImage(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_corrections");
5014 DOCTEST_CHECK(!image_path.empty());
5017 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
5018 std::ifstream json_file(json_path);
5019 DOCTEST_CHECK(json_file.is_open());
5021 if (json_file.is_open()) {
5027 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
5028 DOCTEST_CHECK(j.contains(
"image_processing"));
5031 auto &img_proc = j[
"image_processing"];
5032 DOCTEST_CHECK(img_proc.contains(
"saturation_adjustment"));
5033 DOCTEST_CHECK(img_proc.contains(
"brightness_adjustment"));
5034 DOCTEST_CHECK(img_proc.contains(
"contrast_adjustment"));
5035 DOCTEST_CHECK(img_proc.contains(
"color_space"));
5037 DOCTEST_CHECK(img_proc[
"saturation_adjustment"].get<double>() == doctest::Approx(saturation).epsilon(0.01));
5038 DOCTEST_CHECK(img_proc[
"brightness_adjustment"].get<double>() == doctest::Approx(brightness).epsilon(0.01));
5039 DOCTEST_CHECK(img_proc[
"contrast_adjustment"].get<double>() == doctest::Approx(contrast).epsilon(0.01));
5040 DOCTEST_CHECK(img_proc[
"color_space"].get<std::string>() ==
"sRGB");
5043 std::remove(image_path.c_str());
5044 std::remove(json_path.c_str());
5049GPU_TEST_CASE(
"RadiationModel - Camera Metadata Agronomic Properties") {
5076 DOCTEST_SUBCASE(
"Agronomic properties with multiple species and weeds") {
5080 context.setObjectData(bean_obj_1,
"plant_name", std::string(
"bean"));
5081 context.setObjectData(bean_obj_1,
"plantID", 1);
5082 context.setObjectData(bean_obj_1,
"plant_type", std::string(
"crop"));
5083 context.setObjectData(bean_obj_1,
"plant_height", 0.45f);
5084 context.setObjectData(bean_obj_1,
"age", 30.0f);
5085 context.setObjectData(bean_obj_1,
"phenology_stage", std::string(
"flowering"));
5086 context.setObjectData(bean_obj_1,
"reflectivity_SW", 0.3f);
5089 context.setObjectData(bean_obj_2,
"plant_name", std::string(
"bean"));
5090 context.setObjectData(bean_obj_2,
"plantID", 2);
5091 context.setObjectData(bean_obj_2,
"plant_type", std::string(
"crop"));
5092 context.setObjectData(bean_obj_2,
"plant_height", 0.50f);
5093 context.setObjectData(bean_obj_2,
"age", 32.0f);
5094 context.setObjectData(bean_obj_2,
"phenology_stage", std::string(
"flowering"));
5095 context.setObjectData(bean_obj_2,
"reflectivity_SW", 0.3f);
5098 context.setObjectData(bean_obj_3,
"plant_name", std::string(
"bean"));
5099 context.setObjectData(bean_obj_3,
"plantID", 3);
5100 context.setObjectData(bean_obj_3,
"plant_type", std::string(
"crop"));
5101 context.setObjectData(bean_obj_3,
"plant_height", 0.42f);
5102 context.setObjectData(bean_obj_3,
"age", 28.0f);
5103 context.setObjectData(bean_obj_3,
"phenology_stage", std::string(
"flowering"));
5104 context.setObjectData(bean_obj_3,
"reflectivity_SW", 0.3f);
5108 context.setObjectData(weed_obj_1,
"plant_name", std::string(
"pigweed"));
5109 context.setObjectData(weed_obj_1,
"plantID", 4);
5110 context.setObjectData(weed_obj_1,
"plant_type", std::string(
"weed"));
5111 context.setObjectData(weed_obj_1,
"plant_height", 0.30f);
5112 context.setObjectData(weed_obj_1,
"age", 15.0f);
5113 context.setObjectData(weed_obj_1,
"phenology_stage", std::string(
"vegetative"));
5114 context.setObjectData(weed_obj_1,
"reflectivity_SW", 0.25f);
5117 context.setObjectData(weed_obj_2,
"plant_name", std::string(
"pigweed"));
5118 context.setObjectData(weed_obj_2,
"plantID", 5);
5119 context.setObjectData(weed_obj_2,
"plant_type", std::string(
"weed"));
5120 context.setObjectData(weed_obj_2,
"plant_height", 0.35f);
5121 context.setObjectData(weed_obj_2,
"age", 18.0f);
5122 context.setObjectData(weed_obj_2,
"phenology_stage", std::string(
"vegetative"));
5123 context.setObjectData(weed_obj_2,
"reflectivity_SW", 0.25f);
5128 camera_props.
HFOV = 60.0f;
5131 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);
5135 radiationmodel.
runBand(
"RGB_R");
5136 radiationmodel.
runBand(
"RGB_G");
5137 radiationmodel.
runBand(
"RGB_B");
5143 DOCTEST_CHECK(!metadata.agronomic_properties.
plant_species.empty());
5144 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.size() == 2);
5148 int pigweed_idx = -1;
5149 for (
size_t i = 0; i < metadata.agronomic_properties.
plant_species.size(); i++) {
5150 if (metadata.agronomic_properties.
plant_species[i] ==
"bean") {
5151 bean_idx =
static_cast<int>(i);
5152 }
else if (metadata.agronomic_properties.
plant_species[i] ==
"pigweed") {
5153 pigweed_idx =
static_cast<int>(i);
5157 DOCTEST_CHECK(bean_idx >= 0);
5158 DOCTEST_CHECK(pigweed_idx >= 0);
5161 if (bean_idx >= 0) {
5162 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[bean_idx] == 3);
5164 if (pigweed_idx >= 0) {
5165 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[pigweed_idx] == 2);
5169 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"moderate");
5172 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m.size() == 2);
5173 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days.size() == 2);
5174 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage.size() == 2);
5175 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2.size() == 2);
5180 if (bean_idx >= 0) {
5181 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] > 0.40f);
5182 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] < 0.52f);
5184 if (pigweed_idx >= 0) {
5185 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] > 0.28f);
5186 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] < 0.37f);
5192 if (bean_idx >= 0) {
5193 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] > 27.0f);
5194 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] < 33.0f);
5196 if (pigweed_idx >= 0) {
5197 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] > 14.0f);
5198 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] < 19.0f);
5204 if (bean_idx >= 0) {
5205 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[bean_idx] ==
"flowering");
5207 if (pigweed_idx >= 0) {
5208 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[pigweed_idx] ==
"vegetative");
5212 if (bean_idx >= 0) {
5213 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[bean_idx] > 0.0f);
5215 if (pigweed_idx >= 0) {
5216 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[pigweed_idx] > 0.0f);
5220 DOCTEST_SUBCASE(
"Agronomic properties with low weed pressure") {
5222 for (
int i = 0; i < 10; i++) {
5224 context.setObjectData(crop_obj,
"plant_name", std::string(
"soybean"));
5225 context.setObjectData(crop_obj,
"plantID", i + 1);
5226 context.setObjectData(crop_obj,
"plant_type", std::string(
"crop"));
5227 context.setObjectData(crop_obj,
"reflectivity_SW", 0.3f);
5231 context.setObjectData(weed_obj,
"plant_name", std::string(
"lambsquarter"));
5232 context.setObjectData(weed_obj,
"plantID", 11);
5233 context.setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
5234 context.setObjectData(weed_obj,
"reflectivity_SW", 0.25f);
5238 camera_props.
HFOV = 90.0f;
5243 radiationmodel.
runBand(
"RGB_R");
5249 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"low");
5252 DOCTEST_SUBCASE(
"Agronomic properties with high weed pressure") {
5255 context.setObjectData(crop_obj_1,
"plant_name", std::string(
"corn"));
5256 context.setObjectData(crop_obj_1,
"plantID", 1);
5257 context.setObjectData(crop_obj_1,
"plant_type", std::string(
"crop"));
5258 context.setObjectData(crop_obj_1,
"reflectivity_SW", 0.3f);
5261 context.setObjectData(crop_obj_2,
"plant_name", std::string(
"corn"));
5262 context.setObjectData(crop_obj_2,
"plantID", 2);
5263 context.setObjectData(crop_obj_2,
"plant_type", std::string(
"crop"));
5264 context.setObjectData(crop_obj_2,
"reflectivity_SW", 0.3f);
5266 for (
int i = 0; i < 3; i++) {
5268 context.setObjectData(weed_obj,
"plant_name", std::string(
"foxtail"));
5269 context.setObjectData(weed_obj,
"plantID", 3 + i);
5270 context.setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
5271 context.setObjectData(weed_obj,
"reflectivity_SW", 0.25f);
5276 camera_props.
HFOV = 60.0f;
5281 radiationmodel.
runBand(
"RGB_R");
5287 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"high");
5290 DOCTEST_SUBCASE(
"Agronomic properties with no plant data") {
5293 for (
const auto &uuid: patch_UUIDs) {
5294 context.setPrimitiveData(uuid,
"reflectivity_SW", 0.3f);
5299 camera_props.
HFOV = 45.0f;
5304 radiationmodel.
runBand(
"RGB_R");
5310 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.empty());
5311 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count.empty());
5312 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"");
5315 DOCTEST_SUBCASE(
"Agronomic properties JSON export") {
5318 context.setObjectData(bean_obj,
"plant_name", std::string(
"bean"));
5319 context.setObjectData(bean_obj,
"plantID", 1);
5320 context.setObjectData(bean_obj,
"plant_type", std::string(
"crop"));
5321 context.setObjectData(bean_obj,
"reflectivity_SW", 0.3f);
5324 context.setObjectData(weed_obj,
"plant_name", std::string(
"weed"));
5325 context.setObjectData(weed_obj,
"plantID", 2);
5326 context.setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
5327 context.setObjectData(weed_obj,
"reflectivity_SW", 0.25f);
5331 camera_props.
HFOV = 50.0f;
5333 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);
5336 radiationmodel.
runBand(
"RGB_R");
5337 radiationmodel.
runBand(
"RGB_G");
5338 radiationmodel.
runBand(
"RGB_B");
5344 std::string image_path = radiationmodel.
writeCameraImage(
"json_export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_agronomic");
5350 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
5351 std::ifstream json_file(json_path);
5352 DOCTEST_CHECK(json_file.is_open());
5354 if (json_file.is_open()) {
5360 DOCTEST_CHECK(j.contains(
"agronomic_properties"));
5362 if (j.contains(
"agronomic_properties")) {
5363 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_species"));
5364 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_count"));
5365 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"weed_pressure"));
5368 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_species"].is_array());
5369 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_count"].is_array());
5370 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"].is_string());
5373 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"] ==
"high");
5377 std::remove(image_path.c_str());
5378 std::remove(json_path.c_str());
5383GPU_TEST_CASE(
"RadiationModel - FOV_aspect_ratio Deprecation") {
5394 DOCTEST_SUBCASE(
"Default FOV_aspect_ratio is auto-calculated") {
5398 camera_props.
HFOV = 45.0f;
5402 std::string stderr_output;
5408 DOCTEST_CHECK(stderr_output.empty());
5413 DOCTEST_CHECK(std::abs(expected_aspect - 1.333333f) < 0.0001f);
5416 DOCTEST_SUBCASE(
"Explicit FOV_aspect_ratio triggers deprecation warning") {
5420 camera_props.
HFOV = 50.0f;
5424 std::string stderr_output;
5430 DOCTEST_CHECK(stderr_output.find(
"WARNING") != std::string::npos);
5431 DOCTEST_CHECK(stderr_output.find(
"FOV_aspect_ratio") != std::string::npos);
5432 DOCTEST_CHECK(stderr_output.find(
"deprecated") != std::string::npos);
5433 DOCTEST_CHECK(stderr_output.find(
"auto-calculated") != std::string::npos);
5436 DOCTEST_SUBCASE(
"Auto-calculated value ensures square pixels") {
5438 std::vector<helios::int2> resolutions = {
5445 for (
const auto &resolution: resolutions) {
5448 camera_props.
HFOV = 60.0f;
5451 std::string camera_label =
"camera_" + std::to_string(resolution.x) +
"x" + std::to_string(resolution.y);
5454 std::string stderr_output;
5460 DOCTEST_CHECK(stderr_output.empty());
5465GPU_TEST_CASE(
"RadiationModel Atmospheric Sky Model for Camera") {
5473 context.setPrimitiveData(UUID,
"temperature", 300.f);
5476 float pressure_Pa = 95000.f;
5477 float temperature_K = 285.f;
5478 float humidity_rel = 0.6f;
5479 float turbidity = 0.08f;
5481 context.setGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
5482 context.setGlobalData(
"atmosphere_temperature_K", temperature_K);
5483 context.setGlobalData(
"atmosphere_humidity_rel", humidity_rel);
5484 context.setGlobalData(
"atmosphere_turbidity", turbidity);
5489 DOCTEST_SUBCASE(
"Sky model requires wavelength bounds with uniform response") {
5505 camera_props.
HFOV = 60.0f;
5512 bool threw_error =
false;
5516 radiationmodel.
runBand(
"VIS");
5517 }
catch (std::runtime_error &e) {
5518 std::string error_msg = e.what();
5519 threw_error = (error_msg.find(
"wavelength bounds") != std::string::npos);
5522 DOCTEST_CHECK(threw_error);
5525 DOCTEST_SUBCASE(
"Sky model computed with camera and wavelength bounds") {
5544 camera_props.
HFOV = 60.0f;
5553 radiationmodel.
runBand(
"VIS");
5557 DOCTEST_CHECK(
true);
5560 DOCTEST_SUBCASE(
"Atmospheric parameters do not cause errors") {
5571 camera_props.
HFOV = 45.0f;
5575 radiationmodel.
runBand(
"VIS");
5579 float high_turbidity = 0.3f;
5580 context.setGlobalData(
"atmosphere_turbidity", high_turbidity);
5582 radiationmodel.
runBand(
"VIS");
5585 DOCTEST_CHECK(
true);
5589GPU_TEST_CASE(
"RadiationModel - Camera samples sky longwave on emission band miss") {
5598 context.setPrimitiveData(dummy_uuid,
"twosided_flag",
uint(0));
5599 context.setPrimitiveData(dummy_uuid,
"emissivity_LW", 0.f);
5600 context.setPrimitiveData(dummy_uuid,
"temperature", 0.f);
5603 radiation.disableMessages();
5605 const float sky_flux_LW = 400.f;
5608 radiation.addRadiationBand(
"LW");
5609 radiation.setDirectRayCount(
"LW", 0);
5610 radiation.setDiffuseRayCount(
"LW", 0);
5611 radiation.setScatteringDepth(
"LW", 1);
5612 radiation.setDiffuseRadiationFlux(
"LW", sky_flux_LW);
5616 camera_props.
HFOV = 30.0f;
5620 radiation.updateGeometry();
5621 radiation.runBand(
"LW");
5629 auto pixels_LW = radiation.getCameraPixelData(
"sky_cam",
"LW");
5630 DOCTEST_CHECK(pixels_LW.size() == 8 * 8);
5631 bool all_finite =
true;
5632 float max_pixel = 0.f;
5633 for (
float v: pixels_LW) {
5634 if (!std::isfinite(v)) {
5637 max_pixel = std::max(max_pixel, v);
5639 DOCTEST_CHECK(all_finite);
5640 DOCTEST_CHECK(max_pixel > 0.f);
5643GPU_TEST_CASE(
"RadiationModel - Camera White Balance") {
5651 context.setPrimitiveData(uuid,
"reflectivity_SW", 0.5f);
5664 DOCTEST_SUBCASE(
"Default white_balance is 'auto'") {
5669 camera_props.
HFOV = 45.0f;
5674 radiationmodel.
addRadiationCamera(
"test_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5681 DOCTEST_SUBCASE(
"White balance mode 'off' preserves raw data") {
5686 camera_props.
HFOV = 45.0f;
5689 radiationmodel.
addRadiationCamera(
"camera_wb_off", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5693 radiationmodel.
runBand(
"RGB_R");
5694 radiationmodel.
runBand(
"RGB_G");
5695 radiationmodel.
runBand(
"RGB_B");
5699 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5702 DOCTEST_CHECK(
true);
5705 DOCTEST_SUBCASE(
"White balance mode 'auto' applies correction") {
5710 camera_props.
HFOV = 45.0f;
5713 radiationmodel.
addRadiationCamera(
"camera_wb_auto", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5717 radiationmodel.
runBand(
"RGB_R");
5718 radiationmodel.
runBand(
"RGB_G");
5719 radiationmodel.
runBand(
"RGB_B");
5723 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5726 DOCTEST_CHECK(
true);
5729 DOCTEST_SUBCASE(
"Single-channel camera skips white balance") {
5734 camera_props.
HFOV = 45.0f;
5741 radiationmodel.
runBand(
"RGB_R");
5745 DOCTEST_CHECK(metadata.camera_properties.
channels == 1);
5746 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5749 DOCTEST_CHECK(
true);
5752 DOCTEST_SUBCASE(
"Update camera white_balance parameter") {
5757 camera_props.
HFOV = 45.0f;
5760 radiationmodel.
addRadiationCamera(
"camera_update", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5773 radiationmodel.
runBand(
"RGB_R");
5774 radiationmodel.
runBand(
"RGB_G");
5775 radiationmodel.
runBand(
"RGB_B");
5779 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5782 DOCTEST_SUBCASE(
"CameraProperties equality includes white_balance") {
5790 DOCTEST_CHECK(props1 == props2);
5796 DOCTEST_CHECK(props1 != props2);
5800GPU_TEST_CASE(
"RadiationModel setDiffuseSpectrum and emission band behavior") {
5802 using namespace helios;
5807 std::vector<vec2> test_spectrum;
5808 test_spectrum.emplace_back(400.f, 1.0f);
5809 test_spectrum.emplace_back(500.f, 1.5f);
5810 test_spectrum.emplace_back(600.f, 1.0f);
5811 test_spectrum.emplace_back(700.f, 0.5f);
5812 context.setGlobalData(
"test_spectrum", test_spectrum);
5815 radiation.disableMessages();
5817 DOCTEST_SUBCASE(
"setDiffuseSpectrum applies to all bands") {
5819 radiation.addRadiationBand(
"band1", 400.f, 500.f);
5820 radiation.addRadiationBand(
"band2", 500.f, 600.f);
5821 radiation.addRadiationBand(
"band3", 600.f, 700.f);
5824 radiation.disableEmission(
"band1");
5825 radiation.disableEmission(
"band2");
5826 radiation.disableEmission(
"band3");
5829 radiation.setDiffuseSpectrum(
"test_spectrum");
5832 float flux1 = radiation.getDiffuseFlux(
"band1");
5833 float flux2 = radiation.getDiffuseFlux(
"band2");
5834 float flux3 = radiation.getDiffuseFlux(
"band3");
5836 DOCTEST_CHECK(flux1 > 0.f);
5837 DOCTEST_CHECK(flux2 > 0.f);
5838 DOCTEST_CHECK(flux3 > 0.f);
5841 DOCTEST_SUBCASE(
"getDiffuseFlux returns 0 for emission-enabled bands with spectrum") {
5843 radiation.addRadiationBand(
"emission_band", 400.f, 700.f);
5846 radiation.setDiffuseSpectrum(
"test_spectrum");
5849 float flux = radiation.getDiffuseFlux(
"emission_band");
5850 DOCTEST_CHECK(flux == 0.f);
5853 DOCTEST_SUBCASE(
"getDiffuseFlux returns manual flux for emission-enabled bands") {
5855 radiation.addRadiationBand(
"emission_band", 400.f, 700.f);
5858 radiation.setDiffuseSpectrum(
"test_spectrum");
5861 float manual_flux = 100.f;
5862 radiation.setDiffuseRadiationFlux(
"emission_band", manual_flux);
5865 float flux = radiation.getDiffuseFlux(
"emission_band");
5866 DOCTEST_CHECK(flux == manual_flux);
5869 DOCTEST_SUBCASE(
"Manual flux overrides spectrum for non-emission bands") {
5871 radiation.addRadiationBand(
"shortwave", 400.f, 700.f);
5872 radiation.disableEmission(
"shortwave");
5875 radiation.setDiffuseSpectrum(
"test_spectrum");
5878 float spectrum_flux = radiation.getDiffuseFlux(
"shortwave");
5879 DOCTEST_CHECK(spectrum_flux > 0.f);
5882 float manual_flux = 999.f;
5883 radiation.setDiffuseRadiationFlux(
"shortwave", manual_flux);
5885 float flux = radiation.getDiffuseFlux(
"shortwave");
5886 DOCTEST_CHECK(flux == manual_flux);
5889 DOCTEST_SUBCASE(
"setDiffuseSpectrum with no bands does not error") {
5893 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5898 DOCTEST_CHECK(
true);
5901 DOCTEST_SUBCASE(
"setDiffuseSpectrum before bands are added applies to later bands") {
5905 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5923 DOCTEST_CHECK(flux1 > 0.f);
5924 DOCTEST_CHECK(flux2 > 0.f);
5927 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral scales global spectrum before bands are added") {
5931 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5936 float target_integral = 850.f;
5948 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5951 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral with wavelength bounds scales global spectrum") {
5955 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
5960 float target_integral = 500.f;
5969 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5972 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral applies to existing bands") {
5974 radiation.addRadiationBand(
"band1", 400.f, 700.f);
5975 radiation.disableEmission(
"band1");
5977 radiation.setDiffuseSpectrum(
"test_spectrum");
5978 float target_integral = 1000.f;
5979 radiation.setDiffuseSpectrumIntegral(target_integral);
5981 float flux = radiation.getDiffuseFlux(
"band1");
5982 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5988GPU_TEST_CASE(
"Radiation - Prague Context data fallback behavior") {
5991 radiation.disableMessages();
5994 radiation.addRadiationBand(
"red");
5995 radiation.addRadiationBand(
"green");
5996 radiation.addRadiationBand(
"blue");
6000 context.setPrimitiveData(UUID,
"radiation_flux_red", 0.f);
6001 context.setPrimitiveData(UUID,
"radiation_flux_green", 0.f);
6002 context.setPrimitiveData(UUID,
"radiation_flux_blue", 0.f);
6007 camera_props.
HFOV = 45.0f;
6009 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
6013 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
6016GPU_TEST_CASE(
"Radiation - Prague Context data integration end-to-end") {
6019 radiation.disableMessages();
6023 std::vector<float> spectral_params(225 * 6);
6024 for (
int i = 0; i < 225; ++i) {
6025 float wavelength = 360.0f + i * 5.0f;
6029 float rayleigh_factor = std::pow(550.0f / wavelength, 4.0f);
6031 spectral_params[base + 0] = wavelength;
6032 spectral_params[base + 1] = 0.3f * rayleigh_factor;
6033 spectral_params[base + 2] = 2.0f;
6034 spectral_params[base + 3] = 15.0f;
6035 spectral_params[base + 4] = 2.0f;
6036 spectral_params[base + 5] = 0.8f;
6039 context.setGlobalData(
"prague_sky_spectral_params", spectral_params);
6040 context.setGlobalData(
"prague_sky_sun_direction",
make_vec3(0, 0.5f, 0.866f));
6041 context.setGlobalData(
"prague_sky_visibility_km", 40.0f);
6042 context.setGlobalData(
"prague_sky_ground_albedo", 0.33f);
6043 context.setGlobalData(
"prague_sky_valid", 1);
6047 DOCTEST_CHECK_NOTHROW(
context.getGlobalData(
"prague_sky_valid", valid));
6048 DOCTEST_CHECK(valid == 1);
6050 std::vector<float> read_params;
6051 DOCTEST_CHECK_NOTHROW(
context.getGlobalData(
"prague_sky_spectral_params", read_params));
6052 DOCTEST_CHECK(read_params.size() == 225 * 6);
6055 radiation.addRadiationBand(
"red");
6056 radiation.addRadiationBand(
"green");
6057 radiation.addRadiationBand(
"blue");
6061 context.setPrimitiveData(UUID,
"radiation_flux_red", 0.f);
6062 context.setPrimitiveData(UUID,
"radiation_flux_green", 0.f);
6063 context.setPrimitiveData(UUID,
"radiation_flux_blue", 0.f);
6068 camera_props.
HFOV = 45.0f;
6070 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
6073 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
6076GPU_TEST_CASE(
"RadiationModel Automatic Spectrum Update Detection") {
6080 radiation.disableMessages();
6083 std::vector<helios::vec2> direct_spectrum_v1 = {{300, 1.0}, {400, 2.0}, {500, 3.0}, {700, 2.0}, {800, 1.0}};
6084 context.setGlobalData(
"test_direct_spectrum", direct_spectrum_v1);
6087 std::vector<helios::vec2> diffuse_spectrum_v1 = {{300, 0.5}, {400, 1.0}, {500, 1.5}, {700, 1.0}, {800, 0.5}};
6088 context.setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v1);
6091 uint sun = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6092 radiation.setSourceSpectrum(sun,
"test_direct_spectrum");
6095 radiation.setDiffuseSpectrum(
"test_diffuse_spectrum");
6098 radiation.addRadiationBand(
"PAR", 400, 700);
6102 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6105 radiation.updateGeometry();
6106 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
6109 context.getPrimitiveData(ground,
"radiation_flux_PAR", flux_v1);
6110 DOCTEST_CHECK(flux_v1 > 0.0f);
6113 std::vector<helios::vec2> direct_spectrum_v2 = {{300, 2.0}, {400, 4.0}, {500, 6.0}, {700, 4.0}, {800, 2.0}};
6114 context.setGlobalData(
"test_direct_spectrum", direct_spectrum_v2);
6117 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
6120 context.getPrimitiveData(ground,
"radiation_flux_PAR", flux_v2);
6123 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.9f);
6124 DOCTEST_CHECK(flux_v2 < flux_v1 * 2.1f);
6127 std::vector<helios::vec2> diffuse_spectrum_v2 = {{300, 1.5}, {400, 3.0}, {500, 4.5}, {700, 3.0}, {800, 1.5}};
6128 context.setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v2);
6131 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
6134 context.getPrimitiveData(ground,
"radiation_flux_PAR", flux_v3);
6138 DOCTEST_CHECK(flux_v3 >= flux_v2 * 0.99f);
6141GPU_TEST_CASE(
"RadiationModel Multiple Sources Same Spectrum Update") {
6145 radiation.disableMessages();
6148 std::vector<helios::vec2> shared_spectrum = {{300, 1.0}, {800, 1.0}};
6149 context.setGlobalData(
"shared_spectrum", shared_spectrum);
6155 for (
int i = 0; i < 3; i++) {
6156 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6157 radiation.setSourceSpectrum(source,
"shared_spectrum");
6161 radiation.addRadiationBand(
"test", 400, 700);
6164 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6166 radiation.updateGeometry();
6167 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
6170 context.getPrimitiveData(ground,
"radiation_flux_test", flux_v1);
6171 DOCTEST_CHECK(flux_v1 > 0.0f);
6174 std::vector<helios::vec2> updated_spectrum = {{300, 2.0}, {800, 2.0}};
6175 context.setGlobalData(
"shared_spectrum", updated_spectrum);
6178 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
6181 context.getPrimitiveData(ground,
"radiation_flux_test", flux_v2);
6184 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.8f);
6187GPU_TEST_CASE(
"RadiationModel No Update When Spectrum Unchanged") {
6191 radiation.disableMessages();
6194 std::vector<helios::vec2> spectrum = {{300, 1.0}, {800, 1.0}};
6195 context.setGlobalData(
"test_spectrum", spectrum);
6197 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6198 radiation.setSourceSpectrum(source,
"test_spectrum");
6199 radiation.addRadiationBand(
"test", 400, 700);
6202 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6204 radiation.updateGeometry();
6205 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
6209 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
6212 context.getPrimitiveData(ground,
"radiation_flux_test", flux);
6213 DOCTEST_CHECK(flux > 0.0f);
6216DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties default camera_zoom") {
6221DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties equality with camera_zoom") {
6225 DOCTEST_CHECK(props1 == props2);
6228 DOCTEST_CHECK(props1 != props2);
6231 DOCTEST_CHECK(props1 == props2);
6234GPU_TEST_CASE(
"RadiationModel - camera_zoom validation in updateCameraParameters") {
6237 radiation.disableMessages();
6244 std::vector<std::string> bands = {
"R"};
6245 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
6251 DOCTEST_CHECK_THROWS_WITH_AS(radiation.updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
6255 DOCTEST_CHECK_THROWS_WITH_AS(radiation.updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
6258GPU_TEST_CASE(
"RadiationModel - camera_zoom parameter get/set") {
6261 radiation.disableMessages();
6268 std::vector<std::string> bands = {
"R",
"G",
"B"};
6269 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
6273 DOCTEST_CHECK(retrieved.
HFOV == 60.0f);
6276GPU_TEST_CASE(
"RadiationModel - update camera_zoom") {
6279 radiation.disableMessages();
6286 std::vector<std::string> bands = {
"R"};
6287 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
6292 radiation.updateCameraParameters(
"test_cam", updated);
6296 DOCTEST_CHECK(final_props.
HFOV == 45.0f);
6298GPU_TEST_CASE(
"Lens Flare - Enable/Disable API") {
6305 camera_props.
HFOV = 45.0f;
6306 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6309 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
6312 radiation.enableCameraLensFlare(
"test_camera");
6313 DOCTEST_CHECK(radiation.isCameraLensFlareEnabled(
"test_camera"));
6316 radiation.disableCameraLensFlare(
"test_camera");
6317 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
6320 DOCTEST_CHECK_THROWS(radiation.enableCameraLensFlare(
"nonexistent_camera"));
6321 DOCTEST_CHECK_THROWS(radiation.disableCameraLensFlare(
"nonexistent_camera"));
6322 DOCTEST_CHECK_THROWS((
void) radiation.isCameraLensFlareEnabled(
"nonexistent_camera"));
6325GPU_TEST_CASE(
"Lens Flare - Properties API") {
6332 camera_props.
HFOV = 45.0f;
6333 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6336 LensFlareProperties default_props = radiation.getCameraLensFlareProperties(
"test_camera");
6339 DOCTEST_CHECK(default_props.
ghost_intensity == doctest::Approx(1.0f));
6353 radiation.setCameraLensFlareProperties(
"test_camera", custom_props);
6354 LensFlareProperties retrieved_props = radiation.getCameraLensFlareProperties(
"test_camera");
6358 DOCTEST_CHECK(retrieved_props.
ghost_intensity == doctest::Approx(0.5f));
6367 invalid_props = default_props;
6369 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6372 invalid_props = default_props;
6374 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6377 invalid_props = default_props;
6379 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6382 invalid_props = default_props;
6384 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6387 invalid_props = default_props;
6389 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6392 invalid_props = default_props;
6394 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
6397GPU_TEST_CASE(
"Lens Flare - Application to Camera Image") {
6400 radiation.disableMessages();
6405 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6406 context.setPrimitiveData(bright_patch,
"twosided_flag",
uint(0));
6409 context.setPrimitiveData(ground,
"reflectivity_red", 0.5f);
6410 context.setPrimitiveData(ground,
"reflectivity_green", 0.5f);
6411 context.setPrimitiveData(ground,
"reflectivity_blue", 0.5f);
6412 context.setPrimitiveData(bright_patch,
"reflectivity_red", 0.99f);
6413 context.setPrimitiveData(bright_patch,
"reflectivity_green", 0.99f);
6414 context.setPrimitiveData(bright_patch,
"reflectivity_blue", 0.99f);
6417 radiation.addRadiationBand(
"red");
6418 radiation.addRadiationBand(
"green");
6419 radiation.addRadiationBand(
"blue");
6422 radiation.disableEmission(
"red");
6423 radiation.disableEmission(
"green");
6424 radiation.disableEmission(
"blue");
6427 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6428 radiation.setSourceFlux(source,
"red", 500.0f);
6429 radiation.setSourceFlux(source,
"green", 500.0f);
6430 radiation.setSourceFlux(source,
"blue", 500.0f);
6432 radiation.setDirectRayCount(
"red", 1000);
6433 radiation.setDirectRayCount(
"green", 1000);
6434 radiation.setDirectRayCount(
"blue", 1000);
6435 radiation.setDiffuseRayCount(
"red", 100);
6436 radiation.setDiffuseRayCount(
"green", 100);
6437 radiation.setDiffuseRayCount(
"blue", 100);
6440 radiation.setScatteringDepth(
"red", 1);
6441 radiation.setScatteringDepth(
"green", 1);
6442 radiation.setScatteringDepth(
"blue", 1);
6447 camera_props.
HFOV = 60.0f;
6449 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6452 radiation.enableCameraLensFlare(
"test_camera");
6457 radiation.setCameraLensFlareProperties(
"test_camera", props);
6460 radiation.updateGeometry();
6461 radiation.runBand({
"red",
"green",
"blue"});
6464 DOCTEST_CHECK_NOTHROW(radiation.applyCameraImageCorrections(
"test_camera",
"red",
"green",
"blue"));
6467 auto all_labels = radiation.getAllCameraLabels();
6468 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"test_camera") != all_labels.end());
6471GPU_TEST_CASE(
"Lens Flare - Disabled Does Nothing") {
6474 radiation.disableMessages();
6478 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6479 context.setPrimitiveData(ground,
"reflectivity_red", 0.5f);
6480 context.setPrimitiveData(ground,
"reflectivity_green", 0.5f);
6481 context.setPrimitiveData(ground,
"reflectivity_blue", 0.5f);
6484 radiation.addRadiationBand(
"red");
6485 radiation.addRadiationBand(
"green");
6486 radiation.addRadiationBand(
"blue");
6489 radiation.disableEmission(
"red");
6490 radiation.disableEmission(
"green");
6491 radiation.disableEmission(
"blue");
6494 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6495 radiation.setSourceFlux(source,
"red", 500.0f);
6496 radiation.setSourceFlux(source,
"green", 500.0f);
6497 radiation.setSourceFlux(source,
"blue", 500.0f);
6499 radiation.setDirectRayCount(
"red", 100);
6500 radiation.setDirectRayCount(
"green", 100);
6501 radiation.setDirectRayCount(
"blue", 100);
6504 radiation.setScatteringDepth(
"red", 1);
6505 radiation.setScatteringDepth(
"green", 1);
6506 radiation.setScatteringDepth(
"blue", 1);
6511 camera_props.
HFOV = 45.0f;
6512 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
6515 radiation.updateGeometry();
6516 radiation.runBand({
"red",
"green",
"blue"});
6519 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
6520 DOCTEST_CHECK_NOTHROW(radiation.applyCameraImageCorrections(
"test_camera",
"red",
"green",
"blue"));
6523GPU_TEST_CASE(
"RadiationModel - Camera Sphere Source Rendering") {
6526 radiation.disableMessages();
6529 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6530 context.setPrimitiveData(ground,
"reflectivity_test_band", 0.0f);
6532 radiation.addRadiationBand(
"test_band");
6533 radiation.disableEmission(
"test_band");
6534 radiation.setDirectRayCount(
"test_band", 100);
6535 radiation.setDiffuseRayCount(
"test_band", 0);
6536 radiation.setScatteringDepth(
"test_band", 1);
6538 uint source = radiation.addSphereRadiationSource(helios::make_vec3(0, 0, 0.5), 0.2f);
6539 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6540 context.setGlobalData(
"test_spectrum", test_spectrum);
6541 radiation.setSourceSpectrum(source,
"test_spectrum");
6545 camera_props.
HFOV = 45.0f;
6549 radiation.updateGeometry();
6550 radiation.runBand(
"test_band");
6552 auto pixel_data = radiation.getCameraPixelData(
"sphere_cam",
"test_band");
6553 DOCTEST_REQUIRE(!pixel_data.empty());
6556 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6559GPU_TEST_CASE(
"RadiationModel - Camera Rectangle Source Rendering") {
6562 radiation.disableMessages();
6565 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6566 context.setPrimitiveData(ground,
"reflectivity_test_band", 0.0f);
6568 radiation.addRadiationBand(
"test_band");
6569 radiation.disableEmission(
"test_band");
6570 radiation.setDirectRayCount(
"test_band", 100);
6571 radiation.setDiffuseRayCount(
"test_band", 0);
6572 radiation.setScatteringDepth(
"test_band", 1);
6574 uint source = radiation.addRectangleRadiationSource(helios::make_vec3(0, 0, 0.5),
helios::make_vec2(0.4f, 0.4f), helios::make_vec3(0, 0, 0));
6575 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6576 context.setGlobalData(
"test_spectrum", test_spectrum);
6577 radiation.setSourceSpectrum(source,
"test_spectrum");
6581 camera_props.
HFOV = 45.0f;
6585 radiation.updateGeometry();
6586 radiation.runBand(
"test_band");
6588 auto pixel_data = radiation.getCameraPixelData(
"rect_cam",
"test_band");
6589 DOCTEST_REQUIRE(!pixel_data.empty());
6592 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6595GPU_TEST_CASE(
"RadiationModel - Camera Disk Source Rendering") {
6598 radiation.disableMessages();
6601 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
6602 context.setPrimitiveData(ground,
"reflectivity_test_band", 0.0f);
6604 radiation.addRadiationBand(
"test_band");
6605 radiation.disableEmission(
"test_band");
6606 radiation.setDirectRayCount(
"test_band", 100);
6607 radiation.setDiffuseRayCount(
"test_band", 0);
6608 radiation.setScatteringDepth(
"test_band", 1);
6610 uint source = radiation.addDiskRadiationSource(helios::make_vec3(0, 0, 0.5), 0.2f, helios::make_vec3(0, 0, 0));
6611 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6612 context.setGlobalData(
"test_spectrum", test_spectrum);
6613 radiation.setSourceSpectrum(source,
"test_spectrum");
6617 camera_props.
HFOV = 45.0f;
6621 radiation.updateGeometry();
6622 radiation.runBand(
"test_band");
6624 auto pixel_data = radiation.getCameraPixelData(
"disk_cam",
"test_band");
6625 DOCTEST_REQUIRE(!pixel_data.empty());
6628 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6631GPU_TEST_CASE(
"RadiationModel - Camera Pixel UUID Indexing Validation") {
6644 context.setPrimitiveData(left_patch,
"patch_id",
uint(1));
6645 context.setPrimitiveData(center_patch,
"patch_id",
uint(2));
6646 context.setPrimitiveData(right_patch,
"patch_id",
uint(3));
6654 cam_props.
HFOV = 90;
6674 std::string test_file =
"test_cam_test_camera_indexing_00000.txt";
6678 std::ifstream label_file(test_file);
6679 DOCTEST_REQUIRE_MESSAGE(label_file.is_open(),
"Could not open label map file");
6681 std::vector<float> labels;
6683 while (label_file >> val) {
6684 labels.push_back(val);
6688 DOCTEST_REQUIRE_EQ(labels.size(), 64 * 64);
6693 int left_votes = 0, center_votes = 0, right_votes = 0;
6694 for (
int j = 26; j < 38; j++) {
6695 for (
int i = 20; i < 25; i++) {
6696 float label = labels[j * 64 + i];
6699 else if (label == 2.0f)
6701 else if (label == 3.0f)
6707 DOCTEST_CHECK_MESSAGE(left_votes > right_votes,
"Left region should see world-left patch (ID=1), not world-right (ID=3)");
6708 DOCTEST_CHECK_MESSAGE(left_votes > center_votes,
"Left region should predominantly see left patch");
6711 left_votes = center_votes = right_votes = 0;
6712 for (
int j = 26; j < 38; j++) {
6713 for (
int i = 39; i < 44; i++) {
6714 float label = labels[j * 64 + i];
6717 else if (label == 2.0f)
6719 else if (label == 3.0f)
6725 DOCTEST_CHECK_MESSAGE(right_votes > left_votes,
"Right region should see world-right patch (ID=3), not world-left (ID=1)");
6726 DOCTEST_CHECK_MESSAGE(right_votes > center_votes,
"Right region should predominantly see right patch");
6729 std::remove(test_file.c_str());
6732GPU_TEST_CASE(
"RadiationModel - Pixel Labeling with Fine Tessellation") {
6740 float camera_height = 20.0f;
6741 float HFOV_degrees = 45.0f;
6744 float ground_size = 2.0f * camera_height * tanf(HFOV_degrees * M_PI / 180.0f / 2.0f);
6749 context.setPrimitiveData(ground,
"ground_id",
uint(42));
6757 cam_props.
HFOV = HFOV_degrees;
6777 std::string test_file =
"test_cam_test_fine_tessellation_00000.txt";
6780 std::ifstream label_file(test_file);
6781 DOCTEST_REQUIRE(label_file.is_open());
6783 std::vector<float> labels;
6785 while (label_file >> val) {
6786 labels.push_back(val);
6791 int valid_count = 0;
6793 for (
float label: labels) {
6794 if (std::isnan(label)) {
6796 }
else if (label == 42.0f) {
6801 float valid_percentage = 100.0f * valid_count / labels.size();
6804 DOCTEST_CHECK_MESSAGE(valid_percentage > 95.0f,
"Pixel labeling with fine tessellation should have >95% valid hits, got " << valid_percentage <<
"%");
6807 std::remove(test_file.c_str());
6810GPU_TEST_CASE(
"RadiationModel - runBand Invalid Band Error Handling") {
6813 DOCTEST_SUBCASE(
"Single invalid band") {
6815 RadiationModel radiation1 = RadiationModelTestHelper::createWithSharedDevice(&context1);
6825 bool exception_thrown =
false;
6826 std::string error_message;
6828 radiation1.
runBand(
"INVALID_BAND");
6829 }
catch (
const std::runtime_error &e) {
6830 exception_thrown =
true;
6831 error_message = e.what();
6832 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6833 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6834 }
catch (
const std::out_of_range &e) {
6836 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6838 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band");
6842 DOCTEST_SUBCASE(
"Mixed valid and invalid bands") {
6844 RadiationModel radiation2 = RadiationModelTestHelper::createWithSharedDevice(&context2);
6856 std::vector<std::string> bands = {
"PAR",
"INVALID_BAND",
"NIR"};
6857 bool exception_thrown =
false;
6858 std::string error_message;
6861 }
catch (
const std::runtime_error &e) {
6862 exception_thrown =
true;
6863 error_message = e.what();
6864 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6865 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6866 }
catch (
const std::out_of_range &e) {
6868 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6870 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band in vector");
6874 DOCTEST_SUBCASE(
"All invalid bands") {
6876 RadiationModel radiation3 = RadiationModelTestHelper::createWithSharedDevice(&context3);
6886 std::vector<std::string> bands = {
"INVALID1",
"INVALID2"};
6887 bool exception_thrown =
false;
6888 std::string error_message;
6891 }
catch (
const std::runtime_error &e) {
6892 exception_thrown =
true;
6893 error_message = e.what();
6895 bool found_invalid = error_message.find(
"INVALID1") != std::string::npos || error_message.find(
"INVALID2") != std::string::npos;
6896 DOCTEST_CHECK(found_invalid);
6897 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6898 }
catch (
const std::out_of_range &e) {
6900 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6902 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for all invalid bands");
6906GPU_TEST_CASE(
"RadiationModel - Segmentation Mask to Image Coordinate Alignment") {
6919 context.setPrimitiveData(top_patch,
"patch_id",
uint(1));
6920 context.setPrimitiveData(bottom_patch,
"patch_id",
uint(2));
6921 context.setPrimitiveData(left_patch,
"patch_id",
uint(3));
6922 context.setPrimitiveData(right_patch,
"patch_id",
uint(4));
6930 cam_props.
HFOV = 60;
6947 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"test_alignment",
"./");
6952 std::ifstream json_file(
"test_alignment_masks.json");
6953 DOCTEST_REQUIRE(json_file.is_open());
6955 std::stringstream buffer;
6956 buffer << json_file.rdbuf();
6959 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6962 std::vector<uint> pixel_UUIDs;
6963 context.getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs);
6966 for (
const auto &ann: coco_json[
"annotations"]) {
6967 int bbox_x = ann[
"bbox"][0];
6968 int bbox_y = ann[
"bbox"][1];
6969 int bbox_w = ann[
"bbox"][2];
6970 int bbox_h = ann[
"bbox"][3];
6973 std::vector<int> seg_coords = ann[
"segmentation"][0];
6976 int sample_x = bbox_x + bbox_w / 2;
6977 int sample_y = bbox_y + bbox_h / 2;
6978 uint sample_UUID = pixel_UUIDs.at(sample_y * 128 + sample_x) - 1;
6980 if (!
context.doesPrimitiveExist(sample_UUID)) {
6985 int min_x = 128, max_x = 0, min_y = 128, max_y = 0;
6986 bool found_any =
false;
6988 for (
int j = 0; j < 128; j++) {
6989 for (
int i = 0; i < 128; i++) {
6990 uint UUID = pixel_UUIDs.at(j * 128 + i) - 1;
6991 if (UUID == sample_UUID) {
6992 min_x = std::min(min_x, i);
6993 max_x = std::max(max_x, i);
6994 min_y = std::min(min_y, j);
6995 max_y = std::max(max_y, j);
7003 DOCTEST_CHECK_MESSAGE(bbox_x <= min_x + 2,
"Bbox x-min should match or slightly exceed actual pixels");
7004 DOCTEST_CHECK_MESSAGE(bbox_x + bbox_w >= max_x - 2,
"Bbox x-max should match or slightly exceed actual pixels");
7005 DOCTEST_CHECK_MESSAGE(bbox_y <= min_y + 2,
"Bbox y-min should match or slightly exceed actual pixels");
7006 DOCTEST_CHECK_MESSAGE(bbox_y + bbox_h >= max_y - 2,
"Bbox y-max should match or slightly exceed actual pixels");
7011 std::remove(
"test_alignment_masks.json");
7012 std::remove(image_file.c_str());
7015GPU_TEST_CASE(
"RadiationModel - Mask Spatial Ordering Matches Image") {
7028 context.setPrimitiveData(left_patch,
"patch_id",
uint(10));
7029 context.setPrimitiveData(center_patch,
"patch_id",
uint(20));
7030 context.setPrimitiveData(right_patch,
"patch_id",
uint(30));
7037 cam_props.
HFOV = 70;
7053 std::vector<uint> pixel_UUIDs_check;
7054 context.getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs_check);
7056 for (
uint uuid: pixel_UUIDs_check) {
7057 if (uuid > 0 &&
context.doesPrimitiveExist(uuid - 1)) {
7058 if (
context.doesPrimitiveDataExist(uuid - 1,
"patch_id")) {
7063 DOCTEST_INFO(
"Pixels hitting patches with patch_id: " << patch_hits);
7064 DOCTEST_REQUIRE_MESSAGE(patch_hits > 0,
"Camera should hit at least some patches");
7067 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"spatial_test",
"./");
7071 std::ifstream json_file(
"spatial_test_masks.json");
7072 DOCTEST_REQUIRE(json_file.is_open());
7074 std::stringstream buffer;
7075 buffer << json_file.rdbuf();
7078 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
7081 DOCTEST_INFO(
"Number of annotations: " << coco_json[
"annotations"].size());
7084 std::map<int, int> patch_center_x;
7086 for (
const auto &ann: coco_json[
"annotations"]) {
7087 int cat_id = ann[
"category_id"];
7088 int bbox_x = ann[
"bbox"][0];
7089 int bbox_w = ann[
"bbox"][2];
7090 int center_x = bbox_x + bbox_w / 2;
7096 patch_center_x[center_x] = center_x;
7100 DOCTEST_CHECK_EQ(coco_json[
"annotations"].size(), 3);
7103 std::vector<int> centers;
7104 for (
const auto &ann: coco_json[
"annotations"]) {
7105 int bbox_x = ann[
"bbox"][0];
7106 int bbox_w = ann[
"bbox"][2];
7107 centers.push_back(bbox_x + bbox_w / 2);
7109 std::sort(centers.begin(), centers.end());
7112 if (centers.size() == 3) {
7113 DOCTEST_CHECK_MESSAGE(centers[0] < centers[1],
"Left patch should be left of center patch");
7114 DOCTEST_CHECK_MESSAGE(centers[1] < centers[2],
"Center patch should be left of right patch");
7117 int spacing1 = centers[1] - centers[0];
7118 int spacing2 = centers[2] - centers[1];
7119 DOCTEST_CHECK_MESSAGE(spacing1 > 5,
"Patches should be visibly separated in x");
7120 DOCTEST_CHECK_MESSAGE(spacing2 > 5,
"Patches should be visibly separated in x");
7121 DOCTEST_CHECK_MESSAGE(abs(spacing1 - spacing2) < spacing1 * 0.5,
"Spacing should be roughly uniform");
7125 std::remove(
"spatial_test_masks.json");
7126 std::remove(image_file.c_str());
7129GPU_TEST_CASE(
"RadiationModel - Data Label Maps Match Segmentation Mask Coordinates") {
7141 uint obj1 =
context.addPolymeshObject(patch1_uuids);
7142 uint obj2 =
context.addPolymeshObject(patch2_uuids);
7143 uint obj3 =
context.addPolymeshObject(patch3_uuids);
7146 context.setPrimitiveData(patch1_uuids,
"patch_id",
uint(10));
7147 context.setPrimitiveData(patch2_uuids,
"patch_id",
uint(20));
7148 context.setPrimitiveData(patch3_uuids,
"patch_id",
uint(30));
7160 cam_props.
HFOV = 90;
7176 std::string image_file = radiationmodel.
writeCameraImage(
"test_cam", {
"SW"},
"coord_match_test",
"./");
7182 std::ifstream prim_file(
"test_cam_coord_match_primdata_00000.txt");
7183 DOCTEST_REQUIRE(prim_file.is_open());
7184 std::vector<float> prim_labels;
7186 while (prim_file >> val) {
7187 prim_labels.push_back(val);
7190 DOCTEST_REQUIRE_EQ(prim_labels.size(), 64 * 64);
7193 std::ifstream obj_file(
"test_cam_coord_match_objdata_00000.txt");
7194 DOCTEST_REQUIRE(obj_file.is_open());
7195 std::vector<float> obj_labels;
7196 while (obj_file >> val) {
7197 obj_labels.push_back(val);
7200 DOCTEST_REQUIRE_EQ(obj_labels.size(), 64 * 64);
7203 std::ifstream json_file(
"./coord_match_masks.json");
7204 DOCTEST_REQUIRE(json_file.is_open());
7205 std::stringstream buffer;
7206 buffer << json_file.rdbuf();
7208 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
7212 int total_annotations = coco_json[
"annotations"].size();
7213 DOCTEST_REQUIRE_MESSAGE(total_annotations == 3,
"Should have 3 annotations, got " << total_annotations);
7221 std::vector<std::tuple<int, int, int, int, int>> ann_data;
7222 for (
size_t idx = 0; idx < coco_json[
"annotations"].size(); idx++) {
7223 const auto &ann = coco_json[
"annotations"][idx];
7224 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)});
7226 std::sort(ann_data.begin(), ann_data.end());
7230 std::vector<uint> expected_obj_ids = {100, 200, 300};
7232 for (
size_t i = 0; i < ann_data.size(); i++) {
7233 auto [bbox_x, bbox_y, bbox_w, bbox_h, ann_idx] = ann_data[i];
7234 uint expected_obj_value = expected_obj_ids[i];
7237 int correct_value_count = 0;
7238 int total_pixels = 0;
7240 for (
int dy = 0; dy < bbox_h; dy++) {
7241 for (
int dx = 0; dx < bbox_w; dx++) {
7242 int px = bbox_x + dx;
7243 int py = bbox_y + dy;
7245 if (px < 0 || px >= 64 || py < 0 || py >= 64)
7248 float obj_value = obj_labels[py * 64 + px];
7251 if (fabs(obj_value - expected_obj_value) < 1.0f) {
7252 correct_value_count++;
7258 float match_percentage = 100.0f * correct_value_count / total_pixels;
7261 int center_x = bbox_x + bbox_w / 2;
7262 int center_y = bbox_y + bbox_h / 2;
7263 float sample_actual_value = obj_labels[center_y * 64 + center_x];
7266 DOCTEST_CHECK_MESSAGE(match_percentage > 80.0f,
"At least 80% of bbox pixels should have CORRECT data value in label map. "
7267 "If this fails, label map coordinates are flipped relative to mask. Got "
7268 << match_percentage <<
"%");
7272 std::remove(
"test_cam_coord_match_primdata_00000.txt");
7273 std::remove(
"test_cam_coord_match_objdata_00000.txt");
7274 std::remove(
"./coord_match_masks.json");
7275 std::remove(image_file.c_str());
7278GPU_TEST_CASE(
"RadiationModel - Pixel Label UUID Mapping With Non-Sequential Object Ordering") {
7294 std::vector<uint> obj_patch_UUIDs;
7295 for (
int i = 0; i < 9; i++) {
7296 float x = -1.5f + (i % 3) * 0.5f;
7297 float y = (i / 3) * 0.5f - 0.5f;
7300 context.addPolymeshObject(obj_patch_UUIDs);
7306 DOCTEST_REQUIRE_EQ(
context.getPrimitiveParentObjectID(orphan_UUID), 0u);
7307 DOCTEST_REQUIRE_GT(
context.getPrimitiveParentObjectID(obj_patch_UUIDs.front()), 0u);
7308 DOCTEST_REQUIRE_GT(orphan_UUID, obj_patch_UUIDs.back());
7311 context.setPrimitiveData(obj_patch_UUIDs,
"element_type", 1u);
7312 context.setPrimitiveData(orphan_UUID,
"element_type", 2u);
7320 cam_props.
HFOV = 90;
7338 std::ifstream label_file(
"test_cam_uuid_mapping_test_00000.txt");
7339 DOCTEST_REQUIRE(label_file.is_open());
7340 std::vector<float> labels;
7342 while (label_file >> val) {
7343 labels.push_back(val);
7346 DOCTEST_REQUIRE_EQ(labels.size(), 64u * 64u);
7351 int left_polymesh = 0, left_orphan = 0;
7352 int right_polymesh = 0, right_orphan = 0;
7354 for (
int j = 0; j < 64; j++) {
7355 for (
int i = 0; i < 64; i++) {
7356 float label = labels[j * 64 + i];
7357 if (std::isnan(label))
continue;
7359 uint label_uint =
static_cast<uint>(label);
7360 bool is_left = (i < 32);
7363 if (label_uint == 1u) left_polymesh++;
7364 else if (label_uint == 2u) left_orphan++;
7366 if (label_uint == 1u) right_polymesh++;
7367 else if (label_uint == 2u) right_orphan++;
7374 DOCTEST_CHECK_MESSAGE(left_polymesh > 0,
"Polymesh patches (element_type=1) should appear on the left side of the image");
7375 DOCTEST_CHECK_MESSAGE(right_orphan > 0,
"Orphan patch (element_type=2) should appear on the right side of the image");
7376 DOCTEST_CHECK_MESSAGE(left_orphan == 0,
7377 "No orphan labels should appear on the left side (got " << left_orphan <<
" — indicates UUID mapping error)");
7378 DOCTEST_CHECK_MESSAGE(right_polymesh == 0,
7379 "No polymesh labels should appear on the right side (got " << right_polymesh <<
" — indicates UUID mapping error)");
7381 std::remove(
"test_cam_uuid_mapping_test_00000.txt");
7384GPU_TEST_CASE(
"Material Backend Migration - Spectrum Interpolation Integration") {
7392 std::vector<helios::vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
7393 std::vector<helios::vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
7395 context.setGlobalData(
"rho_young", spectrum_young);
7396 context.setGlobalData(
"rho_old", spectrum_old);
7400 context.setPrimitiveData(uuid,
"leaf_age", 8.0f);
7403 std::vector<uint> uuids = {uuid};
7404 std::vector<std::string> spectra = {
"rho_young",
"rho_old"};
7405 std::vector<float> values = {0.0f, 10.0f};
7419 radiationmodel.
runBand(
"PAR");
7422 std::string assigned_spectrum;
7423 DOCTEST_REQUIRE(
context.doesPrimitiveDataExist(uuid,
"reflectivity_spectrum"));
7424 context.getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
7425 DOCTEST_CHECK(assigned_spectrum ==
"rho_old");
7428GPU_TEST_CASE(
"Material Backend Migration - Camera Weighted Materials") {
7436 std::vector<helios::vec2> object_spectrum = {{400, 0.1}, {500, 0.3}, {600, 0.5}, {700, 0.7}};
7437 context.setGlobalData(
"object_rho", object_spectrum);
7440 std::vector<helios::vec2> camera_response = {{400, 0.2}, {500, 0.8}, {600, 0.8}, {700, 0.2}};
7441 context.setGlobalData(
"camera_green", camera_response);
7444 std::vector<helios::vec2> source_spectrum = {{400, 0.8}, {500, 1.0}, {600, 1.0}, {700, 0.9}};
7445 context.setGlobalData(
"sunlight", source_spectrum);
7449 context.setPrimitiveData(uuid,
"reflectivity_spectrum", std::string(
"object_rho"));
7464 cam_props.
HFOV = 45.f;
7468 std::vector<std::string> band_labels = {
"VIS"};
7469 radiationmodel.
addRadiationCamera(
"test_cam", band_labels, helios::make_vec3(0, -5, 0), helios::make_vec3(0, 0, 0), cam_props, 1);
7474 radiationmodel.
runBand(
"VIS");
7477 DOCTEST_CHECK(
context.doesGlobalDataExist(
"camera_test_cam_VIS"));
7480 if (
context.doesGlobalDataExist(
"camera_test_cam_VIS")) {
7481 std::vector<float> camera_data;
7482 context.getGlobalData(
"camera_test_cam_VIS", camera_data);
7483 DOCTEST_CHECK(camera_data.size() == 100);
7487GPU_TEST_CASE(
"RadiationModel - Specular Reflection Camera Rendering") {
7493 radiation.disableMessages();
7497 context.setPrimitiveData(UUID,
"twosided_flag",
uint(1));
7500 std::vector<helios::vec2> reflectivity = {
make_vec2(400, 0.05f),
make_vec2(700, 0.05f)};
7501 context.setGlobalData(
"reflectivity", reflectivity);
7502 context.setPrimitiveData(UUID,
"reflectivity_spectrum",
"reflectivity");
7504 std::vector<helios::vec2> zero_transmissivity = {
make_vec2(400, 0.0f),
make_vec2(700, 0.0f)};
7505 context.setGlobalData(
"zero_transmissivity", zero_transmissivity);
7506 context.setPrimitiveData(UUID,
"transmissivity_spectrum",
"zero_transmissivity");
7509 radiation.addRadiationBand(
"SUN");
7510 radiation.setScatteringDepth(
"SUN", 1);
7513 uint source = radiation.addCollimatedRadiationSource(sun_direction);
7514 radiation.setSourceFlux(source,
"SUN", 1000.0f);
7515 radiation.setDirectRayCount(
"SUN", 10000);
7516 radiation.setDiffuseRayCount(
"SUN", 0);
7517 radiation.disableEmission(
"SUN");
7526 cam_props.
HFOV = 30.0f;
7527 radiation.addRadiationCamera(
"test_cam", {
"SUN"}, camera_pos, camera_lookat, cam_props, 100);
7529 std::vector<helios::vec2> camera_response = {
make_vec2(400, 1.0f),
make_vec2(700, 1.0f)};
7530 context.setGlobalData(
"camera_response", camera_response);
7531 radiation.setCameraSpectralResponse(
"test_cam",
"SUN",
"camera_response");
7536 context.setPrimitiveData(UUID,
"specular_exponent", -1.0f);
7537 radiation.updateGeometry();
7538 radiation.runBand(
"SUN");
7541 std::vector<float> pixels_no_specular;
7542 context.getGlobalData(
"camera_test_cam_SUN", pixels_no_specular);
7544 float sum_no_specular = 0.0f;
7545 for (
float p: pixels_no_specular) {
7546 sum_no_specular += p;
7548 float avg_no_specular = sum_no_specular / (float)pixels_no_specular.size();
7553 context.setPrimitiveData(UUID,
"specular_exponent", 50.0f);
7554 radiation.updateGeometry();
7555 radiation.runBand(
"SUN");
7558 std::vector<float> pixels_with_specular;
7559 context.getGlobalData(
"camera_test_cam_SUN", pixels_with_specular);
7561 float sum_with_specular = 0.0f;
7562 for (
float p: pixels_with_specular) {
7563 sum_with_specular += p;
7565 float avg_with_specular = sum_with_specular / (float)pixels_with_specular.size();
7567 float difference = avg_with_specular - avg_no_specular;
7569 DOCTEST_MESSAGE(
"No specular avg: " << avg_no_specular <<
", With specular avg: " << avg_with_specular <<
", Difference: " << difference);
7572 DOCTEST_CHECK_MESSAGE(difference > 5.0f,
"Specular exponent should increase camera intensity. "
7574 << avg_no_specular <<
", With specular: " << avg_with_specular <<
", Difference: " << difference);
7577GPU_TEST_CASE(
"RadiationModel - Specular Reflection Multiple Cameras") {
7584 radiation.disableMessages();
7588 context.setPrimitiveData(UUID,
"twosided_flag",
uint(1));
7590 std::vector<helios::vec2> reflectivity = {
make_vec2(400, 0.05f),
make_vec2(700, 0.05f)};
7591 context.setGlobalData(
"reflectivity", reflectivity);
7592 context.setPrimitiveData(UUID,
"reflectivity_spectrum",
"reflectivity");
7594 std::vector<helios::vec2> zero_transmissivity = {
make_vec2(400, 0.0f),
make_vec2(700, 0.0f)};
7595 context.setGlobalData(
"zero_transmissivity", zero_transmissivity);
7596 context.setPrimitiveData(UUID,
"transmissivity_spectrum",
"zero_transmissivity");
7598 context.setPrimitiveData(UUID,
"specular_exponent", 50.0f);
7600 radiation.addRadiationBand(
"SUN");
7601 radiation.setScatteringDepth(
"SUN", 1);
7604 uint source = radiation.addCollimatedRadiationSource(sun_direction);
7605 radiation.setSourceFlux(source,
"SUN", 1000.0f);
7606 radiation.setDirectRayCount(
"SUN", 10000);
7607 radiation.setDiffuseRayCount(
"SUN", 0);
7608 radiation.disableEmission(
"SUN");
7614 cam_props.
HFOV = 30.0f;
7616 std::vector<helios::vec2> camera_response = {
make_vec2(400, 1.0f),
make_vec2(700, 1.0f)};
7617 context.setGlobalData(
"camera_response", camera_response);
7620 radiation.addRadiationCamera(
"cam_A", {
"SUN"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), cam_props, 100);
7621 radiation.setCameraSpectralResponse(
"cam_A",
"SUN",
"camera_response");
7624 radiation.addRadiationCamera(
"cam_B", {
"SUN"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), cam_props, 100);
7625 radiation.setCameraSpectralResponse(
"cam_B",
"SUN",
"camera_response");
7629 radiation.updateGeometry();
7630 radiation.runBand(
"SUN");
7634 std::vector<float> pixels_A, pixels_B;
7635 context.getGlobalData(
"camera_cam_A_SUN", pixels_A);
7636 context.getGlobalData(
"camera_cam_B_SUN", pixels_B);
7638 float sum_A = 0.0f, sum_B = 0.0f;
7639 for (
float p : pixels_A) sum_A += p;
7640 for (
float p : pixels_B) sum_B += p;
7641 float avg_A = sum_A / (float)pixels_A.size();
7642 float avg_B = sum_B / (float)pixels_B.size();
7645 float diffuse_baseline = 15.0f;
7647 DOCTEST_MESSAGE(
"Camera A avg: " << avg_A <<
", Camera B avg: " << avg_B <<
", Diffuse baseline ~" << diffuse_baseline);
7650 DOCTEST_CHECK_MESSAGE(avg_A > diffuse_baseline * 2.0f,
"Camera A should show specular highlight. avg_A: " << avg_A);
7651 DOCTEST_CHECK_MESSAGE(avg_B > diffuse_baseline * 2.0f,
"Camera B should show specular highlight. avg_B: " << avg_B);
7654 float ratio = (avg_A > avg_B) ? avg_B / avg_A : avg_A / avg_B;
7655 DOCTEST_CHECK_MESSAGE(ratio > 0.8f,
"Both cameras should see similar specular intensity. "
7656 "avg_A: " << avg_A <<
", avg_B: " << avg_B <<
", ratio: " << ratio);
7659GPU_TEST_CASE(
"RadiationModel More Than 4 Simultaneous Radiation Bands") {
7664 const int Nbands = 6;
7665 const float error_threshold = 0.005f;
7666 const uint Ndirect = 10000;
7670 radiation.disableMessages();
7676 uint src = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
7678 std::vector<std::string> band_names;
7679 std::vector<float> expected_flux;
7681 for (
int b = 0; b < Nbands; b++) {
7682 std::string name =
"band_" + std::to_string(b);
7683 band_names.push_back(name);
7684 float flux = float(b + 1) * 100.f;
7685 expected_flux.push_back(flux);
7687 radiation.addRadiationBand(name);
7688 radiation.disableEmission(name);
7689 radiation.setSourceFlux(src, name, flux);
7690 radiation.setDirectRayCount(name, Ndirect);
7693 radiation.updateGeometry();
7694 radiation.runBand(band_names);
7696 for (
int b = 0; b < Nbands; b++) {
7698 context.getPrimitiveData(UUID, (
"radiation_flux_" + band_names[b]).c_str(), measured);
7699 float rel_error = std::abs(measured - expected_flux[b]) / expected_flux[b];
7700 DOCTEST_CHECK_MESSAGE(rel_error <= error_threshold,
"Band " << band_names[b] <<
": expected " << expected_flux[b] <<
", got " << measured <<
" (error " << rel_error <<
")");
7708GPU_TEST_CASE(
"RadiationModel - Camera triangle vs patch rendering parity") {
7717 radiation.disableMessages();
7721 float reflectivity = 0.5f;
7724 context.setPrimitiveData(patch_id,
"reflectivity_SW", reflectivity);
7725 context.setPrimitiveData(patch_id,
"twosided_flag",
uint(0));
7731 context.setPrimitiveData(tri_id,
"reflectivity_SW", reflectivity);
7734 radiation.addRadiationBand(
"SW");
7735 radiation.disableEmission(
"SW");
7736 radiation.setDirectRayCount(
"SW", 250);
7737 radiation.setDiffuseRayCount(
"SW", 100);
7738 radiation.setScatteringDepth(
"SW", 1);
7741 uint src = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
7742 radiation.setSourceFlux(src,
"SW", 500.f);
7747 cam_props.
HFOV = 60.0f;
7749 radiation.addRadiationCamera(
"test_cam", {
"SW"},
7754 radiation.updateGeometry();
7755 radiation.runBand(
"SW");
7757 auto pixel_data = radiation.getCameraPixelData(
"test_cam",
"SW");
7758 DOCTEST_REQUIRE(pixel_data.size() == 64 * 64);
7766 float patch_sum = 0;
7767 int patch_count = 0;
7772 for (
int j = 24; j < 40; j++) {
7773 for (
int i = 40; i < 56; i++) {
7774 patch_sum += pixel_data[j * 64 + i];
7780 for (
int j = 24; j < 40; j++) {
7781 for (
int i = 8; i < 24; i++) {
7782 tri_sum += pixel_data[j * 64 + i];
7787 float patch_avg = patch_sum / float(patch_count);
7788 float tri_avg = tri_sum / float(tri_count);
7791 DOCTEST_CHECK_MESSAGE(patch_avg > 0.0f,
7792 "Patch region average radiance should be > 0, got " << patch_avg);
7793 DOCTEST_CHECK_MESSAGE(tri_avg > 0.0f,
7794 "Triangle region average radiance should be > 0, got " << tri_avg);
7798 if (patch_avg > 0.0f && tri_avg > 0.0f) {
7799 float ratio = tri_avg / patch_avg;
7800 DOCTEST_CHECK_MESSAGE(ratio > 0.1f,
7801 "Triangle/patch radiance ratio too low: " << ratio
7802 <<
" (tri_avg=" << tri_avg <<
", patch_avg=" << patch_avg <<
")");
7803 DOCTEST_CHECK_MESSAGE(ratio < 10.0f,
7804 "Triangle/patch radiance ratio too high: " << ratio
7805 <<
" (tri_avg=" << tri_avg <<
", patch_avg=" << patch_avg <<
")");
7809GPU_TEST_CASE(
"RadiationModel - Multi-tile camera rendering consistency") {
7821 radiation.disableMessages();
7826 context.setPrimitiveData(ground,
"reflectivity_red", 0.3f);
7827 context.setPrimitiveData(ground,
"reflectivity_green", 0.3f);
7828 context.setPrimitiveData(ground,
"reflectivity_blue", 0.3f);
7829 context.setPrimitiveData(ground,
"twosided_flag",
uint(0));
7833 uint src = radiation.addSunSphereRadiationSource(sun_dir);
7834 radiation.setSourceSpectrum(src,
"solar_spectrum_ASTMG173");
7837 radiation.addRadiationBand(
"red");
7838 radiation.disableEmission(
"red");
7839 radiation.setScatteringDepth(
"red", 1);
7840 radiation.setDiffuseRadiationExtinctionCoeff(
"red", 0.2f, sun_dir);
7841 radiation.copyRadiationBand(
"red",
"green");
7842 radiation.copyRadiationBand(
"red",
"blue");
7844 radiation.setDiffuseSpectrum(
"solar_spectrum_ASTMG173");
7845 radiation.setDiffuseSpectrumIntegral(100.f);
7853 radiation.addRadiationCameraFromLibrary(
"iphone_cam",
"iPhone12ProMAX",
7854 cam_pos, cam_lookat, 100);
7857 radiation.updateGeometry();
7861 radiation.runBand({
"red",
"green",
"blue"});
7868 DOCTEST_REQUIRE(W == 3024);
7869 DOCTEST_REQUIRE(H == 4032);
7871 auto pixel_red = radiation.getCameraPixelData(
"iphone_cam",
"red");
7872 DOCTEST_REQUIRE(pixel_red.size() == (
size_t)W * H);
7882 float tile1_sum = 0;
7883 int tile1_count = 0;
7884 for (
int j = 500; j < 600; j++) {
7885 for (
int i = W / 4; i < 3 * W / 4; i += 10) {
7886 tile1_sum += pixel_red[j * W + i];
7892 float tile2_sum = 0;
7893 int tile2_count = 0;
7894 for (
int j = 3600; j < 3700; j++) {
7895 for (
int i = W / 4; i < 3 * W / 4; i += 10) {
7896 tile2_sum += pixel_red[j * W + i];
7901 float tile1_avg = tile1_sum / float(tile1_count);
7902 float tile2_avg = tile2_sum / float(tile2_count);
7905 DOCTEST_CHECK_MESSAGE(tile1_avg > 0.0f,
7906 "Tile 1 (rows 500-600) average radiance should be > 0, got " << tile1_avg);
7907 DOCTEST_CHECK_MESSAGE(tile2_avg > 0.0f,
7908 "Tile 2 (rows 3600-3700) average radiance should be > 0, got " << tile2_avg);
7911 if (tile1_avg > 0.0f && tile2_avg > 0.0f) {
7912 float ratio = tile2_avg / tile1_avg;
7913 DOCTEST_CHECK_MESSAGE(ratio > 0.1f,
7914 "Tile 2/tile 1 radiance ratio too low: " << ratio
7915 <<
" (tile1=" << tile1_avg <<
", tile2=" << tile2_avg <<
")");
7916 DOCTEST_CHECK_MESSAGE(ratio < 10.0f,
7917 "Tile 2/tile 1 radiance ratio too high: " << ratio
7918 <<
" (tile1=" << tile1_avg <<
", tile2=" << tile2_avg <<
")");
7935 struct FluspectReferenceConfig {
7936 float Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, fqe, V2Z;
7937 float wle_step, wlf_step;
7940 FluspectReferenceConfig load_fluspect_reference_config() {
7941 FluspectReferenceConfig c{};
7942 const std::filesystem::path p =
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_config.csv");
7944 DOCTEST_REQUIRE_MESSAGE(f.good(),
"Failed to open " << p.string());
7946 while (std::getline(f, line)) {
7947 if (line.empty() || line[0] ==
'#' || line.substr(0, 3) ==
"key")
continue;
7948 const auto comma = line.find(
',');
7949 if (comma == std::string::npos)
continue;
7950 const std::string key = line.substr(0, comma);
7951 const float val = std::stof(line.substr(comma + 1));
7952 if (key ==
"Cab") c.Cab = val;
7953 else if (key ==
"Cca") c.Cca = val;
7954 else if (key ==
"Cw") c.Cw = val;
7955 else if (key ==
"Cdm") c.Cdm = val;
7956 else if (key ==
"Cs") c.Cs = val;
7957 else if (key ==
"Cant") c.Cant = val;
7958 else if (key ==
"Cp") c.Cp = val;
7959 else if (key ==
"Cbc") c.Cbc = val;
7960 else if (key ==
"N") c.N = val;
7961 else if (key ==
"fqe") c.fqe = val;
7962 else if (key ==
"V2Z") c.V2Z = val;
7963 else if (key ==
"wle_step") c.wle_step = val;
7964 else if (key ==
"wlf_step") c.wlf_step = val;
7975 std::vector<std::vector<double>> load_matrix_csv(
const std::filesystem::path &p,
7976 std::vector<float> &wle_out,
7977 std::vector<float> &wlf_out) {
7979 DOCTEST_REQUIRE_MESSAGE(f.good(),
"Failed to open " << p.string());
7983 std::vector<std::vector<double>> M;
7986 while (std::getline(f, line)) {
7987 if (line.empty() || line[0] ==
'#')
continue;
7991 std::stringstream hs(line);
7994 while (std::getline(hs, cell,
',')) {
7995 if (first) { first =
false;
continue; }
7996 wle_out.push_back(std::stof(cell));
7999 while (std::getline(f, line)) {
8000 if (line.empty() || line[0] ==
'#')
continue;
8001 std::stringstream ds(line);
8002 std::vector<double> row;
8003 bool is_first =
true;
8004 while (std::getline(ds, cell,
',')) {
8006 wlf_out.push_back(std::stof(cell));
8010 row.push_back(std::stod(cell));
8012 if (!row.empty()) M.push_back(std::move(row));
8019DOCTEST_TEST_CASE(
"SIF V&V Tier 1 (v2): Fluspect-B C++ port matches MATLAB reference") {
8022 const std::filesystem::path optipar_xml =
helios::resolveFilePath(
"plugins/radiation/spectral_data/fluspect_B_optipar.xml");
8024 DOCTEST_CHECK(optipar.wavelengths_nm.size() > 1000);
8027 const FluspectReferenceConfig cfg = load_fluspect_reference_config();
8028 std::vector<float> ref_wle, ref_wlf;
8029 const auto Mf_ref = load_matrix_csv(
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_Mf.csv"), ref_wle, ref_wlf);
8030 std::vector<float> ref_wle2, ref_wlf2;
8031 const auto Mb_ref = load_matrix_csv(
helios::resolveFilePath(
"plugins/radiation/tests/reference/fluspect_reference_Mb.csv"), ref_wle2, ref_wlf2);
8032 DOCTEST_REQUIRE(ref_wle == ref_wle2);
8033 DOCTEST_REQUIRE(ref_wlf == ref_wlf2);
8034 DOCTEST_REQUIRE(Mf_ref.size() == ref_wlf.size());
8038 biochem.
Cab = cfg.Cab;
8039 biochem.
Cca = cfg.Cca;
8040 biochem.
Cw = cfg.Cw;
8041 biochem.
Cdm = cfg.Cdm;
8042 biochem.
Cs = cfg.Cs;
8043 biochem.
Cant = cfg.Cant;
8044 biochem.
Cp = cfg.Cp;
8045 biochem.
Cbc = cfg.Cbc;
8047 biochem.
fqe = cfg.fqe;
8048 biochem.
V2Z = cfg.V2Z;
8052 DOCTEST_REQUIRE(out.
wle.size() == ref_wle.size());
8053 DOCTEST_REQUIRE(out.
wlf.size() == ref_wlf.size());
8054 for (
size_t j = 0; j < out.
wle.size(); ++j) {
8055 DOCTEST_CHECK(out.
wle[j] == doctest::Approx(ref_wle[j]).epsilon(1e-5));
8057 for (
size_t i = 0; i < out.
wlf.size(); ++i) {
8058 DOCTEST_CHECK(out.
wlf[i] == doctest::Approx(ref_wlf[i]).epsilon(1e-5));
8066 size_t n_total = 0, n_checked_Mf = 0, n_checked_Mb = 0;
8067 double max_rel_err_Mf = 0.0, max_rel_err_Mb = 0.0;
8068 for (
size_t i = 0; i < out.
wlf.size(); ++i) {
8069 for (
size_t j = 0; j < out.
wle.size(); ++j) {
8070 const double cf = out.
Mf[i][j];
8071 const double cb = out.
Mb[i][j];
8072 const double rf = Mf_ref[i][j];
8073 const double rb = Mb_ref[i][j];
8074 if (std::abs(rf) > 1e-10) {
8075 max_rel_err_Mf = std::max(max_rel_err_Mf, std::abs(cf - rf) / std::abs(rf));
8078 if (std::abs(rb) > 1e-10) {
8079 max_rel_err_Mb = std::max(max_rel_err_Mb, std::abs(cb - rb) / std::abs(rb));
8085 DOCTEST_MESSAGE(
"Fluspect Mf max relative error: " << max_rel_err_Mf);
8086 DOCTEST_MESSAGE(
"Fluspect Mb max relative error: " << max_rel_err_Mb);
8087 DOCTEST_MESSAGE(
"Elements compared: Mf=" << n_checked_Mf <<
"/" << n_total
8088 <<
" Mb=" << n_checked_Mb <<
"/" << n_total
8089 <<
" (rest below 1e-10 magnitude floor — anti-Stokes wavelengths)");
8092 DOCTEST_CHECK(n_checked_Mf > 0.9 * n_total);
8093 DOCTEST_CHECK(n_checked_Mb > 0.9 * n_total);
8094 DOCTEST_CHECK(max_rel_err_Mf < 1e-4);
8095 DOCTEST_CHECK(max_rel_err_Mb < 1e-4);
8111 void sif_stamp_biochem(
Context &ctx,
const std::vector<uint> &UUIDs,
const std::string &label,
8112 float Cab = 40.f,
float Cca = 10.f,
float Cw = 0.009f,
float Cdm = 0.012f,
8113 float Cs = 0.f,
float Cant = 1.f,
float Cp = 0.f,
float Cbc = 0.f,
8114 float N = 1.5f,
float V2Z = 0.f,
float fqe = 1.f) {
8115 const std::string full_label =
"fluspect_biochem_" + label;
8116 std::vector<float> biochem = {Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe};
8139GPU_TEST_CASE(
"SIF V&V Tier 2 (v2): full pipeline with solar source + SIF camera") {
8145 sif_stamp_biochem(ctx, {leaf},
"t2");
8152 const float sensor_side = 1.f;
8157 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8158 radiation.disableMessages();
8161 radiation.addRadiationBand(
"SIF_red", 680.f, 700.f);
8162 radiation.addRadiationBand(
"SIF_farred", 730.f, 760.f);
8163 radiation.setDirectRayCount(
"SIF_red", 500);
8164 radiation.setDirectRayCount(
"SIF_farred", 500);
8165 radiation.setDiffuseRayCount(
"SIF_red", 500);
8166 radiation.setDiffuseRayCount(
"SIF_farred", 500);
8167 radiation.setScatteringDepth(
"SIF_red", 1);
8168 radiation.setScatteringDepth(
"SIF_farred", 1);
8174 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8175 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8181 cam_props.
HFOV = 30.f;
8183 radiation.addSIFCamera(
"sif_cam", {
"SIF_red",
"SIF_farred"},
8186 DOCTEST_CHECK(radiation.isSIFCamera(
"sif_cam"));
8187 DOCTEST_CHECK(!radiation.isSIFCamera(
"nonexistent"));
8189 radiation.updateGeometry();
8190 const std::vector<std::string> sif_bands = {
"SIF_red",
"SIF_farred"};
8191 radiation.runBand(sif_bands);
8197 DOCTEST_CHECK(phi_f > 0.f);
8198 DOCTEST_CHECK(phi_f < 0.1f);
8199 DOCTEST_MESSAGE(
"Leaf Phi_F = " << phi_f);
8204 float flux_red = 0.f, flux_farred = 0.f;
8207 DOCTEST_MESSAGE(
"Sensor F_red=" << flux_red <<
" F_farred=" << flux_farred
8208 <<
" ratio=" << (flux_red / std::max(flux_farred, 1e-12f)));
8209 DOCTEST_CHECK(std::isfinite(flux_red));
8210 DOCTEST_CHECK(std::isfinite(flux_farred));
8211 DOCTEST_CHECK(flux_red > 0.f);
8212 DOCTEST_CHECK(flux_farred > 0.f);
8216 auto pixels_red = radiation.getCameraPixelData(
"sif_cam",
"SIF_red");
8217 auto pixels_farred = radiation.getCameraPixelData(
"sif_cam",
"SIF_farred");
8218 DOCTEST_CHECK(pixels_red.size() == 16 * 16);
8219 DOCTEST_CHECK(pixels_farred.size() == 16 * 16);
8220 float max_pixel_red = 0.f, max_pixel_farred = 0.f;
8221 for (
float v : pixels_red) {
8222 DOCTEST_CHECK(std::isfinite(v));
8223 DOCTEST_CHECK(v >= 0.f);
8224 if (v > max_pixel_red) max_pixel_red = v;
8226 for (
float v : pixels_farred) {
8227 DOCTEST_CHECK(std::isfinite(v));
8228 DOCTEST_CHECK(v >= 0.f);
8229 if (v > max_pixel_farred) max_pixel_farred = v;
8231 DOCTEST_CHECK(max_pixel_red > 0.f);
8232 DOCTEST_CHECK(max_pixel_farred > 0.f);
8253GPU_TEST_CASE(
"SIF V&V Tier 3 (v2): multi-camera pipeline with distinct excitation resolutions") {
8259 sif_stamp_biochem(ctx, {leaf},
"t3");
8264 const float sensor_side = 1.f;
8269 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8270 radiation.disableMessages();
8273 radiation.addRadiationBand(
"SIF_red_fine", 680.f, 700.f);
8274 radiation.addRadiationBand(
"SIF_red_coarse", 680.f, 700.f);
8275 radiation.addRadiationBand(
"SIF_farred_fine", 730.f, 760.f);
8276 for (
const auto &b : {
"SIF_red_fine",
"SIF_red_coarse",
"SIF_farred_fine"}) {
8277 radiation.setDirectRayCount(b, 500);
8278 radiation.setDiffuseRayCount(b, 500);
8279 radiation.setScatteringDepth(b, 0);
8283 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8284 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8299 radiation.addSIFCamera(
"cam_a", {
"SIF_red_fine",
"SIF_farred_fine"},
8303 radiation.addSIFCamera(
"cam_b", {
"SIF_farred_fine"},
8306 radiation.addSIFCamera(
"cam_c", {
"SIF_red_coarse"},
8310 DOCTEST_CHECK(radiation.isSIFCamera(
"cam_a"));
8311 DOCTEST_CHECK(radiation.isSIFCamera(
"cam_b"));
8312 DOCTEST_CHECK(radiation.isSIFCamera(
"cam_c"));
8313 DOCTEST_CHECK(!radiation.isSIFCamera(
"nonexistent"));
8318 regular_props.
HFOV = 20.f;
8319 radiation.addRadiationCamera(
"regular_cam", {
"SIF_red_fine"},
8321 DOCTEST_CHECK(!radiation.isSIFCamera(
"regular_cam"));
8327 DOCTEST_CHECK(radiation.doesBandExist(
"_SIF_exc_10_400_410"));
8328 DOCTEST_CHECK(radiation.doesBandExist(
"_SIF_exc_10_740_750"));
8329 DOCTEST_CHECK(radiation.doesBandExist(
"_SIF_exc_25_400_425"));
8330 DOCTEST_CHECK(radiation.doesBandExist(
"_SIF_exc_25_725_750"));
8335 DOCTEST_CHECK_THROWS_AS(
8336 radiation.addSIFCamera(
"cam_conflict", {
"SIF_red_fine"},
8338 std::runtime_error);
8344 radiation.updateGeometry();
8345 const std::vector<std::string> sif_bands_list = {
"SIF_red_fine",
"SIF_red_coarse",
"SIF_farred_fine"};
8346 radiation.runBand(sif_bands_list);
8348 float flux_red_fine = 0.f, flux_red_coarse = 0.f, flux_farred_fine = 0.f;
8349 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_red_fine", flux_red_fine);
8350 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_red_coarse", flux_red_coarse);
8351 ctx.
getPrimitiveData(sensor,
"radiation_flux_SIF_farred_fine", flux_farred_fine);
8352 DOCTEST_MESSAGE(
"Sensor F_red_fine=" << flux_red_fine
8353 <<
" F_red_coarse=" << flux_red_coarse
8354 <<
" F_farred_fine=" << flux_farred_fine);
8355 DOCTEST_CHECK(flux_red_fine > 0.f);
8356 DOCTEST_CHECK(flux_red_coarse > 0.f);
8357 DOCTEST_CHECK(flux_farred_fine > 0.f);
8365 DOCTEST_CHECK(flux_red_coarse > 0.33f * flux_red_fine);
8366 DOCTEST_CHECK(flux_red_coarse < 3.f * flux_red_fine);
8378GPU_TEST_CASE(
"SIF warnings: no source spectrum set") {
8381 sif_stamp_biochem(ctx, {leaf},
"warn_no_spec");
8383 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8384 radiation.addRadiationBand(
"SIF_red", 680.f, 700.f);
8387 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8392 cam_props.
HFOV = 20.f;
8395 std::string captured;
8398 radiation.addSIFCamera(
"warn_cam_no_spectrum", {
"SIF_red"},
8402 DOCTEST_CHECK(captured.find(
"no radiation source has a spectrum set") != std::string::npos);
8405GPU_TEST_CASE(
"SIF warnings: no fluspect_spectrum on any primitive") {
8411 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8412 radiation.addRadiationBand(
"SIF_red", 680.f, 700.f);
8413 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8414 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8418 cam_props.
HFOV = 20.f;
8421 std::string captured;
8424 radiation.addSIFCamera(
"warn_no_biochem", {
"SIF_red"},
8428 DOCTEST_CHECK(captured.find(
"fluspect_spectrum") != std::string::npos);
8431GPU_TEST_CASE(
"SIF warnings: leaves have biochemistry but lack electron_transport_ratio at runtime") {
8438 sif_stamp_biochem(ctx, {leaf},
"warn_no_etr");
8441 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8442 radiation.addRadiationBand(
"SIF_red", 680.f, 700.f);
8443 radiation.setScatteringDepth(
"SIF_red", 1);
8444 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8445 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8449 cam_props.
HFOV = 20.f;
8454 std::string setup_captured;
8457 radiation.addSIFCamera(
"warn_no_etr", {
"SIF_red"},
8461 DOCTEST_CHECK(setup_captured.find(
"electron_transport_ratio") == std::string::npos);
8464 std::string runtime_captured;
8467 radiation.updateGeometry();
8468 const std::vector<std::string> sif_bands = {
"SIF_red"};
8469 radiation.runBand(sif_bands);
8472 DOCTEST_CHECK(runtime_captured.find(
"electron_transport_ratio") != std::string::npos);
8475GPU_TEST_CASE(
"SIF warnings: camera bound to band with scattering depth 0") {
8479 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8481 radiation.addRadiationBand(
"PAR", 400.f, 700.f);
8482 DOCTEST_REQUIRE(radiation.doesBandExist(
"PAR"));
8486 cam_props.
HFOV = 20.f;
8488 std::string captured;
8491 radiation.addRadiationCamera(
"warn_scatter0", {
"PAR"},
8495 DOCTEST_CHECK(captured.find(
"scatteringDepth == 0") != std::string::npos);
8496 DOCTEST_CHECK(captured.find(
"Camera pixels for this band will be zero") != std::string::npos);
8504GPU_TEST_CASE(
"SIF: non-blackbody leaf optics (rho+tau<1) should not trip ε+ρ+τ=1 check") {
8508 sif_stamp_biochem(ctx, {leaf},
"conserv_test");
8518 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8519 radiation.disableMessages();
8520 radiation.addRadiationBand(
"SIF_farred", 730.f, 760.f);
8521 radiation.setScatteringDepth(
"SIF_farred", 2);
8523 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8524 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8528 cam_props.
HFOV = 20.f;
8530 radiation.addSIFCamera(
"cam_cons", {
"SIF_farred"},
make_vec3(0, 0, 1),
make_vec3(0, 0, 0), cam_props, 1);
8532 radiation.updateGeometry();
8533 const std::vector<std::string> sif_bands = {
"SIF_farred"};
8534 DOCTEST_CHECK_NOTHROW(radiation.runBand(sif_bands));
8540GPU_TEST_CASE(
"SIF: disabled emission on a SIF band is soft-overridden with warning") {
8544 sif_stamp_biochem(ctx, {leaf},
"override_test");
8548 RadiationModel radiation = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8550 radiation.addRadiationBand(
"SIF_farred", 730.f, 760.f);
8551 radiation.setScatteringDepth(
"SIF_farred", 1);
8553 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(1.f, 0.f, 1.f));
8554 radiation.setSourceSpectrum(sun,
"solar_spectrum_direct_ASTMG173");
8558 cam_props.
HFOV = 20.f;
8560 radiation.addSIFCamera(
"cam_override", {
"SIF_farred"},
make_vec3(0, 0, 1),
make_vec3(0, 0, 0), cam_props, 1);
8564 radiation.disableEmission(
"SIF_farred");
8566 std::string captured;
8569 radiation.updateGeometry();
8570 const std::vector<std::string> sif_bands = {
"SIF_farred"};
8571 radiation.runBand(sif_bands);
8574 DOCTEST_CHECK(captured.find(
"has emission disabled") != std::string::npos);
8575 DOCTEST_CHECK(captured.find(
"Re-enabling emission") != std::string::npos);
8582GPU_TEST_CASE(
"SIF: excitation_scattering_depth propagates to excitation bands and suppresses per-band warning spam") {
8585 sif_stamp_biochem(ctx, {leaf},
"depth_test");
8591 RadiationModel radiation_a = RadiationModelTestHelper::createWithSharedDevice(&ctx);
8608 for (
float wmin = 400.f; wmin < 750.f; wmin += 50.f) {
8609 const float wmax = std::min(750.f, wmin + 50.f);
8610 std::ostringstream oss;
8611 oss <<
"_SIF_exc_50_" << wmin <<
"_" << wmax;
8612 const std::string label = oss.str();
8617 std::string captured;
8621 const std::vector<std::string> sif_bands = {
"SIF_red"};
8622 radiation_a.
runBand(sif_bands);
8627 const bool exc_warning_absent = (captured.find(
"_SIF_exc_") == std::string::npos) ||
8628 (captured.find(
"scattering iterations are disabled") == std::string::npos);
8629 DOCTEST_CHECK(exc_warning_absent);
8639 sif_stamp_biochem(ctx2, {leaf2},
"depth_test_2");
8642 RadiationModel radiation_b = RadiationModelTestHelper::createWithSharedDevice(&ctx2);
8660 const std::vector<std::string> sif_bands_b = {
"SIF_red"};
8661 DOCTEST_CHECK_NOTHROW(radiation_b.
runBand(sif_bands_b));
8672static helios::vec3 glass_tau_rho_alpha_ref(
float cos_theta,
float n,
float KL) {
8673 cos_theta = std::fmax(1e-4f, std::fmin(1.f, cos_theta));
8674 const float theta = std::acos(std::fmax(-1.f, std::fmin(1.f, cos_theta)));
8675 const float sin_t = std::sin(theta);
8676 const float sin_tr = sin_t / n;
8677 const float cos_tr = std::sqrt(std::fmax(0.f, 1.f - sin_tr * sin_tr));
8678 const float theta_r = std::asin(std::fmax(-1.f, std::fmin(1.f, sin_tr)));
8681 if (theta < 1e-3f) {
8682 const float r0 = ((n - 1.f) / (n + 1.f)) * ((n - 1.f) / (n + 1.f));
8686 const float s_minus = std::sin(theta_r - theta);
8687 const float s_plus = std::sin(theta_r + theta);
8688 const float t_minus = std::tan(theta_r - theta);
8689 const float t_plus = std::tan(theta_r + theta);
8690 r_per = (s_minus * s_minus) / std::fmax(1e-12f, s_plus * s_plus);
8691 r_par = (t_minus * t_minus) / std::fmax(1e-12f, t_plus * t_plus);
8694 const float tau_a = (KL > 0.f) ? std::exp(-KL / std::fmax(1e-4f, cos_tr)) : 1.f;
8695 float tau = 0.f, rho = 0.f;
8696 for (
int pol = 0; pol < 2; pol++) {
8697 const float r = (pol == 0) ? r_per : r_par;
8698 const float denom = std::fmax(1e-6f, 1.f - (r * tau_a) * (r * tau_a));
8699 const float tau_i = tau_a * (1.f - r) * (1.f - r) / denom;
8700 const float rho_i = r * (1.f + tau_a * tau_i);
8701 tau += 0.5f * tau_i;
8702 rho += 0.5f * rho_i;
8704 tau = std::fmax(0.f, std::fmin(1.f, tau));
8705 rho = std::fmax(0.f, std::fmin(1.f, rho));
8711DOCTEST_TEST_CASE(
"Glass cover Fresnel+Bouguer reference math") {
8713 helios::vec3 t0 = glass_tau_rho_alpha_ref(1.0f, 1.5f, 0.0f);
8714 DOCTEST_CHECK(t0.
x == doctest::Approx(0.9231f).epsilon(0.002));
8715 DOCTEST_CHECK(t0.
z == doctest::Approx(0.0f).epsilon(0.001));
8716 DOCTEST_CHECK((t0.
x + t0.
y + t0.
z) == doctest::Approx(1.0f).epsilon(1e-4));
8719 float tau_0 = glass_tau_rho_alpha_ref(std::cos(0.0f), 1.5f, 0.0f).
x;
8720 float tau_60 = glass_tau_rho_alpha_ref(std::cos(60.0f *
float(M_PI) / 180.f), 1.5f, 0.0f).
x;
8721 float tau_75 = glass_tau_rho_alpha_ref(std::cos(75.0f *
float(M_PI) / 180.f), 1.5f, 0.0f).
x;
8722 DOCTEST_CHECK(tau_0 > tau_60);
8723 DOCTEST_CHECK(tau_60 > tau_75);
8724 DOCTEST_CHECK(tau_75 < 0.7f);
8727 helios::vec3 tA = glass_tau_rho_alpha_ref(1.0f, 1.5f, 0.05f);
8728 DOCTEST_CHECK(tA.
x < t0.
x);
8729 DOCTEST_CHECK(tA.
z > 0.01f);
8730 DOCTEST_CHECK((tA.
x + tA.
y + tA.
z) == doctest::Approx(1.0f).epsilon(1e-4));
8738GPU_TEST_CASE(
"RadiationModel Glass Cover Normal-Incidence Transmittance") {
8743 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8744 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8745 context.setPrimitiveData(cover,
"glass_n_SW", 1.5f);
8748 radiation.disableMessages();
8749 radiation.addRadiationBand(
"SW");
8750 radiation.disableEmission(
"SW");
8751 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8752 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8753 radiation.setDirectRayCount(
"SW", 10000);
8754 radiation.setScatteringDepth(
"SW", 1);
8756 radiation.updateGeometry();
8757 radiation.runBand(
"SW");
8759 float flux_receiver;
8760 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_receiver);
8762 const float tau0 = glass_tau_rho_alpha_ref(1.0f, 1.5f, 0.0f).
x;
8763 const float expected = 1000.0f * tau0;
8764 DOCTEST_CHECK(flux_receiver == doctest::Approx(expected).epsilon(0.02));
8771GPU_TEST_CASE(
"RadiationModel Glass Cover Oblique Transmittance") {
8773 const float theta = 30.0f * float(M_PI) / 180.f;
8777 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8778 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8779 context.setPrimitiveData(cover,
"glass_n_SW", 1.5f);
8782 radiation.disableMessages();
8783 radiation.addRadiationBand(
"SW");
8784 radiation.disableEmission(
"SW");
8786 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(std::sin(theta), 0.f, std::cos(theta)));
8787 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8788 radiation.setDirectRayCount(
"SW", 20000);
8789 radiation.setScatteringDepth(
"SW", 1);
8791 radiation.updateGeometry();
8792 radiation.runBand(
"SW");
8794 float flux_receiver;
8795 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_receiver);
8797 const float tau_theta = glass_tau_rho_alpha_ref(std::cos(theta), 1.5f, 0.0f).
x;
8799 const float expected = 1000.0f * std::cos(theta) * tau_theta;
8800 DOCTEST_CHECK(flux_receiver == doctest::Approx(expected).epsilon(0.03));
8805GPU_TEST_CASE(
"RadiationModel Glass Cover Overrides Constant Transmissivity") {
8809 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8810 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8811 context.setPrimitiveData(cover,
"glass_n_SW", 1.5f);
8812 context.setPrimitiveData(cover,
"transmissivity_SW", 0.2f);
8815 radiation.disableMessages();
8816 radiation.addRadiationBand(
"SW");
8817 radiation.disableEmission(
"SW");
8818 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8819 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8820 radiation.setDirectRayCount(
"SW", 10000);
8821 radiation.setScatteringDepth(
"SW", 1);
8823 radiation.updateGeometry();
8824 radiation.runBand(
"SW");
8826 float flux_receiver;
8827 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_receiver);
8830 const float tau0 = glass_tau_rho_alpha_ref(1.0f, 1.5f, 0.0f).
x;
8831 DOCTEST_CHECK(flux_receiver == doctest::Approx(1000.0f * tau0).epsilon(0.02));
8832 DOCTEST_CHECK(flux_receiver > 800.0f);
8839GPU_TEST_CASE(
"RadiationModel Glass Cover Preserves Beam Direction") {
8848 const float theta = 45.0f * float(M_PI) / 180.f;
8849 const float dz = 2.0f;
8850 const float shift = dz * std::tan(theta);
8858 context.setPrimitiveData(ceil_left,
"twosided_flag",
uint(1));
8859 context.setPrimitiveData(ceil_right,
"twosided_flag",
uint(1));
8860 context.setPrimitiveData(ceil_left,
"reflectivity_SW", 0.0f);
8861 context.setPrimitiveData(ceil_right,
"reflectivity_SW", 0.0f);
8862 context.setPrimitiveData(window,
"glass_n_SW", 1.5f);
8867 context.setPrimitiveData(receiver_inpath,
"twosided_flag",
uint(0));
8868 context.setPrimitiveData(receiver_below,
"twosided_flag",
uint(0));
8869 context.setPrimitiveData(receiver_inpath,
"reflectivity_SW", 0.0f);
8870 context.setPrimitiveData(receiver_below,
"reflectivity_SW", 0.0f);
8873 radiation.disableMessages();
8874 radiation.addRadiationBand(
"SW");
8875 radiation.disableEmission(
"SW");
8876 radiation.addCollimatedRadiationSource(
make_vec3(std::sin(theta), 0.f, std::cos(theta)));
8877 radiation.setSourceFlux(0,
"SW", 1000.0f);
8878 radiation.setDirectRayCount(
"SW", 40000);
8879 radiation.setScatteringDepth(
"SW", 1);
8881 radiation.updateGeometry();
8882 radiation.runBand(
"SW");
8884 float flux_inpath, flux_below;
8885 context.getPrimitiveData(receiver_inpath,
"radiation_flux_SW", flux_inpath);
8886 context.getPrimitiveData(receiver_below,
"radiation_flux_SW", flux_below);
8889 const float tau_theta = glass_tau_rho_alpha_ref(std::cos(theta), 1.5f, 0.0f).
x;
8890 const float beam_expected = 1000.0f * std::cos(theta) * tau_theta;
8891 DOCTEST_CHECK(flux_inpath == doctest::Approx(beam_expected).epsilon(0.08));
8894 DOCTEST_CHECK(flux_below < 0.4f * flux_inpath);
8899GPU_TEST_CASE(
"RadiationModel Glass Cover Inactive Without glass_n") {
8903 context.setPrimitiveData(cover,
"twosided_flag",
uint(0));
8904 context.setPrimitiveData(cover,
"reflectivity_SW", 0.0f);
8905 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8906 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8909 radiation.disableMessages();
8910 radiation.addRadiationBand(
"SW");
8911 radiation.disableEmission(
"SW");
8912 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8913 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8914 radiation.setDirectRayCount(
"SW", 10000);
8915 radiation.setScatteringDepth(
"SW", 0);
8917 radiation.updateGeometry();
8918 radiation.runBand(
"SW");
8920 float flux_receiver;
8921 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_receiver);
8922 DOCTEST_CHECK(flux_receiver < 5.0f);
8927GPU_TEST_CASE(
"RadiationModel Glass Cover Double Layer Multiplicative") {
8932 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8933 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8934 context.setPrimitiveData(cover_hi,
"glass_n_SW", 1.5f);
8935 context.setPrimitiveData(cover_lo,
"glass_n_SW", 1.5f);
8938 radiation.disableMessages();
8939 radiation.addRadiationBand(
"SW");
8940 radiation.disableEmission(
"SW");
8941 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8942 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8943 radiation.setDirectRayCount(
"SW", 10000);
8944 radiation.setScatteringDepth(
"SW", 1);
8946 radiation.updateGeometry();
8947 radiation.runBand(
"SW");
8949 float flux_receiver;
8950 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_receiver);
8952 const float tau0 = glass_tau_rho_alpha_ref(1.0f, 1.5f, 0.0f).
x;
8953 const float expected = 1000.0f * tau0 * tau0;
8954 DOCTEST_CHECK(flux_receiver == doctest::Approx(expected).epsilon(0.03));
8957GPU_TEST_CASE(
"RadiationModel Glass Cover Mixed Bands Treated As Opaque") {
8967 context.setPrimitiveData(receiver,
"twosided_flag",
uint(0));
8968 context.setPrimitiveData(receiver,
"reflectivity_SW", 0.0f);
8969 context.setPrimitiveData(receiver,
"reflectivity_LW", 0.0f);
8970 context.setPrimitiveData(cover,
"glass_n_SW", 1.5f);
8971 context.setPrimitiveData(cover,
"reflectivity_LW", 0.0f);
8974 radiation.disableMessages();
8975 radiation.addRadiationBand(
"SW");
8976 radiation.addRadiationBand(
"LW");
8977 radiation.disableEmission(
"SW");
8978 radiation.disableEmission(
"LW");
8979 uint sun = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
8980 radiation.setSourceFlux(sun,
"SW", 1000.0f);
8981 radiation.setSourceFlux(sun,
"LW", 1000.0f);
8982 radiation.setDirectRayCount(
"SW", 10000);
8983 radiation.setDirectRayCount(
"LW", 10000);
8984 radiation.setScatteringDepth(
"SW", 1);
8985 radiation.setScatteringDepth(
"LW", 1);
8987 radiation.updateGeometry();
8988 std::vector<std::string> mixed_bands = {
"SW",
"LW"};
8989 radiation.runBand(mixed_bands);
8991 float flux_SW, flux_LW;
8992 context.getPrimitiveData(receiver,
"radiation_flux_SW", flux_SW);
8993 context.getPrimitiveData(receiver,
"radiation_flux_LW", flux_LW);
8996 DOCTEST_CHECK(flux_SW < 5.0f);
8997 DOCTEST_CHECK(flux_LW < 5.0f);