1.3.77
 
Loading...
Searching...
No Matches
LiDAR.h
Go to the documentation of this file.
1
16#ifndef LIDARPLUGIN
17#define LIDARPLUGIN
18
19#include "CollisionDetection.h"
20#include "Context.h"
21#include "Visualizer.h"
22
23#include "triangulation_cdt.h"
24
25#include <functional>
26
27template<class datatype>
28class HitTable {
29public:
30 uint Ntheta, Nphi;
31
32 HitTable(void) {
33 Ntheta = 0;
34 Nphi = 0;
35 }
36 HitTable(const int nx, const int ny) {
37 Ntheta = nx;
38 Nphi = ny;
39 data.resize(Nphi);
40 for (int j = 0; j < Nphi; j++) {
41 data.at(j).resize(Ntheta);
42 }
43 }
44 HitTable(const int nx, const int ny, const datatype initval) {
45 Ntheta = nx;
46 Nphi = ny;
47 data.resize(Nphi);
48 for (int j = 0; j < Nphi; j++) {
49 data.at(j).resize(Ntheta, initval);
50 }
51 }
52
53 datatype get(const int i, const int j) const {
54 if (i >= 0 && i < Ntheta && j >= 0 && j < Nphi) {
55 return data.at(j).at(i);
56 } else {
57 helios::helios_runtime_error("ERROR (hit_map.get): get index out of range. Attempting to get index map at (" + std::to_string(i) + "," + std::to_string(j) + "), but size of scan is " + std::to_string(Ntheta) + " x " +
58 std::to_string(Nphi) + ".");
59 }
60 return data.at(j).at(i); // unreachable; silences control-reaches-end-of-non-void-function warning
61 }
62 void set(const int i, const int j, const datatype value) {
63 if (i >= 0 && i < Ntheta && j >= 0 && j < Nphi) {
64 data.at(j).at(i) = value;
65 } else {
66 helios::helios_runtime_error("ERROR (hit_map.set): set index out of range. Attempting to set index map at (" + std::to_string(i) + "," + std::to_string(j) + "), but size of scan is " + std::to_string(Ntheta) + " x " +
67 std::to_string(Nphi) + ".");
68 }
69 }
70 void resize(const int nx, const int ny, const datatype initval) {
71 Ntheta = nx;
72 Nphi = ny;
73 data.resize(Nphi);
74 for (int j = 0; j < Nphi; j++) {
75 data.at(j).resize(Ntheta);
76 for (int i = 0; i < Ntheta; i++) {
77 data.at(j).at(i) = initval;
78 }
79 }
80 }
81
82private:
83 std::vector<std::vector<datatype>> data;
84};
85
86struct HitPoint {
87 helios::vec3 position;
88 helios::SphericalCoord direction;
89 helios::int2 row_column;
90 helios::RGBcolor color;
91 int gridcell;
92 int scanID;
93 // NOTE: per-hit scalar data (intensity, distance, timestamp, custom labels, ...) is NOT stored
94 // here. It lives in cloud-level columnar storage on LiDARcloud (hit_data_columns/hit_data_present),
95 // indexed by the hit's position in the `hits` vector. Storing it as N independent
96 // std::map<std::string,double> trees (one per hit) made bulk field extraction O(K*N) cache-cold
97 // tree descents and dominated export time. See LiDARcloud::getHitData / getHitDataColumn.
98 HitPoint(void) {
99 position = helios::make_vec3(0, 0, 0);
100 direction = helios::make_SphericalCoord(0, 0);
101 row_column = helios::make_int2(0, 0);
102 color = helios::RGB::red;
103 gridcell = -2;
104 scanID = -1;
105 }
106 HitPoint(int __scanID, helios::vec3 __position, helios::SphericalCoord __direction, helios::int2 __row_column, helios::RGBcolor __color) {
107 scanID = __scanID;
108 position = __position;
109 direction = __direction;
110 row_column = __row_column;
111 color = __color;
112 gridcell = -2;
113 }
114};
115
117 helios::vec3 vertex0, vertex1, vertex2;
118 int ID0, ID1, ID2;
119 int scanID;
120 int gridcell;
121 helios::RGBcolor color;
122 float area;
123 Triangulation(void) {
124 vertex0 = helios::make_vec3(0, 0, 0);
125 vertex1 = helios::make_vec3(0, 0, 0);
126 vertex2 = helios::make_vec3(0, 0, 0);
127 ID0 = 0;
128 ID1 = 0;
129 ID2 = 0;
130 scanID = -1;
131 gridcell = -2;
132 color = helios::RGB::green;
133 area = 0;
134 }
135 Triangulation(int __scanID, helios::vec3 __vertex0, helios::vec3 __vertex1, helios::vec3 __vertex2, int __ID0, int __ID1, int __ID2, helios::RGBcolor __color, int __gridcell) {
136 scanID = __scanID;
137 vertex0 = __vertex0;
138 vertex1 = __vertex1;
139 vertex2 = __vertex2;
140 ID0 = __ID0;
141 ID1 = __ID1;
142 ID2 = __ID2;
143 gridcell = __gridcell;
144 color = __color;
145
146 // calculate area
147 helios::vec3 s0 = vertex1 - vertex0;
148 helios::vec3 s1 = vertex2 - vertex0;
149 helios::vec3 s2 = vertex2 - vertex1;
150
151 float a = s0.magnitude();
152 float b = s1.magnitude();
153 float c = s2.magnitude();
154 float s = 0.5f * (a + b + c);
155
156 area = sqrt(s * (s - a) * (s - b) * (s - c));
157 }
158};
159
160struct GridCell {
161 helios::vec3 center;
162 helios::vec3 global_anchor;
163 helios::vec3 size;
164 helios::vec3 global_size;
165 helios::int3 global_ijk;
166 helios::int3 global_count;
169 float leaf_area;
170 float Gtheta;
171 float ground_height;
172 float vegetation_height;
173 float maximum_height;
174 // ---- LAD inversion uncertainty (sufficient statistics + sampling variance) ----
175 // These quantify the statistical SAMPLING uncertainty of the leaf-area inversion,
176 // conditional on the beams that entered the voxel (Pimont et al. 2018, RSE 215:343-370).
177 // They do NOT capture occlusion/coverage bias (voxels shadowed so beams never penetrate).
178 int beam_count = -1;
179 float I_rdi = 0.f;
180 float zbar_e = 0.f;
181 float var_path = 0.f;
182 float L1_element = -1.f;
183 float LAD_variance = -1.f;
184 bool ci_valid = false;
185 GridCell(helios::vec3 __center, helios::vec3 __global_anchor, helios::vec3 __size, helios::vec3 __global_size, float __azimuthal_rotation, helios::int3 __global_ijk, helios::int3 __global_count) {
186 center = __center;
187 global_anchor = __global_anchor;
188 size = __size;
189 global_size = __global_size;
190 azimuthal_rotation = __azimuthal_rotation;
191 global_ijk = __global_ijk;
192 global_count = __global_count;
193 leaf_area = 0;
194 Gtheta = 0;
195 ground_height = 0;
196 vegetation_height = 0;
197 maximum_height = 0;
198 }
199};
200
202
223
225
242
244
260
262
280
282
316
318
327
329 ScanMetadata();
330
332
350 const std::vector<std::string> &columnFormat, float scanTiltRoll = 0.f, float scanTiltPitch = 0.f, float scanAzimuthOffset = 0.f);
351
353
377 ScanMetadata(const helios::vec3 &origin, const std::vector<float> &beamZenithAngles, uint Nphi, float phiMin, float phiMax, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev,
378 const std::vector<std::string> &columnFormat, float scanTiltRoll = 0.f, float scanTiltPitch = 0.f, float scanAzimuthOffset = 0.f);
379
381 std::string data_file;
382
386
389 float thetaMin;
391
394 float thetaMax;
395
399
402 float phiMin;
404
407 float phiMax;
408
411
413
417
419
423
425
433
435
445
447
454
456
465
467
475 int maxReturns = 1;
476
478
485 float pulseWidth = 0.f;
486
488
501 float detectionThreshold = 0.05f;
502
504
517
519
528
530
544
546 std::vector<std::string> columnFormat;
547
549
553
555
560 std::vector<float> beamZenithAngles;
561
563
568 std::vector<RisleyPrism> risley_prisms;
569
571
573
575
580 helios::SphericalCoord rc2direction(uint row, uint column) const;
581
583
587 helios::int2 direction2rc(const helios::SphericalCoord &direction) const;
588
589 // ---- Moving-platform (mobile/airborne) scan support ----
590 // When isMoving is true the scanner pose is driven by a dense timestamped 6-DOF trajectory and the
591 // synthetic scan generates a per-pulse origin and orientation instead of a single static origin. The
592 // static scanTilt_roll/pitch/azimuth fields are NOT applied in this mode: attitude comes entirely from
593 // the trajectory quaternions composed with the fixed boresight misalignment. When isMoving is false the
594 // scan behaves exactly as before (single static origin). Populated via \ref LiDARcloud::addScanMoving().
595
597 bool isMoving = false;
598
600 std::vector<double> traj_t;
601
603 std::vector<helios::vec3> traj_pos;
604
606
610 std::vector<helios::vec4> traj_quat;
611
613 helios::vec3 lever_arm = helios::make_vec3(0, 0, 0);
614
616 helios::vec3 boresight_rpy = helios::make_vec3(0, 0, 0);
617
620 double pulse_period = 1.0;
621
623 double t0 = 0.0;
624
625 // ---- Self-describing acquisition descriptors ----
626 // These make a scan introspectable ("how was it acquired / how fast did it spin / how many revolutions?")
627 // without reverse-engineering the answer from scanPattern, isMoving, and phiMax. They are set by the
628 // high-level scan-creation entry points (\ref LiDARcloud::addScanSpinning, \ref LiDARcloud::addScanMovingRaster,
629 // etc.) and are purely descriptive; the underlying mechanism is still scanPattern + isMoving.
630
633
636
638
639 double rotation_rate = 0.0;
640
642
643 double n_revolutions = 0.0;
644
646
655 void poseAt(double t, helios::vec3 &pos, helios::vec4 &quat) const;
656};
657
660private:
661 size_t Nhits;
662
663 std::vector<ScanMetadata> scans;
664
665 std::vector<HitPoint> hits;
666
667 // ---- Columnar per-hit scalar data ----
668 // Per-hit scalar fields are stored column-wise (one contiguous array per label) rather than as one
669 // std::map<std::string,double> per hit. This makes bulk field extraction a cache-linear pass instead
670 // of N cache-cold red-black-tree lookups, matching the speed of XYZ/RGB export.
671 // INVARIANT: every column in hit_data_columns and every mask in hit_data_present has length
672 // hits.size() at all times. Column slot `s` holds label hit_data_labels[s]; a value is meaningful
673 // only where hit_data_present[s][i] != 0 (a hit may be missing a value for a label). The values
674 // for slot s of hit i are hit_data_columns[s][i]. These structures are kept in lockstep with the
675 // `hits` vector everywhere it is mutated (addHitPoint, deleteHitPoint's swap-and-pop, clearHits).
676 std::vector<std::string> hit_data_labels;
677 std::unordered_map<std::string, size_t> hit_data_label_index;
678 std::vector<std::vector<double>> hit_data_columns;
679 std::vector<std::vector<char>> hit_data_present;
680
683 size_t getOrCreateHitDataColumn(const std::string &label);
684
687 void appendHitData(const std::map<std::string, double> &data);
688
690 void clearHits();
691
694 void transformHitOrigin(uint index, const std::function<helios::vec3(const helios::vec3 &)> &transform);
695
697 helios::vec3 hitOriginOrFallback(uint index, const helios::vec3 &fallback) const;
698
699 std::vector<GridCell> grid_cells;
700
704 bool force_bruteforce_LAD = false;
705
706 std::vector<Triangulation> triangles;
707
709 std::vector<HitTable<int>> hit_tables;
710
712 bool hitgridcellcomputed;
713
715 bool triangulationcomputed;
716
721 std::size_t triangulation_candidate_count;
722 std::size_t triangulation_dropped_lmax;
723 std::size_t triangulation_dropped_aspect;
724 std::size_t triangulation_dropped_degenerate;
725
729 int getContainingGridCell(const helios::vec3 &p) const;
730
732 bool printmessages;
733
735 std::function<void(float, const std::string &)> progress_callback;
736
744 size_t synthetic_scan_memory_budget_bytes = 0; // 0 => automatic (see syntheticScan)
745
747 static constexpr size_t SYNTHETIC_SCAN_DEFAULT_BUDGET_CPU = size_t(4) * 1024 * 1024 * 1024; // 4 GiB
748
750 static constexpr size_t SYNTHETIC_SCAN_DEFAULT_BUDGET_GPU = size_t(8) * 1024 * 1024 * 1024; // 8 GiB
751
753 CollisionDetection *collision_detection;
754
757 volatile int *cancel_flag = nullptr;
758
764 volatile int *synthetic_scan_progress = nullptr;
765
767
773 void prepareUnifiedRayTracing(helios::Context *context);
774
776 void finishUnifiedRayTracing();
777
779
795 void castRaysUnified(size_t total_rays, helios::vec3 *ray_origins, helios::vec3 *direction, float *hit_t, float *hit_fnorm, int *hit_ID, size_t packet_size = 1);
796
797 // -------- I/O --------- //
798
799 // -------- RECONSTRUCTION --------- //
800
801 // first index: leaf group, second index: triangle #
802 std::vector<std::vector<Triangulation>> reconstructed_triangles;
803
804 std::vector<std::vector<Triangulation>> reconstructed_trunk_triangles;
805
806 std::vector<helios::vec3> reconstructed_alphamasks_center;
807 std::vector<helios::vec2> reconstructed_alphamasks_size;
808 std::vector<helios::SphericalCoord> reconstructed_alphamasks_rotation;
809 std::vector<uint> reconstructed_alphamasks_gridcell;
810 std::string reconstructed_alphamasks_maskfile;
811 std::vector<uint> reconstructed_alphamasks_direct_flag;
812
813 void leafReconstructionFloodfill();
814
815 void backfillLeavesAlphaMask(const std::vector<float> &leaf_size, float leaf_aspect_ratio, float solidfraction, const std::vector<bool> &group_filter_flag);
816
817 void calculateLeafAngleCDF(uint Nbins, std::vector<std::vector<float>> &CDF_theta, std::vector<std::vector<float>> &CDF_phi);
818
819 void 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);
820
821 // -------- HELPERS --------- //
822
824
830 void computeGtheta(uint Ncells, uint Nscans, std::vector<float> &Gtheta, std::vector<float> &Gtheta_bar);
831
833
837 bool anyScanMoving() const;
838
840
850 void calculateLeafArea_inner(helios::Context *context, int min_voxel_hits, float element_width, float supplied_Gtheta);
851
853
864 bool invertLAD(uint voxel_index, float P, float Gtheta, const std::vector<float> &dr_samples, int min_voxel_hits, const helios::vec3 &gridsize, float &leaf_area, helios::WarningAggregator &warnings);
865
867 struct LADInversionResult {
868 float leaf_area = 0.f;
869 float LAD_variance = -1.f;
870 int beam_count = 0;
871 float I_rdi = 0.f;
872 float zbar_e = 0.f;
873 float var_path = 0.f;
874 float L1_element = -1.f;
875 bool converged = false;
876 bool element_size_known = false;
877 };
878
880
895 LADInversionResult invertLADWithVariance(uint voxel_index, float P, float Gtheta, const std::vector<float> &dr_samples, float sum_frac_sq, float element_width, int min_voxel_hits, const helios::vec3 &gridsize,
896 helios::WarningAggregator &warnings);
897
899
905 bool ciValidPimont(float L, float L1, int N, float confidence_level) const;
906
907 // -------- MULTI-RETURN HELPERS --------- //
908
910
913 bool isMultiReturnData() const;
914
916
920 struct BeamGrouping {
921 uint Nbeams = 0;
922 std::vector<uint> beam_members;
923 std::vector<uint> beam_offsets;
924
926 uint beamSize(uint k) const {
927 return beam_offsets[k + 1] - beam_offsets[k];
928 }
929 };
930
932
936 BeamGrouping groupHitsByTimestamp(const std::vector<uint> &scan_indices) const;
937
939
945 struct VoxelLattice {
946 bool valid = false;
947 helios::vec3 origin;
948 helios::vec3 anchor;
949 helios::vec3 cell_extent;
950 float rotation = 0.f;
951 helios::int3 count;
952 std::vector<int> ijk_to_index;
953 };
954
956
958 VoxelLattice detectVoxelLattice() const;
959
961
975 static void accumulateBeamCell(const uint *return_indices, size_t Nreturns, const std::vector<float> &dr, const std::vector<uint> &hit_location, float &P_equal_numerator, float &P_equal_denominator, float &P_equal_sumsq,
976 std::vector<float> &dr_array_cell);
977
979
987 std::vector<uint> loadTreeQSM_impl(helios::Context *context, const std::string &filename, uint radial_subdivisions, bool use_colormap, const std::string &colormap_or_texture);
988
990
996 std::vector<helios::vec3> gapfillMisses_timestamp(uint scanID, const bool gapfill_grid_only, const bool add_flags);
997
999
1012 std::vector<helios::vec3> gapfillMisses_rowcolumn(uint scanID, const bool add_flags);
1013
1014public:
1016 LiDARcloud();
1017
1019 ~LiDARcloud();
1020
1022 static int selfTest(int argc = 0, char **argv = nullptr);
1023
1024 void validateRayDirections();
1025
1027 void disableMessages();
1028
1030 void enableMessages();
1031
1033
1038 void setProgressCallback(std::function<void(float, const std::string &)> callback);
1039
1041
1050 void setCancelFlag(volatile int *flag);
1051
1053
1061 void setSyntheticScanProgressPointer(volatile int *ptr);
1062
1065
1067 void performUnifiedRayTracing(helios::Context *context, size_t N, int Npulse, helios::vec3 *ray_origins, helios::vec3 *direction, float *hit_t, float *hit_fnorm, int *hit_ID);
1068
1070
1085 [[nodiscard]] static float applyRangeIntensityCorrection(float intensity, float distance);
1086
1087 // ------- SCANS -------- //
1088
1091
1093
1097 uint addScan(ScanMetadata &newscan);
1098
1100
1120 uint addScanMoving(ScanMetadata scan, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec4> &traj_quat, const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy,
1121 float pulse_rate_hz, double t0 = 0.0);
1122
1124
1141 uint addScanMoving(ScanMetadata scan, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec3> &traj_rpy, const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy,
1142 float pulse_rate_hz, double t0 = 0.0);
1143
1145
1180 uint addScanSpinning(const std::vector<float> &beamElevationAngles, float azimuthStep_rad, float pulse_rate_hz, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec4> &traj_quat,
1181 const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev,
1182 const std::vector<std::string> &columnFormat = {"x", "y", "z"}, double t0 = 0.0);
1183
1185
1205 uint addScanSpinning(const std::vector<float> &beamElevationAngles, float azimuthStep_rad, float pulse_rate_hz, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec3> &traj_rpy,
1206 const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev,
1207 const std::vector<std::string> &columnFormat = {"x", "y", "z"}, double t0 = 0.0);
1208
1210
1236 uint addScanMovingRaster(uint Ntheta, float thetaMin, float thetaMax, uint Nphi, float phiMin, float phiMax, float pulse_rate_hz, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos,
1237 const std::vector<helios::vec4> &traj_quat, const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev,
1238 const std::vector<std::string> &columnFormat = {"x", "y", "z"}, double t0 = 0.0);
1239
1241
1277 uint addScanRisley(const std::vector<RisleyPrism> &prisms, double refractive_index_air, float pulse_rate_hz, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec4> &traj_quat,
1278 const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev, const std::vector<std::string> &columnFormat = {"x", "y", "z"},
1279 double t0 = 0.0);
1280
1282
1302 uint addScanRisley(const std::vector<RisleyPrism> &prisms, double refractive_index_air, float pulse_rate_hz, const std::vector<double> &traj_t, const std::vector<helios::vec3> &traj_pos, const std::vector<helios::vec3> &traj_rpy,
1303 const helios::vec3 &lever_arm, const helios::vec3 &boresight_rpy, float exitDiameter, float beamDivergence, float rangeNoiseStdDev, float angleNoiseStdDev, const std::vector<std::string> &columnFormat = {"x", "y", "z"},
1304 double t0 = 0.0);
1305
1307
1313 void addHitPoint(uint scanID, const helios::vec3 &xyz, const helios::SphericalCoord &direction);
1314
1316
1323 void addHitPoint(uint scanID, const helios::vec3 &xyz, const helios::SphericalCoord &direction, const helios::RGBcolor &color);
1324
1326
1332 void addHitPoint(uint scanID, const helios::vec3 &xyz, const helios::SphericalCoord &direction, const std::map<std::string, double> &data);
1333
1335
1342 void addHitPoint(uint scanID, const helios::vec3 &xyz, const helios::SphericalCoord &direction, const helios::RGBcolor &color, const std::map<std::string, double> &data);
1343
1345
1352 void addHitPoint(uint scanID, const helios::vec3 &xyz, const helios::int2 &row_column, const helios::RGBcolor &color, const std::map<std::string, double> &data);
1353
1355
1358 void deleteHitPoint(uint index);
1359
1361 uint getHitCount() const;
1362
1364
1367 helios::vec3 getScanOrigin(uint scanID) const;
1368
1370
1373 uint getScanSizeTheta(uint scanID) const;
1374
1376
1379 uint getScanSizePhi(uint scanID) const;
1380
1382
1386 helios::vec2 getScanRangeTheta(uint scanID) const;
1387
1389
1393 helios::vec2 getScanRangePhi(uint scanID) const;
1394
1396
1400 float getScanBeamExitDiameter(uint scanID) const;
1401
1403
1406 std::vector<std::string> getScanColumnFormat(uint scanID) const;
1407
1409
1413 ScanPattern getScanPattern(uint scanID) const;
1414
1416
1420 std::vector<float> getScanBeamZenithAngles(uint scanID) const;
1421
1423
1427 ScanMode getScanMode(uint scanID) const;
1428
1430
1434 uint getScanStepsPerRev(uint scanID) const;
1435
1437
1441 double getScanRotationRate(uint scanID) const;
1442
1444
1448 double getScanRevolutions(uint scanID) const;
1449
1451
1455 std::vector<RisleyPrism> getScanRisleyPrisms(uint scanID) const;
1456
1458
1462 double getScanRisleyRefractiveIndexAir(uint scanID) const;
1463
1465
1469 float getScanBeamDivergence(uint scanID) const;
1470
1472
1476 float getScanRangeNoiseStdDev(uint scanID) const;
1477
1479
1483 float getScanAngleNoiseStdDev(uint scanID) const;
1484
1486
1490 ReturnMode getScanReturnMode(uint scanID) const;
1491
1493
1497 void setScanReturnMode(uint scanID, ReturnMode returnMode);
1498
1500
1505
1507
1512
1514
1518 int getScanMaxReturns(uint scanID) const;
1519
1521
1525 void setScanMaxReturns(uint scanID, int maxReturns);
1526
1528
1532 float getScanPulseWidth(uint scanID) const;
1533
1535
1539 void setScanPulseWidth(uint scanID, float pulseWidth);
1540
1542
1546 float getScanDetectionThreshold(uint scanID) const;
1547
1549
1553 void setScanDetectionThreshold(uint scanID, float detectionThreshold);
1554
1556
1560 float getScanTiltRoll(uint scanID) const;
1561
1563
1567 float getScanTiltPitch(uint scanID) const;
1568
1570
1574 float getScanAzimuthOffset(uint scanID) const;
1575
1577
1580 helios::vec3 getHitXYZ(uint index) const;
1581
1583
1590 helios::vec3 getHitOrigin(uint index) const;
1591
1593
1597
1599
1604 double getHitData(uint index, const char *label) const;
1605
1607
1612 void setHitData(uint index, const char *label, double value);
1613
1615
1619 bool doesHitDataExist(uint index, const char *label) const;
1620
1622
1628 int getHitDataColumnIndex(const char *label) const;
1629
1631
1641 void getHitDataColumn(const char *label, std::vector<double> &data, double absent_value = -9999) const;
1642
1644
1651 static constexpr float LIDAR_MISS_DISTANCE = 20000.f;
1652
1654
1660 static constexpr float LIDAR_RAYTRACE_MISS_T = 1001.f;
1661
1663
1670 bool isHitMiss(uint index) const;
1671
1673
1678 bool hasMisses() const;
1679
1681
1684 helios::RGBcolor getHitColor(uint index) const;
1685
1687
1690 int getHitScanID(uint index) const;
1691
1693
1699 int getHitIndex(uint scanID, uint row, uint column) const;
1700
1702
1707 int getHitGridCell(uint index) const;
1708
1710
1714 void setHitGridCell(uint index, int cell);
1715
1717
1720 void coordinateShift(const helios::vec3 &shift);
1721
1723
1727 void coordinateShift(uint scanID, const helios::vec3 &shift);
1728
1730
1733 void coordinateRotation(const helios::SphericalCoord &rotation);
1734
1736
1740 void coordinateRotation(uint scanID, const helios::SphericalCoord &rotation);
1741
1743
1748 void coordinateRotation(float rotation, const helios::vec3 &line_base, const helios::vec3 &line_direction);
1749
1751 uint getTriangleCount() const;
1752
1756 std::size_t getTriangulationCandidateCount() const;
1757
1760 std::size_t getTriangulationDroppedByLmax() const;
1761
1766 std::size_t getTriangulationDroppedByAspect() const;
1767
1770 std::size_t getTriangulationDroppedByDegenerate() const;
1771
1773
1777 Triangulation getTriangle(uint index) const;
1778
1779 // ------- FILE I/O --------- //
1780
1782
1785 void loadXML(const char *filename);
1786
1788
1792 void loadXML(const char *filename, bool load_grid_only);
1793
1795
1800 size_t loadASCIIFile(uint scanID, const std::string &ASCII_data_file);
1801
1803
1806 void exportTriangleNormals(const char *filename);
1807
1809
1813 void exportTriangleNormals(const char *filename, int gridcell);
1814
1816
1819 void exportTriangleAreas(const char *filename);
1820
1822
1826 void exportTriangleAreas(const char *filename, int gridcell);
1827
1830
1834 void exportTriangleInclinationDistribution(const char *filename, uint Nbins);
1835
1838
1842 void exportTriangleAzimuthDistribution(const char *filename, uint Nbins);
1843
1845
1848 void exportLeafAreas(const char *filename);
1849
1851
1854 void exportLeafAreaDensities(const char *filename);
1855
1857
1860 void exportGtheta(const char *filename);
1861
1863
1869 void exportLeafAreaUncertainty(const char *filename);
1870
1872
1878 void exportPointCloud(const char *filename, bool write_header = true);
1879
1881
1887 void exportPointCloud(const char *filename, uint scanID, bool write_header = true);
1888
1890
1894 void exportPointCloudPTX(const char *filename, uint scanID);
1895
1897
1903 void exportScans(const char *filename);
1904
1905 // ------- VISUALIZER --------- //
1906
1908
1912 void addHitsToVisualizer(Visualizer *visualizer, uint pointsize) const;
1913
1915
1920 void addHitsToVisualizer(Visualizer *visualizer, uint pointsize, const helios::RGBcolor &point_color) const;
1921
1923
1928 void addHitsToVisualizer(Visualizer *visualizer, uint pointsize, const char *color_value) const;
1929
1931
1934 void addGridToVisualizer(Visualizer *visualizer) const;
1935
1937
1941 void addGridWireFrametoVisualizer(Visualizer *visualizer, float linewidth_pixels = 1.0f) const;
1942
1944
1950 void addGrid(const helios::vec3 &center, const helios::vec3 &size, const helios::int3 &ndiv, float rotation);
1951
1953
1956 void addTrianglesToVisualizer(Visualizer *visualizer) const;
1957
1959
1963 void addTrianglesToVisualizer(Visualizer *visualizer, uint gridcell) const;
1964
1966
1969 void addLeafReconstructionToVisualizer(Visualizer *visualizer) const;
1970
1972
1975 void addTrunkReconstructionToVisualizer(Visualizer *visualizer) const;
1976
1978
1982 void addTrunkReconstructionToVisualizer(Visualizer *visualizer, const helios::RGBcolor &trunk_color) const;
1983
1985
1990 std::vector<uint> addLeafReconstructionToContext(helios::Context *context) const;
1991
1993
1999 std::vector<uint> addLeafReconstructionToContext(helios::Context *context, const helios::int2 &subpatches) const;
2000
2002
2007
2009
2013
2015
2019 void getHitBoundingBox(helios::vec3 &boxmin, helios::vec3 &boxmax) const;
2020
2022
2026 void getGridBoundingBox(helios::vec3 &boxmin, helios::vec3 &boxmax) const;
2027
2028 // --------- POINT FILTERING ----------- //
2029
2031
2034 void distanceFilter(float maxdistance);
2035
2037
2046 void xyzFilter(float xmin, float xmax, float ymin, float ymax, float zmin, float zmax);
2047
2049
2059 void xyzFilter(float xmin, float xmax, float ymin, float ymax, float zmin, float zmax, bool deleteOutside);
2060
2061
2063
2067 void reflectanceFilter(float minreflectance);
2068
2070
2076 void scalarFilter(const char *scalar_field, float threshold, const char *comparator);
2077
2079
2083 void maxPulseFilter(const char *scalar);
2084
2086
2090 void minPulseFilter(const char *scalar);
2091
2093
2097 void firstHitFilter();
2098
2100
2104 void lastHitFilter();
2105
2106 // ------- TRIANGULATION --------- //
2107
2109
2115 void triangulateHitPoints(float Lmax, float max_aspect_ratio);
2116
2117 // ERK
2119
2129 void triangulateHitPoints(float Lmax, float max_aspect_ratio, const char *scalar_field, float threshold, const char *comparator);
2130
2132
2154 void setExternalTriangulation(const std::vector<helios::vec3> &triangle_vertices, const std::vector<int> &scanIDs);
2155
2156
2158
2162
2163 // -------- GRID ----------- //
2164
2166 uint getGridCellCount() const;
2167
2169
2174 void addGridCell(const helios::vec3 &center, const helios::vec3 &size, float rotation);
2175
2177
2186 void addGridCell(const helios::vec3 &center, const helios::vec3 &global_anchor, const helios::vec3 &size, const helios::vec3 &global_size, float rotation, const helios::int3 &global_ijk, const helios::int3 &global_count);
2187
2189
2192 helios::vec3 getCellCenter(uint index) const;
2193
2195
2199
2201
2205 helios::vec3 getCellSize(uint index) const;
2206
2208
2212 float getCellRotation(uint index) const;
2213
2214 // ------- SYNTHETIC SCAN ------ //
2215
2217
2225
2227
2231 void syntheticScan(helios::Context *context, bool append);
2232
2234
2240 void syntheticScan(helios::Context *context, bool scan_grid_only, bool record_misses);
2241
2243
2250 void syntheticScan(helios::Context *context, bool scan_grid_only, bool record_misses, bool append);
2251
2253
2261 void syntheticScan(helios::Context *context, int rays_per_pulse, float pulse_distance_threshold);
2262
2264
2271 void syntheticScan(helios::Context *context, int rays_per_pulse, float pulse_distance_threshold, bool append);
2272
2274
2282 void syntheticScan(helios::Context *context, int rays_per_pulse, float pulse_distance_threshold, bool scan_grid_only, bool record_misses);
2283
2285
2294 void syntheticScan(helios::Context *context, int rays_per_pulse, float pulse_distance_threshold, bool scan_grid_only, bool record_misses, bool append);
2295
2297
2314 void syntheticScan(helios::Context *context, int rays_per_pulse, float pulse_distance_threshold, ReturnMode return_mode, bool scan_grid_only = false, bool record_misses = false, bool append = true);
2315
2317
2331 void setSyntheticScanMemoryBudget(size_t bytes);
2332
2334
2338 [[nodiscard]] size_t getSyntheticScanMemoryBudget() const;
2339
2341
2345
2347
2351
2352 // -------- LEAF AREA -------- //
2353
2355
2359 void setCellLeafArea(float area, uint index);
2360
2362
2365 float getCellLeafArea(uint index) const;
2366
2368
2371 float getCellLeafAreaDensity(uint index) const;
2372
2374
2378 void setCellGtheta(float Gtheta, uint index);
2379
2381
2384 float getCellGtheta(uint index) const;
2385
2386 // -------- LEAF AREA INVERSION UNCERTAINTY -------- //
2387 //
2388 // The following accessors expose the per-voxel statistical SAMPLING uncertainty of the
2389 // leaf-area inversion (Pimont et al. 2018, RSE 215:343-370). This is the uncertainty owing
2390 // to the finite number of beams that sampled the voxel and to vegetation-element position
2391 // variability. It is CONDITIONAL on the beams that entered the voxel and does NOT capture
2392 // occlusion/coverage bias (voxels shadowed so that beams never penetrate). The group form
2393 // (\ref getGroupLADConfidenceInterval()) is the recommended path: single-voxel intervals are
2394 // routinely +-50-100%, whereas group intervals (a vertical slice, a whole plant) are +-5-10%.
2395
2397
2401 int getCellBeamCount(uint index) const;
2402
2404
2408 float getCellRelativeDensityIndex(uint index) const;
2409
2411
2415 float getCellMeanPathLength(uint index) const;
2416
2418
2422 float getCellLADVariance(uint index) const;
2423
2425
2434 bool getCellLeafAreaConfidenceInterval(uint index, float confidence_level, float &lower, float &upper) const;
2435
2437
2447 bool getGroupLADConfidenceInterval(const std::vector<uint> &indices, float confidence_level, float &mean_lad, float &lower, float &upper) const;
2448
2450
2453 std::vector<helios::vec3> gapfillMisses();
2454
2456
2460 std::vector<helios::vec3> gapfillMisses(uint scanID);
2461
2463
2469 std::vector<helios::vec3> gapfillMisses(uint scanID, const bool gapfill_grid_only, const bool add_flags);
2470
2471
2473
2477 void forceBruteForceLeafArea(bool force) {
2478 force_bruteforce_LAD = force;
2479 }
2480
2482
2488
2490
2500 void calculateLeafArea(helios::Context *context, int min_voxel_hits);
2501
2503
2515 void calculateLeafArea(helios::Context *context, int min_voxel_hits, float element_width);
2516
2518
2538 void calculateLeafArea(helios::Context *context, float Gtheta, int min_voxel_hits, float element_width);
2539
2541
2546 [[deprecated("Use calculateLeafArea() instead. GPU functionality is now provided by the CollisionDetection plugin.")]]
2548
2550
2556 [[deprecated("Use calculateLeafArea(context, min_voxel_hits) instead. GPU functionality is now provided by the CollisionDetection plugin.")]]
2557 void calculateLeafAreaGPU(helios::Context *context, int min_voxel_hits);
2558
2560 void enableGPUAcceleration();
2561
2564
2566
2570 [[nodiscard]] bool isGPUAvailable() const;
2571
2573 [[nodiscard]] bool isGPUAccelerationEnabled() const;
2574
2576 void calculateHitGridCell();
2577
2578 // -------- RECONSTRUCTION --------- //
2579
2581
2587 void leafReconstructionAlphaMask(float minimum_leaf_group_area, float maximum_leaf_group_area, float leaf_aspect_ratio, const char *mask_file);
2588
2590
2597 void leafReconstructionAlphaMask(float minimum_leaf_group_area, float maximum_leaf_group_area, float leaf_aspect_ratio, float leaf_length_constant, const char *mask_file);
2598
2601
2607 void trunkReconstruction(const helios::vec3 &box_center, const helios::vec3 &box_size, float Lmax, float max_aspect_ratio);
2608
2610
2617 std::vector<uint> loadTreeQSM(helios::Context *context, const std::string &filename, uint radial_subdivisions, const std::string &texture_file = "");
2618
2620
2628 std::vector<uint> loadTreeQSMColormap(helios::Context *context, const std::string &filename, uint radial_subdivisions, const std::string &colormap_name);
2629
2631
2634 void cropBeamsToGridAngleRange(uint source);
2635
2637
2640 std::vector<uint> peakFinder(std::vector<float> signal);
2641};
2642
2643bool sortcol0(const std::vector<double> &v0, const std::vector<double> &v1);
2644
2645bool sortcol1(const std::vector<double> &v0, const std::vector<double> &v1);
2646
2647#endif