3#define DOCTEST_CONFIG_IMPLEMENT
5#include "doctest_utils.h"
13 return helios::runDoctestWithValidation(argc, argv);
16DOCTEST_TEST_CASE(
"LiDAR Single Voxel Sphere Test") {
20 DOCTEST_CHECK_NOTHROW(pointcloud.
loadXML(
"plugins/lidar/xml/sphere.xml"));
29DOCTEST_TEST_CASE(
"LiDAR Single Voxel Isotropic Patches Test") {
34 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
37 float thetaMin = 0.0f;
38 float thetaMax =
M_PI;
40 float phiMax = 2.0f *
M_PI;
41 float exitDiameter = 0.0f;
42 float beamDivergence = 0.0f;
43 std::vector<std::string> columnFormat;
45 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
46 exitDiameter, beamDivergence, columnFormat);
47 DOCTEST_CHECK_NOTHROW(synthetic_1.
addScan(scan));
50 vec3 grid_center(0.0f, 0.0f, 0.5f);
51 vec3 grid_size(1.0f, 1.0f, 1.0f);
53 DOCTEST_CHECK_NOTHROW(synthetic_1.
addGrid(grid_center, grid_size, grid_divisions, 0));
58 std::vector<uint> UUIDs_1 = context_2.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
60 float LAD_exact = 0.f;
61 for (
uint UUID: UUIDs_1) {
66 float Gtheta_exact_numerator = 0.f;
67 float Gtheta_exact_denominator = 0.f;
68 for (
uint UUID : UUIDs_1) {
72 vec3 raydir = vertices.front() - scan_origin;
76 float normal_dot_ray = fabs(normal * raydir);
77 Gtheta_exact_numerator += normal_dot_ray * area;
78 Gtheta_exact_denominator += area;
81 float Gtheta_exact = 0.f;
82 if (Gtheta_exact_denominator > 0) {
83 Gtheta_exact = Gtheta_exact_numerator / Gtheta_exact_denominator;
92 DOCTEST_CHECK(LAD == LAD);
93 DOCTEST_CHECK(fabs(LAD - LAD_exact) / LAD_exact == doctest::Approx(0.0f).epsilon(0.02f));
97 DOCTEST_CHECK(Gtheta == Gtheta);
98 DOCTEST_CHECK(fabs(Gtheta - Gtheta_exact) / Gtheta_exact == doctest::Approx(0.0f).epsilon(0.05f));
101DOCTEST_TEST_CASE(
"LiDAR Eight Voxel Isotropic Patches Test") {
106 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
109 float thetaMin = 0.0f;
110 float thetaMax =
M_PI;
112 float phiMax = 2.0f *
M_PI;
113 float exitDiameter = 0.0f;
114 float beamDivergence = 0.0f;
115 std::vector<std::string> columnFormat;
117 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
118 exitDiameter, beamDivergence, columnFormat);
119 DOCTEST_CHECK_NOTHROW(synthetic_2.
addScan(scan));
122 vec3 grid_center(0.0f, 0.0f, 0.5f);
123 vec3 grid_size(1.0f, 1.0f, 1.0f);
125 DOCTEST_CHECK_NOTHROW(synthetic_2.
addGrid(grid_center, grid_size, grid_divisions, 0));
130 std::vector<uint> UUIDs_1 = context_2.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
132 std::vector<float> LAD_ex(8, 0);
133 for (
uint UUID: UUIDs_1) {
146 int ID = k * 4 + j * 2 + i;
149 LAD_ex.at(ID) += area / (gsize.
x * gsize.
y * gsize.
z);
152 DOCTEST_CHECK_NOTHROW(synthetic_2.
syntheticScan(&context_2));
159 RMSE += powf(LAD - LAD_ex.at(i), 2) / float(synthetic_2.
getGridCellCount());
163 DOCTEST_CHECK(RMSE == doctest::Approx(0.0f).epsilon(0.06f));
166DOCTEST_TEST_CASE(
"LiDAR Single Voxel Anisotropic Patches Test") {
171 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
174 float thetaMin = 0.0f;
175 float thetaMax =
M_PI;
177 float phiMax = 2.0f *
M_PI;
178 float exitDiameter = 0.0f;
179 float beamDivergence = 0.0f;
180 std::vector<std::string> columnFormat;
182 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
183 exitDiameter, beamDivergence, columnFormat);
184 DOCTEST_CHECK_NOTHROW(synthetic_3.
addScan(scan));
187 vec3 grid_center(0.0f, 0.0f, 0.5f);
188 vec3 grid_size(1.0f, 1.0f, 1.0f);
190 DOCTEST_CHECK_NOTHROW(synthetic_3.
addGrid(grid_center, grid_size, grid_divisions, 0));
195 std::vector<uint> UUIDs_1 = context_2.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_erectophile.xml",
true);
197 float LAD_exact = 0.f;
198 for (
uint UUID: UUIDs_1) {
203 float Gtheta_exact_numerator = 0.f;
204 float Gtheta_exact_denominator = 0.f;
205 for (
uint UUID : UUIDs_1) {
209 vec3 raydir = vertices.front() - scan_origin;
213 float normal_dot_ray = fabs(normal * raydir);
214 Gtheta_exact_numerator += normal_dot_ray * area;
215 Gtheta_exact_denominator += area;
218 float Gtheta_exact = 0.f;
219 if (Gtheta_exact_denominator > 0) {
220 Gtheta_exact = Gtheta_exact_numerator / Gtheta_exact_denominator;
223 DOCTEST_CHECK_NOTHROW(synthetic_3.
syntheticScan(&context_2));
229 DOCTEST_CHECK(LAD == LAD);
230 DOCTEST_CHECK(fabs(LAD - LAD_exact) / LAD_exact == doctest::Approx(0.0f).epsilon(0.03f));
234 DOCTEST_CHECK(Gtheta == Gtheta);
235 DOCTEST_CHECK(fabs(Gtheta - Gtheta_exact) / Gtheta_exact == doctest::Approx(0.0f).epsilon(0.05f));
238DOCTEST_TEST_CASE(
"LiDAR Synthetic Almond Tree Test") {
245 DOCTEST_CHECK_NOTHROW(synthetic_4.
loadXML(
"plugins/lidar/xml/almond.xml"));
246 DOCTEST_CHECK_NOTHROW(synthetic_4.
syntheticScan(&context_4));
255 std::vector<float> total_area;
256 total_area.resize(Ncells);
258 std::vector<float> Gtheta;
259 Gtheta.resize(Ncells);
261 std::vector<float> area_sum;
262 area_sum.resize(Ncells, 0.f);
263 std::vector<float> sin_sum;
264 sin_sum.resize(Ncells, 0.f);
265 std::vector<uint> cell_tri_count;
266 cell_tri_count.resize(Ncells, 0);
269 for (
int p = 0; p < UUIDs.size(); p++) {
271 uint UUID = UUIDs.at(p);
278 if (gridCell >= 0 && gridCell < Ncells) {
287 vec3 raydir = vertices.front() - origin;
293 Gtheta.at(gridCell) += fabs(normal * raydir) * area * fabs(sin(theta));
295 area_sum.at(gridCell) += area;
296 sin_sum.at(gridCell) += fabs(sin(theta));
297 cell_tri_count.at(gridCell) += 1;
303 for (
uint v = 0; v < Ncells; v++) {
304 if (cell_tri_count[v] > 0) {
305 Gtheta[v] *= float(cell_tri_count[v]) / (area_sum[v] * sin_sum[v]);
309 float RMSE_LAD = 0.f;
310 float bias_LAD = 0.f;
311 float RMSE_Gtheta = 0.f;
312 for (
uint i = 0; i < Ncells; i++) {
314 if (LAD == LAD && total_area.at(i) > 0 && total_area.at(i) == total_area.at(i)) {
315 RMSE_LAD += pow(LAD - total_area.at(i), 2) / float(Ncells);
316 bias_LAD += (LAD - total_area.at(i)) /
float(Ncells);
319 if (Gtheta_bar == Gtheta_bar && Gtheta.at(i) > 0 && Gtheta.at(i) == Gtheta.at(i)) {
320 RMSE_Gtheta += pow(Gtheta_bar - Gtheta.at(i), 2) / float(Ncells);
323 RMSE_LAD = sqrt(RMSE_LAD);
324 RMSE_Gtheta = sqrt(RMSE_Gtheta);
326 DOCTEST_CHECK(RMSE_LAD <= 0.35f);
327 DOCTEST_CHECK(bias_LAD <= 0.0f);
328 DOCTEST_CHECK(RMSE_Gtheta <= 0.15f);
329 DOCTEST_CHECK(RMSE_LAD != 0.f);
332DOCTEST_TEST_CASE(
"LiDAR Synthetic Scan Append/Overwrite Test") {
334 context_test.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
339 DOCTEST_CHECK_NOTHROW(synthetic_test.
loadXML(
"plugins/lidar/xml/synthetic_test.xml"));
342 DOCTEST_CHECK_NOTHROW(synthetic_test.
syntheticScan(&context_test));
344 DOCTEST_CHECK(hit_count_first > 0);
347 DOCTEST_CHECK_NOTHROW(synthetic_test.
syntheticScan(&context_test,
true));
349 DOCTEST_CHECK(hit_count_append == 2 * hit_count_first);
352 DOCTEST_CHECK_NOTHROW(synthetic_test.
syntheticScan(&context_test,
false));
354 DOCTEST_CHECK(hit_count_overwrite == hit_count_first);
358 DOCTEST_CHECK_NOTHROW(synthetic_test.
syntheticScan(&context_test,
false,
false,
false));
360 DOCTEST_CHECK(hit_count_overwrite2 == hit_count_first);
363 DOCTEST_CHECK_NOTHROW(synthetic_test.
syntheticScan(&context_test, 1, 0.0f,
true));
365 DOCTEST_CHECK(hit_count_append2 == 2 * hit_count_first);
368DOCTEST_TEST_CASE(
"LiDAR TreeQSM Loading Test") {
374 std::vector<uint> tube_UUIDs;
375 uint radial_subdivisions = 6;
376 DOCTEST_CHECK_NOTHROW(tube_UUIDs = lidar.
loadTreeQSM(&context_treeqsm,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", radial_subdivisions));
379 DOCTEST_CHECK(tube_UUIDs.size() > 0);
382 for (
uint UUID: tube_UUIDs) {
384 DOCTEST_CHECK(context_treeqsm.
getObjectType(UUID) == helios::OBJECT_TYPE_TUBE);
388 for (
uint UUID: tube_UUIDs) {
393 context_treeqsm.
getObjectData(UUID,
"branch_order", branch_order);
394 DOCTEST_CHECK(branch_order >= 0);
398 DOCTEST_CHECK(branch_id >= 0);
403 std::vector<uint> tube_UUIDs2;
404 DOCTEST_CHECK_NOTHROW(tube_UUIDs2 = lidar.
loadTreeQSM(&context_treeqsm2,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", radial_subdivisions,
""));
405 DOCTEST_CHECK(tube_UUIDs2.size() == tube_UUIDs.size());
409 DOCTEST_CHECK_THROWS(lidar.
loadTreeQSM(&context_error,
"nonexistent_file.txt", radial_subdivisions));
413 std::vector<uint> tube_UUIDs3;
414 uint different_subdivisions = 8;
415 DOCTEST_CHECK_NOTHROW(tube_UUIDs3 = lidar.
loadTreeQSM(&context_treeqsm3,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", different_subdivisions));
416 DOCTEST_CHECK(tube_UUIDs3.size() == tube_UUIDs.size());
419 for (
uint UUID: tube_UUIDs) {
421 DOCTEST_CHECK(primitive_UUIDs.size() > 0);
424 for (
uint prim_UUID: primitive_UUIDs) {
425 DOCTEST_CHECK(context_treeqsm.
getPrimitiveType(prim_UUID) == helios::PRIMITIVE_TYPE_TRIANGLE);
430DOCTEST_TEST_CASE(
"LiDAR TreeQSM Colormap Loading Test") {
436 std::vector<uint> tube_UUIDs;
437 uint radial_subdivisions = 6;
438 std::string colormap_name =
"hot";
439 DOCTEST_CHECK_NOTHROW(tube_UUIDs = lidar.
loadTreeQSMColormap(&context_colormap,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", radial_subdivisions, colormap_name));
442 DOCTEST_CHECK(tube_UUIDs.size() > 0);
445 for (
uint UUID: tube_UUIDs) {
447 DOCTEST_CHECK(context_colormap.
getObjectType(UUID) == helios::OBJECT_TYPE_TUBE);
451 for (
uint UUID: tube_UUIDs) {
456 context_colormap.
getObjectData(UUID,
"branch_order", branch_order);
457 DOCTEST_CHECK(branch_order >= 0);
460 context_colormap.
getObjectData(UUID,
"branch_id", branch_id);
461 DOCTEST_CHECK(branch_id >= 0);
466 std::vector<uint> tube_UUIDs2;
467 std::string colormap_name2 =
"cool";
468 DOCTEST_CHECK_NOTHROW(tube_UUIDs2 = lidar.
loadTreeQSMColormap(&context_colormap2,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", radial_subdivisions, colormap_name2));
469 DOCTEST_CHECK(tube_UUIDs2.size() == tube_UUIDs.size());
473 DOCTEST_CHECK_THROWS(lidar.
loadTreeQSMColormap(&context_error2,
"nonexistent_file.txt", radial_subdivisions, colormap_name));
476 for (
uint UUID: tube_UUIDs) {
478 DOCTEST_CHECK(primitive_UUIDs.size() > 0);
481 for (
uint prim_UUID: primitive_UUIDs) {
482 DOCTEST_CHECK(context_colormap.
getPrimitiveType(prim_UUID) == helios::PRIMITIVE_TYPE_TRIANGLE);
488 std::string invalid_colormap =
"invalid_colormap_name";
489 DOCTEST_CHECK_THROWS(lidar.
loadTreeQSMColormap(&context_colormap3,
"plugins/lidar/data/cylinder_tree_QSM_test.txt", radial_subdivisions, invalid_colormap));
492DOCTEST_TEST_CASE(
"LiDAR Collision Detection Integration Test") {
509 const int Npulse = 2;
513 std::vector<helios::vec3> directions = {
522 float hit_t[N * Npulse];
523 float hit_fnorm[N * Npulse];
524 int hit_ID[N * Npulse];
527 for (
size_t i = 0; i < N * Npulse; i++) {
534 std::vector<helios::vec3> ray_origins(N * Npulse, scan_origin);
537 DOCTEST_CHECK_NOTHROW(lidar.
performUnifiedRayTracing(&test_context, N, Npulse, ray_origins.data(), directions.data(), hit_t, hit_fnorm, hit_ID));
540 bool found_hit =
false;
541 bool found_miss =
false;
542 for (
size_t i = 0; i < N * Npulse; i++) {
543 if (hit_t[i] < 1000.0f) {
546 DOCTEST_CHECK(hit_t[i] > 0.0f);
547 DOCTEST_CHECK(hit_t[i] < 100.0f);
549 DOCTEST_CHECK(hit_ID[i] >= 0);
551 DOCTEST_CHECK(std::isfinite(hit_fnorm[i]));
554 DOCTEST_CHECK(hit_ID[i] == -1);
559 DOCTEST_CHECK(found_hit);
560 DOCTEST_CHECK(found_miss);
563DOCTEST_TEST_CASE(
"LiDAR Data Format Conversion Test") {
572 const int Npulse = 1;
574 std::vector<helios::vec3> test_directions = {
582 float hit_t[N * Npulse];
583 float hit_fnorm[N * Npulse];
584 int hit_ID[N * Npulse];
590 std::vector<helios::vec3> ray_origins(N * Npulse, origin);
593 DOCTEST_CHECK_NOTHROW(lidar.
performUnifiedRayTracing(&test_context, N, Npulse, ray_origins.data(), test_directions.data(), hit_t, hit_fnorm, hit_ID));
596 for (
size_t i = 0; i < N * Npulse; i++) {
597 DOCTEST_CHECK(std::isfinite(hit_t[i]));
598 DOCTEST_CHECK(std::isfinite(hit_fnorm[i]));
599 DOCTEST_CHECK(hit_ID[i] >= -1);
603DOCTEST_TEST_CASE(
"LiDAR Edge Cases and Error Conditions Test") {
624 DOCTEST_CHECK_NOTHROW(lidar.
performUnifiedRayTracing(&test_context, 1, 1, ray_origins, &direction, hit_t, hit_fnorm, hit_ID));
627 DOCTEST_CHECK(std::isfinite(hit_t[0]));
628 DOCTEST_CHECK(std::isfinite(hit_fnorm[0]));
629 DOCTEST_CHECK(hit_ID[0] >= -1);
632DOCTEST_TEST_CASE(
"LiDAR Collision Detection Memory Management Test") {
646 float hit_t, hit_fnorm;
652 DOCTEST_CHECK_NOTHROW(lidar.
performUnifiedRayTracing(&test_context, 1, 1, ray_origins, &direction, &hit_t, &hit_fnorm, &hit_ID));
655 DOCTEST_CHECK(std::isfinite(hit_t));
656 DOCTEST_CHECK(std::isfinite(hit_fnorm));
657 DOCTEST_CHECK(hit_ID >= -1);
660DOCTEST_TEST_CASE(
"LiDAR Synthetic Scan Integration Test") {
666 DOCTEST_CHECK_NOTHROW(synthetic_scan_test.
loadXML(
"plugins/lidar/xml/synthetic_test.xml"));
670 std::vector<uint> patch_UUIDs = scan_context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
671 DOCTEST_CHECK(patch_UUIDs.size() > 0);
674 DOCTEST_CHECK_NOTHROW(synthetic_scan_test.
syntheticScan(&scan_context));
678 DOCTEST_CHECK(hit_count > 0);
681 for (
uint i = 0; i < std::min(hit_count, 10u); i++) {
685 DOCTEST_CHECK(std::isfinite(hit_pos.
x));
686 DOCTEST_CHECK(std::isfinite(hit_pos.
y));
687 DOCTEST_CHECK(std::isfinite(hit_pos.
z));
690 DOCTEST_CHECK(fabs(hit_pos.
x) < 100.0f);
691 DOCTEST_CHECK(fabs(hit_pos.
y) < 100.0f);
692 DOCTEST_CHECK(fabs(hit_pos.
z) < 100.0f);
696 DOCTEST_CHECK(std::isfinite(ray_dir.
zenith));
697 DOCTEST_CHECK(std::isfinite(ray_dir.
azimuth));
698 DOCTEST_CHECK(std::isfinite(ray_dir.
radius));
708 if (cell_count > 0) {
710 DOCTEST_CHECK(std::isfinite(leaf_area_density));
711 DOCTEST_CHECK(leaf_area_density >= 0.0f);
715DOCTEST_TEST_CASE(
"LiDAR Multi-Return Equal Weighting Test") {
720 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
723 float thetaMin = 0.0f;
724 float thetaMax =
M_PI;
726 float phiMax = 2.0f *
M_PI;
727 float exitDiameter = 0.0f;
728 float beamDivergence = 0.0f;
729 std::vector<std::string> columnFormat;
731 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
732 exitDiameter, beamDivergence, columnFormat);
733 DOCTEST_CHECK_NOTHROW(lidar.
addScan(scan));
736 vec3 grid_center(0.0f, 0.0f, 0.5f);
737 vec3 grid_size(1.0f, 1.0f, 1.0f);
739 DOCTEST_CHECK_NOTHROW(lidar.
addGrid(grid_center, grid_size, grid_divisions, 0));
744 std::vector<uint> UUIDs = context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
746 float LAD_exact = 0.f;
747 for (
uint UUID: UUIDs) {
752 float Gtheta_exact_numerator = 0.f;
753 float Gtheta_exact_denominator = 0.f;
754 for (
uint UUID : UUIDs) {
758 vec3 raydir = vertices.front() - scan_origin;
762 float normal_dot_ray = fabs(normal * raydir);
763 Gtheta_exact_numerator += normal_dot_ray * area;
764 Gtheta_exact_denominator += area;
767 float Gtheta_exact = 0.f;
768 if (Gtheta_exact_denominator > 0) {
769 Gtheta_exact = Gtheta_exact_numerator / Gtheta_exact_denominator;
774 DOCTEST_CHECK_NOTHROW(lidar.
syntheticScan(&context, 2, 0.1f,
true,
true));
789 DOCTEST_CHECK_NOTHROW(lidar2.
addScan(scan));
790 DOCTEST_CHECK_NOTHROW(lidar2.
addGrid(grid_center, grid_size, grid_divisions, 0));
792 DOCTEST_CHECK_NOTHROW(lidar2.
syntheticScan(&context, 2, 0.1f,
false,
true));
801 float LAD_multi = LAD_grid_true;
802 float Gtheta_multi = G_grid_true;
805 DOCTEST_CHECK(fabs(LAD_grid_true - LAD_grid_false) < 0.01f);
806 DOCTEST_CHECK(fabs(G_grid_true - G_grid_false) < 0.01f);
811 DOCTEST_CHECK_NOTHROW(lidar_single.
addScan(scan));
812 DOCTEST_CHECK_NOTHROW(lidar_single.
addGrid(grid_center, grid_size, grid_divisions, 0));
814 DOCTEST_CHECK_NOTHROW(lidar_single.
syntheticScan(&context,
true,
true));
824 DOCTEST_CHECK(LAD_multi > LAD_exact * 0.98f);
825 DOCTEST_CHECK(LAD_multi < LAD_exact * 1.02f);
828 DOCTEST_CHECK(Gtheta_multi == Gtheta_multi);
829 DOCTEST_CHECK(fabs(Gtheta_multi - Gtheta_exact) / Gtheta_exact == doctest::Approx(0.0f).epsilon(0.05f));
832DOCTEST_TEST_CASE(
"LiDAR Eight Voxel Multi-Return Equal Weighting Test") {
840 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
843 float thetaMin = 0.0f;
844 float thetaMax =
M_PI;
846 float phiMax = 2.0f *
M_PI;
847 float exitDiameter = 0.0f;
848 float beamDivergence = 0.0004f;
849 std::vector<std::string> columnFormat;
851 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
852 exitDiameter, beamDivergence, columnFormat);
853 DOCTEST_CHECK_NOTHROW(synthetic_mr8.
addScan(scan));
856 vec3 grid_center(0.0f, 0.0f, 0.5f);
857 vec3 grid_size(1.0f, 1.0f, 1.0f);
859 DOCTEST_CHECK_NOTHROW(synthetic_mr8.
addGrid(grid_center, grid_size, grid_divisions, 0));
865 std::vector<uint> UUIDs = context_mr8.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
868 std::vector<float> LAD_ex(8, 0);
869 std::vector<float> Gtheta_ex(8, 0);
870 std::vector<float> Gtheta_ex_numerator(8, 0);
871 std::vector<float> Gtheta_ex_denominator(8, 0);
873 for (
uint UUID: UUIDs) {
877 if (v.
x > 0.f) { i = 1; }
878 if (v.
y > 0.f) { j = 1; }
879 if (v.
z > 0.5f) { k = 1; }
880 int ID = k * 4 + j * 2 + i;
883 LAD_ex.at(ID) += area / (gsize.
x * gsize.
y * gsize.
z);
888 vec3 raydir = vertices.front() - scan_origin;
892 float normal_dot_ray = fabs(normal * raydir);
893 Gtheta_ex_numerator.at(ID) += normal_dot_ray * area;
894 Gtheta_ex_denominator.at(ID) += area;
899 for (
int i = 0; i < 8; i++) {
900 if (Gtheta_ex_denominator[i] > 0) {
901 Gtheta_ex[i] = Gtheta_ex_numerator[i] / Gtheta_ex_denominator[i];
906 DOCTEST_CHECK_NOTHROW(synthetic_mr8.
syntheticScan(&context_mr8, 100, 0.1f,
true,
true));
910 uint multi_return_count = 0;
911 for (
uint i = 0; i < hits_grid_true; i++) {
913 synthetic_mr8.
getHitData(i,
"target_count") > 1) {
914 multi_return_count++;
923 std::vector<float> LAD_grid_true(8);
924 std::vector<float> G_grid_true(8);
925 for (
int i = 0; i < 8; i++) {
933 bool has_target_index =
true;
934 bool has_target_count =
true;
935 bool has_timestamp =
true;
937 for (
uint i = 0; i < hits_grid_true; i++) {
938 if (!synthetic_mr8.
doesHitDataExist(i,
"target_index")) has_target_index =
false;
939 if (!synthetic_mr8.
doesHitDataExist(i,
"target_count")) has_target_count =
false;
943 DOCTEST_CHECK(has_target_index);
944 DOCTEST_CHECK(has_target_count);
945 DOCTEST_CHECK(has_timestamp);
948 std::map<int, int> timestamp_first_return_count;
949 for (
uint i = 0; i < hits_grid_true; i++) {
952 int tidx =
static_cast<int>(synthetic_mr8.
getHitData(i,
"target_index"));
953 int tstamp =
static_cast<int>(synthetic_mr8.
getHitData(i,
"timestamp"));
955 timestamp_first_return_count[tstamp]++;
960 for (
const auto& pair : timestamp_first_return_count) {
961 DOCTEST_CHECK(pair.second == 1);
966 for (
int i = 0; i < 8; i++) {
967 float LAD = LAD_grid_true[i];
968 RMSE += powf(LAD - LAD_ex.at(i), 2) / LAD_ex.at(i) / 8.0f;
972 DOCTEST_CHECK(RMSE == doctest::Approx(0.0f).epsilon(0.1f));
975 for (
int i = 0; i < 8; i++) {
976 float G = G_grid_true[i];
977 float G_exact = Gtheta_ex[i];
980 if (LAD_grid_true[i] > 0.001f && G_exact > 0) {
981 DOCTEST_CHECK(G == G);
982 DOCTEST_CHECK(G_exact == G_exact);
983 DOCTEST_CHECK(fabs(G - G_exact) / G_exact == doctest::Approx(0.0f).epsilon(0.05f));
988DOCTEST_TEST_CASE(
"LiDAR Beam Perturbation - Single Return with Sphere") {
994 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
997 float thetaMin = 1.45f;
998 float thetaMax = 1.69f;
1000 float phiMax = 6.28f;
1001 float exitDiameter = 0.0f;
1002 float beamDivergence = 0.0f;
1003 std::vector<std::string> columnFormat;
1005 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1006 exitDiameter, beamDivergence, columnFormat);
1007 DOCTEST_CHECK_NOTHROW(lidar.
addScan(scan));
1010 vec3 grid_center(0.0f, 0.0f, 0.5f);
1011 vec3 grid_size(1.0f, 1.0f, 1.0f);
1013 DOCTEST_CHECK_NOTHROW(lidar.
addGrid(grid_center, grid_size, grid_divisions, 0));
1015 uint expected_rays = Ntheta * Nphi;
1019 vec3 sphere_center(0, 0, 0.5);
1020 float sphere_radius = 50.0f;
1024 DOCTEST_CHECK_NOTHROW(lidar.
syntheticScan(&context, 1, 0.001f));
1028 DOCTEST_CHECK(hit_count == expected_rays);
1031DOCTEST_TEST_CASE(
"LiDAR Beam Perturbation - Multi Return Zero Beam Width") {
1037 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
1040 float thetaMin = 0.0f;
1041 float thetaMax =
M_PI;
1042 float phiMin = 0.0f;
1043 float phiMax = 2.0f *
M_PI;
1044 float exitDiameter = 0.0f;
1045 float beamDivergence = 0.0f;
1046 std::vector<std::string> columnFormat;
1048 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1049 exitDiameter, beamDivergence, columnFormat);
1050 DOCTEST_CHECK_NOTHROW(lidar.
addScan(scan));
1053 vec3 grid_center(0.0f, 0.0f, 0.5f);
1054 vec3 grid_size(1.0f, 1.0f, 1.0f);
1056 DOCTEST_CHECK_NOTHROW(lidar.
addGrid(grid_center, grid_size, grid_divisions, 0));
1058 uint expected_rays = Ntheta * Nphi;
1061 vec3 sphere_center(0, 0, 0.5);
1062 float sphere_radius = 50.0f;
1066 DOCTEST_CHECK_NOTHROW(lidar.
syntheticScan(&context, 2, 0.001f));
1071 DOCTEST_CHECK(hit_count == expected_rays);
1074DOCTEST_TEST_CASE(
"LiDAR Beam Perturbation - Multi Return Miss Recording") {
1080 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
1083 float thetaMin = 0.0f;
1084 float thetaMax =
M_PI;
1085 float phiMin = 0.0f;
1086 float phiMax = 2.0f *
M_PI;
1087 float exitDiameter = 0.0f;
1088 float beamDivergence = 0.0f;
1089 std::vector<std::string> columnFormat;
1091 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1092 exitDiameter, beamDivergence, columnFormat);
1093 DOCTEST_CHECK_NOTHROW(lidar.
addScan(scan));
1096 vec3 grid_center(0.0f, 0.0f, 0.5f);
1097 vec3 grid_size(1.0f, 1.0f, 1.0f);
1099 DOCTEST_CHECK_NOTHROW(lidar.
addGrid(grid_center, grid_size, grid_divisions, 0));
1101 uint expected_rays = Ntheta * Nphi;
1108 DOCTEST_CHECK_NOTHROW(lidar.
syntheticScan(&context, 2, 0.1f,
false,
true));
1113 DOCTEST_CHECK(hit_count == expected_rays);
1116 bool all_misses =
true;
1117 for (
uint i = 0; i < hit_count; i++) {
1119 float dist = lidar.
getHitData(i,
"distance");
1120 if (dist < 1000.0f) {
1126 DOCTEST_CHECK(all_misses);
1129DOCTEST_TEST_CASE(
"LiDAR Multi-Return with Beam Spreading") {
1135 vec3 scan_origin(-5.0f, 0.0f, 0.5f);
1136 uint Ntheta = 10000;
1138 float thetaMin = 0.0f;
1139 float thetaMax =
M_PI;
1140 float phiMin = 0.0f;
1141 float phiMax = 2.0f *
M_PI;
1142 float exitDiameter = 0.0f;
1143 float beamDivergence = 0.0004f;
1144 std::vector<std::string> columnFormat;
1146 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1147 exitDiameter, beamDivergence, columnFormat);
1148 DOCTEST_CHECK_NOTHROW(lidar.
addScan(scan));
1151 vec3 grid_center(0.0f, 0.0f, 0.5f);
1152 vec3 grid_size(1.0f, 1.0f, 1.0f);
1154 DOCTEST_CHECK_NOTHROW(lidar.
addGrid(grid_center, grid_size, grid_divisions, 0));
1159 std::vector<uint> UUIDs = context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
1161 float LAD_exact = 0.f;
1162 for (
uint UUID: UUIDs) {
1167 DOCTEST_CHECK_NOTHROW(lidar.
syntheticScan(&context, 50, 0.1f,
true,
true));
1172 std::map<int, int> timestamp_first_return_count;
1173 for (
uint i = 0; i < hit_count; i++) {
1175 int tidx =
static_cast<int>(lidar.
getHitData(i,
"target_index"));
1176 int tstamp =
static_cast<int>(lidar.
getHitData(i,
"timestamp"));
1178 timestamp_first_return_count[tstamp]++;
1182 int timestamps_with_multi_first = 0;
1183 for (
auto& pair : timestamp_first_return_count) {
1184 if (pair.second > 1) timestamps_with_multi_first++;
1198 DOCTEST_CHECK(LAD > LAD_exact * 0.9f);
1199 DOCTEST_CHECK(LAD < LAD_exact * 1.1f);
1202 DOCTEST_CHECK(Gtheta > 0.45f);
1203 DOCTEST_CHECK(Gtheta < 0.55f);
1206DOCTEST_TEST_CASE(
"LiDAR Exit Diameter - Comparative Spread Test") {
1209 context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
1211 vec3 scan_origin(0, 0, 5.0f);
1217 lidar_point.
addScan(scan_point);
1218 lidar_point.
syntheticScan(&context, 50, 0.05f,
false,
false);
1224 lidar_exit.
addScan(scan_exit);
1225 lidar_exit.
syntheticScan(&context, 50, 0.05f,
false,
false);
1231 float x_min_pt = 1e6f, x_max_pt = -1e6f, y_min_pt = 1e6f, y_max_pt = -1e6f;
1234 x_min_pt = fmin(x_min_pt, pos.
x); x_max_pt = fmax(x_max_pt, pos.
x);
1235 y_min_pt = fmin(y_min_pt, pos.
y); y_max_pt = fmax(y_max_pt, pos.
y);
1239 float x_min_ex = 1e6f, x_max_ex = -1e6f, y_min_ex = 1e6f, y_max_ex = -1e6f;
1242 x_min_ex = fmin(x_min_ex, pos.
x); x_max_ex = fmax(x_max_ex, pos.
x);
1243 y_min_ex = fmin(y_min_ex, pos.
y); y_max_ex = fmax(y_max_ex, pos.
y);
1246 float extent_point = fmax(x_max_pt - x_min_pt, y_max_pt - y_min_pt);
1247 float extent_exit = fmax(x_max_ex - x_min_ex, y_max_ex - y_min_ex);
1250 DOCTEST_CHECK(extent_exit > extent_point * 1.01f);
1253DOCTEST_TEST_CASE(
"LiDAR Exit Diameter - Zero Backward Compatibility") {
1256 context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
1261 ScanMetadata scan_zero(
vec3(0, 0, 5), 100, 0,
M_PI, 100, 0, 2 *
M_PI, 0.0f, 0.0f, {});
1262 lidar_zero.
addScan(scan_zero);
1263 DOCTEST_CHECK_NOTHROW(lidar_zero.
syntheticScan(&context, 50, 0.05f,
false,
false));
1269 ScanMetadata scan_exit(
vec3(0, 0, 5), 100, 0,
M_PI, 100, 0, 2 *
M_PI, 0.01f, 0.0f, {});
1270 lidar_exit.
addScan(scan_exit);
1271 DOCTEST_CHECK_NOTHROW(lidar_exit.
syntheticScan(&context, 50, 0.05f,
false,
false));
1275DOCTEST_TEST_CASE(
"LiDAR Exit Diameter - Combined with Beam Divergence") {
1278 context.
loadXML(
"plugins/lidar/xml/leaf_cube_LAI2_lw0_01_spherical.xml",
true);
1280 vec3 scan_origin(0, 0, 5.0f);
1286 lidar_both.
addScan(scan_both);
1287 lidar_both.
syntheticScan(&context, 50, 0.05f,
false,
false);
1300 float x_min_b = 1e6f, x_max_b = -1e6f, y_min_b = 1e6f, y_max_b = -1e6f;
1303 x_min_b = fmin(x_min_b, pos.
x); x_max_b = fmax(x_max_b, pos.
x);
1304 y_min_b = fmin(y_min_b, pos.
y); y_max_b = fmax(y_max_b, pos.
y);
1308 float x_min_d = 1e6f, x_max_d = -1e6f, y_min_d = 1e6f, y_max_d = -1e6f;
1311 x_min_d = fmin(x_min_d, pos.
x); x_max_d = fmax(x_max_d, pos.
x);
1312 y_min_d = fmin(y_min_d, pos.
y); y_max_d = fmax(y_max_d, pos.
y);
1315 float spread_both = fmax(x_max_b - x_min_b, y_max_b - y_min_b);
1316 float spread_div = fmax(x_max_d - x_min_d, y_max_d - y_min_d);
1319 DOCTEST_CHECK(spread_both > spread_div * 1.01f);
1322DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Grid Position Verification") {
1328 DOCTEST_CHECK_NOTHROW(lidar.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1342 DOCTEST_CHECK(hits_before_gapfill > 0);
1345 std::vector<vec3> filled_points = lidar.
gapfillMisses(0,
false,
true);
1349 DOCTEST_CHECK(hits_after_gapfill > hits_before_gapfill);
1350 DOCTEST_CHECK(filled_points.size() > 0);
1354 std::map<std::pair<int,int>,
bool> filled_grid_positions;
1360 float theta = raydir.
zenith;
1365 int row = round((theta - theta_range.
x) / (theta_range.
y - theta_range.
x) * (Ntheta - 1));
1366 int col = round((phi - phi_range.
x) / (phi_range.
y - phi_range.
x) * (Nphi - 1));
1368 filled_grid_positions[std::make_pair(row, col)] =
true;
1372 uint filled_cells = filled_grid_positions.size();
1375 uint flag_0_count = 0;
1376 uint flag_1_count = 0;
1377 uint flag_2_count = 0;
1378 uint flag_3_count = 0;
1382 int code = (int)lidar.
getHitData(r,
"gapfillMisses_code");
1383 if (code == 0) flag_0_count++;
1384 else if (code == 1) flag_1_count++;
1385 else if (code == 2) flag_2_count++;
1386 else if (code == 3) flag_3_count++;
1390 DOCTEST_CHECK(flag_0_count == hits_before_gapfill);
1391 DOCTEST_CHECK((flag_1_count + flag_2_count + flag_3_count) == filled_points.size());
1395 DOCTEST_CHECK((flag_1_count + flag_2_count + flag_3_count) > 0);
1398DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Comparison with Record Misses") {
1406 DOCTEST_CHECK_NOTHROW(lidar1.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1407 DOCTEST_CHECK_NOTHROW(lidar2.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1420 std::vector<vec3> filled = lidar2.
gapfillMisses(0,
false,
false);
1426 DOCTEST_CHECK(hits_after_gapfill > hits_before_gapfill);
1427 DOCTEST_CHECK(filled.size() > 0);
1431 float hit_ratio = float(hits_after_gapfill) / float(hits_with_misses);
1432 DOCTEST_CHECK(hit_ratio > 0.7f);
1433 DOCTEST_CHECK(hit_ratio < 1.3f);
1437 uint real_hits_method1 = 0;
1438 uint real_hits_method2 = 0;
1444 if (dist < 1000) real_hits_method1++;
1451 if (dist < 1000) real_hits_method2++;
1455 DOCTEST_CHECK(real_hits_method1 == real_hits_method2);
1458DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Edge Cases") {
1463 bool caught_error =
false;
1466 }
catch (
const std::runtime_error& e) {
1467 caught_error =
true;
1468 std::string msg(e.what());
1469 DOCTEST_CHECK(msg.find(
"Invalid scanID") != std::string::npos);
1471 DOCTEST_CHECK(caught_error);
1474 DOCTEST_CHECK_NOTHROW(lidar.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1480 std::vector<vec3> filled;
1481 DOCTEST_CHECK_NOTHROW(filled = lidar.
gapfillMisses(0,
false,
false));
1484 if (hits_before == 0) {
1485 DOCTEST_CHECK(filled.empty());
1492 DOCTEST_CHECK_NOTHROW(lidar2.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1497 std::vector<vec3> filled_all;
1501 DOCTEST_CHECK(hits_after_all >= hits_before_all);
1504DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Grid Only Mode") {
1510 DOCTEST_CHECK_NOTHROW(lidar.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1520 std::vector<vec3> filled_grid_only = lidar.
gapfillMisses(0,
true,
false);
1526 DOCTEST_CHECK_NOTHROW(lidar2.
loadXML(
"plugins/lidar/xml/synthetic_test_8.xml"));
1529 std::vector<vec3> filled_full = lidar2.
gapfillMisses(0,
false,
false);
1533 DOCTEST_CHECK(filled_grid_only.size() <= filled_full.size());
1536DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Multi-Return Data") {
1549 vec3 scan_origin(-3, 0, 1);
1552 float thetaMin =
M_PI/3;
1553 float thetaMax = 2*
M_PI/3;
1556 float exitDiameter = 0.015;
1557 float beamDivergence = 0.002;
1559 ScanMetadata scan(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1560 exitDiameter, beamDivergence,
1561 std::vector<std::string>{
"x",
"y",
"z",
"timestamp"});
1569 bool has_multi_return =
false;
1570 for (
size_t r = 0; r < lidar.
getHitCount(); r++) {
1573 has_multi_return =
true;
1581 std::vector<vec3> filled = lidar.
gapfillMisses(scanID,
false,
true);
1585 DOCTEST_CHECK(hits_after > hits_before);
1586 DOCTEST_CHECK(filled.size() > 0);
1589 uint flag_0_count = 0;
1590 uint flag_other_count = 0;
1594 int code = (int)lidar.
getHitData(r,
"gapfillMisses_code");
1604 if (has_multi_return) {
1605 DOCTEST_CHECK(flag_0_count > 0);
1606 DOCTEST_CHECK(flag_other_count == filled.size());
1616DOCTEST_TEST_CASE(
"LiDAR Miss Gapfilling - Strict Accuracy Verification") {
1622 vec3 scan_origin(-4, 0, 1.5);
1625 float thetaMin =
M_PI/3;
1626 float thetaMax = 2*
M_PI/3;
1639 ScanMetadata scan1(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1640 0, 0, std::vector<std::string>{
"x",
"y",
"z",
"timestamp"});
1647 std::map<std::pair<int,int>,
vec3> ground_truth_positions;
1648 std::map<std::pair<int,int>,
bool> ground_truth_is_miss;
1655 float theta = raydir.
zenith;
1657 int row = round((theta - thetaMin) / (thetaMax - thetaMin) * (Ntheta - 1));
1658 int col = round((phi - phiMin) / (phiMax - phiMin) * (Nphi - 1));
1660 ground_truth_positions[std::make_pair(row, col)] = pos;
1663 float dist = sqrt(pow(pos.
x - scan_origin.
x, 2) +
1664 pow(pos.
y - scan_origin.
y, 2) +
1665 pow(pos.
z - scan_origin.
z, 2));
1666 ground_truth_is_miss[std::make_pair(row, col)] = (dist > 1000);
1670 uint expected_grid_size = Ntheta * Nphi;
1672 DOCTEST_CHECK_MESSAGE(hits_ground_truth == expected_grid_size,
1673 "record_misses should produce Ntheta×Nphi hits but got " << hits_ground_truth <<
" vs " << expected_grid_size);
1675 DOCTEST_CHECK(ground_truth_positions.size() == hits_ground_truth);
1681 ScanMetadata scan2(scan_origin, Ntheta, thetaMin, thetaMax, Nphi, phiMin, phiMax,
1682 0, 0, std::vector<std::string>{
"x",
"y",
"z",
"timestamp"});
1688 std::vector<vec3> filled = lidar2.
gapfillMisses(0,
false,
false);
1692 std::map<std::pair<int,int>,
vec3> gapfilled_positions;
1693 std::map<std::pair<int,int>,
uint> gapfilled_hit_count;
1699 float theta = raydir.
zenith;
1701 int row = round((theta - thetaMin) / (thetaMax - thetaMin) * (Ntheta - 1));
1702 int col = round((phi - phiMin) / (phiMax - phiMin) * (Nphi - 1));
1704 auto key = std::make_pair(row, col);
1705 gapfilled_positions[key] = pos;
1706 gapfilled_hit_count[key]++;
1710 uint gapfilled_duplicates = hits_after_gapfill - gapfilled_positions.size();
1711 DOCTEST_CHECK_MESSAGE(gapfilled_duplicates == 0,
1712 "CRITICAL BUG: gapfillMisses created " << gapfilled_duplicates
1713 <<
" duplicate hits at positions that already had hits!");
1720 uint ground_truth_positions_found = 0;
1721 for (
const auto& kv : ground_truth_positions) {
1722 if (gapfilled_positions.find(kv.first) != gapfilled_positions.end()) {
1723 ground_truth_positions_found++;
1727 float ground_truth_recovery = float(ground_truth_positions_found) / float(ground_truth_positions.size());
1728 DOCTEST_CHECK_MESSAGE(ground_truth_recovery > 0.95f,
1729 "Gapfilling missed positions that record_misses found: only recovered "
1730 << ground_truth_positions_found <<
" of " << ground_truth_positions.size()
1731 <<
" (" << (ground_truth_recovery*100) <<
"%)");
1734 uint invalid_positions = 0;
1735 for (
const auto& kv : gapfilled_positions) {
1736 int row = kv.first.first;
1737 int col = kv.first.second;
1739 if (row < 0 || row >= (
int)Ntheta || col < 0 || col >= (
int)Nphi) {
1740 invalid_positions++;
1743 DOCTEST_CHECK_MESSAGE(invalid_positions == 0,
1744 "Gapfilling created " << invalid_positions <<
" points at invalid grid positions");
1747 uint position_mismatches = 0;
1748 float max_position_error = 0;
1750 for (
const auto& kv : ground_truth_positions) {
1751 auto key = kv.first;
1752 if (gapfilled_positions.find(key) != gapfilled_positions.end()) {
1753 vec3 pos_gt = kv.second;
1754 vec3 pos_gf = gapfilled_positions[key];
1756 bool is_miss = ground_truth_is_miss[key];
1758 float dist = sqrt(pow(pos_gt.
x - pos_gf.
x, 2) +
1759 pow(pos_gt.
y - pos_gf.
y, 2) +
1760 pow(pos_gt.
z - pos_gf.
z, 2));
1762 if (dist > max_position_error) max_position_error = dist;
1766 if (!is_miss && dist > 0.01f) {
1767 position_mismatches++;
1768 }
else if (is_miss) {
1770 vec3 dir_gt = pos_gt - scan_origin;
1771 vec3 dir_gf = pos_gf - scan_origin;
1772 float mag_gt = sqrt(dir_gt.
x*dir_gt.
x + dir_gt.
y*dir_gt.
y + dir_gt.
z*dir_gt.
z);
1773 float mag_gf = sqrt(dir_gf.
x*dir_gf.
x + dir_gf.
y*dir_gf.
y + dir_gf.
z*dir_gf.
z);
1775 float dot = (dir_gt.
x*dir_gf.
x + dir_gt.
y*dir_gf.
y + dir_gt.
z*dir_gf.
z) / (mag_gt * mag_gf);
1776 if (dot < 0.9998f) {
1777 position_mismatches++;
1783 float position_match_rate = float(ground_truth_positions_found - position_mismatches) / float(ground_truth_positions_found);
1784 DOCTEST_CHECK_MESSAGE(position_match_rate > 0.95f,
1785 "Position accuracy too low: " << position_mismatches <<
" mismatches out of "
1786 << ground_truth_positions_found <<
" (" << (position_match_rate*100) <<
"% correct)");
1789 DOCTEST_CHECK_MESSAGE(hits_after_gapfill == hits_ground_truth,
1790 "Gapfilling should exactly match record_misses for ideal scan: "
1791 << hits_after_gapfill <<
" vs " << hits_ground_truth
1792 <<
" (difference: " << (
int)hits_after_gapfill - (
int)hits_ground_truth <<
")");