Skip to content

Commit 61c0b49

Browse files
authored
changing the timestamps that are written to fullwave files. (#71)
* changing the timestamps that are written to fullwave files. * adding fullwave conversion script in first version * added explanations * version bump * fixing shifted position due to waveform time shift, added one bin safety margin
1 parent 9eec326 commit 61c0b49

File tree

4 files changed

+96
-12
lines changed

4 files changed

+96
-12
lines changed

pyhelios_demo/txt2las_wdp.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/python
2+
# txt2las_wdp
3+
# copyright 2021 3DGeo Heidelberg
4+
# Script to transform a HELIOS++ xyz point cloud and fullwave file into las1.4 and external WDP file
5+
# Result can be viewed e.g. with CloudCompare (open file with "open"-> select "las 1.3 and las 1.4" as file type)
6+
7+
import struct
8+
import laspy
9+
import numpy as np
10+
from collections import OrderedDict
11+
12+
wfs = OrderedDict()
13+
with open(r"..\output\Survey Playback\als_hd_demo\points\leg000_fullwave.txt", 'r') as inf:
14+
for line in inf.readlines():
15+
data = [float(x) for x in line.split(" ")]
16+
wf_idx = int(data[0])
17+
tmin = data[7]
18+
tmax = data[8]
19+
fwf = np.abs(np.array(data[10:]))
20+
fwf = fwf/np.max(fwf) * 255.
21+
fwf = fwf.astype(int)
22+
dx, dy, dz = data[4], data[5], data[6]
23+
wfs[wf_idx] = [tmin, tmax, dx, dy, dz, fwf]
24+
25+
max_len = max([len(x[-1][-1]) for x in wfs.items()])
26+
wfs_lut = {idx: val for idx, val in enumerate(np.unique(wfs.keys())[0])}
27+
wfs_lut_rev = {val: idx for idx, val in wfs_lut.items()}
28+
pts = np.loadtxt(r"..\output\Survey Playback\als_hd_demo\points\leg000_points.xyz", delimiter=" ")
29+
30+
las = laspy.create(file_version="1.4", point_format=4)
31+
xyz = pts[:, :3]
32+
las.header.offsets = np.min(xyz, axis=0)
33+
las.header.scales = [0.001, 0.001, 0.001]
34+
las.x = xyz[:, 0]
35+
las.y = xyz[:, 1]
36+
las.z = xyz[:, 2]
37+
las.intensity = pts[:, 3]
38+
las.wavepacket_index=np.ones((xyz.shape[0])) # 1 refers to the value 100 (99+1) in the VLR below
39+
las.wavepacket_offset = 60 + np.array([wfs_lut_rev[idx] for idx in pts[:, 7]]) * (max_len)
40+
las.wavepacket_size = [max_len] * xyz.shape[0] # always write a wavepacket of the longest size
41+
las.return_point_wave_location = [0] * xyz.shape[0] # adapt this if you need the information on which position this point
42+
# represents in the waveform
43+
las.x_t = [wfs[idx][2] for idx in pts[:, 7]]
44+
las.y_t = [wfs[idx][3] for idx in pts[:, 7]]
45+
las.z_t = [wfs[idx][4] for idx in pts[:, 7]]
46+
47+
VLR_data = struct.pack("<BBLLdd",
48+
8, # bits per sample
49+
0, # compression type
50+
max_len, # number of samples
51+
10, # temporal sample spacing (ps)
52+
1, # digitizer gain
53+
0, # digitizer offset
54+
)
55+
new_vlr = laspy.VLR(user_id="LASF_Spec", record_id=100, record_data=VLR_data)
56+
las.vlrs.append(new_vlr)
57+
58+
las.write("test.las")
59+
60+
wf_data = bytes() # WDP header (same as EVLR header)
61+
wf_data = b''.join([wf_data, struct.pack("<H", 0)]) #reserved
62+
wf_data = b''.join([wf_data, struct.pack("<s", b"LASF_Spec ")]) #user id
63+
wf_data = b''.join([wf_data, struct.pack("<H", 65535)]) #record id
64+
wf_data = b''.join([wf_data, struct.pack("<Q", max_len*len(wfs))]) #record length after header
65+
wf_data = b''.join([wf_data, struct.pack("<s", b" "*32)]) #descripton
66+
67+
with open("test.wdp", 'wb') as f:
68+
f.write(wf_data)
69+
70+
for wf in wfs.items():
71+
byt = np.zeros((max_len,), dtype=int)
72+
byt[:len(wf[1][-1])] = [int(d) for d in wf[1][-1]]
73+
f.write(struct.pack("<%dB" % max_len, *byt))
74+
75+
76+
with open("test.las", 'r+b') as f:
77+
f.seek(6)
78+
f.write(struct.pack("<H", int('0000000000000100', 2))) # set "external waveform" bit to 1 in las file (not possible with laspy)

src/helios_version.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include <helios_version.h>
22

3-
const char * HELIOS_VERSION = "1.0.8";
3+
const char * HELIOS_VERSION = "1.0.9";
44

55
const char * getHeliosVersion(){
66
return HELIOS_VERSION;

src/scanner/detector/FullWaveformPulseRunnable.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ void FullWaveformPulseRunnable::digestIntersections(
269269
fullwave,
270270
distanceThreshold,
271271
minHitTime_ns,
272-
nsPerBin
272+
nsPerBin,
273+
peakIntensityIndex
273274
);
274275

275276
// Digest full waveform, generating measurements
@@ -337,12 +338,17 @@ bool FullWaveformPulseRunnable::initializeFullWaveform(
337338
){
338339
// Calc time at minimum and maximum distance
339340
// (i.e. total beam time in fwf signal)
340-
minHitTime_ns = minHitDist_m / cfg_speedOfLight_mPerNanosec;
341-
maxHitTime_ns = maxHitDist_m / cfg_speedOfLight_mPerNanosec;
341+
342+
peakIntensityIndex = detector->scanner->peakIntensityIndex;
343+
nsPerBin = detector->scanner->FWF_settings.binSize_ns;
344+
345+
minHitTime_ns = minHitDist_m / cfg_speedOfLight_mPerNanosec - peakIntensityIndex * nsPerBin; // time until first maximum minus rising flank
346+
maxHitTime_ns = maxHitDist_m / cfg_speedOfLight_mPerNanosec + // time until last maximum
347+
detector->scanner->getPulseLength_ns() - peakIntensityIndex * nsPerBin + // time for signal decay
348+
nsPerBin; // 1 bin for buffer
342349

343350
// Calc ranges and threshold
344-
double hitTimeDelta_ns = maxHitTime_ns - minHitTime_ns +
345-
detector->scanner->getPulseLength_ns();
351+
double hitTimeDelta_ns = maxHitTime_ns - minHitTime_ns;
346352
double maxFullwaveRange_ns =
347353
detector->scanner->FWF_settings.maxFullwaveRange_ns;
348354
distanceThreshold = maxHitDist_m;
@@ -359,9 +365,7 @@ bool FullWaveformPulseRunnable::initializeFullWaveform(
359365
}
360366

361367
// Compute fullwave variables
362-
nsPerBin = detector->scanner->FWF_settings.binSize_ns;
363368
numFullwaveBins = (int)(hitTimeDelta_ns / nsPerBin);
364-
peakIntensityIndex = detector->scanner->peakIntensityIndex;
365369

366370
return true;
367371
}
@@ -371,7 +375,8 @@ void FullWaveformPulseRunnable::populateFullWaveform(
371375
std::vector<double> &fullwave,
372376
double distanceThreshold,
373377
double minHitTime_ns,
374-
double nsPerBin
378+
double nsPerBin,
379+
int peakIntensityIndex
375380
){
376381
// Multiply each sub-beam intensity with time_wave and
377382
// add to the full waveform
@@ -382,7 +387,7 @@ void FullWaveformPulseRunnable::populateFullWaveform(
382387
if(entryDistance_m > distanceThreshold) continue;
383388
double entryIntensity = it->second;
384389
double wavePeakTime_ns = (entryDistance_m / cfg_speedOfLight_mPerNanosec); // [ns]
385-
int binStart = (int)((wavePeakTime_ns-minHitTime_ns) / nsPerBin);
390+
int binStart = (int)((wavePeakTime_ns-minHitTime_ns) / nsPerBin) - peakIntensityIndex;
386391
for (size_t i = 0; i < time_wave.size(); i++) {
387392
fullwave[binStart + i] += time_wave[i] * entryIntensity;
388393
}
@@ -440,7 +445,7 @@ void FullWaveformPulseRunnable::digestFullWaveform(
440445

441446
// Compute distance
442447
double distance = cfg_speedOfLight_mPerNanosec *
443-
((i-peakIntensityIndex) * nsPerBin + minHitTime_ns);
448+
(i * nsPerBin + minHitTime_ns);
444449

445450
// Build list of objects that produced this return
446451
double minDifference = numeric_limits<double>::max();

src/scanner/detector/FullWaveformPulseRunnable.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ class FullWaveformPulseRunnable : public AbstractPulseRunnable {
229229
std::vector<double> &fullwave,
230230
double distanceThreshold,
231231
double minHitTime_ns,
232-
double nsPerBin
232+
double nsPerBin,
233+
int peakIntensityIndex
233234
);
234235
/**
235236
* @brief Digest a previously populated full waveform vector,

0 commit comments

Comments
 (0)