5#define DOCTEST_CONFIG_IMPLEMENT
7#include "doctest_utils.h"
12 return helios::runDoctestWithValidation(argc, argv);
15DOCTEST_TEST_CASE(
"BufferIndexing Correctness") {
20 DOCTEST_CHECK(indexer(0, 0) == 0);
21 DOCTEST_CHECK(indexer(0, 1) == 1);
22 DOCTEST_CHECK(indexer(0, 4) == 4);
23 DOCTEST_CHECK(indexer(1, 0) == 5);
24 DOCTEST_CHECK(indexer(1, 1) == 6);
25 DOCTEST_CHECK(indexer(9, 4) == 49);
28 for (
size_t i = 0; i < 10; i++) {
29 for (
size_t j = 0; j < 5; j++) {
30 size_t manual = i * 5 + j;
31 size_t indexed = indexer(i, j);
32 DOCTEST_CHECK(manual == indexed);
41 DOCTEST_CHECK(indexer(0, 0, 0) == 0);
42 DOCTEST_CHECK(indexer(0, 0, 1) == 1);
43 DOCTEST_CHECK(indexer(0, 0, 3) == 3);
44 DOCTEST_CHECK(indexer(0, 1, 0) == 4);
45 DOCTEST_CHECK(indexer(0, 2, 0) == 8);
46 DOCTEST_CHECK(indexer(1, 0, 0) == 12);
47 DOCTEST_CHECK(indexer(1, 2, 3) == 23);
50 for (
size_t i = 0; i < 2; i++) {
51 for (
size_t j = 0; j < 3; j++) {
52 for (
size_t k = 0; k < 4; k++) {
53 size_t manual = i * 3 * 4 + j * 4 + k;
54 size_t indexed = indexer(i, j, k);
55 DOCTEST_CHECK(manual == indexed);
65 DOCTEST_CHECK(indexer(0, 0, 0, 0) == 0);
66 DOCTEST_CHECK(indexer(0, 0, 0, 1) == 1);
67 DOCTEST_CHECK(indexer(0, 0, 1, 0) == 2);
68 DOCTEST_CHECK(indexer(0, 1, 0, 0) == 4);
69 DOCTEST_CHECK(indexer(1, 0, 0, 0) == 8);
70 DOCTEST_CHECK(indexer(1, 1, 1, 1) == 15);
73 for (
size_t i = 0; i < 2; i++) {
74 for (
size_t j = 0; j < 2; j++) {
75 for (
size_t k = 0; k < 2; k++) {
76 for (
size_t l = 0; l < 2; l++) {
77 size_t manual = i * 2 * 2 * 2 + j * 2 * 2 + k * 2 + l;
78 size_t indexed = indexer(i, j, k, l);
79 DOCTEST_CHECK(manual == indexed);
88 const size_t Nsources = 5;
89 const size_t Nprimitives = 100;
90 const size_t Nbands = 20;
91 const size_t Ncameras = 3;
96 DOCTEST_CHECK(mat_indexer(0, 0, 0) == 0);
97 DOCTEST_CHECK(mat_indexer(0, 0, 1) == 1);
98 DOCTEST_CHECK(mat_indexer(0, 1, 0) == 20);
99 DOCTEST_CHECK(mat_indexer(1, 0, 0) == 2000);
102 for (
size_t s = 0; s < Nsources; s++) {
103 for (
size_t p = 0; p < Nprimitives; p++) {
104 for (
size_t b = 0; b < Nbands; b++) {
105 size_t manual = s * Nprimitives * Nbands + p * Nbands + b;
106 size_t indexed = mat_indexer(s, p, b);
107 DOCTEST_CHECK(manual == indexed);
115 for (
size_t s = 0; s < 2; s++) {
116 for (
size_t p = 0; p < 10; p++) {
117 for (
size_t b = 0; b < Nbands; b++) {
118 for (
size_t c = 0; c < Ncameras; c++) {
119 size_t manual = s * Nprimitives * Nbands * Ncameras + p * Nbands * Ncameras + b * Ncameras + c;
120 size_t indexed = cam_mat_indexer(s, p, b, c);
121 DOCTEST_CHECK(manual == indexed);
129DOCTEST_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Squares") {
130 float error_threshold = 0.005;
133 uint Ndiffuse_1 = 100000;
134 uint Ndirect_1 = 5000;
137 float sigma = 5.6703744E-8;
139 float shortwave_exact_0 = 0.7f * Qs;
140 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
141 float longwave_exact_0 = 0.f;
142 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
155 float shortwave_rho = 0.3f;
159 radiationmodel_1.disableMessages();
162 radiationmodel_1.addRadiationBand(
"LW");
163 radiationmodel_1.setDirectRayCount(
"LW", Ndiffuse_1);
164 radiationmodel_1.setDiffuseRayCount(
"LW", Ndiffuse_1);
165 radiationmodel_1.setScatteringDepth(
"LW", 0);
168 uint SunSource_1 = radiationmodel_1.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
169 radiationmodel_1.addRadiationBand(
"SW");
170 radiationmodel_1.disableEmission(
"SW");
171 radiationmodel_1.setDirectRayCount(
"SW", Ndirect_1);
172 radiationmodel_1.setDiffuseRayCount(
"SW", Ndirect_1);
173 radiationmodel_1.setScatteringDepth(
"SW", 1);
174 radiationmodel_1.setSourceFlux(SunSource_1,
"SW", Qs);
176 radiationmodel_1.updateGeometry();
178 float longwave_model_0 = 0.f;
179 float longwave_model_1 = 0.f;
180 float shortwave_model_0 = 0.f;
181 float shortwave_model_1 = 0.f;
184 for (
int r = 0; r < Nensemble; r++) {
185 std::vector<std::string> bands{
"LW",
"SW"};
186 radiationmodel_1.runBand(bands);
190 longwave_model_0 +=
R / float(Nensemble);
193 longwave_model_1 +=
R / float(Nensemble);
197 shortwave_model_0 +=
R / float(Nensemble);
200 shortwave_model_1 +=
R / float(Nensemble);
203 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
204 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
205 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
208 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
209 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
211 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
212 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
215DOCTEST_TEST_CASE(
"RadiationModel Black Parallel Rectangles") {
216 float error_threshold = 0.005;
219 uint Ndiffuse_2 = 50000;
231 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));
233 float shortwave_exact_0 = (1.f - F12);
234 float shortwave_exact_1 = (1.f - F12);
245 radiationmodel_2.disableMessages();
248 radiationmodel_2.addRadiationBand(
"SW");
249 radiationmodel_2.disableEmission(
"SW");
250 radiationmodel_2.setDiffuseRayCount(
"SW", Ndiffuse_2);
251 radiationmodel_2.setDiffuseRadiationFlux(
"SW", 1.f);
252 radiationmodel_2.setScatteringDepth(
"SW", 0);
254 radiationmodel_2.updateGeometry();
256 float shortwave_model_0 = 0.f;
257 float shortwave_model_1 = 0.f;
260 for (
int r = 0; r < Nensemble; r++) {
261 radiationmodel_2.runBand(
"SW");
264 shortwave_model_0 +=
R / float(Nensemble);
266 shortwave_model_1 +=
R / float(Nensemble);
269 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
270 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
272 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
273 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
276DOCTEST_TEST_CASE(
"RadiationModel Gray Parallel Rectangles") {
277 float error_threshold = 0.005;
279 float sigma = 5.6703744E-8;
281 uint Ndiffuse_3 = 100000;
284 float longwave_rho = 0.4;
300 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));
302 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);
303 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));
304 longwave_exact_0 = fabsf(longwave_exact_0);
323 radiationmodel_3.disableMessages();
326 radiationmodel_3.addRadiationBand(
"LW");
327 radiationmodel_3.setDirectRayCount(
"LW", Ndiffuse_3);
328 radiationmodel_3.setDiffuseRayCount(
"LW", Ndiffuse_3);
329 radiationmodel_3.setDiffuseRadiationFlux(
"LW", 0.f);
330 radiationmodel_3.setScatteringDepth(
"LW", Nscatter_3);
332 radiationmodel_3.updateGeometry();
334 float longwave_model_0 = 0.f;
335 float longwave_model_1 = 0.f;
338 for (
int r = 0; r < Nensemble; r++) {
339 radiationmodel_3.runBand(
"LW");
342 longwave_model_0 +=
R / float(Nensemble);
344 longwave_model_1 +=
R / float(Nensemble);
347 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
348 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
350 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
351 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
354DOCTEST_TEST_CASE(
"RadiationModel Sphere Source") {
355 float error_threshold = 0.005;
358 uint Ndirect_4 = 10000;
368 float F12 = 0.25f / float(
M_PI) * atanf(sqrtf(1.f / (D1 * D1 + D2 * D2 + D1 * D1 * D2 * D2)));
370 float shortwave_exact_0 = 4.0f * float(
M_PI) * r * r * F12 / (l1 * l2);
376 radiationmodel_4.disableMessages();
378 uint Source_4 = radiationmodel_4.addSphereRadiationSource(
make_vec3(0, 0, d), r);
381 radiationmodel_4.addRadiationBand(
"SW");
382 radiationmodel_4.disableEmission(
"SW");
383 radiationmodel_4.setDirectRayCount(
"SW", Ndirect_4);
384 radiationmodel_4.setSourceFlux(Source_4,
"SW", 1.f);
385 radiationmodel_4.setScatteringDepth(
"SW", 0);
387 radiationmodel_4.updateGeometry();
389 float shortwave_model_0 = 0.f;
392 for (
int i = 0; i < Nensemble; i++) {
393 radiationmodel_4.runBand(
"SW");
396 shortwave_model_0 +=
R / float(Nensemble);
399 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
401 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
404DOCTEST_TEST_CASE(
"RadiationModel 90 Degree Common-Edge Sub-Triangles") {
405 float error_threshold = 0.005;
407 float sigma = 5.6703744E-8;
411 uint Ndiffuse_5 = 100000;
412 uint Ndirect_5 = 5000;
414 float shortwave_exact_0 = 0.7f * Qs;
415 float shortwave_exact_1 = 0.3f * 0.2f * Qs;
416 float longwave_exact_0 = 0.f;
417 float longwave_exact_1 = sigma * powf(300.f, 4) * 0.2f;
432 float shortwave_rho = 0.3f;
443 radiationmodel_5.disableMessages();
446 radiationmodel_5.addRadiationBand(
"LW");
447 radiationmodel_5.setDirectRayCount(
"LW", Ndiffuse_5);
448 radiationmodel_5.setDiffuseRayCount(
"LW", Ndiffuse_5);
449 radiationmodel_5.setScatteringDepth(
"LW", 0);
452 uint SunSource_5 = radiationmodel_5.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
453 radiationmodel_5.addRadiationBand(
"SW");
454 radiationmodel_5.disableEmission(
"SW");
455 radiationmodel_5.setDirectRayCount(
"SW", Ndirect_5);
456 radiationmodel_5.setDiffuseRayCount(
"SW", Ndirect_5);
457 radiationmodel_5.setScatteringDepth(
"SW", 1);
458 radiationmodel_5.setSourceFlux(SunSource_5,
"SW", Qs);
460 radiationmodel_5.updateGeometry();
462 float longwave_model_0 = 0.f;
463 float longwave_model_1 = 0.f;
464 float shortwave_model_0 = 0.f;
465 float shortwave_model_1 = 0.f;
468 for (
int i = 0; i < Nensemble; i++) {
469 std::vector<std::string> bands{
"SW",
"LW"};
470 radiationmodel_5.runBand(bands);
474 longwave_model_0 += 0.5f *
R / float(Nensemble);
476 longwave_model_0 += 0.5f *
R / float(Nensemble);
479 longwave_model_1 += 0.5f *
R / float(Nensemble);
481 longwave_model_1 += 0.5f *
R / float(Nensemble);
485 shortwave_model_0 += 0.5f *
R / float(Nensemble);
487 shortwave_model_0 += 0.5f *
R / float(Nensemble);
490 shortwave_model_1 += 0.5f *
R / float(Nensemble);
492 shortwave_model_1 += 0.5f *
R / float(Nensemble);
495 float shortwave_error_0 = fabsf(shortwave_model_0 - shortwave_exact_0) / fabsf(shortwave_exact_0);
496 float shortwave_error_1 = fabsf(shortwave_model_1 - shortwave_exact_1) / fabsf(shortwave_exact_1);
497 float longwave_error_1 = fabsf(longwave_model_1 - longwave_exact_1) / fabsf(longwave_exact_1);
499 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
500 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
502 DOCTEST_CHECK(longwave_model_0 == longwave_exact_0);
503 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
506DOCTEST_TEST_CASE(
"RadiationModel Parallel Disks Texture Masked Patches") {
507 float error_threshold = 0.005;
509 float sigma = 5.6703744E-8;
511 uint Ndirect_6 = 1000;
512 uint Ndiffuse_6 = 500000;
514 float shortwave_rho = 0.3;
520 float A1 =
M_PI * r1 * r1;
521 float A2 =
M_PI * r2 * r2;
526 float X = 1.f + (1.f + R2 * R2) / (R1 * R1);
527 float F12 = 0.5f * (X - std::sqrt(X * X - 4.f * powf(R2 / R1, 2)));
529 float shortwave_exact_0 = (A1 - A2) / A1 * (1.f - shortwave_rho);
530 float shortwave_exact_1 = (A1 - A2) / A1 * F12 * A1 / A2 * shortwave_rho;
531 float longwave_exact_0 = sigma * powf(300.f, 4) * F12;
532 float longwave_exact_1 = sigma * powf(300.f, 4) * F12 * A1 / A2;
551 radiationmodel_6.disableMessages();
553 uint SunSource_6 = radiationmodel_6.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
556 radiationmodel_6.addRadiationBand(
"SW");
557 radiationmodel_6.disableEmission(
"SW");
558 radiationmodel_6.setDirectRayCount(
"SW", Ndirect_6);
559 radiationmodel_6.setDiffuseRayCount(
"SW", Ndiffuse_6);
560 radiationmodel_6.setSourceFlux(SunSource_6,
"SW", 1.f);
561 radiationmodel_6.setDiffuseRadiationFlux(
"SW", 0);
562 radiationmodel_6.setScatteringDepth(
"SW", 1);
565 radiationmodel_6.addRadiationBand(
"LW");
566 radiationmodel_6.setDiffuseRayCount(
"LW", Ndiffuse_6);
567 radiationmodel_6.setDiffuseRadiationFlux(
"LW", 0.f);
568 radiationmodel_6.setScatteringDepth(
"LW", 0);
570 radiationmodel_6.updateGeometry();
572 float shortwave_model_0 = 0;
573 float shortwave_model_1 = 0;
574 float longwave_model_0 = 0;
575 float longwave_model_1 = 0;
578 for (
uint i = 0; i < Nensemble; i++) {
579 radiationmodel_6.runBand(
"SW");
580 radiationmodel_6.runBand(
"LW");
583 shortwave_model_0 +=
R / float(Nensemble);
586 shortwave_model_1 +=
R / float(Nensemble);
589 longwave_model_0 +=
R / float(Nensemble);
592 longwave_model_1 +=
R / float(Nensemble);
595 float shortwave_error_0 = fabsf(shortwave_exact_0 - shortwave_model_0) / fabsf(shortwave_exact_0);
596 float shortwave_error_1 = fabsf(shortwave_exact_1 - shortwave_model_1) / fabsf(shortwave_exact_1);
597 float longwave_error_0 = fabsf(longwave_exact_0 - longwave_model_0) / fabsf(longwave_exact_0);
598 float longwave_error_1 = fabsf(longwave_exact_1 - longwave_model_1) / fabsf(longwave_exact_1);
600 DOCTEST_CHECK(shortwave_error_0 <= error_threshold);
601 DOCTEST_CHECK(shortwave_error_1 <= error_threshold);
602 DOCTEST_CHECK(longwave_error_0 <= error_threshold);
603 DOCTEST_CHECK(longwave_error_1 <= error_threshold);
606DOCTEST_TEST_CASE(
"RadiationModel Second Law Equilibrium Test") {
607 float error_threshold = 0.005;
608 float sigma = 5.6703744E-8;
610 uint Ndiffuse_7 = 50000;
619 uint objID_7 = context_7.
addBoxObject(
make_vec3(0, 0, 0),
make_vec3(10, 10, 10),
make_int3(5, 5, 5), RGB::black,
true);
630 radiationmodel_7.disableMessages();
633 radiationmodel_7.addRadiationBand(
"LW");
634 radiationmodel_7.setDiffuseRayCount(
"LW", Ndiffuse_7);
635 radiationmodel_7.setDiffuseRadiationFlux(
"LW", 0);
636 radiationmodel_7.setScatteringDepth(
"LW", 5);
638 radiationmodel_7.updateGeometry();
640 radiationmodel_7.runBand(
"LW");
643 float flux_err = 0.f;
644 for (
int p = 0; p < UUIDt.size(); p++) {
647 flux_err += fabsf(
R - eps1_7 * sigma * powf(300, 4)) / (eps1_7 * sigma * powf(300, 4)) /
float(UUIDt.size());
650 DOCTEST_CHECK(flux_err <= error_threshold);
653 for (
uint p: UUIDt) {
655 if (context_7.
randu() < 0.5f) {
664 radiationmodel_7.updateGeometry();
665 radiationmodel_7.runBand(
"LW");
668 for (
int p = 0; p < UUIDt.size(); p++) {
673 flux_err += fabsf(
R - emissivity * sigma * powf(300, 4)) / (emissivity * sigma * powf(300, 4)) /
float(UUIDt.size());
676 DOCTEST_CHECK(flux_err <= error_threshold);
679DOCTEST_TEST_CASE(
"RadiationModel Texture Mapping") {
680 float error_threshold = 0.005;
686 uint source = radiation.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
688 radiation.addRadiationBand(
"SW");
690 radiation.setDirectRayCount(
"SW", 10000);
691 radiation.disableEmission(
"SW");
692 radiation.disableMessages();
694 radiation.setSourceFlux(source,
"SW", 1.f);
706 radiation.updateGeometry();
708 radiation.runBand(
"SW");
714 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
715 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
723 radiation.updateGeometry();
725 radiation.runBand(
"SW");
731 for (
uint p: UUIDs1) {
742 bool test_8b_pass =
true;
743 for (
uint p = 0; p < UUIDs1.size(); p++) {
746 if (fabs(
R - 1.f) > error_threshold) {
747 test_8b_pass =
false;
751 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
752 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
753 DOCTEST_CHECK(test_8b_pass);
760 radiation.updateGeometry();
762 radiation.runBand(
"SW");
767 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
768 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
775 radiation.updateGeometry();
777 radiation.runBand(
"SW");
782 DOCTEST_CHECK(fabs(F0 - (1.f - 0.25f *
M_PI)) <= error_threshold);
783 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
788 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),
791 radiation.updateGeometry();
793 radiation.runBand(
"SW");
798 DOCTEST_CHECK(fabs(F0 - 0.5 - 0.5 * (1.f - 0.25f *
M_PI)) <= error_threshold);
799 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
806 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),
809 radiation.updateGeometry();
811 radiation.runBand(
"SW");
818 DOCTEST_CHECK(fabsf(F0) <= error_threshold);
819 DOCTEST_CHECK(fabsf(F1 - 1.f) <= error_threshold);
820 DOCTEST_CHECK(fabsf(F2 - 1.f) <= error_threshold);
829 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),
831 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),
834 radiation.updateGeometry();
836 radiation.runBand(
"SW");
842 DOCTEST_CHECK(fabsf(F1) <= error_threshold);
843 DOCTEST_CHECK(fabsf(F2) <= error_threshold);
844 DOCTEST_CHECK(fabsf(F0 - 1.f) <= error_threshold);
847DOCTEST_TEST_CASE(
"RadiationModel Homogeneous Canopy of Patches") {
848 float error_threshold = 0.005;
849 float sigma = 5.6703744E-8;
851 uint Ndirect_9 = 1000;
852 uint Ndiffuse_9 = 5000;
858 float w_leaf_9 = 0.075;
860 int Nleaves = (int) lroundf(LAI_9 * D_9 * D_9 / w_leaf_9 / w_leaf_9);
864 std::vector<uint> UUIDs_leaf, UUIDs_inc;
866 for (
int i = 0; i < Nleaves; i++) {
867 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);
871 if (fabsf(position.x) <= 0.5 * D_inc_9 && fabsf(position.y) <= 0.5 * D_inc_9) {
872 UUIDs_inc.push_back(UUID);
880 radiation_9.disableMessages();
882 radiation_9.addRadiationBand(
"direct");
883 radiation_9.disableEmission(
"direct");
884 radiation_9.setDirectRayCount(
"direct", Ndirect_9);
885 float theta_s = 0.2 *
M_PI;
887 radiation_9.setSourceFlux(ID,
"direct", 1.f / cosf(theta_s));
889 radiation_9.addRadiationBand(
"diffuse");
890 radiation_9.disableEmission(
"diffuse");
891 radiation_9.setDiffuseRayCount(
"diffuse", Ndiffuse_9);
892 radiation_9.setDiffuseRadiationFlux(
"diffuse", 1.f);
894 radiation_9.updateGeometry();
896 radiation_9.runBand(
"direct");
897 radiation_9.runBand(
"diffuse");
899 float intercepted_leaf_direct = 0.f;
900 float intercepted_leaf_diffuse = 0.f;
901 for (
uint i: UUIDs_inc) {
905 intercepted_leaf_direct += flux * area / D_inc_9 / D_inc_9;
907 intercepted_leaf_diffuse += flux * area / D_inc_9 / D_inc_9;
910 float intercepted_ground_direct = 0.f;
911 float intercepted_ground_diffuse = 0.f;
912 for (
uint i: UUIDs_ground) {
919 if (fabsf(position.
x) <= 0.5 * D_inc_9 && fabsf(position.
y) <= 0.5 * D_inc_9) {
920 intercepted_ground_direct += flux_dir * area / D_inc_9 / D_inc_9;
921 intercepted_ground_diffuse += flux_diff * area / D_inc_9 / D_inc_9;
925 intercepted_ground_direct = 1.f - intercepted_ground_direct;
926 intercepted_ground_diffuse = 1.f - intercepted_ground_diffuse;
929 float dtheta = 0.5f * float(
M_PI) / float(N);
931 float intercepted_theoretical_diffuse = 0.f;
932 for (
int i = 0; i < N; i++) {
933 float theta = (float(i) + 0.5f) * dtheta;
934 intercepted_theoretical_diffuse += 2.f * (1.f - expf(-0.5f * LAI_9 / cosf(theta))) * cosf(theta) * sinf(theta) * dtheta;
937 float intercepted_theoretical_direct = 1.f - expf(-0.5f * LAI_9 / cosf(theta_s));
939 DOCTEST_CHECK(fabsf(intercepted_ground_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
940 DOCTEST_CHECK(fabsf(intercepted_leaf_direct - intercepted_theoretical_direct) <= 2.f * error_threshold);
941 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
942 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse - intercepted_theoretical_diffuse) <= 2.f * error_threshold);
945DOCTEST_TEST_CASE(
"RadiationModel Gas-filled Furnace") {
946 float error_threshold = 0.005;
947 float sigma = 5.6703744E-8;
949 float Rref_10 = 33000.f;
950 uint Ndiffuse_10 = 10000;
956 float Tw_10 = 1273.f;
957 float Tm_10 = 1773.f;
959 float kappa_10 = 0.1f;
960 float eps_m_10 = 1.f;
961 float w_patch_10 = 0.01;
963 int Npatches_10 = (int) lroundf(2.f * kappa_10 * w_10 * h_10 * d_10 / w_patch_10 / w_patch_10);
967 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);
972 std::vector<uint> UUIDs_patches;
974 for (
int i = 0; i < Npatches_10; i++) {
975 float x = -0.5f * d_10 + 0.5f * w_patch_10 + (d_10 - 2 * w_patch_10) * context_10.
randu();
976 float y = -0.5f * w_10 + 0.5f * w_patch_10 + (w_10 - 2 * w_patch_10) * context_10.
randu();
977 float z = -0.5f * h_10 + 0.5f * w_patch_10 + (h_10 - 2 * w_patch_10) * context_10.
randu();
979 float theta = acosf(1.f - context_10.
randu());
980 float phi = 2.f * float(
M_PI) * context_10.
randu();
986 context_10.
setPrimitiveData(UUIDs_patches,
"reflectivity_LW", 1.f - eps_m_10);
989 radiation_10.disableMessages();
991 radiation_10.addRadiationBand(
"LW");
992 radiation_10.setDiffuseRayCount(
"LW", Ndiffuse_10);
993 radiation_10.setScatteringDepth(
"LW", 0);
995 radiation_10.updateGeometry();
996 radiation_10.runBand(
"LW");
1000 for (
uint i: UUIDs_box) {
1005 R_wall += flux * area;
1007 R_wall = R_wall / A_wall - sigma * powf(Tw_10, 4);
1009 DOCTEST_CHECK(fabsf(R_wall - Rref_10) / Rref_10 <= error_threshold);
1012DOCTEST_TEST_CASE(
"RadiationModel Purely Scattering Medium Between Infinite Plates") {
1013 float error_threshold = 0.005;
1014 float sigma = 5.6703744E-8;
1020 float Tw1_11 = 300.f;
1021 float Tw2_11 = 400.f;
1023 float epsw1_11 = 0.8f;
1024 float epsw2_11 = 0.5f;
1026 float omega_11 = 1.f;
1027 float tauL_11 = 0.1f;
1029 float Psi2_exact = 0.427;
1031 float w_patch_11 = 0.05;
1033 float beta = tauL_11 / h_11;
1035 int Nleaves_11 = (int) lroundf(2.f * beta * W_11 * W_11 * h_11 / w_patch_11 / w_patch_11);
1054 std::vector<uint> UUIDs_patches_11;
1056 for (
int i = 0; i < Nleaves_11; i++) {
1057 float x = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1058 float y = -0.5f * W_11 + 0.5f * w_patch_11 + (W_11 - w_patch_11) * context_11.
randu();
1059 float z = -0.5f * h_11 + 0.5f * w_patch_11 + (h_11 - w_patch_11) * context_11.
randu();
1061 float theta = acosf(1.f - context_11.
randu());
1062 float phi = 2.f * float(
M_PI) * context_11.
randu();
1067 context_11.
setPrimitiveData(UUIDs_patches_11,
"emissivity_LW", 1.f - omega_11);
1068 context_11.
setPrimitiveData(UUIDs_patches_11,
"reflectivity_LW", omega_11);
1071 radiation_11.disableMessages();
1073 radiation_11.addRadiationBand(
"LW");
1074 radiation_11.setDiffuseRayCount(
"LW", 10000);
1075 radiation_11.setScatteringDepth(
"LW", 4);
1077 radiation_11.updateGeometry();
1078 radiation_11.runBand(
"LW");
1081 float A_wall2 = 0.f;
1082 for (
int i = 0; i < UUIDs_1.size(); i++) {
1085 if (fabsf(position.
x) < 0.5 * w_11 && fabsf(position.
y) < 0.5 * w_11) {
1090 R_wall2 += flux * area;
1095 R_wall2 = (R_wall2 / A_wall2 - epsw2_11 * sigma * pow(Tw2_11, 4)) / (sigma * (pow(Tw1_11, 4) - pow(Tw2_11, 4)));
1097 DOCTEST_CHECK(fabsf(R_wall2 - Psi2_exact) <= 10.f * error_threshold);
1100DOCTEST_TEST_CASE(
"RadiationModel Homogeneous Canopy with Periodic Boundaries") {
1101 float error_threshold = 0.005;
1103 uint Ndirect_12 = 1000;
1104 uint Ndiffuse_12 = 5000;
1109 float w_leaf_12 = 0.05;
1111 int Nleaves_12 = round(LAI_12 * D_12 * D_12 / w_leaf_12 / w_leaf_12);
1115 std::vector<uint> UUIDs_leaf_12;
1117 for (
int i = 0; i < Nleaves_12; i++) {
1118 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);
1122 UUIDs_leaf_12.push_back(UUID);
1129 radiation_12.disableMessages();
1131 radiation_12.addRadiationBand(
"direct");
1132 radiation_12.disableEmission(
"direct");
1133 radiation_12.setDirectRayCount(
"direct", Ndirect_12);
1134 float theta_s = 0.2 *
M_PI;
1136 radiation_12.setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1138 radiation_12.addRadiationBand(
"diffuse");
1139 radiation_12.disableEmission(
"diffuse");
1140 radiation_12.setDiffuseRayCount(
"diffuse", Ndiffuse_12);
1141 radiation_12.setDiffuseRadiationFlux(
"diffuse", 1.f);
1143 radiation_12.enforcePeriodicBoundary(
"xy");
1145 radiation_12.updateGeometry();
1147 radiation_12.runBand(
"direct");
1148 radiation_12.runBand(
"diffuse");
1150 float intercepted_leaf_direct_12 = 0.f;
1151 float intercepted_leaf_diffuse_12 = 0.f;
1152 for (
int i = 0; i < UUIDs_leaf_12.size(); i++) {
1155 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_direct", flux);
1156 intercepted_leaf_direct_12 += flux * area / D_12 / D_12;
1157 context_12.
getPrimitiveData(UUIDs_leaf_12.at(i),
"radiation_flux_diffuse", flux);
1158 intercepted_leaf_diffuse_12 += flux * area / D_12 / D_12;
1161 float intercepted_ground_direct_12 = 0.f;
1162 float intercepted_ground_diffuse_12 = 0.f;
1163 for (
int i = 0; i < UUIDs_ground_12.size(); i++) {
1166 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_direct", flux_dir);
1168 context_12.
getPrimitiveData(UUIDs_ground_12.at(i),
"radiation_flux_diffuse", flux_diff);
1170 intercepted_ground_direct_12 += flux_dir * area / D_12 / D_12;
1171 intercepted_ground_diffuse_12 += flux_diff * area / D_12 / D_12;
1174 intercepted_ground_direct_12 = 1.f - intercepted_ground_direct_12;
1175 intercepted_ground_diffuse_12 = 1.f - intercepted_ground_diffuse_12;
1178 float dtheta = 0.5 *
M_PI / float(N);
1180 float intercepted_theoretical_diffuse_12 = 0.f;
1181 for (
int i = 0; i < N; i++) {
1182 float theta = (i + 0.5f) * dtheta;
1183 intercepted_theoretical_diffuse_12 += 2.f * (1.f - exp(-0.5 * LAI_12 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1186 float intercepted_theoretical_direct_12 = 1.f - exp(-0.5 * LAI_12 / cos(theta_s));
1188 DOCTEST_CHECK(fabsf(intercepted_ground_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1189 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_12 - intercepted_theoretical_direct_12) <= 2.f * error_threshold);
1190 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1191 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_12 - intercepted_theoretical_diffuse_12) <= 2.f * error_threshold);
1194DOCTEST_TEST_CASE(
"RadiationModel Texture-masked Tile Objects with Periodic Boundaries") {
1195 float error_threshold = 0.005;
1197 uint Ndirect_13 = 1000;
1198 uint Ndiffuse_13 = 5000;
1203 float w_leaf_13 = 0.05;
1211 for (
uint p = 0; p < UUIDs_ptype.size(); p++) {
1215 int Nleaves_13 = round(LAI_13 * D_13 * D_13 / A_leaf);
1217 std::vector<uint> UUIDs_leaf_13;
1219 for (
int i = 0; i < Nleaves_13; i++) {
1220 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);
1225 context_13.
rotateObject(objID, -rotation.elevation,
"y");
1230 UUIDs_leaf_13.insert(UUIDs_leaf_13.end(), UUIDs.begin(), UUIDs.end());
1239 radiation_13.disableMessages();
1241 radiation_13.addRadiationBand(
"direct");
1242 radiation_13.disableEmission(
"direct");
1243 radiation_13.setDirectRayCount(
"direct", Ndirect_13);
1244 float theta_s = 0.2 *
M_PI;
1246 radiation_13.setSourceFlux(ID,
"direct", 1.f / cos(theta_s));
1248 radiation_13.addRadiationBand(
"diffuse");
1249 radiation_13.disableEmission(
"diffuse");
1250 radiation_13.setDiffuseRayCount(
"diffuse", Ndiffuse_13);
1251 radiation_13.setDiffuseRadiationFlux(
"diffuse", 1.f);
1253 radiation_13.enforcePeriodicBoundary(
"xy");
1255 radiation_13.updateGeometry();
1257 radiation_13.runBand(
"direct");
1258 radiation_13.runBand(
"diffuse");
1260 float intercepted_leaf_direct_13 = 0.f;
1261 float intercepted_leaf_diffuse_13 = 0.f;
1262 for (
int i = 0; i < UUIDs_leaf_13.size(); i++) {
1265 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_direct", flux);
1266 intercepted_leaf_direct_13 += flux * area / D_13 / D_13;
1267 context_13.
getPrimitiveData(UUIDs_leaf_13.at(i),
"radiation_flux_diffuse", flux);
1268 intercepted_leaf_diffuse_13 += flux * area / D_13 / D_13;
1271 float intercepted_ground_direct_13 = 0.f;
1272 float intercepted_ground_diffuse_13 = 0.f;
1273 for (
int i = 0; i < UUIDs_ground_13.size(); i++) {
1276 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_direct", flux_dir);
1278 context_13.
getPrimitiveData(UUIDs_ground_13.at(i),
"radiation_flux_diffuse", flux_diff);
1280 intercepted_ground_direct_13 += flux_dir * area / D_13 / D_13;
1281 intercepted_ground_diffuse_13 += flux_diff * area / D_13 / D_13;
1284 intercepted_ground_direct_13 = 1.f - intercepted_ground_direct_13;
1285 intercepted_ground_diffuse_13 = 1.f - intercepted_ground_diffuse_13;
1288 float dtheta = 0.5 *
M_PI / float(N);
1290 float intercepted_theoretical_diffuse_13 = 0.f;
1291 for (
int i = 0; i < N; i++) {
1292 float theta = (i + 0.5f) * dtheta;
1293 intercepted_theoretical_diffuse_13 += 2.f * (1.f - exp(-0.5 * LAI_13 / cos(theta))) * cos(theta) * sin(theta) * dtheta;
1296 float intercepted_theoretical_direct_13 = 1.f - exp(-0.5 * LAI_13 / cos(theta_s));
1298 DOCTEST_CHECK(fabsf(intercepted_ground_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1299 DOCTEST_CHECK(fabsf(intercepted_leaf_direct_13 - intercepted_theoretical_direct_13) <= 2.f * error_threshold);
1300 DOCTEST_CHECK(fabsf(intercepted_ground_diffuse_13 - intercepted_theoretical_diffuse_13) <= 2.f * error_threshold);
1301 DOCTEST_CHECK(fabsf(intercepted_leaf_diffuse_13 - intercepted_theoretical_diffuse_13) <= 4.f * error_threshold);
1304DOCTEST_TEST_CASE(
"RadiationModel Anisotropic Diffuse Radiation Horizontal Patch") {
1305 float error_threshold = 0.005;
1307 uint Ndiffuse_14 = 50000;
1311 std::vector<float> K_14;
1312 K_14.push_back(0.f);
1313 K_14.push_back(0.25f);
1314 K_14.push_back(1.f);
1316 std::vector<float> thetas_14;
1317 thetas_14.push_back(0.f);
1318 thetas_14.push_back(0.25 *
M_PI);
1324 radiation_14.disableMessages();
1326 radiation_14.addRadiationBand(
"diffuse");
1327 radiation_14.disableEmission(
"diffuse");
1328 radiation_14.setDiffuseRayCount(
"diffuse", Ndiffuse_14);
1329 radiation_14.setDiffuseRadiationFlux(
"diffuse", 1.f);
1331 radiation_14.updateGeometry();
1333 for (
int t = 0; t < thetas_14.size(); t++) {
1334 for (
int k = 0; k < K_14.size(); k++) {
1335 radiation_14.setDiffuseRadiationExtinctionCoeff(
"diffuse", K_14.at(k),
make_SphericalCoord(0.5 *
M_PI - thetas_14.at(t), 0.f));
1336 radiation_14.runBand(
"diffuse");
1341 DOCTEST_CHECK(fabsf(Rdiff - 1.f) <= 2.f * error_threshold);
1346DOCTEST_TEST_CASE(
"RadiationModel Prague Sky Diffuse Radiation Normalization") {
1347 float error_threshold = 0.015;
1349 uint Ndiffuse_prague = 100000;
1356 std::vector<std::vector<float>> prague_test_conditions;
1359 std::vector<float> clear_sky;
1360 clear_sky.push_back(3.0f);
1361 clear_sky.push_back(15.0f);
1362 clear_sky.push_back(1.5f);
1363 prague_test_conditions.push_back(clear_sky);
1366 std::vector<float> turbid_sky;
1367 turbid_sky.push_back(8.0f);
1368 turbid_sky.push_back(10.0f);
1369 turbid_sky.push_back(2.5f);
1370 prague_test_conditions.push_back(turbid_sky);
1373 std::vector<float> overcast_sky;
1374 overcast_sky.push_back(0.5f);
1375 overcast_sky.push_back(30.0f);
1376 overcast_sky.push_back(1.2f);
1377 prague_test_conditions.push_back(overcast_sky);
1383 radiation_prague.disableMessages();
1385 radiation_prague.addRadiationBand(
"diffuse");
1386 radiation_prague.disableEmission(
"diffuse");
1387 radiation_prague.setDiffuseRayCount(
"diffuse", Ndiffuse_prague);
1390 radiation_prague.setDiffuseRadiationFlux(
"diffuse", 1.f);
1393 std::vector<helios::vec2> diffuse_spectrum_prague = {{400, 1.0}, {550, 1.0}, {700, 1.0}};
1394 context_prague.
setGlobalData(
"prague_test_diffuse_spectrum", diffuse_spectrum_prague);
1395 radiation_prague.setDiffuseSpectrum(
"prague_test_diffuse_spectrum");
1397 radiation_prague.updateGeometry();
1402 context_prague.
setGlobalData(
"prague_sky_visibility_km", 50.0f);
1403 context_prague.
setGlobalData(
"prague_sky_ground_albedo", 0.2f);
1405 for (
size_t cond = 0; cond < prague_test_conditions.size(); cond++) {
1406 float circ_str = prague_test_conditions[cond][0];
1407 float circ_width = prague_test_conditions[cond][1];
1408 float horiz_bright = prague_test_conditions[cond][2];
1412 float integral = 0.0f;
1414 for (
int j = 0; j < N; ++j) {
1415 for (
int i = 0; i < N; ++i) {
1416 float theta = 0.5f *
M_PI * (i + 0.5f) / N;
1417 float phi = 2.0f *
M_PI * (j + 0.5f) / N;
1421 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));
1422 float gamma = std::acos(cos_gamma) * 180.0f /
M_PI;
1425 float cos_theta = std::max(0.0f, dir.z);
1426 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
1427 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
1428 float pattern = circ_term * horizon_term;
1430 integral += pattern * std::cos(theta) * std::sin(theta) * (
M_PI / (2.0f * N)) * (2.0f *
M_PI / N);
1433 float normalization = 1.0f / std::max(integral, 1e-10f);
1436 std::vector<float> prague_params;
1437 prague_params.push_back(550.0f);
1438 prague_params.push_back(0.1f);
1439 prague_params.push_back(circ_str);
1440 prague_params.push_back(circ_width);
1441 prague_params.push_back(horiz_bright);
1442 prague_params.push_back(normalization);
1444 context_prague.
setGlobalData(
"prague_sky_spectral_params", prague_params);
1447 radiation_prague.runBand(
"diffuse");
1451 context_prague.
getPrimitiveData(UUID_prague,
"radiation_flux_diffuse", Rdiff_prague);
1455 DOCTEST_CHECK(fabsf(Rdiff_prague - 1.f) <= 2.f * error_threshold);
1459DOCTEST_TEST_CASE(
"RadiationModel Disk Radiation Source Above Circular Element") {
1460 float error_threshold = 0.005;
1462 uint Ndirect_15 = 10000;
1470 radiation_15.disableMessages();
1476 radiation_15.addRadiationBand(
"light");
1477 radiation_15.disableEmission(
"light");
1478 radiation_15.setSourceFlux(ID_15,
"light", 1.f);
1479 radiation_15.setDirectRayCount(
"light", Ndirect_15);
1481 radiation_15.updateGeometry();
1482 radiation_15.runBand(
"light");
1487 float R1_15 = r1_15 / a_15;
1488 float R2_15 = r2_15 / a_15;
1489 float X_15 = 1.f + (1.f + R2_15 * R2_15) / (R1_15 * R1_15);
1490 float F12_exact_15 = 0.5f * (X_15 - sqrtf(X_15 * X_15 - 4.f * powf(R2_15 / R1_15, 2)));
1492 DOCTEST_CHECK(fabs(F12_15 - F12_exact_15 * r1_15 * r1_15 / r2_15 / r2_15) <= 2.f * error_threshold);
1495DOCTEST_TEST_CASE(
"RadiationModel Rectangular Radiation Source Above Patch") {
1496 float error_threshold = 0.01;
1498 uint Ndirect_16 = 50000;
1506 radiation_16.disableMessages();
1512 radiation_16.addRadiationBand(
"light");
1513 radiation_16.disableEmission(
"light");
1514 radiation_16.setSourceFlux(ID_16,
"light", 1.f);
1515 radiation_16.setDirectRayCount(
"light", Ndirect_16);
1517 radiation_16.updateGeometry();
1518 radiation_16.runBand(
"light");
1523 float X_16 = a_16 / c_16;
1524 float Y_16 = b_16 / c_16;
1525 float X2_16 = X_16 * X_16;
1526 float Y2_16 = Y_16 * Y_16;
1528 float F12_exact_16 = 2.0f / float(
M_PI * X_16 * Y_16) *
1529 (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)) -
1530 X_16 * atanf(X_16) - Y_16 * atanf(Y_16));
1532 DOCTEST_CHECK(fabs(F12_16 - F12_exact_16) <= error_threshold);
1535DOCTEST_TEST_CASE(
"RadiationModel ROMC Camera Test Verification") {
1537 float sunzenithd = 30;
1538 float reflectivityleaf = 0.02;
1539 float transmissivityleaf = 0.01;
1540 std::string bandname =
"RED";
1542 float viewazimuth = 0;
1543 float heightscene = 30.f;
1544 float rangescene = 100.f;
1545 std::vector<float> viewangles = {-75, 0, 36};
1546 float sunazimuth = 0;
1548 std::vector<float> referencevalues = {21.f, 71.6f, 87.2f};
1551 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},
1552 {33.5709, -6.31039, 14.5332}, {11.9126, 8.32062, 12.1220}, {32.4756, -26.9023, 16.3684}};
1554 for (
int w = -1; w < 2; w++) {
1556 for (
auto &CSposition: CSpositions) {
1557 vec3 transpos = movew +
make_vec3(CSposition.at(0), CSposition.at(1), CSposition.at(2));
1559 std::vector<uint> iCUUIDsn = cameracalibration.readROMCCanopy();
1562 context_17.
setPrimitiveData(iCUUIDsn,
"reflectivity_spectrum",
"leaf_reflectivity");
1563 context_17.
setPrimitiveData(iCUUIDsn,
"transmissivity_spectrum",
"leaf_transmissivity");
1568 std::vector<helios::vec2> leafspectrarho(2200);
1569 std::vector<helios::vec2> leafspectratau(2200);
1570 std::vector<helios::vec2> sourceintensity(2200);
1571 for (
int i = 0; i < leafspectrarho.size(); i++) {
1572 leafspectrarho.at(i).x = float(301 + i);
1573 leafspectrarho.at(i).y = reflectivityleaf;
1574 leafspectratau.at(i).x = float(301 + i);
1575 leafspectratau.at(i).y = transmissivityleaf;
1576 sourceintensity.at(i).x = float(301 + i);
1577 sourceintensity.at(i).y = 1;
1579 context_17.
setGlobalData(
"leaf_reflectivity", leafspectrarho);
1580 context_17.
setGlobalData(
"leaf_transmissivity", leafspectratau);
1581 context_17.
setGlobalData(
"camera_response", sourceintensity);
1582 context_17.
setGlobalData(
"source_intensity", sourceintensity);
1586 std::vector<std::string> cameralabels;
1588 radiation_17.disableMessages();
1589 for (
float viewangle: viewangles) {
1592 vec3 camera_position = 100000 * camerarotation + camera_lookat;
1597 cameraproperties.
HFOV = 0.02864786f * 2.f;
1600 std::string cameralabel =
"ROMC" + std::to_string(viewangle);
1601 radiation_17.addRadiationCamera(cameralabel, {bandname}, camera_position, camera_lookat, cameraproperties, 60);
1602 cameralabels.push_back(cameralabel);
1605 radiation_17.setSourceSpectrum(0,
"source_intensity");
1606 radiation_17.addRadiationBand(bandname, 500, 502);
1607 radiation_17.setDiffuseRayCount(bandname, 20);
1608 radiation_17.disableEmission(bandname);
1609 radiation_17.setSourceFlux(0, bandname, 5);
1610 radiation_17.setScatteringDepth(bandname, 1);
1611 radiation_17.setDiffuseRadiationFlux(bandname, 0);
1612 radiation_17.setDiffuseRadiationExtinctionCoeff(bandname, 0.f,
make_vec3(-0.5, 0.5, 1));
1614 for (
const auto &cameralabel: cameralabels) {
1615 radiation_17.setCameraSpectralResponse(cameralabel, bandname,
"camera_response");
1617 radiation_17.updateGeometry();
1618 radiation_17.runBand(bandname);
1621 std::vector<float> camera_data;
1622 std::vector<uint> camera_UUID;
1624 for (
int i = 0; i < cameralabels.size(); i++) {
1625 std::string global_data_label =
"camera_" + cameralabels.at(i) +
"_" + bandname;
1626 std::string global_UUID =
"camera_" + cameralabels.at(i) +
"_pixel_UUID";
1627 context_17.
getGlobalData(global_data_label.c_str(), camera_data);
1629 float camera_all_data = 0;
1630 for (
int v = 0; v < camera_data.size(); v++) {
1631 uint iUUID = camera_UUID.at(v) - 1;
1633 camera_all_data += camera_data.at(v);
1636 cameravalue = std::abs(referencevalues.at(i) - camera_all_data);
1637 DOCTEST_CHECK(cameravalue <= 1.5f);
1641DOCTEST_TEST_CASE(
"RadiationModel Spectral Integration and Interpolation Tests") {
1645 radiation.disableMessages();
1649 std::vector<helios::vec2> test_spectrum;
1650 test_spectrum.push_back(
make_vec2(400, 0.1f));
1651 test_spectrum.push_back(
make_vec2(500, 0.5f));
1652 test_spectrum.push_back(
make_vec2(600, 0.3f));
1653 test_spectrum.push_back(
make_vec2(700, 0.2f));
1656 float full_integral = radiation.integrateSpectrum(test_spectrum);
1658 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;
1659 DOCTEST_CHECK(std::abs(full_integral - expected_integral) < 1e-5f);
1663 float partial_integral = radiation.integrateSpectrum(test_spectrum, 450, 650);
1665 DOCTEST_CHECK(std::abs(partial_integral - full_integral) < 1e-5f);
1670 std::vector<helios::vec2> source_spectrum;
1671 source_spectrum.push_back(
make_vec2(400, 1.0f));
1672 source_spectrum.push_back(
make_vec2(500, 2.0f));
1673 source_spectrum.push_back(
make_vec2(600, 1.5f));
1674 source_spectrum.push_back(
make_vec2(700, 0.5f));
1676 std::vector<helios::vec2> surface_spectrum;
1677 surface_spectrum.push_back(
make_vec2(400, 0.2f));
1678 surface_spectrum.push_back(
make_vec2(500, 0.6f));
1679 surface_spectrum.push_back(
make_vec2(600, 0.4f));
1680 surface_spectrum.push_back(
make_vec2(700, 0.1f));
1683 radiation.setSourceSpectrum(source_ID, source_spectrum);
1685 float integrated_product = radiation.integrateSpectrum(source_ID, surface_spectrum, 400, 700);
1688 DOCTEST_CHECK(integrated_product > 0.0f);
1689 DOCTEST_CHECK(integrated_product <= 1.0f);
1694 std::vector<helios::vec2> surface_spectrum;
1695 surface_spectrum.push_back(
make_vec2(400, 0.3f));
1696 surface_spectrum.push_back(
make_vec2(500, 0.7f));
1697 surface_spectrum.push_back(
make_vec2(600, 0.5f));
1698 surface_spectrum.push_back(
make_vec2(700, 0.2f));
1700 std::vector<helios::vec2> camera_response;
1701 camera_response.push_back(
make_vec2(400, 0.1f));
1702 camera_response.push_back(
make_vec2(500, 0.8f));
1703 camera_response.push_back(
make_vec2(600, 0.9f));
1704 camera_response.push_back(
make_vec2(700, 0.3f));
1706 float camera_integrated = radiation.integrateSpectrum(surface_spectrum, camera_response);
1707 DOCTEST_CHECK(camera_integrated >= 0.0f);
1708 DOCTEST_CHECK(camera_integrated <= 1.0f);
1712DOCTEST_TEST_CASE(
"RadiationModel Spectral Radiative Properties Setting and Validation") {
1716 radiation.disableMessages();
1724 std::vector<helios::vec2> leaf_reflectivity;
1725 leaf_reflectivity.push_back(
make_vec2(400, 0.05f));
1726 leaf_reflectivity.push_back(
make_vec2(500, 0.10f));
1727 leaf_reflectivity.push_back(
make_vec2(600, 0.08f));
1728 leaf_reflectivity.push_back(
make_vec2(700, 0.45f));
1729 leaf_reflectivity.push_back(
make_vec2(800, 0.50f));
1731 std::vector<helios::vec2> leaf_transmissivity;
1732 leaf_transmissivity.push_back(
make_vec2(400, 0.02f));
1733 leaf_transmissivity.push_back(
make_vec2(500, 0.05f));
1734 leaf_transmissivity.push_back(
make_vec2(600, 0.04f));
1735 leaf_transmissivity.push_back(
make_vec2(700, 0.40f));
1736 leaf_transmissivity.push_back(
make_vec2(800, 0.45f));
1738 context.
setGlobalData(
"test_leaf_reflectivity", leaf_reflectivity);
1739 context.
setGlobalData(
"test_leaf_transmissivity", leaf_transmissivity);
1742 context.
setPrimitiveData(patch_UUID,
"reflectivity_spectrum",
"test_leaf_reflectivity");
1743 context.
setPrimitiveData(patch_UUID,
"transmissivity_spectrum",
"test_leaf_transmissivity");
1746 std::string refl_spectrum_label;
1747 context.
getPrimitiveData(patch_UUID,
"reflectivity_spectrum", refl_spectrum_label);
1748 DOCTEST_CHECK(refl_spectrum_label ==
"test_leaf_reflectivity");
1750 std::string trans_spectrum_label;
1751 context.
getPrimitiveData(patch_UUID,
"transmissivity_spectrum", trans_spectrum_label);
1752 DOCTEST_CHECK(trans_spectrum_label ==
"test_leaf_transmissivity");
1755 std::vector<helios::vec2> retrieved_refl;
1756 context.
getGlobalData(
"test_leaf_reflectivity", retrieved_refl);
1757 DOCTEST_CHECK(retrieved_refl.size() == leaf_reflectivity.size());
1759 for (
size_t i = 0; i < retrieved_refl.size(); i++) {
1760 DOCTEST_CHECK(std::abs(retrieved_refl[i].x - leaf_reflectivity[i].x) < 1e-5f);
1761 DOCTEST_CHECK(std::abs(retrieved_refl[i].y - leaf_reflectivity[i].y) < 1e-5f);
1767 radiation.addRadiationBand(
"VIS", 400, 700);
1768 radiation.addRadiationBand(
"NIR", 700, 900);
1771 std::vector<helios::vec2> solar_spectrum;
1772 solar_spectrum.push_back(
make_vec2(400, 1.5f));
1773 solar_spectrum.push_back(
make_vec2(500, 2.0f));
1774 solar_spectrum.push_back(
make_vec2(600, 1.8f));
1775 solar_spectrum.push_back(
make_vec2(700, 1.2f));
1776 solar_spectrum.push_back(
make_vec2(800, 1.0f));
1777 solar_spectrum.push_back(
make_vec2(900, 0.8f));
1780 radiation.setSourceSpectrum(sun_source, solar_spectrum);
1782 radiation.setScatteringDepth(
"VIS", 0);
1783 radiation.setScatteringDepth(
"NIR", 0);
1784 radiation.disableEmission(
"VIS");
1785 radiation.disableEmission(
"NIR");
1788 radiation.updateGeometry();
1796 DOCTEST_CHECK(has_refl_spectrum);
1797 DOCTEST_CHECK(has_trans_spectrum);
1802 std::vector<helios::vec2> rgb_red_response;
1803 rgb_red_response.push_back(
make_vec2(400, 0.0f));
1804 rgb_red_response.push_back(
make_vec2(500, 0.1f));
1805 rgb_red_response.push_back(
make_vec2(600, 0.6f));
1806 rgb_red_response.push_back(
make_vec2(700, 0.9f));
1807 rgb_red_response.push_back(
make_vec2(800, 0.1f));
1809 context.
setGlobalData(
"rgb_red_response", rgb_red_response);
1813 camera_properties.
HFOV = 45.0f *
M_PI / 180.0f;
1815 radiation.addRadiationCamera(
"test_camera", {
"VIS"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_properties, 1);
1817 radiation.setCameraSpectralResponse(
"test_camera",
"VIS",
"rgb_red_response");
1821 radiation.updateGeometry();
1825 DOCTEST_CHECK(
true);
1829DOCTEST_TEST_CASE(
"RadiationModel Spectral Edge Cases and Error Handling") {
1833 radiation.disableMessages();
1837 std::vector<helios::vec2> empty_spectrum;
1840 bool caught_error =
false;
1842 float integral = radiation.integrateSpectrum(empty_spectrum);
1844 caught_error =
true;
1846 DOCTEST_CHECK(caught_error);
1851 std::vector<helios::vec2> single_point;
1852 single_point.push_back(
make_vec2(550, 0.5f));
1854 bool caught_error =
false;
1856 float integral = radiation.integrateSpectrum(single_point);
1858 caught_error =
true;
1860 DOCTEST_CHECK(caught_error);
1865 std::vector<helios::vec2> test_spectrum;
1866 test_spectrum.push_back(
make_vec2(400, 0.2f));
1867 test_spectrum.push_back(
make_vec2(600, 0.8f));
1868 test_spectrum.push_back(
make_vec2(800, 0.3f));
1870 bool caught_error =
false;
1873 float integral = radiation.integrateSpectrum(test_spectrum, 700, 500);
1875 caught_error =
true;
1877 DOCTEST_CHECK(caught_error);
1879 caught_error =
false;
1882 float integral = radiation.integrateSpectrum(test_spectrum, 600, 600);
1884 caught_error =
true;
1886 DOCTEST_CHECK(caught_error);
1891 std::vector<helios::vec2> non_monotonic;
1892 non_monotonic.push_back(
make_vec2(500, 0.3f));
1893 non_monotonic.push_back(
make_vec2(400, 0.5f));
1894 non_monotonic.push_back(
make_vec2(600, 0.2f));
1898 bool function_completed =
true;
1900 context.
setGlobalData(
"non_monotonic_spectrum", non_monotonic);
1902 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"non_monotonic_spectrum");
1904 radiation.addRadiationBand(
"test", 400, 700);
1905 radiation.updateGeometry();
1907 function_completed =
false;
1910 DOCTEST_CHECK(function_completed);
1915 std::vector<helios::vec2> limited_spectrum;
1916 limited_spectrum.push_back(
make_vec2(500, 0.3f));
1917 limited_spectrum.push_back(
make_vec2(600, 0.7f));
1920 float extended_integral = radiation.integrateSpectrum(limited_spectrum, 400, 800);
1921 float limited_integral = radiation.integrateSpectrum(limited_spectrum, 500, 600);
1924 DOCTEST_CHECK(extended_integral == 0.0f);
1925 DOCTEST_CHECK(limited_integral > 0.0f);
1929DOCTEST_TEST_CASE(
"RadiationModel Spectral Caching and Performance Validation") {
1933 radiation.disableMessages();
1938 std::vector<helios::vec2> common_spectrum;
1939 common_spectrum.push_back(
make_vec2(400, 0.1f));
1940 common_spectrum.push_back(
make_vec2(500, 0.5f));
1941 common_spectrum.push_back(
make_vec2(600, 0.3f));
1942 common_spectrum.push_back(
make_vec2(700, 0.2f));
1944 context.
setGlobalData(
"common_leaf_spectrum", common_spectrum);
1947 std::vector<uint> patch_UUIDs;
1948 for (
int i = 0; i < 10; i++) {
1950 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"common_leaf_spectrum");
1951 context.
setPrimitiveData(patch,
"transmissivity_spectrum",
"common_leaf_spectrum");
1952 patch_UUIDs.push_back(patch);
1956 radiation.addRadiationBand(
"test_band", 400, 700);
1958 radiation.setSourceSpectrum(source, common_spectrum);
1960 radiation.disableEmission(
"test_band");
1961 radiation.setScatteringDepth(
"test_band", 0);
1964 auto start_time = std::chrono::high_resolution_clock::now();
1965 radiation.updateGeometry();
1966 auto end_time = std::chrono::high_resolution_clock::now();
1968 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1971 DOCTEST_CHECK(duration.count() < 10000000);
1974 for (
uint patch_UUID: patch_UUIDs) {
1977 DOCTEST_CHECK(has_spectrum);
1982DOCTEST_TEST_CASE(
"RadiationModel Spectral Library Integration") {
1986 radiation.disableMessages();
1994 bool library_available =
false;
1996 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"leaf_reflectivity");
1999 library_available =
false;
2002 if (library_available) {
2004 radiation.addRadiationBand(
"test", 400, 800);
2005 radiation.updateGeometry();
2007 std::string spectrum_label;
2009 DOCTEST_CHECK(spectrum_label ==
"leaf_reflectivity");
2012 DOCTEST_CHECK(
true);
2017DOCTEST_TEST_CASE(
"RadiationModel Multi-Spectrum Primitive Assignment") {
2021 radiation.disableMessages();
2024 std::vector<helios::vec2> red_spectrum;
2025 red_spectrum.push_back(
make_vec2(400, 0.1f));
2026 red_spectrum.push_back(
make_vec2(500, 0.1f));
2027 red_spectrum.push_back(
make_vec2(600, 0.8f));
2028 red_spectrum.push_back(
make_vec2(700, 0.9f));
2030 std::vector<helios::vec2> green_spectrum;
2031 green_spectrum.push_back(
make_vec2(400, 0.1f));
2032 green_spectrum.push_back(
make_vec2(500, 0.8f));
2033 green_spectrum.push_back(
make_vec2(600, 0.9f));
2034 green_spectrum.push_back(
make_vec2(700, 0.1f));
2036 std::vector<helios::vec2> blue_spectrum;
2037 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2038 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2039 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2040 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2048 std::vector<uint> red_patches, green_patches, blue_patches;
2051 for (
int i = 0; i < 5; i++) {
2054 red_patches.push_back(patch);
2058 for (
int i = 0; i < 5; i++) {
2060 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2061 green_patches.push_back(patch);
2065 for (
int i = 0; i < 5; i++) {
2068 blue_patches.push_back(patch);
2072 radiation.addRadiationBand(
"R", 600, 700);
2073 radiation.addRadiationBand(
"G", 500, 600);
2074 radiation.addRadiationBand(
"B", 400, 500);
2077 radiation.setDiffuseRayCount(
"R", 10000);
2078 radiation.setDiffuseRayCount(
"G", 10000);
2079 radiation.setDiffuseRayCount(
"B", 10000);
2083 std::vector<helios::vec2> uniform_spectrum;
2084 uniform_spectrum.push_back(
make_vec2(300, 1.0f));
2085 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2086 radiation.setSourceSpectrum(source, uniform_spectrum);
2087 radiation.setSourceFlux(source,
"R", 1000.0f);
2088 radiation.setSourceFlux(source,
"G", 1000.0f);
2089 radiation.setSourceFlux(source,
"B", 1000.0f);
2090 radiation.setDirectRayCount(
"R", 1000);
2091 radiation.setDirectRayCount(
"G", 1000);
2092 radiation.setDirectRayCount(
"B", 1000);
2096 std::vector<helios::vec2> camera_spectrum;
2097 camera_spectrum.push_back(
make_vec2(400, 0.3f));
2098 camera_spectrum.push_back(
make_vec2(500, 0.9f));
2099 camera_spectrum.push_back(
make_vec2(600, 0.8f));
2100 camera_spectrum.push_back(
make_vec2(700, 0.2f));
2104 std::vector<helios::vec2> camera_spectrum2;
2105 camera_spectrum2.push_back(
make_vec2(400, 0.2f));
2106 camera_spectrum2.push_back(
make_vec2(500, 0.3f));
2107 camera_spectrum2.push_back(
make_vec2(600, 0.8f));
2108 camera_spectrum2.push_back(
make_vec2(700, 0.9f));
2109 context.
setGlobalData(
"camera2_spectrum", camera_spectrum2);
2111 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2114 camera_props.
HFOV = 2.0f;
2116 radiation.addRadiationCamera(
"camera1", band_labels,
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2117 radiation.setCameraSpectralResponse(
"camera1",
"R",
"camera1_spectrum");
2118 radiation.setCameraSpectralResponse(
"camera1",
"G",
"camera1_spectrum");
2119 radiation.setCameraSpectralResponse(
"camera1",
"B",
"camera1_spectrum");
2121 radiation.addRadiationCamera(
"camera2", band_labels,
make_vec3(5, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2122 radiation.setCameraSpectralResponse(
"camera2",
"R",
"camera2_spectrum");
2123 radiation.setCameraSpectralResponse(
"camera2",
"G",
"camera2_spectrum");
2124 radiation.setCameraSpectralResponse(
"camera2",
"B",
"camera2_spectrum");
2126 radiation.disableEmission(
"R");
2127 radiation.disableEmission(
"G");
2128 radiation.disableEmission(
"B");
2129 radiation.setScatteringDepth(
"R", 1);
2130 radiation.setScatteringDepth(
"G", 1);
2131 radiation.setScatteringDepth(
"B", 1);
2134 radiation.updateGeometry();
2137 radiation.runBand(
"R");
2138 radiation.runBand(
"G");
2139 radiation.runBand(
"B");
2143 float red_patch_R_flux = 0, red_patch_G_flux = 0, red_patch_B_flux = 0;
2144 for (
uint patch: red_patches) {
2145 float flux_R, flux_G, flux_B;
2149 red_patch_R_flux += flux_R;
2150 red_patch_G_flux += flux_G;
2151 red_patch_B_flux += flux_B;
2153 red_patch_R_flux /= red_patches.size();
2154 red_patch_G_flux /= red_patches.size();
2155 red_patch_B_flux /= red_patches.size();
2158 float green_patch_R_flux = 0, green_patch_G_flux = 0, green_patch_B_flux = 0;
2159 for (
uint patch: green_patches) {
2160 float flux_R, flux_G, flux_B;
2164 green_patch_R_flux += flux_R;
2165 green_patch_G_flux += flux_G;
2166 green_patch_B_flux += flux_B;
2168 green_patch_R_flux /= green_patches.size();
2169 green_patch_G_flux /= green_patches.size();
2170 green_patch_B_flux /= green_patches.size();
2173 float blue_patch_R_flux = 0, blue_patch_G_flux = 0, blue_patch_B_flux = 0;
2174 for (
uint patch: blue_patches) {
2175 float flux_R, flux_G, flux_B;
2179 blue_patch_R_flux += flux_R;
2180 blue_patch_G_flux += flux_G;
2181 blue_patch_B_flux += flux_B;
2183 blue_patch_R_flux /= blue_patches.size();
2184 blue_patch_G_flux /= blue_patches.size();
2185 blue_patch_B_flux /= blue_patches.size();
2189 DOCTEST_CHECK(red_patch_R_flux < red_patch_G_flux);
2190 DOCTEST_CHECK(red_patch_R_flux < red_patch_B_flux);
2193 DOCTEST_CHECK(green_patch_G_flux < green_patch_R_flux);
2194 DOCTEST_CHECK(green_patch_G_flux < green_patch_B_flux);
2197 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_R_flux);
2198 DOCTEST_CHECK(blue_patch_B_flux < blue_patch_G_flux);
2201 for (
uint i = 1; i < red_patches.size(); i++) {
2202 float flux_R_0, flux_R_i;
2205 DOCTEST_CHECK(std::abs(flux_R_0 - flux_R_i) / flux_R_0 < 0.15f);
2209DOCTEST_TEST_CASE(
"RadiationModel Band-Specific Camera Spectral Response") {
2213 radiation.disableMessages();
2217 std::vector<helios::vec2> red_spectrum;
2218 red_spectrum.push_back(
make_vec2(400, 0.1f));
2219 red_spectrum.push_back(
make_vec2(500, 0.1f));
2220 red_spectrum.push_back(
make_vec2(600, 0.8f));
2221 red_spectrum.push_back(
make_vec2(700, 0.9f));
2225 std::vector<helios::vec2> green_spectrum;
2226 green_spectrum.push_back(
make_vec2(400, 0.1f));
2227 green_spectrum.push_back(
make_vec2(500, 0.8f));
2228 green_spectrum.push_back(
make_vec2(600, 0.9f));
2229 green_spectrum.push_back(
make_vec2(700, 0.1f));
2233 std::vector<helios::vec2> blue_spectrum;
2234 blue_spectrum.push_back(
make_vec2(400, 0.9f));
2235 blue_spectrum.push_back(
make_vec2(500, 0.8f));
2236 blue_spectrum.push_back(
make_vec2(600, 0.1f));
2237 blue_spectrum.push_back(
make_vec2(700, 0.1f));
2241 std::vector<uint> red_patches, green_patches, blue_patches, white_patches;
2244 for (
int i = 0; i < 2; i++) {
2247 red_patches.push_back(patch);
2251 for (
int i = 0; i < 2; i++) {
2253 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"green_spectrum");
2254 green_patches.push_back(patch);
2258 for (
int i = 0; i < 2; i++) {
2261 blue_patches.push_back(patch);
2265 for (
int i = 0; i < 2; i++) {
2267 context.
setPrimitiveData(patch,
"reflectivity_spectrum",
"white_spectrum");
2268 white_patches.push_back(patch);
2272 radiation.addRadiationBand(
"R", 600, 700);
2273 radiation.addRadiationBand(
"G", 500, 600);
2274 radiation.addRadiationBand(
"B", 400, 500);
2277 radiation.setDiffuseRayCount(
"R", 10000);
2278 radiation.setDiffuseRayCount(
"G", 10000);
2279 radiation.setDiffuseRayCount(
"B", 10000);
2283 std::vector<helios::vec2> uniform_spectrum;
2284 uniform_spectrum.push_back(
make_vec2(350, 1.0f));
2285 uniform_spectrum.push_back(
make_vec2(800, 1.0f));
2286 radiation.setSourceSpectrum(source, uniform_spectrum);
2287 radiation.setSourceFlux(source,
"R", 1000.0f);
2288 radiation.setSourceFlux(source,
"G", 1000.0f);
2289 radiation.setSourceFlux(source,
"B", 1000.0f);
2293 std::vector<std::string> band_labels = {
"R",
"G",
"B"};
2296 camera_props.
HFOV = 2.0f;
2299 std::vector<helios::vec2> cam1_R_spectrum;
2300 cam1_R_spectrum.push_back(
make_vec2(600, 1.0f));
2301 cam1_R_spectrum.push_back(
make_vec2(700, 1.0f));
2304 std::vector<helios::vec2> cam1_G_spectrum;
2305 cam1_G_spectrum.push_back(
make_vec2(500, 0.05f));
2306 cam1_G_spectrum.push_back(
make_vec2(600, 0.05f));
2309 std::vector<helios::vec2> cam1_B_spectrum;
2310 cam1_B_spectrum.push_back(
make_vec2(400, 0.05f));
2311 cam1_B_spectrum.push_back(
make_vec2(500, 0.05f));
2314 radiation.addRadiationCamera(
"camera1", band_labels,
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2315 radiation.setCameraSpectralResponse(
"camera1",
"R",
"cam1_R_spectrum");
2316 radiation.setCameraSpectralResponse(
"camera1",
"G",
"cam1_G_spectrum");
2317 radiation.setCameraSpectralResponse(
"camera1",
"B",
"cam1_B_spectrum");
2320 std::vector<helios::vec2> cam2_R_spectrum;
2321 cam2_R_spectrum.push_back(
make_vec2(600, 0.05f));
2322 cam2_R_spectrum.push_back(
make_vec2(700, 0.05f));
2325 std::vector<helios::vec2> cam2_G_spectrum;
2326 cam2_G_spectrum.push_back(
make_vec2(500, 0.3f));
2327 cam2_G_spectrum.push_back(
make_vec2(600, 0.3f));
2330 std::vector<helios::vec2> cam2_B_spectrum;
2331 cam2_B_spectrum.push_back(
make_vec2(400, 1.0f));
2332 cam2_B_spectrum.push_back(
make_vec2(500, 1.0f));
2335 radiation.addRadiationCamera(
"camera2", band_labels,
make_vec3(5, 0, 5),
make_vec3(0, 0, 0), camera_props, 100);
2336 radiation.setCameraSpectralResponse(
"camera2",
"R",
"cam2_R_spectrum");
2337 radiation.setCameraSpectralResponse(
"camera2",
"G",
"cam2_G_spectrum");
2338 radiation.setCameraSpectralResponse(
"camera2",
"B",
"cam2_B_spectrum");
2340 radiation.disableEmission(
"R");
2341 radiation.disableEmission(
"G");
2342 radiation.disableEmission(
"B");
2343 radiation.setScatteringDepth(
"R", 1);
2344 radiation.setScatteringDepth(
"G", 1);
2345 radiation.setScatteringDepth(
"B", 1);
2349 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
2352 radiation.runBand(
"R");
2353 radiation.runBand(
"G");
2354 radiation.runBand(
"B");
2357 uint red_patch = red_patches[0];
2358 float red_flux_R, red_flux_G, red_flux_B;
2363 uint green_patch = green_patches[0];
2364 float green_flux_R, green_flux_G, green_flux_B;
2369 uint blue_patch = blue_patches[0];
2370 float blue_flux_R, blue_flux_G, blue_flux_B;
2404DOCTEST_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary") {
2408 radiation.disableMessages();
2411 vec3 position(0, 0, 5);
2412 vec3 lookat(0, 0, 0);
2417 radiation.addRadiationCameraFromLibrary(
"cam1",
"Canon_20D", position, lookat, 1);
2421 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2422 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2425 DOCTEST_CHECK(radiation.doesBandExist(
"red"));
2426 DOCTEST_CHECK(radiation.doesBandExist(
"green"));
2427 DOCTEST_CHECK(radiation.doesBandExist(
"blue"));
2436 DOCTEST_CHECK(context.
getGlobalDataType(
"Canon_20D_green") == HELIOS_TYPE_VEC2);
2440 std::vector<vec2> red_response;
2442 DOCTEST_CHECK(red_response.size() == 33);
2445 DOCTEST_CHECK(red_response.front().x == 400.0f);
2446 DOCTEST_CHECK(red_response.back().x == 720.0f);
2450 radiation.addRadiationCameraFromLibrary(
"cam2",
"iPhone11", position, lookat, 1);
2451 DOCTEST_CHECK(std::find(radiation.getAllCameraLabels().begin(), radiation.getAllCameraLabels().end(),
"cam2") != radiation.getAllCameraLabels().end());
2461 DOCTEST_CHECK_THROWS_AS(radiation.addRadiationCameraFromLibrary(
"cam3",
"InvalidCamera", position, lookat, 1), std::runtime_error);
2465 vec3 cam_pos = radiation.getCameraPosition(
"cam1");
2466 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2467 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2468 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2471 vec3 cam_lookat = radiation.getCameraLookat(
"cam1");
2472 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2473 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2474 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2477 std::vector<std::string> available_cameras = {
"Canon_20D",
"Nikon_D700",
"Nikon_D50",
"iPhone11",
"iPhone12ProMAX"};
2479 for (
const auto &cam_name: available_cameras) {
2480 if (cam_name !=
"Canon_20D" && cam_name !=
"iPhone11") {
2481 std::string label =
"cam" + std::to_string(cam_count++);
2482 radiation.addRadiationCameraFromLibrary(label, cam_name, position, lookat, 1);
2483 DOCTEST_CHECK(std::find(radiation.getAllCameraLabels().begin(), radiation.getAllCameraLabels().end(), label) != radiation.getAllCameraLabels().end());
2488DOCTEST_TEST_CASE(
"RadiationModel - addRadiationCameraFromLibrary with custom band labels") {
2492 radiation.disableMessages();
2494 vec3 position(0, 0, 5);
2495 vec3 lookat(0, 0, 0);
2498 std::vector<std::string> custom_labels = {
"R_custom",
"G_custom",
"B_custom"};
2503 radiation.addRadiationCameraFromLibrary(
"cam_custom",
"Canon_20D", position, lookat, 1, custom_labels);
2507 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2508 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam_custom") != cameras.end());
2511 DOCTEST_CHECK(radiation.doesBandExist(
"R_custom"));
2512 DOCTEST_CHECK(radiation.doesBandExist(
"G_custom"));
2513 DOCTEST_CHECK(radiation.doesBandExist(
"B_custom"));
2516 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"red"));
2517 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"green"));
2518 DOCTEST_CHECK_FALSE(radiation.doesBandExist(
"blue"));
2526 std::vector<vec2> red_response;
2528 DOCTEST_CHECK(red_response.size() == 33);
2533 std::vector<std::string> wrong_size = {
"A",
"B"};
2534 DOCTEST_CHECK_THROWS_AS(radiation.addRadiationCameraFromLibrary(
"cam_fail",
"Canon_20D", position, lookat, 1, wrong_size), std::runtime_error);
2540 radiation2.disableMessages();
2545 radiation2.addRadiationCameraFromLibrary(
"cam_default",
"iPhone11", position, lookat, 1, std::vector<std::string>());
2549 DOCTEST_CHECK(radiation2.doesBandExist(
"red"));
2550 DOCTEST_CHECK(radiation2.doesBandExist(
"green"));
2551 DOCTEST_CHECK(radiation2.doesBandExist(
"blue"));
2557 radiation3.disableMessages();
2559 std::vector<std::string> custom_labels2 = {
"NIR",
"VIS",
"UV"};
2564 radiation3.addRadiationCameraFromLibrary(
"cam_test",
"Nikon_D700", position, lookat, 1, custom_labels2);
2568 DOCTEST_CHECK(radiation3.doesBandExist(
"NIR"));
2569 DOCTEST_CHECK(radiation3.doesBandExist(
"VIS"));
2570 DOCTEST_CHECK(radiation3.doesBandExist(
"UV"));
2578DOCTEST_TEST_CASE(
"RadiationModel - updateCameraParameters") {
2582 radiation.disableMessages();
2585 radiation.addRadiationBand(
"red");
2586 radiation.addRadiationBand(
"green");
2587 radiation.addRadiationBand(
"blue");
2590 vec3 position(0, 0, 5);
2591 vec3 lookat(0, 0, 0);
2594 initial_props.
HFOV = 45.0f;
2598 initial_props.
model =
"TestCamera";
2600 std::vector<std::string> bands = {
"red",
"green",
"blue"};
2601 radiation.addRadiationCamera(
"cam1", bands, position, lookat, initial_props, 1);
2604 std::vector<std::string> cameras = radiation.getAllCameraLabels();
2605 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2610 updated_props.
HFOV = 60.0f;
2614 updated_props.
model =
"UpdatedCamera";
2617 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", updated_props));
2620 vec3 cam_pos = radiation.getCameraPosition(
"cam1");
2621 DOCTEST_CHECK(cam_pos.
x == doctest::Approx(position.
x).epsilon(0.001));
2622 DOCTEST_CHECK(cam_pos.
y == doctest::Approx(position.
y).epsilon(0.001));
2623 DOCTEST_CHECK(cam_pos.
z == doctest::Approx(position.
z).epsilon(0.001));
2625 vec3 cam_lookat = radiation.getCameraLookat(
"cam1");
2626 DOCTEST_CHECK(cam_lookat.
x == doctest::Approx(lookat.x).epsilon(0.001));
2627 DOCTEST_CHECK(cam_lookat.
y == doctest::Approx(lookat.y).epsilon(0.001));
2628 DOCTEST_CHECK(cam_lookat.
z == doctest::Approx(lookat.z).epsilon(0.001));
2636 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"nonexistent_camera", props), std::runtime_error);
2643 invalid_props.
HFOV = 45.0f;
2645 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2652 invalid_props.
HFOV = 45.0f;
2654 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2661 invalid_props.
HFOV = 0.0f;
2663 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2670 invalid_props.
HFOV = 180.0f;
2672 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2679 invalid_props.
HFOV = 200.0f;
2681 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2688 invalid_props.
HFOV = -10.0f;
2690 DOCTEST_CHECK_THROWS_AS(radiation.updateCameraParameters(
"cam1", invalid_props), std::runtime_error);
2697 edge_props.
HFOV = 0.001f;
2698 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", edge_props));
2705 edge_props.
HFOV = 179.999f;
2706 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", edge_props));
2710 DOCTEST_CHECK(radiation.doesBandExist(
"red"));
2711 DOCTEST_CHECK(radiation.doesBandExist(
"green"));
2712 DOCTEST_CHECK(radiation.doesBandExist(
"blue"));
2718 nonsquare_props.
HFOV = 70.0f;
2719 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", nonsquare_props));
2726 pinhole_props.
HFOV = 45.0f;
2728 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", pinhole_props));
2732 for (
int i = 0; i < 5; i++) {
2735 multi_update_props.
HFOV = 45.0f + i * 5.0f;
2736 DOCTEST_CHECK_NOTHROW(radiation.updateCameraParameters(
"cam1", multi_update_props));
2740 cameras = radiation.getAllCameraLabels();
2741 DOCTEST_CHECK(std::find(cameras.begin(), cameras.end(),
"cam1") != cameras.end());
2744DOCTEST_TEST_CASE(
"RadiationModel - getCameraParameters") {
2748 radiation.disableMessages();
2751 radiation.addRadiationBand(
"red");
2752 radiation.addRadiationBand(
"green");
2753 radiation.addRadiationBand(
"blue");
2756 vec3 position(1, 2, 3);
2757 vec3 lookat(0, 0, 0);
2760 initial_props.
HFOV = 60.0f;
2764 initial_props.
model =
"TestCameraModel";
2766 std::vector<std::string> bands = {
"red",
"green",
"blue"};
2767 radiation.addRadiationCamera(
"test_cam", bands, position, lookat, initial_props, 1);
2770 CameraProperties retrieved_props = radiation.getCameraParameters(
"test_cam");
2773 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(initial_props.
HFOV).epsilon(0.001));
2777 DOCTEST_CHECK(retrieved_props.
model == initial_props.
model);
2781 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
2786 updated_props.
HFOV = 75.0f;
2790 updated_props.
model =
"UpdatedModel";
2792 radiation.updateCameraParameters(
"test_cam", updated_props);
2793 retrieved_props = radiation.getCameraParameters(
"test_cam");
2797 DOCTEST_CHECK(retrieved_props.
HFOV == doctest::Approx(updated_props.
HFOV).epsilon(0.001));
2801 DOCTEST_CHECK(retrieved_props.
model == updated_props.
model);
2805 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
2810 DOCTEST_CHECK_THROWS_AS(radiation.getCameraParameters(
"nonexistent_camera"), std::runtime_error);
2814 CameraProperties roundtrip_props = radiation.getCameraParameters(
"test_cam");
2819 radiation.updateCameraParameters(
"test_cam", roundtrip_props);
2821 DOCTEST_CHECK(captured.empty());
2824 CameraProperties roundtrip_props2 = radiation.getCameraParameters(
"test_cam");
2828 DOCTEST_CHECK(roundtrip_props.
HFOV == doctest::Approx(roundtrip_props2.
HFOV).epsilon(0.001));
2832 DOCTEST_CHECK(roundtrip_props.
model == roundtrip_props2.
model);
2838 nonsquare_props.
HFOV = 90.0f;
2839 radiation.updateCameraParameters(
"test_cam", nonsquare_props);
2840 retrieved_props = radiation.getCameraParameters(
"test_cam");
2842 expected_aspect = 1280.0f / 720.0f;
2843 DOCTEST_CHECK(retrieved_props.
FOV_aspect_ratio == doctest::Approx(expected_aspect).epsilon(0.001));
2848 pinhole_props.
HFOV = 45.0f;
2851 radiation.updateCameraParameters(
"test_cam", pinhole_props);
2852 retrieved_props = radiation.getCameraParameters(
"test_cam");
2854 DOCTEST_CHECK(retrieved_props.
lens_diameter == doctest::Approx(0.0f).epsilon(0.001));
2859 cam2_props.
HFOV = 50.0f;
2860 cam2_props.
model =
"Camera2Model";
2861 radiation.addRadiationCamera(
"test_cam2", bands, position, lookat, cam2_props, 1);
2865 cam3_props.
HFOV = 70.0f;
2866 cam3_props.
model =
"Camera3Model";
2867 radiation.addRadiationCamera(
"test_cam3", bands, position, lookat, cam3_props, 1);
2875 DOCTEST_CHECK(check_cam2.
HFOV == doctest::Approx(50.0f).epsilon(0.001));
2876 DOCTEST_CHECK(check_cam2.
model ==
"Camera2Model");
2880 DOCTEST_CHECK(check_cam3.
HFOV == doctest::Approx(70.0f).epsilon(0.001));
2881 DOCTEST_CHECK(check_cam3.
model ==
"Camera3Model");
2884DOCTEST_TEST_CASE(
"CameraCalibration Basic Functionality") {
2889 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
2890 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
2893 std::vector<uint> all_colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
2894 DOCTEST_CHECK(all_colorboard_UUIDs.size() == 24);
2897 std::vector<uint> all_UUIDs = context.
getAllUUIDs();
2898 DOCTEST_CHECK(all_UUIDs.size() >= 24);
2901 int patches_with_reflectivity = 0;
2902 for (
uint UUID: calibrite_UUIDs) {
2904 patches_with_reflectivity++;
2907 DOCTEST_CHECK(patches_with_reflectivity == 24);
2911 std::vector<uint> spyder_UUIDs = calibration2.addSpyderCHECKRColorboard(
make_vec3(0.5, 0.5, 0.001), 0.05);
2912 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
2915 patches_with_reflectivity = 0;
2916 for (
uint UUID: spyder_UUIDs) {
2918 patches_with_reflectivity++;
2921 DOCTEST_CHECK(patches_with_reflectivity == 24);
2924 std::vector<helios::vec2> test_spectrum;
2925 test_spectrum.push_back(
make_vec2(400.0f, 0.1f));
2926 test_spectrum.push_back(
make_vec2(500.0f, 0.5f));
2927 test_spectrum.push_back(
make_vec2(600.0f, 0.8f));
2928 test_spectrum.push_back(
make_vec2(700.0f, 0.3f));
2931 bool write_success = calibration.writeSpectralXMLfile(
"test_spectrum.xml",
"Test spectrum",
"test_label", &test_spectrum);
2932 DOCTEST_CHECK(write_success ==
true);
2935 std::remove(
"test_spectrum.xml");
2938DOCTEST_TEST_CASE(
"CameraCalibration DGK Integration") {
2949 std::vector<uint> colorboard_UUIDs = calibration.getAllColorBoardUUIDs();
2951 DOCTEST_CHECK(colorboard_UUIDs.size() == 0);
2954 std::vector<uint> test_patches;
2955 for (
int i = 0; i < 18; i++) {
2957 test_patches.push_back(patch);
2963 std::vector<uint> all_UUIDs = context.
getAllUUIDs();
2964 DOCTEST_CHECK(all_UUIDs.size() >= 18);
2967 int dgk_labeled_patches = 0;
2968 for (
uint UUID: test_patches) {
2970 dgk_labeled_patches++;
2973 DOCTEST_CHECK(dgk_labeled_patches == 18);
2979DOCTEST_TEST_CASE(
"CameraCalibration Multiple Colorboards") {
2984 std::vector<uint> dgk_UUIDs = calibration.addDGKColorboard(
make_vec3(0, 0, 0.001), 0.05);
2985 DOCTEST_CHECK(dgk_UUIDs.size() == 18);
2987 std::vector<uint> calibrite_UUIDs = calibration.addCalibriteColorboard(
make_vec3(0.5, 0, 0.001), 0.05);
2988 DOCTEST_CHECK(calibrite_UUIDs.size() == 24);
2990 std::vector<uint> spyder_UUIDs = calibration.addSpyderCHECKRColorboard(
make_vec3(1.0, 0, 0.001), 0.05);
2991 DOCTEST_CHECK(spyder_UUIDs.size() == 24);
2994 std::vector<uint> all_UUIDs = calibration.getAllColorBoardUUIDs();
2995 DOCTEST_CHECK(all_UUIDs.size() == 66);
2998 std::vector<std::string> detected_types = calibration.detectColorBoardTypes();
2999 DOCTEST_CHECK(detected_types.size() == 3);
3000 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"DGK") != detected_types.end());
3001 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"Calibrite") != detected_types.end());
3002 DOCTEST_CHECK(std::find(detected_types.begin(), detected_types.end(),
"SpyderCHECKR") != detected_types.end());
3006 std::vector<uint> dgk_UUIDs_2;
3009 dgk_UUIDs_2 = calibration.addDGKColorboard(
make_vec3(0, 0.5, 0.001), 0.05);
3011 DOCTEST_CHECK(dgk_UUIDs_2.size() == 18);
3014 std::vector<uint> all_UUIDs_2 = calibration.getAllColorBoardUUIDs();
3015 DOCTEST_CHECK(all_UUIDs_2.size() == 66);
3018 int dgk_labeled = 0, calibrite_labeled = 0, spyder_labeled = 0;
3019 std::vector<uint> context_UUIDs = context.
getAllUUIDs();
3020 for (
uint UUID: context_UUIDs) {
3025 calibrite_labeled++;
3031 DOCTEST_CHECK(dgk_labeled == 18);
3032 DOCTEST_CHECK(calibrite_labeled == 24);
3033 DOCTEST_CHECK(spyder_labeled == 24);
3036DOCTEST_TEST_CASE(
"RadiationModel CCM Export and Import") {
3039 radiationmodel.disableMessages();
3042 std::vector<std::string> band_labels = {
"red",
"green",
"blue"};
3043 std::string camera_label =
"test_camera";
3049 camera_properties.
HFOV = 45.0f;
3054 radiationmodel.addRadiationCamera(camera_label, band_labels,
make_vec3(0, 0, 1),
make_vec3(0, 0, 0), camera_properties, 1);
3057 size_t pixel_count = resolution.
x * resolution.
y;
3058 std::vector<float> red_data(pixel_count, 0.8f);
3059 std::vector<float> green_data(pixel_count, 0.6f);
3060 std::vector<float> blue_data(pixel_count, 0.4f);
3063 radiationmodel.setCameraPixelData(camera_label,
"red", red_data);
3064 radiationmodel.setCameraPixelData(camera_label,
"green", green_data);
3065 radiationmodel.setCameraPixelData(camera_label,
"blue", blue_data);
3070 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}};
3072 std::string ccm_file_path =
"test_ccm_3x3.xml";
3075 radiationmodel.exportColorCorrectionMatrixXML(ccm_file_path, camera_label, test_matrix,
"/path/to/test_image.jpg",
"DGK", 15.5f);
3078 std::ifstream test_file(ccm_file_path);
3079 DOCTEST_CHECK(test_file.good());
3083 std::string loaded_camera_label;
3084 std::vector<std::vector<float>> loaded_matrix = radiationmodel.loadColorCorrectionMatrixXML(ccm_file_path, loaded_camera_label);
3087 DOCTEST_CHECK(loaded_camera_label == camera_label);
3088 DOCTEST_CHECK(loaded_matrix.size() == 3);
3089 DOCTEST_CHECK(loaded_matrix[0].size() == 3);
3092 for (
size_t i = 0; i < 3; i++) {
3093 for (
size_t j = 0; j < 3; j++) {
3094 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix[i][j]) < 1e-5f);
3099 std::remove(ccm_file_path.c_str());
3105 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}};
3107 std::string ccm_file_path =
"test_ccm_4x3.xml";
3110 radiationmodel.exportColorCorrectionMatrixXML(ccm_file_path, camera_label, test_matrix_4x3,
"/path/to/test_image.jpg",
"Calibrite", 12.3f);
3113 std::string loaded_camera_label;
3114 std::vector<std::vector<float>> loaded_matrix = radiationmodel.loadColorCorrectionMatrixXML(ccm_file_path, loaded_camera_label);
3116 DOCTEST_CHECK(loaded_camera_label == camera_label);
3117 DOCTEST_CHECK(loaded_matrix.size() == 3);
3118 DOCTEST_CHECK(loaded_matrix[0].size() == 4);
3121 for (
size_t i = 0; i < 3; i++) {
3122 for (
size_t j = 0; j < 4; j++) {
3123 DOCTEST_CHECK(std::abs(loaded_matrix[i][j] - test_matrix_4x3[i][j]) < 1e-5f);
3128 std::remove(ccm_file_path.c_str());
3134 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}};
3136 std::string ccm_file_path =
"test_apply_ccm_3x3.xml";
3137 radiationmodel.exportColorCorrectionMatrixXML(ccm_file_path, camera_label, test_matrix,
"/path/to/test.jpg",
"DGK", 10.0f);
3140 std::vector<float> initial_red = radiationmodel.getCameraPixelData(camera_label,
"red");
3141 std::vector<float> initial_green = radiationmodel.getCameraPixelData(camera_label,
"green");
3142 std::vector<float> initial_blue = radiationmodel.getCameraPixelData(camera_label,
"blue");
3145 radiationmodel.applyCameraColorCorrectionMatrix(camera_label,
"red",
"green",
"blue", ccm_file_path);
3148 std::vector<float> corrected_red = radiationmodel.getCameraPixelData(camera_label,
"red");
3149 std::vector<float> corrected_green = radiationmodel.getCameraPixelData(camera_label,
"green");
3150 std::vector<float> corrected_blue = radiationmodel.getCameraPixelData(camera_label,
"blue");
3154 float expected_red = test_matrix[0][0] * initial_red[0] + test_matrix[0][1] * initial_green[0] + test_matrix[0][2] * initial_blue[0];
3155 float expected_green = test_matrix[1][0] * initial_red[0] + test_matrix[1][1] * initial_green[0] + test_matrix[1][2] * initial_blue[0];
3156 float expected_blue = test_matrix[2][0] * initial_red[0] + test_matrix[2][1] * initial_green[0] + test_matrix[2][2] * initial_blue[0];
3158 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3159 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3160 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3163 std::remove(ccm_file_path.c_str());
3169 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}};
3171 std::string ccm_file_path =
"test_apply_ccm_4x3.xml";
3172 radiationmodel.exportColorCorrectionMatrixXML(ccm_file_path, camera_label, test_matrix,
"/path/to/test.jpg",
"SpyderCHECKR", 8.5f);
3175 std::fill(red_data.begin(), red_data.end(), 0.7f);
3176 std::fill(green_data.begin(), green_data.end(), 0.5f);
3177 std::fill(blue_data.begin(), blue_data.end(), 0.3f);
3179 radiationmodel.setCameraPixelData(camera_label,
"red", red_data);
3180 radiationmodel.setCameraPixelData(camera_label,
"green", green_data);
3181 radiationmodel.setCameraPixelData(camera_label,
"blue", blue_data);
3184 radiationmodel.applyCameraColorCorrectionMatrix(camera_label,
"red",
"green",
"blue", ccm_file_path);
3187 std::vector<float> corrected_red = radiationmodel.getCameraPixelData(camera_label,
"red");
3188 std::vector<float> corrected_green = radiationmodel.getCameraPixelData(camera_label,
"green");
3189 std::vector<float> corrected_blue = radiationmodel.getCameraPixelData(camera_label,
"blue");
3192 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];
3193 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];
3194 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];
3196 DOCTEST_CHECK(std::abs(corrected_red[0] - expected_red) < 1e-5f);
3197 DOCTEST_CHECK(std::abs(corrected_green[0] - expected_green) < 1e-5f);
3198 DOCTEST_CHECK(std::abs(corrected_blue[0] - expected_blue) < 1e-5f);
3201 std::remove(ccm_file_path.c_str());
3205DOCTEST_TEST_CASE(
"RadiationModel CCM Error Handling") {
3211 std::string camera_label;
3212 bool exception_thrown =
false;
3214 std::vector<std::vector<float>> matrix = radiationmodel.loadColorCorrectionMatrixXML(
"/nonexistent/path.xml", camera_label);
3215 }
catch (
const std::runtime_error &e) {
3216 exception_thrown =
true;
3217 std::string error_msg(e.what());
3218 DOCTEST_CHECK(error_msg.find(
"Failed to open file for reading") != std::string::npos);
3220 DOCTEST_CHECK(exception_thrown);
3225 std::string malformed_ccm_path =
"malformed_ccm.xml";
3226 std::ofstream malformed_file(malformed_ccm_path);
3227 malformed_file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3228 malformed_file <<
"<helios>\n";
3229 malformed_file <<
" <InvalidTag>\n";
3230 malformed_file <<
" <row>1.0 0.0 0.0</row>\n";
3231 malformed_file <<
" </InvalidTag>\n";
3232 malformed_file <<
"</helios>\n";
3233 malformed_file.close();
3235 std::string camera_label;
3236 bool exception_thrown =
false;
3238 std::vector<std::vector<float>> matrix = radiationmodel.loadColorCorrectionMatrixXML(malformed_ccm_path, camera_label);
3239 }
catch (
const std::runtime_error &e) {
3240 exception_thrown =
true;
3241 std::string error_msg(e.what());
3242 DOCTEST_CHECK(error_msg.find(
"No matrix data found") != std::string::npos);
3244 DOCTEST_CHECK(exception_thrown);
3246 std::remove(malformed_ccm_path.c_str());
3251 std::string ccm_file_path =
"test_error_ccm.xml";
3252 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}};
3254 radiationmodel.exportColorCorrectionMatrixXML(ccm_file_path,
"test_camera", identity_matrix,
"/test.jpg",
"DGK", 5.0f);
3256 bool exception_thrown =
false;
3258 radiationmodel.applyCameraColorCorrectionMatrix(
"nonexistent_camera",
"red",
"green",
"blue", ccm_file_path);
3259 }
catch (
const std::runtime_error &e) {
3260 exception_thrown =
true;
3261 std::string error_msg(e.what());
3262 DOCTEST_CHECK(error_msg.find(
"Camera 'nonexistent_camera' does not exist") != std::string::npos);
3264 DOCTEST_CHECK(exception_thrown);
3266 std::remove(ccm_file_path.c_str());
3270DOCTEST_TEST_CASE(
"RadiationModel Spectrum Interpolation from Primitive Data") {
3274 radiationmodel.disableMessages();
3277 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3278 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3279 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3300 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3301 std::vector<uint> uuids = {uuid0, uuid1, uuid2, uuid3, uuid4};
3302 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3303 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3305 radiationmodel.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3308 radiationmodel.addRadiationBand(
"PAR");
3309 uint source = radiationmodel.addCollimatedRadiationSource();
3310 radiationmodel.setSourceFlux(source,
"PAR", 1000.f);
3311 radiationmodel.updateGeometry();
3312 radiationmodel.runBand(
"PAR");
3315 std::string assigned_spectrum;
3316 context.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
3317 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3319 context.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
3320 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3322 context.
getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
3323 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3325 context.
getPrimitiveData(uuid3,
"reflectivity_spectrum", assigned_spectrum);
3326 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3328 context.
getPrimitiveData(uuid4,
"reflectivity_spectrum", assigned_spectrum);
3329 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3333 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3336 radiationmodel2.disableMessages();
3347 std::vector<uint> uuids = {uuid_a, uuid_b};
3348 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3349 std::vector<float> values = {0.0f, 10.0f};
3351 radiationmodel2.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"leaf_age",
"transmissivity_spectrum");
3353 radiationmodel2.addRadiationBand(
"PAR");
3354 uint source = radiationmodel2.addCollimatedRadiationSource();
3355 radiationmodel2.setSourceFlux(source,
"PAR", 1000.f);
3356 radiationmodel2.updateGeometry();
3357 radiationmodel2.runBand(
"PAR");
3359 std::string assigned_spectrum;
3360 context2.
getPrimitiveData(uuid_a,
"transmissivity_spectrum", assigned_spectrum);
3361 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3363 context2.
getPrimitiveData(uuid_b,
"transmissivity_spectrum", assigned_spectrum);
3364 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3368 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3371 radiationmodel3.disableMessages();
3378 std::vector<uint> uuids = {uuid};
3379 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3380 std::vector<float> values = {0.0f};
3382 bool exception_thrown =
false;
3384 radiationmodel3.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3385 }
catch (
const std::runtime_error &e) {
3386 exception_thrown =
true;
3387 std::string error_msg(e.what());
3388 DOCTEST_CHECK(error_msg.find(
"must have the same length") != std::string::npos);
3390 DOCTEST_CHECK(exception_thrown);
3394 DOCTEST_SUBCASE(
"Error: empty vectors") {
3397 radiationmodel4.disableMessages();
3401 std::vector<uint> uuids = {uuid};
3402 std::vector<std::string> spectra;
3403 std::vector<float> values;
3405 bool exception_thrown =
false;
3407 radiationmodel4.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3408 }
catch (
const std::runtime_error &e) {
3409 exception_thrown =
true;
3410 std::string error_msg(e.what());
3411 DOCTEST_CHECK(error_msg.find(
"cannot be empty") != std::string::npos);
3413 DOCTEST_CHECK(exception_thrown);
3417 DOCTEST_SUBCASE(
"Error: invalid global data label") {
3420 radiationmodel5.disableMessages();
3425 std::vector<uint> uuids = {uuid};
3426 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
3427 std::vector<float> values = {0.0f};
3430 radiationmodel5.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3432 radiationmodel5.addRadiationBand(
"PAR");
3433 uint source = radiationmodel5.addCollimatedRadiationSource();
3434 radiationmodel5.setSourceFlux(source,
"PAR", 1000.f);
3435 radiationmodel5.updateGeometry();
3438 bool exception_thrown =
false;
3440 radiationmodel5.runBand(
"PAR");
3441 }
catch (
const std::runtime_error &e) {
3442 exception_thrown =
true;
3443 std::string error_msg(e.what());
3444 DOCTEST_CHECK(error_msg.find(
"does not exist") != std::string::npos);
3446 DOCTEST_CHECK(exception_thrown);
3450 DOCTEST_SUBCASE(
"Error: wrong global data type") {
3453 radiationmodel6.disableMessages();
3460 std::vector<uint> uuids = {uuid};
3461 std::vector<std::string> spectra = {
"wrong_type"};
3462 std::vector<float> values = {0.0f};
3465 radiationmodel6.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3467 radiationmodel6.addRadiationBand(
"PAR");
3468 uint source = radiationmodel6.addCollimatedRadiationSource();
3469 radiationmodel6.setSourceFlux(source,
"PAR", 1000.f);
3470 radiationmodel6.updateGeometry();
3473 bool exception_thrown =
false;
3475 radiationmodel6.runBand(
"PAR");
3476 }
catch (
const std::runtime_error &e) {
3477 exception_thrown =
true;
3478 std::string error_msg(e.what());
3479 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_VEC2") != std::string::npos);
3481 DOCTEST_CHECK(exception_thrown);
3485 DOCTEST_SUBCASE(
"Invalid UUID is silently skipped") {
3488 radiationmodel7.disableMessages();
3495 std::vector<uint> uuids = {valid_uuid, 99999};
3496 std::vector<std::string> spectra = {
"spec"};
3497 std::vector<float> values = {0.0f};
3500 radiationmodel7.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3502 radiationmodel7.addRadiationBand(
"PAR");
3503 uint source = radiationmodel7.addCollimatedRadiationSource();
3504 radiationmodel7.setSourceFlux(source,
"PAR", 1000.f);
3505 radiationmodel7.updateGeometry();
3508 radiationmodel7.runBand(
"PAR");
3511 std::string assigned_spectrum;
3512 context7.
getPrimitiveData(valid_uuid,
"reflectivity_spectrum", assigned_spectrum);
3513 DOCTEST_CHECK(assigned_spectrum ==
"spec");
3517 DOCTEST_SUBCASE(
"Error: wrong primitive data type for query") {
3520 radiationmodel8.disableMessages();
3527 std::vector<uint> uuids = {uuid};
3528 std::vector<std::string> spectra = {
"spec"};
3529 std::vector<float> values = {0.0f};
3532 radiationmodel8.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3534 radiationmodel8.addRadiationBand(
"PAR");
3535 uint source = radiationmodel8.addCollimatedRadiationSource();
3536 radiationmodel8.setSourceFlux(source,
"PAR", 1000.f);
3537 radiationmodel8.updateGeometry();
3540 bool exception_thrown =
false;
3542 radiationmodel8.runBand(
"PAR");
3543 }
catch (
const std::runtime_error &e) {
3544 exception_thrown =
true;
3545 std::string error_msg(e.what());
3546 DOCTEST_CHECK(error_msg.find(
"HELIOS_TYPE_FLOAT") != std::string::npos);
3548 DOCTEST_CHECK(exception_thrown);
3552 DOCTEST_SUBCASE(
"Primitive without query data is skipped") {
3555 radiationmodel9.disableMessages();
3566 std::vector<uint> uuids = {uuid_with_data, uuid_without_data};
3567 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3568 std::vector<float> values = {0.0f, 10.0f};
3570 radiationmodel9.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"age",
"reflectivity_spectrum");
3572 radiationmodel9.addRadiationBand(
"PAR");
3573 uint source = radiationmodel9.addCollimatedRadiationSource();
3574 radiationmodel9.setSourceFlux(source,
"PAR", 1000.f);
3575 radiationmodel9.updateGeometry();
3576 radiationmodel9.runBand(
"PAR");
3579 std::string assigned_spectrum;
3580 context9.
getPrimitiveData(uuid_with_data,
"reflectivity_spectrum", assigned_spectrum);
3581 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
3586 context9.
getPrimitiveData(uuid_without_data,
"reflectivity_spectrum", assigned_spectrum);
3592DOCTEST_TEST_CASE(
"RadiationModel Spectrum Interpolation from Object Data") {
3596 radiationmodel.disableMessages();
3599 std::vector<vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
3600 std::vector<vec2> spectrum_mature = {{400, 0.3}, {500, 0.35}, {600, 0.4}, {700, 0.45}};
3601 std::vector<vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
3622 DOCTEST_SUBCASE(
"Basic interpolation with 3 spectra") {
3623 std::vector<uint> obj_ids = {obj0, obj1, obj2, obj3, obj4};
3624 std::vector<std::string> spectra = {
"spectrum_age_0",
"spectrum_age_5",
"spectrum_age_10"};
3625 std::vector<float> values = {0.0f, 5.0f, 10.0f};
3627 radiationmodel.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3630 radiationmodel.addRadiationBand(
"PAR");
3631 uint source = radiationmodel.addCollimatedRadiationSource();
3632 radiationmodel.setSourceFlux(source,
"PAR", 1000.f);
3633 radiationmodel.updateGeometry();
3634 radiationmodel.runBand(
"PAR");
3637 std::string assigned_spectrum;
3639 for (
uint uuid: prim_uuids0) {
3640 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3641 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3645 for (
uint uuid: prim_uuids1) {
3646 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3647 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_0");
3651 for (
uint uuid: prim_uuids2) {
3652 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3653 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_5");
3657 for (
uint uuid: prim_uuids3) {
3658 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3659 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3663 for (
uint uuid: prim_uuids4) {
3664 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3665 DOCTEST_CHECK(assigned_spectrum ==
"spectrum_age_10");
3670 DOCTEST_SUBCASE(
"Interpolation with transmissivity_spectrum") {
3673 radiationmodel2.disableMessages();
3684 std::vector<uint> obj_ids = {obj_a, obj_b};
3685 std::vector<std::string> spectra = {
"trans_young",
"trans_old"};
3686 std::vector<float> values = {0.0f, 10.0f};
3688 radiationmodel2.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"leaf_age",
"transmissivity_spectrum");
3690 radiationmodel2.addRadiationBand(
"PAR");
3691 uint source = radiationmodel2.addCollimatedRadiationSource();
3692 radiationmodel2.setSourceFlux(source,
"PAR", 1000.f);
3693 radiationmodel2.updateGeometry();
3694 radiationmodel2.runBand(
"PAR");
3696 std::string assigned_spectrum;
3698 for (
uint uuid: prim_uuids_a) {
3699 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3700 DOCTEST_CHECK(assigned_spectrum ==
"trans_young");
3704 for (
uint uuid: prim_uuids_b) {
3705 context2.
getPrimitiveData(uuid,
"transmissivity_spectrum", assigned_spectrum);
3706 DOCTEST_CHECK(assigned_spectrum ==
"trans_old");
3711 DOCTEST_SUBCASE(
"Error: mismatched vector lengths") {
3714 radiationmodel3.disableMessages();
3717 std::vector<uint> obj_ids = {obj_test};
3718 std::vector<std::string> spectra = {
"spec1",
"spec2"};
3719 std::vector<float> values = {0.0f};
3721 bool caught_error =
false;
3723 radiationmodel3.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3724 }
catch (
const std::exception &e) {
3725 caught_error =
true;
3727 DOCTEST_CHECK(caught_error);
3731 DOCTEST_SUBCASE(
"Error: empty spectra vector") {
3734 radiationmodel4.disableMessages();
3737 std::vector<uint> obj_ids = {obj_test};
3738 std::vector<std::string> spectra;
3739 std::vector<float> values;
3741 bool caught_error =
false;
3743 radiationmodel4.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3744 }
catch (
const std::exception &e) {
3745 caught_error =
true;
3747 DOCTEST_CHECK(caught_error);
3751 DOCTEST_SUBCASE(
"Error: empty object_IDs vector") {
3754 radiationmodel5.disableMessages();
3756 std::vector<uint> obj_ids;
3757 std::vector<std::string> spectra = {
"spec1"};
3758 std::vector<float> values = {0.0f};
3760 bool caught_error =
false;
3762 radiationmodel5.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3763 }
catch (
const std::exception &e) {
3764 caught_error =
true;
3766 DOCTEST_CHECK(caught_error);
3770 DOCTEST_SUBCASE(
"Error: empty query label") {
3773 radiationmodel6.disableMessages();
3776 std::vector<uint> obj_ids = {obj_test};
3777 std::vector<std::string> spectra = {
"spec1"};
3778 std::vector<float> values = {0.0f};
3780 bool caught_error =
false;
3782 radiationmodel6.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"",
"reflectivity_spectrum");
3783 }
catch (
const std::exception &e) {
3784 caught_error =
true;
3786 DOCTEST_CHECK(caught_error);
3790 DOCTEST_SUBCASE(
"Error: empty target label") {
3793 radiationmodel7.disableMessages();
3796 std::vector<uint> obj_ids = {obj_test};
3797 std::vector<std::string> spectra = {
"spec1"};
3798 std::vector<float> values = {0.0f};
3800 bool caught_error =
false;
3802 radiationmodel7.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"");
3803 }
catch (
const std::exception &e) {
3804 caught_error =
true;
3806 DOCTEST_CHECK(caught_error);
3810 DOCTEST_SUBCASE(
"Graceful skip: object without query data") {
3813 radiationmodel8.disableMessages();
3823 std::vector<uint> obj_ids = {obj_with_data, obj_without_data};
3824 std::vector<std::string> spectra = {
"spec1"};
3825 std::vector<float> values = {5.0f};
3827 radiationmodel8.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3829 radiationmodel8.addRadiationBand(
"PAR");
3830 uint source = radiationmodel8.addCollimatedRadiationSource();
3831 radiationmodel8.setSourceFlux(source,
"PAR", 1000.f);
3832 radiationmodel8.updateGeometry();
3833 radiationmodel8.runBand(
"PAR");
3835 std::string assigned_spectrum;
3837 for (
uint uuid: prim_uuids_with) {
3838 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3839 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
3844 for (
uint uuid: prim_uuids_without) {
3846 context8.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3852 DOCTEST_SUBCASE(
"Graceful skip: invalid object ID") {
3855 radiationmodel9.disableMessages();
3865 std::vector<uint> obj_ids = {obj_valid, obj_to_delete};
3866 std::vector<std::string> spectra = {
"spec1"};
3867 std::vector<float> values = {5.0f};
3869 radiationmodel9.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3874 radiationmodel9.addRadiationBand(
"PAR");
3875 uint source = radiationmodel9.addCollimatedRadiationSource();
3876 radiationmodel9.setSourceFlux(source,
"PAR", 1000.f);
3877 radiationmodel9.updateGeometry();
3878 radiationmodel9.runBand(
"PAR");
3880 std::string assigned_spectrum;
3882 for (
uint uuid: prim_uuids_valid) {
3883 context9.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
3884 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
3889 DOCTEST_SUBCASE(
"Error: wrong object data type") {
3892 radiationmodel10.disableMessages();
3899 std::vector<uint> obj_ids = {obj_test};
3900 std::vector<std::string> spectra = {
"spec1"};
3901 std::vector<float> values = {5.0f};
3903 radiationmodel10.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3905 radiationmodel10.addRadiationBand(
"PAR");
3906 uint source = radiationmodel10.addCollimatedRadiationSource();
3907 radiationmodel10.setSourceFlux(source,
"PAR", 1000.f);
3908 radiationmodel10.updateGeometry();
3910 bool caught_error =
false;
3912 radiationmodel10.runBand(
"PAR");
3913 }
catch (
const std::exception &e) {
3914 caught_error =
true;
3916 DOCTEST_CHECK(caught_error);
3920 DOCTEST_SUBCASE(
"Error: invalid global data") {
3923 radiationmodel11.disableMessages();
3928 std::vector<uint> obj_ids = {obj_test};
3929 std::vector<std::string> spectra = {
"nonexistent_spectrum"};
3930 std::vector<float> values = {5.0f};
3932 radiationmodel11.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3934 radiationmodel11.addRadiationBand(
"PAR");
3935 uint source = radiationmodel11.addCollimatedRadiationSource();
3936 radiationmodel11.setSourceFlux(source,
"PAR", 1000.f);
3937 radiationmodel11.updateGeometry();
3939 bool caught_error =
false;
3941 radiationmodel11.runBand(
"PAR");
3942 }
catch (
const std::exception &e) {
3943 caught_error =
true;
3945 DOCTEST_CHECK(caught_error);
3949 DOCTEST_SUBCASE(
"Error: wrong global data type") {
3952 radiationmodel12.disableMessages();
3959 std::vector<uint> obj_ids = {obj_test};
3960 std::vector<std::string> spectra = {
"wrong_type"};
3961 std::vector<float> values = {5.0f};
3963 radiationmodel12.interpolateSpectrumFromObjectData(obj_ids, spectra, values,
"age",
"reflectivity_spectrum");
3965 radiationmodel12.addRadiationBand(
"PAR");
3966 uint source = radiationmodel12.addCollimatedRadiationSource();
3967 radiationmodel12.setSourceFlux(source,
"PAR", 1000.f);
3968 radiationmodel12.updateGeometry();
3970 bool caught_error =
false;
3972 radiationmodel12.runBand(
"PAR");
3973 }
catch (
const std::exception &e) {
3974 caught_error =
true;
3976 DOCTEST_CHECK(caught_error);
3980DOCTEST_TEST_CASE(
"RadiationModel Spectrum Interpolation - Duplicate Handling") {
3983 DOCTEST_SUBCASE(
"Primitive: Merge duplicates with matching spectra") {
3986 radiationmodel.disableMessages();
3988 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
3989 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4002 radiationmodel.interpolateSpectrumFromPrimitiveData({uuid0, uuid1}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4005 radiationmodel.interpolateSpectrumFromPrimitiveData({uuid1, uuid2}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4007 radiationmodel.addRadiationBand(
"PAR");
4008 uint source = radiationmodel.addCollimatedRadiationSource();
4009 radiationmodel.setSourceFlux(source,
"PAR", 1000.f);
4010 radiationmodel.updateGeometry();
4011 radiationmodel.runBand(
"PAR");
4014 std::string assigned_spectrum;
4015 context.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4016 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4018 context.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4019 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4021 context.
getPrimitiveData(uuid2,
"reflectivity_spectrum", assigned_spectrum);
4022 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4026 DOCTEST_SUBCASE(
"Primitive: Replace config with different spectra") {
4029 radiationmodel2.disableMessages();
4031 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4032 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4033 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4045 radiationmodel2.interpolateSpectrumFromPrimitiveData({uuid0, uuid1}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4048 radiationmodel2.interpolateSpectrumFromPrimitiveData({uuid0, uuid1}, {
"spec1",
"spec2",
"spec3"}, {0.0f, 10.0f, 20.0f},
"age",
"reflectivity_spectrum");
4050 radiationmodel2.addRadiationBand(
"PAR");
4051 uint source = radiationmodel2.addCollimatedRadiationSource();
4052 radiationmodel2.setSourceFlux(source,
"PAR", 1000.f);
4053 radiationmodel2.updateGeometry();
4054 radiationmodel2.runBand(
"PAR");
4057 std::string assigned_spectrum;
4058 context2.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum);
4059 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4061 context2.
getPrimitiveData(uuid1,
"reflectivity_spectrum", assigned_spectrum);
4062 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4066 DOCTEST_SUBCASE(
"Object: Merge duplicates with matching spectra") {
4069 radiationmodel3.disableMessages();
4071 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4072 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4085 radiationmodel3.interpolateSpectrumFromObjectData({obj0, obj1}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4088 radiationmodel3.interpolateSpectrumFromObjectData({obj1, obj2}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4090 radiationmodel3.addRadiationBand(
"PAR");
4091 uint source = radiationmodel3.addCollimatedRadiationSource();
4092 radiationmodel3.setSourceFlux(source,
"PAR", 1000.f);
4093 radiationmodel3.updateGeometry();
4094 radiationmodel3.runBand(
"PAR");
4097 std::string assigned_spectrum;
4099 for (
uint uuid: prim_uuids0) {
4100 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4101 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4105 for (
uint uuid: prim_uuids1) {
4106 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4107 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4111 for (
uint uuid: prim_uuids2) {
4112 context3.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4113 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4118 DOCTEST_SUBCASE(
"Object: Replace config with different spectra") {
4121 radiationmodel4.disableMessages();
4123 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4124 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4125 std::vector<vec2> spectrum3 = {{400, 0.5}, {500, 0.55}};
4137 radiationmodel4.interpolateSpectrumFromObjectData({obj0, obj1}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4140 radiationmodel4.interpolateSpectrumFromObjectData({obj0, obj1}, {
"spec1",
"spec2",
"spec3"}, {0.0f, 10.0f, 20.0f},
"age",
"reflectivity_spectrum");
4142 radiationmodel4.addRadiationBand(
"PAR");
4143 uint source = radiationmodel4.addCollimatedRadiationSource();
4144 radiationmodel4.setSourceFlux(source,
"PAR", 1000.f);
4145 radiationmodel4.updateGeometry();
4146 radiationmodel4.runBand(
"PAR");
4149 std::string assigned_spectrum;
4151 for (
uint uuid: prim_uuids0) {
4152 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4153 DOCTEST_CHECK(assigned_spectrum ==
"spec1");
4157 for (
uint uuid: prim_uuids1) {
4158 context4.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
4159 DOCTEST_CHECK(assigned_spectrum ==
"spec2");
4164 DOCTEST_SUBCASE(
"Primitive: Separate configs for different labels") {
4167 radiationmodel5.disableMessages();
4169 std::vector<vec2> spectrum1 = {{400, 0.1}, {500, 0.15}};
4170 std::vector<vec2> spectrum2 = {{400, 0.3}, {500, 0.35}};
4180 radiationmodel5.interpolateSpectrumFromPrimitiveData({uuid0}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"age",
"reflectivity_spectrum");
4181 radiationmodel5.interpolateSpectrumFromPrimitiveData({uuid0}, {
"spec1",
"spec2"}, {0.0f, 10.0f},
"maturity",
"transmissivity_spectrum");
4183 radiationmodel5.addRadiationBand(
"PAR");
4184 uint source = radiationmodel5.addCollimatedRadiationSource();
4185 radiationmodel5.setSourceFlux(source,
"PAR", 1000.f);
4186 radiationmodel5.updateGeometry();
4187 radiationmodel5.runBand(
"PAR");
4190 std::string assigned_spectrum_rho;
4191 std::string assigned_spectrum_tau;
4192 context5.
getPrimitiveData(uuid0,
"reflectivity_spectrum", assigned_spectrum_rho);
4193 context5.
getPrimitiveData(uuid0,
"transmissivity_spectrum", assigned_spectrum_tau);
4195 DOCTEST_CHECK(assigned_spectrum_rho ==
"spec1");
4196 DOCTEST_CHECK(assigned_spectrum_tau ==
"spec2");
4200DOCTEST_TEST_CASE(
"RadiationModel - Camera Metadata Export") {
4209 radiationmodel.disableMessages();
4216 radiationmodel.addRadiationBand(
"RGB_R");
4217 radiationmodel.addRadiationBand(
"RGB_G");
4218 radiationmodel.addRadiationBand(
"RGB_B");
4220 uint source = radiationmodel.addCollimatedRadiationSource();
4221 radiationmodel.setSourceFlux(source,
"RGB_R", 100.f);
4222 radiationmodel.setSourceFlux(source,
"RGB_G", 100.f);
4223 radiationmodel.setSourceFlux(source,
"RGB_B", 100.f);
4225 DOCTEST_SUBCASE(
"Auto-populate metadata with custom sensor size") {
4231 camera_props.
HFOV = 45.0f;
4235 radiationmodel.addRadiationCamera(
"test_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
4240 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"test_camera");
4243 DOCTEST_CHECK(metadata.camera_properties.
width == 512);
4244 DOCTEST_CHECK(metadata.camera_properties.
height == 512);
4245 DOCTEST_CHECK(metadata.camera_properties.
channels == 3);
4248 DOCTEST_CHECK(metadata.camera_properties.
sensor_width == 24.0f);
4249 DOCTEST_CHECK(metadata.camera_properties.
sensor_height == doctest::Approx(24.0f).epsilon(0.01));
4252 float expected_focal_length = 24.0f / (2.0f * tan(45.0f *
M_PI / 180.0f / 2.0f));
4253 DOCTEST_CHECK(metadata.camera_properties.
focal_length == doctest::Approx(expected_focal_length).epsilon(0.01));
4256 float lens_diameter_mm = 0.05f * 1000.0f;
4257 float expected_f_number = expected_focal_length / lens_diameter_mm;
4258 std::ostringstream expected_aperture;
4259 expected_aperture <<
"f/" << std::fixed << std::setprecision(1) << expected_f_number;
4260 DOCTEST_CHECK(metadata.camera_properties.
aperture == expected_aperture.str());
4263 DOCTEST_CHECK(metadata.camera_properties.
model ==
"generic");
4266 DOCTEST_CHECK(metadata.location_properties.
latitude == doctest::Approx(34.0522).epsilon(0.0001));
4267 DOCTEST_CHECK(metadata.location_properties.
longitude == doctest::Approx(-118.2437).epsilon(0.0001));
4270 DOCTEST_CHECK(metadata.acquisition_properties.
date ==
"2025-09-30");
4271 DOCTEST_CHECK(metadata.acquisition_properties.
time ==
"10:30:00");
4272 DOCTEST_CHECK(metadata.acquisition_properties.
UTC_offset == 8.0f);
4273 DOCTEST_CHECK(metadata.acquisition_properties.
camera_height_m == 2.0f);
4278 DOCTEST_CHECK(metadata.acquisition_properties.
camera_angle_deg == doctest::Approx(21.8).epsilon(0.5));
4281 DOCTEST_CHECK(metadata.acquisition_properties.
light_source ==
"sunlight");
4284 DOCTEST_CHECK(metadata.
path ==
"");
4287 DOCTEST_SUBCASE(
"Pinhole camera aperture") {
4292 camera_props.
HFOV = 30.0f;
4296 radiationmodel.addRadiationCamera(
"pinhole_camera", {
"RGB_R"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 1);
4299 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"pinhole_camera");
4302 DOCTEST_CHECK(metadata.camera_properties.
aperture ==
"pinhole");
4305 DOCTEST_SUBCASE(
"Light source detection") {
4308 camera_props.
HFOV = 20.0f;
4313 radiationmodel2.disableMessages();
4315 radiationmodel2.addRadiationBand(
"test");
4316 radiationmodel2.addRadiationCamera(
"camera1", {
"test"},
make_vec3(0, 0, 1),
make_vec3(0, 0, 0), camera_props, 1);
4319 CameraMetadata metadata1 = radiationmodel2.getCameraMetadata(
"camera1");
4320 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"none");
4323 uint source1 = radiationmodel2.addCollimatedRadiationSource();
4324 radiationmodel2.setSourceFlux(source1,
"test", 100.f);
4326 metadata1 = radiationmodel2.getCameraMetadata(
"camera1");
4327 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"sunlight");
4331 radiationmodel2.setSourceFlux(source2,
"test", 50.f);
4333 metadata1 = radiationmodel2.getCameraMetadata(
"camera1");
4334 DOCTEST_CHECK(metadata1.acquisition_properties.
light_source ==
"mixed");
4337 DOCTEST_SUBCASE(
"Set metadata and automatic JSON export") {
4340 camera_props.
HFOV = 35.0f;
4343 radiationmodel.addRadiationCamera(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -3, 1.5),
make_vec3(0, 0, 0), camera_props, 1);
4346 radiationmodel.enableCameraMetadata(
"export_camera");
4349 radiationmodel.updateGeometry();
4350 radiationmodel.runBand(
"RGB_R");
4351 radiationmodel.runBand(
"RGB_G");
4352 radiationmodel.runBand(
"RGB_B");
4355 std::string image_path = radiationmodel.writeCameraImage(
"export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_metadata");
4358 DOCTEST_CHECK(!image_path.empty());
4359 DOCTEST_CHECK(image_path.find(
".jpeg") != std::string::npos);
4362 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4363 std::ifstream json_file(json_path);
4364 DOCTEST_CHECK(json_file.is_open());
4366 if (json_file.is_open()) {
4373 DOCTEST_CHECK(j.contains(
"path"));
4374 DOCTEST_CHECK(j.contains(
"camera_properties"));
4375 DOCTEST_CHECK(j.contains(
"location_properties"));
4376 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
4379 DOCTEST_CHECK(j[
"camera_properties"].contains(
"height"));
4380 DOCTEST_CHECK(j[
"camera_properties"].contains(
"width"));
4381 DOCTEST_CHECK(j[
"camera_properties"].contains(
"channels"));
4382 DOCTEST_CHECK(j[
"camera_properties"].contains(
"focal_length"));
4383 DOCTEST_CHECK(j[
"camera_properties"].contains(
"aperture"));
4384 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_width"));
4385 DOCTEST_CHECK(j[
"camera_properties"].contains(
"sensor_height"));
4386 DOCTEST_CHECK(j[
"camera_properties"].contains(
"model"));
4389 DOCTEST_CHECK(j[
"camera_properties"][
"width"] == 256);
4390 DOCTEST_CHECK(j[
"camera_properties"][
"height"] == 256);
4391 DOCTEST_CHECK(j[
"camera_properties"][
"channels"] == 3);
4392 DOCTEST_CHECK(j[
"camera_properties"][
"model"] ==
"generic");
4395 size_t last_slash = image_path.find_last_of(
"/\\");
4396 std::string expected_filename = (last_slash != std::string::npos) ? image_path.substr(last_slash + 1) : image_path;
4397 DOCTEST_CHECK(j[
"path"] == expected_filename);
4400 std::remove(image_path.c_str());
4401 std::remove(json_path.c_str());
4405 DOCTEST_SUBCASE(
"Manual metadata population") {
4409 radiationmodel.addRadiationCamera(
"manual_camera", {
"RGB_R"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), camera_props, 1);
4413 metadata.camera_properties.
width = 128;
4414 metadata.camera_properties.
height = 128;
4415 metadata.camera_properties.
channels = 1;
4417 metadata.camera_properties.
aperture =
"f/1.8";
4420 metadata.camera_properties.
model =
"Nikon D700";
4422 metadata.location_properties.
latitude = 40.0f;
4423 metadata.location_properties.
longitude = -75.0f;
4425 metadata.acquisition_properties.
date =
"2025-01-01";
4426 metadata.acquisition_properties.
time =
"12:00:00";
4427 metadata.acquisition_properties.
UTC_offset = 5.0f;
4430 metadata.acquisition_properties.
light_source =
"artificial";
4433 radiationmodel.setCameraMetadata(
"manual_camera", metadata);
4437 DOCTEST_CHECK(
true);
4440 DOCTEST_SUBCASE(
"Enable metadata for multiple cameras with vector") {
4443 camera_props.
HFOV = 30.0f;
4446 radiationmodel.addRadiationCamera(
"camera_A", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4447 radiationmodel.addRadiationCamera(
"camera_B", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(2, 0, 1),
make_vec3(0, 0, 0), camera_props, 1);
4448 radiationmodel.addRadiationCamera(
"camera_C", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, 2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4451 std::vector<std::string> camera_labels = {
"camera_A",
"camera_B",
"camera_C"};
4452 radiationmodel.enableCameraMetadata(camera_labels);
4455 radiationmodel.updateGeometry();
4456 radiationmodel.runBand(
"RGB_R");
4457 radiationmodel.runBand(
"RGB_G");
4458 radiationmodel.runBand(
"RGB_B");
4461 std::vector<std::string> image_paths;
4462 std::vector<std::string> json_paths;
4464 for (
const auto &label: camera_labels) {
4465 std::string image_path = radiationmodel.writeCameraImage(label, {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_vector");
4466 DOCTEST_CHECK(!image_path.empty());
4468 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4469 std::ifstream json_file(json_path);
4470 DOCTEST_CHECK(json_file.is_open());
4473 image_paths.push_back(image_path);
4474 json_paths.push_back(json_path);
4478 for (
size_t i = 0; i < image_paths.size(); i++) {
4479 std::remove(image_paths[i].c_str());
4480 std::remove(json_paths[i].c_str());
4484 DOCTEST_SUBCASE(
"applyCameraImageCorrections stores parameters in metadata") {
4490 camera_props.
HFOV = 45.0f;
4493 radiationmodel.addRadiationCamera(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -2, 1),
make_vec3(0, 0, 0), camera_props, 1);
4496 radiationmodel.enableCameraMetadata(
"corrections_camera");
4499 radiationmodel.updateGeometry();
4500 radiationmodel.runBand(
"RGB_R");
4501 radiationmodel.runBand(
"RGB_G");
4502 radiationmodel.runBand(
"RGB_B");
4505 float saturation = 1.5f;
4506 float brightness = 1.2f;
4507 float contrast = 1.1f;
4508 radiationmodel.applyCameraImageCorrections(
"corrections_camera",
"RGB_R",
"RGB_G",
"RGB_B", saturation, brightness, contrast);
4511 std::string image_path = radiationmodel.writeCameraImage(
"corrections_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_corrections");
4513 DOCTEST_CHECK(!image_path.empty());
4516 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4517 std::ifstream json_file(json_path);
4518 DOCTEST_CHECK(json_file.is_open());
4520 if (json_file.is_open()) {
4526 DOCTEST_CHECK(j.contains(
"acquisition_properties"));
4527 DOCTEST_CHECK(j.contains(
"image_processing"));
4530 auto &img_proc = j[
"image_processing"];
4531 DOCTEST_CHECK(img_proc.contains(
"saturation_adjustment"));
4532 DOCTEST_CHECK(img_proc.contains(
"brightness_adjustment"));
4533 DOCTEST_CHECK(img_proc.contains(
"contrast_adjustment"));
4534 DOCTEST_CHECK(img_proc.contains(
"color_space"));
4536 DOCTEST_CHECK(img_proc[
"saturation_adjustment"].get<double>() == doctest::Approx(saturation).epsilon(0.01));
4537 DOCTEST_CHECK(img_proc[
"brightness_adjustment"].get<double>() == doctest::Approx(brightness).epsilon(0.01));
4538 DOCTEST_CHECK(img_proc[
"contrast_adjustment"].get<double>() == doctest::Approx(contrast).epsilon(0.01));
4539 DOCTEST_CHECK(img_proc[
"color_space"].get<std::string>() ==
"sRGB");
4542 std::remove(image_path.c_str());
4543 std::remove(json_path.c_str());
4548DOCTEST_TEST_CASE(
"RadiationModel - Camera Metadata Agronomic Properties") {
4557 radiationmodel.disableMessages();
4560 radiationmodel.addRadiationBand(
"RGB_R");
4561 radiationmodel.addRadiationBand(
"RGB_G");
4562 radiationmodel.addRadiationBand(
"RGB_B");
4565 uint source = radiationmodel.addCollimatedRadiationSource();
4566 radiationmodel.setSourceFlux(source,
"RGB_R", 100.f);
4567 radiationmodel.setSourceFlux(source,
"RGB_G", 100.f);
4568 radiationmodel.setSourceFlux(source,
"RGB_B", 100.f);
4571 radiationmodel.setScatteringDepth(
"RGB_R", 1);
4572 radiationmodel.setScatteringDepth(
"RGB_G", 1);
4573 radiationmodel.setScatteringDepth(
"RGB_B", 1);
4575 DOCTEST_SUBCASE(
"Agronomic properties with multiple species and weeds") {
4579 context.
setObjectData(bean_obj_1,
"plant_name", std::string(
"bean"));
4581 context.
setObjectData(bean_obj_1,
"plant_type", std::string(
"crop"));
4584 context.
setObjectData(bean_obj_1,
"phenology_stage", std::string(
"flowering"));
4588 context.
setObjectData(bean_obj_2,
"plant_name", std::string(
"bean"));
4590 context.
setObjectData(bean_obj_2,
"plant_type", std::string(
"crop"));
4593 context.
setObjectData(bean_obj_2,
"phenology_stage", std::string(
"flowering"));
4597 context.
setObjectData(bean_obj_3,
"plant_name", std::string(
"bean"));
4599 context.
setObjectData(bean_obj_3,
"plant_type", std::string(
"crop"));
4602 context.
setObjectData(bean_obj_3,
"phenology_stage", std::string(
"flowering"));
4607 context.
setObjectData(weed_obj_1,
"plant_name", std::string(
"pigweed"));
4609 context.
setObjectData(weed_obj_1,
"plant_type", std::string(
"weed"));
4612 context.
setObjectData(weed_obj_1,
"phenology_stage", std::string(
"vegetative"));
4613 context.
setObjectData(weed_obj_1,
"reflectivity_SW", 0.25f);
4616 context.
setObjectData(weed_obj_2,
"plant_name", std::string(
"pigweed"));
4618 context.
setObjectData(weed_obj_2,
"plant_type", std::string(
"weed"));
4621 context.
setObjectData(weed_obj_2,
"phenology_stage", std::string(
"vegetative"));
4622 context.
setObjectData(weed_obj_2,
"reflectivity_SW", 0.25f);
4627 camera_props.
HFOV = 60.0f;
4630 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);
4633 radiationmodel.updateGeometry();
4634 radiationmodel.runBand(
"RGB_R");
4635 radiationmodel.runBand(
"RGB_G");
4636 radiationmodel.runBand(
"RGB_B");
4639 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"test_camera");
4642 DOCTEST_CHECK(!metadata.agronomic_properties.
plant_species.empty());
4643 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.size() == 2);
4647 int pigweed_idx = -1;
4648 for (
size_t i = 0; i < metadata.agronomic_properties.
plant_species.size(); i++) {
4649 if (metadata.agronomic_properties.
plant_species[i] ==
"bean") {
4650 bean_idx =
static_cast<int>(i);
4651 }
else if (metadata.agronomic_properties.
plant_species[i] ==
"pigweed") {
4652 pigweed_idx =
static_cast<int>(i);
4656 DOCTEST_CHECK(bean_idx >= 0);
4657 DOCTEST_CHECK(pigweed_idx >= 0);
4660 if (bean_idx >= 0) {
4661 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[bean_idx] == 3);
4663 if (pigweed_idx >= 0) {
4664 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count[pigweed_idx] == 2);
4668 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"moderate");
4671 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m.size() == 2);
4672 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days.size() == 2);
4673 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage.size() == 2);
4674 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2.size() == 2);
4679 if (bean_idx >= 0) {
4680 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] > 0.40f);
4681 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[bean_idx] < 0.52f);
4683 if (pigweed_idx >= 0) {
4684 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] > 0.28f);
4685 DOCTEST_CHECK(metadata.agronomic_properties.
plant_height_m[pigweed_idx] < 0.37f);
4691 if (bean_idx >= 0) {
4692 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] > 27.0f);
4693 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[bean_idx] < 33.0f);
4695 if (pigweed_idx >= 0) {
4696 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] > 14.0f);
4697 DOCTEST_CHECK(metadata.agronomic_properties.
plant_age_days[pigweed_idx] < 19.0f);
4703 if (bean_idx >= 0) {
4704 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[bean_idx] ==
"flowering");
4706 if (pigweed_idx >= 0) {
4707 DOCTEST_CHECK(metadata.agronomic_properties.
plant_stage[pigweed_idx] ==
"vegetative");
4711 if (bean_idx >= 0) {
4712 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[bean_idx] > 0.0f);
4714 if (pigweed_idx >= 0) {
4715 DOCTEST_CHECK(metadata.agronomic_properties.
leaf_area_m2[pigweed_idx] > 0.0f);
4719 DOCTEST_SUBCASE(
"Agronomic properties with low weed pressure") {
4721 for (
int i = 0; i < 10; i++) {
4723 context.
setObjectData(crop_obj,
"plant_name", std::string(
"soybean"));
4725 context.
setObjectData(crop_obj,
"plant_type", std::string(
"crop"));
4730 context.
setObjectData(weed_obj,
"plant_name", std::string(
"lambsquarter"));
4732 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
4737 camera_props.
HFOV = 90.0f;
4739 radiationmodel.addRadiationCamera(
"low_weed_camera", {
"RGB_R"},
make_vec3(1.5, 0.25, 2.0),
make_vec3(1.5, 0.25, 0), camera_props, 1);
4741 radiationmodel.updateGeometry();
4742 radiationmodel.runBand(
"RGB_R");
4745 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"low_weed_camera");
4748 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"low");
4751 DOCTEST_SUBCASE(
"Agronomic properties with high weed pressure") {
4754 context.
setObjectData(crop_obj_1,
"plant_name", std::string(
"corn"));
4756 context.
setObjectData(crop_obj_1,
"plant_type", std::string(
"crop"));
4760 context.
setObjectData(crop_obj_2,
"plant_name", std::string(
"corn"));
4762 context.
setObjectData(crop_obj_2,
"plant_type", std::string(
"crop"));
4765 for (
int i = 0; i < 3; i++) {
4767 context.
setObjectData(weed_obj,
"plant_name", std::string(
"foxtail"));
4769 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
4775 camera_props.
HFOV = 60.0f;
4777 radiationmodel.addRadiationCamera(
"high_weed_camera", {
"RGB_R"},
make_vec3(0.5, 0.25, 2.5),
make_vec3(0.5, 0.25, 0), camera_props, 1);
4779 radiationmodel.updateGeometry();
4780 radiationmodel.runBand(
"RGB_R");
4783 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"high_weed_camera");
4786 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"high");
4789 DOCTEST_SUBCASE(
"Agronomic properties with no plant data") {
4792 for (
const auto &uuid: patch_UUIDs) {
4798 camera_props.
HFOV = 45.0f;
4800 radiationmodel.addRadiationCamera(
"no_data_camera", {
"RGB_R"},
make_vec3(0.5, 0.5, 2.0),
make_vec3(0.5, 0.5, 0), camera_props, 1);
4802 radiationmodel.updateGeometry();
4803 radiationmodel.runBand(
"RGB_R");
4806 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"no_data_camera");
4809 DOCTEST_CHECK(metadata.agronomic_properties.
plant_species.empty());
4810 DOCTEST_CHECK(metadata.agronomic_properties.
plant_count.empty());
4811 DOCTEST_CHECK(metadata.agronomic_properties.
weed_pressure ==
"");
4814 DOCTEST_SUBCASE(
"Agronomic properties JSON export") {
4817 context.
setObjectData(bean_obj,
"plant_name", std::string(
"bean"));
4819 context.
setObjectData(bean_obj,
"plant_type", std::string(
"crop"));
4823 context.
setObjectData(weed_obj,
"plant_name", std::string(
"weed"));
4825 context.
setObjectData(weed_obj,
"plant_type", std::string(
"weed"));
4830 camera_props.
HFOV = 50.0f;
4832 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);
4834 radiationmodel.updateGeometry();
4835 radiationmodel.runBand(
"RGB_R");
4836 radiationmodel.runBand(
"RGB_G");
4837 radiationmodel.runBand(
"RGB_B");
4840 radiationmodel.enableCameraMetadata(
"json_export_camera");
4843 std::string image_path = radiationmodel.writeCameraImage(
"json_export_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
"test_agronomic");
4846 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"json_export_camera");
4849 std::string json_path = image_path.substr(0, image_path.find_last_of(
".")) +
".json";
4850 std::ifstream json_file(json_path);
4851 DOCTEST_CHECK(json_file.is_open());
4853 if (json_file.is_open()) {
4859 DOCTEST_CHECK(j.contains(
"agronomic_properties"));
4861 if (j.contains(
"agronomic_properties")) {
4862 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_species"));
4863 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"plant_count"));
4864 DOCTEST_CHECK(j[
"agronomic_properties"].contains(
"weed_pressure"));
4867 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_species"].is_array());
4868 DOCTEST_CHECK(j[
"agronomic_properties"][
"plant_count"].is_array());
4869 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"].is_string());
4872 DOCTEST_CHECK(j[
"agronomic_properties"][
"weed_pressure"] ==
"high");
4876 std::remove(image_path.c_str());
4877 std::remove(json_path.c_str());
4882DOCTEST_TEST_CASE(
"RadiationModel - FOV_aspect_ratio Deprecation") {
4888 radiationmodel.disableMessages();
4891 radiationmodel.addRadiationBand(
"test");
4893 DOCTEST_SUBCASE(
"Default FOV_aspect_ratio is auto-calculated") {
4897 camera_props.
HFOV = 45.0f;
4901 std::string stderr_output;
4904 radiationmodel.addRadiationCamera(
"test_camera_1", {
"test"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), camera_props, 1);
4907 DOCTEST_CHECK(stderr_output.empty());
4912 DOCTEST_CHECK(std::abs(expected_aspect - 1.333333f) < 0.0001f);
4915 DOCTEST_SUBCASE(
"Explicit FOV_aspect_ratio triggers deprecation warning") {
4919 camera_props.
HFOV = 50.0f;
4923 std::string stderr_output;
4926 radiationmodel.addRadiationCamera(
"test_camera_2", {
"test"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), camera_props, 1);
4929 DOCTEST_CHECK(stderr_output.find(
"WARNING") != std::string::npos);
4930 DOCTEST_CHECK(stderr_output.find(
"FOV_aspect_ratio") != std::string::npos);
4931 DOCTEST_CHECK(stderr_output.find(
"deprecated") != std::string::npos);
4932 DOCTEST_CHECK(stderr_output.find(
"auto-calculated") != std::string::npos);
4935 DOCTEST_SUBCASE(
"Auto-calculated value ensures square pixels") {
4937 std::vector<helios::int2> resolutions = {
4944 for (
const auto &resolution: resolutions) {
4947 camera_props.
HFOV = 60.0f;
4950 std::string camera_label =
"camera_" + std::to_string(resolution.
x) +
"x" + std::to_string(resolution.
y);
4953 std::string stderr_output;
4956 radiationmodel.addRadiationCamera(camera_label, {
"test"},
make_vec3(0, 0, 2),
make_vec3(0, 0, 0), camera_props, 1);
4959 DOCTEST_CHECK(stderr_output.empty());
4964DOCTEST_TEST_CASE(
"RadiationModel Atmospheric Sky Model for Camera") {
4975 float pressure_Pa = 95000.f;
4976 float temperature_K = 285.f;
4977 float humidity_rel = 0.6f;
4978 float turbidity = 0.08f;
4980 context.
setGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
4981 context.
setGlobalData(
"atmosphere_temperature_K", temperature_K);
4982 context.
setGlobalData(
"atmosphere_humidity_rel", humidity_rel);
4986 radiationmodel.disableMessages();
4988 DOCTEST_SUBCASE(
"Sky model requires wavelength bounds with uniform response") {
4990 radiationmodel.addRadiationBand(
"VIS");
4991 radiationmodel.setScatteringDepth(
"VIS", 1);
4992 radiationmodel.setDirectRayCount(
"VIS", 100);
4993 radiationmodel.setDiffuseRayCount(
"VIS", 100);
4994 radiationmodel.disableEmission(
"VIS");
4995 radiationmodel.setDiffuseRadiationFlux(
"VIS", 100.f);
4998 uint SunSource = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
4999 radiationmodel.setSourceFlux(SunSource,
"VIS", 1000.f);
5004 camera_props.
HFOV = 60.0f;
5005 radiationmodel.addRadiationCamera(
"test_camera", {
"VIS"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 10);
5007 radiationmodel.updateGeometry();
5011 bool threw_error =
false;
5015 radiationmodel.runBand(
"VIS");
5016 }
catch (std::runtime_error &e) {
5017 std::string error_msg = e.what();
5018 threw_error = (error_msg.find(
"wavelength bounds") != std::string::npos);
5021 DOCTEST_CHECK(threw_error);
5024 DOCTEST_SUBCASE(
"Sky model computed with camera and wavelength bounds") {
5026 radiationmodel.addRadiationBand(
"VIS", 400.f, 700.f);
5027 radiationmodel.setDirectRayCount(
"VIS", 100);
5028 radiationmodel.setDiffuseRayCount(
"VIS", 100);
5029 radiationmodel.disableEmission(
"VIS");
5030 radiationmodel.setDiffuseRadiationFlux(
"VIS", 100.f);
5036 SunSource = radiationmodel.addCollimatedRadiationSource(
make_vec3(0.5, 0.3, 0.8));
5038 radiationmodel.setSourceFlux(SunSource,
"VIS", 1000.f);
5043 camera_props.
HFOV = 60.0f;
5044 radiationmodel.addRadiationCamera(
"test_camera", {
"VIS"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), camera_props, 10);
5046 radiationmodel.updateGeometry();
5052 radiationmodel.runBand(
"VIS");
5056 DOCTEST_CHECK(
true);
5059 DOCTEST_SUBCASE(
"Atmospheric parameters do not cause errors") {
5061 radiationmodel.addRadiationBand(
"VIS", 400.f, 700.f);
5062 radiationmodel.setDirectRayCount(
"VIS", 0);
5063 radiationmodel.setDiffuseRayCount(
"VIS", 0);
5064 radiationmodel.disableEmission(
"VIS");
5065 radiationmodel.setDiffuseRadiationFlux(
"VIS", 100.f);
5070 camera_props.
HFOV = 45.0f;
5071 radiationmodel.addRadiationCamera(
"sky_camera", {
"VIS"},
make_vec3(10, 10, 10),
make_vec3(0, 0, 1), camera_props, 50);
5073 radiationmodel.updateGeometry();
5074 radiationmodel.runBand(
"VIS");
5078 float high_turbidity = 0.3f;
5079 context.
setGlobalData(
"atmosphere_turbidity", high_turbidity);
5081 radiationmodel.runBand(
"VIS");
5084 DOCTEST_CHECK(
true);
5088DOCTEST_TEST_CASE(
"RadiationModel - Camera White Balance") {
5092 radiationmodel.disableMessages();
5099 radiationmodel.addRadiationBand(
"RGB_R");
5100 radiationmodel.addRadiationBand(
"RGB_G");
5101 radiationmodel.addRadiationBand(
"RGB_B");
5104 uint source = radiationmodel.addCollimatedRadiationSource();
5105 radiationmodel.setSourceFlux(source,
"RGB_R", 100.f);
5106 radiationmodel.setSourceFlux(source,
"RGB_G", 150.f);
5107 radiationmodel.setSourceFlux(source,
"RGB_B", 80.f);
5109 DOCTEST_SUBCASE(
"Default white_balance is 'auto'") {
5114 camera_props.
HFOV = 45.0f;
5119 radiationmodel.addRadiationCamera(
"test_camera", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5122 CameraProperties retrieved_props = radiationmodel.getCameraParameters(
"test_camera");
5126 DOCTEST_SUBCASE(
"White balance mode 'off' preserves raw data") {
5131 camera_props.
HFOV = 45.0f;
5134 radiationmodel.addRadiationCamera(
"camera_wb_off", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5137 radiationmodel.updateGeometry();
5138 radiationmodel.runBand(
"RGB_R");
5139 radiationmodel.runBand(
"RGB_G");
5140 radiationmodel.runBand(
"RGB_B");
5143 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"camera_wb_off");
5144 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5147 DOCTEST_CHECK(
true);
5150 DOCTEST_SUBCASE(
"White balance mode 'auto' applies correction") {
5155 camera_props.
HFOV = 45.0f;
5158 radiationmodel.addRadiationCamera(
"camera_wb_auto", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5161 radiationmodel.updateGeometry();
5162 radiationmodel.runBand(
"RGB_R");
5163 radiationmodel.runBand(
"RGB_G");
5164 radiationmodel.runBand(
"RGB_B");
5167 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"camera_wb_auto");
5168 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5171 DOCTEST_CHECK(
true);
5174 DOCTEST_SUBCASE(
"Single-channel camera skips white balance") {
5179 camera_props.
HFOV = 45.0f;
5182 radiationmodel.addRadiationCamera(
"camera_1ch", {
"RGB_R"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5185 radiationmodel.updateGeometry();
5186 radiationmodel.runBand(
"RGB_R");
5189 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"camera_1ch");
5190 DOCTEST_CHECK(metadata.camera_properties.
channels == 1);
5191 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"auto");
5194 DOCTEST_CHECK(
true);
5197 DOCTEST_SUBCASE(
"Update camera white_balance parameter") {
5202 camera_props.
HFOV = 45.0f;
5205 radiationmodel.addRadiationCamera(
"camera_update", {
"RGB_R",
"RGB_G",
"RGB_B"},
make_vec3(0, -5, 2),
make_vec3(0, 0, 0), camera_props, 1);
5208 CameraProperties updated_props = radiationmodel.getCameraParameters(
"camera_update");
5210 radiationmodel.updateCameraParameters(
"camera_update", updated_props);
5213 CameraProperties retrieved_props = radiationmodel.getCameraParameters(
"camera_update");
5217 radiationmodel.updateGeometry();
5218 radiationmodel.runBand(
"RGB_R");
5219 radiationmodel.runBand(
"RGB_G");
5220 radiationmodel.runBand(
"RGB_B");
5223 CameraMetadata metadata = radiationmodel.getCameraMetadata(
"camera_update");
5224 DOCTEST_CHECK(metadata.camera_properties.
white_balance ==
"off");
5227 DOCTEST_SUBCASE(
"CameraProperties equality includes white_balance") {
5235 DOCTEST_CHECK(props1 == props2);
5241 DOCTEST_CHECK(props1 != props2);
5245DOCTEST_TEST_CASE(
"RadiationModel setDiffuseSpectrum and emission band behavior") {
5247 using namespace helios;
5252 std::vector<vec2> test_spectrum;
5253 test_spectrum.emplace_back(400.f, 1.0f);
5254 test_spectrum.emplace_back(500.f, 1.5f);
5255 test_spectrum.emplace_back(600.f, 1.0f);
5256 test_spectrum.emplace_back(700.f, 0.5f);
5260 radiation.disableMessages();
5262 DOCTEST_SUBCASE(
"setDiffuseSpectrum applies to all bands") {
5264 radiation.addRadiationBand(
"band1", 400.f, 500.f);
5265 radiation.addRadiationBand(
"band2", 500.f, 600.f);
5266 radiation.addRadiationBand(
"band3", 600.f, 700.f);
5269 radiation.disableEmission(
"band1");
5270 radiation.disableEmission(
"band2");
5271 radiation.disableEmission(
"band3");
5274 radiation.setDiffuseSpectrum(
"test_spectrum");
5277 float flux1 = radiation.getDiffuseFlux(
"band1");
5278 float flux2 = radiation.getDiffuseFlux(
"band2");
5279 float flux3 = radiation.getDiffuseFlux(
"band3");
5281 DOCTEST_CHECK(flux1 > 0.f);
5282 DOCTEST_CHECK(flux2 > 0.f);
5283 DOCTEST_CHECK(flux3 > 0.f);
5286 DOCTEST_SUBCASE(
"getDiffuseFlux returns 0 for emission-enabled bands with spectrum") {
5288 radiation.addRadiationBand(
"emission_band", 400.f, 700.f);
5291 radiation.setDiffuseSpectrum(
"test_spectrum");
5294 float flux = radiation.getDiffuseFlux(
"emission_band");
5295 DOCTEST_CHECK(flux == 0.f);
5298 DOCTEST_SUBCASE(
"getDiffuseFlux returns manual flux for emission-enabled bands") {
5300 radiation.addRadiationBand(
"emission_band", 400.f, 700.f);
5303 radiation.setDiffuseSpectrum(
"test_spectrum");
5306 float manual_flux = 100.f;
5307 radiation.setDiffuseRadiationFlux(
"emission_band", manual_flux);
5310 float flux = radiation.getDiffuseFlux(
"emission_band");
5311 DOCTEST_CHECK(flux == manual_flux);
5314 DOCTEST_SUBCASE(
"Manual flux overrides spectrum for non-emission bands") {
5316 radiation.addRadiationBand(
"shortwave", 400.f, 700.f);
5317 radiation.disableEmission(
"shortwave");
5320 radiation.setDiffuseSpectrum(
"test_spectrum");
5323 float spectrum_flux = radiation.getDiffuseFlux(
"shortwave");
5324 DOCTEST_CHECK(spectrum_flux > 0.f);
5327 float manual_flux = 999.f;
5328 radiation.setDiffuseRadiationFlux(
"shortwave", manual_flux);
5330 float flux = radiation.getDiffuseFlux(
"shortwave");
5331 DOCTEST_CHECK(flux == manual_flux);
5334 DOCTEST_SUBCASE(
"setDiffuseSpectrum with no bands does not error") {
5339 radiation2.disableMessages();
5342 radiation2.setDiffuseSpectrum(
"test_spectrum");
5343 DOCTEST_CHECK(
true);
5346 DOCTEST_SUBCASE(
"setDiffuseSpectrum before bands are added applies to later bands") {
5351 radiation2.disableMessages();
5354 radiation2.setDiffuseSpectrum(
"test_spectrum");
5357 radiation2.addRadiationBand(
"band1", 400.f, 500.f);
5358 radiation2.addRadiationBand(
"band2", 500.f, 600.f);
5361 radiation2.disableEmission(
"band1");
5362 radiation2.disableEmission(
"band2");
5365 float flux1 = radiation2.getDiffuseFlux(
"band1");
5366 float flux2 = radiation2.getDiffuseFlux(
"band2");
5368 DOCTEST_CHECK(flux1 > 0.f);
5369 DOCTEST_CHECK(flux2 > 0.f);
5372 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral scales global spectrum before bands are added") {
5377 radiation2.disableMessages();
5380 radiation2.setDiffuseSpectrum(
"test_spectrum");
5381 float target_integral = 850.f;
5382 radiation2.setDiffuseSpectrumIntegral(target_integral);
5385 radiation2.addRadiationBand(
"full", 400.f, 700.f);
5386 radiation2.disableEmission(
"full");
5390 float flux = radiation2.getDiffuseFlux(
"full");
5393 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5396 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral with wavelength bounds scales global spectrum") {
5401 radiation2.disableMessages();
5404 radiation2.setDiffuseSpectrum(
"test_spectrum");
5405 float target_integral = 500.f;
5406 radiation2.setDiffuseSpectrumIntegral(target_integral, 500.f, 600.f);
5409 radiation2.addRadiationBand(
"partial", 500.f, 600.f);
5410 radiation2.disableEmission(
"partial");
5413 float flux = radiation2.getDiffuseFlux(
"partial");
5414 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5417 DOCTEST_SUBCASE(
"setDiffuseSpectrumIntegral applies to existing bands") {
5419 radiation.addRadiationBand(
"band1", 400.f, 700.f);
5420 radiation.disableEmission(
"band1");
5422 radiation.setDiffuseSpectrum(
"test_spectrum");
5423 float target_integral = 1000.f;
5424 radiation.setDiffuseSpectrumIntegral(target_integral);
5426 float flux = radiation.getDiffuseFlux(
"band1");
5427 DOCTEST_CHECK(flux == doctest::Approx(target_integral).epsilon(0.01));
5433TEST_CASE(
"Radiation - Prague Context data fallback behavior") {
5436 radiation.disableMessages();
5439 radiation.addRadiationBand(
"red");
5440 radiation.addRadiationBand(
"green");
5441 radiation.addRadiationBand(
"blue");
5452 camera_props.
HFOV = 45.0f;
5454 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
5458 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
5461TEST_CASE(
"Radiation - Prague Context data integration end-to-end") {
5464 radiation.disableMessages();
5468 std::vector<float> spectral_params(225 * 6);
5469 for (
int i = 0; i < 225; ++i) {
5470 float wavelength = 360.0f + i * 5.0f;
5474 float rayleigh_factor = std::pow(550.0f / wavelength, 4.0f);
5476 spectral_params[base + 0] = wavelength;
5477 spectral_params[base + 1] = 0.3f * rayleigh_factor;
5478 spectral_params[base + 2] = 2.0f;
5479 spectral_params[base + 3] = 15.0f;
5480 spectral_params[base + 4] = 2.0f;
5481 spectral_params[base + 5] = 0.8f;
5484 context.
setGlobalData(
"prague_sky_spectral_params", spectral_params);
5492 DOCTEST_CHECK_NOTHROW(context.
getGlobalData(
"prague_sky_valid", valid));
5493 DOCTEST_CHECK(valid == 1);
5495 std::vector<float> read_params;
5496 DOCTEST_CHECK_NOTHROW(context.
getGlobalData(
"prague_sky_spectral_params", read_params));
5497 DOCTEST_CHECK(read_params.size() == 225 * 6);
5500 radiation.addRadiationBand(
"red");
5501 radiation.addRadiationBand(
"green");
5502 radiation.addRadiationBand(
"blue");
5513 camera_props.
HFOV = 45.0f;
5515 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
make_vec3(0, -3, 2),
make_vec3(0, 0, 0), camera_props, 1);
5518 DOCTEST_CHECK_NOTHROW(radiation.updateGeometry());
5521DOCTEST_TEST_CASE(
"RadiationModel Automatic Spectrum Update Detection") {
5525 radiation.disableMessages();
5528 std::vector<helios::vec2> direct_spectrum_v1 = {{300, 1.0}, {400, 2.0}, {500, 3.0}, {700, 2.0}, {800, 1.0}};
5529 context.
setGlobalData(
"test_direct_spectrum", direct_spectrum_v1);
5532 std::vector<helios::vec2> diffuse_spectrum_v1 = {{300, 0.5}, {400, 1.0}, {500, 1.5}, {700, 1.0}, {800, 0.5}};
5533 context.
setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v1);
5536 uint sun = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
5537 radiation.setSourceSpectrum(sun,
"test_direct_spectrum");
5540 radiation.setDiffuseSpectrum(
"test_diffuse_spectrum");
5543 radiation.addRadiationBand(
"PAR", 400, 700);
5550 radiation.updateGeometry();
5551 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
5555 DOCTEST_CHECK(flux_v1 > 0.0f);
5558 std::vector<helios::vec2> direct_spectrum_v2 = {{300, 2.0}, {400, 4.0}, {500, 6.0}, {700, 4.0}, {800, 2.0}};
5559 context.
setGlobalData(
"test_direct_spectrum", direct_spectrum_v2);
5562 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
5568 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.9f);
5569 DOCTEST_CHECK(flux_v2 < flux_v1 * 2.1f);
5572 std::vector<helios::vec2> diffuse_spectrum_v2 = {{300, 1.5}, {400, 3.0}, {500, 4.5}, {700, 3.0}, {800, 1.5}};
5573 context.
setGlobalData(
"test_diffuse_spectrum", diffuse_spectrum_v2);
5576 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"PAR"));
5583 DOCTEST_CHECK(flux_v3 >= flux_v2 * 0.99f);
5586DOCTEST_TEST_CASE(
"RadiationModel Multiple Sources Same Spectrum Update") {
5590 radiation.disableMessages();
5593 std::vector<helios::vec2> shared_spectrum = {{300, 1.0}, {800, 1.0}};
5600 for (
int i = 0; i < 3; i++) {
5601 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
5602 radiation.setSourceSpectrum(source,
"shared_spectrum");
5606 radiation.addRadiationBand(
"test", 400, 700);
5611 radiation.updateGeometry();
5612 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
5616 DOCTEST_CHECK(flux_v1 > 0.0f);
5619 std::vector<helios::vec2> updated_spectrum = {{300, 2.0}, {800, 2.0}};
5623 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
5629 DOCTEST_CHECK(flux_v2 > flux_v1 * 1.8f);
5632DOCTEST_TEST_CASE(
"RadiationModel No Update When Spectrum Unchanged") {
5636 radiation.disableMessages();
5639 std::vector<helios::vec2> spectrum = {{300, 1.0}, {800, 1.0}};
5642 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
5643 radiation.setSourceSpectrum(source,
"test_spectrum");
5644 radiation.addRadiationBand(
"test", 400, 700);
5649 radiation.updateGeometry();
5650 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
5654 DOCTEST_CHECK_NOTHROW(radiation.runBand(
"test"));
5658 DOCTEST_CHECK(flux > 0.0f);
5661DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties default camera_zoom") {
5666DOCTEST_TEST_CASE(
"RadiationModel - CameraProperties equality with camera_zoom") {
5670 DOCTEST_CHECK(props1 == props2);
5673 DOCTEST_CHECK(props1 != props2);
5676 DOCTEST_CHECK(props1 == props2);
5679DOCTEST_TEST_CASE(
"RadiationModel - camera_zoom validation in updateCameraParameters") {
5682 radiation.disableMessages();
5689 std::vector<std::string> bands = {
"R"};
5690 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
5696 DOCTEST_CHECK_THROWS_WITH_AS(radiation.updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
5700 DOCTEST_CHECK_THROWS_WITH_AS(radiation.updateCameraParameters(
"test_cam", invalid),
"ERROR (RadiationModel::updateCameraParameters): camera_zoom must be greater than 0.", std::runtime_error);
5703DOCTEST_TEST_CASE(
"RadiationModel - camera_zoom parameter get/set") {
5706 radiation.disableMessages();
5713 std::vector<std::string> bands = {
"R",
"G",
"B"};
5714 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
5718 DOCTEST_CHECK(retrieved.
HFOV == 60.0f);
5721DOCTEST_TEST_CASE(
"RadiationModel - update camera_zoom") {
5724 radiation.disableMessages();
5731 std::vector<std::string> bands = {
"R"};
5732 radiation.addRadiationCamera(
"test_cam", bands,
make_vec3(0, 0, 5),
make_vec3(0, 0, -1), props, 1);
5737 radiation.updateCameraParameters(
"test_cam", updated);
5741 DOCTEST_CHECK(final_props.
HFOV == 45.0f);
5743DOCTEST_TEST_CASE(
"Lens Flare - Enable/Disable API") {
5750 camera_props.
HFOV = 45.0f;
5751 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
5754 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
5757 radiation.enableCameraLensFlare(
"test_camera");
5758 DOCTEST_CHECK(radiation.isCameraLensFlareEnabled(
"test_camera"));
5761 radiation.disableCameraLensFlare(
"test_camera");
5762 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
5765 DOCTEST_CHECK_THROWS(radiation.enableCameraLensFlare(
"nonexistent_camera"));
5766 DOCTEST_CHECK_THROWS(radiation.disableCameraLensFlare(
"nonexistent_camera"));
5767 DOCTEST_CHECK_THROWS((
void) radiation.isCameraLensFlareEnabled(
"nonexistent_camera"));
5770DOCTEST_TEST_CASE(
"Lens Flare - Properties API") {
5777 camera_props.
HFOV = 45.0f;
5778 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
5781 LensFlareProperties default_props = radiation.getCameraLensFlareProperties(
"test_camera");
5784 DOCTEST_CHECK(default_props.
ghost_intensity == doctest::Approx(1.0f));
5798 radiation.setCameraLensFlareProperties(
"test_camera", custom_props);
5799 LensFlareProperties retrieved_props = radiation.getCameraLensFlareProperties(
"test_camera");
5803 DOCTEST_CHECK(retrieved_props.
ghost_intensity == doctest::Approx(0.5f));
5812 invalid_props = default_props;
5814 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5817 invalid_props = default_props;
5819 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5822 invalid_props = default_props;
5824 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5827 invalid_props = default_props;
5829 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5832 invalid_props = default_props;
5834 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5837 invalid_props = default_props;
5839 DOCTEST_CHECK_THROWS(radiation.setCameraLensFlareProperties(
"test_camera", invalid_props));
5842DOCTEST_TEST_CASE(
"Lens Flare - Application to Camera Image") {
5845 radiation.disableMessages();
5862 radiation.addRadiationBand(
"red");
5863 radiation.addRadiationBand(
"green");
5864 radiation.addRadiationBand(
"blue");
5867 radiation.disableEmission(
"red");
5868 radiation.disableEmission(
"green");
5869 radiation.disableEmission(
"blue");
5872 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
5873 radiation.setSourceFlux(source,
"red", 500.0f);
5874 radiation.setSourceFlux(source,
"green", 500.0f);
5875 radiation.setSourceFlux(source,
"blue", 500.0f);
5877 radiation.setDirectRayCount(
"red", 1000);
5878 radiation.setDirectRayCount(
"green", 1000);
5879 radiation.setDirectRayCount(
"blue", 1000);
5880 radiation.setDiffuseRayCount(
"red", 100);
5881 radiation.setDiffuseRayCount(
"green", 100);
5882 radiation.setDiffuseRayCount(
"blue", 100);
5885 radiation.setScatteringDepth(
"red", 1);
5886 radiation.setScatteringDepth(
"green", 1);
5887 radiation.setScatteringDepth(
"blue", 1);
5892 camera_props.
HFOV = 60.0f;
5894 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
5897 radiation.enableCameraLensFlare(
"test_camera");
5902 radiation.setCameraLensFlareProperties(
"test_camera", props);
5905 radiation.updateGeometry();
5906 radiation.runBand({
"red",
"green",
"blue"});
5909 DOCTEST_CHECK_NOTHROW(radiation.applyCameraImageCorrections(
"test_camera",
"red",
"green",
"blue"));
5912 auto all_labels = radiation.getAllCameraLabels();
5913 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"test_camera") != all_labels.end());
5916DOCTEST_TEST_CASE(
"Lens Flare - Disabled Does Nothing") {
5919 radiation.disableMessages();
5929 radiation.addRadiationBand(
"red");
5930 radiation.addRadiationBand(
"green");
5931 radiation.addRadiationBand(
"blue");
5934 radiation.disableEmission(
"red");
5935 radiation.disableEmission(
"green");
5936 radiation.disableEmission(
"blue");
5939 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
5940 radiation.setSourceFlux(source,
"red", 500.0f);
5941 radiation.setSourceFlux(source,
"green", 500.0f);
5942 radiation.setSourceFlux(source,
"blue", 500.0f);
5944 radiation.setDirectRayCount(
"red", 100);
5945 radiation.setDirectRayCount(
"green", 100);
5946 radiation.setDirectRayCount(
"blue", 100);
5949 radiation.setScatteringDepth(
"red", 1);
5950 radiation.setScatteringDepth(
"green", 1);
5951 radiation.setScatteringDepth(
"blue", 1);
5956 camera_props.
HFOV = 45.0f;
5957 radiation.addRadiationCamera(
"test_camera", {
"red",
"green",
"blue"},
helios::make_vec3(0, 0, 5),
helios::make_vec3(0, 0, 0), camera_props, 1);
5960 radiation.updateGeometry();
5961 radiation.runBand({
"red",
"green",
"blue"});
5964 DOCTEST_CHECK(!radiation.isCameraLensFlareEnabled(
"test_camera"));
5965 DOCTEST_CHECK_NOTHROW(radiation.applyCameraImageCorrections(
"test_camera",
"red",
"green",
"blue"));
5968DOCTEST_TEST_CASE(
"RadiationModel - Camera Sphere Source Rendering") {
5971 radiation.disableMessages();
5977 radiation.addRadiationBand(
"test_band");
5978 radiation.disableEmission(
"test_band");
5979 radiation.setDirectRayCount(
"test_band", 100);
5980 radiation.setDiffuseRayCount(
"test_band", 0);
5981 radiation.setScatteringDepth(
"test_band", 1);
5983 uint source = radiation.addSphereRadiationSource(helios::make_vec3(0, 0, 0.5), 0.2f);
5984 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
5986 radiation.setSourceSpectrum(source,
"test_spectrum");
5990 camera_props.
HFOV = 45.0f;
5994 radiation.updateGeometry();
5995 radiation.runBand(
"test_band");
5997 auto pixel_data = radiation.getCameraPixelData(
"sphere_cam",
"test_band");
5998 DOCTEST_REQUIRE(!pixel_data.empty());
6001 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6004DOCTEST_TEST_CASE(
"RadiationModel - Camera Rectangle Source Rendering") {
6007 radiation.disableMessages();
6013 radiation.addRadiationBand(
"test_band");
6014 radiation.disableEmission(
"test_band");
6015 radiation.setDirectRayCount(
"test_band", 100);
6016 radiation.setDiffuseRayCount(
"test_band", 0);
6017 radiation.setScatteringDepth(
"test_band", 1);
6019 uint source = radiation.addRectangleRadiationSource(helios::make_vec3(0, 0, 0.5),
helios::make_vec2(0.4f, 0.4f), helios::make_vec3(0, 0, 0));
6020 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6022 radiation.setSourceSpectrum(source,
"test_spectrum");
6026 camera_props.
HFOV = 45.0f;
6030 radiation.updateGeometry();
6031 radiation.runBand(
"test_band");
6033 auto pixel_data = radiation.getCameraPixelData(
"rect_cam",
"test_band");
6034 DOCTEST_REQUIRE(!pixel_data.empty());
6037 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6040DOCTEST_TEST_CASE(
"RadiationModel - Camera Disk Source Rendering") {
6043 radiation.disableMessages();
6049 radiation.addRadiationBand(
"test_band");
6050 radiation.disableEmission(
"test_band");
6051 radiation.setDirectRayCount(
"test_band", 100);
6052 radiation.setDiffuseRayCount(
"test_band", 0);
6053 radiation.setScatteringDepth(
"test_band", 1);
6055 uint source = radiation.addDiskRadiationSource(helios::make_vec3(0, 0, 0.5), 0.2f, helios::make_vec3(0, 0, 0));
6056 std::vector<helios::vec2> test_spectrum = {{400, 1.0f}, {700, 1.0f}};
6058 radiation.setSourceSpectrum(source,
"test_spectrum");
6062 camera_props.
HFOV = 45.0f;
6066 radiation.updateGeometry();
6067 radiation.runBand(
"test_band");
6069 auto pixel_data = radiation.getCameraPixelData(
"disk_cam",
"test_band");
6070 DOCTEST_REQUIRE(!pixel_data.empty());
6073 DOCTEST_CHECK(pixel_data[center_idx] > 0.0f);
6078DOCTEST_TEST_CASE(
"Phase1.E Step2: Backend GPU Memory Query Integration") {
6084 radiation.disableMessages();
6087 DOCTEST_REQUIRE_NOTHROW(radiation.queryBackendGPUMemory());
6094DOCTEST_TEST_CASE(
"RadiationModel buildGeometryData() Extraction") {
6101 context.
addTriangle(helios::make_vec3(2, 0, 0), helios::make_vec3(3, 0, 0), helios::make_vec3(2.5, 1, 0));
6104 radiation.disableMessages();
6107 size_t prim_count = radiation.testBuildGeometryData();
6110 DOCTEST_CHECK(prim_count == 2);
6118DOCTEST_TEST_CASE(
"Phase1.E Step4: updateGeometry() Backend Integration") {
6125 context.
addTriangle(helios::make_vec3(2, 0, 0), helios::make_vec3(3, 0, 0), helios::make_vec3(2.5, 1, 0));
6128 radiation.disableMessages();
6131 DOCTEST_REQUIRE_NOTHROW(radiation.updateGeometry());
6140 size_t prim_count = radiation.testBuildGeometryData();
6141 DOCTEST_CHECK(prim_count == 2);
6144DOCTEST_TEST_CASE(
"Phase1.E Step5: Backend Functional Validation - Direct Radiation") {
6157 radiation.disableMessages();
6160 radiation.addRadiationBand(
"PAR");
6163 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, -1));
6164 radiation.setSourceFlux(source,
"PAR", 1000.0f);
6169 radiation.testBuildAllBackendData();
6172 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->updateGeometry(radiation.getGeometryData()));
6173 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->buildAccelerationStructure());
6174 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->updateMaterials(radiation.getMaterialData()));
6175 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->updateSources(radiation.getSourceData()));
6178 radiation.getBackend()->zeroRadiationBuffers(1);
6191 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->launchDirectRays(params));
6195 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->getRadiationResults(results));
6202 float expected = 1000.0f;
6204 float tolerance = 10.0f;
6206 DOCTEST_CHECK_MESSAGE(std::abs(actual - expected) < tolerance,
"Backend radiation incorrect: expected " << expected <<
" W, got " << actual <<
" W");
6209DOCTEST_TEST_CASE(
"Phase1.E Step6: Backend Functional Validation - Diffuse Radiation") {
6222 radiation.disableMessages();
6225 radiation.addRadiationBand(
"SW");
6226 radiation.setDiffuseRadiationFlux(
"SW", 100.0f);
6227 radiation.setDiffuseRayCount(
"SW", 10000);
6228 radiation.disableEmission(
"SW");
6231 radiation.testBuildAllBackendData();
6234 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->updateGeometry(radiation.getGeometryData()));
6235 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->buildAccelerationStructure());
6236 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->updateMaterials(radiation.getMaterialData()));
6239 radiation.getBackend()->zeroRadiationBuffers(1);
6262 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->launchDiffuseRays(params));
6266 DOCTEST_REQUIRE_NOTHROW(radiation.getBackend()->getRadiationResults(results));
6273 float expected = 100.0f;
6275 float tolerance = 2.0f;
6277 DOCTEST_CHECK_MESSAGE(std::abs(actual - expected) < tolerance,
"Backend diffuse radiation incorrect: expected " << expected <<
" W, got " << actual <<
" W");
6280DOCTEST_TEST_CASE(
"Phase1.E Step6b: Backend Diffuse With Partial Occlusion") {
6301 radiation.disableMessages();
6304 radiation.addRadiationBand(
"SW");
6305 radiation.setDiffuseRadiationFlux(
"SW", 100.0f);
6306 radiation.setDiffuseRayCount(
"SW", 10000);
6307 radiation.disableEmission(
"SW");
6310 radiation.testBuildAllBackendData();
6311 radiation.getBackend()->updateGeometry(radiation.getGeometryData());
6312 radiation.getBackend()->buildAccelerationStructure();
6313 radiation.getBackend()->updateMaterials(radiation.getMaterialData());
6314 radiation.getBackend()->zeroRadiationBuffers(1);
6334 radiation.getBackend()->launchDiffuseRays(params);
6338 radiation.getBackend()->getRadiationResults(results);
6347 DOCTEST_CHECK_MESSAGE(patch0_radiation < 90.0f,
"Patch0 should be partially blocked by patch1, got " << patch0_radiation <<
" W (expected <90)");
6350DOCTEST_TEST_CASE(
"RadiationModel Multi-Patch Direct Radiation Occlusion Test") {
6372 radiation.disableMessages();
6375 radiation.addRadiationBand(
"PAR");
6376 uint source = radiation.addCollimatedRadiationSource(helios::make_vec3(0, 0, 1));
6377 radiation.setSourceFlux(source,
"PAR", 100.0f);
6380 radiation.testBuildAllBackendData();
6381 radiation.getBackend()->updateGeometry(radiation.getGeometryData());
6382 radiation.getBackend()->buildAccelerationStructure();
6383 radiation.getBackend()->updateMaterials(radiation.getMaterialData());
6384 radiation.getBackend()->updateSources(radiation.getSourceData());
6385 radiation.getBackend()->zeroRadiationBuffers(1);
6398 radiation.getBackend()->launchDirectRays(params);
6402 radiation.getBackend()->getRadiationResults(results);
6411 DOCTEST_CHECK_MESSAGE(patch0_radiation < 10.0f,
"Patch0 should be blocked by patch1, got " << patch0_radiation <<
" W (expected ~0)");
6412 DOCTEST_CHECK_MESSAGE(patch1_radiation > 90.0f,
"Patch1 should receive full flux, got " << patch1_radiation <<
" W (expected ~100)");
6414DOCTEST_TEST_CASE(
"RadiationModel - Camera Pixel UUID Indexing Validation") {
6433 radiationmodel.disableMessages();
6437 cam_props.
HFOV = 90;
6441 radiationmodel.addRadiationCamera(
"test_cam", {
"SW"},
make_vec3(0, 0, 5),
6445 radiationmodel.addRadiationBand(
"SW");
6446 radiationmodel.setScatteringDepth(
"SW", 1);
6449 uint source = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
6450 radiationmodel.setSourceFlux(source,
"SW", 1000.f);
6452 radiationmodel.updateGeometry();
6453 radiationmodel.runBand(
"SW");
6457 std::string test_file =
"test_cam_test_camera_indexing_00000.txt";
6458 radiationmodel.writePrimitiveDataLabelMap(
"test_cam",
"patch_id",
"test_camera_indexing",
"./", 0, 0.0f);
6461 std::ifstream label_file(test_file);
6462 DOCTEST_REQUIRE_MESSAGE(label_file.is_open(),
"Could not open label map file");
6464 std::vector<float> labels;
6466 while (label_file >> val) {
6467 labels.push_back(val);
6471 DOCTEST_REQUIRE_EQ(labels.size(), 64 * 64);
6476 int left_votes = 0, center_votes = 0, right_votes = 0;
6477 for (
int j = 26; j < 38; j++) {
6478 for (
int i = 20; i < 25; i++) {
6479 float label = labels[j * 64 + i];
6482 else if (label == 2.0f)
6484 else if (label == 3.0f)
6490 DOCTEST_CHECK_MESSAGE(left_votes > right_votes,
"Left region should see world-left patch (ID=1), not world-right (ID=3)");
6491 DOCTEST_CHECK_MESSAGE(left_votes > center_votes,
"Left region should predominantly see left patch");
6494 left_votes = center_votes = right_votes = 0;
6495 for (
int j = 26; j < 38; j++) {
6496 for (
int i = 39; i < 44; i++) {
6497 float label = labels[j * 64 + i];
6500 else if (label == 2.0f)
6502 else if (label == 3.0f)
6508 DOCTEST_CHECK_MESSAGE(right_votes > left_votes,
"Right region should see world-right patch (ID=3), not world-left (ID=1)");
6509 DOCTEST_CHECK_MESSAGE(right_votes > center_votes,
"Right region should predominantly see right patch");
6512 std::remove(test_file.c_str());
6515DOCTEST_TEST_CASE(
"RadiationModel - Pixel Labeling with Fine Tessellation") {
6523 float camera_height = 20.0f;
6524 float HFOV_degrees = 45.0f;
6527 float ground_size = 2.0f * camera_height * tanf(HFOV_degrees *
M_PI / 180.0f / 2.0f);
6536 radiationmodel.disableMessages();
6540 cam_props.
HFOV = HFOV_degrees;
6544 radiationmodel.addRadiationCamera(
"test_cam", {
"SW"},
make_vec3(0, 0, camera_height),
6548 radiationmodel.addRadiationBand(
"SW");
6549 radiationmodel.setScatteringDepth(
"SW", 1);
6552 uint source = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
6553 radiationmodel.setSourceFlux(source,
"SW", 1000.f);
6555 radiationmodel.updateGeometry();
6556 radiationmodel.runBand(
"SW");
6560 std::string test_file =
"test_cam_test_fine_tessellation_00000.txt";
6561 radiationmodel.writePrimitiveDataLabelMap(
"test_cam",
"ground_id",
"test_fine_tessellation",
"./", 0, 0.0f);
6563 std::ifstream label_file(test_file);
6564 DOCTEST_REQUIRE(label_file.is_open());
6566 std::vector<float> labels;
6568 while (label_file >> val) {
6569 labels.push_back(val);
6574 int valid_count = 0;
6576 for (
float label: labels) {
6577 if (std::isnan(label)) {
6579 }
else if (label == 42.0f) {
6584 float valid_percentage = 100.0f * valid_count / labels.size();
6587 DOCTEST_CHECK_MESSAGE(valid_percentage > 95.0f,
"Pixel labeling with fine tessellation should have >95% valid hits, got " << valid_percentage <<
"%");
6590 std::remove(test_file.c_str());
6593DOCTEST_TEST_CASE(
"RadiationModel - runBand Invalid Band Error Handling") {
6596 DOCTEST_SUBCASE(
"Single invalid band") {
6599 radiation1.disableMessages();
6602 radiation1.addRadiationBand(
"PAR");
6603 uint source = radiation1.addCollimatedRadiationSource();
6604 radiation1.setSourceFlux(source,
"PAR", 1000.f);
6605 radiation1.updateGeometry();
6608 bool exception_thrown =
false;
6609 std::string error_message;
6611 radiation1.runBand(
"INVALID_BAND");
6612 }
catch (
const std::runtime_error &e) {
6613 exception_thrown =
true;
6614 error_message = e.what();
6615 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6616 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6617 }
catch (
const std::out_of_range &e) {
6619 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6621 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band");
6625 DOCTEST_SUBCASE(
"Mixed valid and invalid bands") {
6628 radiation2.disableMessages();
6631 radiation2.addRadiationBand(
"PAR");
6632 radiation2.addRadiationBand(
"NIR");
6633 uint source = radiation2.addCollimatedRadiationSource();
6634 radiation2.setSourceFlux(source,
"PAR", 1000.f);
6635 radiation2.setSourceFlux(source,
"NIR", 500.f);
6636 radiation2.updateGeometry();
6639 std::vector<std::string> bands = {
"PAR",
"INVALID_BAND",
"NIR"};
6640 bool exception_thrown =
false;
6641 std::string error_message;
6643 radiation2.runBand(bands);
6644 }
catch (
const std::runtime_error &e) {
6645 exception_thrown =
true;
6646 error_message = e.what();
6647 DOCTEST_CHECK(error_message.find(
"INVALID_BAND") != std::string::npos);
6648 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6649 }
catch (
const std::out_of_range &e) {
6651 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6653 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for invalid band in vector");
6657 DOCTEST_SUBCASE(
"All invalid bands") {
6660 radiation3.disableMessages();
6663 radiation3.addRadiationBand(
"PAR");
6664 uint source = radiation3.addCollimatedRadiationSource();
6665 radiation3.setSourceFlux(source,
"PAR", 1000.f);
6666 radiation3.updateGeometry();
6669 std::vector<std::string> bands = {
"INVALID1",
"INVALID2"};
6670 bool exception_thrown =
false;
6671 std::string error_message;
6673 radiation3.runBand(bands);
6674 }
catch (
const std::runtime_error &e) {
6675 exception_thrown =
true;
6676 error_message = e.what();
6678 bool found_invalid = error_message.find(
"INVALID1") != std::string::npos || error_message.find(
"INVALID2") != std::string::npos;
6679 DOCTEST_CHECK(found_invalid);
6680 DOCTEST_CHECK(error_message.find(
"not a valid band") != std::string::npos);
6681 }
catch (
const std::out_of_range &e) {
6683 DOCTEST_FAIL(
"Caught std::out_of_range instead of helios_runtime_error. This indicates the bug is present.");
6685 DOCTEST_CHECK_MESSAGE(exception_thrown,
"Expected helios_runtime_error for all invalid bands");
6689DOCTEST_TEST_CASE(
"RadiationModel - Segmentation Mask to Image Coordinate Alignment") {
6709 radiationmodel.disableMessages();
6713 cam_props.
HFOV = 60;
6717 radiationmodel.addRadiationCamera(
"test_cam", {
"SW"},
make_vec3(0, -10, 0),
6720 radiationmodel.addRadiationBand(
"SW");
6721 radiationmodel.setScatteringDepth(
"SW", 1);
6723 uint source = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 1, 0));
6724 radiationmodel.setSourceFlux(source,
"SW", 1000.f);
6726 radiationmodel.updateGeometry();
6727 radiationmodel.runBand(
"SW");
6730 std::string image_file = radiationmodel.writeCameraImage(
"test_cam", {
"SW"},
"test_alignment",
"./");
6732 radiationmodel.writeImageSegmentationMasks(
"test_cam",
"patch_id", 1u,
"test_alignment_masks.json", image_file, {},
false);
6735 std::ifstream json_file(
"test_alignment_masks.json");
6736 DOCTEST_REQUIRE(json_file.is_open());
6738 std::stringstream buffer;
6739 buffer << json_file.rdbuf();
6742 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6745 std::vector<uint> pixel_UUIDs;
6746 context.
getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs);
6749 for (
const auto &ann: coco_json[
"annotations"]) {
6750 int bbox_x = ann[
"bbox"][0];
6751 int bbox_y = ann[
"bbox"][1];
6752 int bbox_w = ann[
"bbox"][2];
6753 int bbox_h = ann[
"bbox"][3];
6756 std::vector<int> seg_coords = ann[
"segmentation"][0];
6759 int sample_x = bbox_x + bbox_w / 2;
6760 int sample_y = bbox_y + bbox_h / 2;
6761 uint sample_UUID = pixel_UUIDs.at(sample_y * 128 + sample_x) - 1;
6768 int min_x = 128, max_x = 0, min_y = 128, max_y = 0;
6769 bool found_any =
false;
6771 for (
int j = 0; j < 128; j++) {
6772 for (
int i = 0; i < 128; i++) {
6773 uint UUID = pixel_UUIDs.at(j * 128 + i) - 1;
6774 if (UUID == sample_UUID) {
6775 min_x = std::min(min_x, i);
6776 max_x = std::max(max_x, i);
6777 min_y = std::min(min_y, j);
6778 max_y = std::max(max_y, j);
6786 DOCTEST_CHECK_MESSAGE(bbox_x <= min_x + 2,
"Bbox x-min should match or slightly exceed actual pixels");
6787 DOCTEST_CHECK_MESSAGE(bbox_x + bbox_w >= max_x - 2,
"Bbox x-max should match or slightly exceed actual pixels");
6788 DOCTEST_CHECK_MESSAGE(bbox_y <= min_y + 2,
"Bbox y-min should match or slightly exceed actual pixels");
6789 DOCTEST_CHECK_MESSAGE(bbox_y + bbox_h >= max_y - 2,
"Bbox y-max should match or slightly exceed actual pixels");
6794 std::remove(
"test_alignment_masks.json");
6795 std::remove(image_file.c_str());
6798DOCTEST_TEST_CASE(
"RadiationModel - Mask Spatial Ordering Matches Image") {
6816 radiationmodel.disableMessages();
6820 cam_props.
HFOV = 70;
6824 radiationmodel.addRadiationCamera(
"test_cam", {
"SW"},
make_vec3(0, -10, 0),
make_vec3(0, 0, 0), cam_props, 1);
6826 radiationmodel.addRadiationBand(
"SW");
6827 radiationmodel.setScatteringDepth(
"SW", 1);
6829 uint source = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 1, 0));
6830 radiationmodel.setSourceFlux(source,
"SW", 1000.f);
6832 radiationmodel.updateGeometry();
6833 radiationmodel.runBand(
"SW");
6836 std::vector<uint> pixel_UUIDs_check;
6837 context.
getGlobalData(
"camera_test_cam_pixel_UUID", pixel_UUIDs_check);
6839 for (
uint uuid: pixel_UUIDs_check) {
6846 DOCTEST_INFO(
"Pixels hitting patches with patch_id: " << patch_hits);
6847 DOCTEST_REQUIRE_MESSAGE(patch_hits > 0,
"Camera should hit at least some patches");
6850 std::string image_file = radiationmodel.writeCameraImage(
"test_cam", {
"SW"},
"spatial_test",
"./");
6851 radiationmodel.writeImageSegmentationMasks(
"test_cam",
"patch_id", 1u,
"spatial_test_masks.json", image_file, {},
false);
6854 std::ifstream json_file(
"spatial_test_masks.json");
6855 DOCTEST_REQUIRE(json_file.is_open());
6857 std::stringstream buffer;
6858 buffer << json_file.rdbuf();
6861 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6864 DOCTEST_INFO(
"Number of annotations: " << coco_json[
"annotations"].size());
6867 std::map<int, int> patch_center_x;
6869 for (
const auto &ann: coco_json[
"annotations"]) {
6870 int cat_id = ann[
"category_id"];
6871 int bbox_x = ann[
"bbox"][0];
6872 int bbox_w = ann[
"bbox"][2];
6873 int center_x = bbox_x + bbox_w / 2;
6879 patch_center_x[center_x] = center_x;
6883 DOCTEST_CHECK_EQ(coco_json[
"annotations"].size(), 3);
6886 std::vector<int> centers;
6887 for (
const auto &ann: coco_json[
"annotations"]) {
6888 int bbox_x = ann[
"bbox"][0];
6889 int bbox_w = ann[
"bbox"][2];
6890 centers.push_back(bbox_x + bbox_w / 2);
6892 std::sort(centers.begin(), centers.end());
6895 if (centers.size() == 3) {
6896 DOCTEST_CHECK_MESSAGE(centers[0] < centers[1],
"Left patch should be left of center patch");
6897 DOCTEST_CHECK_MESSAGE(centers[1] < centers[2],
"Center patch should be left of right patch");
6900 int spacing1 = centers[1] - centers[0];
6901 int spacing2 = centers[2] - centers[1];
6902 DOCTEST_CHECK_MESSAGE(spacing1 > 5,
"Patches should be visibly separated in x");
6903 DOCTEST_CHECK_MESSAGE(spacing2 > 5,
"Patches should be visibly separated in x");
6904 DOCTEST_CHECK_MESSAGE(abs(spacing1 - spacing2) < spacing1 * 0.5,
"Spacing should be roughly uniform");
6908 std::remove(
"spatial_test_masks.json");
6909 std::remove(image_file.c_str());
6912DOCTEST_TEST_CASE(
"RadiationModel - Data Label Maps Match Segmentation Mask Coordinates") {
6939 radiationmodel.disableMessages();
6943 cam_props.
HFOV = 90;
6947 radiationmodel.addRadiationCamera(
"test_cam", {
"SW"},
make_vec3(0, 0, 5),
make_vec3(0, 0, 0), cam_props, 1);
6949 radiationmodel.addRadiationBand(
"SW");
6950 radiationmodel.setScatteringDepth(
"SW", 1);
6952 uint source = radiationmodel.addCollimatedRadiationSource(
make_vec3(0, 0, 1));
6953 radiationmodel.setSourceFlux(source,
"SW", 1000.f);
6955 radiationmodel.updateGeometry();
6956 radiationmodel.runBand(
"SW");
6959 std::string image_file = radiationmodel.writeCameraImage(
"test_cam", {
"SW"},
"coord_match_test",
"./");
6960 radiationmodel.writePrimitiveDataLabelMap(
"test_cam",
"patch_id",
"coord_match_primdata",
"./", 0, 0.0f);
6961 radiationmodel.writeObjectDataLabelMap(
"test_cam",
"obj_id",
"coord_match_objdata",
"./", 0, 0.0f);
6962 radiationmodel.writeImageSegmentationMasks_ObjectData(
"test_cam",
"obj_id", 1u,
"./coord_match_masks.json", image_file, {},
false);
6965 std::ifstream prim_file(
"test_cam_coord_match_primdata_00000.txt");
6966 DOCTEST_REQUIRE(prim_file.is_open());
6967 std::vector<float> prim_labels;
6969 while (prim_file >> val) {
6970 prim_labels.push_back(val);
6973 DOCTEST_REQUIRE_EQ(prim_labels.size(), 64 * 64);
6976 std::ifstream obj_file(
"test_cam_coord_match_objdata_00000.txt");
6977 DOCTEST_REQUIRE(obj_file.is_open());
6978 std::vector<float> obj_labels;
6979 while (obj_file >> val) {
6980 obj_labels.push_back(val);
6983 DOCTEST_REQUIRE_EQ(obj_labels.size(), 64 * 64);
6986 std::ifstream json_file(
"./coord_match_masks.json");
6987 DOCTEST_REQUIRE(json_file.is_open());
6988 std::stringstream buffer;
6989 buffer << json_file.rdbuf();
6991 nlohmann::json coco_json = nlohmann::json::parse(buffer.str());
6995 int total_annotations = coco_json[
"annotations"].size();
6996 DOCTEST_REQUIRE_MESSAGE(total_annotations == 3,
"Should have 3 annotations, got " << total_annotations);
7004 std::vector<std::tuple<int, int, int, int, int>> ann_data;
7005 for (
size_t idx = 0; idx < coco_json[
"annotations"].size(); idx++) {
7006 const auto &ann = coco_json[
"annotations"][idx];
7007 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)});
7009 std::sort(ann_data.begin(), ann_data.end());
7013 std::vector<uint> expected_obj_ids = {100, 200, 300};
7015 for (
size_t i = 0; i < ann_data.size(); i++) {
7016 auto [bbox_x, bbox_y, bbox_w, bbox_h, ann_idx] = ann_data[i];
7017 uint expected_obj_value = expected_obj_ids[i];
7020 int correct_value_count = 0;
7021 int total_pixels = 0;
7023 for (
int dy = 0; dy < bbox_h; dy++) {
7024 for (
int dx = 0; dx < bbox_w; dx++) {
7025 int px = bbox_x + dx;
7026 int py = bbox_y + dy;
7028 if (px < 0 || px >= 64 || py < 0 || py >= 64)
7031 float obj_value = obj_labels[py * 64 + px];
7034 if (fabs(obj_value - expected_obj_value) < 1.0f) {
7035 correct_value_count++;
7041 float match_percentage = 100.0f * correct_value_count / total_pixels;
7044 int center_x = bbox_x + bbox_w / 2;
7045 int center_y = bbox_y + bbox_h / 2;
7046 float sample_actual_value = obj_labels[center_y * 64 + center_x];
7049 DOCTEST_CHECK_MESSAGE(match_percentage > 80.0f,
"At least 80% of bbox pixels should have CORRECT data value in label map. "
7050 "If this fails, label map coordinates are flipped relative to mask. Got "
7051 << match_percentage <<
"%");
7055 std::remove(
"test_cam_coord_match_primdata_00000.txt");
7056 std::remove(
"test_cam_coord_match_objdata_00000.txt");
7057 std::remove(
"./coord_match_masks.json");
7058 std::remove(image_file.c_str());
7061DOCTEST_TEST_CASE(
"Material Backend Migration - Spectrum Interpolation Integration") {
7066 radiationmodel.disableMessages();
7069 std::vector<helios::vec2> spectrum_young = {{400, 0.1}, {500, 0.15}, {600, 0.2}, {700, 0.25}};
7070 std::vector<helios::vec2> spectrum_old = {{400, 0.5}, {500, 0.55}, {600, 0.6}, {700, 0.65}};
7080 std::vector<uint> uuids = {uuid};
7081 std::vector<std::string> spectra = {
"rho_young",
"rho_old"};
7082 std::vector<float> values = {0.0f, 10.0f};
7083 radiationmodel.interpolateSpectrumFromPrimitiveData(uuids, spectra, values,
"leaf_age",
"reflectivity_spectrum");
7086 radiationmodel.addRadiationBand(
"PAR", 400.f, 700.f);
7087 radiationmodel.disableEmission(
"PAR");
7088 radiationmodel.setScatteringDepth(
"PAR", 1);
7091 uint source = radiationmodel.addCollimatedRadiationSource();
7092 radiationmodel.setSourceFlux(source,
"PAR", 1000.f);
7095 radiationmodel.updateGeometry();
7096 radiationmodel.runBand(
"PAR");
7099 std::string assigned_spectrum;
7101 context.
getPrimitiveData(uuid,
"reflectivity_spectrum", assigned_spectrum);
7102 DOCTEST_CHECK(assigned_spectrum ==
"rho_old");
7105DOCTEST_TEST_CASE(
"Material Backend Migration - Camera Weighted Materials") {
7110 radiationmodel.disableMessages();
7113 std::vector<helios::vec2> object_spectrum = {{400, 0.1}, {500, 0.3}, {600, 0.5}, {700, 0.7}};
7117 std::vector<helios::vec2> camera_response = {{400, 0.2}, {500, 0.8}, {600, 0.8}, {700, 0.2}};
7121 std::vector<helios::vec2> source_spectrum = {{400, 0.8}, {500, 1.0}, {600, 1.0}, {700, 0.9}};
7126 context.
setPrimitiveData(uuid,
"reflectivity_spectrum", std::string(
"object_rho"));
7129 radiationmodel.addRadiationBand(
"VIS", 400.f, 700.f);
7130 radiationmodel.disableEmission(
"VIS");
7131 radiationmodel.setScatteringDepth(
"VIS", 1);
7134 uint source = radiationmodel.addCollimatedRadiationSource();
7135 radiationmodel.setSourceFlux(source,
"VIS", 1000.f);
7136 radiationmodel.setSourceSpectrum(source,
"sunlight");
7141 cam_props.
HFOV = 45.f;
7145 std::vector<std::string> band_labels = {
"VIS"};
7146 radiationmodel.addRadiationCamera(
"test_cam", band_labels, helios::make_vec3(0, -5, 0), helios::make_vec3(0, 0, 0), cam_props, 1);
7147 radiationmodel.setCameraSpectralResponse(
"test_cam",
"VIS",
"camera_green");
7150 radiationmodel.updateGeometry();
7151 radiationmodel.runBand(
"VIS");
7158 std::vector<float> camera_data;
7160 DOCTEST_CHECK(camera_data.size() == 100);
7164DOCTEST_TEST_CASE(
"RadiationModel - Specular Reflection Camera Rendering") {
7170 radiation.disableMessages();
7177 std::vector<helios::vec2> reflectivity;
7178 reflectivity.push_back(
make_vec2(400, 0.05f));
7179 reflectivity.push_back(
make_vec2(700, 0.05f));
7183 std::vector<helios::vec2> zero_transmissivity;
7184 zero_transmissivity.push_back(
make_vec2(400, 0.0f));
7185 zero_transmissivity.push_back(
make_vec2(700, 0.0f));
7186 context.
setGlobalData(
"zero_transmissivity", zero_transmissivity);
7187 context.
setPrimitiveData(UUID,
"transmissivity_spectrum",
"zero_transmissivity");
7190 radiation.addRadiationBand(
"SUN");
7191 radiation.setScatteringDepth(
"SUN", 1);
7194 uint source = radiation.addCollimatedRadiationSource(sun_direction);
7195 radiation.setSourceFlux(source,
"SUN", 1000.0f);
7196 radiation.setDirectRayCount(
"SUN", 10000);
7197 radiation.setDiffuseRayCount(
"SUN", 0);
7198 radiation.disableEmission(
"SUN");
7207 cam_props.
HFOV = 30.0f;
7208 radiation.addRadiationCamera(
"test_cam", {
"SUN"}, camera_pos, camera_lookat, cam_props, 1);
7211 std::vector<helios::vec2> camera_response;
7212 camera_response.push_back(
make_vec2(400, 1.0f));
7213 camera_response.push_back(
make_vec2(700, 1.0f));
7215 radiation.setCameraSpectralResponse(
"test_cam",
"SUN",
"camera_response");
7218 std::string output1;
7222 radiation.updateGeometry();
7223 radiation.runBand(
"SUN");
7227 std::vector<float> pixels_no_specular;
7228 context.
getGlobalData(
"camera_test_cam_SUN", pixels_no_specular);
7230 float sum_no_specular = 0.0f;
7231 for (
float p: pixels_no_specular) {
7232 sum_no_specular += p;
7234 float avg_no_specular = sum_no_specular / pixels_no_specular.size();
7237 std::string output2;
7241 radiation.updateGeometry();
7242 radiation.runBand(
"SUN");
7246 std::vector<float> pixels_with_specular;
7247 context.
getGlobalData(
"camera_test_cam_SUN", pixels_with_specular);
7249 float sum_with_specular = 0.0f;
7250 for (
float p: pixels_with_specular) {
7251 sum_with_specular += p;
7253 float avg_with_specular = sum_with_specular / pixels_with_specular.size();
7255 float difference = avg_with_specular - avg_no_specular;
7258 DOCTEST_CHECK_MESSAGE(std::abs(difference) > 1.0f,
"Specular exponent should affect camera intensity. "
7260 << avg_no_specular <<
", With specular: " << avg_with_specular <<
", Difference: " << difference);