{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Basic M3C2 algorithm" ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "This presents how the M3C2 algorithm ([Lague et al., 2013](#References)) for point cloud distance computation can be run using the `py4dgeo` package. As a first step, we import the `py4dgeo` and `numpy` packages:" ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import py4dgeo" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "Next, we need to load two datasets that cover the same scene at two different points in time. Point cloud datasets are represented by `numpy` arrays of shape `n x 3` using a 64 bit floating point type (`np.float64`). Here, we work with a rather small synthetical data set:" ] }, { "cell_type": "code", "execution_count": null, "id": "4", "metadata": {}, "outputs": [], "source": [ "epoch1, epoch2 = py4dgeo.read_from_xyz(\n", " \"plane_horizontal_t1.xyz\", \"plane_horizontal_t2.xyz\"\n", ")" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "The analysis of point cloud distances is executed on so-called *core points* (cf. Lague et al., 2013). These could be, e.g., one of the input point clouds, a subsampled version thereof, points in an equidistant grid, etc. Here, we choose a subsampling by taking every 50th point of the reference point cloud:" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": {}, "outputs": [], "source": [ "corepoints = epoch1.cloud[::50]" ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "Next, we instantiate the algorithm class and run the distance calculation:" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": {}, "outputs": [], "source": [ "m3c2 = py4dgeo.M3C2(\n", " epochs=(epoch1, epoch2),\n", " corepoints=corepoints,\n", " cyl_radius=2.0,\n", " normal_radii=[0.5, 1.0, 2.0],\n", ")\n", "\n", "distances, uncertainties = m3c2.run()" ] }, { "cell_type": "markdown", "id": "9", "metadata": {}, "source": [ "The calculated result is an array with one distance per core point. The order of distances corresponds exactly to the order of input core points." ] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": [ "distances" ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "Corresponding to the derived distances, an uncertainty array is returned which contains several quantities that can be accessed individually: The level of detection `lodetection`, the spread of the distance across points in either cloud (`spread1` and `spread2`, by default measured as the standard deviation of distances) and the total number of points taken into consideration in either cloud (`num_samples1` and `num_samples2`):" ] }, { "cell_type": "code", "execution_count": null, "id": "12", "metadata": {}, "outputs": [], "source": [ "uncertainties[\"lodetection\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "uncertainties[\"spread1\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "14", "metadata": {}, "outputs": [], "source": [ "uncertainties[\"num_samples1\"]" ] }, { "cell_type": "markdown", "id": "15", "metadata": {}, "source": [ "The direction of surface change in the M3C2 algorithm is determined via local normal vectors per core point. The normal vectors used in the calculation can be accessed via the `directions()` method of the M3C2 algorithm in `py4dgeo`, which returns an array (Nx3) of length N corresponding to the number of core points with three entries for the normal vector components in x, y, and z direction. " ] }, { "cell_type": "code", "execution_count": null, "id": "16", "metadata": {}, "outputs": [], "source": [ "m3c2.directions()" ] }, { "cell_type": "markdown", "id": "17", "metadata": {}, "source": [ "The property `directions_radii` returns an array (Nx1) of length N corresponding to the number of core points and one entry for the radius used for normal computation at the respective core point. This is relevant for the multi-scale functionality of the M3C2, i.e. the possibility to specify multiple normal radii of which the one with maximized planarity is used for change analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "18", "metadata": {}, "outputs": [], "source": [ "m3c2.directions_radii()" ] }, { "cell_type": "markdown", "id": "19", "metadata": {}, "source": [ "### References\n", "\n", "* Lague, D., Brodu, N., & Leroux, J. (2013). Accurate 3D comparison of complex topography with terrestrial laser scanner: Application to the Rangitikei canyon (N-Z). ISPRS Journal of Photogrammetry and Remote Sensing, 82, pp. 10-26. doi: [10.1016/j.isprsjprs.2013.04.009](https://doi.org/10.1016/j.isprsjprs.2013.04.009)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }