{ "cells": [ { "cell_type": "markdown", "id": "99ba5a82", "metadata": {}, "source": [ "# Survey\n", "\n", "The survey is where everything comes together. It takes a scene, a scanner and a platform and the so-called \"legs\" that define the positions/trajectories of the platform during the survey and the speicifc settings of the scanner." ] }, { "cell_type": "code", "execution_count": 1, "id": "8ee46410", "metadata": {}, "outputs": [], "source": [ "import copy\n", "import pandas as pd\n", "import helios" ] }, { "cell_type": "markdown", "id": "f9328326", "metadata": {}, "source": [ "Let's demonstrate this for a basic ALS survey and prepare the platform, scanner and scene." ] }, { "cell_type": "code", "execution_count": 2, "id": "f6c82717", "metadata": {}, "outputs": [], "source": [ "platform = helios.platform_from_name(\"sr22\")\n", "scanner = helios.scanner_from_name(\"leica_als50\")\n", "sceneparts = helios.ScenePart.from_objs(\"../data/sceneparts/toyblocks/*.obj\")\n", "scene = helios.StaticScene(sceneparts)" ] }, { "cell_type": "markdown", "id": "57ad1e95", "metadata": {}, "source": [ "From this, we can create an \"empty\" survey, which we can then populate with legs and settings:" ] }, { "cell_type": "code", "execution_count": 3, "id": "5da547a2", "metadata": {}, "outputs": [], "source": [ "survey = helios.Survey(scene=scene, scanner=scanner, platform=platform)" ] }, { "cell_type": "code", "execution_count": 4, "id": "2c53a05d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "()" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# no legs defined yet\n", "survey.legs" ] }, { "cell_type": "markdown", "id": "385cafbd", "metadata": {}, "source": [ "To use surveys created for ealier versions of `helios` (< v3.0.0), we can also load surveys from XML files. To make sure the survey can be loaded correctly, add the directory where the survey XML file is located as an asset directory (here: the root directory of the cloned `helios` repository):" ] }, { "cell_type": "code", "execution_count": 5, "id": "7829e29f", "metadata": {}, "outputs": [], "source": [ "helios.add_asset_directory(\"D:/Software/_helios_versions/helios/\")\n", "tls_toyblocks_survey = helios.Survey.from_xml(\n", " \"data/surveys/toyblocks/tls_toyblocks.xml\"\n", ")" ] }, { "cell_type": "markdown", "id": "7530cbfe", "metadata": {}, "source": [ "## Scanner settings\n", "\n", "Scanner settings are defined per leg. Often, many or all legs of a survey will share the same scanner settings. We can define them once and then pass them to the legs. Depending on the `optics` of the respective scanner, different scan settings are relevant.\n", "\n", "Note that we always specify the unit of the settings, resolving any ambiguity and ensuring that the settings are correctly interpreted. This is done by the help of the package [pint](https://pint.readthedocs.io/en/stable/). We can either specify the units by multiplying with the unit from `helios.units` or by using a string with the unit." ] }, { "cell_type": "code", "execution_count": 6, "id": "5a3336ab", "metadata": {}, "outputs": [], "source": [ "scan_settings = helios.ScannerSettings(\n", " pulse_frequency=100_000 * helios.units.Hz,\n", " scan_angle=20 * helios.units.deg,\n", " scan_frequency=\"70 Hz\",\n", " trajectory_time_interval=0.01 * helios.units.s,\n", ")" ] }, { "cell_type": "markdown", "id": "2771380b", "metadata": {}, "source": [ "## Platform settings\n", "\n", "Likewise, we also have platform settings. We can use `PlatformSettings` for static platforms and `DynamicPlatformSettings` for moving platforms. The latter allows us to specify the speed of the platform, or - if using interpolated trajectory, the trajectory settings. However, since platform settings are usually not shared between legs, we will often just specify the individual parameters directly when constructing or adding legs." ] }, { "cell_type": "markdown", "id": "8f5f6e25", "metadata": {}, "source": [ "## Legs\n", "\n", "Legs are the positions or waypoints of a survey. In trajectory mode, they can also be portions of a given trajectory.\n", "\n", "We define a list of waypoints that the airplane should fly through during the survey. Additionally, we set the speed of the platform." ] }, { "cell_type": "code", "execution_count": 7, "id": "457b4327", "metadata": {}, "outputs": [], "source": [ "waypoints = [[-50, -25, 250], [50, -25, 250], [50, 25, 250], [-50, 25, 250]]\n", "speed = 30 # m/s" ] }, { "cell_type": "markdown", "id": "db240d78", "metadata": {}, "source": [ "Using a simple loop, we now add the legs at the respective positions and apply the scan settings." ] }, { "cell_type": "code", "execution_count": 8, "id": "cf68190c", "metadata": {}, "outputs": [], "source": [ "for x, y, z in waypoints:\n", " survey.add_leg(x=x, y=y, z=z, speed_m_s=speed, scanner_settings=scan_settings)" ] }, { "cell_type": "markdown", "id": "43e4d33c", "metadata": {}, "source": [ "## Running a survey\n", "\n", "When running a survey, we can provide `ExecutionSettings` and `OutputSettings` to control the execution of the survey and the output format.\n", "\n", "### Execution settings\n", "\n", "For example, we can explicitly change the number of threads, the logging verbosity or the parallelization strategy." ] }, { "cell_type": "code", "execution_count": 9, "id": "008a4a83", "metadata": {}, "outputs": [], "source": [ "execution_settings = helios.ExecutionSettings(\n", " num_threads=4,\n", " verbosity=helios.LogVerbosity.VERY_VERBOSE,\n", " parallelization=helios.ParallelizationStrategy.WAREHOUSE,\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "id": "4655bb17", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "71c0d8abf0044f5fa9f06a8f2e9e402e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Legs: 0%| | 0/4 [00:00, 43847 points, 1 vlrs)>\n", "\n", "[211257.86001 211257.86002 211257.86003 ... 211263.58571 211263.58572\n", " 211263.58573]\n" ] } ], "source": [ "print(las)\n", "print(las.x)\n", "print(las.gps_time)" ] }, { "cell_type": "code", "execution_count": 17, "id": "9d004ce0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([(211257. , [-50.0003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.01, [-49.7003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.02, [-49.4003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.03, [-49.1003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.04, [-48.8003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.05, [-48.5003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.06, [-48.2003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.07, [-47.9003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.08, [-47.6003, -25. , 250. ], 0., 0., 4.71238898),\n", " (211257.09, [-47.3003, -25. , 250. ], 0., 0., 4.71238898)],\n", " dtype=[('gps_time', '" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Display the results with matplotlib\n", "import matplotlib.pyplot as plt\n", "\n", "# 3d figure\n", "fig = plt.figure(figsize=(10, 10))\n", "ax = fig.add_subplot(111, projection=\"3d\")\n", "for i, (angle, pc) in enumerate(results.items()):\n", " ax.scatter(pc[:, 0], pc[:, 1], pc[:, 2], s=1, label=f\"Angle: {angle}°\")\n", "ax.set_xlabel(\"X\")\n", "ax.set_ylabel(\"Y\")\n", "ax.set_zlabel(\"Z\")\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "a193528d", "metadata": {}, "source": [ "### Example 2: Modifications of full waveform settings\n", "\n", "To test different full waveform settings for the same scene, we use the utility function `combine_parameters`. To create the parameter combinations, we change the `bin_size`, `win_size`, and `beam_sample_quality` variables. The `combine_parameters` function is documented in detail in the [Utility functions](utils.ipynb) section, and more information on the full waveform is provided in the [Full waveform and intensity modelling](intensity_fwf.rst) section." ] }, { "cell_type": "code", "execution_count": 21, "id": "850da58d", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "bb8b3169afa34324ae5667cad565e103", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Legs: 0%| | 0/2 [00:00 1).sum()\n", " num_intermediate_returns = (\n", " (pc[\"return_number\"] > 1) & (pc[\"return_number\"] < pc[\"number_of_returns\"])\n", " ).sum()\n", " ratio_intermediate = num_intermediate_returns / num_points\n", " ratio_multiple = num_multiple_returns / num_points\n", " return_metric_results[\n", " params[\"bin_size\"], params[\"win_size\"], params[\"beam_sample_quality\"]\n", " ] = {\n", " \"num_points\": num_points,\n", " \"num_first_returns\": num_first_returns,\n", " \"num_multiple_returns\": num_multiple_returns,\n", " \"ratio_multiple\": ratio_multiple,\n", " \"num_intermediate_returns\": num_intermediate_returns,\n", " \"ratio_intermediate\": ratio_intermediate,\n", " }" ] }, { "cell_type": "markdown", "id": "2e4a2d94", "metadata": {}, "source": [ "Now we create a table to compare the return metrics for the different settings." ] }, { "cell_type": "code", "execution_count": 22, "id": "0a7541f9", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num_pointsnum_first_returnsnum_multiple_returnsratio_multiplenum_intermediate_returnsratio_intermediate
bin_sizewin_sizebeam_sample_quality
0.252.031807581800017570.419%80.004%
51809751800019740.538%220.012%
1.0318656818000165673.520%6630.355%
518799518000179944.252%9830.523%
0.5318729918000172983.896%9050.483%
518888518000188844.703%12990.688%
0.102.031808201800018190.453%80.004%
518102818000110270.567%200.011%
1.0318668618000166853.581%6780.363%
518810118000181004.306%9880.525%
0.5318737118000173703.933%8800.470%
518894618000189454.734%12620.668%
0.052.031807551800017540.417%80.004%
51809531800019520.526%190.010%
1.0318654418000165433.507%6550.351%
518793918000179384.224%9490.505%
0.5318721118000172103.851%8160.436%
518874618000187454.633%11800.625%
\n", "
" ], "text/plain": [ " num_points num_first_returns \\\n", "bin_size win_size beam_sample_quality \n", "0.25 2.0 3 180758 180001 \n", " 5 180975 180001 \n", " 1.0 3 186568 180001 \n", " 5 187995 180001 \n", " 0.5 3 187299 180001 \n", " 5 188885 180001 \n", "0.10 2.0 3 180820 180001 \n", " 5 181028 180001 \n", " 1.0 3 186686 180001 \n", " 5 188101 180001 \n", " 0.5 3 187371 180001 \n", " 5 188946 180001 \n", "0.05 2.0 3 180755 180001 \n", " 5 180953 180001 \n", " 1.0 3 186544 180001 \n", " 5 187939 180001 \n", " 0.5 3 187211 180001 \n", " 5 188746 180001 \n", "\n", " num_multiple_returns ratio_multiple \\\n", "bin_size win_size beam_sample_quality \n", "0.25 2.0 3 757 0.419% \n", " 5 974 0.538% \n", " 1.0 3 6567 3.520% \n", " 5 7994 4.252% \n", " 0.5 3 7298 3.896% \n", " 5 8884 4.703% \n", "0.10 2.0 3 819 0.453% \n", " 5 1027 0.567% \n", " 1.0 3 6685 3.581% \n", " 5 8100 4.306% \n", " 0.5 3 7370 3.933% \n", " 5 8945 4.734% \n", "0.05 2.0 3 754 0.417% \n", " 5 952 0.526% \n", " 1.0 3 6543 3.507% \n", " 5 7938 4.224% \n", " 0.5 3 7210 3.851% \n", " 5 8745 4.633% \n", "\n", " num_intermediate_returns \\\n", "bin_size win_size beam_sample_quality \n", "0.25 2.0 3 8 \n", " 5 22 \n", " 1.0 3 663 \n", " 5 983 \n", " 0.5 3 905 \n", " 5 1299 \n", "0.10 2.0 3 8 \n", " 5 20 \n", " 1.0 3 678 \n", " 5 988 \n", " 0.5 3 880 \n", " 5 1262 \n", "0.05 2.0 3 8 \n", " 5 19 \n", " 1.0 3 655 \n", " 5 949 \n", " 0.5 3 816 \n", " 5 1180 \n", "\n", " ratio_intermediate \n", "bin_size win_size beam_sample_quality \n", "0.25 2.0 3 0.004% \n", " 5 0.012% \n", " 1.0 3 0.355% \n", " 5 0.523% \n", " 0.5 3 0.483% \n", " 5 0.688% \n", "0.10 2.0 3 0.004% \n", " 5 0.011% \n", " 1.0 3 0.363% \n", " 5 0.525% \n", " 0.5 3 0.470% \n", " 5 0.668% \n", "0.05 2.0 3 0.004% \n", " 5 0.010% \n", " 1.0 3 0.351% \n", " 5 0.505% \n", " 0.5 3 0.436% \n", " 5 0.625% " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "return_df = pd.DataFrame.from_dict(\n", " return_metric_results,\n", " orient=\"index\",\n", " columns=[\n", " \"num_points\",\n", " \"num_first_returns\",\n", " \"num_multiple_returns\",\n", " \"ratio_multiple\",\n", " \"num_intermediate_returns\",\n", " \"ratio_intermediate\",\n", " ],\n", ")\n", "return_df.index.names = [\"bin_size\", \"win_size\", \"beam_sample_quality\"]\n", "# formatting\n", "for k in return_df.keys():\n", " if \"ratio\" in k:\n", " return_df[k] = return_df[k].map(lambda x: f\"{x:.3%}\")\n", " elif \"number\" in k:\n", " return_df[k] = return_df[k].map(lambda x: f\"{int(x):,}\")\n", "\n", "display(return_df)" ] }, { "cell_type": "markdown", "id": "8b8f157d", "metadata": {}, "source": [ "We can observe the following trends in the results:\n", "- We usually get more points and multiple returns with higher beam sample quality since sampling more subrays within the footprint increases the chances of registering multiple returns.\n", "- The number of multiple returns also increases with smaller window sizes. The window size is used in the maximum detection, so if the window size is smaller than the spacing between two returns, they can be registered as separate returns, leading to more multiple returns.\n", "\n", "By comparing these numbers with a real-world reference, we may choose the full waveform settings that are most suitable for our simulation scenario." ] } ], "metadata": { "kernelspec": { "display_name": "helios-dev-alpha", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.2" } }, "nbformat": 4, "nbformat_minor": 5 }