├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── doc
├── images
│ ├── findstems.png
│ ├── findstems_regions.png
│ ├── getcrownvolume.png
│ ├── getdtmslice.png
│ ├── segmentcrown.png
│ ├── segmentstem.png
│ ├── segmentstem_regions.png
│ └── treeseg_cover.png
├── tutorial_downsample.md
├── tutorial_findstems.md
├── tutorial_getcrownvolume.md
├── tutorial_getdtmslice.md
├── tutorial_overview.md
├── tutorial_preprocessing.md
├── tutorial_segmentcrown.md
├── tutorial_segmentstem.md
└── tutorial_tldr.md
├── include
├── leafsep.h
├── riegl
│ └── .keep_include
├── treeseg.h
└── treeseg_pointtype.h
├── lib
└── riegl
│ └── .keep_lib
└── src
├── downsample.cpp
├── findstems.cpp
├── getcrownvolume.cpp
├── getdtmslice.cpp
├── leafsep.cpp
├── nearestneighbour.cpp
├── pcdPointTreeseg2txt.cpp
├── pcdPointXYZRGB2txt.cpp
├── plotcoords.cpp
├── rxp2pcd.cpp
├── rxp2tree.cpp
├── segmentcrown.cpp
├── segmentstem.cpp
├── sepwoodleaf.cpp
├── thin.cpp
├── treeparams.cpp
├── treeseg.cpp
└── txtPointTreeseg2pcd.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
34 | #Directories
35 | /build/
36 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 | project(treeseg)
3 |
4 | find_package(PCL 1.10 REQUIRED)
5 | find_package(Armadillo 9.8 REQUIRED)
6 |
7 | include_directories(${PCL_INCLUDE_DIRS} ${ARMADILLO_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include/riegl)
8 | link_directories(${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib/riegl)
9 |
10 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
11 | if(EXISTS "${CMAKE_SOURCE_DIR}/lib/riegl/libscanifc-mt.so")
12 | add_executable(rxp2pcd ${CMAKE_SOURCE_DIR}/src/rxp2pcd.cpp)
13 | target_link_libraries(rxp2pcd treeseg scanifc-mt)
14 | endif()
15 | else()
16 | message(WARNING "rxp executables not building because either RIEGL libs/headers not found, or not a compatible OS.")
17 | endif()
18 |
19 | add_library(treeseg SHARED ${CMAKE_SOURCE_DIR}/src/treeseg.cpp)
20 | target_link_libraries(treeseg ${PCL_LIBRARIES})
21 |
22 | add_library(leafsep SHARED ${CMAKE_SOURCE_DIR}/src/leafsep.cpp)
23 | target_link_libraries(leafsep treeseg ${PCL_LIBRARIES} ${ARMADILLO_LIBRARIES})
24 |
25 | add_executable(plotcoords ${CMAKE_SOURCE_DIR}/src/plotcoords.cpp)
26 | target_link_libraries(plotcoords)
27 |
28 | add_executable(nearestneighbour ${CMAKE_SOURCE_DIR}/src/nearestneighbour.cpp)
29 | target_link_libraries(nearestneighbour treeseg ${PCL_LIBRARIES})
30 |
31 | add_executable(downsample ${CMAKE_SOURCE_DIR}/src/downsample.cpp)
32 | target_link_libraries(downsample treeseg ${PCL_LIBRARIES})
33 |
34 | add_executable(thin ${CMAKE_SOURCE_DIR}/src/thin.cpp)
35 | target_link_libraries(thin treeseg ${PCL_LIBRARIES})
36 |
37 | add_executable(getdtmslice ${CMAKE_SOURCE_DIR}/src/getdtmslice.cpp)
38 | target_link_libraries(getdtmslice treeseg ${PCL_LIBRARIES})
39 |
40 | add_executable(findstems ${CMAKE_SOURCE_DIR}/src/findstems.cpp)
41 | target_link_libraries(findstems treeseg ${PCL_LIBRARIES})
42 |
43 | add_executable(segmentstem ${CMAKE_SOURCE_DIR}/src/segmentstem.cpp)
44 | target_link_libraries(segmentstem treeseg ${PCL_LIBRARIES})
45 |
46 | add_executable(getcrownvolume ${CMAKE_SOURCE_DIR}/src/getcrownvolume.cpp)
47 | target_link_libraries(getcrownvolume treeseg ${PCL_LIBRARIES})
48 |
49 | add_executable(segmentcrown ${CMAKE_SOURCE_DIR}/src/segmentcrown.cpp)
50 | target_link_libraries(segmentcrown treeseg ${PCL_LIBRARIES})
51 |
52 | add_executable(sepwoodleaf ${CMAKE_SOURCE_DIR}/src/sepwoodleaf.cpp)
53 | target_link_libraries(sepwoodleaf treeseg leafsep ${PCL_LIBRARIES})
54 |
55 | add_executable(pcdPointTreeseg2txt ${CMAKE_SOURCE_DIR}/src/pcdPointTreeseg2txt.cpp)
56 | target_link_libraries(pcdPointTreeseg2txt treeseg ${PCL_LIBRARIES})
57 |
58 | add_executable(txtPointTreeseg2pcd ${CMAKE_SOURCE_DIR}/src/txtPointTreeseg2pcd.cpp)
59 | target_link_libraries(txtPointTreeseg2pcd treeseg ${PCL_LIBRARIES})
60 |
61 | add_executable(pcdPointXYZRGB2txt ${CMAKE_SOURCE_DIR}/src/pcdPointXYZRGB2txt.cpp)
62 | target_link_libraries(pcdPointXYZRGB2txt ${PCL_LIBRARIES})
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Andrew Burt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # treeseg
2 |
3 | Extract individual trees from lidar point clouds
4 |
5 |
6 |
7 | ## Table of contents
8 |
9 | - [Overview](#overview)
10 | - [Installation](#installation)
11 | - [Usage](#usage)
12 | - [Acknowledgements](#acknowledgements)
13 | - [Authors](#authors)
14 | - [Citing](#citing)
15 | - [License](#license)
16 |
17 | ## Overview
18 |
19 | treeseg has been developed to near-automatically segment individual tree point clouds from high-density larger-area lidar point clouds acquired in forests. A formal, albeit somewhat outdated description of the methods can be found in our [paper](https://doi.org/10.1111/2041-210X.13121).
20 |
21 | ## Installation
22 |
23 | treeseg has been developed and tested on Ubuntu 20.04 LTS only, and is dependent on the following packages:
24 |
25 | * Point Cloud Library (v1.10)
26 | * Armadillo (v9.8)
27 |
28 | These dependencies are installed via apt:
29 |
30 | ```
31 | apt install libpcl-dev libarmadillo-dev
32 | ```
33 |
34 | treeseg can then be installed using:
35 |
36 | ```
37 | git clone https://github.com/apburt/treeseg.git;
38 | mkdir ./treeseg/build; cd ./treeseg/build; cmake ..; make;
39 | ```
40 |
41 | Optionally, for users with RIEGL V-Line scan data, treeseg includes the executable `rxp2pcd`, to convert and preprocess data in RXP data stream format, to binary PCD format. This executable will automatically be built if the directories `./treeseg/include/reigl/` and `./treeseg/lib/riegl/` are populated with the RIEGL RiVLIB headers and libraries (as appropriate for the user's particular CPU architecture and gcc version), which can be downloaded from the Members Area of the RIEGL website (e.g., rivlib-2_5_10-x86_64-linux-gcc9.zip).
42 |
43 | Finally, the environment variable `PATH` can then be updated to include the directory containing the built treeseg executables, either temporarily, by calling the following, or permanently, by inserting it at the top of `~/.bashrc`:
44 |
45 | ```
46 | export PATH="$PATH:/path/to/treeseg/build"
47 | ```
48 |
49 | ## Usage
50 |
51 | A tutorial demonstrating the usage of treeseg is available [here](/doc/tutorial_overview.md).
52 |
53 | ## Acknowledgements
54 |
55 | treeseg makes extensive use of the Point Cloud Library ([PCL](http://pointclouds.org)).
56 |
57 | ## Authors
58 |
59 | * Andrew Burt
60 | * Mathias Disney
61 | * Kim Calders
62 | * Matheus Boni Vicari
63 | * Tony Peter
64 |
65 | ## Citing
66 |
67 | treeseg can be cited as:
68 |
69 | Burt, A., Disney, M., Calders, K. (2019). Extracting individual trees from lidar point clouds using *treeseg*. *Methods Ecol Evol* 10(3), 438–445. doi: 10.1111/2041-210X.13121
70 |
71 | A doi for the latest version is available in [releases](https://github.com/apburt/treeseg/releases).
72 |
73 | ## License
74 |
75 | This project is licensed under the terms of the MIT license - see the [LICENSE](LICENSE) file for details.
76 |
--------------------------------------------------------------------------------
/doc/images/findstems.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/findstems.png
--------------------------------------------------------------------------------
/doc/images/findstems_regions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/findstems_regions.png
--------------------------------------------------------------------------------
/doc/images/getcrownvolume.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/getcrownvolume.png
--------------------------------------------------------------------------------
/doc/images/getdtmslice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/getdtmslice.png
--------------------------------------------------------------------------------
/doc/images/segmentcrown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentcrown.png
--------------------------------------------------------------------------------
/doc/images/segmentstem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentstem.png
--------------------------------------------------------------------------------
/doc/images/segmentstem_regions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentstem_regions.png
--------------------------------------------------------------------------------
/doc/images/treeseg_cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/treeseg_cover.png
--------------------------------------------------------------------------------
/doc/tutorial_downsample.md:
--------------------------------------------------------------------------------
1 | # Downsampling
2 |
3 | Downsampling the data is an optional step, but provides two potential benefits: i) some of the point cloud processing methods (e.g., Euclidean clustering) will provide more robust results, and ii) reduces computation. However, determining the appropriate level of downsampling is a complicated subject, requiring careful consideration (e.g., the downstream implications of downsampling, such as the impacts on above-ground biomass estimation), which is briefly discussed in the following subsection.
4 |
5 | treeseg implements two methods for downsampling, both employing octrees. The first, and the one used in this tutorial, `downsample`, replaces the points inside each occupied voxel (leaf-node), with a single aggregate point:
6 |
7 | ```
8 | downsample 0.04 NOU11.tile.*.pcd
9 | ```
10 |
11 | Where 0.04 is the edge length of each voxel, in meters. This will output a downsampled point cloud for each input point cloud, e.g., `NOU11.tile.downsample.342.pcd`, whose point count has reduced by approximately 92% when compared to its original counterpart.
12 |
13 | The alternative approach, `thin`, preserves the original data, rather than replacing it, by keeping the original point in each voxel that is closest to the would-be aggregate point. This might be useful when [additional point fields](tutorial_preprocessing.md#Parsing-additional-point-fields) are parsed (e.g., reflectance), and their aggregation would not make sense. `thin` can be similarly called as:
14 |
15 | ```
16 | thin 0.04 NOU11.tile.*.pcd
17 | ```
18 |
19 | Note: if the preprocessor macro [XYZRRDRS](../include/treeseg_pointtype.h#L29) is defined as true, the behaviour of `thin` is modified. Instead, the point with the smallest deviation value (i.e., a proxy for range estimate goodness) will be preserved (if a voxel contains multiple points with the same deviation, the point nearest to the would-be aggregate is selected). This behaviour can be changed in [treeseg.cpp](../src/treeseg.cpp#L343).
20 |
21 | ## Downsampling resolution
22 |
23 | A potential place to start when determining the downsampling resolution, is to consider the characteristics of the lidar instrument (i.e., beam divergence, diameter of the beam at emission, and ranging accuracy), and to compare them with the observed distances between individual points in the point clouds collected by that instrument. For the data considered in this tutorial, this exercise was undertaken in figure 2 of our [paper](https://doi.org/10.1111/2041-210X.13121), where it can be seen that a voxel edge length of 0.04 m provided some balance between removing redundant points close to the ground (e.g., returns from the stems whose beam overlapped), whilst conserving important data from the canopy.
24 |
25 | treeseg includes the executable `nearestneighbour`, which may help in making this decision, by outputting the mean vertically-resolved nearest neighbour distances through a point cloud (i.e., the cloud is partitioned into height bins, and the mean distance of each point to its N nearest neighbours is calculated and averaged for each bin):
26 |
27 | ```
28 | nearestneighbour 2 6 NOU11.tile.342.pcd
29 | ```
30 |
31 | Where 2 is the width of the bins, in meters, and 6 is the number of nearest neighbours to be considered.
32 |
33 | ## [Next: Digital terrain model](tutorial_getdtmslice.md)
34 |
--------------------------------------------------------------------------------
/doc/tutorial_findstems.md:
--------------------------------------------------------------------------------
1 | # Identifying the stems
2 |
3 | Individual tree stems are then identified in this slice across the larger-area point cloud through four steps:
4 |
5 | 1. Euclidean clustering: the slice is organised into individual clusters, based on the spatial distances between points.
6 | 2. Region-based segmentation: these clusters are further reduced to regions, based on the properties of their underlying surfaces (themselves inferred from point normals).
7 | 3. Shape fitting: RANSAC methods are used to fit cylinders to each region, and fit diagnostics are used to determine the likelihood of the underlying surface being a stem.
8 | 4. Principal component analysis: the angle between the principal components of each region and the ground are calculated, with regions broadly perpendicular to the ground deemed to be stems.
9 |
10 | These steps are implemented in the `findstems` executable, which can be called:
11 |
12 | ```
13 | cd ./clusters/;
14 | findstems 4 0.2 2 ../NOU11.coords.dat ../NOU11.slice.pcd;
15 | ```
16 |
17 | Where the inputs are: i) smoothness (degrees, see the following subsection), ii) minimum stem diameter (m), iii) maximum stem diameter (m), iv) the coordinates of the plot boundaries, and v) the slice of point cloud segmented from the larger-area point cloud along the xy-plane. That is, regions passing steps 1-4 are considered stems if their fitted cylinders have a diameter between or equal to the minimum and maximum diameter, and their centre resides inside or on the bounding box specified by the plot boundaries.
18 |
19 | The output point clouds, `NOU11.cluster.0.pcd` to `NOU11.cluster.189.pcd`, contain the inliers of each cylinder fit (ordered: descending by diameter), and can be viewed using:
20 |
21 | ```
22 | pcl_viewer NOU11.cluster.*.pcd
23 | ```
24 |
25 | Which will look something similar to:
26 |
27 |
28 |
29 | Also output are intermediate files useful for determining the source of any errors. For example, the following visualises the slice after applying Euclidean clustering and region-based segmentation:
30 |
31 | ```
32 | pcl_viewer NOU11.intermediate.slice.clusters.regions.pcd
33 | ```
34 |
35 |
36 |
37 | ## Parameters
38 |
39 | The input parameter smoothness is not particularly interpretable: it is used in the region-based segmentation to define the maximum permitted angle between point normals, for points to be considered belonging to the same region or underlying surface. However, it is important to select an appropriate value of smoothness, which unfortunately, varies depending on factors including the lidar instrument, the sampling pattern, and the scene itself. Therefore, some experimentation is required: the idea is that each resulting region wholly represents an individual stem (as shown in the figure above). If the value is too large, stems and surrounding vegetation will be lumped into the same region, and if too small, each stem will comprise of multiple regions.
40 |
41 | In addition to smoothness, other important parameters have been hard-coded into `findstems`, which may require further experimentation when applying these methods to other lidar datasets:
42 |
43 | 1. Euclidean clustering: [nnearest](../src/findstems.cpp#L22) and [nmin](../src/findstems.cpp#L23).
44 | 2. Region-based segmentation: [nnearest](../src/findstems.cpp#L38), [nneighbours](../src/findstems.cpp#L39), [nmin](../src/findstems.cpp#L39), [nmax](../src/findstems.cpp#L39) and [curvature](../src/findstems.cpp#L39).
45 | 3. Shape fitting: [nnearest](../src/findstems.cpp#L50), [lmin](../src/findstems.cpp#L70), [stepcovmax](../src/findstems.cpp#L71) and [radratiomin](../src/findstems.cpp#L72).
46 | 4. Principal component analysis: [anglemax](../src/findstems.cpp#L104).
47 |
48 | ## Errors of omission and commission
49 |
50 | Scenarios will arise where `findstems` either incorrectly identifies a region as a stem, or not a stem. So it can be useful to undertake a manual check, by comparing the original slice with the extracted inliers:
51 |
52 | ```
53 | pcdPointTreeseg2txt NOU11.intermediate.slice.clusters.regions.pcd NOU11.cluster.*.pcd;
54 | CloudCompare NOU11.intermediate.slice.clusters.regions.txt NOU11.cluster.*.txt;
55 | ```
56 |
57 | Whereby the slice can be coloured different to the inliers for ready comparison. Errors of commission (i.e., a region incorrectly identified as a stem) can be deleted (e.g., here, by removing the file `NOU11.cluster.33.pcd`, which is the stem of a palm). Errors of omission (i.e., a region incorrectly identified as not being a stem) can be corrected by manually segmenting missed stems from this slice, and saving them as additional point clouds (e.g., using the naming convention: `NOU11.cluster.190.txt` onwards). Note: it is then necessary convert these newly created ASCII point clouds to PCD format, which can be undertaken using the `txtPointTreeseg2pcd` executable.
58 |
59 | ## [Next: Stem segmentation](tutorial_segmentstem.md)
60 |
--------------------------------------------------------------------------------
/doc/tutorial_getcrownvolume.md:
--------------------------------------------------------------------------------
1 | # Crown segmentation - Part 1
2 |
3 | At this point, the stems of the 10 largest trees inside the 1 ha stand have been successfully segmented. It is now necessary to extract their crowns from the neighbouring vegetation. The first part of this two step process is to generate a point cloud for each tree, containing: i) the stem, ii) the crown, and iii) neighbouring crown vegetation. This is undertaken using the following methods:
4 |
5 | 1. Shape fitting: RANSAC methods are used to fit a cylinder to each of the stem point clouds, to estimate the stem diameter of each tree.
6 | 2. Spatial filtering: pass-through filters (closed cylinder) are applied to the larger-area point cloud, as driven by stem-diameter:tree-height and stem-diameter:crown-diameter allometries, to generate point clouds containing the crown of each tree.
7 | 3. Duplicate point removal: the stem and crown point clouds are concatenated, and duplicate points removed.
8 |
9 | These steps are implemented in the `getcrownvolume` executable, which can be called:
10 |
11 | ```
12 | cd ../volumes/;
13 | getcrownvolume 0.66 ../stems/NOU11.stem.*.pcd ../NOU11.tile.downsample.*.pcd;
14 | ```
15 |
16 | Where the inputs are: i) the height along each stem defining the bottom of the pass-through cylinder, ii) the stem point clouds output from `segmentstem`, and iii) the downsampled tiles constituting the larger-area point cloud. The output point clouds can be viewed using:
17 |
18 | ```
19 | pcl_viewer NOU11.volume.0.pcd
20 | ```
21 |
22 | Which will look something similar to:
23 |
24 |
25 |
26 | ## Allometrics
27 |
28 | The purpose of the [stem-diameter:tree-height](../src/getcrownvolume.cpp#L7) and [stem-diameter:crown-diameter](../src/getcrownvolume.cpp#L15) allometries, is to segment an appropriately sized section of the larger-area point cloud containing the entirety of the crown of the tree of interest. These allometries were constructed using terrestrial lidar data collected across the tropics via a non-linear power-law model. The parameters used in these models are the upper 95% confidence intervals, and also include a further relaxation term. When applied to other lidar datasets, it may be necessary to relax these allometries further, to avoid accidental cropping of crowns.
29 |
30 | ## [Next: Crown segmentation - Part 2](tutorial_segmentcrown.md)
31 |
--------------------------------------------------------------------------------
/doc/tutorial_getdtmslice.md:
--------------------------------------------------------------------------------
1 | # Digital terrain model
2 |
3 | The first principal step of treeseg is to identify the individual trees in the larger-area point cloud. This identification is undertaken on a slice segmented from the larger-area point cloud along the xy-plane. To generate this slice at some defined height above-ground whilst adjusting for topography, it is necessary to construct an underlying digital terrain model (DTM). Both this DTM, and the slice can be generated using:
4 |
5 | ```
6 | getdtmslice 2 2.5 3 6 NOU11.tile.downsample.*.pcd > NOU11.dtm.dat
7 | ```
8 |
9 | Where the inputs are: 1) the resolution of the DTM (m), 2) the percentile of the z-coordinates of the points inside each DTM grid which is considered to constitute the ground (this is used, rather than the absolute minimum, to provide more robust results in noisy point clouds), 3) the minimum height of the slice above-ground (m), 4) the maximum height of the slice above-ground (m), and 5) the downsampled tiles constituting the larger-area point cloud.
10 |
11 | The resulting slice can be viewed using:
12 |
13 | ```
14 | pcl_viewer NOU11.slice.pcd
15 | ```
16 |
17 | Which will look something similar to:
18 |
19 |
20 |
21 | ## [Next: Identifying the stems](tutorial_findstems.md)
22 |
--------------------------------------------------------------------------------
/doc/tutorial_overview.md:
--------------------------------------------------------------------------------
1 | # Tutorial
2 |
3 | The objective of this tutorial is to demonstrate the usage of treeseg, by extracting the 10 largest trees inside a complex forest scene.
4 |
5 | ## TL;DR
6 |
7 | See [here](tutorial_tldr.md).
8 |
9 | ## Primer
10 |
11 | If you plan to extensively use or develop treeseg, it might first be useful to familiarise yourself with the various point cloud processing methods that are used. A brief overview of these methods can be found in our [paper](https://doi.org/10.1111/2041-210X.13121).
12 |
13 | treeseg uses the Point Cloud Libray (PCL) to implement these methods, and the [PCL website](https://pointclouds.org/) provides good generic tutorials. The most relevant are: Basic Usage, Features, Filtering, KdTree, Octree, Sample Consensus and Segmentation.
14 |
15 | ## Prerequisites
16 |
17 | It is assumed treeseg has been successfully built. Several other packages are also required, which can be installed via apt:
18 |
19 | ```
20 | apt install pcl-tools cloudcompare
21 | ```
22 |
23 | ## Data
24 |
25 | The terrestrial lidar data that will be downloaded and used in this tutorial were collected from a one hectare stand of old-growth intact tropical rainforest in French Guiana. A more complete description of these data is available via [](https://doi.org/10.5281/zenodo.4497751)
26 |
27 | ### Directory structure
28 |
29 | It is assumed the following folders have been created:
30 |
31 | ```
32 | ./treeseg_tutorial/
33 | ├───data/
34 | └───processing/
35 | ├───clusters/
36 | ├───stems/
37 | ├───volumes/
38 | ├───trees/
39 | ```
40 |
41 | ### Option 1 (recommended): Raw lidar data
42 |
43 | If the `rxp2pcd` executable has been compiled (see [here](../README.md#installation)), then the raw lidar data should be downloaded:
44 |
45 | ```
46 | cd ./treeseg_tutorial/data/;
47 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part1.zip;
48 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part2.zip;
49 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part3.zip;
50 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part4.zip;
51 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part5.zip;
52 | unzip '2015-11-10.001.riproject.part?.zip';
53 | ```
54 |
55 | ### Option 2: Preprocessed lidar data
56 |
57 | If the `rxp2pcd` executable could not been compiled (e.g., it was not possible to access and download the RIEGL RiVLIB headers and libraries), then the preprocessed data should be downloaded. It is also necessary to create a file containing the coordinates of plot boundaries (this is discussed further in the following section):
58 |
59 | ```
60 | cd ./treeseg_tutorial/processing/;
61 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part1.zip;
62 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part2.zip;
63 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part3.zip;
64 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part4.zip;
65 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part5.zip;
66 | unzip '2015-11-10.001.preprocessed.part?.zip';
67 | echo -101.261 4.48606 -104.789 0 > NOU11.coords.dat;
68 | ```
69 |
70 | ## [Next: Preprocessing](tutorial_preprocessing.md)
71 |
--------------------------------------------------------------------------------
/doc/tutorial_preprocessing.md:
--------------------------------------------------------------------------------
1 | # Preprocessing
2 |
3 | ## The larger-area point cloud
4 |
5 | The primary input to treeseg is some larger-area point cloud collected in a forest scene (e.g., in this tutorial, the point cloud representing the 1 ha tropical rainforest plot in French Guiana). It is expected that this point cloud is stored in binary or ASCII [PCD format](https://pcl.readthedocs.io/projects/tutorials/en/latest/pcd_file_format.html). By default, the expected point fields are the Cartesian coordinates (x, y, z) of each point, but additional fields can be included (as described in the following subsection). It is also expected that the larger-area point cloud is broken into N tiles (Nmin = 1), using the following strict naming convention:
6 |
7 | ```
8 | mycloud.tile.0.pcd
9 | mycloud.tile.1.pcd
10 | ...
11 | mycloud.tile.N.pcd
12 | ```
13 |
14 | Where 'mycloud' can be replaced with some meaningful identifier of the larger-area point cloud. Depending on computing environment and resources, it will often be necessary for each tile to have a maximum size of a few gigabytes, to circumvent issues that potentially arise from requiring large contiguous blocks of unoccupied RAM to be available when loading large point clouds.
15 |
16 | ## Parsing additional point fields
17 |
18 | treeseg implements a custom point type, `PointTreeseg`, to provide the user with flexibility over the point fields. By default PointTreeseg = [pcl::PointXYZ](https://pointclouds.org/documentation/structpcl_1_1_point_x_y_z.html). However, the definition of [PointTreeseg](../include/treeseg_pointtype.h#L33) can be modified if the user wishes to include other fields (e.g., reflectance).
19 |
20 | An example of how this can be implemented is provided by the preprocessor macro [XYZRRDRS](../include/treeseg_pointtype.h#L29). If this macro is defined as true (default = false) and treeseg recompiled, then the `PointTreeseg` point fields become: x (m), y (m), z (m), range (m), reflectance (dB), deviation (#), return number (#) and scan number (#). These additional fields will then be preserved throughout the various subsequent processing steps.
21 |
22 | ## RXP to PCD conversion
23 |
24 | In this tutorial we only consider preprocessing lidar data stored in RIEGL's RXP data stream format, to the abovedescribed expected tiled PCD format. However, various open-source tools are available for converting lidar data in other formats (e.g., LAS).
25 |
26 | The raw lidar data downloaded during the previous step (i.e., [option 1](tutorial_overview.md#Data)) are preprocessed using the `rxp2pcd` executable. The overall x-y limits of the larger-area point cloud are defined by the coordinates of a bounding box, which here, we want dictated by the plot boundaries, which can be approximated from the scan locations via the transformation matrices in `./treeseg_tutorial/data/matrix/` using `plotcoords`:
27 |
28 | ```
29 | cd ../processing/;
30 | plotcoords ../data/matrix/ > NOU11.coords.dat;
31 | ```
32 |
33 | Whereby the resulting ASCII file `NOU11.coords.dat` contains:
34 |
35 | ```
36 | -101.261 4.48606 -104.789 0
37 | ```
38 |
39 | I.e., the minimum and maximum coordinates of the 121 scan locations (xmin, xmax, ymin, ymax), in meters.
40 |
41 | `rxp2pcd` can then be called as:
42 |
43 | ```
44 | rxp2pcd ../data/ NOU11.coords.dat 25 15 NOU11
45 | ```
46 |
47 | Where the inputs are: 1) the top directory containing the lidar data; 2) the coordinates of the plot boundaries (note: the dimensions of the bounding box are increased by 20 m in each direction to ensure data relating to the trees of interest are not truncated (e.g., the crowns of large trees at the edges of the plot); 3) the area of each output tile (m2); 4) the maximum permitted value of point deviation (a proxy for range estimate goodness that is used to remove noisy returns); and 5) the larger-area point cloud identifier.
48 |
49 | This command will output the larger-area point cloud into 870 binary PCD tiles: `NOU11.tile.0.pcd` to `NOU11.tile.869.pcd`. These tiles can then be viewed, e.g., using `pcl_viewer`:
50 |
51 | ```
52 | pcl_viewer NOU11.tile.342.pcd
53 | ```
54 |
55 | ## [Next: Downsampling](tutorial_downsample.md)
56 |
--------------------------------------------------------------------------------
/doc/tutorial_segmentcrown.md:
--------------------------------------------------------------------------------
1 | # Crown segmentation - Part 2
2 |
3 | The second step in the process of extracting the crowns uses the following methods:
4 |
5 | 1. Region-based segmentation: the point clouds output from `getcrownvolume` are organised into regions, based on the properties of their underlying surfaces.
6 | 2. Connectivity testing: the tree is built from the stem up, by adding nearby regions considered belonging to the tree.
7 |
8 | These methods are implemented in the `segmentcrown` executable, which can be called:
9 |
10 | ```
11 | cd ../trees/;
12 | segmentcrown 4 ../volumes/NOU11.volume.*.pcd;
13 | ```
14 |
15 | Where the input parameters are: i) smoothness (degrees), and ii) the point clouds output from `getcrownvolume`. Depending on point cloud size and computing resources, this currently single-threaded process can take between 12-24 hours to complete. It might therefore be worthwhile running multiple instances in parallel.
16 |
17 | The output tree-level point clouds can then be viewed using:
18 |
19 | ```
20 | pcl_viewer NOU11_1.pcd
21 | ```
22 |
23 | Which will look something similar to:
24 |
25 |
26 |
27 | # The buildtree function
28 |
29 | The connectivity testing is implemented in the [buildtree](../src/treeseg.cpp#L963) function, and called by `segmentcrown` as:
30 |
31 | ```
32 | buildTree(regions,20,1,0.2,5,1.0,tree);
33 | ```
34 |
35 | Where the input parameters are: i) the vector of point clouds derived from the region-based segmentation, ii) cyclecount (integer), iii) firstcount (integer), iv) firstdist (m), v) nnearest (integer), vi) seconddist (m), and vii) the resulting tree-level point cloud. That is, first, the region representing the stem is designated the seed. For `firstcount` iteration(s), regions within the distance `firstdist` to the seed(s) are added to the tree and designated the new seeds. For the remaining `cyclecount` iterations, the `nnearest` regions closest to each seed and within the distance `seconddist` are added to the tree and designated the new seeds. These parameters may require further experimentation when applied to other lidar datasets. For example, for very high-quality lidar data, or for more sparsely populated forest scenes, it might be possible to significantly reduce `cyclecount`. This will substantially reduce compute time.
36 |
37 | In addition to this function, other important parameters have been hard-coded into `segmentcrown`:
38 |
39 | 1. Region-based segmentation: [nnearest](../src/segmentcrown.cpp#L24), [nneighbours](../src/segmentcrown.cpp#L25), [nmin](../src/segmentcrown.cpp#L25), [nmax](../src/segmentcrown.cpp#L25) and [curvature](../src/segmentcrown.cpp#L25).
40 |
41 | # Errors of commission
42 |
43 | Errors of commission in the final tree-level point clouds can then be manually segmented in CloudCompare, e.g.,:
44 |
45 | ```
46 | pcdPointTreeseg2txt NOU11_1.pcd;
47 | CloudCompare NOU11_1.txt;
48 | ```
49 |
--------------------------------------------------------------------------------
/doc/tutorial_segmentstem.md:
--------------------------------------------------------------------------------
1 | # Stem segmentation
2 |
3 | Next, the full stem of each identified tree is segmented from the larger-area point cloud as follows:
4 |
5 | 1. Shape fitting: RANSAC methods are used to fit a cylinder to each set of inliers output from `findstems`, to describe the orientation and radius of each stem.
6 | 2. Spatial filtering: pass-through filters (infinite cylinder) are applied to the larger-area point cloud, as driven by expanded versions of these cylinders, to generate sections of point cloud containing each stem and neighbouring vegetation.
7 | 3. Plane fitting: points are classified as ground returns and segmented, if they are inliers of the plane fitted to the bottom of each of these sections via [SACMODEL_NORMAL_PARALLEL_PLANE](https://pointclouds.org/documentation/classpcl_1_1_sample_consensus_model_normal_parallel_plane.html) (n.b., this model imposes conditions on both the inliers, and the plane itself).
8 | 4. Region-based segmentation: the remaining sections are organised into regions, based on the properties of their underlying surfaces, in order to classify each element of vegetation.
9 |
10 | These steps are implemented in the `segmentstem` executable, which can be called:
11 |
12 | ```
13 | cd ../stems/;
14 | segmentstem 4 ../clusters/NOU11.cluster.?.pcd ../NOU11.tile.downsample.*.pcd;
15 | ```
16 |
17 | Where the inputs are: i) smoothness (degrees), ii) the inliers of the 10 largest stems output from `findstems`, and iii) the downsampled tiles constituting the larger-area point cloud. The output stem point clouds can be viewed using:
18 |
19 | ```
20 | pcl_viewer NOU11.stem.?.pcd
21 | ```
22 |
23 | Which will look something similar to:
24 |
25 |
26 |
27 | Also output are intermediate diagnostic files. For example, the following visualises the section of point cloud generated for `NOU11.cluster.1.pcd`, after the spatial filtering, plane fitting and region-based segmentation have been applied:
28 |
29 | ```
30 | pcl_viewer NOU11.c.ng.r.1.pcd
31 | ```
32 |
33 |
34 |
35 | ## Parameters
36 |
37 | In addition to smoothness (described [here](tutorial_findstems.md#Parameters)), other important parameters have been hard-coded into `segmentstem`, which may require further experimentation when applying these methods to other lidar datasets:
38 |
39 | 1. Shape fitting: [nnearest](../src/segmentstem.cpp#L27).
40 | 2. Spatial filtering: [expansionfactor](../src/segmentstem.cpp#L35).
41 | 3. Plane fitting: [groundidx](../src/segmentstem.cpp#L50), [ground](../src/segmentstem.cpp#L54), [nnearest](../src/segmentstem.cpp#L56), [dthreshold](../src/segmentstem.cpp#L57), [nweight](../src/segmentstem.cpp#L57), [angle](../src/segmentstem.cpp#L57).
42 | 4. Region-based segmentation: [nnearest](../src/segmentstem.cpp#L68), [nneighbours](../src/segmentstem.cpp#L70), [nmin](../src/segmentstem.cpp#L70), [nmax](../src/segmentstem.cpp#L70) and [curvature](../src/segmentstem.cpp#L70).
43 |
44 | ## Errors of commission
45 |
46 | Scenarios will arise where the outputs from `segmentstem` erroneously include neighbouring vegetation (e.g., `NOU.stem.0.pcd`). A potential approach to correcting this is to lower the value of smoothness (e.g., 3.5). However this can lead to errors of omission, which are more difficult to correct. At this point is is unnecessary to manually correct errors of commission, as this will be undertaken once the crown of each tree has been segmented in the final step.
47 |
48 | ## [Next: Crown segmentation - Part 1](tutorial_getcrownvolume.md)
49 |
--------------------------------------------------------------------------------
/doc/tutorial_tldr.md:
--------------------------------------------------------------------------------
1 | # TL;DR
2 |
3 | ```
4 | mkdir treeseg_tutorial; cd treeseg_tutorial/; mkdir data processing; cd data/;
5 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part1.zip;
6 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part2.zip;
7 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part3.zip;
8 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part4.zip;
9 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part5.zip;
10 | unzip '2015-11-10.001.riproject.part?.zip';
11 | ```
12 |
13 | ```
14 | cd ../processing/;
15 | mkdir clusters stems volumes trees;
16 | plotcoords ../data/matrix/ > NOU11.coords.dat;
17 | rxp2pcd ../data/ NOU11.coords.dat 25 15 NOU11;
18 | downsample 0.04 NOU11.tile.*.pcd;
19 | getdtmslice 2 2.5 3 6 NOU11.tile.downsample.*.pcd > NOU11.dtm.dat;
20 | ```
21 |
22 | ```
23 | cd ./clusters/;
24 | findstems 4 0.2 2 ../NOU11.coords.dat ../NOU11.slice.pcd;
25 | cd ../stems/;
26 | segmentstem 4 ../clusters/NOU11.cluster.?.pcd ../NOU11.tile.downsample.*.pcd;
27 | cd ../volumes/;
28 | getcrownvolume 0.66 ../stems/NOU11.stem.*.pcd ../NOU11.tile.downsample.*.pcd;
29 | cd ../trees/;
30 | segmentcrown 4 ../volumes/NOU11.volume.*.pcd;
31 | ```
32 |
--------------------------------------------------------------------------------
/include/leafsep.h:
--------------------------------------------------------------------------------
1 | /*
2 | * leafsep.h
3 | *
4 | * MIT License
5 | *
6 | * Copyright 2018 Andrew Burt and Matheus Boni Vicari
7 | *
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy
9 | * of this software and associated documentation files (the "Software"), to
10 | * deal in the Software without restriction, including without limitation the
11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 | * sell copies of the Software, and to permit persons to whom the Software is
13 | * furnished to do so, subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be included in
16 | * all copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 | * IN THE SOFTWARE.
25 | */
26 |
27 | // leafsep has been developed using:
28 | // TLSeparation (https://github.com/TLSeparation)
29 | // Point Cloud Library (http://www.pointclouds.org)
30 | // Armadillo (http://arma.sourceforge.net)
31 |
32 | #include "treeseg.h"
33 |
34 | #include
35 |
36 | void gmmByPoint(const pcl::PointCloud::Ptr &cloud, int knn, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_diag &model);
37 | void gmmByCluster(const std::vector::Ptr> &clouds, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_full &model);
38 | std::vector classifyGmmPointModel(const pcl::PointCloud::Ptr &cloud, int n_gaus, arma::mat &featmatrix, arma::gmm_diag &model);
39 | std::vector classifyGmmClusterModel(const std::vector::Ptr> &clouds, int n_gaus, arma::mat &featmatrix, arma::gmm_full &model);
40 |
41 | void separateCloudsClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::vector::Ptr> &classifiedclouds);
42 | void separateCloudsClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::vector::Ptr> &classifiedclouds);
43 |
44 | void writeCloudClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::string fname);
45 | void writeCloudClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::string fname);
46 |
--------------------------------------------------------------------------------
/include/riegl/.keep_include:
--------------------------------------------------------------------------------
1 | abc
2 |
--------------------------------------------------------------------------------
/include/treeseg.h:
--------------------------------------------------------------------------------
1 | /*
2 | * treeseg.h
3 | *
4 | * MIT License
5 | *
6 | * Copyright 2017 Andrew Burt
7 | *
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy
9 | * of this software and associated documentation files (the "Software"), to
10 | * deal in the Software without restriction, including without limitation the
11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 | * sell copies of the Software, and to permit persons to whom the Software is
13 | * furnished to do so, subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be included in
16 | * all copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 | * IN THE SOFTWARE.
25 | */
26 |
27 | // treeseg has been developed using:
28 | // Point Cloud Library (http://www.pointclouds.org)
29 |
30 | #include "treeseg_pointtype.h"
31 |
32 | #include
33 | #include
34 | #include
35 |
36 | struct treeparams
37 | {
38 | float x,y;
39 | float d;
40 | float h;
41 | float c;
42 | };
43 |
44 | struct cylinder
45 | {
46 | bool ismodel;
47 | float x,y,z;
48 | float dx,dy,dz;
49 | float rad;
50 | float len;
51 | float steprad;
52 | float stepcov;
53 | float radratio;
54 | pcl::PointCloud::Ptr cloud;
55 | pcl::PointCloud::Ptr inliers;
56 | };
57 |
58 | struct cloudmetrics
59 | {
60 | int count;
61 | Eigen::Vector4f min3D;
62 | Eigen::Vector4f max3D;
63 | Eigen::Vector4f centroid;
64 | Eigen::Matrix3f covariancematrix;
65 | Eigen::Matrix3f eigenvectors;
66 | Eigen::Vector3f eigenvalues;
67 | Eigen::Vector3f vector3D;
68 | float length;
69 | };
70 |
71 | struct basiccloudmetrics
72 | {
73 | int count;
74 | Eigen::Vector4f min3D;
75 | Eigen::Vector4f max3D;
76 | };
77 |
78 | //File IO
79 |
80 | std::vector getFileID(std::string filename);
81 | void readTiles(const std::vector &args, pcl::PointCloud::Ptr &cloud);
82 | int getTilesStartIdx(const std::vector &args);
83 | void writeClouds(const std::vector::Ptr> &clusters, std::string fname, bool doPCA);
84 |
85 | //Nearest neighbour analysis
86 |
87 | std::vector dNN(const pcl::PointCloud::Ptr &cloud, int nnearest);
88 | std::vector> dNNz(const pcl::PointCloud::Ptr &cloud, int nnearest, float zstep);
89 | float interpolatedNNZ(float x, const std::vector> &nndata, bool extrapolate);
90 |
91 | //Cloud metrics
92 |
93 | void getCloudMetrics(const pcl::PointCloud::Ptr &cloud, cloudmetrics metrics);
94 | float getCloudLength(const pcl::PointCloud::Ptr &cloud, const Eigen::Vector4f ¢roid, const Eigen::Matrix3f &eigenvectors);
95 | void getBasicCloudMetrics(const pcl::PointCloud::Ptr &cloud, basiccloudmetrics metrics);
96 |
97 | //Downsampling
98 |
99 | void downsample(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool octree=true);
100 | void thin(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool preservePointClosestToVoxelCentroid=false);
101 |
102 | //Spatial filters
103 |
104 | void spatial1DFilter(const pcl::PointCloud::Ptr &original, std::string dimension, float min, float max, pcl::PointCloud::Ptr &filtered);
105 | void spatial3DCylinderFilter(const pcl::PointCloud::Ptr &original, cylinder cyl, pcl::PointCloud::Ptr &filtered);
106 |
107 | //Clustering
108 |
109 | void euclideanClustering(const pcl::PointCloud::Ptr &cloud, float dmax, int nmin, std::vector::Ptr> &clusters);
110 |
111 | //Principal component analysis
112 |
113 | void computePCA(const pcl::PointCloud::Ptr &cloud, Eigen::Vector4f ¢roid, Eigen::Matrix3f &covariancematrix, Eigen::Matrix3f &eigenvectors, Eigen::Vector3f &eigenvalues);
114 |
115 | //Surface normals
116 |
117 | void estimateNormals(const pcl::PointCloud::Ptr &cloud, int nnearest, pcl::PointCloud::Ptr &normals);
118 |
119 | //Segmentation
120 |
121 | void regionSegmentation(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, int nneighbours, int nmin, int nmax, float smoothness, float curvature, std::vector::Ptr> ®ions);
122 |
123 | //Shape fitting
124 |
125 | void fitPlane(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, float dthreshold, pcl::PointIndices::Ptr &inliers, float nweight=0, float angle=0, Eigen::Vector3f axis={0,0,1});
126 | std::vector fitCircle(const pcl::PointCloud::Ptr &cloud, int nnearest);
127 | void fitCylinder(const pcl::PointCloud::Ptr &cloud, int nnearest, bool finite, bool diagnostics, cylinder &cyl);
128 | void cylinderDiagnostics(cylinder &cyl, int nnearest);
129 |
130 | //Generic
131 |
132 | bool sortCloudByX(const PointTreeseg &p1, const PointTreeseg &p2);
133 | bool sortCloudByY(const PointTreeseg &p1, const PointTreeseg &p2);
134 | bool sortCloudByZ(const PointTreeseg &p1, const PointTreeseg &p2);
135 | bool sortCloudByDescEuclidDist(const PointTreeseg &p1, const PointTreeseg &p2);
136 | #if XYZRRDRS == true
137 | bool sortCloudByRange(const PointTreeseg &p1, const PointTreeseg &p2);
138 | bool sortCloudByReflectance(const PointTreeseg &p1, const PointTreeseg &p2);
139 | bool sortCloudByDeviation(const PointTreeseg &p1, const PointTreeseg &p2);
140 | bool sortCloudByReturnNumber(const PointTreeseg &p1, const PointTreeseg &p2);
141 | bool sortCloudByScanNumber(const PointTreeseg &p1, const PointTreeseg &p2);
142 | #endif
143 | bool equalPoint(const PointTreeseg &p1, const PointTreeseg &p2);
144 | bool sort2DFloatVectorByCol1(const std::vector &v1, const std::vector &v2);
145 | bool sort2DFloatVectorByCol2(const std::vector &v1, const std::vector &v2);
146 | int findClosestIdx(const pcl::PointCloud::Ptr &cloud, const std::vector::Ptr> &clouds, bool biggest);
147 | int findPrincipalCloudIdx(const std::vector::Ptr> &clouds);
148 | void extractIndices(const pcl::PointCloud::Ptr &cloud, const pcl::PointIndices::Ptr &inliers, bool invert, pcl::PointCloud::Ptr &filtered);
149 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b);
150 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b, const pcl::KdTreeFLANN &kdtree);
151 | bool intersectionTest3DBox(const Eigen::Vector4f &amin, const Eigen::Vector4f &amax, const Eigen::Vector4f &bmin, const Eigen::Vector4f &bmax);
152 | void catIntersectingClouds(std::vector::Ptr> &clouds);
153 | void removeDuplicatePoints(pcl::PointCloud::Ptr &cloud);
154 |
155 | //treeseg specific
156 |
157 | std::vector> getDtmAndSlice(const pcl::PointCloud::Ptr &plot, float resolution, float percentile, float zmin, float zmax, pcl::PointCloud::Ptr &slice);
158 | void correctStem(const pcl::PointCloud::Ptr &stem, float nnearest, float zstart, float zstep, float stepcovmax, float radchangemin, pcl::PointCloud::Ptr &corrected);
159 | void removeFarRegions(float dmin, std::vector::Ptr> ®ions);
160 | void precalculateIntersections(const std::vector::Ptr> ®ions, std::vector> &intersections, float expansion);
161 | void buildTree(const std::vector::Ptr> ®ions, int cyclecount, int firstcount, float firstdistance, int nnearest, float seconddist, pcl::PointCloud::Ptr &tree);
162 | treeparams getTreeParams(const pcl::PointCloud::Ptr &cloud, int nnearest, float zstep, float diffmax);
163 |
--------------------------------------------------------------------------------
/include/treeseg_pointtype.h:
--------------------------------------------------------------------------------
1 | /*
2 | * treeseg_pointtype.h
3 | *
4 | * MIT License
5 | *
6 | * Copyright 2020 Andrew Burt
7 | *
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy
9 | * of this software and associated documentation files (the "Software"), to
10 | * deal in the Software without restriction, including without limitation the
11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 | * sell copies of the Software, and to permit persons to whom the Software is
13 | * furnished to do so, subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be included in
16 | * all copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 | * IN THE SOFTWARE.
25 | */
26 |
27 | #define PCL_NO_PRECOMPILE
28 |
29 | #define XYZRRDRS false
30 |
31 | #include
32 |
33 | struct PointTreeseg
34 | {
35 | PCL_ADD_POINT4D;
36 | #if XYZRRDRS == true
37 | union
38 | {
39 | struct
40 | {
41 | float range;
42 | float reflectance;
43 | };
44 | float data_rr[4]; // 16bytes
45 | };
46 | union
47 | {
48 | struct
49 | {
50 | std::uint16_t deviation;
51 | std::uint16_t return_number;
52 | std::uint16_t scan_number;
53 | };
54 | std::uint16_t data_drs[4]; // 8bytes
55 | };
56 | //this may be poorly aligned (i.e., padding)
57 | #endif
58 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW
59 | };
60 | POINT_CLOUD_REGISTER_POINT_STRUCT
61 | (
62 | PointTreeseg,
63 | (float,x,x)
64 | (float,y,y)
65 | (float,z,z)
66 | #if XYZRRDRS == true
67 | (float,range,range)
68 | (float,reflectance,reflectance)
69 | (std::uint16_t,deviation,deviation)
70 | (std::uint16_t,return_number,return_number)
71 | (std::uint16_t,scan_number,scan_number)
72 | #endif
73 | )
74 |
--------------------------------------------------------------------------------
/lib/riegl/.keep_lib:
--------------------------------------------------------------------------------
1 | abc
2 |
--------------------------------------------------------------------------------
/src/downsample.cpp:
--------------------------------------------------------------------------------
1 | #include "treeseg.h"
2 |
3 | #include
4 |
5 | int main(int argc, char **argv)
6 | {
7 | std::vector args(argv+1,argv+argc);
8 | float edgelength = std::stof(args[0]);
9 | bool otree = true;
10 | pcl::PCDReader reader;
11 | pcl::PCDWriter writer;
12 | pcl::PointCloud::Ptr original(new pcl::PointCloud);
13 | pcl::PointCloud::Ptr downsampled(new pcl::PointCloud);
14 | for(int i=1;i id = getFileID(args[i]);
17 | std::stringstream ss;
18 | ss << id[0] << ".tile.downsample." << id[1] << ".pcd";
19 | reader.read(args[i],*original);
20 | downsample(original,edgelength,downsampled,otree);
21 | writer.write(ss.str(),*downsampled,true);
22 | original->clear();
23 | downsampled->clear();
24 | }
25 | return 0;
26 | }
27 |
--------------------------------------------------------------------------------
/src/findstems.cpp:
--------------------------------------------------------------------------------
1 | #include "treeseg.h"
2 |
3 | #include
4 | #include
5 |
6 | int main(int argc, char **argv)
7 | {
8 | std::vector args(argv+1,argv+argc);
9 | //
10 | pcl::PCDReader reader;
11 | pcl::PCDWriter writer;
12 | std::stringstream ss;
13 | //
14 | std::cout << "Reading slice: " << std::flush;
15 | std::vector id = getFileID(args[4]);
16 | pcl::PointCloud::Ptr slice(new pcl::PointCloud);
17 | reader.read(args[4],*slice);
18 | std::cout << "complete" << std::endl;
19 | //
20 | std::cout << "Cluster extraction: " << std::flush;
21 | std::vector::Ptr> clusters;
22 | int nnearest = 18;
23 | int nmin = 100;
24 | std::vector nndata = dNN(slice,nnearest);
25 | euclideanClustering(slice,nndata[0],nmin,clusters);
26 | ss.str("");
27 | ss << id[0] << ".intermediate.slice.clusters.pcd";
28 | writeClouds(clusters,ss.str(),false);
29 | std::cout << ss.str() << " | " << clusters.size() << std::endl;
30 | //
31 | std::cout << "Region-based segmentation: " << std::flush;
32 | std::vector::Ptr> regions;
33 | pcl::PointCloud::Ptr normals(new pcl::PointCloud);
34 | float smoothness = std::stof(args[0]);
35 | for(int i=0;i::Ptr> tmpregions;
38 | estimateNormals(clusters[i],50,normals);
39 | regionSegmentation(clusters[i],normals,250,100,std::numeric_limits::max(),smoothness,2,tmpregions);
40 | for(int j=0;jclear();
42 | }
43 | ss.str("");
44 | ss << id[0] << ".intermediate.slice.clusters.regions.pcd";
45 | writeClouds(regions,ss.str(),false);
46 | std::cout << ss.str() << " | " << regions.size() << std::endl;
47 | //
48 | std::cout << "RANSAC cylinder fits: " << std::flush;
49 | std::vector::Ptr>> cylinders;
50 | nnearest = 60;
51 | float dmin = std::stof(args[1]);
52 | float dmax = std::stof(args[2]);
53 | std::ifstream coordfile;
54 | coordfile.open(args[3]);
55 | float coords[4];
56 | int n = 0;
57 | if(coordfile.is_open())
58 | {
59 | while(!coordfile.eof())
60 | {
61 | coordfile >> coords[n];
62 | n++;
63 | }
64 | }
65 | coordfile.close();
66 | float xmin = coords[0];
67 | float xmax = coords[1];
68 | float ymin = coords[2];
69 | float ymax = coords[3];
70 | float lmin = 2.5; //assumes 3m slice
71 | float stepcovmax = 0.1;
72 | float radratiomin = 0.9;
73 | for(int i=0;i= dmin && cyl.rad*2 <= dmax && cyl.len >= lmin)
81 | {
82 | if(cyl.stepcov <= stepcovmax && cyl.radratio > radratiomin)
83 | {
84 | if(cyl.x >= xmin && cyl.x <= xmax)
85 | {
86 | if(cyl.y >= ymin && cyl.y <= ymax)
87 | {
88 | cylinders.push_back(std::make_pair(cyl.rad,cyl.inliers));
89 | }
90 | }
91 | }
92 | }
93 | }
94 | }
95 | std::sort(cylinders.rbegin(),cylinders.rend());
96 | std::vector::Ptr> cyls;
97 | for(int i=0;i idx;
106 | for(int j=0;j= (90 - anglemax) && angle <= (90 + anglemax)) idx.push_back(j);
117 | }
118 | std::vector::Ptr> pca;
119 | for(int k=0;k::Ptr> stems;
128 | stems = pca;
129 | catIntersectingClouds(stems);
130 | ss.str("");
131 | ss << id[0] << ".intermediate.slice.clusters.regions.cylinders.principal.cat.pcd";
132 | writeClouds(stems,ss.str(),false);
133 | for(int m=0;m
4 | #include
5 | #include
6 |
7 | float maxheight(float dbh)
8 | {
9 | //m -> 41.22 * dbh ^ 0.3406
10 | //ci_u -> 42.30 * dbh ^ 0.3697
11 | float height = 42.30 * pow(dbh,0.3697) + 50;
12 | return height;
13 | }
14 |
15 | float maxcrown(float dbh)
16 | {
17 | //m -> 29.40 * dbh ^ 0.6524
18 | //ci_u -> 30.36 * dbh ^ 0.6931
19 | float extent = 30.36 * pow(dbh,0.6931) + 30;
20 | return extent;
21 | }
22 |
23 | int main(int argc, char **argv)
24 | {
25 | std::vector args(argv+1,argv+argc);
26 | pcl::PCDReader reader;
27 | pcl::PCDWriter writer;
28 | std::cout << "Reading plot-level cloud: " << std::flush;
29 | pcl::PointCloud::Ptr plot(new pcl::PointCloud);
30 | readTiles(args,plot);
31 | std::cout << "complete" << std::endl;
32 | //
33 | for(int i=1;i id = getFileID(args[i]);
38 | pcl::PointCloud::Ptr stem(new pcl::PointCloud);
39 | reader.read(args[i],*stem);
40 | //
41 | std::cout << "Estimating DBH: " << std::flush;
42 | int nnearest = 90;
43 | float zstep = 0.75;
44 | float diffmax = 0.1;
45 | treeparams params = getTreeParams(stem,nnearest,zstep,diffmax);
46 | std::cout << params.d << std::endl;
47 | //
48 | std::cout << "Volume dimensions: " << std::flush;
49 | float h = maxheight(params.d);
50 | float r = maxcrown(params.d)/2;
51 | std::cout << h << "m x " << r << "m (height + radius)" << std::endl;
52 | //
53 | std::cout << "Segmenting volume: " << std::flush;
54 | pcl::PointCloud::Ptr volume(new pcl::PointCloud);
55 | pcl::PointCloud::Ptr tmp1(new pcl::PointCloud);
56 | pcl::PointCloud::Ptr tmp2(new pcl::PointCloud);
57 | Eigen::Vector4f min,max,centroid;
58 | cylinder cyl;
59 | pcl::getMinMax3D(*stem,min,max);
60 | pcl::compute3DCentroid(*stem,centroid);
61 | cyl.x = centroid[0];
62 | cyl.y = centroid[1];
63 | cyl.z = centroid[2];
64 | cyl.rad = r;
65 | cyl.dx = 0;
66 | cyl.dy = 0;
67 | cyl.dz = 1;
68 | spatial3DCylinderFilter(plot,cyl,tmp1);
69 | float stempos = std::stof(args[0]);
70 | spatial1DFilter(tmp1,"z",max[2]*stempos,min[2]+h,tmp2);
71 | *volume += *stem;
72 | *volume += *tmp2;
73 | std::stringstream ss;
74 | ss << id[0] << ".v." << id[1] << ".pcd";
75 | writer.write(ss.str(),*volume,true);
76 | std::cout << ss.str() << std::endl;
77 | //
78 | std::cout << "Removing duplicate points: " << std::flush;
79 | removeDuplicatePoints(volume);
80 | ss.str("");
81 | ss << id[0] << ".volume." << id[1] << ".pcd";
82 | writer.write(ss.str(),*volume,true);
83 | std::cout << ss.str() << std::endl;
84 | }
85 | return 0;
86 | }
87 |
--------------------------------------------------------------------------------
/src/getdtmslice.cpp:
--------------------------------------------------------------------------------
1 | #include "treeseg.h"
2 |
3 | #include
4 |
5 | int main(int argc, char **argv)
6 | {
7 | std::vector args(argv+1,argv+argc);
8 | float resolution = std::stof(args[0]);
9 | float percentile = std::stof(args[1]);
10 | float zmin = std::stof(args[2]);
11 | float zmax = std::stof(args[3]);
12 | pcl::PointCloud::Ptr plotcloud(new pcl::PointCloud);
13 | pcl::PCDWriter writer;
14 | std::vector id = getFileID(args[4]);
15 | readTiles(args,plotcloud);
16 | std::stringstream ss;
17 | ss.str("");
18 | ss << id[0] << ".slice.pcd";
19 | std::vector> dem;
20 | pcl::PointCloud::Ptr slice(new pcl::PointCloud);
21 | dem = getDtmAndSlice(plotcloud,resolution,percentile,zmin,zmax,slice);
22 | for(int j=0;j
35 |
36 | #include
37 | #include
38 | #include
39 |
40 | void gmmByPoint(const pcl::PointCloud::Ptr &cloud, int knn, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_diag &model)
41 | {
42 | pcl::PointCloud::Ptr kcloud(new pcl::PointCloud);
43 | Eigen::Vector4f centroid;
44 | Eigen::Matrix3f covariancematrix,eigenvectors;
45 | Eigen::Vector3f eigenvalues;
46 | float esum,e1,e2,e3;
47 | float features[6];
48 | featmatrix.resize(6,cloud->points.size());
49 | featmatrix.zeros();
50 | pcl::KdTreeFLANN tree;
51 | tree.setInputCloud(cloud);
52 | int k = knn+1;
53 | std::vector pointIdxNKNSearch(k);
54 | std::vector pointNKNSquaredDistance(k);
55 | for(int i=0;ipoints.size();i++)
56 | {
57 | PointTreeseg searchPoint = cloud->points[i];
58 | tree.nearestKSearch(searchPoint,k,pointIdxNKNSearch,pointNKNSquaredDistance);
59 | for(int j=0;jinsert(kcloud->end(),cloud->points[pointIdxNKNSearch[j]]);
60 | computePCA(kcloud,centroid,covariancematrix,eigenvectors,eigenvalues);
61 | esum = eigenvalues(0) + eigenvalues(1) + eigenvalues(2);
62 | e1 = eigenvalues(2) / esum;
63 | e2 = eigenvalues(1) / esum;
64 | e3 = eigenvalues(0) / esum;
65 | features[0] = e3;
66 | features[1] = e1 - e2;
67 | features[2] = e2 - e3;
68 | features[3] = (e2 - e3) / e1;
69 | features[4] = (e1 * log(e1)) + (e2 * log(e2)) + (e3 * log(e3));
70 | features[5] = (e1 - e2) / e1;
71 | for(int k=0;kclear();
73 | }
74 | bool status = model.learn(featmatrix,n_gaus,arma::gmm_dist_mode(dist_mode),arma::gmm_seed_mode(seed_mode),km_iter,em_iter,1e-10,false);
75 | }
76 |
77 | void gmmByCluster(const std::vector::Ptr> &clouds, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_full &model)
78 | {
79 | Eigen::Vector4f centroid;
80 | Eigen::Matrix3f covariancematrix,eigenvectors;
81 | Eigen::Vector3f eigenvalues;
82 | float esum,e1,e2,e3;
83 | float features[6];
84 | featmatrix.resize(6,clouds.size());
85 | featmatrix.zeros();
86 | for(int i=0;i classifyGmmPointModel(const pcl::PointCloud::Ptr &cloud, int n_gaus, arma::mat &featmatrix, arma::gmm_diag &model)
113 | {
114 | int features =6;
115 | Eigen::MatrixXf means(features,n_gaus);
116 | for(int i=0;i woodlist(n_gaus,0);
131 | for(int i=0;ipoints.size()-1),arma::eucl_dist);
136 | std::vector classifications;
137 | for(int i=0;ipoints.size();i++)
138 | {
139 | if(woodlist[ids[i]] == 1) classifications.push_back(0);
140 | else if(woodlist[ids[i]] == 0) classifications.push_back(1);
141 | }
142 | return classifications;
143 | }
144 |
145 | std::vector classifyGmmClusterModel(const std::vector::Ptr> &clouds, int n_gaus, arma::mat &featmatrix, arma::gmm_full &model)
146 | {
147 | int features = 6;
148 | Eigen::MatrixXf means(features,n_gaus);
149 | for(int i=0;i woodlist(n_gaus,0);
164 | for(int i=0;i classifications;
169 | arma::urowvec ids = model.assign(featmatrix.cols(0,clouds.size()-1),arma::eucl_dist);
170 | for(int i=0;i::Ptr &cloud, const std::vector &classifications, std::vector::Ptr> &classifiedclouds)
179 | {
180 | auto minmax = minmax_element(classifications.begin(),classifications.end());
181 | int min = *(minmax.first);
182 | int max = *(minmax.second);
183 | for(int i=0;i<=max;i++)
184 | {
185 | pcl::PointCloud::Ptr ccloud(new pcl::PointCloud);
186 | classifiedclouds.push_back(ccloud);
187 | }
188 | for(int j=0;jpoints.size();j++) classifiedclouds[classifications[j]]->insert(classifiedclouds[classifications[j]]->end(),cloud->points[j]);
189 | }
190 |
191 | void separateCloudsClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::vector::Ptr> &classifiedclouds)
192 | {
193 | auto minmax = minmax_element(classifications.begin(),classifications.end());
194 | int min = *(minmax.first);
195 | int max = *(minmax.second);
196 | for(int i=0;i<=max;i++)
197 | {
198 | pcl::PointCloud::Ptr ccloud(new pcl::PointCloud);
199 | classifiedclouds.push_back(ccloud);
200 | }
201 | for(int j=0;jpoints.size();k++) classifiedclouds[classifications[j]]->insert(classifiedclouds[classifications[j]]->end(),clouds[j]->points[k]);
204 | }
205 | }
206 |
207 | void writeCloudClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::string fname)
208 | {
209 | pcl::PCDWriter writer;
210 | auto minmax = minmax_element(classifications.begin(),classifications.end());
211 | int min = *(minmax.first);
212 | int max = *(minmax.second);
213 | int colours[max+1][3];
214 | for(int i=0;i::Ptr out(new pcl::PointCloud);
221 | //srand(time(NULL));
222 | for(int j=0;jpoints.size();j++)
223 | {
224 | pcl::PointXYZRGB point;
225 | point.x = cloud->points[j].x;
226 | point.y = cloud->points[j].y;
227 | point.z = cloud->points[j].z;
228 | point.r = colours[classifications[j]][0];
229 | point.g = colours[classifications[j]][1];
230 | point.b = colours[classifications[j]][2];
231 | out->insert(out->end(),point);
232 | }
233 | writer.write(fname,*out,true);
234 | }
235 |
236 | void writeCloudClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::string fname)
237 | {
238 | pcl::PCDWriter writer;
239 | auto minmax = minmax_element(classifications.begin(),classifications.end());
240 | int min = *(minmax.first);
241 | int max = *(minmax.second);
242 | int colours[max+1][3];
243 | for(int i=0;i::Ptr out(new pcl::PointCloud);
250 | for(int j=0;jpoints.size();k++)
253 | {
254 | pcl::PointXYZRGB point;
255 | point.x = clouds[j]->points[k].x;
256 | point.y = clouds[j]->points[k].y;
257 | point.z = clouds[j]->points[k].z;
258 | point.r = colours[classifications[j]][0];
259 | point.g = colours[classifications[j]][1];
260 | point.b = colours[classifications[j]][2];
261 | out->insert(out->end(),point);
262 | }
263 | }
264 | writer.write(fname,*out,true);
265 | }
266 |
--------------------------------------------------------------------------------
/src/nearestneighbour.cpp:
--------------------------------------------------------------------------------
1 | #include "treeseg.h"
2 |
3 | #include
4 |
5 | int main(int argc, char **argv)
6 | {
7 | std::vector args(argv+1,argv+argc);
8 | pcl::PCDReader reader;
9 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
10 | float zstep = std::stof(args[0]);
11 | int nnearest = std::stoi(args[1]);
12 | reader.read(args[2],*cloud);
13 | std::vector> nndata;
14 | nndata = dNNz(cloud,nnearest,zstep);
15 | for(int i=0;i
4 |
5 | int main (int argc, char **argv)
6 | {
7 | std::vector args(argv+1,argv+argc);
8 | pcl::PCDReader reader;
9 | pcl::PCDWriter writer;
10 | for(int i=0;i tmp1,tmp2;
14 | boost::split(tmp1,args[i],boost::is_any_of("/"));
15 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of("."));
16 | for(int j=0;j::Ptr cloud(new pcl::PointCloud);
19 | reader.read(args[i],*cloud);
20 | std::ofstream outfile(ss.str());
21 | for(int i=0;ipoints.size();i++)
22 | {
23 | #if XYZRRDRS == false
24 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << "\n";
25 | #else
26 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << " " << cloud->points[i].range << " " << cloud->points[i].reflectance << " " << cloud->points[i].deviation << " " << cloud->points[i].return_number << " " << cloud->points[i].scan_number<< "\n";
27 | #endif
28 | }
29 | outfile.close();
30 | }
31 | return 0;
32 | }
33 |
--------------------------------------------------------------------------------
/src/pcdPointXYZRGB2txt.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main (int argc, char **argv)
4 | {
5 | std::vector args(argv+1,argv+argc);
6 | pcl::PCDReader reader;
7 | pcl::PCDWriter writer;
8 | for(int i=0;i tmp1,tmp2;
12 | boost::split(tmp1,args[i],boost::is_any_of("/"));
13 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of("."));
14 | for(int j=0;j::Ptr cloud(new pcl::PointCloud);
17 | reader.read(args[i],*cloud);
18 | std::ofstream outfile(ss.str());
19 | for(int i=0;ipoints.size();i++)
20 | {
21 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << " " << (int)cloud->points[i].r << " " << (int)cloud->points[i].g << " " << (int)cloud->points[i].b << "\n";
22 | }
23 | outfile.close();
24 | }
25 | return 0;
26 | }
27 |
--------------------------------------------------------------------------------
/src/plotcoords.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | int main(int argc, char **argv)
10 | {
11 | std::vector args(argv+1,argv+argc);
12 | std::string matrix_dir = args[0];
13 | if(matrix_dir[matrix_dir.length()-1] != '/') matrix_dir = matrix_dir + '/';
14 | std::vector fnames;
15 | char buf[PATH_MAX + 1];
16 | DIR *dir = NULL;
17 | struct dirent *drnt = NULL;
18 | dir = opendir(matrix_dir.c_str());
19 | while(drnt = readdir(dir)) fnames.push_back(drnt->d_name);
20 | closedir(dir);
21 | std::vector x,y;
22 | for(int i=0;i> matrix[no_count];
38 | no_count++;
39 | }
40 | }
41 | x.push_back(matrix[3]);
42 | y.push_back(matrix[7]);
43 | }
44 | }
45 | float x_sum = 0;
46 | float y_sum = 0;
47 | for(int j=0;j
2 | #include
3 | #include
4 |
5 | #include
6 |
7 | #include
8 |
9 | #include "treeseg.h"
10 |
11 | struct pcloud
12 | {
13 | std::vector x,y,z;
14 | std::vector range;
15 | std::vector amplitude;
16 | std::vector reflectance;
17 | std::vector deviation;
18 | std::vector return_number;
19 | std::uint16_t scan_number;
20 | std::vector time;
21 | float matrix[16];
22 | };
23 |
24 | class importer : public scanlib::pointcloud
25 | {
26 | pcloud &pc;
27 | public:
28 | importer(pcloud &pc) : scanlib::pointcloud(false), pc(pc){}
29 | protected:
30 | //void on_echo_transformed(echo_type echo)
31 | void on_shot_end()
32 | {
33 | for(int i=0;i args(argv+1,argv+argc);
55 | std::string top_dir = args[0];
56 | if(top_dir[top_dir.length()-1] != '/') top_dir = top_dir + "/";
57 | std::ifstream cfile;
58 | cfile.open(args[1]);
59 | float coordfile[4];
60 | int no_count = 0;
61 | if(cfile.is_open())
62 | {
63 | while(!cfile.eof())
64 | {
65 | cfile >> coordfile[no_count];
66 | no_count++;
67 | }
68 | }
69 | cfile.close();
70 | float plot_xmin = coordfile[0] - 20;
71 | float plot_xmax = coordfile[1] + 20;
72 | float plot_ymin = coordfile[2] - 20;
73 | float plot_ymax = coordfile[3] + 20;
74 | float tile_area = std::stof(args[2]);
75 | float tile_length = sqrt(tile_area);
76 | int tile_count = 0;
77 | for(float x=plot_xmin;x positions;
112 | DIR *tdir = NULL;
113 | tdir = opendir (top_dir.c_str());
114 | struct dirent *tent = NULL;
115 | while(tent = readdir(tdir)) positions.push_back(tent->d_name);
116 | closedir(tdir);
117 | for(int k=0;k position_contents;
128 | DIR *pdir = NULL;
129 | pdir = opendir(c_position);
130 | struct dirent *pent = NULL;
131 | while(pent = readdir(pdir)) position_contents.push_back(pent->d_name);
132 | closedir(pdir);
133 | std::string rxpname;
134 | for(int l=0;l rc;
153 | rc = scanlib::basic_rconnection::create(rxpname);
154 | rc->open();
155 | scanlib::decoder_rxpmarker dec(rc);
156 | importer imp(pc);
157 | scanlib::buffer buf;
158 | for(dec.get(buf);!dec.eoi();dec.get(buf))
159 | {
160 | imp.dispatch(buf.begin(), buf.end());
161 | }
162 | rc->close();
163 | }
164 | catch(...)
165 | {
166 | continue;
167 | }
168 | ss.str("");
169 | if(positions[k][7] == '0' && positions[k][8] == '0') ss << positions[k][9];
170 | else if(positions[k][7] == '0') ss << positions[k][8] << positions[k][9];
171 | else ss << positions[k][7] << positions[k][8] << positions[k][9];
172 | std::string scan_number = ss.str();
173 | pc.scan_number = (std::uint16_t)atoi(scan_number.c_str());
174 | std::ifstream mfile;
175 | mfile.open(matrixname);
176 | int no_count = 0;
177 | if(mfile.is_open())
178 | {
179 | while(!mfile.eof())
180 | {
181 | mfile >> pc.matrix[no_count];
182 | no_count++;
183 | }
184 | }
185 | for(int m=0;m= tile_coords[n][0] && X < tile_coords[n][1])
193 | {
194 | if(Y >= tile_coords[n][2] && Y < tile_coords[n][3])
195 | {
196 | if(pc.deviation[m] <= deviation_max)
197 | {
198 | xyzfiles[n].write(reinterpret_cast(&X),sizeof(X));
199 | xyzfiles[n].write(reinterpret_cast(&Y),sizeof(Y));
200 | xyzfiles[n].write(reinterpret_cast(&Z),sizeof(Z));
201 | #if XYZRRDRS == true
202 | xyzfiles[n].write(reinterpret_cast(&pc.range[m]),sizeof(pc.range[m]));
203 | xyzfiles[n].write(reinterpret_cast(&pc.reflectance[m]),sizeof(pc.reflectance[m]));
204 | xyzfiles[n].write(reinterpret_cast(&pc.deviation[m]),sizeof(pc.deviation[m]));
205 | xyzfiles[n].write(reinterpret_cast(&pc.return_number[m]),sizeof(pc.return_number[m]));
206 | xyzfiles[n].write(reinterpret_cast(&pc.scan_number),sizeof(pc.scan_number));
207 | #endif
208 | tile_pointcount[n] += 1;
209 | }
210 | }
211 | }
212 | }
213 | }
214 | }
215 | }
216 | for(int p=0;p " << pcdnames[q] << "; rm header.tmp " << xyznames[q];
231 | std::string string;
232 | const char* cc;
233 | string = ss.str();
234 | cc = string.c_str();
235 | system(cc);
236 | }
237 | return 0;
238 | }
239 |
--------------------------------------------------------------------------------
/src/rxp2tree.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | struct pcloud
12 | {
13 | std::vector x,y,z;
14 | std::vector range;
15 | std::vector amplitude;
16 | std::vector reflectance;
17 | std::vector deviation;
18 | std::vector return_number;
19 | float scan_number;
20 | std::vector time;
21 | float matrix[16];
22 | };
23 |
24 | class importer : public scanlib::pointcloud
25 | {
26 | pcloud &pc;
27 | public:
28 | importer(pcloud &pc) : scanlib::pointcloud(false), pc(pc){}
29 | protected:
30 | void on_shot_end()
31 | {
32 | for(int i=0;i fnames;
54 | std::vector count;
55 | std::vector x,y,z;
56 | for(int i=3;i name1;
60 | std::vector name2;
61 | std::vector