0.1.22
Loading...
Searching...
No Matches
ULiDARWrapper.py
Go to the documentation of this file.
1"""
2ULiDARWrapper - ctypes wrapper for LiDAR plugin
3
4Provides low-level ctypes interface to LiDAR C++ plugin for point cloud processing,
5synthetic scanning, triangulation, and leaf area calculations.
6"""
7
8import ctypes
9from typing import List, Tuple, Optional
10from .UContextWrapper import UContext
11from ..plugins import helios_lib
12from ..exceptions import check_helios_error
13
14
15# Opaque structure for LiDARcloud
16class ULiDARcloud(ctypes.Structure):
17 """Opaque structure for LiDARcloud C++ class"""
18 pass
19
20
21# Error checking callback
22def _check_error(result, func, args):
23 """Automatic error checking for all LiDAR functions"""
24 check_helios_error(helios_lib.getLastErrorCode, helios_lib.getLastErrorMessage)
25 return result
26
27
28# Function prototypes with availability detection
29try:
30 # Cloud lifecycle
31 helios_lib.createLiDARcloud.argtypes = []
32 helios_lib.createLiDARcloud.restype = ctypes.POINTER(ULiDARcloud)
33 helios_lib.createLiDARcloud.errcheck = _check_error
35 helios_lib.destroyLiDARcloud.argtypes = [ctypes.POINTER(ULiDARcloud)]
36 helios_lib.destroyLiDARcloud.restype = None
37
38 # Scan management
39 helios_lib.addLiDARScan.argtypes = [
40 ctypes.POINTER(ULiDARcloud),
41 ctypes.POINTER(ctypes.c_float), # origin[3]
42 ctypes.c_uint, # Ntheta
43 ctypes.c_float, # thetaMin
44 ctypes.c_float, # thetaMax
45 ctypes.c_uint, # Nphi
46 ctypes.c_float, # phiMin
47 ctypes.c_float, # phiMax
48 ctypes.c_float, # exitDiameter
49 ctypes.c_float, # beamDivergence
50 ctypes.c_float, # rangeNoiseStdDev
51 ctypes.c_float, # angleNoiseStdDev
52 ctypes.POINTER(ctypes.c_char_p), # columnFormat
53 ctypes.c_uint, # nCols
54 ctypes.c_float, # scanTiltRoll
55 ctypes.c_float # scanTiltPitch
56 ]
57 helios_lib.addLiDARScan.restype = ctypes.c_uint
58 helios_lib.addLiDARScan.errcheck = _check_error
59
60 helios_lib.addLiDARScanMultibeam.argtypes = [
61 ctypes.POINTER(ULiDARcloud),
62 ctypes.POINTER(ctypes.c_float), # origin[3]
63 ctypes.POINTER(ctypes.c_float), # beamZenithAngles[nAngles]
64 ctypes.c_uint, # nAngles
65 ctypes.c_uint, # Nphi
66 ctypes.c_float, # phiMin
67 ctypes.c_float, # phiMax
68 ctypes.c_float, # exitDiameter
69 ctypes.c_float, # beamDivergence
70 ctypes.c_float, # rangeNoiseStdDev
71 ctypes.c_float, # angleNoiseStdDev
72 ctypes.POINTER(ctypes.c_char_p), # columnFormat
73 ctypes.c_uint, # nCols
74 ctypes.c_float, # scanTiltRoll
75 ctypes.c_float # scanTiltPitch
76 ]
77 helios_lib.addLiDARScanMultibeam.restype = ctypes.c_uint
78 helios_lib.addLiDARScanMultibeam.errcheck = _check_error
79
80 helios_lib.getLiDARScanCount.argtypes = [ctypes.POINTER(ULiDARcloud)]
81 helios_lib.getLiDARScanCount.restype = ctypes.c_uint
82 helios_lib.getLiDARScanCount.errcheck = _check_error
83
84 helios_lib.getLiDARScanOrigin.argtypes = [
85 ctypes.POINTER(ULiDARcloud),
86 ctypes.c_uint,
87 ctypes.POINTER(ctypes.c_float)
88 ]
89 helios_lib.getLiDARScanOrigin.restype = None
90 helios_lib.getLiDARScanOrigin.errcheck = _check_error
91
92 helios_lib.getLiDARScanSizeTheta.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
93 helios_lib.getLiDARScanSizeTheta.restype = ctypes.c_uint
94 helios_lib.getLiDARScanSizeTheta.errcheck = _check_error
95
96 helios_lib.getLiDARScanSizePhi.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
97 helios_lib.getLiDARScanSizePhi.restype = ctypes.c_uint
98 helios_lib.getLiDARScanSizePhi.errcheck = _check_error
99
100 helios_lib.getLiDARScanRangeNoiseStdDev.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
101 helios_lib.getLiDARScanRangeNoiseStdDev.restype = ctypes.c_float
102 helios_lib.getLiDARScanRangeNoiseStdDev.errcheck = _check_error
103
104 helios_lib.getLiDARScanAngleNoiseStdDev.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
105 helios_lib.getLiDARScanAngleNoiseStdDev.restype = ctypes.c_float
106 helios_lib.getLiDARScanAngleNoiseStdDev.errcheck = _check_error
107
108 helios_lib.getLiDARScanTiltRoll.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
109 helios_lib.getLiDARScanTiltRoll.restype = ctypes.c_float
110 helios_lib.getLiDARScanTiltRoll.errcheck = _check_error
111
112 helios_lib.getLiDARScanTiltPitch.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
113 helios_lib.getLiDARScanTiltPitch.restype = ctypes.c_float
114 helios_lib.getLiDARScanTiltPitch.errcheck = _check_error
115
116 helios_lib.getLiDARScanPattern.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
117 helios_lib.getLiDARScanPattern.restype = ctypes.c_int
118 helios_lib.getLiDARScanPattern.errcheck = _check_error
119
120 helios_lib.getLiDARScanBeamZenithAngleCount.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
121 helios_lib.getLiDARScanBeamZenithAngleCount.restype = ctypes.c_uint
122 helios_lib.getLiDARScanBeamZenithAngleCount.errcheck = _check_error
123
124 helios_lib.getLiDARScanBeamZenithAngles.argtypes = [
125 ctypes.POINTER(ULiDARcloud), ctypes.c_uint,
126 ctypes.POINTER(ctypes.c_float), ctypes.c_uint,
127 ]
128 helios_lib.getLiDARScanBeamZenithAngles.restype = None
129 helios_lib.getLiDARScanBeamZenithAngles.errcheck = _check_error
130
131 # Miss detection
132 helios_lib.isLiDARHitMiss.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
133 helios_lib.isLiDARHitMiss.restype = ctypes.c_int
134 helios_lib.isLiDARHitMiss.errcheck = _check_error
135
136 helios_lib.lidarHasMisses.argtypes = [ctypes.POINTER(ULiDARcloud)]
137 helios_lib.lidarHasMisses.restype = ctypes.c_int
138 helios_lib.lidarHasMisses.errcheck = _check_error
139
140 helios_lib.getLiDARMissDistance.argtypes = []
141 helios_lib.getLiDARMissDistance.restype = ctypes.c_float
142 # No errcheck: pure constant accessor, never sets an error.
143
144 # Hit point operations
145 helios_lib.addLiDARHitPoint.argtypes = [
146 ctypes.POINTER(ULiDARcloud),
147 ctypes.c_uint,
148 ctypes.POINTER(ctypes.c_float),
149 ctypes.POINTER(ctypes.c_float)
150 ]
151 helios_lib.addLiDARHitPoint.restype = None
152 helios_lib.addLiDARHitPoint.errcheck = _check_error
153
154 helios_lib.addLiDARHitPointRGB.argtypes = [
155 ctypes.POINTER(ULiDARcloud),
156 ctypes.c_uint,
157 ctypes.POINTER(ctypes.c_float),
158 ctypes.POINTER(ctypes.c_float),
159 ctypes.POINTER(ctypes.c_float)
160 ]
161 helios_lib.addLiDARHitPointRGB.restype = None
162 helios_lib.addLiDARHitPointRGB.errcheck = _check_error
163
164 helios_lib.addLiDARHitPoints.argtypes = [
165 ctypes.POINTER(ULiDARcloud),
166 ctypes.c_uint,
167 ctypes.POINTER(ctypes.c_float), # xyzs[count*3]
168 ctypes.POINTER(ctypes.c_float), # directions[count*3]
169 ctypes.c_uint, # count
170 ctypes.POINTER(ctypes.c_float) # colors[count*3] or NULL
171 ]
172 helios_lib.addLiDARHitPoints.restype = None
173 helios_lib.addLiDARHitPoints.errcheck = _check_error
174
175 helios_lib.addLiDARHitPointsWithData.argtypes = [
176 ctypes.POINTER(ULiDARcloud),
177 ctypes.c_uint,
178 ctypes.POINTER(ctypes.c_float), # xyzs[count*3]
179 ctypes.POINTER(ctypes.c_float), # directions[count*3]
180 ctypes.c_uint, # count
181 ctypes.POINTER(ctypes.c_float), # colors[count*3] or NULL
182 ctypes.POINTER(ctypes.c_char_p), # dataLabels[nLabels]
183 ctypes.c_uint, # nLabels
184 ctypes.POINTER(ctypes.c_double) # dataValues[count*nLabels] or NULL
185 ]
186 helios_lib.addLiDARHitPointsWithData.restype = None
187 helios_lib.addLiDARHitPointsWithData.errcheck = _check_error
188
189 helios_lib.getLiDARHitCount.argtypes = [ctypes.POINTER(ULiDARcloud)]
190 helios_lib.getLiDARHitCount.restype = ctypes.c_uint
191 helios_lib.getLiDARHitCount.errcheck = _check_error
192
193 helios_lib.getLiDARHitXYZ.argtypes = [
194 ctypes.POINTER(ULiDARcloud),
195 ctypes.c_uint,
196 ctypes.POINTER(ctypes.c_float)
197 ]
198 helios_lib.getLiDARHitXYZ.restype = None
199 helios_lib.getLiDARHitXYZ.errcheck = _check_error
200
201 helios_lib.getLiDARHitRaydir.argtypes = [
202 ctypes.POINTER(ULiDARcloud),
203 ctypes.c_uint,
204 ctypes.POINTER(ctypes.c_float)
205 ]
206 helios_lib.getLiDARHitRaydir.restype = None
207 helios_lib.getLiDARHitRaydir.errcheck = _check_error
208
209 helios_lib.getLiDARHitColor.argtypes = [
210 ctypes.POINTER(ULiDARcloud),
211 ctypes.c_uint,
212 ctypes.POINTER(ctypes.c_float)
213 ]
214 helios_lib.getLiDARHitColor.restype = None
215 helios_lib.getLiDARHitColor.errcheck = _check_error
216
217 helios_lib.getLiDARHitScanID.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
218 helios_lib.getLiDARHitScanID.restype = ctypes.c_int
219 helios_lib.getLiDARHitScanID.errcheck = _check_error
220
221 helios_lib.doesLiDARHitDataExist.argtypes = [
222 ctypes.POINTER(ULiDARcloud),
223 ctypes.c_uint,
224 ctypes.c_char_p
225 ]
226 helios_lib.doesLiDARHitDataExist.restype = ctypes.c_int
227 helios_lib.doesLiDARHitDataExist.errcheck = _check_error
228
229 helios_lib.getLiDARHitData.argtypes = [
230 ctypes.POINTER(ULiDARcloud),
231 ctypes.c_uint,
232 ctypes.c_char_p
233 ]
234 helios_lib.getLiDARHitData.restype = ctypes.c_double
235 helios_lib.getLiDARHitData.errcheck = _check_error
236
237 helios_lib.getLiDARHitData_all.argtypes = [
238 ctypes.POINTER(ULiDARcloud),
239 ctypes.c_char_p,
240 ctypes.POINTER(ctypes.c_float),
241 ctypes.c_uint
242 ]
243 helios_lib.getLiDARHitData_all.restype = None
244 helios_lib.getLiDARHitData_all.errcheck = _check_error
245
246 helios_lib.getLiDARHitsXYZRGB_all.argtypes = [
247 ctypes.POINTER(ULiDARcloud),
248 ctypes.POINTER(ctypes.c_float),
249 ctypes.POINTER(ctypes.c_float),
250 ctypes.c_uint
251 ]
252 helios_lib.getLiDARHitsXYZRGB_all.restype = None
253 helios_lib.getLiDARHitsXYZRGB_all.errcheck = _check_error
254
255 helios_lib.deleteLiDARHitPoint.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
256 helios_lib.deleteLiDARHitPoint.restype = None
257 helios_lib.deleteLiDARHitPoint.errcheck = _check_error
258
259 # Transformations
260 helios_lib.lidarCoordinateShift.argtypes = [
261 ctypes.POINTER(ULiDARcloud),
262 ctypes.POINTER(ctypes.c_float)
263 ]
264 helios_lib.lidarCoordinateShift.restype = None
265 helios_lib.lidarCoordinateShift.errcheck = _check_error
266
267 helios_lib.lidarCoordinateRotation.argtypes = [
268 ctypes.POINTER(ULiDARcloud),
269 ctypes.POINTER(ctypes.c_float)
270 ]
271 helios_lib.lidarCoordinateRotation.restype = None
272 helios_lib.lidarCoordinateRotation.errcheck = _check_error
273
274 # Triangulation
275 helios_lib.lidarTriangulateHitPoints.argtypes = [
276 ctypes.POINTER(ULiDARcloud),
277 ctypes.c_float,
278 ctypes.c_float
279 ]
280 helios_lib.lidarTriangulateHitPoints.restype = None
281 helios_lib.lidarTriangulateHitPoints.errcheck = _check_error
282
283 helios_lib.getLiDARTriangleCount.argtypes = [ctypes.POINTER(ULiDARcloud)]
284 helios_lib.getLiDARTriangleCount.restype = ctypes.c_uint
285 helios_lib.getLiDARTriangleCount.errcheck = _check_error
286
287 helios_lib.getLiDARTriangulationStats.argtypes = [
288 ctypes.POINTER(ULiDARcloud),
289 ctypes.POINTER(ctypes.c_uint), # out[4]
290 ]
291 helios_lib.getLiDARTriangulationStats.restype = None
292 helios_lib.getLiDARTriangulationStats.errcheck = _check_error
293
294 helios_lib.getLiDARTriangleVertices_all.argtypes = [
295 ctypes.POINTER(ULiDARcloud),
296 ctypes.POINTER(ctypes.c_float), # out_xyz[triCount*9]
297 ctypes.POINTER(ctypes.c_int), # out_scan[triCount] or NULL
298 ctypes.c_uint # triCount
299 ]
300 helios_lib.getLiDARTriangleVertices_all.restype = None
301 helios_lib.getLiDARTriangleVertices_all.errcheck = _check_error
302
303 # Filters
304 helios_lib.lidarDistanceFilter.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_float]
305 helios_lib.lidarDistanceFilter.restype = None
306 helios_lib.lidarDistanceFilter.errcheck = _check_error
307
308 helios_lib.lidarReflectanceFilter.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_float]
309 helios_lib.lidarReflectanceFilter.restype = None
310 helios_lib.lidarReflectanceFilter.errcheck = _check_error
311
312 helios_lib.lidarFirstHitFilter.argtypes = [ctypes.POINTER(ULiDARcloud)]
313 helios_lib.lidarFirstHitFilter.restype = None
314 helios_lib.lidarFirstHitFilter.errcheck = _check_error
315
316 helios_lib.lidarLastHitFilter.argtypes = [ctypes.POINTER(ULiDARcloud)]
317 helios_lib.lidarLastHitFilter.restype = None
318 helios_lib.lidarLastHitFilter.errcheck = _check_error
319
320 # File I/O
321 helios_lib.exportLiDARPointCloud.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p, ctypes.c_bool]
322 helios_lib.exportLiDARPointCloud.restype = None
323 helios_lib.exportLiDARPointCloud.errcheck = _check_error
324
325 helios_lib.exportLiDARLeafAreaUncertainty.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
326 helios_lib.exportLiDARLeafAreaUncertainty.restype = None
327 helios_lib.exportLiDARLeafAreaUncertainty.errcheck = _check_error
328
329 helios_lib.exportLiDARScans.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
330 helios_lib.exportLiDARScans.restype = None
331 helios_lib.exportLiDARScans.errcheck = _check_error
332
333 helios_lib.loadLiDARXML.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
334 helios_lib.loadLiDARXML.restype = None
335 helios_lib.loadLiDARXML.errcheck = _check_error
336
337 # Message control
338 helios_lib.lidarDisableMessages.argtypes = [ctypes.POINTER(ULiDARcloud)]
339 helios_lib.lidarDisableMessages.restype = None
340 helios_lib.lidarDisableMessages.errcheck = _check_error
341
342 helios_lib.lidarEnableMessages.argtypes = [ctypes.POINTER(ULiDARcloud)]
343 helios_lib.lidarEnableMessages.restype = None
344 helios_lib.lidarEnableMessages.errcheck = _check_error
345
346 # Grid cell management
347 helios_lib.addLiDARGrid.argtypes = [
348 ctypes.POINTER(ULiDARcloud),
349 ctypes.POINTER(ctypes.c_float),
350 ctypes.POINTER(ctypes.c_float),
351 ctypes.POINTER(ctypes.c_int),
352 ctypes.c_float
353 ]
354 helios_lib.addLiDARGrid.restype = None
355 helios_lib.addLiDARGrid.errcheck = _check_error
356
357 helios_lib.addLiDARGridCell.argtypes = [
358 ctypes.POINTER(ULiDARcloud),
359 ctypes.POINTER(ctypes.c_float),
360 ctypes.POINTER(ctypes.c_float),
361 ctypes.c_float
362 ]
363 helios_lib.addLiDARGridCell.restype = None
364 helios_lib.addLiDARGridCell.errcheck = _check_error
365
366 helios_lib.getLiDARGridCellCount.argtypes = [ctypes.POINTER(ULiDARcloud)]
367 helios_lib.getLiDARGridCellCount.restype = ctypes.c_uint
368 helios_lib.getLiDARGridCellCount.errcheck = _check_error
369
370 helios_lib.getLiDARCellCenter.argtypes = [
371 ctypes.POINTER(ULiDARcloud),
372 ctypes.c_uint,
373 ctypes.POINTER(ctypes.c_float)
374 ]
375 helios_lib.getLiDARCellCenter.restype = None
376 helios_lib.getLiDARCellCenter.errcheck = _check_error
377
378 helios_lib.getLiDARCellSize.argtypes = [
379 ctypes.POINTER(ULiDARcloud),
380 ctypes.c_uint,
381 ctypes.POINTER(ctypes.c_float)
382 ]
383 helios_lib.getLiDARCellSize.restype = None
384 helios_lib.getLiDARCellSize.errcheck = _check_error
385
386 helios_lib.getLiDARCellLeafArea.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
387 helios_lib.getLiDARCellLeafArea.restype = ctypes.c_float
388 helios_lib.getLiDARCellLeafArea.errcheck = _check_error
389
390 helios_lib.getLiDARCellLeafAreaDensity.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
391 helios_lib.getLiDARCellLeafAreaDensity.restype = ctypes.c_float
392 helios_lib.getLiDARCellLeafAreaDensity.errcheck = _check_error
393
394 # Leaf-area sampling uncertainty (Pimont et al. 2018)
395 helios_lib.getLiDARCellBeamCount.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
396 helios_lib.getLiDARCellBeamCount.restype = ctypes.c_int
397 helios_lib.getLiDARCellBeamCount.errcheck = _check_error
398
399 helios_lib.getLiDARCellRelativeDensityIndex.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
400 helios_lib.getLiDARCellRelativeDensityIndex.restype = ctypes.c_float
401 helios_lib.getLiDARCellRelativeDensityIndex.errcheck = _check_error
402
403 helios_lib.getLiDARCellMeanPathLength.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
404 helios_lib.getLiDARCellMeanPathLength.restype = ctypes.c_float
405 helios_lib.getLiDARCellMeanPathLength.errcheck = _check_error
406
407 helios_lib.getLiDARCellLADVariance.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
408 helios_lib.getLiDARCellLADVariance.restype = ctypes.c_float
409 helios_lib.getLiDARCellLADVariance.errcheck = _check_error
410
411 helios_lib.getLiDARCellLeafAreaConfidenceInterval.argtypes = [
412 ctypes.POINTER(ULiDARcloud), ctypes.c_uint, ctypes.c_float,
413 ctypes.POINTER(ctypes.c_float), # out_bounds[2]
414 ]
415 helios_lib.getLiDARCellLeafAreaConfidenceInterval.restype = ctypes.c_int
416 helios_lib.getLiDARCellLeafAreaConfidenceInterval.errcheck = _check_error
417
418 helios_lib.getLiDARGroupLADConfidenceInterval.argtypes = [
419 ctypes.POINTER(ULiDARcloud),
420 ctypes.POINTER(ctypes.c_uint), # indices[nIndices]
421 ctypes.c_uint, ctypes.c_float,
422 ctypes.POINTER(ctypes.c_float), # out_results[3]
423 ]
424 helios_lib.getLiDARGroupLADConfidenceInterval.restype = ctypes.c_int
425 helios_lib.getLiDARGroupLADConfidenceInterval.errcheck = _check_error
426
427 helios_lib.calculateLiDARHitGridCell.argtypes = [ctypes.POINTER(ULiDARcloud)]
428 helios_lib.calculateLiDARHitGridCell.restype = None
429 helios_lib.calculateLiDARHitGridCell.errcheck = _check_error
430
431 # Synthetic scanning
432 helios_lib.syntheticLiDARScan.argtypes = [
433 ctypes.POINTER(ULiDARcloud),
434 ctypes.POINTER(UContext)
435 ]
436 helios_lib.syntheticLiDARScan.restype = None
437 helios_lib.syntheticLiDARScan.errcheck = _check_error
438
439 helios_lib.syntheticLiDARScanAppend.argtypes = [
440 ctypes.POINTER(ULiDARcloud),
441 ctypes.POINTER(UContext),
442 ctypes.c_bool
443 ]
444 helios_lib.syntheticLiDARScanAppend.restype = None
445 helios_lib.syntheticLiDARScanAppend.errcheck = _check_error
446
447 helios_lib.syntheticLiDARScanDiscrete.argtypes = [
448 ctypes.POINTER(ULiDARcloud),
449 ctypes.POINTER(UContext),
450 ctypes.c_bool, # scan_grid_only
451 ctypes.c_bool, # record_misses
452 ctypes.c_bool, # append
453 ]
454 helios_lib.syntheticLiDARScanDiscrete.restype = None
455 helios_lib.syntheticLiDARScanDiscrete.errcheck = _check_error
456
457 helios_lib.syntheticLiDARScanWaveform.argtypes = [
458 ctypes.POINTER(ULiDARcloud),
459 ctypes.POINTER(UContext),
460 ctypes.c_int,
461 ctypes.c_float
462 ]
463 helios_lib.syntheticLiDARScanWaveform.restype = None
464 helios_lib.syntheticLiDARScanWaveform.errcheck = _check_error
465
466 helios_lib.syntheticLiDARScanFull.argtypes = [
467 ctypes.POINTER(ULiDARcloud),
468 ctypes.POINTER(UContext),
469 ctypes.c_int,
470 ctypes.c_float,
471 ctypes.c_bool,
472 ctypes.c_bool,
473 ctypes.c_bool
474 ]
475 helios_lib.syntheticLiDARScanFull.restype = None
476 helios_lib.syntheticLiDARScanFull.errcheck = _check_error
477
478 # Leaf area calculations
479 helios_lib.calculateLiDARLeafArea.argtypes = [
480 ctypes.POINTER(ULiDARcloud),
481 ctypes.POINTER(UContext)
482 ]
483 helios_lib.calculateLiDARLeafArea.restype = None
484 helios_lib.calculateLiDARLeafArea.errcheck = _check_error
485
486 helios_lib.calculateLiDARLeafAreaMinHits.argtypes = [
487 ctypes.POINTER(ULiDARcloud),
488 ctypes.POINTER(UContext),
489 ctypes.c_int
490 ]
491 helios_lib.calculateLiDARLeafAreaMinHits.restype = None
492 helios_lib.calculateLiDARLeafAreaMinHits.errcheck = _check_error
493
494 helios_lib.calculateLiDARLeafAreaUncertainty.argtypes = [
495 ctypes.POINTER(ULiDARcloud),
496 ctypes.POINTER(UContext),
497 ctypes.c_int,
498 ctypes.c_float,
499 ]
500 helios_lib.calculateLiDARLeafAreaUncertainty.restype = None
501 helios_lib.calculateLiDARLeafAreaUncertainty.errcheck = _check_error
502
503 helios_lib.calculateSyntheticLiDARLeafArea.argtypes = [
504 ctypes.POINTER(ULiDARcloud),
505 ctypes.POINTER(UContext)
506 ]
507 helios_lib.calculateSyntheticLiDARLeafArea.restype = None
508 helios_lib.calculateSyntheticLiDARLeafArea.errcheck = _check_error
509
510 helios_lib.calculateSyntheticLiDARGtheta.argtypes = [
511 ctypes.POINTER(ULiDARcloud),
512 ctypes.POINTER(UContext)
513 ]
514 helios_lib.calculateSyntheticLiDARGtheta.restype = None
515 helios_lib.calculateSyntheticLiDARGtheta.errcheck = _check_error
516
517 helios_lib.getLiDARCellGtheta.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_uint]
518 helios_lib.getLiDARCellGtheta.restype = ctypes.c_float
519 helios_lib.getLiDARCellGtheta.errcheck = _check_error
520
521 helios_lib.setLiDARCellGtheta.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_float, ctypes.c_uint]
522 helios_lib.setLiDARCellGtheta.restype = None
523 helios_lib.setLiDARCellGtheta.errcheck = _check_error
524
525 helios_lib.gapfillLiDARMisses.argtypes = [ctypes.POINTER(ULiDARcloud)]
526 helios_lib.gapfillLiDARMisses.restype = None
527 helios_lib.gapfillLiDARMisses.errcheck = _check_error
528
529 helios_lib.exportLiDARGtheta.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
530 helios_lib.exportLiDARGtheta.restype = None
531 helios_lib.exportLiDARGtheta.errcheck = _check_error
532
533 # Additional export functions
534 helios_lib.exportLiDARTriangleNormals.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
535 helios_lib.exportLiDARTriangleNormals.restype = None
536 helios_lib.exportLiDARTriangleNormals.errcheck = _check_error
537
538 helios_lib.exportLiDARTriangleAreas.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
539 helios_lib.exportLiDARTriangleAreas.restype = None
540 helios_lib.exportLiDARTriangleAreas.errcheck = _check_error
541
542 helios_lib.exportLiDARLeafAreas.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
543 helios_lib.exportLiDARLeafAreas.restype = None
544 helios_lib.exportLiDARLeafAreas.errcheck = _check_error
545
546 helios_lib.exportLiDARLeafAreaDensities.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
547 helios_lib.exportLiDARLeafAreaDensities.restype = None
548 helios_lib.exportLiDARLeafAreaDensities.errcheck = _check_error
549
550 helios_lib.exportLiDARGtheta.argtypes = [ctypes.POINTER(ULiDARcloud), ctypes.c_char_p]
551 helios_lib.exportLiDARGtheta.restype = None
552 helios_lib.exportLiDARGtheta.errcheck = _check_error
553
554 # Context integration
555 helios_lib.addLiDARTrianglesToContext.argtypes = [
556 ctypes.POINTER(ULiDARcloud),
557 ctypes.POINTER(UContext)
558 ]
559 helios_lib.addLiDARTrianglesToContext.restype = None
560 helios_lib.addLiDARTrianglesToContext.errcheck = _check_error
561
562 helios_lib.initializeLiDARCollisionDetection.argtypes = [
563 ctypes.POINTER(ULiDARcloud),
564 ctypes.POINTER(UContext)
565 ]
566 helios_lib.initializeLiDARCollisionDetection.restype = None
567 helios_lib.initializeLiDARCollisionDetection.errcheck = _check_error
568
569 helios_lib.enableLiDARCDGPUAcceleration.argtypes = [ctypes.POINTER(ULiDARcloud)]
570 helios_lib.enableLiDARCDGPUAcceleration.restype = None
571 helios_lib.enableLiDARCDGPUAcceleration.errcheck = _check_error
572
573 helios_lib.disableLiDARCDGPUAcceleration.argtypes = [ctypes.POINTER(ULiDARcloud)]
574 helios_lib.disableLiDARCDGPUAcceleration.restype = None
575 helios_lib.disableLiDARCDGPUAcceleration.errcheck = _check_error
576
577 _LIDAR_FUNCTIONS_AVAILABLE = True
578
579except AttributeError:
580 _LIDAR_FUNCTIONS_AVAILABLE = False
581
583# Python wrapper functions
584def createLiDARcloud() -> ctypes.POINTER(ULiDARcloud):
585 """Create LiDARcloud instance"""
586 if not _LIDAR_FUNCTIONS_AVAILABLE:
587 raise NotImplementedError(
588 "LiDAR functions not available. Rebuild PyHelios with lidar plugin:\n"
589 " build_scripts/build_helios --plugins lidar"
590 )
591 return helios_lib.createLiDARcloud()
592
593
594def destroyLiDARcloud(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
595 """Destroy LiDARcloud instance"""
596 if cloud_ptr and _LIDAR_FUNCTIONS_AVAILABLE:
597 helios_lib.destroyLiDARcloud(cloud_ptr)
598
599
600def addLiDARScan(cloud_ptr: ctypes.POINTER(ULiDARcloud),
601 origin: List[float], Ntheta: int, theta_range: Tuple[float, float],
602 Nphi: int, phi_range: Tuple[float, float],
603 exit_diameter: float, beam_divergence: float,
604 column_format: Optional[List[str]] = None,
605 range_noise_stddev: float = 0.0, angle_noise_stddev: float = 0.0,
606 scan_tilt_roll: float = 0.0, scan_tilt_pitch: float = 0.0) -> int:
607 """Add a LiDAR scan to the point cloud"""
608 if not _LIDAR_FUNCTIONS_AVAILABLE:
609 raise NotImplementedError("LiDAR functions not available")
610
611 if len(origin) != 3:
612 raise ValueError("Origin must be a 3-element array [x, y, z]")
613
614 origin_array = (ctypes.c_float * 3)(*origin)
615
616 if column_format:
617 column_array = (ctypes.c_char_p * len(column_format))(
618 *[c.encode('utf-8') for c in column_format]
619 )
620 n_cols = len(column_format)
621 else:
622 column_array = None
623 n_cols = 0
624
625 return helios_lib.addLiDARScan(
626 cloud_ptr, origin_array, Ntheta, theta_range[0], theta_range[1],
627 Nphi, phi_range[0], phi_range[1], exit_diameter, beam_divergence,
628 float(range_noise_stddev), float(angle_noise_stddev),
629 column_array, n_cols,
630 float(scan_tilt_roll), float(scan_tilt_pitch)
631 )
632
633
634def addLiDARScanMultibeam(cloud_ptr: ctypes.POINTER(ULiDARcloud),
635 origin: List[float], beam_zenith_angles: List[float],
636 Nphi: int, phi_range: Tuple[float, float],
637 exit_diameter: float, beam_divergence: float,
638 column_format: Optional[List[str]] = None,
639 range_noise_stddev: float = 0.0, angle_noise_stddev: float = 0.0,
640 scan_tilt_roll: float = 0.0, scan_tilt_pitch: float = 0.0) -> int:
641 """Add a spinning multibeam LiDAR scan (rotating multi-channel sensor)"""
642 if not _LIDAR_FUNCTIONS_AVAILABLE:
643 raise NotImplementedError("LiDAR functions not available")
644
645 if len(origin) != 3:
646 raise ValueError("Origin must be a 3-element array [x, y, z]")
647 if not beam_zenith_angles:
648 raise ValueError("beam_zenith_angles must contain at least one per-channel angle")
649
650 origin_array = (ctypes.c_float * 3)(*origin)
651 n_angles = len(beam_zenith_angles)
652 angles_array = (ctypes.c_float * n_angles)(*[float(a) for a in beam_zenith_angles])
653
654 if column_format:
655 column_array = (ctypes.c_char_p * len(column_format))(
656 *[c.encode('utf-8') for c in column_format]
657 )
658 n_cols = len(column_format)
659 else:
660 column_array = None
661 n_cols = 0
662
663 return helios_lib.addLiDARScanMultibeam(
664 cloud_ptr, origin_array, angles_array, n_angles,
665 Nphi, phi_range[0], phi_range[1], exit_diameter, beam_divergence,
666 float(range_noise_stddev), float(angle_noise_stddev),
667 column_array, n_cols,
668 float(scan_tilt_roll), float(scan_tilt_pitch)
669 )
670
671
672def getLiDARScanTiltRoll(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> float:
673 """Get the global scanner tilt roll angle (radians) for a scan."""
674 if not _LIDAR_FUNCTIONS_AVAILABLE:
675 raise NotImplementedError("LiDAR functions not available")
676 return helios_lib.getLiDARScanTiltRoll(cloud_ptr, scanID)
677
678
679def getLiDARScanTiltPitch(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> float:
680 """Get the global scanner tilt pitch angle (radians) for a scan."""
681 if not _LIDAR_FUNCTIONS_AVAILABLE:
682 raise NotImplementedError("LiDAR functions not available")
683 return helios_lib.getLiDARScanTiltPitch(cloud_ptr, scanID)
684
685
686def getLiDARScanPattern(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> int:
687 """Get the scan pattern (0 = raster, 1 = spinning multibeam)."""
688 if not _LIDAR_FUNCTIONS_AVAILABLE:
689 raise NotImplementedError("LiDAR functions not available")
690 return helios_lib.getLiDARScanPattern(cloud_ptr, scanID)
691
692
693def getLiDARScanBeamZenithAngles(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> List[float]:
694 """Get the per-channel beam zenith angles (radians) for a multibeam scan (empty for raster)."""
695 if not _LIDAR_FUNCTIONS_AVAILABLE:
696 raise NotImplementedError("LiDAR functions not available")
697 count = helios_lib.getLiDARScanBeamZenithAngleCount(cloud_ptr, scanID)
698 if count == 0:
699 return []
700 out = (ctypes.c_float * count)()
701 helios_lib.getLiDARScanBeamZenithAngles(cloud_ptr, scanID, out, count)
702 return [float(out[i]) for i in range(count)]
703
704
705def isLiDARHitMiss(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> bool:
706 """Return True if the hit is a miss (a transmitted beam that returned nothing)."""
707 if not _LIDAR_FUNCTIONS_AVAILABLE:
708 raise NotImplementedError("LiDAR functions not available")
709 return bool(helios_lib.isLiDARHitMiss(cloud_ptr, index))
710
711
712def lidarHasMisses(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> bool:
713 """Return True if the cloud contains at least one miss."""
714 if not _LIDAR_FUNCTIONS_AVAILABLE:
715 raise NotImplementedError("LiDAR functions not available")
716 return bool(helios_lib.lidarHasMisses(cloud_ptr))
717
718
719def getLiDARMissDistance() -> float:
720 """Return the LIDAR_MISS_DISTANCE constant (meters)."""
721 if not _LIDAR_FUNCTIONS_AVAILABLE:
722 raise NotImplementedError("LiDAR functions not available")
723 return helios_lib.getLiDARMissDistance()
724
725
726def getLiDARScanCount(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> int:
727 """Get number of scans in the cloud"""
728 if not _LIDAR_FUNCTIONS_AVAILABLE:
729 raise NotImplementedError("LiDAR functions not available")
730 return helios_lib.getLiDARScanCount(cloud_ptr)
731
732
733def getLiDARScanOrigin(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> List[float]:
734 """Get origin of a specific scan"""
735 if not _LIDAR_FUNCTIONS_AVAILABLE:
736 raise NotImplementedError("LiDAR functions not available")
737
738 origin = (ctypes.c_float * 3)()
739 helios_lib.getLiDARScanOrigin(cloud_ptr, scanID, origin)
740 return list(origin)
741
743def getLiDARScanSizeTheta(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> int:
744 """Get number of zenith scan points"""
745 if not _LIDAR_FUNCTIONS_AVAILABLE:
746 raise NotImplementedError("LiDAR functions not available")
747 return helios_lib.getLiDARScanSizeTheta(cloud_ptr, scanID)
748
749
750def getLiDARScanSizePhi(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> int:
751 """Get number of azimuthal scan points"""
752 if not _LIDAR_FUNCTIONS_AVAILABLE:
753 raise NotImplementedError("LiDAR functions not available")
754 return helios_lib.getLiDARScanSizePhi(cloud_ptr, scanID)
755
756
757def getLiDARScanRangeNoiseStdDev(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> float:
758 """Get the range (along-beam) measurement noise standard deviation for a scan (meters)"""
759 if not _LIDAR_FUNCTIONS_AVAILABLE:
760 raise NotImplementedError("LiDAR functions not available")
761 return helios_lib.getLiDARScanRangeNoiseStdDev(cloud_ptr, scanID)
762
763
764def getLiDARScanAngleNoiseStdDev(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int) -> float:
765 """Get the angular (beam-pointing) jitter standard deviation for a scan (radians)"""
766 if not _LIDAR_FUNCTIONS_AVAILABLE:
767 raise NotImplementedError("LiDAR functions not available")
768 return helios_lib.getLiDARScanAngleNoiseStdDev(cloud_ptr, scanID)
769
770
771def addLiDARHitPoint(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int,
772 xyz: List[float], direction: List[float]) -> None:
773 """Add a hit point to the cloud"""
774 if not _LIDAR_FUNCTIONS_AVAILABLE:
775 raise NotImplementedError("LiDAR functions not available")
776
777 if len(xyz) != 3:
778 raise ValueError("XYZ must be a 3-element array")
779 if len(direction) < 2:
780 raise ValueError("Direction must have at least 2 elements [radius, elevation]")
781
782 xyz_array = (ctypes.c_float * 3)(*xyz)
783 direction_array = (ctypes.c_float * 3)(direction[0], direction[1], direction[2] if len(direction) > 2 else 0)
784 helios_lib.addLiDARHitPoint(cloud_ptr, scanID, xyz_array, direction_array)
785
786
787def addLiDARHitPointRGB(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int,
788 xyz: List[float], direction: List[float], color: List[float]) -> None:
789 """Add a hit point with color to the cloud"""
790 if not _LIDAR_FUNCTIONS_AVAILABLE:
791 raise NotImplementedError("LiDAR functions not available")
792
793 if len(xyz) != 3:
794 raise ValueError("XYZ must be a 3-element array")
795 if len(direction) < 2:
796 raise ValueError("Direction must have at least 2 elements")
797 if len(color) != 3:
798 raise ValueError("Color must be a 3-element array [r, g, b]")
799
800 xyz_array = (ctypes.c_float * 3)(*xyz)
801 direction_array = (ctypes.c_float * 3)(direction[0], direction[1], direction[2] if len(direction) > 2 else 0)
802 color_array = (ctypes.c_float * 3)(*color)
803 helios_lib.addLiDARHitPointRGB(cloud_ptr, scanID, xyz_array, direction_array, color_array)
804
805
806def addLiDARHitPoints(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int,
807 xyzs, directions, count: int, colors=None) -> None:
808 """Add many hit points to the cloud in a single call (bulk ingestion).
809
810 xyzs and directions must be contiguous float32 buffers (e.g. numpy arrays)
811 of shape (count, 3). colors, if given, must be a contiguous float32 buffer
812 of shape (count, 3); pass None to add without color. The buffers are passed
813 straight through to C without per-point Python marshalling.
814 """
815 if not _LIDAR_FUNCTIONS_AVAILABLE:
816 raise NotImplementedError("LiDAR functions not available")
817
818 xyzs_ptr = xyzs.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
819 directions_ptr = directions.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
820 colors_ptr = colors.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) if colors is not None else None
821 helios_lib.addLiDARHitPoints(cloud_ptr, scanID, xyzs_ptr, directions_ptr, count, colors_ptr)
822
823
824def addLiDARHitPointsWithData(cloud_ptr: ctypes.POINTER(ULiDARcloud), scanID: int,
825 xyzs, directions, count: int, colors=None,
826 labels=None, data_values=None) -> None:
827 """Bulk-ingest hit points carrying a per-hit data map in one call.
828
829 xyzs/directions (and colors, if given) must be contiguous float32 buffers of
830 shape (count, 3); directions is (radius, elevation, azimuth). labels is a
831 list of data-map key names; data_values must be a contiguous float64 buffer
832 of shape (count, len(labels)). Pass labels=None/empty (and data_values=None)
833 to ingest with an empty data map.
834 """
835 if not _LIDAR_FUNCTIONS_AVAILABLE:
836 raise NotImplementedError("LiDAR functions not available")
837
838 xyzs_ptr = xyzs.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
839 directions_ptr = directions.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
840 colors_ptr = colors.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) if colors is not None else None
841
842 labels = list(labels or [])
843 n_labels = len(labels)
844 if n_labels:
845 labels_arr = (ctypes.c_char_p * n_labels)(*[s.encode('utf-8') for s in labels])
846 values_ptr = data_values.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
847 else:
848 labels_arr = None
849 values_ptr = None
850
851 helios_lib.addLiDARHitPointsWithData(cloud_ptr, scanID, xyzs_ptr, directions_ptr,
852 count, colors_ptr, labels_arr, n_labels, values_ptr)
853
854
855def getLiDARHitCount(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> int:
856 """Get total number of hit points"""
857 if not _LIDAR_FUNCTIONS_AVAILABLE:
858 raise NotImplementedError("LiDAR functions not available")
859 return helios_lib.getLiDARHitCount(cloud_ptr)
860
861
862def getLiDARHitXYZ(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> List[float]:
863 """Get coordinates of a hit point"""
864 if not _LIDAR_FUNCTIONS_AVAILABLE:
865 raise NotImplementedError("LiDAR functions not available")
866
867 xyz = (ctypes.c_float * 3)()
868 helios_lib.getLiDARHitXYZ(cloud_ptr, index, xyz)
869 return list(xyz)
870
872def getLiDARHitRaydir(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> List[float]:
873 """Get ray direction of a hit point"""
874 if not _LIDAR_FUNCTIONS_AVAILABLE:
875 raise NotImplementedError("LiDAR functions not available")
876
877 direction = (ctypes.c_float * 3)()
878 helios_lib.getLiDARHitRaydir(cloud_ptr, index, direction)
879 return list(direction)
880
882def getLiDARHitColor(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> List[float]:
883 """Get color of a hit point"""
884 if not _LIDAR_FUNCTIONS_AVAILABLE:
885 raise NotImplementedError("LiDAR functions not available")
886
887 color = (ctypes.c_float * 3)()
888 helios_lib.getLiDARHitColor(cloud_ptr, index, color)
889 return list(color)
890
892def getLiDARHitScanID(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> int:
893 """Get the scan ID a hit point belongs to"""
894 if not _LIDAR_FUNCTIONS_AVAILABLE:
895 raise NotImplementedError("LiDAR functions not available")
896 return helios_lib.getLiDARHitScanID(cloud_ptr, index)
897
898
899def doesLiDARHitDataExist(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int, label: str) -> bool:
900 """Check whether a named scalar data value exists for a hit point"""
901 if not _LIDAR_FUNCTIONS_AVAILABLE:
902 raise NotImplementedError("LiDAR functions not available")
903 return bool(helios_lib.doesLiDARHitDataExist(cloud_ptr, index, label.encode('utf-8')))
904
905
906def getLiDARHitData(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int, label: str) -> float:
907 """Get a named scalar data value for a hit point"""
908 if not _LIDAR_FUNCTIONS_AVAILABLE:
909 raise NotImplementedError("LiDAR functions not available")
910 return helios_lib.getLiDARHitData(cloud_ptr, index, label.encode('utf-8'))
911
912
913def getLiDARHitData_all(cloud_ptr: ctypes.POINTER(ULiDARcloud), label: str, n: int) -> List[float]:
914 """Bulk-export a named scalar data value for all hit points in one call"""
915 if not _LIDAR_FUNCTIONS_AVAILABLE:
916 raise NotImplementedError("LiDAR functions not available")
917
918 out = (ctypes.c_float * n)()
919 helios_lib.getLiDARHitData_all(cloud_ptr, label.encode('utf-8'), out, n)
920 return list(out)
921
923def getLiDARHitsXYZRGB_all(cloud_ptr: ctypes.POINTER(ULiDARcloud), n: int) -> Tuple[List[float], List[float]]:
924 """Bulk-export XYZ coordinates and RGB colors for all hit points in one call.
925
926 Returns a tuple (xyz_flat, rgb_flat) of flat 3*n-element lists.
927 """
928 if not _LIDAR_FUNCTIONS_AVAILABLE:
929 raise NotImplementedError("LiDAR functions not available")
930
931 xyz = (ctypes.c_float * (3 * n))()
932 rgb = (ctypes.c_float * (3 * n))()
933 helios_lib.getLiDARHitsXYZRGB_all(cloud_ptr, xyz, rgb, n)
934 return list(xyz), list(rgb)
936
937def deleteLiDARHitPoint(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> None:
938 """Delete a hit point from the cloud"""
939 if not _LIDAR_FUNCTIONS_AVAILABLE:
940 raise NotImplementedError("LiDAR functions not available")
941 helios_lib.deleteLiDARHitPoint(cloud_ptr, index)
942
943
944def lidarCoordinateShift(cloud_ptr: ctypes.POINTER(ULiDARcloud), shift: List[float]) -> None:
945 """Translate all hit points by a shift vector"""
946 if not _LIDAR_FUNCTIONS_AVAILABLE:
947 raise NotImplementedError("LiDAR functions not available")
948
949 if len(shift) != 3:
950 raise ValueError("Shift must be a 3-element array [x, y, z]")
951
952 shift_array = (ctypes.c_float * 3)(*shift)
953 helios_lib.lidarCoordinateShift(cloud_ptr, shift_array)
954
955
956def lidarCoordinateRotation(cloud_ptr: ctypes.POINTER(ULiDARcloud), rotation: List[float]) -> None:
957 """Rotate all hit points by spherical rotation angles"""
958 if not _LIDAR_FUNCTIONS_AVAILABLE:
959 raise NotImplementedError("LiDAR functions not available")
960
961 if len(rotation) < 2:
962 raise ValueError("Rotation must have at least 2 elements [radius, elevation]")
963
964 rotation_array = (ctypes.c_float * 3)(rotation[0], rotation[1], rotation[2] if len(rotation) > 2 else 0)
965 helios_lib.lidarCoordinateRotation(cloud_ptr, rotation_array)
966
967
968def lidarTriangulateHitPoints(cloud_ptr: ctypes.POINTER(ULiDARcloud),
969 Lmax: float, max_aspect_ratio: float) -> None:
970 """Generate triangle mesh from hit points"""
971 if not _LIDAR_FUNCTIONS_AVAILABLE:
972 raise NotImplementedError("LiDAR functions not available")
973 helios_lib.lidarTriangulateHitPoints(cloud_ptr, Lmax, max_aspect_ratio)
974
975
976def getLiDARTriangleCount(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> int:
977 """Get number of triangles in the mesh"""
978 if not _LIDAR_FUNCTIONS_AVAILABLE:
979 raise NotImplementedError("LiDAR functions not available")
980 return helios_lib.getLiDARTriangleCount(cloud_ptr)
981
982
983def getLiDARTriangulationStats(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> dict:
984 """Filter diagnostics from the most recent triangulateHitPoints() call.
986 Returns a dict with candidate/dropped counts. Each dropped triangle is
987 attributed to one primary reason, so:
988 candidates == kept + dropped_lmax + dropped_aspect + dropped_degenerate
989 where `kept` equals getLiDARTriangleCount(). All zero if triangulation has
990 not been run.
991 """
992 if not _LIDAR_FUNCTIONS_AVAILABLE:
993 raise NotImplementedError("LiDAR functions not available")
994 out = (ctypes.c_uint * 4)()
995 helios_lib.getLiDARTriangulationStats(cloud_ptr, out)
996 return {
997 "candidates": int(out[0]),
998 "dropped_lmax": int(out[1]),
999 "dropped_aspect": int(out[2]),
1000 "dropped_degenerate": int(out[3]),
1001 }
1002
1003
1004def getLiDARTriangleVertices_all(cloud_ptr: ctypes.POINTER(ULiDARcloud), tri_count: int):
1005 """Bulk-export every triangle's vertices (and source scan) in one call.
1006
1007 Returns (xyz_flat, scan_ids) as numpy arrays: xyz_flat is (tri_count*9,)
1008 float32 laid out [v0x,v0y,v0z, v1x,v1y,v1z, v2x,v2y,v2z] per triangle, and
1009 scan_ids is (tri_count,) int32. Returns empty arrays when tri_count is 0.
1010 """
1011 import numpy as np
1012 if not _LIDAR_FUNCTIONS_AVAILABLE:
1013 raise NotImplementedError("LiDAR functions not available")
1014 if tri_count <= 0:
1015 return np.empty((0,), dtype=np.float32), np.empty((0,), dtype=np.int32)
1016
1017 xyz = np.empty((tri_count * 9,), dtype=np.float32)
1018 scan = np.empty((tri_count,), dtype=np.int32)
1019 helios_lib.getLiDARTriangleVertices_all(
1020 cloud_ptr,
1021 xyz.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
1022 scan.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
1023 tri_count,
1024 )
1025 return xyz, scan
1026
1027
1028def lidarDistanceFilter(cloud_ptr: ctypes.POINTER(ULiDARcloud), maxdistance: float) -> None:
1029 """Filter hit points by maximum distance"""
1030 if not _LIDAR_FUNCTIONS_AVAILABLE:
1031 raise NotImplementedError("LiDAR functions not available")
1032 helios_lib.lidarDistanceFilter(cloud_ptr, maxdistance)
1033
1034
1035def lidarReflectanceFilter(cloud_ptr: ctypes.POINTER(ULiDARcloud), minreflectance: float) -> None:
1036 """Filter hit points by minimum reflectance"""
1037 if not _LIDAR_FUNCTIONS_AVAILABLE:
1038 raise NotImplementedError("LiDAR functions not available")
1039 helios_lib.lidarReflectanceFilter(cloud_ptr, minreflectance)
1040
1041
1042def lidarFirstHitFilter(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1043 """Keep only first return hit points"""
1044 if not _LIDAR_FUNCTIONS_AVAILABLE:
1045 raise NotImplementedError("LiDAR functions not available")
1046 helios_lib.lidarFirstHitFilter(cloud_ptr)
1047
1048
1049def lidarLastHitFilter(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1050 """Keep only last return hit points"""
1051 if not _LIDAR_FUNCTIONS_AVAILABLE:
1052 raise NotImplementedError("LiDAR functions not available")
1053 helios_lib.lidarLastHitFilter(cloud_ptr)
1054
1055
1056def exportLiDARPointCloud(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str,
1057 write_header: bool = True) -> None:
1058 """Export point cloud to ASCII file (optionally with a '#'-prefixed column header)"""
1059 if not _LIDAR_FUNCTIONS_AVAILABLE:
1060 raise NotImplementedError("LiDAR functions not available")
1061 helios_lib.exportLiDARPointCloud(cloud_ptr, filename.encode('utf-8'), bool(write_header))
1062
1063
1064def exportLiDARLeafAreaUncertainty(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1065 """Export per-voxel leaf-area sampling uncertainty to a self-describing ASCII file"""
1066 if not _LIDAR_FUNCTIONS_AVAILABLE:
1067 raise NotImplementedError("LiDAR functions not available")
1068 helios_lib.exportLiDARLeafAreaUncertainty(cloud_ptr, filename.encode('utf-8'))
1069
1070
1071def exportLiDARScans(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1072 """Export all scans to an XML metadata file plus one ASCII data file per scan"""
1073 if not _LIDAR_FUNCTIONS_AVAILABLE:
1074 raise NotImplementedError("LiDAR functions not available")
1075 helios_lib.exportLiDARScans(cloud_ptr, filename.encode('utf-8'))
1076
1077
1078def loadLiDARXML(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1079 """Load scan metadata from XML file"""
1080 if not _LIDAR_FUNCTIONS_AVAILABLE:
1081 raise NotImplementedError("LiDAR functions not available")
1082 helios_lib.loadLiDARXML(cloud_ptr, filename.encode('utf-8'))
1083
1084
1085def lidarDisableMessages(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1086 """Disable console output messages"""
1087 if not _LIDAR_FUNCTIONS_AVAILABLE:
1088 raise NotImplementedError("LiDAR functions not available")
1089 helios_lib.lidarDisableMessages(cloud_ptr)
1090
1091
1092def lidarEnableMessages(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1093 """Enable console output messages"""
1094 if not _LIDAR_FUNCTIONS_AVAILABLE:
1095 raise NotImplementedError("LiDAR functions not available")
1096 helios_lib.lidarEnableMessages(cloud_ptr)
1097
1098
1099def addLiDARGrid(cloud_ptr: ctypes.POINTER(ULiDARcloud), center: List[float],
1100 size: List[float], ndiv: List[int], rotation: float) -> None:
1101 """Add a rectangular grid of voxel cells"""
1102 if not _LIDAR_FUNCTIONS_AVAILABLE:
1103 raise NotImplementedError("LiDAR functions not available")
1104
1105 if len(center) != 3:
1106 raise ValueError("Center must be a 3-element array [x, y, z]")
1107 if len(size) != 3:
1108 raise ValueError("Size must be a 3-element array [x, y, z]")
1109 if len(ndiv) != 3:
1110 raise ValueError("Ndiv must be a 3-element array [nx, ny, nz]")
1111
1112 center_array = (ctypes.c_float * 3)(*center)
1113 size_array = (ctypes.c_float * 3)(*size)
1114 ndiv_array = (ctypes.c_int * 3)(*ndiv)
1115 helios_lib.addLiDARGrid(cloud_ptr, center_array, size_array, ndiv_array, rotation)
1116
1117
1118def addLiDARGridCell(cloud_ptr: ctypes.POINTER(ULiDARcloud), center: List[float],
1119 size: List[float], rotation: float) -> None:
1120 """Add a single grid cell"""
1121 if not _LIDAR_FUNCTIONS_AVAILABLE:
1122 raise NotImplementedError("LiDAR functions not available")
1123
1124 if len(center) != 3:
1125 raise ValueError("Center must be a 3-element array [x, y, z]")
1126 if len(size) != 3:
1127 raise ValueError("Size must be a 3-element array [x, y, z]")
1128
1129 center_array = (ctypes.c_float * 3)(*center)
1130 size_array = (ctypes.c_float * 3)(*size)
1131 helios_lib.addLiDARGridCell(cloud_ptr, center_array, size_array, rotation)
1132
1133
1134def getLiDARGridCellCount(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> int:
1135 """Get number of grid cells"""
1136 if not _LIDAR_FUNCTIONS_AVAILABLE:
1137 raise NotImplementedError("LiDAR functions not available")
1138 return helios_lib.getLiDARGridCellCount(cloud_ptr)
1139
1140
1141def getLiDARCellCenter(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> List[float]:
1142 """Get center position of a grid cell"""
1143 if not _LIDAR_FUNCTIONS_AVAILABLE:
1144 raise NotImplementedError("LiDAR functions not available")
1145
1146 center = (ctypes.c_float * 3)()
1147 helios_lib.getLiDARCellCenter(cloud_ptr, index, center)
1148 return list(center)
1149
1151def getLiDARCellSize(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> List[float]:
1152 """Get size of a grid cell"""
1153 if not _LIDAR_FUNCTIONS_AVAILABLE:
1154 raise NotImplementedError("LiDAR functions not available")
1155
1156 size = (ctypes.c_float * 3)()
1157 helios_lib.getLiDARCellSize(cloud_ptr, index, size)
1158 return list(size)
1159
1161def getLiDARCellLeafArea(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1162 """Get leaf area of a grid cell"""
1163 if not _LIDAR_FUNCTIONS_AVAILABLE:
1164 raise NotImplementedError("LiDAR functions not available")
1165 return helios_lib.getLiDARCellLeafArea(cloud_ptr, index)
1166
1167
1168def getLiDARCellLeafAreaDensity(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1169 """Get leaf area density of a grid cell"""
1170 if not _LIDAR_FUNCTIONS_AVAILABLE:
1171 raise NotImplementedError("LiDAR functions not available")
1172 return helios_lib.getLiDARCellLeafAreaDensity(cloud_ptr, index)
1173
1174
1175def calculateLiDARHitGridCell(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1176 """Calculate hit point grid cell assignments"""
1177 if not _LIDAR_FUNCTIONS_AVAILABLE:
1178 raise NotImplementedError("LiDAR functions not available")
1179 helios_lib.calculateLiDARHitGridCell(cloud_ptr)
1180
1181
1182def syntheticLiDARScan(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1183 context_ptr: ctypes.POINTER(UContext)) -> None:
1184 """Perform synthetic discrete-return LiDAR scan"""
1185 if not _LIDAR_FUNCTIONS_AVAILABLE:
1186 raise NotImplementedError("LiDAR functions not available")
1187 helios_lib.syntheticLiDARScan(cloud_ptr, context_ptr)
1188
1189
1190def syntheticLiDARScanAppend(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1191 context_ptr: ctypes.POINTER(UContext),
1192 append: bool) -> None:
1193 """Perform synthetic scan with append control"""
1194 if not _LIDAR_FUNCTIONS_AVAILABLE:
1195 raise NotImplementedError("LiDAR functions not available")
1196 helios_lib.syntheticLiDARScanAppend(cloud_ptr, context_ptr, append)
1197
1198
1199def syntheticLiDARScanDiscrete(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1200 context_ptr: ctypes.POINTER(UContext),
1201 scan_grid_only: bool, record_misses: bool, append: bool) -> None:
1202 """Perform discrete-return synthetic scan with miss-recording control"""
1203 if not _LIDAR_FUNCTIONS_AVAILABLE:
1204 raise NotImplementedError("LiDAR functions not available")
1205 helios_lib.syntheticLiDARScanDiscrete(cloud_ptr, context_ptr, scan_grid_only, record_misses, append)
1206
1207
1208def syntheticLiDARScanWaveform(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1209 context_ptr: ctypes.POINTER(UContext),
1210 rays_per_pulse: int,
1211 pulse_distance_threshold: float) -> None:
1212 """Perform synthetic full-waveform LiDAR scan"""
1213 if not _LIDAR_FUNCTIONS_AVAILABLE:
1214 raise NotImplementedError("LiDAR functions not available")
1215 helios_lib.syntheticLiDARScanWaveform(cloud_ptr, context_ptr, rays_per_pulse, pulse_distance_threshold)
1216
1218def syntheticLiDARScanFull(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1219 context_ptr: ctypes.POINTER(UContext),
1220 rays_per_pulse: int,
1221 pulse_distance_threshold: float,
1222 scan_grid_only: bool,
1223 record_misses: bool,
1224 append: bool) -> None:
1225 """Perform synthetic scan with full control"""
1226 if not _LIDAR_FUNCTIONS_AVAILABLE:
1227 raise NotImplementedError("LiDAR functions not available")
1228 helios_lib.syntheticLiDARScanFull(cloud_ptr, context_ptr, rays_per_pulse,
1229 pulse_distance_threshold, scan_grid_only,
1230 record_misses, append)
1231
1232
1233def calculateLiDARLeafArea(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1234 context_ptr: ctypes.POINTER(UContext)) -> None:
1235 """Calculate leaf area for each grid cell"""
1236 if not _LIDAR_FUNCTIONS_AVAILABLE:
1237 raise NotImplementedError("LiDAR functions not available")
1238 helios_lib.calculateLiDARLeafArea(cloud_ptr, context_ptr)
1239
1240
1241def calculateLiDARLeafAreaMinHits(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1242 context_ptr: ctypes.POINTER(UContext),
1243 min_voxel_hits: int) -> None:
1244 """Calculate leaf area with minimum voxel hits threshold"""
1245 if not _LIDAR_FUNCTIONS_AVAILABLE:
1246 raise NotImplementedError("LiDAR functions not available")
1247 helios_lib.calculateLiDARLeafAreaMinHits(cloud_ptr, context_ptr, min_voxel_hits)
1248
1249
1250def calculateLiDARLeafAreaUncertainty(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1251 context_ptr: ctypes.POINTER(UContext),
1252 min_voxel_hits: int, element_width: float) -> None:
1253 """Calculate leaf area plus per-voxel sampling uncertainty (Pimont et al. 2018)"""
1254 if not _LIDAR_FUNCTIONS_AVAILABLE:
1255 raise NotImplementedError("LiDAR functions not available")
1256 helios_lib.calculateLiDARLeafAreaUncertainty(
1257 cloud_ptr, context_ptr, min_voxel_hits, float(element_width))
1258
1260def getLiDARCellBeamCount(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> int:
1261 """Get the beam count N entering a grid cell (-1 if calculateLeafArea not run)."""
1262 if not _LIDAR_FUNCTIONS_AVAILABLE:
1263 raise NotImplementedError("LiDAR functions not available")
1264 return helios_lib.getLiDARCellBeamCount(cloud_ptr, index)
1265
1266
1267def getLiDARCellRelativeDensityIndex(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1268 """Get the relative density index I_rdi for a grid cell."""
1269 if not _LIDAR_FUNCTIONS_AVAILABLE:
1270 raise NotImplementedError("LiDAR functions not available")
1271 return helios_lib.getLiDARCellRelativeDensityIndex(cloud_ptr, index)
1272
1273
1274def getLiDARCellMeanPathLength(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1275 """Get the mean beam path length (m) through a grid cell."""
1276 if not _LIDAR_FUNCTIONS_AVAILABLE:
1277 raise NotImplementedError("LiDAR functions not available")
1278 return helios_lib.getLiDARCellMeanPathLength(cloud_ptr, index)
1279
1280
1281def getLiDARCellLADVariance(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1282 """Get the per-voxel LAD sampling variance (-1 if unavailable)."""
1283 if not _LIDAR_FUNCTIONS_AVAILABLE:
1284 raise NotImplementedError("LiDAR functions not available")
1285 return helios_lib.getLiDARCellLADVariance(cloud_ptr, index)
1286
1287
1288def getLiDARCellLeafAreaConfidenceInterval(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int,
1289 confidence_level: float = 0.95):
1290 """Single-voxel leaf-area confidence interval.
1291
1292 Returns (valid, lower, upper). ``valid`` is False when the interval is gated
1293 out by the Pimont validity envelope (the bounds are then not trustworthy).
1294 """
1295 if not _LIDAR_FUNCTIONS_AVAILABLE:
1296 raise NotImplementedError("LiDAR functions not available")
1297 out = (ctypes.c_float * 2)()
1298 valid = helios_lib.getLiDARCellLeafAreaConfidenceInterval(
1299 cloud_ptr, index, float(confidence_level), out)
1300 return bool(valid), float(out[0]), float(out[1])
1302
1303def getLiDARGroupLADConfidenceInterval(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1304 indices: List[int], confidence_level: float = 0.95):
1305 """Group-scale LAD confidence interval over a set of cells (recommended path).
1306
1307 Returns (valid, mean_lad, lower, upper).
1308 """
1309 if not _LIDAR_FUNCTIONS_AVAILABLE:
1310 raise NotImplementedError("LiDAR functions not available")
1311 if not indices:
1312 raise ValueError("indices must contain at least one cell index")
1313 n = len(indices)
1314 idx_array = (ctypes.c_uint * n)(*[int(i) for i in indices])
1315 out = (ctypes.c_float * 3)()
1316 valid = helios_lib.getLiDARGroupLADConfidenceInterval(
1317 cloud_ptr, idx_array, n, float(confidence_level), out)
1318 return bool(valid), float(out[0]), float(out[1]), float(out[2])
1319
1320
1321def exportLiDARTriangleNormals(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1322 """Export triangle normal vectors"""
1323 if not _LIDAR_FUNCTIONS_AVAILABLE:
1324 raise NotImplementedError("LiDAR functions not available")
1325 helios_lib.exportLiDARTriangleNormals(cloud_ptr, filename.encode('utf-8'))
1326
1327
1328def exportLiDARTriangleAreas(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1329 """Export triangle areas"""
1330 if not _LIDAR_FUNCTIONS_AVAILABLE:
1331 raise NotImplementedError("LiDAR functions not available")
1332 helios_lib.exportLiDARTriangleAreas(cloud_ptr, filename.encode('utf-8'))
1333
1334
1335def exportLiDARLeafAreas(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1336 """Export leaf areas for each grid cell"""
1337 if not _LIDAR_FUNCTIONS_AVAILABLE:
1338 raise NotImplementedError("LiDAR functions not available")
1339 helios_lib.exportLiDARLeafAreas(cloud_ptr, filename.encode('utf-8'))
1340
1341
1342def exportLiDARLeafAreaDensities(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1343 """Export leaf area densities for each grid cell"""
1344 if not _LIDAR_FUNCTIONS_AVAILABLE:
1345 raise NotImplementedError("LiDAR functions not available")
1346 helios_lib.exportLiDARLeafAreaDensities(cloud_ptr, filename.encode('utf-8'))
1347
1348
1349def exportLiDARGtheta(cloud_ptr: ctypes.POINTER(ULiDARcloud), filename: str) -> None:
1350 """Export G(theta) values for each grid cell"""
1351 if not _LIDAR_FUNCTIONS_AVAILABLE:
1352 raise NotImplementedError("LiDAR functions not available")
1353 helios_lib.exportLiDARGtheta(cloud_ptr, filename.encode('utf-8'))
1354
1355
1356def getLiDARCellGtheta(cloud_ptr: ctypes.POINTER(ULiDARcloud), index: int) -> float:
1357 """Get G(theta) value for a grid cell"""
1358 if not _LIDAR_FUNCTIONS_AVAILABLE:
1359 raise NotImplementedError("LiDAR functions not available")
1360 return helios_lib.getLiDARCellGtheta(cloud_ptr, index)
1361
1362
1363def setLiDARCellGtheta(cloud_ptr: ctypes.POINTER(ULiDARcloud), Gtheta: float, index: int) -> None:
1364 """Set G(theta) value for a grid cell"""
1365 if not _LIDAR_FUNCTIONS_AVAILABLE:
1366 raise NotImplementedError("LiDAR functions not available")
1367 helios_lib.setLiDARCellGtheta(cloud_ptr, Gtheta, index)
1368
1369
1370def gapfillLiDARMisses(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1371 """Gapfill sky/miss points"""
1372 if not _LIDAR_FUNCTIONS_AVAILABLE:
1373 raise NotImplementedError("LiDAR functions not available")
1374 helios_lib.gapfillLiDARMisses(cloud_ptr)
1375
1376
1377def calculateSyntheticLiDARLeafArea(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1378 context_ptr: ctypes.POINTER(UContext)) -> None:
1379 """Calculate synthetic leaf area for validation"""
1380 if not _LIDAR_FUNCTIONS_AVAILABLE:
1381 raise NotImplementedError("LiDAR functions not available")
1382 helios_lib.calculateSyntheticLiDARLeafArea(cloud_ptr, context_ptr)
1383
1384
1385def calculateSyntheticLiDARGtheta(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1386 context_ptr: ctypes.POINTER(UContext)) -> None:
1387 """Calculate synthetic G(theta) for validation"""
1388 if not _LIDAR_FUNCTIONS_AVAILABLE:
1389 raise NotImplementedError("LiDAR functions not available")
1390 helios_lib.calculateSyntheticLiDARGtheta(cloud_ptr, context_ptr)
1391
1392
1393def addLiDARTrianglesToContext(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1394 context_ptr: ctypes.POINTER(UContext)) -> None:
1395 """Add triangulated mesh to Context as primitives"""
1396 if not _LIDAR_FUNCTIONS_AVAILABLE:
1397 raise NotImplementedError("LiDAR functions not available")
1398 helios_lib.addLiDARTrianglesToContext(cloud_ptr, context_ptr)
1399
1400
1401def initializeLiDARCollisionDetection(cloud_ptr: ctypes.POINTER(ULiDARcloud),
1402 context_ptr: ctypes.POINTER(UContext)) -> None:
1403 """Initialize CollisionDetection for ray tracing"""
1404 if not _LIDAR_FUNCTIONS_AVAILABLE:
1405 raise NotImplementedError("LiDAR functions not available")
1406 helios_lib.initializeLiDARCollisionDetection(cloud_ptr, context_ptr)
1407
1408
1409def enableLiDARCDGPUAcceleration(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1410 """Enable GPU acceleration for collision detection"""
1411 if not _LIDAR_FUNCTIONS_AVAILABLE:
1412 raise NotImplementedError("LiDAR functions not available")
1413 helios_lib.enableLiDARCDGPUAcceleration(cloud_ptr)
1414
1415
1416def disableLiDARCDGPUAcceleration(cloud_ptr: ctypes.POINTER(ULiDARcloud)) -> None:
1417 """Disable GPU acceleration for collision detection"""
1418 if not _LIDAR_FUNCTIONS_AVAILABLE:
1419 raise NotImplementedError("LiDAR functions not available")
1420 helios_lib.disableLiDARCDGPUAcceleration(cloud_ptr)
1421
1422
1423# Mock mode for development
1424if not _LIDAR_FUNCTIONS_AVAILABLE:
1425 def mock_createLiDARcloud(*args, **kwargs):
1426 raise RuntimeError(
1427 "Mock mode: LiDAR not available. "
1428 "This would create a LiDAR cloud instance with native library."
1429 )
1430
1431 def mock_lidar_operation(*args, **kwargs):
1432 raise RuntimeError(
1433 "Mock mode: LiDAR operation not available. "
1434 "This would execute LiDAR operations with native library."
1435 )
1436
1437 # Replace functions with mocks for development
1438 createLiDARcloud = mock_createLiDARcloud
1439 addLiDARScan = mock_lidar_operation
1440 addLiDARHitPoint = mock_lidar_operation
Opaque structure for LiDARcloud C++ class.
int getLiDARScanPattern(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the scan pattern (0 = raster, 1 = spinning multibeam).
_check_error(result, func, args)
Automatic error checking for all LiDAR functions.
None addLiDARHitPointRGB(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID, List[float] xyz, List[float] direction, List[float] color)
Add a hit point with color to the cloud.
None addLiDARHitPoints(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID, xyzs, directions, int count, colors=None)
Add many hit points to the cloud in a single call (bulk ingestion).
int addLiDARScanMultibeam(ctypes.POINTER(ULiDARcloud) cloud_ptr, List[float] origin, List[float] beam_zenith_angles, int Nphi, Tuple[float, float] phi_range, float exit_diameter, float beam_divergence, Optional[List[str]] column_format=None, float range_noise_stddev=0.0, float angle_noise_stddev=0.0, float scan_tilt_roll=0.0, float scan_tilt_pitch=0.0)
Add a spinning multibeam LiDAR scan (rotating multi-channel sensor)
bool isLiDARHitMiss(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Return True if the hit is a miss (a transmitted beam that returned nothing).
List[float] getLiDARScanOrigin(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get origin of a specific scan.
None lidarCoordinateShift(ctypes.POINTER(ULiDARcloud) cloud_ptr, List[float] shift)
Translate all hit points by a shift vecto.
List[float] getLiDARScanBeamZenithAngles(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the per-channel beam zenith angles (radians) for a multibeam scan (empty for raster).
int getLiDARScanSizeTheta(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get number of zenith scan points.
float getLiDARScanTiltPitch(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the global scanner tilt pitch angle (radians) for a scan.
int getLiDARScanSizePhi(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get number of azimuthal scan points.
float getLiDARScanRangeNoiseStdDev(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the range (along-beam) measurement noise standard deviation for a scan (meters)
float getLiDARScanAngleNoiseStdDev(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the angular (beam-pointing) jitter standard deviation for a scan (radians)
bool lidarHasMisses(ctypes.POINTER(ULiDARcloud) cloud_ptr)
Return True if the cloud contains at least one miss.
List[float] getLiDARHitXYZ(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Get coordinates of a hit point.
None deleteLiDARHitPoint(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Delete a hit point from the cloud.
List[float] getLiDARHitColor(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Get color of a hit point.
List[float] getLiDARHitRaydir(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Get ray direction of a hit point.
float getLiDARHitData(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index, str label)
Get a named scalar data value for a hit point.
None destroyLiDARcloud(ctypes.POINTER(ULiDARcloud) cloud_ptr)
Destroy LiDARcloud instance.
int getLiDARHitScanID(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index)
Get the scan ID a hit point belongs to.
float getLiDARScanTiltRoll(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID)
Get the global scanner tilt roll angle (radians) for a scan.
createLiDARcloud
Create LiDARcloud instance.
float getLiDARMissDistance()
Return the LIDAR_MISS_DISTANCE constant (meters).
int getLiDARHitCount(ctypes.POINTER(ULiDARcloud) cloud_ptr)
Get total number of hit points.
bool doesLiDARHitDataExist(ctypes.POINTER(ULiDARcloud) cloud_ptr, int index, str label)
Check whether a named scalar data value exists for a hit point.
Tuple[List[float], List[float]] getLiDARHitsXYZRGB_all(ctypes.POINTER(ULiDARcloud) cloud_ptr, int n)
Bulk-export XYZ coordinates and RGB colors for all hit points in one call.
None addLiDARHitPointsWithData(ctypes.POINTER(ULiDARcloud) cloud_ptr, int scanID, xyzs, directions, int count, colors=None, labels=None, data_values=None)
Bulk-ingest hit points carrying a per-hit data map in one call.
int getLiDARScanCount(ctypes.POINTER(ULiDARcloud) cloud_ptr)
Get number of scans in the cloud.
List[float] getLiDARHitData_all(ctypes.POINTER(ULiDARcloud) cloud_ptr, str label, int n)
Bulk-export a named scalar data value for all hit points in one call.