19using namespace helios;
37ScanMetadata::ScanMetadata(
const vec3 &a_origin,
uint a_Ntheta,
float a_thetaMin,
float a_thetaMax,
uint a_Nphi,
float a_phiMin,
float a_phiMax,
float a_exitDiameter,
float a_beamDivergence,
const vector<string> &a_columnFormat) {
57 float elevation = 0.5f *
M_PI - zenith;
64 float theta = direction.
zenith;
72 }
else if (row >=
Ntheta) {
77 }
else if (column >=
Nphi) {
87 hitgridcellcomputed =
false;
88 triangulationcomputed =
false;
90 collision_detection =
nullptr;
94 delete collision_detection;
98 printmessages =
false;
102 if (collision_detection ==
nullptr) {
109 const float miss_distance = 1001.0f;
111 size_t total_rays = N * Npulse;
120 std::vector<CollisionDetection::RayQuery> ray_queries;
121 ray_queries.reserve(total_rays);
123 for (
size_t i = 0; i < total_rays; i++) {
124 ray_queries.emplace_back(ray_origins[i], direction[i], miss_distance);
128 std::vector<CollisionDetection::HitResult> hit_results = collision_detection->
castRays(ray_queries);
134 size_t hit_count = 0;
135 for (
size_t i = 0; i < total_rays; i++) {
136 const auto &result = hit_results[i];
139 hit_t[i] = result.distance;
140 hit_ID[i] =
static_cast<int>(result.primitive_UUID);
144 hit_fnorm[i] = ray_dir.
x * result.normal.x + ray_dir.
y * result.normal.y + ray_dir.
z * result.normal.z;
147 hit_t[i] = miss_distance;
155 printmessages =
true;
158void LiDARcloud::validateRayDirections() {
174 if (err_theta > 1e-6 || err_phi > 1e-6) {
175 helios_runtime_error(
"ERROR (LiDARcloud::validateRayDirections): validation of ray directions failed.");
189 float epsilon = 1e-5;
192 std::cerr <<
"WARNING (LiDARcloud::addScan): Specified scan minimum zenith angle of " << newscan.
thetaMin <<
" is less than 0. Truncating to 0." << std::endl;
196 std::cerr <<
"WARNING (LiDARcloud::addScan): Specified scan minimum azimuth angle of " << newscan.
phiMin <<
" is less than 0. Truncating to 0." << std::endl;
200 std::cerr <<
"WARNING (LiDARcloud::addScan): Specified scan maximum zenith angle of " << newscan.
thetaMax <<
" is greater than pi. Setting thetaMin to 0 and truncating thetaMax to pi. Did you mistakenly use degrees instead of radians?"
206 std::cerr <<
"WARNING (LiDARcloud::addScan): Specified scan maximum azimuth angle of " << newscan.
phiMax <<
" is greater than 2pi. Did you mistakenly use degrees instead of radians?" << std::endl;
211 table.resize(newscan.
Ntheta, newscan.
Nphi, -1);
212 hit_tables.push_back(table);
214 scans.emplace_back(newscan);
216 return scans.size() - 1;
225 std::map<std::string, double> data;
241 std::map<std::string, double> data;
249 if (scanID >= scans.size()) {
250 helios_runtime_error(
"ERROR (LiDARcloud::addHitPoint): Hit point cannot be added to scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
256 HitPoint hit(scanID, xyz, direction, row_column, color, data);
266 HitPoint hit(scanID, xyz, direction, row_column, color, data);
273 if (index >= hits.size()) {
274 cerr <<
"WARNING (deleteHitPoint): Hit point #" << index <<
" cannot be deleted from the scan because there have only been " << hits.size() <<
" hit points added." << endl;
280 int scanID = hit.scanID;
283 std::swap(hits.at(index), hits.back());
292 if (scanID >= scans.size()) {
293 helios_runtime_error(
"ERROR (LiDARcloud::getScanOrigin): Cannot get origin of scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
295 return scans.at(scanID).origin;
299 if (scanID >= scans.size()) {
300 helios_runtime_error(
"ERROR (LiDARcloud::getScanSizeTheta): Cannot get theta size for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
302 return scans.at(scanID).Ntheta;
306 if (scanID >= scans.size()) {
307 helios_runtime_error(
"ERROR (LiDARcloud::getScanSizePhi): Cannot get phi size for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
309 return scans.at(scanID).Nphi;
313 if (scanID >= scans.size()) {
314 helios_runtime_error(
"ERROR (LiDARcloud::getScanRangeTheta): Cannot get theta range for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
320 if (scanID >= scans.size()) {
321 helios_runtime_error(
"ERROR (LiDARcloud::getScanRangePhi): Cannot get phi range for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
327 if (scanID >= scans.size()) {
328 helios_runtime_error(
"ERROR (LiDARcloud::getScanBeamExitDiameter): Cannot get exit diameter for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
330 return scans.at(scanID).exitDiameter;
334 if (scanID >= scans.size()) {
335 helios_runtime_error(
"ERROR (LiDARcloud::getScanBeamDivergence): Cannot get beam divergence for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
337 return scans.at(scanID).beamDivergence;
341 if (scanID >= scans.size()) {
342 helios_runtime_error(
"ERROR (LiDARcloud::getScanColumnFormat): Cannot get column format for scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
344 return scans.at(scanID).columnFormat;
349 if (index >= hits.size()) {
350 helios_runtime_error(
"ERROR (LiDARcloud::getHitXYZ): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
353 return hits.at(index).position;
358 if (index >= hits.size()) {
359 helios_runtime_error(
"ERROR (LiDARcloud::getHitRaydir): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
368 if (index >= hits.size()) {
369 helios_runtime_error(
"ERROR (LiDARcloud::setHitScalarData): Hit point index out of bounds. Tried to set hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
372 hits.at(index).data[label] = value;
377 if (index >= hits.size()) {
378 helios_runtime_error(
"ERROR (LiDARcloud::getHitData): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
381 std::map<std::string, double> hit_data = hits.at(index).data;
382 if (hit_data.find(label) == hit_data.end()) {
383 helios_runtime_error(
"ERROR (LiDARcloud::getHitData): Data value ``" + std::string(label) +
"'' does not exist.");
386 return hit_data.at(label);
391 if (index >= hits.size()) {
395 std::map<std::string, double> hit_data = hits.at(index).data;
396 if (hit_data.find(label) == hit_data.end()) {
405 if (index >= hits.size()) {
406 helios_runtime_error(
"ERROR (LiDARcloud::getHitColor): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
409 return hits.at(index).color;
414 if (index >= hits.size()) {
415 helios_runtime_error(
"ERROR (LiDARcloud::getHitColor): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
418 return hits.at(index).scanID;
423 if (scanID >= scans.size()) {
424 helios_runtime_error(
"ERROR (LiDARcloud::deleteHitPoint): Hit point cannot be deleted from scan #" + std::to_string(scanID) +
" because there have only been " + std::to_string(scans.size()) +
" scans added.");
429 helios_runtime_error(
"ERROR (LiDARcloud::getHitIndex): Column in scan data table out of range.");
432 int hit = hit_tables.at(scanID).get(row, column);
441 if (index >= hits.size()) {
442 helios_runtime_error(
"ERROR (LiDARcloud::getHitGridCell): Hit point index out of bounds. Requesting hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
443 }
else if (hits.at(index).gridcell == -2) {
444 cerr <<
"WARNING (LiDARcloud::getHitGridCell): hit grid cell for point #" << index <<
" was never set. Returning a value of `-1'. Did you forget to call calculateHitGridCell[*] first?" << endl;
448 return hits.at(index).gridcell;
453 if (index >= hits.size()) {
454 helios_runtime_error(
"ERROR (LiDARcloud::setHitGridCell): Hit point index out of bounds. Tried to set hit #" + std::to_string(index) +
" but scan only has " + std::to_string(hits.size()) +
" hits.");
457 hits.at(index).gridcell = cell;
462 for (
auto &scan: scans) {
466 for (
auto &hit: hits) {
467 hit.position = hit.position + shift;
473 if (scanID >= scans.size()) {
474 helios_runtime_error(
"ERROR (LiDARcloud::coordinateShift): Cannot apply coordinate shift to scan " + std::to_string(scanID) +
" because it does not exist.");
477 scans.at(scanID).origin = scans.at(scanID).origin + shift;
479 for (
auto &hit: hits) {
480 if (hit.scanID == scanID) {
481 hit.position = hit.position + shift;
488 for (
auto &scan: scans) {
492 for (
auto &hit: hits) {
493 hit.position =
rotatePoint(hit.position, rotation);
494 hit.direction =
cart2sphere(hit.position - scans.at(hit.scanID).origin);
500 if (scanID >= scans.size()) {
501 helios_runtime_error(
"ERROR (LiDARcloud::coordinateRotation): Cannot apply rotation to scan " + std::to_string(scanID) +
" because it does not exist.");
504 scans.at(scanID).origin =
rotatePoint(scans.at(scanID).origin, rotation);
506 for (
auto &hit: hits) {
507 if (hit.scanID == scanID) {
508 hit.position =
rotatePoint(hit.position, rotation);
509 hit.direction =
cart2sphere(hit.position - scans.at(scanID).origin);
516 for (
auto &scan: scans) {
520 for (
auto &hit: hits) {
522 hit.direction =
cart2sphere(hit.position - scans.at(hit.scanID).origin);
527 return triangles.size();
531 if (index >= triangles.size()) {
532 helios_runtime_error(
"ERROR (LiDARcloud::getTriangle): Triangle index out of bounds. Tried to get triangle #" + std::to_string(index) +
" but point cloud only has " + std::to_string(triangles.size()) +
" triangles.");
535 return triangles.at(index);
544 if (printmessages && scans.size() == 0) {
545 std::cout <<
"WARNING (LiDARcloud::addHitsToVisualizer): There are no scans in the point cloud, and thus there is no geometry to add...skipping." << std::endl;
558 if (printmessages && scans.size() == 0) {
559 std::cout <<
"WARNING (LiDARcloud::addHitsToVisualizer): There are no scans in the point cloud, and thus there is no geometry to add...skipping." << std::endl;
566 if (strcmp(color_value,
"gridcell") == 0) {
569 }
else if (strcmp(color_value,
"") != 0) {
572 float data = float(
getHitData(i, color_value));
585 if (minval != 1e9 && maxval != -1e9) {
586 cmap.setRange(minval, maxval);
591 if (strcmp(color_value,
"") == 0) {
593 }
else if (strcmp(color_value,
"gridcell") == 0) {
603 float data = float(
getHitData(i, color_value));
604 color = cmap.query(data);
616 if (printmessages && scans.size() == 0) {
617 std::cout <<
"WARNING (LiDARcloud::addGridToVisualizer): There are no scans in the point cloud, and thus there is no geometry to add...skipping." << std::endl;
634 if (minval != 1e9 && maxval != -1e9) {
635 cmap.setRange(minval, maxval);
664 float R = 2.f * sqrt(pow(boxmax.
x - boxmin.
x, 2) + pow(boxmax.
y - boxmin.
y, 2) + pow(boxmax.
z - boxmin.
z, 2));
669 if (printmessages && scans.size() == 0) {
670 std::cout <<
"WARNING (LiDARcloud::addGeometryToVisualizer): There are no scans in the point cloud, and thus there is no geometry to add...skipping." << std::endl;
674 for (
uint i = 0; i < triangles.size(); i++) {
684 if (printmessages && scans.size() == 0) {
685 std::cout <<
"WARNING (LiDARcloud::addTrianglesToVisualizer): There are no scans in the point cloud, and thus there is no geometry to add...skipping." << std::endl;
689 for (
uint i = 0; i < triangles.size(); i++) {
693 if (tri.gridcell == gridcell) {
700 if (size.
x <= 0 || size.
y <= 0 || size.
z <= 0) {
705 if (ndiv.
x <= 0 || ndiv.
y <= 0 || ndiv.
z <= 0) {
707 helios_runtime_error(
"ERROR (LiDARcloud::addGrid): The number of grid cells in each direction must be positive.");
711 vec3 gsubsize =
make_vec3(
float(size.
x) /
float(ndiv.
x),
float(size.
y) /
float(ndiv.
y),
float(size.
z) /
float(ndiv.
z));
715 for (
int k = 0; k < ndiv.
z; k++) {
716 z = -0.5f * float(size.
z) + (float(k) + 0.5f) *
float(gsubsize.
z);
717 for (
int j = 0; j < ndiv.
y; j++) {
718 y = -0.5f * float(size.
y) + (float(j) + 0.5f) *
float(gsubsize.
y);
719 for (
int i = 0; i < ndiv.
x; i++) {
720 x = -0.5f * float(size.
x) + (float(i) + 0.5f) *
float(gsubsize.
x);
727 cout <<
"Adding grid cell #" << count <<
" with center " << subcenter_rot.
x + center.
x <<
"," << subcenter_rot.
y + center.
y <<
"," << subcenter.
z + center.
z <<
" and size " << gsubsize.
x <<
" x " << gsubsize.
y <<
" x "
728 << gsubsize.
z << endl;
747 boxmin =
make_vec3(center.
x - 0.5 * size.
x, center.
y - 0.5 * size.
y, center.
z - 0.5 * size.
z);
748 boxmax =
make_vec3(center.
x + 0.5 * size.
x, center.
y + 0.5 * size.
y, center.
z + 0.5 * size.
z);
772 size_t Ngroups = reconstructed_triangles.size();
774 std::vector<helios::RGBcolor> ctable;
775 std::vector<float> clocs;
777 ctable.push_back(RGB::violet);
778 ctable.push_back(RGB::blue);
779 ctable.push_back(RGB::green);
780 ctable.push_back(RGB::yellow);
781 ctable.push_back(RGB::orange);
782 ctable.push_back(RGB::red);
784 clocs.push_back(0.f);
785 clocs.push_back(0.2f);
786 clocs.push_back(0.4f);
787 clocs.push_back(0.6f);
788 clocs.push_back(0.8f);
789 clocs.push_back(1.f);
791 Colormap colormap(ctable, clocs, 100, 0, Ngroups - 1);
793 for (
size_t g = 0; g < Ngroups; g++) {
795 float randi =
randu() * (Ngroups - 1);
796 RGBcolor color = colormap.query(randi);
798 for (
size_t t = 0; t < reconstructed_triangles.at(g).size(); t++) {
800 helios::vec3 v0 = reconstructed_triangles.at(g).at(t).vertex0;
801 helios::vec3 v1 = reconstructed_triangles.at(g).at(t).vertex1;
802 helios::vec3 v2 = reconstructed_triangles.at(g).at(t).vertex2;
810 Ngroups = reconstructed_alphamasks_center.size();
812 for (
size_t g = 0; g < Ngroups; g++) {
820 size_t Ngroups = reconstructed_trunk_triangles.size();
822 for (
size_t g = 0; g < Ngroups; g++) {
824 for (
size_t t = 0; t < reconstructed_trunk_triangles.at(g).size(); t++) {
826 helios::vec3 v0 = reconstructed_trunk_triangles.at(g).at(t).vertex0;
827 helios::vec3 v1 = reconstructed_trunk_triangles.at(g).at(t).vertex1;
828 helios::vec3 v2 = reconstructed_trunk_triangles.at(g).at(t).vertex2;
830 RGBcolor color = reconstructed_trunk_triangles.at(g).at(t).color;
839 size_t Ngroups = reconstructed_trunk_triangles.size();
841 for (
size_t g = 0; g < Ngroups; g++) {
843 for (
size_t t = 0; t < reconstructed_trunk_triangles.at(g).size(); t++) {
845 helios::vec3 v0 = reconstructed_trunk_triangles.at(g).at(t).vertex0;
846 helios::vec3 v1 = reconstructed_trunk_triangles.at(g).at(t).vertex1;
847 helios::vec3 v2 = reconstructed_trunk_triangles.at(g).at(t).vertex2;
860 std::vector<uint> UUIDs;
862 std::vector<uint> UUID_leaf_template;
863 if (subpatches.
x > 1 || subpatches.
y > 1) {
867 size_t Ngroups = reconstructed_alphamasks_center.size();
869 for (
size_t g = 0; g < Ngroups; g++) {
873 uint zone = reconstructed_alphamasks_gridcell.at(g);
875 if (reconstructed_alphamasks_size.at(g).x > 0 && reconstructed_alphamasks_size.at(g).y > 0) {
876 std::vector<uint> UUIDs_leaf;
877 if (subpatches.
x == 1 && subpatches.
y == 1) {
878 UUIDs_leaf.push_back(context->
addPatch(reconstructed_alphamasks_center.at(g), reconstructed_alphamasks_size.at(g), reconstructed_alphamasks_rotation.at(g), reconstructed_alphamasks_maskfile.c_str()));
881 context->
scalePrimitive(UUIDs_leaf,
make_vec3(reconstructed_alphamasks_size.at(g).x, reconstructed_alphamasks_size.at(g).y, 1));
882 context->
rotatePrimitive(UUIDs_leaf, -reconstructed_alphamasks_rotation.at(g).elevation,
"x");
883 context->
rotatePrimitive(UUIDs_leaf, -reconstructed_alphamasks_rotation.at(g).azimuth,
"z");
887 uint flag = reconstructed_alphamasks_direct_flag.at(g);
889 UUIDs.insert(UUIDs.end(), UUIDs_leaf.begin(), UUIDs_leaf.end());
900 std::vector<uint> UUIDs;
902 size_t Ngroups = reconstructed_triangles.size();
904 for (
size_t g = 0; g < Ngroups; g++) {
906 int leafGroup = round(context->
randu() * (Ngroups - 1));
908 for (
size_t t = 0; t < reconstructed_triangles.at(g).size(); t++) {
910 helios::vec3 v0 = reconstructed_triangles.at(g).at(t).vertex0;
911 helios::vec3 v1 = reconstructed_triangles.at(g).at(t).vertex1;
912 helios::vec3 v2 = reconstructed_triangles.at(g).at(t).vertex2;
914 RGBcolor color = reconstructed_triangles.at(g).at(t).color;
916 UUIDs.push_back(context->
addTriangle(v0, v1, v2, color));
918 uint zone = reconstructed_triangles.at(g).at(t).gridcell;
930 std::vector<uint> UUIDs;
932 size_t Ngroups = reconstructed_trunk_triangles.size();
934 for (
size_t g = 0; g < Ngroups; g++) {
936 for (
size_t t = 0; t < reconstructed_trunk_triangles.at(g).size(); t++) {
938 helios::vec3 v0 = reconstructed_trunk_triangles.at(g).at(t).vertex0;
939 helios::vec3 v1 = reconstructed_trunk_triangles.at(g).at(t).vertex1;
940 helios::vec3 v2 = reconstructed_trunk_triangles.at(g).at(t).vertex2;
942 RGBcolor color = reconstructed_trunk_triangles.at(g).at(t).color;
944 UUIDs.push_back(context->
addTriangle(v0, v1, v2, color));
953 if (printmessages && hits.size() == 0) {
954 std::cout <<
"WARNING (getHitBoundingBox): There are no hit points in the point cloud, cannot determine bounding box...skipping." << std::endl;
961 for (std::size_t i = 0; i < hits.size(); i++) {
965 if (xyz.
x < boxmin.
x) {
968 if (xyz.
x > boxmax.
x) {
971 if (xyz.
y < boxmin.
y) {
974 if (xyz.
y > boxmax.
y) {
977 if (xyz.
z < boxmin.
z) {
980 if (xyz.
z > boxmax.
z) {
989 std::cout <<
"WARNING (getGridBoundingBox): There are no grid cells in the point cloud, cannot determine bounding box...skipping." << std::endl;
996 std::size_t count = 0;
1004 vec3 xyz_min = center - 0.5f * size;
1006 vec3 xyz_max = center + 0.5f * size;
1009 if (xyz_min.
x < boxmin.
x) {
1010 boxmin.
x = xyz_min.
x;
1012 if (xyz_max.
x > boxmax.
x) {
1013 boxmax.
x = xyz_max.
x;
1015 if (xyz_min.
y < boxmin.
y) {
1016 boxmin.
y = xyz_min.
y;
1018 if (xyz_max.
y > boxmax.
y) {
1019 boxmax.
y = xyz_max.
y;
1021 if (xyz_min.
z < boxmin.
z) {
1022 boxmin.
z = xyz_min.
z;
1024 if (xyz_max.
z > boxmax.
z) {
1025 boxmax.
z = xyz_max.
z;
1032 std::size_t delete_count = 0;
1045 if (printmessages) {
1046 std::cout <<
"Removed " << delete_count <<
" hit points based on distance filter." << std::endl;
1052 std::size_t delete_count = 0;
1054 if (hits.at(r).data.find(
"reflectance") != hits.at(r).data.end()) {
1056 if (
R < minreflectance) {
1063 if (printmessages) {
1064 std::cout <<
"Removed " << delete_count <<
" hit points based on reflectance filter." << std::endl;
1070 std::size_t delete_count = 0;
1072 if (hits.at(r).data.find(scalar_field) != hits.at(r).data.end()) {
1074 if (strcmp(comparator,
"<") == 0) {
1075 if (
R < threshold) {
1079 }
else if (strcmp(comparator,
">") == 0) {
1080 if (
R > threshold) {
1084 }
else if (strcmp(comparator,
"=") == 0) {
1085 if (
R == threshold) {
1093 if (printmessages) {
1094 std::cout <<
"Removed " << delete_count <<
" hit points based on scalar filter." << std::endl;
1100 xyzFilter(xmin, xmax, ymin, ymax, zmin, zmax,
true);
1105 if (xmin > xmax || ymin > ymax || zmin > zmax) {
1106 std::cout <<
"WARNING: at least one minimum value provided is greater than one maximum value. " << std::endl;
1109 std::size_t delete_count = 0;
1111 if (deleteOutside) {
1115 if (xyz.
x < xmin || xyz.
x > xmax || xyz.
y < ymin || xyz.
y > ymax || xyz.
z < zmin || xyz.
z > zmax) {
1124 if (xyz.
x >= xmin && xyz.
x < xmax && xyz.
y > ymin && xyz.
y < ymax && xyz.
z > zmin && xyz.
z < zmax) {
1132 if (printmessages) {
1133 std::cout <<
"Removed " << delete_count <<
" hit points based on provided bounding box." << std::endl;
1145bool sortcol0(
const std::vector<double> &v0,
const std::vector<double> &v1) {
1146 return v0.at(0) < v1.at(0);
1149bool sortcol1(
const std::vector<double> &v0,
const std::vector<double> &v1) {
1150 return v0.at(1) < v1.at(1);
1155 if (printmessages) {
1156 std::cout <<
"Filtering point cloud by maximum " << scalar <<
" per pulse..." << std::flush;
1159 std::vector<std::vector<double>> timestamps;
1162 std::size_t delete_count = 0;
1166 std::cerr <<
"failed\nERROR (LiDARcloud::maxPulseFilter): Hit point " << r
1167 <<
" does not have scalar data "
1169 ", which is required for max pulse filtering. No filtering will be performed."
1173 std::cerr <<
"failed\nERROR (LiDARcloud::maxPulseFilter): Hit point " << r
1174 <<
" does not have scalar data "
1178 ". No filtering will be performed."
1185 timestamps.at(r) = v;
1188 std::sort(timestamps.begin(), timestamps.end(), sortcol0);
1190 std::vector<std::vector<double>> isort;
1191 std::vector<int> to_delete;
1192 double time_old = timestamps.at(0).at(0);
1193 for (std::size_t r = 0; r < timestamps.size(); r++) {
1195 if (timestamps.at(r).at(0) != time_old) {
1197 if (isort.size() > 1) {
1199 std::sort(isort.begin(), isort.end(), sortcol1);
1201 for (
int i = 0; i < isort.size() - 1; i++) {
1202 to_delete.push_back(
int(isort.at(i).at(2)));
1207 time_old = timestamps.at(r).at(0);
1210 isort.push_back(timestamps.at(r));
1213 std::sort(to_delete.begin(), to_delete.end());
1215 for (
int i = to_delete.size() - 1; i >= 0; i--) {
1219 if (printmessages) {
1220 std::cout <<
"done." << std::endl;
1226 if (printmessages) {
1227 std::cout <<
"Filtering point cloud by minimum " << scalar <<
" per pulse..." << std::flush;
1230 std::vector<std::vector<double>> timestamps;
1233 std::size_t delete_count = 0;
1237 std::cerr <<
"failed\nERROR (LiDARcloud::maxPulseFilter): Hit point " << r
1238 <<
" does not have scalar data "
1240 ", which is required for max pulse filtering. No filtering will be performed."
1244 std::cerr <<
"failed\nERROR (LiDARcloud::maxPulseFilter): Hit point " << r
1245 <<
" does not have scalar data "
1249 ". No filtering will be performed."
1256 timestamps.at(r) = v;
1259 std::sort(timestamps.begin(), timestamps.end(), sortcol0);
1261 std::vector<std::vector<double>> isort;
1262 std::vector<int> to_delete;
1263 double time_old = timestamps.at(0).at(0);
1264 for (std::size_t r = 0; r < timestamps.size(); r++) {
1266 if (timestamps.at(r).at(0) != time_old) {
1268 if (isort.size() > 1) {
1270 std::sort(isort.begin(), isort.end(), sortcol1);
1272 for (
int i = 1; i < isort.size(); i++) {
1273 to_delete.push_back(
int(isort.at(i).at(2)));
1278 time_old = timestamps.at(r).at(0);
1281 isort.push_back(timestamps.at(r));
1284 std::sort(to_delete.begin(), to_delete.end());
1286 for (
int i = to_delete.size() - 1; i >= 0; i--) {
1290 if (printmessages) {
1291 std::cout <<
"done." << std::endl;
1297 if (printmessages) {
1298 std::cout <<
"Filtering point cloud to only first hits per pulse..." << std::flush;
1301 std::vector<float> target_index;
1305 for (std::size_t r = 0; r < target_index.size(); r++) {
1308 std::cerr <<
"failed\nERROR (LiDARcloud::firstHitFilter): Hit point " << r
1309 <<
" does not have scalar data "
1311 ". No filtering will be performed."
1316 target_index.at(r) =
getHitData(r,
"target_index");
1318 if (target_index.at(r) == 0) {
1323 for (
int r = target_index.size() - 1; r >= 0; r--) {
1325 if (target_index.at(r) != min_tindex) {
1330 if (printmessages) {
1331 std::cout <<
"done." << std::endl;
1337 if (printmessages) {
1338 std::cout <<
"Filtering point cloud to only last hits per pulse..." << std::flush;
1341 std::vector<float> target_index;
1345 for (std::size_t r = 0; r < target_index.size(); r++) {
1348 std::cout <<
"failed\n";
1349 std::cerr <<
"ERROR (LiDARcloud::lastHitFilter): Hit point " << r
1350 <<
" does not have scalar data "
1352 ". No filtering will be performed."
1356 std::cout <<
"failed\n";
1357 std::cerr <<
"ERROR (LiDARcloud::lastHitFilter): Hit point " << r
1358 <<
" does not have scalar data "
1360 ". No filtering will be performed."
1365 target_index.at(r) =
getHitData(r,
"target_index");
1367 if (target_index.at(r) == 0) {
1372 for (
int r = target_index.size() - 1; r >= 0; r--) {
1374 float target_count =
getHitData(r,
"target_count");
1376 if (target_index.at(r) == target_count - 1 + min_tindex) {
1381 if (printmessages) {
1382 std::cout <<
"done." << std::endl;
1387 std::vector<helios::vec3> xyz_filled;
1389 std::vector<helios::vec3> filled_this_scan =
gapfillMisses(scanID,
false,
false);
1390 xyz_filled.insert(xyz_filled.end(), filled_this_scan.begin(), filled_this_scan.end());
1403 helios_runtime_error(
"ERROR (LiDARcloud::gapfillMisses): Invalid scanID " + std::to_string(scanID) +
1404 ". Only " + std::to_string(
getScanCount()) +
" scans exist.");
1407 if (printmessages) {
1408 std::cout <<
"Gap filling complete misses in scan " << scanID <<
"..." << std::flush;
1411 float gap_distance = 20000;
1414 std::vector<helios::vec3> xyz_filled;
1418 std::vector<std::vector<double>> hit_table;
1431 " is missing required 'timestamp' data. Cannot perform gap filling.");
1434 double timestamp =
getHitData(r,
"timestamp");
1435 std::vector<double> data;
1437 data.at(0) = float(r);
1438 data.at(1) = timestamp;
1439 data.at(2) = raydir.
zenith;
1441 hit_table.push_back(data);
1446 if (hit_table.empty()) {
1447 if (printmessages) {
1448 std::cout <<
"scan has no hits. Skipping gap fill." << std::endl;
1456 std::sort(hit_table.begin(), hit_table.end(), sortcol1);
1459 for (
size_t r = 0; r < hit_table.size() - 1; r++) {
1463 if (
getHitData(hit_table.at(r).at(0),
"target_index") == 0) {
1471 int ndup_target = 0;
1473 std::vector<std::vector<double>> hit_table_semiclean;
1474 for (
size_t r = 0; r < hit_table.size() - 1; r++) {
1478 if (
getHitData(hit_table.at(r).at(0),
"target_index") > min_tindex) {
1484 hit_table_semiclean.push_back(hit_table.at(r));
1487 if (!hit_table.empty()) {
1488 hit_table_semiclean.push_back(hit_table.back());
1493 std::vector<double> dt_semiclean;
1494 dt_semiclean.resize(hit_table_semiclean.size());
1495 for (
size_t r = 0; r < hit_table_semiclean.size() - 1; r++) {
1497 dt_semiclean.at(r) = hit_table_semiclean.at(r + 1).at(1) - hit_table_semiclean.at(r).at(1);
1499 hit_table_semiclean.at(r).at(0) = r;
1506 std::vector<std::vector<double>> hit_table_clean;
1507 for (
size_t r = 0; r < hit_table_semiclean.size() - 1; r++) {
1511 if (dt_semiclean.at(r) == 0) {
1516 hit_table_clean.push_back(hit_table_semiclean.at(r));
1521 std::vector<double> dt_clean;
1522 std::vector<float> dtheta_clean;
1523 dt_clean.resize(hit_table_clean.size());
1524 dtheta_clean.resize(hit_table_clean.size());
1526 double dt_clean_min = 1e6;
1527 for (
size_t r = 0; r < hit_table_clean.size() - 1; r++) {
1529 dt_clean.at(r) = hit_table_clean.at(r + 1).at(1) - hit_table_clean.at(r).at(1);
1530 dtheta_clean.at(r) = hit_table_clean.at(r + 1).at(2) - hit_table_clean.at(r).at(2);
1532 hit_table_clean.at(r).at(0) = r;
1534 if (dt_clean.at(r) < dt_clean_min) {
1535 dt_clean_min = dt_clean.at(r);
1541 std::vector<std::vector<std::vector<double>>> hit_table2D;
1544 hit_table2D.resize(1);
1545 for (
size_t r = 0; r < hit_table_clean.size() - 1; r++) {
1547 hit_table2D.at(column).push_back(hit_table_clean.at(r));
1551 if (dtheta_clean.at(r) < -0.1745329f) {
1553 hit_table2D.resize(column + 1);
1564 float dtheta_avg = 0;
1567 for (
int j = 0; j < hit_table2D.size(); j++) {
1568 for (
int i = 0; i < hit_table2D.at(j).size(); i++) {
1569 int r = int(hit_table2D.at(j).at(i).at(0));
1570 if (dt_clean.at(r) >= dt_clean_min && dt_clean.at(r) < 1.5 * dt_clean_min) {
1571 dt_avg += dt_clean.at(r);
1575 dtheta_avg += dtheta_clean.at(r);
1581 if (dt_sum == 0 || dtheta_sum == 0) {
1582 if (printmessages) {
1583 std::cout <<
"insufficient valid hit pairs. Skipping gap fill." << std::endl;
1588 dt_avg = dt_avg / float(dt_sum);
1590 dtheta_avg = dtheta_avg / float(dtheta_sum);
1596 std::set<std::pair<int, int>> filled_positions;
1602 helios::int2 rc = scans.at(scanID).direction2rc(raydir);
1603 filled_positions.insert(std::make_pair(rc.
x, rc.
y));
1608 for (
int j = 0; j < hit_table2D.size(); j++) {
1610 if (hit_table2D.at(j).size() > 0) {
1611 for (
int i = 0; i < hit_table2D.at(j).size() - 1; i++) {
1613 double dt = hit_table2D.at(j).at(i + 1).at(1) - hit_table2D.at(j).at(i).at(1);
1615 if (dt > 1.5f * dt_clean_min) {
1618 int Ngap = round(dt / dt_avg) - 1;
1621 for (
int k = 1; k <= Ngap; k++) {
1623 float timestep = hit_table2D.at(j).at(i).at(1) + dt_avg * float(k);
1626 float theta = hit_table2D.at(j).at(i).at(2) + (hit_table2D.at(j).at(i + 1).at(2) - hit_table2D.at(j).at(i).at(2)) *
float(k) / float(Ngap + 1);
1627 float phi = hit_table2D.at(j).at(i).at(3) + (hit_table2D.at(j).at(i + 1).at(3) - hit_table2D.at(j).at(i).at(3)) *
float(k) / float(Ngap + 1);
1629 if (phi > 2.f *
M_PI) {
1630 phi = phi - 2.f *
M_PI;
1631 }
else if (phi < 0.f) {
1632 phi = phi + 2.f *
M_PI;
1637 helios::int2 rc = scans.at(scanID).direction2rc(dir_to_check);
1638 auto grid_key = std::make_pair(rc.
x, rc.
y);
1641 if (filled_positions.find(grid_key) == filled_positions.end()) {
1645 xyz_filled.push_back(xyz);
1647 std::map<std::string, double> data;
1648 data.insert(std::pair<std::string, double>(
"timestamp", timestep));
1649 data.insert(std::pair<std::string, double>(
"target_index", min_tindex));
1650 data.insert(std::pair<std::string, double>(
"nRaysHit", 500));
1653 data.insert(std::pair<std::string, double>(
"gapfillMisses_code", 1.0));
1656 filled_positions.insert(grid_key);
1663 uint npointsfilled = xyz_filled.size();
1666 float grid_dtheta = (theta_range.
y - theta_range.
x) /
float(scans.at(scanID).Ntheta - 1);
1667 float grid_dphi = (scans.at(scanID).phiMax - scans.at(scanID).phiMin) /
float(scans.at(scanID).Nphi - 1);
1669 if (gapfill_grid_only ==
true) {
1672 std::vector<helios::vec3> grid_vertices;
1675 grid_vertices.push_back(boxmin);
1676 grid_vertices.push_back(boxmax);
1684 float max_theta = 0;
1685 float min_theta =
M_PI;
1687 float min_phi = 2 *
M_PI;
1688 for (
uint gg = 0; gg < grid_vertices.size(); gg++) {
1699 if (sc.
zenith < min_theta) {
1703 if (sc.
zenith > max_theta) {
1709 if (min_theta < theta_range.
x) {
1710 min_theta = theta_range.
x;
1713 if (max_theta > theta_range.
y) {
1714 max_theta = theta_range.
y;
1721 for (
int j = 0; j < hit_table2D.size(); j++) {
1723 if (hit_table2D.at(j).size() > 0) {
1726 if (hit_table2D.at(j).front().at(2) > theta_range.
x) {
1728 float dtheta = dtheta_avg;
1729 float theta = hit_table2D.at(j).at(0).at(2) - dtheta;
1731 float phi = hit_table2D.at(j).at(0).at(3);
1732 float timestep = hit_table2D.at(j).at(0).at(1) - dt_avg;
1737 while (theta > theta_range.
x) {
1741 helios::int2 rc = scans.at(scanID).direction2rc(dir_to_check);
1744 if (rc.
x >= 0 && rc.
x < (
int)scans.at(scanID).Ntheta &&
1745 rc.
y >= 0 && rc.
y < (
int)scans.at(scanID).Nphi) {
1748 auto grid_key = std::make_pair(rc.
x, rc.
y);
1749 if (filled_positions.find(grid_key) == filled_positions.end()) {
1753 xyz_filled.push_back(xyz);
1755 std::map<std::string, double> data;
1756 data.insert(std::pair<std::string, double>(
"timestamp", timestep));
1757 data.insert(std::pair<std::string, double>(
"target_index", min_tindex));
1758 data.insert(std::pair<std::string, double>(
"nRaysHit", 500));
1761 data.insert(std::pair<std::string, double>(
"gapfillMisses_code", 3.0));
1765 filled_positions.insert(grid_key);
1769 theta = theta - dtheta;
1770 timestep = timestep - dt_avg;
1775 if (hit_table2D.at(j).back().at(2) < theta_range.
y) {
1777 int sz = hit_table2D.at(j).size();
1779 float dtheta = dtheta_avg;
1780 float theta = hit_table2D.at(j).at(sz - 1).at(2) + dtheta;
1781 float phi = hit_table2D.at(j).at(sz - 1).at(3);
1782 float timestep = hit_table2D.at(j).at(sz - 1).at(1) + dt_avg;
1783 while (theta < theta_range.
y) {
1787 helios::int2 rc = scans.at(scanID).direction2rc(dir_to_check);
1790 if (rc.
x >= 0 && rc.
x < (
int)scans.at(scanID).Ntheta &&
1791 rc.
y >= 0 && rc.
y < (
int)scans.at(scanID).Nphi) {
1794 auto grid_key = std::make_pair(rc.
x, rc.
y);
1795 if (filled_positions.find(grid_key) == filled_positions.end()) {
1799 xyz_filled.push_back(xyz);
1801 std::map<std::string, double> data;
1802 data.insert(std::pair<std::string, double>(
"timestamp", timestep));
1803 data.insert(std::pair<std::string, double>(
"target_index", min_tindex));
1804 data.insert(std::pair<std::string, double>(
"nRaysHit", 500));
1807 data.insert(std::pair<std::string, double>(
"gapfillMisses_code", 2.0));
1811 filled_positions.insert(grid_key);
1815 theta = theta + dtheta;
1816 timestep = timestep + dt_avg;
1822 uint npointsextrapolated = xyz_filled.size() - npointsfilled;
1824 if (printmessages) {
1825 std::cout <<
"filled " << xyz_filled.size() <<
" points (" << npointsfilled <<
" interior, "
1826 << npointsextrapolated <<
" edge)." << std::endl;
1827 std::cout <<
" Processed " << hit_table2D.size() <<
" scan columns" << std::endl;
1835 cout <<
"WARNING (triangulateHitPoints): No scans have been added to the point cloud. Skipping triangulation..." << endl;
1838 cout <<
"WARNING (triangulateHitPoints): No hit points have been added to the point cloud. Skipping triangulation..." << endl;
1842 if (!hitgridcellcomputed) {
1849 bool use_adaptive_threshold = isMultiReturnData();
1850 float adaptive_sep_threshold = 0.0f;
1852 if (use_adaptive_threshold) {
1853 if (printmessages) {
1854 std::cout <<
"Multi-return data detected - calculating adaptive separation ratio threshold..." << std::endl;
1858 std::vector<float> all_separation_ratios;
1861 std::vector<int> Delaunay_inds_pass1;
1862 std::vector<Shx> pts_pass1, pts_copy_pass1;
1863 int count_pass1 = 0;
1868 if (use_adaptive_threshold) {
1879 pt.id = count_pass1;
1882 pts_pass1.push_back(pt);
1883 Delaunay_inds_pass1.push_back(r);
1888 if (pts_pass1.size() == 0)
continue;
1891 float h[2] = {0, 0};
1892 for (
int r = 0; r < pts_pass1.size(); r++) {
1893 if (pts_pass1.at(r).c < 0.5 *
M_PI) h[0] += 1.f;
1894 else if (pts_pass1.at(r).c > 1.5 *
M_PI) h[1] += 1.f;
1896 h[0] /= float(pts_pass1.size());
1897 h[1] /= float(pts_pass1.size());
1898 if (h[0] + h[1] > 0.4) {
1899 for (
int r = 0; r < pts_pass1.size(); r++) {
1900 pts_pass1.at(r).c +=
M_PI;
1901 if (pts_pass1.at(r).c > 2.f *
M_PI) pts_pass1.at(r).c -= 2.f *
M_PI;
1905 std::vector<int> dupes_pass1;
1906 de_duplicate(pts_pass1, dupes_pass1);
1908 std::vector<Triad> triads_pass1;
1909 int success = 0, Ntries = 0;
1910 while (success != 1 && Ntries < 3) {
1912 success = s_hull_pro(pts_pass1, triads_pass1);
1914 for (
int r = 0; r < pts_pass1.size(); r++) {
1915 pts_pass1.at(r).c += 0.25 *
M_PI;
1916 if (pts_pass1.at(r).c > 2.f *
M_PI) pts_pass1.at(r).c -= 2.f *
M_PI;
1921 if (success != 1)
continue;
1924 for (
int t = 0; t < triads_pass1.size(); t++) {
1925 int ID0 = Delaunay_inds_pass1.at(triads_pass1.at(t).a);
1926 int ID1 = Delaunay_inds_pass1.at(triads_pass1.at(t).b);
1927 int ID2 = Delaunay_inds_pass1.at(triads_pass1.at(t).c);
1936 float L0 = (v0 - v1).magnitude();
1937 float L1 = (v0 - v2).magnitude();
1938 float L2 = (v1 - v2).magnitude();
1941 if (L0 > Lmax || L1 > Lmax || L2 > Lmax) {
1949 float ratio01 = L0 / (ang01 + 1e-6);
1950 float ratio02 = L1 / (ang02 + 1e-6);
1951 float ratio12 = L2 / (ang12 + 1e-6);
1952 float max_sep_ratio =
max(
max(ratio01, ratio02), ratio12);
1954 all_separation_ratios.push_back(max_sep_ratio);
1959 if (!all_separation_ratios.empty()) {
1960 std::sort(all_separation_ratios.begin(), all_separation_ratios.end());
1961 size_t idx_25 = all_separation_ratios.size() / 4;
1962 float percentile_25 = all_separation_ratios[idx_25];
1963 adaptive_sep_threshold = 8.5f * percentile_25;
1965 if (printmessages) {
1966 std::cout <<
" 25th percentile separation ratio: " << percentile_25 << std::endl;
1967 std::cout <<
" Adaptive threshold: " << adaptive_sep_threshold << std::endl;
1975 std::vector<int> Delaunay_inds;
1977 std::vector<Shx> pts, pts_copy;
1984 if (use_adaptive_threshold) {
2002 Delaunay_inds.push_back(r);
2008 if (pts.size() == 0) {
2009 if (printmessages) {
2010 std::cout <<
"Scan " << s <<
" contains no triangles. Skipping this scan..." << std::endl;
2015 float h[2] = {0, 0};
2016 for (
int r = 0; r < pts.size(); r++) {
2017 if (pts.at(r).c < 0.5 *
M_PI) {
2019 }
else if (pts.at(r).c > 1.5 *
M_PI) {
2023 h[0] /= float(pts.size());
2024 h[1] /= float(pts.size());
2025 if (h[0] + h[1] > 0.4) {
2026 if (printmessages) {
2027 std::cout <<
"Shifting scan " << s << std::endl;
2029 for (
int r = 0; r < pts.size(); r++) {
2030 pts.at(r).c +=
M_PI;
2031 if (pts.at(r).c > 2.f *
M_PI) {
2032 pts.at(r).c -= 2.f *
M_PI;
2040 const float COORD_SNAP_PRECISION = 1e-6f;
2041 for (
auto& pt : pts) {
2042 pt.r = std::round(pt.r / COORD_SNAP_PRECISION) * COORD_SNAP_PRECISION;
2043 pt.c = std::round(pt.c / COORD_SNAP_PRECISION) * COORD_SNAP_PRECISION;
2046 std::vector<int> dupes;
2047 int nx = de_duplicate(pts, dupes);
2050 std::vector<Triad> triads;
2052 if (printmessages) {
2053 std::cout <<
"starting triangulation for scan " << s <<
"..." << std::endl;
2058 while (success != 1 && Ntries < 3) {
2061 success = s_hull_pro(pts, triads);
2066 if (printmessages) {
2067 std::cout <<
"Shifting scan " << s <<
" (try " << Ntries <<
" of 3)" << std::endl;
2069 for (
int r = 0; r < pts.size(); r++) {
2070 pts.at(r).c += 0.25 *
M_PI;
2071 if (pts.at(r).c > 2.f *
M_PI) {
2072 pts.at(r).c -= 2.f *
M_PI;
2079 if (printmessages) {
2080 std::cout <<
"FAILED: could not triangulate scan " << s <<
". Skipping this scan." << std::endl;
2083 }
else if (printmessages) {
2084 std::cout <<
"finished triangulation" << std::endl;
2087 for (
int t = 0; t < triads.size(); t++) {
2089 int ID0 = Delaunay_inds.at(triads.at(t).a);
2090 int ID1 = Delaunay_inds.at(triads.at(t).b);
2091 int ID2 = Delaunay_inds.at(triads.at(t).c);
2103 v = vertex0 - vertex1;
2105 v = vertex0 - vertex2;
2107 v = vertex1 - vertex2;
2110 float aspect_ratio =
max(
max(L0, L1), L2) /
min(
min(L0, L1), L2);
2113 bool filtered = (L0 > Lmax || L1 > Lmax || L2 > Lmax);
2115 if (use_adaptive_threshold) {
2121 float ratio01 = L0 / (ang01 + 1e-6);
2122 float ratio02 = L1 / (ang02 + 1e-6);
2123 float ratio12 = L2 / (ang12 + 1e-6);
2124 float max_sep_ratio =
max(
max(ratio01, ratio02), ratio12);
2126 filtered = filtered || (max_sep_ratio > adaptive_sep_threshold) || (aspect_ratio > max_aspect_ratio);
2129 filtered = filtered || (aspect_ratio > max_aspect_ratio);
2138 if (printmessages && gridcell == -2) {
2139 cout <<
"WARNING (triangulateHitPoints): You typically want to define the hit grid cell for all hit points before performing triangulation." << endl;
2143 color.
r = (hits.at(ID0).color.r + hits.at(ID1).color.r + hits.at(ID2).color.r) / 3.f;
2144 color.
g = (hits.at(ID0).color.g + hits.at(ID1).color.g + hits.at(ID2).color.g) / 3.f;
2145 color.
b = (hits.at(ID0).color.b + hits.at(ID1).color.b + hits.at(ID2).color.b) / 3.f;
2147 Triangulation tri(s, vertex0, vertex1, vertex2, ID0, ID1, ID2, color, gridcell);
2149 if (tri.area != tri.area) {
2153 triangles.push_back(tri);
2159 triangulationcomputed =
true;
2161 if (printmessages) {
2163 cout <<
"\rTriangulating...formed " << Ntriangles <<
" total triangles." << endl;
2170 cout <<
"WARNING (triangulateHitPoints): No scans have been added to the point cloud. Skipping triangulation..." << endl;
2173 cout <<
"WARNING (triangulateHitPoints): No hit points have been added to the point cloud. Skipping triangulation..." << endl;
2177 if (!hitgridcellcomputed) {
2184 bool use_adaptive_threshold = isMultiReturnData();
2185 float adaptive_sep_threshold = 0.0f;
2187 if (use_adaptive_threshold) {
2188 if (printmessages) {
2189 std::cout <<
"Multi-return data detected - calculating adaptive separation ratio threshold..." << std::endl;
2193 std::vector<float> all_separation_ratios;
2196 std::vector<int> Delaunay_inds_pass1;
2197 std::vector<Shx> pts_pass1, pts_copy_pass1;
2198 int count_pass1 = 0;
2207 pt.id = count_pass1;
2210 pts_pass1.push_back(pt);
2211 Delaunay_inds_pass1.push_back(r);
2216 if (pts_pass1.size() == 0)
continue;
2219 float h[2] = {0, 0};
2220 for (
int r = 0; r < pts_pass1.size(); r++) {
2221 if (pts_pass1.at(r).c < 0.5 *
M_PI) h[0] += 1.f;
2222 else if (pts_pass1.at(r).c > 1.5 *
M_PI) h[1] += 1.f;
2224 h[0] /= float(pts_pass1.size());
2225 h[1] /= float(pts_pass1.size());
2226 if (h[0] + h[1] > 0.4) {
2227 for (
int r = 0; r < pts_pass1.size(); r++) {
2228 pts_pass1.at(r).c +=
M_PI;
2229 if (pts_pass1.at(r).c > 2.f *
M_PI) pts_pass1.at(r).c -= 2.f *
M_PI;
2233 std::vector<int> dupes_pass1;
2234 de_duplicate(pts_pass1, dupes_pass1);
2236 std::vector<Triad> triads_pass1;
2237 int success = 0, Ntries = 0;
2238 while (success != 1 && Ntries < 3) {
2240 success = s_hull_pro(pts_pass1, triads_pass1);
2242 for (
int r = 0; r < pts_pass1.size(); r++) {
2243 pts_pass1.at(r).c += 0.25 *
M_PI;
2244 if (pts_pass1.at(r).c > 2.f *
M_PI) pts_pass1.at(r).c -= 2.f *
M_PI;
2249 if (success != 1)
continue;
2252 for (
int t = 0; t < triads_pass1.size(); t++) {
2253 int ID0 = Delaunay_inds_pass1.at(triads_pass1.at(t).a);
2254 int ID1 = Delaunay_inds_pass1.at(triads_pass1.at(t).b);
2255 int ID2 = Delaunay_inds_pass1.at(triads_pass1.at(t).c);
2264 float L0 = (v0 - v1).magnitude();
2265 float L1 = (v0 - v2).magnitude();
2266 float L2 = (v1 - v2).magnitude();
2269 if (L0 > Lmax || L1 > Lmax || L2 > Lmax) {
2277 float ratio01 = L0 / (ang01 + 1e-6);
2278 float ratio02 = L1 / (ang02 + 1e-6);
2279 float ratio12 = L2 / (ang12 + 1e-6);
2280 float max_sep_ratio =
max(
max(ratio01, ratio02), ratio12);
2282 all_separation_ratios.push_back(max_sep_ratio);
2287 if (!all_separation_ratios.empty()) {
2288 std::sort(all_separation_ratios.begin(), all_separation_ratios.end());
2289 size_t idx_25 = all_separation_ratios.size() / 4;
2290 float percentile_25 = all_separation_ratios[idx_25];
2291 adaptive_sep_threshold = 8.5f * percentile_25;
2293 if (printmessages) {
2294 std::cout <<
" 25th percentile separation ratio: " << percentile_25 << std::endl;
2295 std::cout <<
" Adaptive threshold: " << adaptive_sep_threshold << std::endl;
2303 std::vector<int> Delaunay_inds;
2305 std::vector<Shx> pts, pts_copy;
2307 std::size_t delete_count = 0;
2315 if (hits.at(r).data.find(scalar_field) != hits.at(r).data.end()) {
2317 if (strcmp(comparator,
"<") == 0) {
2318 if (
R < threshold) {
2322 }
else if (strcmp(comparator,
">") == 0) {
2323 if (
R > threshold) {
2327 }
else if (strcmp(comparator,
"=") == 0) {
2328 if (
R == threshold) {
2348 Delaunay_inds.push_back(r);
2354 if (printmessages) {
2355 std::cout <<
"Scan " << s <<
" triangulation: " << count <<
" points used, "
2356 << delete_count <<
" points filtered out";
2357 if (strlen(scalar_field) > 0) {
2358 std::cout <<
" (filter: " << scalar_field <<
" " << comparator <<
" " << threshold <<
")";
2360 std::cout << std::endl;
2363 if (pts.size() == 0) {
2364 if (printmessages) {
2365 std::cout <<
"Scan " << s <<
" contains no triangles. Skipping this scan..." << std::endl;
2370 float h[2] = {0, 0};
2371 for (
int r = 0; r < pts.size(); r++) {
2372 if (pts.at(r).c < 0.5 *
M_PI) {
2374 }
else if (pts.at(r).c > 1.5 *
M_PI) {
2378 h[0] /= float(pts.size());
2379 h[1] /= float(pts.size());
2380 if (h[0] + h[1] > 0.4) {
2381 if (printmessages) {
2382 std::cout <<
"Shifting scan " << s << std::endl;
2384 for (
int r = 0; r < pts.size(); r++) {
2385 pts.at(r).c +=
M_PI;
2386 if (pts.at(r).c > 2.f *
M_PI) {
2387 pts.at(r).c -= 2.f *
M_PI;
2395 const float COORD_SNAP_PRECISION = 1e-6f;
2396 for (
auto& pt : pts) {
2397 pt.r = std::round(pt.r / COORD_SNAP_PRECISION) * COORD_SNAP_PRECISION;
2398 pt.c = std::round(pt.c / COORD_SNAP_PRECISION) * COORD_SNAP_PRECISION;
2401 std::vector<int> dupes;
2402 int nx = de_duplicate(pts, dupes);
2405 std::vector<Triad> triads;
2407 if (printmessages) {
2408 std::cout <<
"starting triangulation for scan " << s <<
"..." << std::endl;
2413 while (success != 1 && Ntries < 3) {
2416 success = s_hull_pro(pts, triads);
2421 if (printmessages) {
2422 std::cout <<
"Shifting scan " << s <<
" (try " << Ntries <<
" of 3)" << std::endl;
2424 for (
int r = 0; r < pts.size(); r++) {
2425 pts.at(r).c += 0.25 *
M_PI;
2426 if (pts.at(r).c > 2.f *
M_PI) {
2427 pts.at(r).c -= 2.f *
M_PI;
2434 if (printmessages) {
2435 std::cout <<
"FAILED: could not triangulate scan " << s <<
". Skipping this scan." << std::endl;
2438 }
else if (printmessages) {
2439 std::cout <<
"finished triangulation" << std::endl;
2442 for (
int t = 0; t < triads.size(); t++) {
2444 int ID0 = Delaunay_inds.at(triads.at(t).a);
2445 int ID1 = Delaunay_inds.at(triads.at(t).b);
2446 int ID2 = Delaunay_inds.at(triads.at(t).c);
2458 v = vertex0 - vertex1;
2460 v = vertex0 - vertex2;
2462 v = vertex1 - vertex2;
2465 float aspect_ratio =
max(
max(L0, L1), L2) /
min(
min(L0, L1), L2);
2468 bool filtered = (L0 > Lmax || L1 > Lmax || L2 > Lmax);
2470 if (use_adaptive_threshold) {
2476 float ratio01 = L0 / (ang01 + 1e-6);
2477 float ratio02 = L1 / (ang02 + 1e-6);
2478 float ratio12 = L2 / (ang12 + 1e-6);
2479 float max_sep_ratio =
max(
max(ratio01, ratio02), ratio12);
2481 filtered = filtered || (max_sep_ratio > adaptive_sep_threshold) || (aspect_ratio > max_aspect_ratio);
2484 filtered = filtered || (aspect_ratio > max_aspect_ratio);
2493 if (printmessages && gridcell == -2) {
2494 cout <<
"WARNING (triangulateHitPoints): You typically want to define the hit grid cell for all hit points before performing triangulation." << endl;
2498 color.
r = (hits.at(ID0).color.r + hits.at(ID1).color.r + hits.at(ID2).color.r) / 3.f;
2499 color.
g = (hits.at(ID0).color.g + hits.at(ID1).color.g + hits.at(ID2).color.g) / 3.f;
2500 color.
b = (hits.at(ID0).color.b + hits.at(ID1).color.b + hits.at(ID2).color.b) / 3.f;
2502 Triangulation tri(s, vertex0, vertex1, vertex2, ID0, ID1, ID2, color, gridcell);
2504 if (tri.area != tri.area) {
2508 triangles.push_back(tri);
2514 triangulationcomputed =
true;
2516 if (printmessages) {
2518 cout <<
"\rTriangulating...formed " << Ntriangles <<
" total triangles." << endl;
2525 if (scans.size() == 0) {
2526 if (printmessages) {
2527 std::cout <<
"WARNING (addTrianglesToContext): There are no scans in the point cloud, and thus there are no triangles to add...skipping." << std::endl;
2536 context->
addTriangle(tri.vertex0, tri.vertex1, tri.vertex2, tri.color);
2541 return grid_cells.size();
2545 addGridCell(center, center, size, size, rotation,
make_int3(1, 1, 1),
make_int3(1, 1, 1));
2550 GridCell newcell(center, global_anchor, size, global_size, rotation, global_ijk, global_count);
2552 grid_cells.push_back(newcell);
2558 helios_runtime_error(
"ERROR (LiDARcloud::getCellCenter): grid cell index out of range. Requested center of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2561 return grid_cells.at(index).center;
2567 helios_runtime_error(
"ERROR (LiDARcloud::getCellGlobalAnchor): grid cell index out of range. Requested anchor of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2570 return grid_cells.at(index).global_anchor;
2576 helios_runtime_error(
"ERROR (LiDARcloud::getCellCenter): grid cell index out of range. Requested size of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2579 return grid_cells.at(index).size;
2585 helios_runtime_error(
"ERROR (LiDARcloud::getCellRotation): grid cell index out of range. Requested rotation of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2588 return grid_cells.at(index).azimuthal_rotation;
2599 std::vector<float> Gtheta;
2600 Gtheta.resize(Ncells);
2602 std::vector<float> area_sum;
2603 area_sum.resize(Ncells, 0.f);
2604 std::vector<uint> cell_tri_count;
2605 cell_tri_count.resize(Ncells, 0);
2608 for (
int p = 0; p < UUIDs.size(); p++) {
2610 uint UUID = UUIDs.at(p);
2621 for (
int s = 0; s < Nscans; s++) {
2623 vec3 raydir = vertices.front() - origin;
2628 Gtheta.at(gridCell) += fabs(normal * raydir) * area;
2630 area_sum.at(gridCell) += area;
2631 cell_tri_count.at(gridCell) += 1;
2637 for (
uint v = 0; v < Ncells; v++) {
2638 if (cell_tri_count[v] > 0) {
2639 Gtheta[v] *= float(cell_tri_count[v]) / (area_sum[v]);
2644 std::vector<float> output_Gtheta;
2645 output_Gtheta.resize(Ncells, 0.f);
2647 for (
int v = 0; v < Ncells; v++) {
2648 output_Gtheta.at(v) = Gtheta.at(v);
2654 return output_Gtheta;
2663 grid_cells.at(index).leaf_area = area;
2669 helios_runtime_error(
"ERROR (LiDARcloud::getCellLeafArea): grid cell index out of range. Requested leaf area of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2672 return grid_cells.at(index).leaf_area;
2678 helios_runtime_error(
"ERROR (LiDARcloud::getCellLeafAreaDensity): grid cell index out of range. Requested leaf area density of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
2679 " cells in the grid.");
2683 return grid_cells.at(index).leaf_area / (gridsize.
x * gridsize.
y * gridsize.
z);
2692 grid_cells.at(index).Gtheta = Gtheta;
2698 helios_runtime_error(
"ERROR (LiDARcloud::getCellGtheta): grid cell index out of range. Requested leaf area of cell #" + std::to_string(index) +
" but there are only " + std::to_string(
getGridCellCount()) +
" cells in the grid.");
2701 return grid_cells.at(index).Gtheta;
2704void LiDARcloud::leafReconstructionFloodfill() {
2706 size_t group_count = 0;
2707 int current_group = 0;
2709 vector<vector<int>> nodes;
2717 if (tri.gridcell >= 0) {
2719 nodes.at(tri.ID0).push_back(t);
2720 nodes.at(tri.ID1).push_back(t);
2721 nodes.at(tri.ID2).push_back(t);
2727 std::vector<int> fill_flag;
2728 fill_flag.resize(Ntri);
2729 for (
size_t t = 0; t < Ntri; t++) {
2730 fill_flag.at(t) = -1;
2733 for (
size_t t = 0; t < Ntri; t++) {
2735 if (fill_flag.at(t) < 0) {
2737 floodfill(t, triangles, fill_flag, nodes, current_group, 0, 1e3);
2743 for (
size_t t = 0; t < Ntri; t++) {
2745 if (fill_flag.at(t) >= 0) {
2746 int fill_group = fill_flag.at(t);
2748 if (fill_group >= reconstructed_triangles.size()) {
2749 reconstructed_triangles.resize(fill_group + 1);
2752 reconstructed_triangles.at(fill_group).push_back(triangles.at(t));
2757void LiDARcloud::floodfill(
size_t t, std::vector<Triangulation> &cloud_triangles, std::vector<int> &fill_flag, std::vector<std::vector<int>> &nodes,
int tag,
int depth,
int maxdepth) {
2761 int verts[3] = {tri.ID0, tri.ID1, tri.ID2};
2763 std::vector<int> connection_list;
2765 for (
int i = 0; i < 3; i++) {
2766 std::vector<int> connected_tris = nodes.at(verts[i]);
2767 connection_list.insert(connection_list.begin(), connected_tris.begin(), connected_tris.end());
2770 std::sort(connection_list.begin(), connection_list.end());
2773 for (
int tt = 1; tt < connection_list.size(); tt++) {
2774 if (connection_list.at(tt - 1) != connection_list.at(tt)) {
2778 int index = connection_list.at(tt - 1);
2780 if (fill_flag.at(index) == -1 && index != t) {
2782 fill_flag.at(index) = tag;
2784 if (depth < maxdepth) {
2785 floodfill(index, cloud_triangles, fill_flag, nodes, tag, depth + 1, maxdepth);
2803 if (printmessages) {
2804 cout <<
"Performing alphamask leaf reconstruction..." << flush;
2807 if (triangles.size() == 0) {
2808 std::cout <<
"failed." << std::endl;
2809 helios_runtime_error(
"ERROR (LiDARcloud::leafReconstructionAlphamask): There are no triangulated points. Either the triangulation failed or 'triangulateHitPoints()' was not called.");
2812 std::string file = mask_file;
2813 if (file.substr(file.find_last_of(
".") + 1) !=
"png") {
2814 std::cout <<
"failed." << std::endl;
2815 helios_runtime_error(
"ERROR (LiDARcloud::leafReconstructionAlphaMask): Mask data file " + std::string(mask_file) +
" must be PNG image format.");
2817 std::vector<std::vector<bool>> maskdata =
readPNGAlpha(mask_file);
2818 if (maskdata.size() == 0) {
2819 std::cout <<
"failed." << std::endl;
2820 helios_runtime_error(
"ERROR (LiDARcloud::leafReconstructionAlphaMask): Could not load mask file " + std::string(mask_file) +
". It contains no data.");
2822 int ix = maskdata.front().size();
2823 int jy = maskdata.size();
2827 for (
uint j = 0; j < masksize.
y; j++) {
2828 for (
uint i = 0; i < masksize.
x; i++) {
2830 if (maskdata.at(j).at(i)) {
2836 float solidfraction = float(Asolid) / float(Atotal);
2838 float total_area = 0.f;
2840 std::vector<std::vector<float>> group_areas;
2843 reconstructed_alphamasks_maskfile = mask_file;
2845 leafReconstructionFloodfill();
2849 uint group_count = reconstructed_triangles.size();
2851 float group_area_max = 0;
2853 std::vector<bool> group_filter_flag;
2854 group_filter_flag.resize(reconstructed_triangles.size());
2856 for (
int group = group_count - 1; group >= 0; group--) {
2860 for (
size_t t = 0; t < reconstructed_triangles.at(group).size(); t++) {
2862 float triangle_area = reconstructed_triangles.at(group).at(t).area;
2864 garea += triangle_area;
2867 if (garea < minimum_leaf_group_area || garea > maximum_leaf_group_area) {
2868 group_filter_flag.at(group) =
false;
2871 group_filter_flag.at(group) =
true;
2872 int cell = reconstructed_triangles.at(group).front().gridcell;
2873 group_areas.at(cell).push_back(garea);
2884 std::sort(group_areas.at(v).begin(), group_areas.at(v).end());
2887 if (group_areas.at(v).size() > Navg) {
2888 for (
int i = group_areas.at(v).size() - 1; i >= group_areas.at(v).size() - Navg; i--) {
2889 Lavg.at(v) += sqrtf(group_areas.at(v).at(i)) / float(Navg);
2891 }
else if (group_areas.at(v).size() == 0) {
2894 for (
int i = 0; i < group_areas.at(v).size(); i++) {
2895 Lavg.at(v) += sqrtf(group_areas.at(v).at(i)) / float(group_areas.at(v).size());
2899 if (printmessages) {
2900 std::cout <<
"Average leaf length for volume #" << v <<
" : " << Lavg.at(v) << endl;
2906 for (
int group = 0; group < reconstructed_triangles.size(); group++) {
2908 if (!group_filter_flag.at(group)) {
2912 int cell = reconstructed_triangles.at(group).front().gridcell;
2915 for (
int t = 0; t < reconstructed_triangles.at(group).size(); t++) {
2916 position = position + reconstructed_triangles.at(group).at(t).vertex0 / float(reconstructed_triangles.at(group).size());
2919 int gind = round(
randu() * (reconstructed_triangles.at(group).size() - 1));
2921 reconstructed_alphamasks_center.push_back(position);
2922 float l = Lavg.at(reconstructed_triangles.at(group).front().gridcell) * sqrt(leaf_aspect_ratio / solidfraction);
2923 float w = l / leaf_aspect_ratio;
2925 helios::vec3 normal =
cross(reconstructed_triangles.at(group).at(gind).vertex1 - reconstructed_triangles.at(group).at(gind).vertex0, reconstructed_triangles.at(group).at(gind).vertex2 - reconstructed_triangles.at(group).at(gind).vertex0);
2927 reconstructed_alphamasks_gridcell.push_back(reconstructed_triangles.at(group).front().gridcell);
2928 reconstructed_alphamasks_direct_flag.push_back(1);
2931 if (printmessages) {
2932 cout <<
"done." << endl;
2933 cout <<
"Directly reconstructed " << reconstructed_alphamasks_center.size() <<
" leaf groups." << endl;
2936 backfillLeavesAlphaMask(Lavg, leaf_aspect_ratio, solidfraction, group_filter_flag);
2938 for (
int group = 0; group < reconstructed_triangles.size(); group++) {
2940 if (!group_filter_flag.at(group)) {
2941 std::swap(reconstructed_triangles.at(group), reconstructed_triangles.back());
2942 reconstructed_triangles.pop_back();
2950void LiDARcloud::backfillLeavesAlphaMask(
const vector<float> &leaf_size,
float leaf_aspect_ratio,
float solidfraction,
const vector<bool> &group_filter_flag) {
2952 if (printmessages) {
2953 cout <<
"Backfilling leaves..." << endl;
2956 unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
2957 std::minstd_rand0 generator;
2958 generator.seed(seed);
2959 std::normal_distribution<float> randn;
2961 uint Ngroups = reconstructed_triangles.size();
2965 std::vector<std::vector<uint>> group_gridcell;
2966 group_gridcell.resize(Ncells);
2969 std::vector<float> leaf_area_current;
2970 leaf_area_current.resize(Ncells);
2974 for (
uint g = 0; g < Ngroups; g++) {
2975 if (group_filter_flag.at(g)) {
2976 if (reconstructed_triangles.at(g).size() > 0) {
2977 cell = reconstructed_triangles.at(g).front().gridcell;
2978 leaf_area_current.at(cell) += leaf_size.at(cell) * leaf_size.at(cell) * solidfraction;
2979 group_gridcell.at(cell).push_back(count);
2985 std::vector<int> deleted_groups;
2986 int backfill_count = 0;
2989 for (
uint v = 0; v < Ncells; v++) {
2993 float reconstruct_frac = (leaf_area_total - leaf_area_current.at(v)) / leaf_area_total;
2995 if (leaf_area_total == 0 || reconstructed_alphamasks_size.size() == 0) {
2996 if (printmessages) {
2997 cout <<
"WARNING: skipping volume #" << v <<
" because it has no measured leaf area." << endl;
3001 if (printmessages) {
3002 cout <<
"WARNING: skipping volume #" << v <<
" because it has no triangles." << endl;
3005 }
else if (leaf_area_current.at(v) == 0) {
3007 std::vector<SphericalCoord> tri_rots;
3012 if (tri.gridcell == v) {
3013 helios::vec3 normal =
cross(tri.vertex1 - tri.vertex0, tri.vertex2 - tri.vertex0);
3018 while (leaf_area_current.at(v) < leaf_area_total) {
3020 int randi = round(
randu() * (tri_rots.size() - 1));
3028 reconstructed_alphamasks_center.push_back(shift);
3029 reconstructed_alphamasks_size.push_back(reconstructed_alphamasks_size.front());
3030 reconstructed_alphamasks_rotation.push_back(tri_rots.at(randi));
3031 reconstructed_alphamasks_gridcell.push_back(v);
3032 reconstructed_alphamasks_direct_flag.push_back(0);
3034 leaf_area_current.at(v) += reconstructed_alphamasks_size.back().x * reconstructed_alphamasks_size.back().y * solidfraction;
3038 }
else if (leaf_area_current.at(v) > leaf_area_total) {
3040 while (leaf_area_current.at(v) > leaf_area_total) {
3042 int randi = round(
randu() * (group_gridcell.at(v).size() - 1));
3044 int group_index = group_gridcell.at(v).at(randi);
3046 deleted_groups.push_back(group_index);
3048 leaf_area_current.at(v) -= reconstructed_alphamasks_size.at(group_index).x * reconstructed_alphamasks_size.at(group_index).y * solidfraction;
3053 while (leaf_area_current.at(v) < leaf_area_total) {
3055 int randi = round(
randu() * (group_gridcell.at(v).size() - 1));
3057 int group_index = group_gridcell.at(v).at(randi);
3065 helios::vec3 shift = reconstructed_alphamasks_center.at(group_index) +
helios::make_vec3(0.25 * randn(generator) * cellsize.
x, 0.25 * randn(generator) * cellsize.
y, 0.25 * randn(generator) * cellsize.
z);
3069 if (group_index >= reconstructed_alphamasks_center.size()) {
3070 helios_runtime_error(
"FAILED: " + std::to_string(group_index) +
" " + std::to_string(reconstructed_alphamasks_center.size()) +
" " + std::to_string(randi));
3071 }
else if (reconstructed_alphamasks_gridcell.at(group_index) != v) {
3075 reconstructed_alphamasks_center.push_back(shift);
3076 reconstructed_alphamasks_size.push_back(reconstructed_alphamasks_size.at(group_index));
3077 reconstructed_alphamasks_rotation.push_back(reconstructed_alphamasks_rotation.at(group_index));
3078 reconstructed_alphamasks_gridcell.push_back(v);
3079 reconstructed_alphamasks_direct_flag.push_back(0);
3081 leaf_area_current.at(v) += reconstructed_alphamasks_size.at(group_index).x * reconstructed_alphamasks_size.at(group_index).y * solidfraction;
3088 for (
uint v = 0; v < Ncells; v++) {
3092 float current_area = 0;
3093 for (
uint i = 0; i < reconstructed_alphamasks_size.size(); i++) {
3094 if (reconstructed_alphamasks_gridcell.at(i) == v) {
3095 current_area += reconstructed_alphamasks_size.at(i).x * reconstructed_alphamasks_size.at(i).y * solidfraction;
3100 if (printmessages) {
3101 cout <<
"Backfilled " << backfill_count <<
" total leaf groups." << endl;
3102 cout <<
"Deleted " << deleted_groups.size() <<
" total leaf groups." << endl;
3105 for (
int i = deleted_groups.size() - 1; i >= 0; i--) {
3106 int group_index = deleted_groups.at(i);
3107 if (group_index >= 0 && group_index < reconstructed_alphamasks_center.size()) {
3109 std::swap(reconstructed_alphamasks_center.at(group_index), reconstructed_alphamasks_center.back());
3110 reconstructed_alphamasks_center.pop_back();
3111 std::swap(reconstructed_alphamasks_size.at(group_index), reconstructed_alphamasks_size.back());
3112 reconstructed_alphamasks_size.pop_back();
3113 std::swap(reconstructed_alphamasks_rotation.at(group_index), reconstructed_alphamasks_rotation.back());
3114 reconstructed_alphamasks_rotation.pop_back();
3115 std::swap(reconstructed_alphamasks_gridcell.at(group_index), reconstructed_alphamasks_gridcell.back());
3116 reconstructed_alphamasks_gridcell.pop_back();
3117 std::swap(reconstructed_alphamasks_direct_flag.at(group_index), reconstructed_alphamasks_direct_flag.back());
3118 reconstructed_alphamasks_direct_flag.pop_back();
3122 if (printmessages) {
3123 cout <<
"done." << endl;
3127void LiDARcloud::calculateLeafAngleCDF(
uint Nbins, std::vector<std::vector<float>> &CDF_theta, std::vector<std::vector<float>> &CDF_phi) {
3131 std::vector<std::vector<float>> PDF_theta, PDF_phi;
3132 CDF_theta.resize(Ncells);
3133 PDF_theta.resize(Ncells);
3134 CDF_phi.resize(Ncells);
3135 PDF_phi.resize(Ncells);
3136 for (
uint v = 0; v < Ncells; v++) {
3137 CDF_theta.at(v).resize(Nbins, 0.f);
3138 PDF_theta.at(v).resize(Nbins, 0.f);
3139 CDF_phi.at(v).resize(Nbins, 0.f);
3140 PDF_phi.at(v).resize(Nbins, 0.f);
3142 float db_theta = 0.5 *
M_PI / Nbins;
3143 float db_phi = 2.f *
M_PI / Nbins;
3146 for (
size_t t = 0; t < triangles.size(); t++) {
3147 float triangle_area = triangles.at(t).area;
3148 int gridcell = triangles.at(t).gridcell;
3150 if (gridcell >= 0 && gridcell < (
int) Ncells) {
3151 helios::vec3 normal =
cross(triangles.at(t).vertex1 - triangles.at(t).vertex0, triangles.at(t).vertex2 - triangles.at(t).vertex0);
3152 normal.
z = fabs(normal.
z);
3156 int bin_theta = floor(normal_dir.
zenith / db_theta);
3157 if (bin_theta >= Nbins) {
3158 bin_theta = Nbins - 1;
3161 int bin_phi = floor(normal_dir.
azimuth / db_phi);
3162 if (bin_phi >= Nbins) {
3163 bin_phi = Nbins - 1;
3166 PDF_theta.at(gridcell).at(bin_theta) += triangle_area;
3167 PDF_phi.at(gridcell).at(bin_phi) += triangle_area;
3172 for (
uint v = 0; v < Ncells; v++) {
3173 for (
uint i = 0; i < Nbins; i++) {
3174 for (
uint j = 0; j <= i; j++) {
3175 CDF_theta.at(v).at(i) += PDF_theta.at(v).at(j);
3176 CDF_phi.at(v).at(i) += PDF_phi.at(v).at(j);
3200 std::vector<helios::vec3> grid_vertices;
3203 grid_vertices.push_back(boxmin);
3204 grid_vertices.push_back(boxmax);
3212 float max_theta = 0;
3213 float min_theta =
M_PI;
3215 float min_phi = 2 *
M_PI;
3216 for (
uint gg = 0; gg < grid_vertices.size(); gg++) {
3220 std::cout <<
"azimuth " << sc.
azimuth * (180 /
M_PI) <<
", zenith " << sc.
zenith * (180 /
M_PI) << std::endl;
3230 if (sc.
zenith < min_theta) {
3234 if (sc.
zenith > max_theta) {
3242 std::cout <<
"theta_range = " << theta_range * (180.0 /
M_PI) << std::endl;
3243 std::cout <<
"phi_range = " << phi_range * (180.0 /
M_PI) << std::endl;
3245 std::cout <<
"original # hitpoints = " <<
getHitCount() << std::endl;
3252 float this_theta = raydir.
zenith;
3253 float this_phi = raydir.
azimuth;
3254 double this_phi_d = double(this_phi);
3256 if (this_phi < phi_range.x || this_phi > phi_range.
y || this_phi < phi_range.x || this_theta > theta_range.
y) {
3262 std::cout <<
"# hitpoints remaining after crop = " <<
getHitCount() << std::endl;
3267void LiDARcloud::computeGtheta(
uint Ncells,
uint Nscans,
3268 std::vector<float> &Gtheta, std::vector<float> &Gtheta_bar) {
3271 Gtheta.resize(Ncells, 0.f);
3272 Gtheta_bar.resize(Ncells, 0.f);
3276 std::vector<float> denom_sum;
3277 denom_sum.resize(Ncells, 0.f);
3278 std::vector<uint> cell_tri_count;
3279 cell_tri_count.resize(Ncells, 0);
3282 for (
size_t t = 0; t < Ntri; t++) {
3285 int cell = tri.gridcell;
3287 if (cell >= 0 && cell < Ncells) {
3302 float S = 0.5f * (L0 + L1 + L2);
3303 float area = sqrt(S * (S - L0) * (S - L1) * (S - L2));
3314 float normal_dot_ray = fabs(normal * raydir);
3315 Gtheta.at(cell) += normal_dot_ray * area * fabs(sin(theta));
3316 denom_sum.at(cell) += fabs(sin(theta)) * area;
3317 cell_tri_count.at(cell) += 1;
3323 for (
uint v = 0; v < Ncells; v++) {
3324 if (cell_tri_count[v] > 0) {
3325 Gtheta[v] = Gtheta[v] / denom_sum[v];
3326 Gtheta_bar[v] += Gtheta[v] / float(Nscans);
3331bool LiDARcloud::invertLAD(
uint voxel_index,
float P,
float Gtheta,
3332 const std::vector<float> &dr_samples,
int min_voxel_hits,
3336 if (Gtheta == 0 || Gtheta != Gtheta) {
3341 if (dr_samples.size() < min_voxel_hits) {
3356 for (
size_t j = 0; j < dr_samples.size(); j++) {
3357 mean += exp(-a * dr_samples[j] * Gtheta);
3359 mean /= float(dr_samples.size());
3360 float error = fabs(
mean - P) / P;
3368 while (error > etol && iter < maxiter) {
3374 for (
size_t j = 0; j < dr_samples.size(); j++) {
3375 mean += exp(-a * dr_samples[j] * Gtheta);
3377 mean /= float(dr_samples.size());
3378 error = fabs(
mean - P) / P;
3382 if (error == eold) {
3387 a = fabs((aold * error - a * eold) / (error - eold));
3392 float dr_bar = 0.0f;
3393 for (
size_t i = 0; i < dr_samples.size(); i++) {
3394 dr_bar += dr_samples[i];
3396 dr_bar /= float(dr_samples.size());
3399 bool converged = (iter < maxiter - 1 && a == a && a <= 100);
3400 bool used_fallback =
false;
3403 if (printmessages) {
3404 std::cout <<
"WARNING: LAD inversion failed for volume #" << voxel_index
3405 <<
". Using average dr formulation." << std::endl;
3407 a = (1.f - P) / (dr_bar * Gtheta);
3408 used_fallback =
true;
3413 a = fmin((1.f - P) / dr_bar / Gtheta, -log(P) / dr_bar / Gtheta);
3417 leaf_area = a * gridsize.
x * gridsize.
y * gridsize.
z;
3423 const std::vector<helios::vec3> &ray_endpoints,
3426 std::vector<uint> &filtered_indices) {
3428 filtered_indices.clear();
3429 filtered_indices.reserve(ray_endpoints.size());
3432 float x0 = bb_center.
x - 0.5f * bb_size.
x;
3433 float x1 = bb_center.
x + 0.5f * bb_size.
x;
3434 float y0 = bb_center.
y - 0.5f * bb_size.
y;
3435 float y1 = bb_center.
y + 0.5f * bb_size.
y;
3436 float z0 = bb_center.
z - 0.5f * bb_size.
z;
3437 float z1 = bb_center.
z + 0.5f * bb_size.
z;
3440 float ox = scan_origin.
x;
3441 float oy = scan_origin.
y;
3442 float oz = scan_origin.
z;
3445 for (
size_t i = 0; i < ray_endpoints.size(); i++) {
3448 if (ox >= x0 && ox <= x1 && oy >= y0 && oy <= y1 && oz >= z0 && oz <= z1) {
3449 filtered_indices.push_back(i);
3454 helios::vec3 direction = ray_endpoints[i] - scan_origin;
3457 float dx = direction.
x;
3458 float dy = direction.
y;
3459 float dz = direction.
z;
3462 float tx_min, ty_min, tz_min;
3463 float tx_max, ty_max, tz_max;
3466 float a = 1.0f / dx;
3468 tx_min = (x0 - ox) * a;
3469 tx_max = (x1 - ox) * a;
3471 tx_min = (x1 - ox) * a;
3472 tx_max = (x0 - ox) * a;
3476 float b = 1.0f / dy;
3478 ty_min = (y0 - oy) * b;
3479 ty_max = (y1 - oy) * b;
3481 ty_min = (y1 - oy) * b;
3482 ty_max = (y0 - oy) * b;
3486 float c = 1.0f / dz;
3488 tz_min = (z0 - oz) * c;
3489 tz_max = (z1 - oz) * c;
3491 tz_min = (z1 - oz) * c;
3492 tz_max = (z0 - oz) * c;
3497 if (ty_min > t0) t0 = ty_min;
3498 if (tz_min > t0) t0 = tz_min;
3502 if (ty_max < t1) t1 = ty_max;
3503 if (tz_max < t1) t1 = tz_max;
3506 if (t0 < t1 && t1 > 1e-6f) {
3507 filtered_indices.push_back(i);
3511 return filtered_indices.size();
3514void LiDARcloud::calculateVoxelPathLengths(
const helios::vec3 &scan_origin,
3515 const std::vector<helios::vec3> &ray_directions,
3516 const std::vector<helios::vec3> &voxel_centers,
3517 const std::vector<helios::vec3> &voxel_sizes,
3518 const std::vector<float> &voxel_rotations,
3519 std::vector<std::vector<float>> &dr_agg,
3520 std::vector<float> &hit_before_agg,
3521 std::vector<float> &hit_after_agg) {
3523 const uint Ncells = voxel_centers.size();
3526 dr_agg.resize(Ncells);
3527 hit_before_agg.resize(Ncells, 0.0f);
3528 hit_after_agg.resize(Ncells, 0.0f);
3533 for (
uint c = 0; c < Ncells; c++) {
3536 float rotation = voxel_rotations[c];
3539 for (
size_t r = 0; r < ray_directions.size(); r++) {
3545 if (fabs(rotation) > 1e-6f) {
3547 helios::vec3 endpoint = scan_origin + ray_directions[r] * 10000.f;
3550 ray_origin =
rotatePointAboutLine(scan_origin - anchor, helios::make_vec3(0, 0, 0), helios::make_vec3(0, 0, 1), -rotation) + anchor;
3553 ray_dir = transformed_endpoint - ray_origin;
3561 float tx_min = (voxel_min.
x - ray_origin.x) / ray_dir.
x;
3562 float tx_max = (voxel_max.x - ray_origin.
x) / ray_dir.
x;
3563 if (tx_min > tx_max) std::swap(tx_min, tx_max);
3565 float ty_min = (voxel_min.y - ray_origin.
y) / ray_dir.
y;
3566 float ty_max = (voxel_max.y - ray_origin.
y) / ray_dir.
y;
3567 if (ty_min > ty_max) std::swap(ty_min, ty_max);
3569 float tz_min = (voxel_min.z - ray_origin.
z) / ray_dir.
z;
3570 float tz_max = (voxel_max.z - ray_origin.
z) / ray_dir.
z;
3571 if (tz_min > tz_max) std::swap(tz_min, tz_max);
3573 float t0 = std::max({tx_min, ty_min, tz_min});
3574 float t1 = std::min({tx_max, ty_max, tz_max});
3577 if (t0 < t1 && t1 > 1e-6f) {
3579 float dr = fabs(t1 - t0);
3580 dr_agg[c].push_back(dr);
3583 float weight = 1.0f;
3584 float zenith_weight = sin(
acos_safe(ray_dir.
z));
3589 hit_after_agg[c] += zenith_weight * weight;
3601 if (printmessages) {
3602 std::cout <<
"Calculating leaf area (CollisionDetection)..." << std::endl;
3606 if (!triangulationcomputed) {
3607 helios_runtime_error(
"ERROR (LiDARcloud::calculateLeafAreaCD): Triangulation must be performed prior to leaf area calculation. See triangulateHitPoints().");
3610 if (!hitgridcellcomputed) {
3621 const bool use_equal_weighting = isMultiReturnData();
3623 if (printmessages) {
3624 if (use_equal_weighting) {
3625 std::cout <<
"Multi-return data detected - using beam-based equal weighting algorithm (CD)" << std::endl;
3628 bool has_gapfilled =
false;
3631 has_gapfilled =
true;
3636 if (!has_gapfilled) {
3637 std::cout <<
"WARNING: Multi-return data detected but gap filling has not been applied." << std::endl;
3638 std::cout <<
" For best results with multi-return data, call gapfillMisses() before calculateLeafArea()." << std::endl;
3641 std::cout <<
"Single-return data - using standard weighting algorithm (CD)" << std::endl;
3646 if (use_equal_weighting) {
3650 std::vector<std::vector<float>> P_equal_numerator_array(Ncells);
3651 std::vector<std::vector<float>> P_equal_denominator_array(Ncells);
3652 std::vector<std::vector<float>> dr_array(Ncells);
3655 std::vector<std::vector<float>> dr_agg;
3656 dr_agg.resize(Ncells);
3657 std::vector<float> Gtheta_bar;
3658 Gtheta_bar.resize(Ncells, 0.f);
3661 for (
uint s = 0; s < Nscans; s++) {
3664 std::vector<helios::vec3> this_scan_xyz;
3665 std::vector<uint> this_scan_index;
3669 this_scan_index.push_back(r);
3672 size_t Nhits = this_scan_xyz.size();
3673 if (Nhits == 0)
continue;
3676 BeamGrouping beams = groupHitsByTimestamp(this_scan_index);
3677 uint Nbeams = beams.Nbeams;
3680 float scanner_range = 5000.0f;
3683 std::vector<float> dr(Nhits, 0.0f);
3684 std::vector<uint> hit_location(Nhits, 0);
3687 for (
uint c = 0; c < Ncells; c++) {
3694 std::fill(dr.begin(), dr.end(), 0.0f);
3695 std::fill(hit_location.begin(), hit_location.end(), 0);
3698 #pragma omp parallel for
3699 for (
int i = 0; i < static_cast<int>(Nhits); i++) {
3703 if (fabs(rotation) > 1e-6f) {
3711 float hit_distance = direction.
magnitude();
3719 float tx_min = (voxel_min.
x - origin.x) / direction.
x;
3720 float tx_max = (voxel_max.
x - origin.x) / direction.
x;
3721 if (tx_min > tx_max) std::swap(tx_min, tx_max);
3723 float ty_min = (voxel_min.
y - origin.y) / direction.
y;
3724 float ty_max = (voxel_max.
y - origin.y) / direction.
y;
3725 if (ty_min > ty_max) std::swap(ty_min, ty_max);
3727 float tz_min = (voxel_min.
z - origin.z) / direction.
z;
3728 float tz_max = (voxel_max.
z - origin.z) / direction.
z;
3729 if (tz_min > tz_max) std::swap(tz_min, tz_max);
3731 float t0 = std::max({tx_min, ty_min, tz_min});
3732 float t1 = std::min({tx_max, ty_max, tz_max});
3735 if (t0 < t1 && t1 > 1e-6f) {
3736 dr[i] = fabs(t1 - t0);
3738 if (hit_distance >= t0 && hit_distance <= t1) {
3739 hit_location[i] = 2;
3740 }
else if (hit_distance > t1 && hit_distance < scanner_range) {
3741 hit_location[i] = 3;
3742 }
else if (hit_distance >= scanner_range) {
3743 hit_location[i] = 4;
3744 }
else if (hit_distance < t0) {
3745 hit_location[i] = 1;
3751 float P_equal_numerator = 0;
3752 float P_equal_denominator = 0;
3754 for (
uint k = 0; k < Nbeams; k++) {
3755 float E_before = 0, E_inside = 0, E_after = 0, E_miss = 0;
3760 for (
uint j = 0; j < beams.beam_array.at(k).size(); j++) {
3761 uint i = beams.beam_array.at(k).at(j);
3768 if (hit_location[i] == 1) E_before++;
3769 else if (hit_location[i] == 2) E_inside++;
3770 else if (hit_location[i] == 3) E_after++;
3771 else if (hit_location[i] == 4) E_miss++;
3776 if (E_inside != 0 || E_after != 0) {
3777 P_equal_numerator += (E_after / (E_inside + E_after));
3778 P_equal_denominator += 1;
3779 }
else if (E_inside == 0 && E_after == 0 && E_before == 0 && E_miss != 0) {
3780 P_equal_numerator += 1;
3781 P_equal_denominator += 1;
3786 float drrx = drr / float(dr_count);
3787 dr_array.at(c).push_back(drrx);
3791 P_equal_numerator_array.at(c).push_back(P_equal_numerator);
3792 P_equal_denominator_array.at(c).push_back(P_equal_denominator);
3797 std::vector<float> Gtheta;
3798 computeGtheta(Ncells, Nscans, Gtheta, Gtheta_bar);
3801 if (printmessages) {
3802 std::cout <<
"Inverting to find LAD..." << std::flush;
3805 for (
uint v = 0; v < Ncells; v++) {
3808 float P_num_sum = 0.0f, P_denom_sum = 0.0f;
3809 for (
uint s = 0; s < P_equal_numerator_array[v].size(); s++) {
3810 P_num_sum += P_equal_numerator_array[v][s];
3811 P_denom_sum += P_equal_denominator_array[v][s];
3813 if (P_denom_sum > 0) {
3814 P = P_num_sum / P_denom_sum;
3818 for (
uint s = 0; s < dr_array[v].size(); s++) {
3819 if (dr_array[v][s] > 0) {
3820 dr_agg[v].push_back(dr_array[v][s]);
3825 if (dr_agg[v].size() < min_voxel_hits) {
3832 float leaf_area = 0.0f;
3834 invertLAD(v, P, Gtheta[v], dr_agg[v], min_voxel_hits, gridsize, leaf_area);
3840 if (printmessages) {
3841 std::cout <<
"done." << std::endl;
3851 std::vector<std::vector<float>> dr_agg;
3852 dr_agg.resize(Ncells);
3853 std::vector<float> hit_before_agg;
3854 hit_before_agg.resize(Ncells, 0);
3855 std::vector<float> hit_after_agg;
3856 hit_after_agg.resize(Ncells, 0);
3857 std::vector<float> hit_inside_agg;
3858 hit_inside_agg.resize(Ncells, 0);
3859 std::vector<float> Gtheta_bar;
3860 Gtheta_bar.resize(Ncells, 0.f);
3863 for (
uint s = 0; s < Nscans; s++) {
3867 const size_t Nmisses = Nt * Np;
3874 std::vector<helios::vec3> scan_endpoints;
3875 scan_endpoints.reserve(Nmisses);
3877 for (
int j = 0; j < Np; j++) {
3878 for (
int i = 0; i < Nt; i++) {
3881 scan_endpoints.push_back(endpoint);
3888 helios::vec3 bbcenter = gboxmin + 0.5f * (gboxmax - gboxmin);
3892 std::vector<uint> bb_hit_indices;
3893 uint Nmissesbb = filterRaysByBoundingBox(origin, scan_endpoints, bbcenter, bbsize, bb_hit_indices);
3895 if (Nmissesbb == 0) {
3896 std::cerr <<
"ERROR (calculateLeafAreaCD): No scan rays passed through grid cells." << std::endl;
3897 for (
uint c = 0; c < Ncells; c++) {
3904 std::vector<helios::vec3> missesbb_directions;
3905 missesbb_directions.reserve(Nmissesbb);
3906 for (
uint idx : bb_hit_indices) {
3909 missesbb_directions.push_back(direction);
3915 std::vector<helios::vec3> voxel_centers_vec, voxel_sizes_vec;
3916 std::vector<float> voxel_rotations_vec;
3917 voxel_centers_vec.reserve(Ncells);
3918 voxel_sizes_vec.reserve(Ncells);
3919 voxel_rotations_vec.reserve(Ncells);
3921 for (
uint c = 0; c < Ncells; c++) {
3928 calculateVoxelPathLengths(origin, missesbb_directions,
3929 voxel_centers_vec, voxel_sizes_vec, voxel_rotations_vec,
3930 dr_agg, hit_before_agg, hit_after_agg);
3938 if (hit_voxel < 0)
continue;
3942 float hit_distance = ray_dir.
magnitude();
3946 for (
uint c = 0; c < Ncells; c++) {
3947 if (c == hit_voxel)
continue;
3950 helios::vec3 voxel_min = voxel_centers_vec[c] - voxel_sizes_vec[c] * 0.5f;
3951 helios::vec3 voxel_max = voxel_centers_vec[c] + voxel_sizes_vec[c] * 0.5f;
3953 float tx_min = (voxel_min.
x - origin.
x) / ray_dir.
x;
3954 float tx_max = (voxel_max.
x - origin.
x) / ray_dir.
x;
3955 if (tx_min > tx_max) std::swap(tx_min, tx_max);
3957 float ty_min = (voxel_min.
y - origin.
y) / ray_dir.
y;
3958 float ty_max = (voxel_max.
y - origin.
y) / ray_dir.
y;
3959 if (ty_min > ty_max) std::swap(ty_min, ty_max);
3961 float tz_min = (voxel_min.
z - origin.
z) / ray_dir.
z;
3962 float tz_max = (voxel_max.
z - origin.
z) / ray_dir.
z;
3963 if (tz_min > tz_max) std::swap(tz_min, tz_max);
3965 float t_enter = std::max({tx_min, ty_min, tz_min});
3966 float t_exit = std::min({tx_max, ty_max, tz_max});
3969 if (t_enter < t_exit && t_exit > 1e-6f) {
3971 if (hit_distance < t_enter) {
3972 float zenith_weight = sin(
acos_safe(ray_dir.
z));
3973 hit_before_agg[c] += zenith_weight;
3993 std::vector<float> Gtheta;
3994 computeGtheta(Ncells, Nscans, Gtheta, Gtheta_bar);
3998 if (printmessages) {
3999 std::cout <<
"Inverting to find LAD..." << std::flush;
4002 for (
uint v = 0; v < Ncells; v++) {
4006 if (hit_after_agg[v] - hit_before_agg[v] > 0) {
4007 P = 1.f - float(hit_inside_agg[v]) / float(hit_after_agg[v] - hit_before_agg[v]);
4011 float leaf_area = 0.0f;
4014 invertLAD(v, P, Gtheta[v], dr_agg[v], min_voxel_hits, gridsize, leaf_area);
4020 if (printmessages) {
4021 std::cout <<
"done." << std::endl;
4026size_t findRayIndexByDirection(
const helios::vec3 &scan_origin,
4028 const std::vector<helios::vec3> &ray_directions) {
4029 helios::vec3 hit_direction = intersection_point - scan_origin;
4032 for (
size_t i = 0; i < ray_directions.size(); i++) {
4033 if ((hit_direction - ray_directions[i]).magnitude() < 1e-5f) {
4050 if (collision_detection !=
nullptr) {
4056 if (collision_detection !=
nullptr) {
4063 if (printmessages) {
4064 std::cout <<
"Grouping hit points by grid cell (CPU)..." << std::flush;
4070 if (total_hits == 0) {
4071 std::cout <<
"WARNING (calculateHitGridCellCD): There are no hits currently in the point cloud. Skipping grid cell binning calculation." << std::endl;
4076 #pragma omp parallel for schedule(dynamic, 1000)
4077 for (
int r = 0; r < static_cast<int>(total_hits); r++) {
4080 int assigned_cell = -1;
4083 for (
uint c = 0; c < Ncells; c++) {
4092 if (fabs(rotation) > 1e-6f) {
4103 float x0 = center.
x - 0.5f * size.
x;
4104 float x1 = center.
x + 0.5f * size.
x;
4105 float y0 = center.
y - 0.5f * size.
y;
4106 float y1 = center.
y + 0.5f * size.
y;
4107 float z0 = center.
z - 0.5f * size.
z;
4108 float z1 = center.
z + 0.5f * size.
z;
4111 float tx_min = (x0 - origin.
x) / direction.
x;
4112 float tx_max = (x1 - origin.
x) / direction.
x;
4113 if (tx_min > tx_max) std::swap(tx_min, tx_max);
4115 float ty_min = (y0 - origin.
y) / direction.
y;
4116 float ty_max = (y1 - origin.
y) / direction.
y;
4117 if (ty_min > ty_max) std::swap(ty_min, ty_max);
4119 float tz_min = (z0 - origin.
z) / direction.
z;
4120 float tz_max = (z1 - origin.
z) / direction.
z;
4121 if (tz_min > tz_max) std::swap(tz_min, tz_max);
4123 float t0 = std::max({tx_min, ty_min, tz_min});
4124 float t1 = std::min({tx_max, ty_max, tz_max});
4127 if (t0 < t1 && t1 > 1e-6f) {
4128 float T = (hit_xyz_rot - origin).magnitude();
4129 if (T >= t0 && T <= t1) {
4140 if (printmessages) {
4141 std::cout <<
"done." << std::endl;
4144 hitgridcellcomputed =
true;
4147bool LiDARcloud::isMultiReturnData()
const {
4154 helios_runtime_error(
"ERROR (isMultiReturnData): Multi-return data detected (target_count > 1) but 'timestamp' field is missing. Cannot group hits into beams.");
4158 helios_runtime_error(
"ERROR (isMultiReturnData): Multi-return data detected (target_count > 1) but 'target_index' field is missing. Cannot filter first returns for triangulation.");
4167LiDARcloud::BeamGrouping LiDARcloud::groupHitsByTimestamp(
const std::vector<uint>& scan_indices)
const {
4169 BeamGrouping result;
4171 if (scan_indices.empty()) {
4177 std::vector<uint> sorted_indices = scan_indices;
4178 std::sort(sorted_indices.begin(), sorted_indices.end(),
4180 return this->getHitData(a,
"timestamp") < this->getHitData(b,
"timestamp");
4184 double previous_time = -1.0;
4186 for (
uint i = 0; i < sorted_indices.size(); i++) {
4187 double current_time =
getHitData(sorted_indices[i],
"timestamp");
4188 if (current_time != previous_time) {
4190 previous_time = current_time;
4195 result.beam_array.resize(result.Nbeams);
4196 double previous_beam =
getHitData(sorted_indices[0],
"timestamp");
4199 for (
uint i = 0; i < sorted_indices.size(); i++) {
4200 double current_beam =
getHitData(sorted_indices[i],
"timestamp");
4202 if (current_beam == previous_beam) {
4203 result.beam_array.at(beam_ID).push_back(sorted_indices[i]);
4206 result.beam_array.at(beam_ID).push_back(sorted_indices[i]);
4207 previous_beam = current_beam;
4216 std::vector<uint> UUIDs_all = context->
getAllUUIDs();
4217 const uint N = UUIDs_all.size();
4221 std::vector<int> prim_vol(N, -1);
4224 #pragma omp parallel for
4225 for (
int p = 0; p < static_cast<int>(N); p++) {
4230 for (
uint c = 0; c < Ncells; c++) {
4238 if (fabs(rotation) > 1e-6f) {
4249 float x0 = center.
x - 0.5f * size.
x;
4250 float x1 = center.
x + 0.5f * size.
x;
4251 float y0 = center.
y - 0.5f * size.
y;
4252 float y1 = center.
y + 0.5f * size.
y;
4253 float z0 = center.
z - 0.5f * size.
z;
4254 float z1 = center.
z + 0.5f * size.
z;
4257 float tx_min = (x0 - origin_pt.
x) / direction.
x;
4258 float tx_max = (x1 - origin_pt.
x) / direction.
x;
4259 if (tx_min > tx_max) std::swap(tx_min, tx_max);
4261 float ty_min = (y0 - origin_pt.
y) / direction.
y;
4262 float ty_max = (y1 - origin_pt.
y) / direction.
y;
4263 if (ty_min > ty_max) std::swap(ty_min, ty_max);
4265 float tz_min = (z0 - origin_pt.
z) / direction.
z;
4266 float tz_max = (z1 - origin_pt.
z) / direction.
z;
4267 if (tz_min > tz_max) std::swap(tz_min, tz_max);
4269 float t0 = std::max({tx_min, ty_min, tz_min});
4270 float t1 = std::min({tx_max, ty_max, tz_max});
4273 if (t0 < t1 && t1 > 1e-6f) {
4274 float T = (prim_xyz_rot - origin_pt).magnitude();
4275 if (T >= t0 && T <= t1) {
4284 std::vector<float> total_area(Ncells, 0.f);
4285 for (
size_t p = 0; p < N; p++) {
4286 if (prim_vol[p] >= 0) {
4287 uint gridcell = prim_vol[p];
4294 std::vector<float> output_LeafArea(Ncells);
4295 for (
int v = 0; v < Ncells; v++) {
4296 output_LeafArea[v] = total_area[v];
4298 context->
setPrimitiveData(UUIDs_all[v],
"synthetic_leaf_area", total_area[v]);
4302 return output_LeafArea;
4314 syntheticScan(context, 1, 0, scan_grid_only, record_misses,
true);
4318 syntheticScan(context, 1, 0, scan_grid_only, record_misses, append);
4322 syntheticScan(context, rays_per_pulse, pulse_distance_threshold,
false,
false,
true);
4326 syntheticScan(context, rays_per_pulse, pulse_distance_threshold,
false,
false, append);
4330 syntheticScan(context, rays_per_pulse, pulse_distance_threshold, scan_grid_only, record_misses,
true);
4340 for (
auto &hit_table: hit_tables) {
4341 hit_table.resize(hit_table.Ntheta, hit_table.Nphi, -1);
4343 hitgridcellcomputed =
false;
4344 triangulationcomputed =
false;
4348 if (rays_per_pulse < 1) {
4351 Npulse = rays_per_pulse;
4354 if (printmessages) {
4356 std::cout <<
"Performing full-waveform synthetic LiDAR scan..." << std::endl;
4358 std::cout <<
"Performing discrete return synthetic LiDAR scan..." << std::endl;
4363 std::cout <<
"WARNING (syntheticScan): No scans added to the point cloud. Exiting.." << std::endl;
4367 float miss_distance = 1001.0;
4372 if (scan_grid_only ==
false) {
4377 bb_center =
helios::make_vec3(xbounds.
x + 0.5 * (xbounds.
y - xbounds.
x), ybounds.
x + 0.5 * (ybounds.
y - ybounds.
x), zbounds.
x + 0.5 * (zbounds.
y - zbounds.
x));
4385 bb_center =
helios::make_vec3(boxmin.
x + 0.5 * (boxmax.
x - boxmin.
x), boxmin.
y + 0.5 * (boxmax.
y - boxmin.
y), boxmin.
z + 0.5 * (boxmax.
z - boxmin.
z));
4393 std::map<std::string, int> textures;
4394 std::map<std::string, helios::int2> texture_size;
4395 std::map<std::string, std::vector<std::vector<bool>>> texture_data;
4398 std::vector<uint> UUIDs_all = context->
getAllUUIDs();
4400 std::vector<uint> ID_mapping;
4405 size_t Npatches = 0;
4406 for (
int p = 0; p < UUIDs_all.size(); p++) {
4412 ID_mapping.resize(Npatches);
4415 int *patch_textureID = (
int *) malloc(Npatches *
sizeof(
int));
4419 for (
int p = 0; p < UUIDs_all.size(); p++) {
4420 uint UUID = UUIDs_all.at(p);
4423 patch_vertex[4 * c] = verts.at(0);
4424 patch_vertex[4 * c + 1] = verts.at(1);
4425 patch_vertex[4 * c + 2] = verts.at(2);
4426 patch_vertex[4 * c + 3] = verts.at(3);
4428 ID_mapping.at(c) = UUIDs_all.at(p);
4432 std::map<std::string, int>::iterator it = textures.find(tex);
4433 if (it != textures.end()) {
4434 patch_textureID[c] = textures.at(tex);
4436 patch_textureID[c] = tID;
4437 textures[tex] = tID;
4445 if (uv.size() == 4) {
4446 patch_uv[2 * c] = uv.at(1);
4447 patch_uv[2 * c + 1] = uv.at(3);
4454 patch_textureID[c] = -1;
4466 size_t Ntriangles = 0;
4467 for (
int p = 0; p < UUIDs_all.size(); p++) {
4473 ID_mapping.resize(Npatches + Ntriangles);
4476 int *tri_textureID = (
int *) malloc(Ntriangles *
sizeof(
int));
4480 for (
int p = 0; p < UUIDs_all.size(); p++) {
4481 uint UUID = UUIDs_all.at(p);
4484 tri_vertex[3 * c] = verts.at(0);
4485 tri_vertex[3 * c + 1] = verts.at(1);
4486 tri_vertex[3 * c + 2] = verts.at(2);
4488 ID_mapping.at(Npatches + c) = UUIDs_all.at(p);
4492 std::map<std::string, int>::iterator it = textures.find(tex);
4493 if (it != textures.end()) {
4494 tri_textureID[c] = textures.at(tex);
4496 tri_textureID[c] = tID;
4497 textures[tex] = tID;
4505 assert(uv.size() == 3);
4506 tri_uv[3 * c] = uv.at(0);
4507 tri_uv[3 * c + 1] = uv.at(1);
4508 tri_uv[3 * c + 2] = uv.at(2);
4511 tri_textureID[c] = -1;
4521 const int Ntextures = textures.size();
4524 for (std::map<std::string, helios::int2>::iterator it = texture_size.begin(); it != texture_size.end(); ++it) {
4525 if (it->second.x > masksize_max.
x) {
4526 masksize_max.
x = it->second.x;
4528 if (it->second.y > masksize_max.
y) {
4529 masksize_max.
y = it->second.y;
4533 bool *maskdata = (
bool *) malloc(Ntextures * masksize_max.
x * masksize_max.
y *
sizeof(
bool));
4536 for (std::map<std::string, helios::int2>::iterator it = texture_size.begin(); it != texture_size.end(); ++it) {
4537 std::string texture_file = it->first;
4539 int ID = textures.at(texture_file);
4541 masksize[ID] = it->second;
4544 for (
int j = 0; j < masksize_max.
y; j++) {
4545 for (
int i = 0; i < masksize_max.
x; i++) {
4547 if (i < texture_size.at(texture_file).x && j < texture_size.at(texture_file).y) {
4548 maskdata[ID * masksize_max.
x * masksize_max.
y + ind] = texture_data.at(texture_file).at(j).at(i);
4550 maskdata[ID * masksize_max.
x * masksize_max.
y + ind] =
false;
4567 float thetamin = thetarange.
x;
4568 float thetamax = thetarange.
y;
4570 float phimin = phirange.
x;
4571 float phimax = phirange.
y;
4575 std::vector<helios::vec3> raydir;
4576 raydir.resize(Ntheta * Nphi);
4578 for (
uint j = 0; j < Nphi; j++) {
4579 float phi = phimin + float(j) * (phimax - phimin) /
float(Nphi - 1);
4580 for (
uint i = 0; i < Ntheta; i++) {
4581 float theta_z = thetamin + float(i) * (thetamax - thetamin) /
float(Ntheta - 1);
4582 float theta_elev = 0.5f *
M_PI - theta_z;
4584 raydir.at(Ntheta * j + i) = dir;
4588 size_t N = Ntheta * Nphi;
4591 std::vector<uint> bb_hit(N, 0);
4598 bool origin_inside_bb = (scan_origin.
x >= bb_min.
x && scan_origin.
x <= bb_max.
x &&
4599 scan_origin.
y >= bb_min.
y && scan_origin.
y <= bb_max.
y &&
4600 scan_origin.
z >= bb_min.
z && scan_origin.
z <= bb_max.
z);
4602 for (
size_t r = 0; r < N; r++) {
4604 if (origin_inside_bb) {
4612 float tx_min, tx_max, ty_min, ty_max, tz_min, tz_max;
4614 float a = 1.0f / ray_dir.
x;
4616 tx_min = (bb_min.
x - scan_origin.
x) * a;
4617 tx_max = (bb_max.
x - scan_origin.
x) * a;
4619 tx_min = (bb_max.
x - scan_origin.
x) * a;
4620 tx_max = (bb_min.
x - scan_origin.
x) * a;
4623 float b = 1.0f / ray_dir.
y;
4625 ty_min = (bb_min.
y - scan_origin.
y) * b;
4626 ty_max = (bb_max.
y - scan_origin.
y) * b;
4628 ty_min = (bb_max.
y - scan_origin.
y) * b;
4629 ty_max = (bb_min.
y - scan_origin.
y) * b;
4632 float c = 1.0f / ray_dir.
z;
4634 tz_min = (bb_min.
z - scan_origin.
z) * c;
4635 tz_max = (bb_max.
z - scan_origin.
z) * c;
4637 tz_min = (bb_max.
z - scan_origin.
z) * c;
4638 tz_max = (bb_min.
z - scan_origin.
z) * c;
4643 if (ty_min > t0) t0 = ty_min;
4644 if (tz_min > t0) t0 = tz_min;
4648 if (ty_max < t1) t1 = ty_max;
4649 if (tz_max < t1) t1 = tz_max;
4651 if (t0 < t1 && t1 > 1e-6f) {
4657 size_t total_scan_rays = Ntheta * Nphi;
4660 for (
int i = 0; i < total_scan_rays; i++) {
4661 if (bb_hit[i] == 1) {
4664 hit_out += sin(dir.
zenith);
4669 std::vector<helios::vec3> base_directions;
4670 base_directions.reserve(N);
4671 std::vector<helios::int2> pulse_scangrid_ij(N);
4674 for (
int i = 0; i < Ntheta * Nphi; i++) {
4675 if (bb_hit[i] == 1) {
4677 base_directions.push_back(raydir.at(i));
4679 int jj = floor(i / Ntheta);
4680 int ii = i - jj * Ntheta;
4692 if (record_misses) {
4693 float miss_dist = 1001.0f;
4694 for (
int i = 0; i < Ntheta * Nphi; i++) {
4695 std::map<std::string, double> data;
4696 data[
"target_index"] = 0;
4697 data[
"target_count"] = 1;
4698 data[
"deviation"] = 0.0;
4699 data[
"timestamp"] = i;
4700 data[
"intensity"] = 1.0;
4701 data[
"distance"] = miss_dist;
4702 data[
"nRaysHit"] = Npulse;
4708 }
else if (printmessages) {
4709 std::cout <<
"WARNING: Synthetic rays did not hit any primitives." << std::endl;
4715 float *hit_t = (
float *) malloc(N * Npulse *
sizeof(
float));
4716 float *hit_fnorm = (
float *) malloc(N * Npulse *
sizeof(
float));
4717 int *hit_ID = (
int *) malloc(N * Npulse *
sizeof(
int));
4725 for (
size_t beam = 0; beam < N; beam++) {
4728 for (
int p = 0; p < Npulse; p++) {
4729 if (p == 0 || beam_divergence == 0.0f) {
4732 direction[beam * Npulse + p] = base_dir;
4736 float ru = context->
randu();
4737 float rv = context->
randu();
4739 float theta_offset = beam_divergence * sqrt(ru);
4740 float phi_offset = 2.0f *
M_PI * rv;
4746 float new_elevation = base_spherical.
elevation + theta_offset * cos(phi_offset);
4747 float new_azimuth = base_spherical.
azimuth + theta_offset * sin(phi_offset) / fmax(cos(base_spherical.
elevation), 1e-6f);
4753 direction[beam * Npulse + p] = perturbed_dir;
4761 if (exit_diameter > 0.0f) {
4763 float radius = exit_diameter * 0.5f;
4765 for (
size_t beam = 0; beam < N; beam++) {
4774 for (
int p = 0; p < Npulse; p++) {
4776 float ru = context->
randu();
4777 float rv = context->
randu();
4778 float r_sample = radius * sqrtf(ru);
4779 float theta = 2.0f *
M_PI * rv;
4780 float x_disk = r_sample * cosf(theta);
4781 float y_disk = r_sample * sinf(theta);
4785 ray_origins[beam * Npulse + p] = scan_origin + offset;
4790 for (
size_t i = 0; i < N * Npulse; i++) {
4791 ray_origins[i] = scan_origin;
4802 size_t beams_with_zero_hits = 0;
4803 size_t beams_with_one_hit = 0;
4804 size_t beams_with_multi_hits = 0;
4807 for (
size_t r = 0; r < N; r++) {
4809 std::vector<std::vector<float>> t_pulse;
4810 std::vector<std::vector<float>> t_hit;
4813 for (
size_t p = 0; p < Npulse; p++) {
4815 float t = hit_t[r * Npulse + p];
4816 float i = hit_fnorm[r * Npulse + p];
4817 float ID = float(hit_ID[r * Npulse + p]);
4819 if (record_misses || (!record_misses && t < miss_distance)) {
4820 std::vector<float> v{t, i, ID};
4821 t_pulse.push_back(v);
4825 if (t_pulse.size() == 0) {
4827 beams_with_zero_hits++;
4828 }
else if (t_pulse.size() == 1) {
4829 beams_with_one_hit++;
4831 float distance = t_pulse.front().at(0);
4832 float intensity = t_pulse.front().at(1);
4833 if (distance >= 0.98f * miss_distance) {
4836 float nPulseHit = 1;
4837 float IDmap = t_pulse.front().at(2);
4839 std::vector<float> v{distance, intensity, nPulseHit, IDmap};
4842 }
else if (t_pulse.size() > 1) {
4843 beams_with_multi_hits++;
4845 std::sort(t_pulse.begin(), t_pulse.end(), [](
const std::vector<float> &a,
const std::vector<float> &b) {
4849 float t0 = t_pulse.at(0).at(0);
4850 float d = t_pulse.at(0).at(0);
4851 float f = t_pulse.at(0).at(1);
4855 for (
size_t hit = 1; hit <= t_pulse.size(); hit++) {
4858 if (hit == t_pulse.size()) {
4860 float distance = d / float(count);
4861 float intensity = f / float(Npulse);
4862 if (distance >= 0.98f * miss_distance) {
4865 float nPulseHit = float(count);
4866 float IDmap = t_pulse.at(hit - 1).at(2);
4869 if (nPulseHit == Npulse & distance >= 0.98f * miss_distance) {
4870 std::vector<float> v{distance, 1.0, nPulseHit, IDmap};
4874 std::vector<float> v{distance, intensity, nPulseHit, IDmap};
4880 }
else if (t_pulse.at(hit).at(0) - t0 > pulse_distance_threshold) {
4882 float distance = d / float(count);
4883 float intensity = f / float(Npulse);
4884 if (distance >= 0.98f * miss_distance) {
4887 float nPulseHit = float(count);
4888 float IDmap = t_pulse.at(hit - 1).at(2);
4891 if (nPulseHit == Npulse & distance >= 0.98f * miss_distance) {
4892 std::vector<float> v{distance, 1.0, nPulseHit, IDmap};
4896 std::vector<float> v{distance, intensity, nPulseHit, IDmap};
4902 d = t_pulse.at(hit).at(0);
4903 t0 = t_pulse.at(hit).at(0);
4904 f = t_pulse.at(hit).at(1);
4910 d += t_pulse.at(hit).at(0);
4911 f += t_pulse.at(hit).at(1);
4917 for (
size_t hit = 0; hit < t_hit.size(); hit++) {
4918 average += t_hit.at(hit).at(0) / float(t_hit.size());
4922 int non_miss_count = 0;
4923 for (
size_t hit = 0; hit < t_hit.size(); hit++) {
4924 if (t_hit.at(hit).at(0) < 0.98f * 1001.0f) {
4929 int real_hit_index = 0;
4930 for (
size_t hit = 0; hit < t_hit.size(); hit++) {
4932 std::map<std::string, double> data;
4935 bool is_miss = (t_hit.at(hit).at(0) >= 0.98f * 1001.0f);
4939 data[
"target_index"] = 99;
4941 data[
"target_index"] = real_hit_index;
4945 data[
"target_count"] = t_hit.size();
4946 data[
"deviation"] = fabs(t_hit.at(hit).at(0) - average);
4947 data[
"timestamp"] = pulse_scangrid_ij.at(r).y * Ntheta + pulse_scangrid_ij.at(r).x;
4948 data[
"intensity"] = t_hit.at(hit).at(1);
4949 data[
"distance"] = t_hit.at(hit).at(0);
4950 data[
"nRaysHit"] = t_hit.at(hit).at(2);
4952 float UUID = t_hit.at(hit).at(3);
4953 if (UUID >= 0 && UUID < ID_mapping.size()) {
4954 UUID = ID_mapping.at(
int(t_hit.at(hit).at(3)));
4966 data[
"object_label"] = double(label);
4972 data.at(
"intensity") *= rho;
4978 helios::vec3 p = scan_origin + dir * t_hit.at(hit).at(0);
4992 if (printmessages) {
4993 std::cout <<
"Created synthetic scan #" << s <<
" with " << Nhits <<
" hit points." << std::endl;
4999 free(patch_textureID);
5002 free(tri_textureID);