Notebook creator: Hannah Weiser & Sina Zumstein, 2023
This demo simulation uses 3D models of toyblocks, which will be scanned by UAV-borne laser scanning (ULS). We will use the command-line access of HELIOS++ to run the simulation, and use Python just for displaying the input XMLs and the resulting point cloud.
from IPython.display import Code
from pyhelios.util.xmldisplayer import display_xml, find_playback_dir
import os
os.chdir("..")
Let's just have a quick look at the survey XML file uls_toyblocks_survey_scene_combo.xml
in data/surveys/toyblocks
and what is special about it:
Code(display_xml('data/surveys/toyblocks/uls_toyblocks_survey_scene_combo.xml'), language='XML')
<document>
<survey name="toyblocks_uls_surveyscene" platform="data/platforms.xml#quadcopter" scanner="data/scanners_als.xml#riegl_vux-1uav22" scene="data/surveys/toyblocks/uls_toyblocks_survey_scene_combo.xml#toyblocks_scene">
<!-- platform: copter_linearpath, deflector: rotating -->
<leg>
<platformSettings x="-70.0" y="-60.0" z="80.000" onGround="false" movePerSec_m="10" smoothTurn="true" slowdownEnabled="false" />
<scannerSettings active="true" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
<leg>
<platformSettings x="70.0" y="-60.0" z="80.000" onGround="false" movePerSec_m="10" smoothTurn="true" slowdownEnabled="false" />
<scannerSettings active="true" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
<leg>
<platformSettings x="70.0" y="60.0" z="80.000" onGround="false" movePerSec_m="7" smoothTurn="true" slowdownEnabled="false" />
<scannerSettings active="true" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
<leg>
<platformSettings x="-70.0" y="60.0" z="80.000" onGround="false" movePerSec_m="10" smoothTurn="true" slowdownEnabled="false" />
<scannerSettings active="false" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
<leg>
<platformSettings x="0.0" y="-60.0" z="80.000" onGround="false" movePerSec_m="4" smoothTurn="true" slowdownEnabled="false" />
<scannerSettings active="true" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
<leg>
<platformSettings x="0.0" y="60.0" z="80.000" onGround="false" movePerSec_m="10" />
<scannerSettings active="false" pulseFreq_hz="50000" scanAngle_deg="90" scanFreq_hz="25" headRotatePerSec_deg="0.00" headRotateStart_deg="0.00" headRotateStop_deg="0.00" trajectoryTimeInterval_s="0.05" />
</leg>
</survey>
<scene id="toyblocks_scene" name="ToyblocksScene">
<part>
<filter type="objloader">
<param type="string" key="filepath" value="data/sceneparts/basic/groundplane/groundplane.obj" />
</filter>
<filter type="scale">
<param type="double" key="scale" value="80" />
</filter>
</part>
<part>
<filter type="objloader">
<param type="string" key="efilepath" value="data/sceneparts/toyblocks/.*.obj" />
</filter>
<filter type="translate">
<param type="vec3" key="offset" value="-40.0;0.0;0" />
</filter>
</part>
</scene>
</document>
Here we see the typical survey parameters such as the <survey>
tag that define the scanner, platform, and scene, and the leg
elements that define the waypoints and the respective scanner and platform settings. We also see that the scene file referenced in the survey
tag is the survey file itself. Both the <survey>
and the <scene>
configurations are combined in a single XML file.
The scene is a very simple toy scene consisting of a ground plane and all .obj
files that can be found in the directory data/sceneparts/toyblocks
. To load these multiple objects with a single scene part
definition, the efilepath
parameter is used. It allows loading multiple objects from file using regular expressions. All of the toyblocks objects are furthermore translated by -40 m along the x-axis.
Next, we will run the simulations for both surveys. In Jupyter Notebooks, we can run external commands with the !command syntax, but you can also just run it from the command line. We will run the ULS simulation first:
!helios data/surveys/toyblocks/uls_toyblocks_survey_scene_combo.xml -q
Now we can display the 3D plot.
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
output_path = find_playback_dir('data/surveys/toyblocks/uls_toyblocks_survey_scene_combo.xml')
print('Loading points from', Path(output_path))
strip_1 = np.loadtxt(Path(output_path) / 'leg000_points.xyz')
strip_2 = np.loadtxt(Path(output_path) / 'leg001_points.xyz')
strip_3 = np.loadtxt(Path(output_path) / 'leg002_points.xyz')
strip_4 = np.loadtxt(Path(output_path) / 'leg004_points.xyz')
#stacking the strips
strips = np.vstack((strip_1, strip_2, strip_3, strip_4))
traj_1 = np.loadtxt(Path(output_path) / 'leg000_trajectory.txt')
traj_2 = np.loadtxt(Path(output_path) / 'leg001_trajectory.txt')
traj_3 = np.loadtxt(Path(output_path) / 'leg002_trajectory.txt')
traj_4 = np.loadtxt(Path(output_path) / 'leg004_trajectory.txt')
#stacking the trajectories
traj = np.vstack((traj_1, traj_2, traj_3, traj_4))
Loading points from E:\Software\_helios_versions\helios\output\toyblocks_uls_surveyscene\2024-05-28_18-47-16
# Matplotlib figures.
fig = plt.figure(figsize=(15,10))
#settings for a discrete colorbar
N=6
cmap=plt.get_cmap('jet',N)
# Scatter plot (coloured by hitObjectId).
ax = fig.add_subplot(projection='3d', computed_zorder=False)
sc = ax.scatter(strips[:, 0], strips[:, 1], strips[:, 2], c=strips[:, 8], cmap=cmap, s=0.02, label='scene', zorder=1)
# Plot of trajectory.
ax.plot(traj[:,0], traj[:,1], traj[:,2], c = 'black', label = 'scanner trajectory', zorder=2)
# Add axis labels.
ax.set_xlabel('$X$')
ax.set_ylabel('$Y$')
ax.set_zlabel('$Z$')
# set equal axes
box = (np.ptp(np.hstack((strips[:, 0], traj[:, 0]))), np.ptp(np.hstack((strips[:, 1], traj[:, 1]))), np.ptp(np.hstack((strips[:, 2], traj[:, 2]))))
ax.set_box_aspect(box)
# Set title.
ax.set_title(label='Point cloud and trajectory',fontsize=18)
cbar = plt.colorbar(sc, ticks=[5/12, 5/4, 25/12, 35/12, 45/12, 55/12])
cbar.set_label('Object Id', fontsize=15)
cbar.ax.set_yticklabels(['0', '1', '2', '3', '4', '5'])
# Display results
plt.show()