├── .gitignore
├── CreateDistribution.txt
├── LICENSE.txt
├── README.md
├── bin
├── pylidar_canopy
├── pylidar_index
├── pylidar_info
├── pylidar_rasterize
├── pylidar_test
├── pylidar_tile
└── pylidar_translate
├── doc
├── Makefile
├── make.bat
└── source
│ ├── arrayvisualisation.rst
│ ├── basedriver.rst
│ ├── commandline_canopy.rst
│ ├── commandline_index.rst
│ ├── commandline_info.rst
│ ├── commandline_tiles.rst
│ ├── commandline_translate.rst
│ ├── conf.py
│ ├── gdaldriver.rst
│ ├── index.rst
│ ├── lidarformats
│ ├── ascii.rst
│ ├── generic.rst
│ ├── gridindexutils.rst
│ ├── h5space.rst
│ ├── las.rst
│ ├── lvisbin.rst
│ ├── lvishdf5.rst
│ ├── pulsewaves.rst
│ ├── riegl.rst
│ ├── spdv3.rst
│ └── spdv4.rst
│ ├── lidarprocessor.rst
│ ├── point.png
│ ├── pointsbypulse.png
│ ├── processorexamples.rst
│ ├── pulse.png
│ ├── pylidar.jpg
│ ├── spdv4format.rst
│ ├── testing.rst
│ ├── threed.gif
│ ├── toolbox
│ ├── arrayutils.rst
│ ├── canopy.rst
│ ├── grdfilters.rst
│ ├── indexing.rst
│ ├── interpolation.rst
│ ├── spatial.rst
│ ├── toolbox.rst
│ ├── translate.rst
│ └── visualisation.rst
│ ├── userclasses.rst
│ └── voxel_example.png
├── pylidar
├── __init__.py
├── basedriver.py
├── gdaldriver.py
├── lidarformats
│ ├── __init__.py
│ ├── ascii.py
│ ├── gedil1a01.py
│ ├── generic.py
│ ├── gridindexutils.py
│ ├── h5space.py
│ ├── las.py
│ ├── lvisbin.py
│ ├── lvishdf5.py
│ ├── pulsewaves.py
│ ├── riegl_rdb.py
│ ├── riegl_rxp.py
│ ├── spdv3.py
│ ├── spdv4.py
│ └── spdv4_index.py
├── lidarprocessor.py
├── testing
│ ├── __init__.py
│ ├── testall.py
│ ├── testsuite1.py
│ ├── testsuite10.py
│ ├── testsuite11.py
│ ├── testsuite12.py
│ ├── testsuite13.py
│ ├── testsuite14.py
│ ├── testsuite15.py
│ ├── testsuite16.py
│ ├── testsuite16b.py
│ ├── testsuite17.py
│ ├── testsuite18.py
│ ├── testsuite19.py
│ ├── testsuite2.py
│ ├── testsuite20.py
│ ├── testsuite21.py
│ ├── testsuite22.py
│ ├── testsuite23.py
│ ├── testsuite23b.py
│ ├── testsuite24.py
│ ├── testsuite3.py
│ ├── testsuite4.py
│ ├── testsuite5.py
│ ├── testsuite6.py
│ ├── testsuite6b.py
│ ├── testsuite7.py
│ ├── testsuite8.py
│ ├── testsuite9.py
│ └── utils.py
├── toolbox
│ ├── __init__.py
│ ├── arrayutils.py
│ ├── canopy
│ │ ├── __init__.py
│ │ ├── canopycommon.py
│ │ ├── canopymetric.py
│ │ ├── pavd_calders2014.py
│ │ ├── pgap_armston2013.py
│ │ └── voxel_hancock2016.py
│ ├── cmdline
│ │ ├── __init__.py
│ │ ├── canopy.py
│ │ ├── index.py
│ │ ├── info.py
│ │ ├── rasterize.py
│ │ ├── tile.py
│ │ └── translate.py
│ ├── grdfilters
│ │ ├── __init__.py
│ │ ├── classGrdReturns.py
│ │ └── pmf.py
│ ├── indexing
│ │ ├── __init__.py
│ │ └── gridindex.py
│ ├── interpolation.py
│ ├── rasterization.py
│ ├── spatial.py
│ ├── translate
│ │ ├── __init__.py
│ │ ├── ascii2spdv4.py
│ │ ├── las2spdv4.py
│ │ ├── lvisbin2spdv4.py
│ │ ├── lvishdf52spdv4.py
│ │ ├── pulsewaves2spdv4.py
│ │ ├── rieglrdb2spdv4.py
│ │ ├── rieglrxp2spdv4.py
│ │ ├── spdv32spdv4.py
│ │ ├── spdv42las.py
│ │ ├── spdv42pulsewaves.py
│ │ └── translatecommon.py
│ └── visualisation.py
└── userclasses.py
├── setup.py
├── src
├── ascii.cpp
├── insidepoly.c
├── las.cpp
├── lvisbin.cpp
├── lvisbin.h
├── pulsewaves.cpp
├── pylfieldinfomap.h
├── pylidar.c
├── pylidar.h
├── pylmatrix.h
├── pylvector.h
├── riegl_rdb.cpp
└── riegl_rxp.cpp
└── testing_cmds.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.o
3 | *.so
4 | *~
5 | *.png
6 | *.img
7 | *.kea
8 | _build
9 | _templates
10 | _static
11 | MANIFEST
12 | build/
13 | doc/build/
14 | dist/
15 |
--------------------------------------------------------------------------------
/CreateDistribution.txt:
--------------------------------------------------------------------------------
1 | How to create a distribution of PyLidar.
2 |
3 | 1. Ensure that you have fetched and committed everything which needs to go in.
4 | 2. Change the version number in the pylidar/__init__.py. Version number
5 | is of the form a.b.c, as discussed below.
6 | DON'T FORGET TO COMMIT THIS, BEFORE THE NEXT STEP!!!!
7 | 3. Push the changes to github with "git push".
8 | 4. Check out a clean copy of the repository into /tmp or
9 | somewhere similar and 'cd' into it.
10 | 5. Ensure your environment has all the extensions being built
11 | (Important!!). In particular, RIVLIB_ROOT, RIWAVELIB_ROOT, RDBLIB_ROOT,
12 | ZLIB_ROOT, LASTOOLS_ROOT and PULSEWAVES_ROOT environment vars should be
13 | set to valid locations. If this is not done not all files will be included
14 | in the zip.
15 | 6. Create the distribution tarball, using
16 | python setup.py sdist --formats=gztar,zip
17 | This creates both a tar.gz and a zip, under a subdirectory called dist
18 | 7. Create checksums of each of these, e.g.
19 | sha256sum pylidar-1.2.3.tar.gz > pylidar-1.2.3.tar.gz.sha256
20 | sha256sum pylidar-1.2.3.zip > pylidar-1.2.3.zip.sha256
21 | 8. Go to the https://github.com/ubarsc/pylidar/releases page, and create a
22 | new release by pressing "Draft a new release".
23 | You should fill in the following:
24 | Tag version: pylidar-A.B.C
25 | Release Title: Version A.B.C
26 | Upload files: Add the tar.gz and zip files, and their sha256 checksum files.
27 | Click "Publish release"
28 |
29 | Version Numbers.
30 | The PyLidar number is structured as A.B.C.
31 | - The A number should change for major alterations, most particularly those
32 | which break backward compatability, or which involve major restructuring of
33 | code or data structures.
34 | - The B number should change for introduction of significant new features
35 | - The C number should change for bug fixes or very minor changes.
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyLidar #
2 |
3 | **Note: This project has moved**
4 |
5 | It is now found at [https://github.com/armstonj/pylidar](https://github.com/armstonj/pylidar).
6 |
7 | For point cloud processing, you may wish to consider these alternatives::
8 |
9 | * [Riegl Tools](https://gitlab.com/jrsrp/sys/lidar/riegl_tools)
10 | * [laspy](https://laspy.readthedocs.io/en/latest/)
11 | * [Cloud Compare](https://github.com/CloudCompare/CloudCompare)
12 |
--------------------------------------------------------------------------------
/bin/pylidar_canopy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.canopy import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
--------------------------------------------------------------------------------
/bin/pylidar_index:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.index import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
--------------------------------------------------------------------------------
/bin/pylidar_info:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.info import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
--------------------------------------------------------------------------------
/bin/pylidar_rasterize:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.rasterize import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
--------------------------------------------------------------------------------
/bin/pylidar_test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.testing import testall
22 |
23 | testall.run()
24 |
--------------------------------------------------------------------------------
/bin/pylidar_tile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.tile import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
--------------------------------------------------------------------------------
/bin/pylidar_translate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of PyLidar
4 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | from __future__ import print_function, division
20 |
21 | from pylidar.toolbox.cmdline.translate import run
22 |
23 | if __name__ == '__main__':
24 | run()
25 |
26 |
--------------------------------------------------------------------------------
/doc/source/arrayvisualisation.rst:
--------------------------------------------------------------------------------
1 | =======================
2 | How to Visualise Arrays
3 | =======================
4 |
5 | Data returned from getPulses(). A one dimensional structured array. Each element describes a Pulse:
6 |
7 | .. image:: pulse.png
8 |
9 | Data returned from getPoints(). A one dimensional structured array. Each element describes a Point:
10 |
11 | .. image:: point.png
12 |
13 | Data returned from getPointsByPulses(). A 2 dimensional masked structured array. Each row has the points for a pulse. As some pulses have more points than others, the mask=True for elements that aren't valid for a pulse (greyed out):
14 |
15 | .. image:: pointsbypulse.png
16 |
17 | Data returned from getPointsByBins()/getPulsesByBins(). A 3 dimensional masked structured array. The X and Y dimensions describe the bins, but each bin has a different number of points/pulses. Clear circles are masked out (mask=True):
18 |
19 | .. image:: threed.gif
20 |
--------------------------------------------------------------------------------
/doc/source/basedriver.rst:
--------------------------------------------------------------------------------
1 | basedriver
2 | ==========
3 | .. automodule:: pylidar.basedriver
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/commandline_canopy.rst:
--------------------------------------------------------------------------------
1 | =====================================
2 | Command Line Examples: pylidar_canopy
3 | =====================================
4 |
5 | Once you have converted your data to SPDV4 format, you can use this
6 | utility to derive various published lidar canopy metrics. Some metrics will also
7 | accept other suitable file formats.
8 |
9 | --------------------------------
10 | Deriving vertical plant profiles
11 | --------------------------------
12 |
13 | *Calders, K., Armston, J., Newnham, G., Herold, M. and Goodwin, N. 2014. Implications of sensor configuration and topography on vertical plant profiles derived from terrestrial LiDAR. Agricultural and forest meteorology, 194: 104--117.*
14 |
15 | Single position vertical plant profiles [Pgap(theta,z), PAI(z), PAVD(z)] from
16 | TLS as described by Calders et al. (2014) Agricultural and Forest
17 | Meteorology are implemented. These are designed to stratify gap fraction,
18 | plant area index, and plant area volume density by height when only
19 | single scan locations are measured. RXP and SPD files are accepted as input.
20 |
21 | Run "pylidar_canopy -h" to obtain full help. The basic usage is to specify
22 | 1 or 2 (in the case of a RIEGL VZ400) input files like this::
23 |
24 | pylidar_canopy -m PAVD_CALDERS2014 -i tls_scan_upright.spd tls_scan_tilted.spd -o vertical_profiles.csv
25 | --minzenith 35.0 5.0 --maxzenith 70.0 35.0 --heightcol HEIGHT
26 |
27 | pylidar_canopy -m PAVD_CALDERS2014 -i tls_scan_upright.rxp -o vertical_profiles.csv
28 | -p -r planefit.rpt --minzenith 35.0 --maxzenith 70.0
29 |
30 | pylidar_canopy -m PAVD_CALDERS2014 -i upright.rxp tilt.rxp -o vertical_profiles.csv
31 | --minazimuth 0 0 --maxazimuth 0 0 --minzenith 35.0 5.0 --maxzenith 70.0 35.0
32 | --externaltransformfn upright.dat tilt.dat --weighted --planecorrection
33 | --reportfile report.rpt --minheight -10 --maxheight 60
34 |
35 | The output is a CSV file with a table of vertical profile metrics [Pgap(theta,z), PAI(z), PAVD(z)]
36 | as columns and vertical height bin (m) starting points as rows. This command can also apply a
37 | plane fit on-the-fly to correct a single location scan for topographic effects (the -p option)
38 | and also output a report on the fit statistics (the -r option). If point heights are already
39 | defined in an SPD file (e.g. from a DEM), specify the point column name to use with --heightcol.
40 |
41 | In the above examples, only view zenith angles between 35 and 70 degrees are used for the
42 | tls_scan_upright.spd file and 5 and 35 degrees for the tls_scan_tilted.spd file. These are
43 | recommended values for the RIEGL VZ400 (Calders *et al.*, 2014).
44 |
45 |
46 | ----------------------------------------------------
47 | Voxel traversal and deriving vertical cover profiles
48 | ----------------------------------------------------
49 |
50 | The voxel traversal is a Python implementation (using numba for speed) of the following algorithm:
51 |
52 | *Amanatides, J. and Woo, A. 1987. A Fast Voxel Traversal Algorithm for Ray Tracing. Proceedings of EuroGraphics. 87.*
53 |
54 | We account for partial interception of each beam by stepwise reduction of beam weighting with each interception. Each beam starts with a weighting of 1, but this is reduced by 1 / *N* with each interception where *N* is the total number of returns for that beam. The end of each beam is assumed to be the edge of the voxel space.
55 |
56 | The derivation of vertical cover profiles is a simplified implementation of the following method:
57 |
58 | *Hancock, S., Anderson, K., Disney, M. and Gaston, K.J. 2017. Measurement of fine-spatial-resolution 3D vegetation structure with airborne waveform lidar: Calibration and validation with voxelised terrestrial lidar. Remote Sensing of Environment, 188: 37--50.*
59 |
60 | The simplification is in how we account for partial interceptions in the calculation of voxel Pgap (see above) and derive the vertical cover profiles (1 - Pgap(z)).
61 |
62 | An example of deriving voxelized vertical Pgap profiles by combining all registered scans::
63 |
64 | pylidar_canopy -m VOXEL_HANCOCK2016 -i scan1.rxp scan2.rxp scan3.rxp
65 | -o nscans.tif cover_profile.tif voxel_class.tif
66 | --voxelsize 0.5 --bounds -50 -50 -10 50 50 60
67 | --rasterdriver GTiff --externaldem dem.tif
68 | --externaltransformfn scan1.dat scan2.dat scan3.dat
69 |
70 | Individual scan products are automatically generated and output:
71 |
72 | 1. Weighted number of beams that reach the voxel unoccluded and have an interception in that voxel (hits)
73 | 2. Weighted number of beams that pass through the voxel unoccluded (miss)
74 | 3. Weighted number of beams that are occluded from the voxel (occl)
75 | 4. Directional gap probability (pgap) calculated as miss / (miss+hits)
76 |
77 | To get the vertical cover for a voxel 1-pgap is averaged across all scans (assuming all intercepted elements have the same projected area at different view angles), with each scan weighted by (miss + hits) / (miss + hits + occl) to account for then visibility of that voxel. The vertical profile of cover is calculated by conditional probability, so assuming a random distribution of elements in each voxel.
78 |
79 | Three merged scan output files are generated and output as specified by the --output command line argument:
80 |
81 | 1. Number of scans that a voxel is visible to (nscans.tif)
82 | 2. Vertical cover profile (cover_profile.tif)
83 | 3. Voxel classification. The following table shows the classification codes:
84 |
85 | +-------------+-------+------+--------+----------+
86 | | Class | Value | Hits | Misses | Occluded |
87 | +=============+=======+======+========+==========+
88 | | Observed | 5 | >0 | >=0 | >=0 |
89 | +-------------+-------+------+--------+----------+
90 | | Empty | 4 | =0 | >0 | >=0 |
91 | +-------------+-------+------+--------+----------+
92 | | Hidden | 3 | =0 | =0 | >0 |
93 | +-------------+-------+------+--------+----------+
94 | | Unobserved | 2 | =0 | =0 | =0 |
95 | +-------------+-------+------+--------+----------+
96 | | Ground | 1 | | | |
97 | +-------------+-------+------+--------+----------+
98 |
99 |
100 | Below is an example of a horizontal slice of a 0.5 m voxelization output from pylidar_canopy using a single scan as input. The command line syntax for this example was::
101 |
102 | pylidar_canopy -m VOXEL_HANCOCK2016 -i 151125_121744.rxp
103 | -o nscans.tif cover_profile.tif voxel_class.tif
104 | --voxelsize 0.5 --bounds -50 -50 -10 50 50 60
105 | --rasterdriver GTiff
106 |
107 | For ease of interpretation we only show a the output using a single scan. The horizontal slice is taken at 2 m because this is the height of the scanner so beams will traverse the voxel space horizontally, further simplifying visual interpretation.
108 |
109 | .. image:: voxel_example.png
110 | :scale: 50 %
111 | :align: center
112 |
113 |
--------------------------------------------------------------------------------
/doc/source/commandline_index.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | Command Line Examples: pylidar_index
3 | ====================================
4 |
5 | --------------------------------------------------------
6 | Creating a Spatial Index with pylidar_index (deprecated)
7 | --------------------------------------------------------
8 |
9 | Once you have converted your data to SPV4 format, you can use this
10 | utility to spatially index the data. Once it is spatially indexed,
11 | you can process it spatially.
12 |
13 | Run "pylidar_index -h" to obtain full help. The basic usage is to specify input and output files
14 | like this::
15 |
16 | pylidar_index --input data.spdv4 --output indexed.spdv4
17 |
18 | Important options that you should consider overriding are:
19 |
20 | * The resolution that the spatial index is calculated on (the -r or --resolution flag). This will control how much data goes in each bin.
21 | * The type of index (the --indextype flag). This controls what the spatial index is calculated on. CARTESIAN or SCAN is usual for airborne, but SPHERICAL for TLS.
22 | * The size of the blocks that the spatial indexing will use (the -b or --blocksize flag).This will determine the amount of memory used. The smaller the blocks the less memory will be used. The value will be in the units that the spatial index is being calculated in. By default pylidar_index uses 200.
23 | * The temporary directory to create the temprary files in. By default this is the current directory, but you may want to change this if you do not have enough space there.
24 |
25 |
--------------------------------------------------------------------------------
/doc/source/commandline_info.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | Command Line Examples: pylidar_info
3 | ===================================
4 |
5 | -----------------------------------------------
6 | Getting Information on a File with pylidar_info
7 | -----------------------------------------------
8 |
9 | The pylidar_info command takes a --input option to specify the path to a file. Information about the file
10 | is then printed on the terminal. Here is an example::
11 |
12 | pylidar_info --input data.rxp
13 |
14 |
15 |
--------------------------------------------------------------------------------
/doc/source/commandline_tiles.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | Command Line Examples: pylidar_tiles
3 | ====================================
4 |
5 | -----------------------------------------------
6 | Splitting a File into Tiles using pylidar_tiles
7 | -----------------------------------------------
8 |
9 | Once you have converted your data to SPDV4 format, you can use this
10 | utility to split it into tiles so they can be processed independently.
11 |
12 | Run "pylidar_tile -h" to obtain full help. The basic usage is to specify a input file like this::
13 |
14 | pylidar_tile --input data.spdv4
15 |
16 | Many of the flags are similar to "pylidar_index" so consult the help above for more information on these.
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/doc/source/gdaldriver.rst:
--------------------------------------------------------------------------------
1 | gdaldriver
2 | ===========
3 | .. automodule:: pylidar.gdaldriver
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. _contents:
2 |
3 | PyLidar
4 | ===================================
5 |
6 | Introduction
7 | ------------
8 |
9 | A set of Python modules which makes it easy to write lidar
10 | processing code in Python. Based on `SPDLib `_ and
11 | built on top of `RIOS `_
12 | it handles the details of opening and closing files, checking alignment of projection and
13 | grid, stepping through the data in small blocks, etc.,
14 | allowing the programmer to concentrate on the processing involved.
15 | It is licensed under GPL 3.
16 |
17 | See :doc:`spdv4format` for description of the SPD V4 file format. Supported
18 | formats are: SPD V3, SPD V4, RIEGL RXP, LAS, LVIS, ASCII and Pulsewaves
19 | (additional libraries may be required).
20 |
21 |
22 | See the :doc:`arrayvisualisation` page to understand how numpy
23 | arrays are used in PyLidar.
24 |
25 | Work funded by:
26 |
27 | - `DSITI `_ and `OEH `_ through the `Joint Remote Sensing Research Program `_
28 | - `University of Maryland `_
29 |
30 | There is a `Google Group `_ where users can post questions.
31 |
32 | Examples
33 | --------
34 |
35 | See :doc:`processorexamples` for more information on programming using PyLidar. See the following links for more information on running the command line utilities:
36 |
37 | - :doc:`commandline_translate`
38 | - :doc:`commandline_index`
39 | - :doc:`commandline_info`
40 | - :doc:`commandline_tiles`
41 | - :doc:`commandline_canopy`
42 |
43 |
44 | Downloads
45 | ---------
46 |
47 | Source
48 | ^^^^^^
49 |
50 | Source code is available from `GitHub `_.
51 | `RIOS `_, `Numba `_, `Numpy `_
52 | and `h5py `_ are required dependencies. Additional formats require
53 | environment variables set to the root installation of other libraries
54 | before building as detailed in this table:
55 |
56 | +---------------+-----------------------------+-----------------------------------------------------------------------------+
57 | | Type of Files | Environment Variable(s) | Link to Software |
58 | +===============+=============================+=============================================================================+
59 | | LAS/LAZ | LASTOOLS_ROOT | `lastools `_ |
60 | +---------------+-----------------------------+-----------------------------------------------------------------------------+
61 | | Riegl | RIVLIB_ROOT | `RiVLIB `_ |
62 | | | RIWAVELIB_ROOT | `RiWaveLIB `_ |
63 | | | RDBLIB_ROOT | `RDBLib `_ |
64 | +---------------+-----------------------------+-----------------------------------------------------------------------------+
65 | | ASCII .gz | ZLIB_ROOT | `zlib `_ |
66 | +---------------+-----------------------------+-----------------------------------------------------------------------------+
67 | | PulseWaves | PULSEWAVES_ROOT | `pulsewaves `_ |
68 | +---------------+-----------------------------+-----------------------------------------------------------------------------+
69 |
70 | The related `pynninterp `_ module is used
71 | for some interpolation operations.
72 |
73 | Test Suite
74 | ^^^^^^^^^^
75 |
76 | After installation, run pylidar_test to run a number of tests to check that the install is OK. You will
77 | need the latest testdata_X.tar.gz file (with the highest 'X') from the links in the
78 | `wiki page `_. Pass the path to this file to
79 | pylidar_test with the -i option.
80 |
81 | Conda
82 | ^^^^^
83 |
84 | `Conda `_ packages are available under the 'rios' channel.
85 | Once you have installed `Conda `_, run the following commands on the
86 | command line to install pylidar (dependencies are obtained automatically): ::
87 |
88 | conda config --add channels conda-forge
89 | conda config --add channels rios
90 | conda create -n myenv pylidar
91 | conda activate myenv
92 |
93 | The related `pynninterp `_ module is used
94 | for some interpolation operations and can be installed via Conda also from the 'rios' channel::
95 |
96 | conda install pynninterp
97 |
98 |
99 | Processing
100 | -----------
101 |
102 | .. toctree::
103 | :maxdepth: 1
104 |
105 | userclasses
106 | lidarprocessor
107 | toolbox/arrayutils
108 | toolbox/toolbox
109 | toolbox/indexing
110 | toolbox/translate
111 |
112 | Drivers
113 | ---------
114 |
115 | .. toctree::
116 | :maxdepth: 1
117 |
118 | basedriver
119 | gdaldriver
120 | lidarformats/generic
121 | lidarformats/spdv3
122 | lidarformats/spdv4
123 | lidarformats/las
124 | lidarformats/riegl
125 | lidarformats/ascii
126 | lidarformats/lvisbin
127 | lidarformats/lvishdf5
128 | lidarformats/pulsewaves
129 | lidarformats/h5space
130 | lidarformats/gridindexutils
131 |
132 | Testing
133 | -------
134 | .. toctree::
135 | :maxdepth: 1
136 |
137 | testing
138 |
139 | Indices and tables
140 | ==================
141 |
142 | * :ref:`genindex`
143 | * :ref:`modindex`
144 | * :ref:`search`
145 |
146 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/ascii.rst:
--------------------------------------------------------------------------------
1 | ASCII
2 | =====
3 | .. automodule:: pylidar.lidarformats.ascii
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/generic.rst:
--------------------------------------------------------------------------------
1 | generic
2 | ==========
3 | .. automodule:: pylidar.lidarformats.generic
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/gridindexutils.rst:
--------------------------------------------------------------------------------
1 | gridindexutils
2 | ==============
3 | .. automodule:: pylidar.lidarformats.gridindexutils
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/h5space.rst:
--------------------------------------------------------------------------------
1 | h5space
2 | ==============
3 | .. automodule:: pylidar.lidarformats.h5space
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/las.rst:
--------------------------------------------------------------------------------
1 | las
2 | ==============
3 | .. automodule:: pylidar.lidarformats.las
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/lvisbin.rst:
--------------------------------------------------------------------------------
1 | LVIS Binary
2 | ===========
3 | .. automodule:: pylidar.lidarformats.lvisbin
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/lvishdf5.rst:
--------------------------------------------------------------------------------
1 | LVIS HDF5
2 | =========
3 | .. automodule:: pylidar.lidarformats.lvishdf5
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/pulsewaves.rst:
--------------------------------------------------------------------------------
1 | PulseWaves
2 | ==========
3 | .. automodule:: pylidar.lidarformats.pulsewaves
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/riegl.rst:
--------------------------------------------------------------------------------
1 | riegl
2 | ==============
3 | .. automodule:: pylidar.lidarformats.riegl
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/spdv3.rst:
--------------------------------------------------------------------------------
1 | spdv3
2 | ==============
3 | .. automodule:: pylidar.lidarformats.spdv3
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarformats/spdv4.rst:
--------------------------------------------------------------------------------
1 | spdv4
2 | ==============
3 | .. automodule:: pylidar.lidarformats.spdv4
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/lidarprocessor.rst:
--------------------------------------------------------------------------------
1 | LiDAR Processor
2 | ===============
3 |
4 | .. automodule:: pylidar.lidarprocessor
5 | :members:
6 | :undoc-members:
7 |
8 | * :ref:`genindex`
9 | * :ref:`modindex`
10 | * :ref:`search`
11 |
--------------------------------------------------------------------------------
/doc/source/point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/point.png
--------------------------------------------------------------------------------
/doc/source/pointsbypulse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/pointsbypulse.png
--------------------------------------------------------------------------------
/doc/source/pulse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/pulse.png
--------------------------------------------------------------------------------
/doc/source/pylidar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/pylidar.jpg
--------------------------------------------------------------------------------
/doc/source/testing.rst:
--------------------------------------------------------------------------------
1 | Testing
2 | ================
3 |
4 | .. automodule:: pylidar.testing
5 | :members:
6 | :undoc-members:
7 |
8 | Utility Functions
9 | ------------------------
10 |
11 | .. automodule:: pylidar.testing.utils
12 | :members:
13 | :undoc-members:
14 |
15 |
16 |
17 | * :ref:`genindex`
18 | * :ref:`modindex`
19 | * :ref:`search`
20 |
--------------------------------------------------------------------------------
/doc/source/threed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/threed.gif
--------------------------------------------------------------------------------
/doc/source/toolbox/arrayutils.rst:
--------------------------------------------------------------------------------
1 | Array Utilities
2 | ================
3 |
4 | .. automodule:: pylidar.toolbox.arrayutils
5 | :members:
6 | :undoc-members:
7 |
8 | * :ref:`genindex`
9 | * :ref:`modindex`
10 | * :ref:`search`
11 |
--------------------------------------------------------------------------------
/doc/source/toolbox/canopy.rst:
--------------------------------------------------------------------------------
1 | Canopy Products
2 | ================
3 |
4 | .. automodule:: pylidar.toolbox.canopy
5 | :members:
6 | :undoc-members:
7 |
8 |
9 | Vertical Foliage Profiles
10 | -------------------------
11 |
12 | .. automodule:: pylidar.toolbox.canopy.pavd_calders2014
13 | :members:
14 | :undoc-members:
15 |
16 |
17 | Voxel Traversal and Vertical Cover Profiles
18 | -------------------------------------------
19 |
20 | .. automodule:: pylidar.toolbox.canopy.voxel_hancock2016
21 | :members:
22 | :undoc-members:
23 |
24 |
25 |
26 | * :ref:`genindex`
27 | * :ref:`modindex`
28 | * :ref:`search`
29 |
--------------------------------------------------------------------------------
/doc/source/toolbox/grdfilters.rst:
--------------------------------------------------------------------------------
1 | Ground Filters
2 | ================
3 |
4 | .. automodule:: pylidar.toolbox.grdfilters
5 | :members:
6 | :undoc-members:
7 |
8 |
9 | Classify Ground Returns
10 | ------------------------
11 |
12 | .. automodule:: pylidar.toolbox.grdfilters.classGrdReturns
13 | :members:
14 | :undoc-members:
15 |
16 |
17 |
18 |
19 | Progressive Morphology Filter
20 | ------------------------------
21 |
22 | .. automodule:: pylidar.toolbox.grdfilters.pmf
23 | :members:
24 | :undoc-members:
25 |
26 |
27 |
28 | * :ref:`genindex`
29 | * :ref:`modindex`
30 | * :ref:`search`
31 |
--------------------------------------------------------------------------------
/doc/source/toolbox/indexing.rst:
--------------------------------------------------------------------------------
1 | Indexing
2 | ================
3 |
4 | .. automodule:: pylidar.toolbox.indexing
5 | :members:
6 | :undoc-members:
7 |
8 | Grid Indexing
9 | ------------------------
10 |
11 | .. automodule:: pylidar.toolbox.indexing.gridindex
12 | :members:
13 | :undoc-members:
14 |
15 |
16 |
17 | * :ref:`genindex`
18 | * :ref:`modindex`
19 | * :ref:`search`
20 |
--------------------------------------------------------------------------------
/doc/source/toolbox/interpolation.rst:
--------------------------------------------------------------------------------
1 | Interpolation
2 | ==============
3 |
4 | .. automodule:: pylidar.toolbox.interpolation
5 | :members:
6 | :undoc-members:
7 |
8 | * :ref:`genindex`
9 | * :ref:`modindex`
10 | * :ref:`search`
11 |
--------------------------------------------------------------------------------
/doc/source/toolbox/spatial.rst:
--------------------------------------------------------------------------------
1 | Spatial Processing Utilities
2 | ============================
3 |
4 | .. automodule:: pylidar.toolbox.spatial
5 | :members:
6 | :undoc-members:
7 |
8 | * :ref:`genindex`
9 | * :ref:`modindex`
10 | * :ref:`search`
11 |
--------------------------------------------------------------------------------
/doc/source/toolbox/toolbox.rst:
--------------------------------------------------------------------------------
1 | Toolbox
2 | ========
3 |
4 | .. automodule:: pylidar.toolbox
5 | :members:
6 | :undoc-members:
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | arrayutils
12 | grdfilters
13 | interpolation
14 | visualisation
15 | spatial
16 | canopy
17 |
18 | * :ref:`genindex`
19 | * :ref:`modindex`
20 | * :ref:`search`
21 |
--------------------------------------------------------------------------------
/doc/source/toolbox/translate.rst:
--------------------------------------------------------------------------------
1 | Translation
2 | ================
3 |
4 | .. automodule:: pylidar.toolbox.translate
5 | :members:
6 | :undoc-members:
7 |
8 | Command Line
9 | ------------------------
10 |
11 | .. automodule:: pylidar.toolbox.cmdline
12 | :members:
13 | :undoc-members:
14 |
15 | Common functions
16 | ------------------------
17 |
18 | .. automodule:: pylidar.toolbox.translate.translatecommon
19 | :members:
20 | :undoc-members:
21 |
22 | LAS to SPDV4
23 | ------------------------
24 |
25 | .. automodule:: pylidar.toolbox.translate.las2spdv4
26 | :members:
27 | :undoc-members:
28 |
29 | SPDV3 to SPDV4
30 | ------------------------
31 |
32 | .. automodule:: pylidar.toolbox.translate.spdv32spdv4
33 | :members:
34 | :undoc-members:
35 |
36 | Riegl to SPDV4
37 | ------------------------
38 |
39 | .. automodule:: pylidar.toolbox.translate.riegl2spdv4
40 | :members:
41 | :undoc-members:
42 |
43 | ASCII to SPDV4
44 | ------------------------
45 |
46 | .. automodule:: pylidar.toolbox.translate.ascii2spdv4
47 | :members:
48 | :undoc-members:
49 |
50 | SPDV4 to LAS
51 | ------------------------
52 |
53 | .. automodule:: pylidar.toolbox.translate.spdv42las
54 | :members:
55 | :undoc-members:
56 |
57 |
58 | * :ref:`genindex`
59 | * :ref:`modindex`
60 | * :ref:`search`
61 |
--------------------------------------------------------------------------------
/doc/source/toolbox/visualisation.rst:
--------------------------------------------------------------------------------
1 | Visualisation
2 | ==============
3 |
4 | .. automodule:: pylidar.toolbox.visualisation
5 | :members:
6 | :undoc-members:
7 |
8 | * :ref:`genindex`
9 | * :ref:`modindex`
10 | * :ref:`search`
11 |
--------------------------------------------------------------------------------
/doc/source/userclasses.rst:
--------------------------------------------------------------------------------
1 | userclasses
2 | ===========
3 | .. automodule:: pylidar.userclasses
4 | :members:
5 | :undoc-members:
6 |
7 | * :ref:`genindex`
8 | * :ref:`modindex`
9 | * :ref:`search`
10 |
--------------------------------------------------------------------------------
/doc/source/voxel_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ubarsc/pylidar/6f8a43d99596f01d7a876d64f0834774b077262c/doc/source/voxel_example.png
--------------------------------------------------------------------------------
/pylidar/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Main module. See the lidarprocessing sub module for the
4 | doProcessing() function and other useful things.
5 | """
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | PYLIDAR_VERSION = '0.4.4'
23 | __version__ = PYLIDAR_VERSION
24 |
--------------------------------------------------------------------------------
/pylidar/basedriver.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Generic 'driver' class. To be subclassed by both
4 | LiDAR and raster drivers.
5 |
6 | Also contains the Extent class which defines the extent
7 | to use for reading or writing the current block.
8 | """
9 | # This file is part of PyLidar
10 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
11 | #
12 | # This program is free software: you can redistribute it and/or modify
13 | # it under the terms of the GNU General Public License as published by
14 | # the Free Software Foundation, either version 3 of the License, or
15 | # (at your option) any later version.
16 | #
17 | # This program is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | # GNU General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU General Public License
23 | # along with this program. If not, see .
24 |
25 | from __future__ import print_function, division
26 |
27 | import abc
28 |
29 | READ = 0
30 | "access modes passed to driver constructor"
31 | UPDATE = 1
32 | "access modes passed to driver constructor"
33 | CREATE = 2
34 | "access modes passed to driver constructor"
35 |
36 | class Extent(object):
37 | """
38 | Class that defines the extent in world coords
39 | of an area to read or write
40 | """
41 | def __init__(self, xMin, xMax, yMin, yMax, binSize):
42 | self.xMin = xMin
43 | self.xMax = xMax
44 | self.yMin = yMin
45 | self.yMax = yMax
46 | self.binSize = binSize
47 |
48 | def __eq__(self, other):
49 | return (self.xMin == other.xMin and self.xMax == other.xMax and
50 | self.yMin == other.yMin and self.yMax == other.yMax and
51 | self.binSize == other.binSize)
52 |
53 | def __ne__(self, other):
54 | return (self.xMin != other.xMin or self.xMax != other.xMax or
55 | self.yMin != other.yMin or self.yMax != other.yMax or
56 | self.binSize != other.binSize)
57 |
58 | def __str__(self):
59 | s = "xMin:%s,xMax:%s,yMin:%s,yMax:%s,binSize:%s" % (repr(self.xMin),
60 | repr(self.xMax), repr(self.yMin), repr(self.yMax), repr(self.binSize))
61 | return s
62 |
63 |
64 | class Driver(object):
65 | """
66 | Base Driver object to be subclassed be both the LiDAR and raster drivers
67 | """
68 | __metaclass__ = abc.ABCMeta
69 |
70 | def __init__(self, fname, mode, controls, userClass):
71 | """
72 | fname is the file to open or create
73 | mode is READ, UPDATE or CREATE
74 | controls is an instance of lidarprocessing.Controls
75 | userClass is the instance of lidarprocessor.LidarFile or lidarprocessor.ImageFile
76 | used to define the file.
77 | """
78 | self.fname = fname
79 | self.mode = mode
80 | self.controls = controls
81 | self.userClass = userClass
82 |
83 | def setExtent(self, extent):
84 | """
85 | Set the extent for reading or writing
86 | """
87 | raise NotImplementedError()
88 |
89 | def getPixelGrid(self):
90 | """
91 | Return the PixelGridDefn for this file
92 | """
93 | raise NotImplementedError()
94 |
95 | def setPixelGrid(self, pixGrid):
96 | """
97 | Set the PixelGridDefn for the reading or
98 | writing we will do
99 | """
100 | raise NotImplementedError()
101 |
102 | @abc.abstractmethod
103 | def close(self):
104 | """
105 | Close all open file handles
106 | """
107 | raise NotImplementedError()
108 |
109 | class FileInfo(object):
110 | """
111 | Class that contains information about a file
112 | At this stage only subclassed by the lidar drivers
113 | """
114 | def __init__(self, fname):
115 | self.fname = fname
116 |
--------------------------------------------------------------------------------
/pylidar/lidarformats/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | lidarformats module. All the lidar format drivers live in this module.
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/testing/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Functions for performing automated testing on pylidar
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/testing/testall.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Runs all the available test suites
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import sys
25 | import shutil
26 | import argparse
27 | import importlib
28 |
29 | from . import utils
30 | # testsuite1 etc loaded dynamically below
31 | from pylidar import lidarprocessor
32 | from pylidar.toolbox import interpolation # so we can check we have pynninterp etc
33 |
34 | def getCmdargs():
35 | """
36 | Get commandline arguments
37 | """
38 | p = argparse.ArgumentParser()
39 | p.add_argument("-i", "--input", help="Input tar file name")
40 | p.add_argument("-p", "--path", default='.',
41 | help="Path to use. (default: %(default)s)")
42 | p.add_argument("-l", "--list", action="store_true", default=False,
43 | help="List tests in input, then exit")
44 | p.add_argument("-n", "--noremove", action="store_true", default=False,
45 | help="Do not clean up files on exit")
46 | p.add_argument("-t", "--test", action="append",
47 | help="Just run specified test. Can be given multiple times")
48 | p.add_argument("--ignore", action="append",
49 | help="Ignore a test. Can be given multiple times")
50 | p.add_argument("--noversioncheck", action="store_true", default=False,
51 | help="Don't do version check on supplied tar file")
52 | p.add_argument("--ignorefailures", action="store_true", default=False,
53 | help="Ignore failed tests and continue. Use with caution.")
54 |
55 | cmdargs = p.parse_args()
56 |
57 | if cmdargs.input is None:
58 | p.print_help()
59 | sys.exit()
60 |
61 | return cmdargs
62 |
63 | def run():
64 | cmdargs = getCmdargs()
65 |
66 | oldpath, newpath, tests = utils.extractTarFile(cmdargs.input, cmdargs.path,
67 | not cmdargs.noversioncheck)
68 |
69 | if cmdargs.list:
70 | for name in tests:
71 | print(name)
72 | if not cmdargs.noremove:
73 | shutil.rmtree(oldpath)
74 | shutil.rmtree(newpath)
75 | sys.exit()
76 |
77 | testsRun = 0
78 | testsIgnored = 0
79 | testsIgnoredNoDriver = 0
80 | testsFailed = 0
81 |
82 | # get current package name (needed for module importing below)
83 | # should be pylidar.testing (remove .testall)
84 | arr = __name__.split('.')
85 | package = '.'.join(arr[:-1])
86 |
87 | for name in tests:
88 |
89 | if cmdargs.test is not None and name not in cmdargs.test:
90 | continue
91 | if cmdargs.ignore is not None and name in cmdargs.ignore:
92 | testsIgnored += 1
93 | continue
94 |
95 | # import module - should we do something better if there
96 | # is an error? ie we don't have the specific test asked for?
97 | mod = importlib.import_module('.' + name, package=package)
98 |
99 | # Check we can actually run this test
100 | doTest = True
101 | if hasattr(mod, 'REQUIRED_FORMATS'):
102 | fmts = getattr(mod, 'REQUIRED_FORMATS')
103 | for fmt in fmts:
104 | if fmt == "LAS":
105 | if not lidarprocessor.HAVE_FMT_LAS:
106 | print('Skipping', name, 'due to missing format driver', fmt)
107 | doTest = False
108 | break
109 | elif fmt == "RIEGLRXP":
110 | if not lidarprocessor.HAVE_FMT_RIEGL_RXP:
111 | print('Skipping', name, 'due to missing format driver', fmt)
112 | doTest = False
113 | break
114 | elif fmt == "RIEGLRDB":
115 | if not lidarprocessor.HAVE_FMT_RIEGL_RDB:
116 | print('Skipping', name, 'due to missing format driver', fmt)
117 | doTest = False
118 | break
119 | elif fmt == "ASCIIGZ":
120 | if not lidarprocessor.HAVE_FMT_ASCII_ZLIB:
121 | print('Skipping', name, 'due to missing format driver', fmt)
122 | doTest = False
123 | break
124 | elif fmt == "PULSEWAVES":
125 | if not lidarprocessor.HAVE_FMT_PULSEWAVES:
126 | print('Skipping', name, 'due to missing format driver', fmt)
127 | doTest = False
128 | break
129 | elif fmt == "PYNNINTERP":
130 | if not interpolation.havePyNNInterp:
131 | print('Skipping', name, 'due to missing feature', fmt)
132 | doTest = False
133 | break
134 | elif fmt == "CGALINTERP":
135 | if not interpolation.haveCGALInterpPy:
136 | print('Skipping', name, 'due to missing feature', fmt)
137 | doTest = False
138 | break
139 | else:
140 | msg = 'Unknown required format/feature %s' % fmt
141 | raise ValueError(msg)
142 |
143 | if doTest:
144 | print('Running', name)
145 | if cmdargs.ignorefailures:
146 | try:
147 | mod.run(oldpath, newpath)
148 | except Exception:
149 | testsFailed += 1
150 |
151 | else:
152 | # just run it and let exception exit
153 | mod.run(oldpath, newpath)
154 | testsRun += 1
155 | else:
156 | testsIgnoredNoDriver += 1
157 |
158 | print(testsRun, 'tests run successfully')
159 | if testsIgnored != 0:
160 | print(testsIgnored, 'test(s) ignored as directed by user')
161 | if testsIgnoredNoDriver != 0:
162 | print(testsIgnoredNoDriver,
163 | 'test(s) skipped because of missing format drivers or features')
164 |
165 | if not cmdargs.noremove:
166 | shutil.rmtree(oldpath)
167 | shutil.rmtree(newpath)
168 |
169 | # return error code
170 | if testsFailed == 0:
171 | return 0
172 | else:
173 | return 1
174 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite1.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a LAS file and create a DEM
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.las2spdv4 import translate
28 | from pylidar.toolbox.indexing.gridindex import createGridSpatialIndex
29 | from pylidar.toolbox.rasterization import rasterize
30 |
31 | REQUIRED_FORMATS = ["LAS"]
32 |
33 | INPUT_LAS = 'apl1dr_x509000ys6945000z56_2009_ba1m6_pbrisba.las'
34 | IMPORTED_SPD = 'testsuite1.spd'
35 | INDEXED_SPD = 'testsuite1_idx.spd'
36 | OUTPUT_DEM = 'testsuite1.img'
37 |
38 | def run(oldpath, newpath):
39 | """
40 | Runs the first basic test suite. Tests:
41 |
42 | Importing LAS
43 | Creating spatial index
44 | creating a raster
45 | """
46 | inputLas = os.path.join(oldpath, INPUT_LAS)
47 | info = generic.getLidarFileInfo(inputLas)
48 |
49 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
50 | translate(info, inputLas, importedSPD, epsg=28356,
51 | pulseIndex='FIRST_RETURN', buildPulses=True)
52 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
53 |
54 | indexedSPD = os.path.join(newpath, INDEXED_SPD)
55 | createGridSpatialIndex(importedSPD, indexedSPD, binSize=2.0,
56 | tempDir=newpath)
57 | utils.compareLiDARFiles(os.path.join(oldpath, INDEXED_SPD), indexedSPD)
58 |
59 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
60 | rasterize([indexedSPD], outputDEM, ['Z'], function="numpy.ma.min",
61 | atype='POINT')
62 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
63 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite10.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a SPDV3 file and create a DEM
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.spdv32spdv4 import translate
28 | from pylidar.toolbox.indexing.gridindex import createGridSpatialIndex
29 | from pylidar.toolbox.rasterization import rasterize
30 |
31 | INPUT_SPDV3 = 'gpv1wf_14501655e03676013s_20120504_aa2f0_r06cd_p300khz_x14_sub.spdv3'
32 | IMPORTED_SPD = 'testsuite10.spd'
33 | INDEXED_SPD = 'testsuite10_idx.spd'
34 | OUTPUT_DEM = 'testsuite10.img'
35 |
36 | # override the default scaling
37 | # type, varname, gain, offset
38 | SCALINGS = [('PULSE', 'X_ORIGIN', 'DFLT', 1000, -1000),
39 | ('PULSE', 'Y_ORIGIN', 'DFLT', 1000, -1000),
40 | ('PULSE', 'Z_ORIGIN', 'DFLT', 1000, -1000),
41 | ('PULSE', 'X_IDX', 'DFLT', 1000, -1000),
42 | ('PULSE', 'Y_IDX', 'DFLT', 1000, -1000),
43 | ('POINT', 'WIDTH_RETURN', 'DFLT', 0.1, -1000.0),
44 | ('POINT', 'X', 'DFLT', 1000, -1000),
45 | ('POINT', 'Y', 'DFLT', 1000, -1000),
46 | ('POINT', 'Z', 'DFLT', 1000, -1000)]
47 |
48 | # because the files have lots of points, use a smaller
49 | # windowsize to prevent running out of memory
50 | WINDOWSIZE = 50
51 |
52 | def run(oldpath, newpath):
53 | """
54 | Runs the 10th basic test suite. Tests:
55 |
56 | Importing SPDV3
57 | Creating spatial index
58 | creating a raster
59 | """
60 | inputLas = os.path.join(oldpath, INPUT_SPDV3)
61 | info = generic.getLidarFileInfo(inputLas)
62 |
63 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
64 | translate(info, inputLas, importedSPD, scaling=SCALINGS)
65 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD,
66 | windowSize=WINDOWSIZE)
67 |
68 | indexedSPD = os.path.join(newpath, INDEXED_SPD)
69 | createGridSpatialIndex(importedSPD, indexedSPD, binSize=2.0,
70 | tempDir=newpath)
71 | utils.compareLiDARFiles(os.path.join(oldpath, INDEXED_SPD), indexedSPD,
72 | windowSize=WINDOWSIZE)
73 |
74 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
75 | rasterize([indexedSPD], outputDEM, ['Z'], function="numpy.ma.min",
76 | atype='POINT', windowSize=WINDOWSIZE)
77 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
78 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite11.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can update a column in an SPDV3 file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import shutil
26 | from . import utils
27 | from pylidar import lidarprocessor
28 | from rios import cuiprogress
29 |
30 | ORIG_FILE = 'gpv1wf_14501655e03676013s_20120504_aa2f0_r06cd_p300khz_x14_sub.spdv3'
31 | UPDATE_FILE = 'testsuite11.spd'
32 |
33 | def updatePointFunc(data):
34 |
35 | zVals = data.input1.getPoints(colNames='Z')
36 | zVals = zVals + 1
37 |
38 | # update Z
39 | data.input1.setPoints(zVals, colName='Z')
40 |
41 | def run(oldpath, newpath):
42 | """
43 | Runs the 11th basic test suite. Tests:
44 |
45 | Updating a column in an SPDV3 file
46 | """
47 | input = os.path.join(oldpath, ORIG_FILE)
48 | update = os.path.join(newpath, UPDATE_FILE)
49 | shutil.copyfile(input, update)
50 |
51 | dataFiles = lidarprocessor.DataFiles()
52 |
53 | dataFiles.input1 = lidarprocessor.LidarFile(update, lidarprocessor.UPDATE)
54 |
55 | controls = lidarprocessor.Controls()
56 | progress = cuiprogress.GDALProgressBar()
57 | controls.setProgress(progress)
58 | controls.setSpatialProcessing(False)
59 |
60 | lidarprocessor.doProcessing(updatePointFunc, dataFiles, controls=controls)
61 |
62 | origUpdate = os.path.join(oldpath, UPDATE_FILE)
63 | utils.compareLiDARFiles(origUpdate, update)
64 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite12.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we update an spd file using info
4 | in a raster
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import shutil
27 | import numpy
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from rios import cuiprogress
31 |
32 | ORIG_FILE = 'testsuite1_idx.spd'
33 | UPDATE_FILE = 'testsuite12.spd'
34 | IMAGE_FILE = 'testsuite1.img'
35 |
36 | def updatePointFunc(data):
37 | """
38 | Does the updating of an spd file with heights from an image
39 | """
40 | zVals = data.input.getPointsByBins(colNames='Z')
41 | (nPts, nRows, nCols) = zVals.shape
42 |
43 | if data.info.isFirstBlock():
44 | data.input.setScaling('HEIGHT', lidarprocessor.ARRAY_TYPE_POINTS,
45 | 100.0, 0.0)
46 |
47 | if nPts > 0:
48 | # read in the DEM data
49 | dem = data.imageIn.getData()
50 | # make it match the size of the pts array
51 | # ie repeat it for the number of bins
52 | dem = numpy.repeat(dem, zVals.shape[0], axis=0)
53 |
54 | # calculate the height
55 | # ensure this is a masked array to match pts
56 | height = numpy.ma.array(zVals - dem, mask=zVals.mask)
57 |
58 | # update the lidar file
59 | data.input.setPoints(height, colName='HEIGHT')
60 |
61 | def run(oldpath, newpath):
62 | """
63 | Runs the 12th basic test suite. Tests:
64 |
65 | Updating a spd file with information in a raster
66 | """
67 | updateFile = os.path.join(newpath, UPDATE_FILE)
68 | shutil.copyfile(os.path.join(oldpath, ORIG_FILE), updateFile)
69 |
70 | imageFile = os.path.join(oldpath, IMAGE_FILE)
71 |
72 | dataFiles = lidarprocessor.DataFiles()
73 | dataFiles.input = lidarprocessor.LidarFile(updateFile, lidarprocessor.UPDATE)
74 | dataFiles.imageIn = lidarprocessor.ImageFile(imageFile, lidarprocessor.READ)
75 |
76 | controls = lidarprocessor.Controls()
77 | progress = cuiprogress.GDALProgressBar()
78 | controls.setProgress(progress)
79 | controls.setSpatialProcessing(True)
80 |
81 | lidarprocessor.doProcessing(updatePointFunc, dataFiles, controls=controls)
82 | utils.compareLiDARFiles(os.path.join(oldpath, UPDATE_FILE), updateFile)
83 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite13.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a time sequential
4 | ascii file
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | from . import utils
27 | from pylidar.lidarformats import generic
28 | from pylidar.toolbox.translate.ascii2spdv4 import translate
29 |
30 | REQUIRED_FORMATS = ["ASCIIGZ"]
31 |
32 | INPUT_ASCII = '0820Q5_142ew48c_sub.dat.gz'
33 | IMPORTED_SPD = 'testsuite13.spd'
34 |
35 | COLTYPES = [('GPS_TIME', 'FLOAT64'), ('X_IDX', 'FLOAT64'), ('Y_IDX', 'FLOAT64'),
36 | ('Z_IDX', 'FLOAT64'), ('X', 'FLOAT64'), ('Y', 'FLOAT64'), ('Z', 'FLOAT64'),
37 | ('CLASSIFICATION', 'UINT8'), ('ORIG_RETURN_NUMBER', 'UINT8'),
38 | ('ORIG_NUMBER_OF_RETURNS', 'UINT8'), ('AMPLITUDE', 'FLOAT64'),
39 | ('FWHM', 'FLOAT64'), ('RANGE', 'FLOAT64')]
40 | PULSE_COLS = ['GPS_TIME', 'X_IDX', 'Y_IDX', 'Z_IDX']
41 |
42 | def run(oldpath, newpath):
43 | """
44 | Runs the 13th basic test suite. Tests:
45 |
46 | Importing time sequential ascii
47 | """
48 | inputASCII = os.path.join(oldpath, INPUT_ASCII)
49 | info = generic.getLidarFileInfo(inputASCII)
50 |
51 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
52 | translate(info, inputASCII, importedSPD, COLTYPES, PULSE_COLS)
53 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
54 |
55 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite14.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a simple ascii file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.ascii2spdv4 import translate
28 |
29 | INPUT_ASCII = 'first_1402.grd.dat'
30 | IMPORTED_SPD = 'testsuite14.spd'
31 |
32 | COLTYPES = [('X', 'FLOAT64'), ('Y', 'FLOAT64'), ('Z', 'FLOAT64'),
33 | ('ORIG_RETURN_NUMBER', 'UINT64')]
34 | CONST_COLS = [('POINT', 'CLASSIFICATION', 'UINT8',
35 | generic.CLASSIFICATION_GROUND)]
36 |
37 | def run(oldpath, newpath):
38 | """
39 | Runs the 14th basic test suite. Tests:
40 |
41 | Importing time sequential ascii
42 | """
43 | inputASCII = os.path.join(oldpath, INPUT_ASCII)
44 | info = generic.getLidarFileInfo(inputASCII)
45 |
46 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
47 | translate(info, inputASCII, importedSPD, COLTYPES, constCols=CONST_COLS)
48 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
49 |
50 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite15.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can process a SPD file into an image
4 | using the spatial toolbox in non-spatial mode
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from numba import jit
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from pylidar.lidarformats import generic
31 | from pylidar.toolbox import spatial
32 |
33 | INPUT_SPD = 'testsuite1.spd'
34 | OUTPUT_DEM = 'testsuite15.img'
35 |
36 | BINSIZE = 1.0
37 |
38 | @jit
39 | def findMinZs(data, outImage, xMin, yMax):
40 | for i in range(data.shape[0]):
41 | if data[i]['CLASSIFICATION'] == lidarprocessor.CLASSIFICATION_GROUND:
42 | row, col = spatial.xyToRowColNumba(data[i]['X'], data[i]['Y'],
43 | xMin, yMax, BINSIZE)
44 | if outImage[row, col] != 0:
45 | if data[i]['Z'] < outImage[row, col]:
46 | outImage[row, col] = data[i]['Z']
47 | else:
48 | outImage[row, col] = data[i]['Z']
49 |
50 | def processChunk(data, otherArgs):
51 | lidar = data.input1.getPoints(colNames=['X', 'Y', 'Z', 'CLASSIFICATION'])
52 | findMinZs(lidar, otherArgs.outImage, otherArgs.xMin, otherArgs.yMax)
53 |
54 | def run(oldpath, newpath):
55 | """
56 | Runs the 15th basic test suite. Tests:
57 |
58 | creating a raster using the 'new' non-spatial mode
59 | """
60 | inputSPD = os.path.join(oldpath, INPUT_SPD)
61 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
62 |
63 | info = generic.getLidarFileInfo(inputSPD)
64 | header = info.header
65 |
66 | dataFiles = lidarprocessor.DataFiles()
67 | dataFiles.input1 = lidarprocessor.LidarFile(inputSPD, lidarprocessor.READ)
68 |
69 | xMin, yMax, ncols, nrows = spatial.getGridInfoFromHeader(header, BINSIZE)
70 |
71 | outImage = numpy.zeros((nrows, ncols))
72 |
73 | otherArgs = lidarprocessor.OtherArgs()
74 | otherArgs.outImage = outImage
75 | otherArgs.xMin = xMin
76 | otherArgs.yMax = yMax
77 |
78 | controls = lidarprocessor.Controls()
79 | controls.setSpatialProcessing(False)
80 |
81 | lidarprocessor.doProcessing(processChunk, dataFiles, otherArgs=otherArgs, controls=controls)
82 |
83 | iw = spatial.ImageWriter(outputDEM, tlx=xMin, tly=yMax, binSize=BINSIZE)
84 | iw.setLayer(outImage)
85 | iw.close()
86 |
87 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
88 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite16.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can do an interpolation in non
4 | spatial mode using the spatial toolbox
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from . import utils
28 | from pylidar import lidarprocessor
29 | from pylidar.toolbox import spatial
30 | from pylidar.toolbox import interpolation
31 |
32 | INPUT_SPD = 'testsuite1.spd'
33 | OUTPUT_DEM = 'testsuite16.img'
34 |
35 | BINSIZE = 1.0
36 |
37 | REQUIRED_FORMATS = ["PYNNINTERP"]
38 |
39 | def run(oldpath, newpath):
40 | """
41 | Runs the 16th basic test suite. Tests:
42 |
43 | creating a raster with interpolation using the 'new' non-spatial mode
44 | """
45 | inputSPD = os.path.join(oldpath, INPUT_SPD)
46 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
47 |
48 | data = spatial.readLidarPoints(inputSPD,
49 | classification=lidarprocessor.CLASSIFICATION_GROUND)
50 |
51 | (xMin, yMax, ncols, nrows) = spatial.getGridInfoFromData(data['X'], data['Y'],
52 | BINSIZE)
53 |
54 | pxlCoords = spatial.getBlockCoordArrays(xMin, yMax, ncols, nrows, BINSIZE)
55 |
56 | dem = interpolation.interpGrid(data['X'], data['Y'], data['Z'], pxlCoords,
57 | method='pynn')
58 |
59 | iw = spatial.ImageWriter(outputDEM, tlx=xMin, tly=yMax, binSize=BINSIZE)
60 | iw.setLayer(dem)
61 | iw.close()
62 |
63 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
64 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite16b.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can do an interpolation in non
4 | spatial mode using the spatial toolbox
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from . import utils
28 | from pylidar import lidarprocessor
29 | from pylidar.toolbox import spatial
30 | from pylidar.toolbox import interpolation
31 |
32 | INPUT_SPD = 'testsuite1.spd'
33 | OUTPUT_DEM = 'testsuite16b.img'
34 |
35 | BINSIZE = 1.0
36 |
37 | REQUIRED_FORMATS = ["CGALINTERP"]
38 |
39 | def run(oldpath, newpath):
40 | """
41 | Runs the 16th basic test suite. Tests:
42 |
43 | creating a raster with interpolation using the 'new' non-spatial mode
44 | """
45 | inputSPD = os.path.join(oldpath, INPUT_SPD)
46 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
47 |
48 | data = spatial.readLidarPoints(inputSPD,
49 | classification=lidarprocessor.CLASSIFICATION_GROUND)
50 |
51 | (xMin, yMax, ncols, nrows) = spatial.getGridInfoFromData(data['X'], data['Y'],
52 | BINSIZE)
53 |
54 | pxlCoords = spatial.getBlockCoordArrays(xMin, yMax, ncols, nrows, BINSIZE)
55 |
56 | dem = interpolation.interpGrid(data['X'], data['Y'], data['Z'], pxlCoords,
57 | method='cgalnn')
58 |
59 | iw = spatial.ImageWriter(outputDEM, tlx=xMin, tly=yMax, binSize=BINSIZE)
60 | iw.setLayer(dem)
61 | iw.close()
62 |
63 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
64 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite17.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can update a lidar file in non
4 | spatial mode using the spatial toolbox
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import shutil
27 | import numpy
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from pylidar.toolbox import spatial
31 | from pylidar.toolbox import arrayutils
32 |
33 | INPUT_SPD = 'testsuite1.spd'
34 | INPUT_DEM = 'testsuite16.img'
35 | UPDATE_SPD = 'testsuite17.spd'
36 |
37 | def processChunk(data, otherArgs):
38 | lidar = data.input1.getPoints(colNames=['X', 'Y', 'Z'])
39 | rows, cols = spatial.xyToRowCol(lidar['X'], lidar['Y'],
40 | otherArgs.xMin, otherArgs.yMax, otherArgs.binSize)
41 |
42 | height = lidar['Z'] - otherArgs.inImage[rows, cols]
43 | lidar = arrayutils.addFieldToStructArray(lidar, 'HEIGHT', numpy.float, height)
44 | data.input1.setScaling('HEIGHT', lidarprocessor.ARRAY_TYPE_POINTS, 10, -10)
45 | data.input1.setPoints(lidar)
46 |
47 | def run(oldpath, newpath):
48 | """
49 | Runs the 17th basic test suite. Tests:
50 |
51 | update a spd file using an image using the non spatial mode
52 | """
53 | inputSPD = os.path.join(oldpath, INPUT_SPD)
54 | updateSPD = os.path.join(newpath, UPDATE_SPD)
55 | shutil.copyfile(inputSPD, updateSPD)
56 |
57 | inputDEM = os.path.join(oldpath, INPUT_DEM)
58 |
59 | dataFiles = lidarprocessor.DataFiles()
60 | dataFiles.input1 = lidarprocessor.LidarFile(updateSPD, lidarprocessor.UPDATE)
61 |
62 | otherArgs = lidarprocessor.OtherArgs()
63 | (otherArgs.inImage, otherArgs.xMin, otherArgs.yMax, otherArgs.binSize) = spatial.readImageLayer(inputDEM)
64 |
65 | controls = lidarprocessor.Controls()
66 | controls.setSpatialProcessing(False)
67 |
68 | lidarprocessor.doProcessing(processChunk, dataFiles, otherArgs=otherArgs, controls=controls)
69 |
70 | origUpdate = os.path.join(oldpath, UPDATE_SPD)
71 | utils.compareLiDARFiles(origUpdate, updateSPD)
72 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite18.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a PulseWaves file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.pulsewaves2spdv4 import translate
28 |
29 | REQUIRED_FORMATS = ["PULSEWAVES"]
30 |
31 | INPUT_PULSEWAVES = 'test.pls' # from the PulseWaves github repo
32 | IMPORTED_SPD = 'testsuite18.spd'
33 |
34 | def run(oldpath, newpath):
35 | """
36 | Runs the 18th basic test suite. Tests:
37 |
38 | Importing PulseWaves
39 | """
40 | inputPW = os.path.join(oldpath, INPUT_PULSEWAVES)
41 | info = generic.getLidarFileInfo(inputPW)
42 |
43 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
44 | translate(info, inputPW, importedSPD)
45 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
46 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite19.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can export to a PulseWaves file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import subprocess
26 | from . import utils
27 | from pylidar.lidarformats import generic
28 | from pylidar.toolbox.translate.spdv42pulsewaves import translate
29 |
30 | REQUIRED_FORMATS = ["PULSEWAVES"]
31 |
32 | IMPORTED_SPD = 'testsuite8.spd' # came from .rxp originally
33 | EXPORTED_PULSEWAVES = 'testsuite19.pls'
34 |
35 | def run(oldpath, newpath):
36 | """
37 | Runs the 19th basic test suite. Tests:
38 |
39 | Exporting to PulseWAVES
40 | """
41 | inputSPD = os.path.join(oldpath, IMPORTED_SPD)
42 | info = generic.getLidarFileInfo(inputSPD)
43 |
44 | exportedPW = os.path.join(newpath, EXPORTED_PULSEWAVES)
45 | translate(info, inputSPD, exportedPW)
46 |
47 | # no pulsediff that we can use to compare. Simply
48 | # see if file identifies as pulsewaves. Probably can do better.
49 | info = generic.getLidarFileInfo(exportedPW)
50 | if info.getDriverName() != 'PulseWaves':
51 | msg = 'output file was not correctly identified as PulseWaves'
52 | raise utils.TestingDataMismatch(msg)
53 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite2.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can create a DEM from 2 files.
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar import lidarprocessor
27 | from pylidar.lidarformats import generic
28 | from pylidar.toolbox.translate.las2spdv4 import translate
29 | from pylidar.toolbox.indexing.gridindex import createGridSpatialIndex
30 | from pylidar.toolbox.rasterization import rasterize
31 |
32 | REQUIRED_FORMATS = ["LAS"]
33 |
34 | INPUT2_LAS = 'apl1dr_x510000ys6945000z56_2009_ba1m6_pbrisba.las'
35 | IMPORTED_SPD = 'testsuite2.spd'
36 | INDEXED_SPD_1 = 'testsuite1_idx.spd'
37 | INDEXED_SPD_2 = 'testsuite2_idx.spd'
38 | OUTPUT_DEM = 'testsuite2.img'
39 |
40 | def run(oldpath, newpath):
41 | """
42 | Runs the first basic test suite. Tests:
43 |
44 | Importing
45 | Creating spatial index
46 | creating a raster from 2 files at a different resolution
47 | """
48 | inputLas = os.path.join(oldpath, INPUT2_LAS)
49 | info = generic.getLidarFileInfo(inputLas)
50 |
51 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
52 | translate(info, inputLas, importedSPD, epsg=28356,
53 | pulseIndex='FIRST_RETURN', buildPulses=True)
54 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
55 |
56 | indexedSPD1 = os.path.join(oldpath, INDEXED_SPD_1)
57 | indexedSPD2 = os.path.join(newpath, INDEXED_SPD_2)
58 | createGridSpatialIndex(importedSPD, indexedSPD2, binSize=2.0,
59 | tempDir=newpath)
60 | utils.compareLiDARFiles(os.path.join(oldpath, INDEXED_SPD_2), indexedSPD2)
61 |
62 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
63 | rasterize([indexedSPD1, indexedSPD2], outputDEM, ['Z'], binSize=3.0,
64 | function="numpy.ma.min", atype='POINT', footprint=lidarprocessor.UNION)
65 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
66 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite21.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a LVIS Binary file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.lvisbin2spdv4 import translate
28 |
29 | # The input files have version 1.02 of the structures the sizes are in bytes:
30 | # lce=36 lge=52 lgw=492
31 | # these sizes have been used to truncate the input files down to
32 | # a reasonable size
33 | # also had to clobber the tlon and tlat values in the lce to make sensible...
34 |
35 | INPUT_LVIS = 'testsuite21.lce'
36 | IMPORTED_SPD = 'testsuite21.spd'
37 |
38 | def run(oldpath, newpath):
39 | """
40 | Runs the 21st basic test suite. Tests:
41 |
42 | Importing LVIS Binary
43 | """
44 | inputLVIS = os.path.join(oldpath, INPUT_LVIS)
45 | info = generic.getLidarFileInfo(inputLVIS)
46 |
47 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
48 | translate(info, inputLVIS, importedSPD)
49 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
50 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite22.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a HDF5 LVIS file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | from . import utils
26 | from pylidar.lidarformats import generic
27 | from pylidar.toolbox.translate.pulsewaves2spdv4 import translate
28 |
29 | INPUT_LVIS = 'testsuite22.h5'
30 | IMPORTED_SPD = 'testsuite22.spd'
31 |
32 | # Coords are lat/long, southern hemisphere
33 | SCALING = [('POINT', 'Y', 'DFLT', 100.0, -100.0)]
34 |
35 | def run(oldpath, newpath):
36 | """
37 | Runs the 22nd basic test suite. Tests:
38 |
39 | Importing LVIS HDF5
40 | """
41 | inputLVIS = os.path.join(oldpath, INPUT_LVIS)
42 | info = generic.getLidarFileInfo(inputLVIS)
43 |
44 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
45 | translate(info, inputLVIS, importedSPD, scaling=SCALING)
46 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
47 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite23.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks the toolbox.interpolation.interpPoints function.
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import numpy
26 | from . import utils
27 | from pylidar import lidarprocessor
28 | from pylidar.toolbox.interpolation import interpPoints
29 |
30 | INPUT_SPD = 'testsuite1.spd'
31 | OUTPUT_DATA = 'testsuite23.npy'
32 |
33 | REQUIRED_FORMATS = ["PYNNINTERP"]
34 |
35 | def processChunk(data, otherArgs):
36 | data = data.input1.getPoints(colNames=['X', 'Y', 'Z', 'CLASSIFICATION'])
37 |
38 | groundMask = data['CLASSIFICATION'] == lidarprocessor.CLASSIFICATION_GROUND
39 | groundPoints = data[groundMask]
40 |
41 | nonGroundMask = ~groundMask
42 | nonGroundPoints = numpy.empty((numpy.count_nonzero(nonGroundMask), 2),
43 | numpy.double)
44 | nonGroundPoints[..., 0] = data['X'][nonGroundMask]
45 | nonGroundPoints[..., 1] = data['Y'][nonGroundMask]
46 |
47 | interped = interpPoints(groundPoints['X'], groundPoints['Y'],
48 | groundPoints['Z'], nonGroundPoints, method='pynn')
49 |
50 | if otherArgs.output is None:
51 | otherArgs.output = interped
52 | else:
53 | otherArgs.output = numpy.append(otherArgs.output, interped)
54 |
55 | def run(oldpath, newpath):
56 | """
57 | Runs the 23nd basic test suite. Tests:
58 |
59 | toolbox.interpolation.interpPoints
60 | """
61 | inputSPD = os.path.join(oldpath, INPUT_SPD)
62 | outputDAT = os.path.join(newpath, OUTPUT_DATA)
63 |
64 | dataFiles = lidarprocessor.DataFiles()
65 | dataFiles.input1 = lidarprocessor.LidarFile(inputSPD, lidarprocessor.READ)
66 |
67 | otherArgs = lidarprocessor.OtherArgs()
68 | otherArgs.output = None
69 |
70 | lidarprocessor.doProcessing(processChunk, dataFiles, otherArgs=otherArgs)
71 |
72 | numpy.save(outputDAT, otherArgs.output)
73 |
74 | utils.compareNumpyFiles(os.path.join(oldpath, OUTPUT_DATA), outputDAT)
75 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite23b.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks the toolbox.interpolation.interpPoints function.
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import numpy
26 | from . import utils
27 | from pylidar import lidarprocessor
28 | from pylidar.toolbox.interpolation import interpPoints
29 |
30 | INPUT_SPD = 'testsuite1.spd'
31 | OUTPUT_DATA = 'testsuite23b.npy'
32 |
33 | REQUIRED_FORMATS = ["CGALINTERP"]
34 |
35 | def processChunk(data, otherArgs):
36 | data = data.input1.getPoints(colNames=['X', 'Y', 'Z', 'CLASSIFICATION'])
37 |
38 | groundMask = data['CLASSIFICATION'] == lidarprocessor.CLASSIFICATION_GROUND
39 | groundPoints = data[groundMask]
40 |
41 | nonGroundMask = ~groundMask
42 | nonGroundPoints = numpy.empty((numpy.count_nonzero(nonGroundMask), 2),
43 | numpy.double)
44 | nonGroundPoints[..., 0] = data['X'][nonGroundMask]
45 | nonGroundPoints[..., 1] = data['Y'][nonGroundMask]
46 |
47 | interped = interpPoints(groundPoints['X'], groundPoints['Y'],
48 | groundPoints['Z'], nonGroundPoints, method='cgalnn')
49 |
50 | if otherArgs.output is None:
51 | otherArgs.output = interped
52 | else:
53 | otherArgs.output = numpy.append(otherArgs.output, interped)
54 |
55 | def run(oldpath, newpath):
56 | """
57 | Runs the 23nd basic test suite. Tests:
58 |
59 | toolbox.interpolation.interpPoints
60 | """
61 | inputSPD = os.path.join(oldpath, INPUT_SPD)
62 | outputDAT = os.path.join(newpath, OUTPUT_DATA)
63 |
64 | dataFiles = lidarprocessor.DataFiles()
65 | dataFiles.input1 = lidarprocessor.LidarFile(inputSPD, lidarprocessor.READ)
66 |
67 | otherArgs = lidarprocessor.OtherArgs()
68 | otherArgs.output = None
69 |
70 | lidarprocessor.doProcessing(processChunk, dataFiles, otherArgs=otherArgs)
71 |
72 | numpy.save(outputDAT, otherArgs.output)
73 |
74 | utils.compareNumpyFiles(os.path.join(oldpath, OUTPUT_DATA), outputDAT)
75 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite24.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a Riegl RDB file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import numpy
26 | import shutil
27 | from . import utils
28 | from pylidar import lidarprocessor
29 | from pylidar.lidarformats import generic
30 | from pylidar.toolbox.translate.rieglrdb2spdv4 import translate
31 | from rios import cuiprogress
32 |
33 | REQUIRED_FORMATS = ["RIEGLRDB"]
34 |
35 | INPUT_RIEGL = 'testsuite24.rdbx'
36 | IMPORTED_SPD = 'testsuite24.spd'
37 |
38 | # override the default scaling
39 | # type, varname, gain, offset
40 | SCALINGS = [('PULSE', 'X_ORIGIN', 'DFLT', 1000, -1000),
41 | ('PULSE', 'Y_ORIGIN', 'DFLT', 1000, -1000),
42 | ('PULSE', 'Z_ORIGIN', 'DFLT', 1000, -1000),
43 | ('PULSE', 'X_IDX', 'DFLT', 1000, -1000),
44 | ('PULSE', 'Y_IDX', 'DFLT', 1000, -1000),
45 | ('POINT', 'X', 'DFLT', 1000, -1000),
46 | ('POINT', 'Y', 'DFLT', 1000, -1000),
47 | ('POINT', 'Z', 'DFLT', 1000, -1000),
48 | ('POINT', 'RHO_APP', 'DFLT', 1000, -1000)]
49 |
50 | # because the files have lots of points, use a smaller
51 | # windowsize to prevent running out of memory
52 | WINDOWSIZE = 50
53 |
54 | def run(oldpath, newpath):
55 | """
56 | Runs the 24th basic test suite. Tests:
57 |
58 | Importing Riegl RDB
59 | """
60 | inputRiegl = os.path.join(oldpath, INPUT_RIEGL)
61 | info = generic.getLidarFileInfo(inputRiegl)
62 |
63 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
64 | translate(info, inputRiegl, importedSPD, scalings=SCALINGS)
65 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD,
66 | windowSize=WINDOWSIZE)
67 |
68 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite3.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite imports a spatially indexed LAS file and
4 | creates a DEM.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | from . import utils
27 | from pylidar.lidarformats import generic
28 | from pylidar.toolbox.translate.las2spdv4 import translate
29 | from pylidar.toolbox.rasterization import rasterize
30 |
31 | REQUIRED_FORMATS = ["LAS"]
32 |
33 | INPUT_LAZ = 'apl1dr_x509000ys6945000z56_2009_ba1m6_pbrisba_zip.laz'
34 | IMPORTED_SPD = 'testsuite3.spd'
35 | OUTPUT_DEM = 'testsuite3.img'
36 |
37 | def run(oldpath, newpath):
38 | """
39 | Runs the 3rd basic test suite. Tests:
40 |
41 | Reading a spatially indexed las file
42 | Creating a DEM
43 | """
44 | inputLas = os.path.join(oldpath, INPUT_LAZ)
45 | info = generic.getLidarFileInfo(inputLas)
46 |
47 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
48 | translate(info, inputLas, importedSPD, epsg=28356,
49 | pulseIndex='FIRST_RETURN', spatial=True, binSize=2.0,
50 | buildPulses=True)
51 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD)
52 |
53 | outputDEM = os.path.join(newpath, OUTPUT_DEM)
54 | rasterize([importedSPD], outputDEM, ['Z'], function="numpy.ma.min",
55 | atype='POINT')
56 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_DEM), outputDEM)
57 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite4.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can update a column and add
4 | a column to a SPDV4 file.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import shutil
27 | from . import utils
28 | from pylidar import lidarprocessor
29 | from rios import cuiprogress
30 |
31 | ORIG_FILE = 'testsuite1_idx.spd'
32 | UPDATE_FILE = 'testsuite4_idx.spd'
33 |
34 | def updatePointFunc(data):
35 |
36 | zVals = data.input1.getPointsByBins(colNames='Z')
37 | zVals = zVals + 1
38 |
39 | zBy2 = zVals / 2.0
40 |
41 | # update Z
42 | data.input1.setPoints(zVals, colName='Z')
43 | # create new column ZBY2
44 | data.input1.setPoints(zBy2, colName='ZBY2')
45 |
46 | def run(oldpath, newpath):
47 | """
48 | Runs the 4th basic test suite. Tests:
49 |
50 | Updating a column in a file
51 | Creating a new column
52 | """
53 | input = os.path.join(oldpath, ORIG_FILE)
54 | update = os.path.join(newpath, UPDATE_FILE)
55 | shutil.copyfile(input, update)
56 |
57 | dataFiles = lidarprocessor.DataFiles()
58 |
59 | dataFiles.input1 = lidarprocessor.LidarFile(update, lidarprocessor.UPDATE)
60 |
61 | controls = lidarprocessor.Controls()
62 | progress = cuiprogress.GDALProgressBar()
63 | controls.setProgress(progress)
64 | controls.setSpatialProcessing(True)
65 |
66 | lidarprocessor.doProcessing(updatePointFunc, dataFiles, controls=controls)
67 |
68 | origUpdate = os.path.join(oldpath, UPDATE_FILE)
69 | utils.compareLiDARFiles(origUpdate, update)
70 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite5.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can export to a LAS file
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import os
25 | import subprocess
26 | from . import utils
27 | from pylidar.lidarformats import generic
28 | from pylidar.toolbox.translate.spdv42las import translate
29 |
30 | REQUIRED_FORMATS = ["LAS"]
31 |
32 | ORIG_LAS = 'apl1dr_x509000ys6945000z56_2009_ba1m6_pbrisba.las'
33 | IMPORTED_SPD = 'testsuite1.spd'
34 | EXPORTED_LAS = 'testsuite5.las'
35 |
36 | def run(oldpath, newpath):
37 | """
38 | Runs the 5th basic test suite. Tests:
39 |
40 | Exporting to LAS
41 | """
42 | inputSPD = os.path.join(oldpath, IMPORTED_SPD)
43 | info = generic.getLidarFileInfo(inputSPD)
44 |
45 | exportedLAS = os.path.join(newpath, EXPORTED_LAS)
46 | translate(info, inputSPD, exportedLAS, spatial=False)
47 |
48 | # now run lasdiff
49 | # maybe need to do better with parsing output
50 | # not sure if this is valid comparisons with rounding errors etc
51 | # I've changed this from comparing against ORIG_LAS as there were
52 | # too many spurious errors. Compare to known good version instead.
53 | # Hope this ok.
54 | origLAS = os.path.join(oldpath, EXPORTED_LAS)
55 | result = subprocess.check_output(['lasdiff', '-i', exportedLAS, origLAS],
56 | stderr=subprocess.STDOUT)
57 |
58 | nLines = len(result.split(b'\n'))
59 | if nLines > 7:
60 | print(result)
61 | msg = ('Seems to be more output from lasdiff than expected, ' +
62 | 'likely to be a difference in file')
63 | raise utils.TestingDataMismatch(msg)
64 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite6.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can do interpolation, set overlap
4 | and pixelgrid.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from osgeo import gdal
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from pylidar.toolbox import interpolation
31 | from rios import cuiprogress
32 | from rios import pixelgrid
33 |
34 | IN_FILE = 'testsuite1_idx.spd'
35 | INTERP_FILE = 'testsuite6.img'
36 | PROJECTION_SOURCE = 'testsuite1.img'
37 |
38 | REQUIRED_FORMATS = ["PYNNINTERP"]
39 |
40 | def interpGroundReturns(data):
41 | # if given a list of fields, returns a structured array with all of them
42 | ptVals = data.input.getPoints(colNames=['X', 'Y', 'Z', 'CLASSIFICATION'])
43 | # create mask for ground
44 | mask = ptVals['CLASSIFICATION'] == lidarprocessor.CLASSIFICATION_GROUND
45 |
46 | # get the coords for this block
47 | pxlCoords = data.info.getBlockCoordArrays()
48 |
49 | if ptVals.shape[0] > 0:
50 | # there is data for this block
51 | xVals = ptVals['X'][mask]
52 | yVals = ptVals['Y'][mask]
53 | zVals = ptVals['Z'][mask]
54 | # 'pynn' needs the pynnterp module installed
55 | out = interpolation.interpGrid(xVals, yVals, zVals, pxlCoords,
56 | method='pynn')
57 |
58 | # mask out where interpolation failed
59 | invalid = numpy.isnan(out)
60 | out[invalid] = 0
61 | else:
62 | # no data - set to zero
63 | out = numpy.empty(pxlCoords[0].shape, dtype=numpy.float64)
64 | out.fill(0)
65 |
66 | out = numpy.expand_dims(out, axis=0)
67 | data.imageOut.setData(out)
68 |
69 | def getProjection(imageFile):
70 | """
71 | Returns the projection of an image as a WKT
72 | """
73 | ds = gdal.Open(imageFile)
74 | return ds.GetProjection()
75 |
76 | def run(oldpath, newpath):
77 | """
78 | Runs the 6th basic test suite. Tests:
79 |
80 | Interpolation
81 | overlap
82 | setting pixel grid
83 | """
84 | input = os.path.join(oldpath, IN_FILE)
85 | interp = os.path.join(newpath, INTERP_FILE)
86 | origInterp = os.path.join(oldpath, INTERP_FILE)
87 |
88 | dataFiles = lidarprocessor.DataFiles()
89 | dataFiles.input = lidarprocessor.LidarFile(input, lidarprocessor.READ)
90 | dataFiles.imageOut = lidarprocessor.ImageFile(interp, lidarprocessor.CREATE)
91 |
92 | controls = lidarprocessor.Controls()
93 | progress = cuiprogress.GDALProgressBar()
94 | controls.setProgress(progress)
95 | controls.setOverlap(2)
96 |
97 | # can't use origInterp as projection source as this might not
98 | # be created yet (eg called from testing_cmds.sh)
99 | projectionSource = os.path.join(oldpath, PROJECTION_SOURCE)
100 | wkt = getProjection(projectionSource)
101 | pixGrid = pixelgrid.PixelGridDefn(xMin=509198.0, yMax=6944830, xMax=509856,
102 | yMin=6944130, xRes=2.0, yRes=2.0, projection=wkt)
103 | controls.setFootprint(lidarprocessor.BOUNDS_FROM_REFERENCE)
104 | controls.setReferencePixgrid(pixGrid)
105 | controls.setSpatialProcessing(True)
106 |
107 | lidarprocessor.doProcessing(interpGroundReturns, dataFiles, controls=controls)
108 |
109 | utils.compareImageFiles(origInterp, interp)
110 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite6b.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can do interpolation, set overlap
4 | and pixelgrid.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from osgeo import gdal
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from pylidar.toolbox import interpolation
31 | from rios import cuiprogress
32 | from rios import pixelgrid
33 |
34 | IN_FILE = 'testsuite1_idx.spd'
35 | INTERP_FILE = 'testsuite6b.img'
36 | PROJECTION_SOURCE = 'testsuite1.img'
37 |
38 | REQUIRED_FORMATS = ["CGALINTERP"]
39 |
40 | def interpGroundReturns(data):
41 | # if given a list of fields, returns a structured array with all of them
42 | ptVals = data.input.getPoints(colNames=['X', 'Y', 'Z', 'CLASSIFICATION'])
43 | # create mask for ground
44 | mask = ptVals['CLASSIFICATION'] == lidarprocessor.CLASSIFICATION_GROUND
45 |
46 | # get the coords for this block
47 | pxlCoords = data.info.getBlockCoordArrays()
48 |
49 | if ptVals.shape[0] > 0:
50 | # there is data for this block
51 | xVals = ptVals['X'][mask]
52 | yVals = ptVals['Y'][mask]
53 | zVals = ptVals['Z'][mask]
54 | # 'pynn' needs the pynnterp module installed
55 | out = interpolation.interpGrid(xVals, yVals, zVals, pxlCoords,
56 | method='cgalnn')
57 |
58 | # mask out where interpolation failed
59 | invalid = numpy.isnan(out)
60 | out[invalid] = 0
61 | else:
62 | # no data - set to zero
63 | out = numpy.empty(pxlCoords[0].shape, dtype=numpy.float64)
64 | out.fill(0)
65 |
66 | out = numpy.expand_dims(out, axis=0)
67 | data.imageOut.setData(out)
68 |
69 | def getProjection(imageFile):
70 | """
71 | Returns the projection of an image as a WKT
72 | """
73 | ds = gdal.Open(imageFile)
74 | return ds.GetProjection()
75 |
76 | def run(oldpath, newpath):
77 | """
78 | Runs the 6th basic test suite. Tests:
79 |
80 | Interpolation
81 | overlap
82 | setting pixel grid
83 | """
84 | input = os.path.join(oldpath, IN_FILE)
85 | interp = os.path.join(newpath, INTERP_FILE)
86 | origInterp = os.path.join(oldpath, INTERP_FILE)
87 |
88 | dataFiles = lidarprocessor.DataFiles()
89 | dataFiles.input = lidarprocessor.LidarFile(input, lidarprocessor.READ)
90 | dataFiles.imageOut = lidarprocessor.ImageFile(interp, lidarprocessor.CREATE)
91 |
92 | controls = lidarprocessor.Controls()
93 | progress = cuiprogress.GDALProgressBar()
94 | controls.setProgress(progress)
95 | controls.setOverlap(2)
96 |
97 | # can't use origInterp as projection source as this might not
98 | # be created yet (eg called from testing_cmds.sh)
99 | projectionSource = os.path.join(oldpath, PROJECTION_SOURCE)
100 | wkt = getProjection(projectionSource)
101 | pixGrid = pixelgrid.PixelGridDefn(xMin=509198.0, yMax=6944830, xMax=509856,
102 | yMin=6944130, xRes=2.0, yRes=2.0, projection=wkt)
103 | controls.setFootprint(lidarprocessor.BOUNDS_FROM_REFERENCE)
104 | controls.setReferencePixgrid(pixGrid)
105 | controls.setSpatialProcessing(True)
106 |
107 | lidarprocessor.doProcessing(interpGroundReturns, dataFiles, controls=controls)
108 |
109 | utils.compareImageFiles(origInterp, interp)
110 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite7.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can set the pixelgrid different
4 | from the spatial index.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from osgeo import gdal
28 | from . import utils
29 | from pylidar import lidarprocessor
30 | from rios import cuiprogress
31 | from rios import pixelgrid
32 |
33 | IN_FILE = 'testsuite1_idx.spd'
34 | OUT_FILE = 'testsuite7.img'
35 | PROJECTION_SOURCE = 'testsuite1.img'
36 |
37 | def writeImageFunc(data):
38 | zValues = data.input.getPointsByBins(colNames='Z')
39 | (maxPts, nRows, nCols) = zValues.shape
40 | nullval = 0
41 | if maxPts > 0:
42 | minZ = zValues.min(axis=0)
43 | stack = numpy.ma.expand_dims(minZ, axis=0)
44 | else:
45 | stack = numpy.empty((1, nRows, nCols), dtype=zValues.dtype)
46 | stack.fill(nullval)
47 | data.imageOut.setData(stack)
48 |
49 | def getProjection(imageFile):
50 | """
51 | Returns the projection of an image as a WKT
52 | """
53 | ds = gdal.Open(imageFile)
54 | return ds.GetProjection()
55 |
56 | def run(oldpath, newpath):
57 | """
58 | Runs the 7th basic test suite. Tests:
59 |
60 | setting pixel grid different from the spatial index
61 | """
62 | input = os.path.join(oldpath, IN_FILE)
63 | interp = os.path.join(newpath, OUT_FILE)
64 | origInterp = os.path.join(oldpath, OUT_FILE)
65 |
66 | dataFiles = lidarprocessor.DataFiles()
67 | dataFiles.input = lidarprocessor.LidarFile(input, lidarprocessor.READ)
68 | dataFiles.imageOut = lidarprocessor.ImageFile(interp, lidarprocessor.CREATE)
69 |
70 | controls = lidarprocessor.Controls()
71 | progress = cuiprogress.GDALProgressBar()
72 | controls.setProgress(progress)
73 | controls.setSpatialProcessing(True)
74 |
75 | # can't use origInterp as projection source as this might not
76 | # be created yet (eg called from testing_cmds.sh)
77 | projectionSource = os.path.join(oldpath, PROJECTION_SOURCE)
78 | wkt = getProjection(projectionSource)
79 | pixGrid = pixelgrid.PixelGridDefn(xMin=509199.0, yMax=6944830, xMax=509857,
80 | yMin=6944130, xRes=2.0, yRes=2.0, projection=wkt)
81 | controls.setFootprint(lidarprocessor.BOUNDS_FROM_REFERENCE)
82 | controls.setReferencePixgrid(pixGrid)
83 |
84 | lidarprocessor.doProcessing(writeImageFunc, dataFiles, controls=controls)
85 |
86 | utils.compareImageFiles(origInterp, interp)
87 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite8.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can import a Riegl RXP file
4 | and create a spatial index and image from it.
5 | Also tests updating resulting file.
6 | """
7 |
8 | # This file is part of PyLidar
9 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
10 | #
11 | # This program is free software: you can redistribute it and/or modify
12 | # it under the terms of the GNU General Public License as published by
13 | # the Free Software Foundation, either version 3 of the License, or
14 | # (at your option) any later version.
15 | #
16 | # This program is distributed in the hope that it will be useful,
17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | # GNU General Public License for more details.
20 | #
21 | # You should have received a copy of the GNU General Public License
22 | # along with this program. If not, see .
23 |
24 | from __future__ import print_function, division
25 |
26 | import os
27 | import numpy
28 | import shutil
29 | from . import utils
30 | from pylidar import lidarprocessor
31 | from pylidar.lidarformats import generic
32 | from pylidar.toolbox.translate.rieglrxp2spdv4 import translate
33 | from pylidar.toolbox.indexing.gridindex import createGridSpatialIndex
34 | from pylidar.toolbox.rasterization import rasterize
35 | from pylidar.toolbox.arrayutils import convertArgResultToIndexTuple
36 | from rios import cuiprogress
37 |
38 | REQUIRED_FORMATS = ["RIEGLRXP"]
39 |
40 | INPUT_RIEGL = '161122_092408.rxp'
41 | IMPORTED_SPD = 'testsuite8.spd'
42 | INDEXED_SPD = 'testsuite8_idx.spd'
43 | OUTPUT_RASTER = 'testsuite8.img'
44 | UPDATED_SPD = 'testsuite8_update.spd'
45 |
46 | # override the default scaling
47 | # type, varname, gain, offset
48 | SCALINGS = [('PULSE', 'X_ORIGIN', 'DFLT', 1000, -1000),
49 | ('PULSE', 'Y_ORIGIN', 'DFLT', 1000, -1000),
50 | ('PULSE', 'Z_ORIGIN', 'DFLT', 1000, -1000),
51 | ('PULSE', 'X_IDX', 'DFLT', 1000, -1000),
52 | ('PULSE', 'Y_IDX', 'DFLT', 1000, -1000),
53 | ('POINT', 'X', 'DFLT', 1000, -1000),
54 | ('POINT', 'Y', 'DFLT', 1000, -1000),
55 | ('POINT', 'Z', 'DFLT', 1000, -1000)]
56 |
57 | # because the files have lots of points, use a smaller
58 | # windowsize to prevent running out of memory
59 | WINDOWSIZE = 50
60 |
61 | def updatePointFunc(data):
62 | """
63 | Function called from the processor that updates the points.
64 | I have taken the opportunity to test the convertArgResultToIndexTuple
65 | function which seems the best way to update with the results of
66 | argmin.
67 | """
68 | pts = data.input1.getPointsByBins(colNames=['Z', 'CLASSIFICATION'])
69 | zVals = pts['Z']
70 | classif = pts['CLASSIFICATION']
71 | if pts.shape[0] > 0:
72 | idx = numpy.argmin(zVals, axis=0)
73 | idxmask = numpy.ma.all(zVals, axis=0)
74 | z, y, x = convertArgResultToIndexTuple(idx, idxmask.mask)
75 |
76 | # set lowest points to class 2
77 | classif[z, y, x] = 2
78 |
79 | data.input1.setPoints(classif, colName='CLASSIFICATION')
80 |
81 | def run(oldpath, newpath):
82 | """
83 | Runs the 8th basic test suite. Tests:
84 |
85 | Importing Riegl
86 | Creating spatial index
87 | Create an image file
88 | updating resulting file
89 | """
90 | inputRiegl = os.path.join(oldpath, INPUT_RIEGL)
91 | info = generic.getLidarFileInfo(inputRiegl)
92 |
93 | importedSPD = os.path.join(newpath, IMPORTED_SPD)
94 | translate(info, inputRiegl, importedSPD, scalings=SCALINGS,
95 | internalrotation=True)
96 | utils.compareLiDARFiles(os.path.join(oldpath, IMPORTED_SPD), importedSPD,
97 | windowSize=WINDOWSIZE)
98 |
99 | indexedSPD = os.path.join(newpath, INDEXED_SPD)
100 | createGridSpatialIndex(importedSPD, indexedSPD, binSize=1.0,
101 | tempDir=newpath)
102 | utils.compareLiDARFiles(os.path.join(oldpath, INDEXED_SPD), indexedSPD,
103 | windowSize=WINDOWSIZE)
104 |
105 | outputRaster = os.path.join(newpath, OUTPUT_RASTER)
106 | rasterize([indexedSPD], outputRaster, ['Z'], function="numpy.ma.min",
107 | atype='POINT', windowSize=WINDOWSIZE)
108 | utils.compareImageFiles(os.path.join(oldpath, OUTPUT_RASTER), outputRaster)
109 |
110 | outputUpdate = os.path.join(newpath, UPDATED_SPD)
111 | shutil.copyfile(indexedSPD, outputUpdate)
112 |
113 | dataFiles = lidarprocessor.DataFiles()
114 | dataFiles.input1 = lidarprocessor.LidarFile(outputUpdate,
115 | lidarprocessor.UPDATE)
116 |
117 | controls = lidarprocessor.Controls()
118 | progress = cuiprogress.GDALProgressBar()
119 | controls.setProgress(progress)
120 | controls.setWindowSize(WINDOWSIZE)
121 | controls.setSpatialProcessing(True)
122 |
123 | lidarprocessor.doProcessing(updatePointFunc, dataFiles, controls=controls)
124 |
125 | utils.compareLiDARFiles(os.path.join(oldpath, UPDATED_SPD), outputUpdate,
126 | windowSize=WINDOWSIZE)
127 |
--------------------------------------------------------------------------------
/pylidar/testing/testsuite9.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Simple testsuite that checks we can build a vertical profile
4 | using the canopy toolbox.
5 | """
6 |
7 | # This file is part of PyLidar
8 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 | from __future__ import print_function, division
24 |
25 | import os
26 | import numpy
27 | from . import utils
28 | from pylidar import lidarprocessor
29 | from pylidar.toolbox.canopy.canopymetric import runCanopyMetric
30 |
31 | # use the imported .rxp file from testsuite8 so we don't
32 | # have to have the Riegl driver present for this test
33 | IMPORTED_SPD = 'testsuite8.spd'
34 | CANOPY_CSV = 'testsuite9.csv'
35 |
36 | def run(oldpath, newpath):
37 | """
38 | Runs the 9th basic test suite. Tests:
39 |
40 | Creating a canopy profile
41 | """
42 | otherargs = lidarprocessor.OtherArgs()
43 |
44 | otherargs.weighted = False
45 | otherargs.heightcol = 'Z'
46 | otherargs.heightbinsize = 0.5
47 | otherargs.minheight = 0.0
48 | otherargs.maxheight = 50.0
49 | otherargs.zenithbinsize = 5.0
50 | otherargs.minazimuth = [0.0]
51 | otherargs.maxazimuth = [360.0]
52 | otherargs.minzenith = [35.0]
53 | otherargs.maxzenith = [70.0]
54 | otherargs.planecorrection = False
55 | otherargs.rptfile = None
56 | otherargs.gridsize = 20
57 | otherargs.gridbinsize = 5.0
58 | otherargs.excludedclasses = []
59 | otherargs.externaldem = None
60 | otherargs.totalpaimethod = "HINGE"
61 |
62 | inFile = os.path.join(oldpath, IMPORTED_SPD)
63 | outFile = os.path.join(newpath, CANOPY_CSV)
64 |
65 | runCanopyMetric([inFile], [outFile], "PAVD_CALDERS2014", otherargs)
66 |
67 | newData = numpy.genfromtxt(outFile, delimiter=',', names=True)
68 | oldData = numpy.genfromtxt(os.path.join(oldpath, CANOPY_CSV),
69 | delimiter=',', names=True)
70 |
71 | if not (newData == oldData).all():
72 | msg = 'New canopy profile does not match old'
73 | raise utils.TestingDataMismatch(msg)
74 |
--------------------------------------------------------------------------------
/pylidar/toolbox/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | toolbox module. A bunch of useful tools for LiDAR Processing using pylidar.
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/arrayutils.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions for use with pylidar.
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from numba import jit
25 |
26 | def addFieldToStructArray(oldArray, newName, newType, newData=0):
27 | """
28 | Creates a new array with all the data from oldArray, but with a new
29 | field as specified by newName and newType. newType should be one
30 | of the numpy types (numpy.uint16 etc).
31 | newData should either be a constant value, or an array of the correct
32 | shape and the new field will be initialised to this value.
33 |
34 | """
35 | # first get all the fields
36 | newdtype = []
37 | for key in oldArray.dtype.fields.keys():
38 | atype = oldArray.dtype.fields[key][0]
39 | newdtype.append((key, atype))
40 |
41 | # add our new type
42 | newdtype.append((newName, newType))
43 |
44 | # new array
45 | newArray = numpy.empty(oldArray.shape, dtype=newdtype)
46 |
47 | # copy old data over
48 | for key in oldArray.dtype.fields.keys():
49 | newArray[key] = oldArray[key]
50 |
51 | # new field
52 | newArray[newName] = newData
53 |
54 | # if oldArray was masked, make newArray masked also
55 | if isinstance(oldArray, numpy.ma.MaskedArray):
56 | # get first field so we can get the 'one mask value per element'
57 | # kind of mask instead of the 'mask value per field' since this
58 | # would have changed
59 | firstField = oldArray.dtype.names[0]
60 | mask = oldArray[firstField].mask
61 | newArray = numpy.ma.MaskedArray(newArray, mask=mask)
62 |
63 | return newArray
64 |
65 | @jit
66 | def convertArgResultToIndexTuple(input, mask):
67 | """
68 | Converts the result of the numpy.ma.arg* set of functions into
69 | a tuple of arrays that can be used to index the original array.
70 | 'mask' should be the mask of the result of numpy.ma.all for the same axis.
71 | Below is an example::
72 |
73 | zVals = pts['Z']
74 | classif = pts['CLASSIFICATION']
75 |
76 | idx = numpy.argmin(zVals, axis=0)
77 | idxmask = numpy.ma.all(zVals, axis=0)
78 | z, y, x = convertArgResultToIndexTuple(idx, idxmask.mask)
79 |
80 | classif[z, y, x] = 2
81 |
82 | """
83 | nrows, ncols = input.shape
84 |
85 | nIndices = (nrows * ncols) - mask.sum()
86 |
87 | zIdxs = numpy.zeros(nIndices, dtype=numpy.uint64)
88 | yIdxs = numpy.zeros(nIndices, dtype=numpy.uint64)
89 | xIdxs = numpy.zeros(nIndices, dtype=numpy.uint64)
90 |
91 | count = 0
92 | for y in range(nrows):
93 | for x in range(ncols):
94 | if not mask[y, x]:
95 | zIdxs[count] = input[y, x]
96 | yIdxs[count] = y
97 | xIdxs[count] = x
98 | count += 1
99 |
100 | return zIdxs, yIdxs, xIdxs
101 |
--------------------------------------------------------------------------------
/pylidar/toolbox/canopy/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | canopy module. Algorithms for creating canopy metrics
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/canopy/canopycommon.py:
--------------------------------------------------------------------------------
1 | """
2 | Common functions and classes for the canopy module
3 | There is some temporary duplication of functions from the spatial branch
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import numpy
25 | from osgeo import osr
26 | from osgeo import gdal
27 | from rios import imageio
28 | from rios import calcstats
29 | from rios import pixelgrid
30 | from pylidar.lidarformats import generic
31 | from pylidar import lidarprocessor
32 |
33 |
34 | class CanopyMetricError(Exception):
35 | "Exception type for canopymetric errors"
36 |
37 |
38 | def prepareInputFiles(infiles, otherargs, index=None):
39 | """
40 | Prepare input files for calculation of canopy metrics
41 | """
42 | dataFiles = lidarprocessor.DataFiles()
43 | if index is not None:
44 | dataFiles.inFiles = [lidarprocessor.LidarFile(infiles[index], lidarprocessor.READ)]
45 | else:
46 | dataFiles.inFiles = [lidarprocessor.LidarFile(fname, lidarprocessor.READ) for fname in infiles]
47 |
48 | otherargs.lidardriver = []
49 | otherargs.proj = []
50 |
51 | nFiles = len(dataFiles.inFiles)
52 | for i in range(nFiles):
53 | info = generic.getLidarFileInfo(dataFiles.inFiles[i].fname)
54 | if info.getDriverName() == 'riegl':
55 | if otherargs.externaltransformfn is not None:
56 | if index is not None:
57 | externaltransform = numpy.loadtxt(otherargs.externaltransformfn[index], ndmin=2, delimiter=" ", dtype=numpy.float32)
58 | else:
59 | externaltransform = numpy.loadtxt(otherargs.externaltransformfn[i], ndmin=2, delimiter=" ", dtype=numpy.float32)
60 | dataFiles.inFiles[i].setLiDARDriverOption("ROTATION_MATRIX", externaltransform)
61 | elif "ROTATION_MATRIX" in info.header:
62 | dataFiles.inFiles[i].setLiDARDriverOption("ROTATION_MATRIX", info.header["ROTATION_MATRIX"])
63 | else:
64 | msg = 'Input file %s has no valid pitch/roll/yaw data' % dataFiles.inFiles[i].fname
65 | raise generic.LiDARInvalidData(msg)
66 |
67 | otherargs.lidardriver.append( info.getDriverName() )
68 |
69 | if "SPATIAL_REFERENCE" in info.header.keys():
70 | if len(info.header["SPATIAL_REFERENCE"]) > 0:
71 | otherargs.proj.append(info.header["SPATIAL_REFERENCE"])
72 | else:
73 | otherargs.proj.append(None)
74 | else:
75 | otherargs.proj.append(None)
76 |
77 | return dataFiles
78 |
--------------------------------------------------------------------------------
/pylidar/toolbox/canopy/canopymetric.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for calculating canopy metrics from lidar data.
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar.lidarformats import generic
25 | from pylidar import lidarprocessor
26 | from rios import cuiprogress
27 |
28 | from pylidar.toolbox import spatial
29 | from pylidar.toolbox.canopy import canopycommon
30 | from pylidar.toolbox.canopy import pavd_calders2014
31 | from pylidar.toolbox.canopy import voxel_hancock2016
32 | from pylidar.toolbox.canopy import pgap_armston2013
33 |
34 |
35 | def runCanopyMetric(infiles, outfiles, metric, otherargs):
36 | """
37 | Apply canopy metric
38 | Metric name should be of the form _
39 | """
40 | controls = lidarprocessor.Controls()
41 |
42 | progress = cuiprogress.GDALProgressBar()
43 | controls.setProgress(progress)
44 |
45 | if metric == "PAVD_CALDERS2014":
46 |
47 | dataFiles = canopycommon.prepareInputFiles(infiles, otherargs)
48 | if otherargs.externaldem is not None:
49 | otherargs.dataDem, otherargs.xMinDem, otherargs.yMaxDem, otherargs.binSizeDem = \
50 | spatial.readImageLayer(otherargs.externaldem)
51 | controls.setSpatialProcessing(False)
52 | controls.setWindowSize(512)
53 | pavd_calders2014.run_pavd_calders2014(dataFiles, controls, otherargs, outfiles[0])
54 |
55 | elif metric == "VOXEL_HANCOCK2016":
56 |
57 | if otherargs.externaldem is not None:
58 | otherargs.dataDem, otherargs.xMinDem, otherargs.yMaxDem, otherargs.binSizeDem = \
59 | spatial.readImageLayer(otherargs.externaldem)
60 | controls.setSpatialProcessing(False)
61 | controls.setWindowSize(512)
62 | voxel_hancock2016.run_voxel_hancock2016(infiles, controls, otherargs, outfiles)
63 |
64 | elif metric == "PGAP_ARMSTON2013":
65 |
66 | controls.setSpatialProcessing(True)
67 | controls.setWindowSize(64)
68 | pgap_armston2013.run_pgap_armston2013(dataFiles, controls, otherargs, outfiles)
69 |
70 | else:
71 |
72 | msg = 'Unsupported metric %s' % metric
73 | raise canopycommon.CanopyMetricError(msg)
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/pylidar/toolbox/canopy/pgap_armston2013.py:
--------------------------------------------------------------------------------
1 | """
2 | Functions for calculating Pgap from ALS waveform data (Armston et al., 2013)
3 | Assumes standard point fields required to calculate return energy integral are available
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import numpy
25 | from numba import jit
26 |
27 | from pylidar import lidarprocessor
28 |
29 |
30 | def run_pgap_armston2013(dataFiles, controls, otherargs, outfile):
31 | """
32 | Main function for PGAP_ARMSTON2013
33 | """
34 | pass
35 |
--------------------------------------------------------------------------------
/pylidar/toolbox/cmdline/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Entry points for running the command line utils
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/cmdline/index.py:
--------------------------------------------------------------------------------
1 | """
2 | Does the creation of a spatial index
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import sys
24 | import argparse
25 |
26 | from pylidar.lidarformats import generic
27 | from pylidar.lidarformats import spdv4
28 | from pylidar.toolbox.indexing import gridindex
29 | from pylidar.basedriver import Extent
30 |
31 | DEFAULT_RESOLUTION = 1.0
32 | DEFAULT_INDEXTYPE = "CARTESIAN"
33 | DEFAULT_PULSEINDEXMETHOD = "FIRST_RETURN"
34 |
35 | def getCmdargs():
36 | """
37 | Get commandline arguments
38 | """
39 | p = argparse.ArgumentParser()
40 | p.add_argument("-i", "--input", help="Input SPD file name")
41 | p.add_argument("-o", "--output", help="Output SPD file with spatial index")
42 | p.add_argument("-r","--resolution", default=DEFAULT_RESOLUTION,
43 | type=float, help="Output SPD file grid resolution " +
44 | "(default: %(default)s)")
45 | p.add_argument("-b","--blocksize", type=float,
46 | help="Override the default blocksize")
47 | p.add_argument("--indextype", default=DEFAULT_INDEXTYPE,
48 | choices=['CARTESIAN', 'SPHERICAL', 'SCAN'],
49 | help="Spatial index type. (default: %(default)s)")
50 | p.add_argument("--pulseindexmethod", default=DEFAULT_PULSEINDEXMETHOD,
51 | choices=['FIRST_RETURN', 'LAST_RETURN'],
52 | help="Pulse index method. (default: %(default)s)")
53 | p.add_argument("--extent", nargs=4, metavar=('xmin', 'ymin', 'xmax', 'ymax'),
54 | help="Set extent of the output file")
55 | p.add_argument("--tempdir", help="Temporary directory to use. "+
56 | " If not specified, a temporary directory will be created and " +
57 | "removed at the end of processing.")
58 | p.add_argument("--wkt", help="projection to use for output in WKT format")
59 |
60 | cmdargs = p.parse_args()
61 |
62 | if cmdargs.input is None or cmdargs.output is None:
63 | p.print_help()
64 | sys.exit()
65 |
66 | return cmdargs
67 |
68 | def run():
69 | """
70 | Main function. Looks at the command line arguments and calls
71 | the indexing code.
72 | """
73 | cmdargs = getCmdargs()
74 |
75 | if cmdargs.extent is not None:
76 | extent = Extent(float(cmdargs.extent[0]), float(cmdargs.extent[2]),
77 | float(cmdargs.extent[1]), float(cmdargs.extent[3]),
78 | cmdargs.resolution)
79 | else:
80 | extent = None
81 |
82 | try:
83 | indexType = getattr(spdv4, "SPDV4_INDEX_%s" % cmdargs.indextype)
84 | except AttributeError:
85 | msg = 'Unsupported index type %s' % cmdargs.indextype
86 | raise generic.LiDARPulseIndexUnsupported(msg)
87 |
88 | try:
89 | pulseindexmethod = getattr(spdv4,
90 | "SPDV4_PULSE_INDEX_%s" % cmdargs.pulseindexmethod)
91 | except AttributeError:
92 | msg = 'Unsupported pulse indexing method %s' % cmdargs.pulseindexmethod
93 | raise generic.LiDARPulseIndexUnsupported(msg)
94 |
95 | gridindex.createGridSpatialIndex(cmdargs.input, cmdargs.output,
96 | extent=extent, tempDir=cmdargs.tempdir,
97 | indexType=indexType,
98 | pulseIndexMethod=pulseindexmethod,
99 | binSize=cmdargs.resolution,
100 | blockSize=cmdargs.blocksize,
101 | wkt=cmdargs.wkt)
102 |
103 |
--------------------------------------------------------------------------------
/pylidar/toolbox/cmdline/info.py:
--------------------------------------------------------------------------------
1 | """
2 | Does the printing out to terminal of file info
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import sys
24 | import pprint
25 | import argparse
26 | from pylidar.lidarformats import generic
27 | # need to import lidarprocessor also so we have
28 | # all the formats imported
29 | from pylidar import lidarprocessor
30 |
31 | def getCmdargs():
32 | """
33 | Get commandline arguments
34 | """
35 | p = argparse.ArgumentParser()
36 | p.add_argument("-i", "--input", help="Input file name")
37 | p.add_argument("-v", "--verbose", default=False, action="store_true",
38 | help="print more verbose output")
39 |
40 | cmdargs = p.parse_args()
41 |
42 | if cmdargs.input is None:
43 | p.print_help()
44 | sys.exit()
45 |
46 | return cmdargs
47 |
48 | def formatAsString(obj):
49 | return pprint.pformat(obj, depth=1, compact=True)
50 |
51 | def run():
52 | """
53 | Main function. Looks at the command line arguments
54 | and prints the info
55 | """
56 | cmdargs = getCmdargs()
57 |
58 | info = generic.getLidarFileInfo(cmdargs.input, cmdargs.verbose)
59 | print('Driver Name:', info.getDriverName())
60 | print('')
61 |
62 | for key, val in sorted(info.__dict__.items()):
63 | if isinstance(val, dict):
64 | print(key.upper()) # TODO: we need to be uppercase??
65 | for hkey, hval in sorted(val.items()):
66 | hval = formatAsString(hval)
67 | print(" ", hkey.upper(), hval)
68 | else:
69 | val = formatAsString(val)
70 | print(key.upper(), val)
71 |
--------------------------------------------------------------------------------
/pylidar/toolbox/cmdline/rasterize.py:
--------------------------------------------------------------------------------
1 | """
2 | Provides support for calling the rasterise function from the
3 | command line.
4 | """
5 |
6 | # This file is part of PyLidar
7 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from __future__ import print_function, division
23 |
24 | import sys
25 | import argparse
26 |
27 | from pylidar import lidarprocessor
28 | from pylidar.toolbox import rasterization
29 |
30 | DEFAULT_FUNCTION = rasterization.DEFAULT_FUNCTION
31 | DEFAULT_ATTRIBUTE = rasterization.DEFAULT_ATTRIBUTE
32 |
33 | def getCmdargs():
34 | """
35 | Get commandline arguments
36 | """
37 | p = argparse.ArgumentParser()
38 | p.add_argument("-i", "--infiles", nargs="+",
39 | help="Input lidar files (required)")
40 | p.add_argument("-o", "--output", help="output file (required)")
41 | p.add_argument("-a", "--attributes", nargs="+",
42 | help="Pulse, point or waveform attributes to rasterize. Can list " +
43 | "multiple attributes for one --type to create an image stack.")
44 | p.add_argument("-f", "--function", default=DEFAULT_FUNCTION,
45 | help="function to apply to data. Must contain module name " +
46 | "and be able to operate on masked arrays and take the 'axis' " +
47 | "parameter. default=%(default)s")
48 | p.add_argument("-t", "--type", default=DEFAULT_ATTRIBUTE,
49 | choices=['POINT', 'PULSE'],
50 | help="Type of data to operate on. default=%(default)s")
51 | p.add_argument("-b","--background", type=float, default=0,
52 | help="Output raster background value. default=%(default)s.")
53 | p.add_argument("--binsize", type=float,
54 | help="resolution to do processing at")
55 | p.add_argument("--footprint", choices=['INTERSECTION', 'UNION', 'BOUNDS_FROM_REFERENCE'],
56 | help="Multiple input files will be combined in this way")
57 | p.add_argument("--windowsize", type=float,
58 | help="Window size to use for each processing block")
59 | p.add_argument("--drivername",
60 | help="GDAL driver to use for the output image file")
61 | p.add_argument("--driveroptions", nargs="+",
62 | help="Creation options to pass to the output format driver, e.g. COMPRESS=LZW")
63 | p.add_argument("-m", "--module", help="Extra modules that need to be " +
64 | "imported for use by --function")
65 | p.add_argument("-q", "--quiet", default=False, action='store_true',
66 | help="Don't show progress etc")
67 |
68 | cmdargs = p.parse_args()
69 | if cmdargs.attributes is None:
70 | print("Must specify attributes to use")
71 | p.print_help()
72 | sys.exit()
73 |
74 | if cmdargs.output is None:
75 | print("Must specify output file name")
76 | p.print_help()
77 | sys.exit()
78 |
79 | if cmdargs.footprint is not None:
80 | # Evaluate the footprint string as a named constant
81 | cmdargs.footprint = eval("lidarprocessor.{}".format(cmdargs.footprint))
82 |
83 | return cmdargs
84 |
85 | def run():
86 | """
87 | Main function. Checks the command line parameters and calls
88 | the rasterisation routine.
89 | """
90 | cmdargs = getCmdargs()
91 |
92 | rasterization.rasterize(cmdargs.infiles, cmdargs.output,
93 | cmdargs.attributes, cmdargs.function,
94 | cmdargs.type, cmdargs.background, cmdargs.binsize, cmdargs.module,
95 | cmdargs.quiet, footprint=cmdargs.footprint,
96 | windowSize=cmdargs.windowsize, driverName=cmdargs.drivername,
97 | driverOptions=cmdargs.driveroptions)
98 |
99 |
--------------------------------------------------------------------------------
/pylidar/toolbox/cmdline/tile.py:
--------------------------------------------------------------------------------
1 | """
2 | Does the creation of a spatial index
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import sys
24 | import os
25 | import argparse
26 |
27 | from pylidar import lidarprocessor
28 | from pylidar.lidarformats import generic
29 | from pylidar.lidarformats import spdv4
30 | from pylidar.toolbox.indexing import gridindex
31 | from pylidar.basedriver import Extent
32 |
33 | DEFAULT_BLOCKSIZE = 1000.0
34 | DEFAULT_INDEXTYPE = "CARTESIAN"
35 | DEFAULT_PULSEINDEXMETHOD = "FIRST_RETURN"
36 |
37 | def getCmdargs():
38 | """
39 | Get commandline arguments
40 | """
41 | p = argparse.ArgumentParser(description="""
42 | Split one or more input lidar files into separate tiles, on a regular grid.
43 | """)
44 | p.add_argument("input", nargs='+',
45 | help=("Input lidar file name. Can be specified multiple times, or using wild cards, for "
46 | +"multiple inputs."))
47 | p.add_argument("-r","--resolution",
48 | type=float, help="Output resolution to use when choosing corners " +
49 | "of the tiles (default: same value as --blocksize)")
50 | p.add_argument("-b","--blocksize", type=float, default=DEFAULT_BLOCKSIZE,
51 | help=("The size (in world coordinates, i.e. metres) of the tiles into which the "+
52 | "data will be divided (default: %(default)s) "))
53 | p.add_argument("--indextype", default=DEFAULT_INDEXTYPE,
54 | choices=['CARTESIAN', 'SPHERICAL', 'SCAN'],
55 | help="Spatial index type. (default: %(default)s)")
56 | p.add_argument("--pulseindexmethod", default=DEFAULT_PULSEINDEXMETHOD,
57 | choices=['FIRST_RETURN', 'LAST_RETURN'],
58 | help="Pulse index method. (default: %(default)s)")
59 | p.add_argument("--extent", nargs=4, metavar=('xmin', 'ymin', 'xmax', 'ymax'),
60 | help="Set extent of the input file to use")
61 | p.add_argument("--outdir", default='.', help="Output directory to use "+
62 | " (default: %(default)s)")
63 | p.add_argument("-q", "--quiet", default=False, action='store_true',
64 | help="Suppress the printing of the tile filenames")
65 | p.add_argument("-f", "--footprint", choices=['union', 'intersection'],
66 | default='union', help='how to combine multiple inputs')
67 | p.add_argument("--format", choices=['SPDV4', 'LAS'], default='SPDV4',
68 | help=("Format to output the tiles as (default: %(default)s). This is not as orthogonal "+
69 | "as it should be, i.e. not all combinations of input and output format actually work. "))
70 | p.add_argument("--keepemptytiles", default=False, action="store_true",
71 | help="Do not delete the tiles which turn out to be empty. Default will remove them")
72 | p.add_argument("--buildpulses", default=False, action="store_true",
73 | help="Build pulse data structure. Default is False (only for LAS inputs)")
74 |
75 | cmdargs = p.parse_args()
76 |
77 | if cmdargs.resolution is None:
78 | cmdargs.resolution = cmdargs.blocksize
79 |
80 | return cmdargs
81 |
82 | def run():
83 | """
84 | Main function. Looks at the command line arguments and calls
85 | the tiling code.
86 | """
87 | cmdargs = getCmdargs()
88 |
89 | if cmdargs.extent is not None:
90 | extent = Extent(float(cmdargs.extent[0]), float(cmdargs.extent[2]),
91 | float(cmdargs.extent[1]), float(cmdargs.extent[3]),
92 | cmdargs.resolution)
93 | else:
94 | extent = None
95 |
96 | try:
97 | indexType = getattr(spdv4, "SPDV4_INDEX_%s" % cmdargs.indextype)
98 | except AttributeError:
99 | msg = 'Unsupported index type %s' % cmdargs.indextype
100 | raise generic.LiDARPulseIndexUnsupported(msg)
101 |
102 | try:
103 | pulseindexmethod = getattr(spdv4,
104 | "SPDV4_PULSE_INDEX_%s" % cmdargs.pulseindexmethod)
105 | except AttributeError:
106 | msg = 'Unsupported pulse indexing method %s' % cmdargs.pulseindexmethod
107 | raise generic.LiDARPulseIndexUnsupported(msg)
108 |
109 | if cmdargs.footprint == 'union':
110 | footprint = lidarprocessor.UNION
111 | elif cmdargs.footprint == 'intersection':
112 | footprint = lidarprocessor.INTERSECTION
113 |
114 | # returns header and extent that we don't use so we ignore.
115 | # those are useful in the spatial indexing situation
116 | header, extent, fnameList = gridindex.splitFileIntoTiles(cmdargs.input,
117 | binSize=cmdargs.resolution,
118 | blockSize=cmdargs.blocksize,
119 | tempDir=cmdargs.outdir,
120 | extent=extent, indexType=indexType,
121 | pulseIndexMethod=pulseindexmethod,
122 | footprint=footprint,
123 | outputFormat=cmdargs.format,
124 | buildPulses=cmdargs.buildpulses)
125 |
126 | # Delete the empty ones
127 | if not cmdargs.keepemptytiles:
128 | fnameList = deleteEmptyTiles(fnameList)
129 |
130 | # now print the names of the tiles to the screen
131 | if not cmdargs.quiet:
132 | for fname, subExtent in fnameList:
133 | print(fname)
134 |
135 |
136 | def deleteEmptyTiles(fnameList):
137 | """
138 | After creating all the tiles, some of them will have ended up being empty. Delete them.
139 | """
140 | newFileList = []
141 | for (fname, subExtent) in fnameList:
142 | info = generic.getLidarFileInfo(fname)
143 | header = info.header
144 | translator = info.getHeaderTranslationDict()
145 | numPointsField = translator[generic.HEADER_NUMBER_OF_POINTS]
146 | if header[numPointsField] > 0:
147 | newFileList.append((fname, subExtent))
148 | else:
149 | if os.path.exists(fname):
150 | os.remove(fname)
151 |
152 | return newFileList
153 |
--------------------------------------------------------------------------------
/pylidar/toolbox/grdfilters/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | grdfilter module. A algorithms for filter ground returns using pylidar.
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/grdfilters/classGrdReturns.py:
--------------------------------------------------------------------------------
1 | """
2 | Functions to classify the ground returns within the point cloud.
3 | """
4 | # This file is part of PyLidar
5 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | from __future__ import print_function, division
21 |
22 | import os
23 | import numpy
24 |
25 |
26 | def classifyGroundReturns(ptBinVals, grdSurf, thres):
27 | """
28 | A function to classify the ground return points within a distance from a
29 | ground surface.
30 | * ptBinVals is a binned list of points
31 | * grdSurf ground surface with the same dimensions as the binned points
32 | * thres is the threshold for defining whether a point is ground or not.
33 | """
34 | ptBinVals['CLASSIFICATION'] = numpy.where( ptBinVals['CLASSIFICATION'] == 3, 0, ptBinVals['CLASSIFICATION'])
35 | ptBinVals['CLASSIFICATION'] = numpy.where( numpy.absolute(ptBinVals['Z'] - grdSurf) < thres, 3, ptBinVals['CLASSIFICATION'])
36 |
--------------------------------------------------------------------------------
/pylidar/toolbox/indexing/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | indexing module. A algorithms for creating spatial indexed data
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/rasterization.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for doing simple rasterization of LiDAR data.
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | import importlib
25 | from pylidar import lidarprocessor
26 | from rios import cuiprogress
27 |
28 | DEFAULT_FUNCTION = "numpy.ma.min"
29 | DEFAULT_ATTRIBUTE = 'POINT'
30 |
31 | POINT = 0
32 | PULSE = 1
33 |
34 | class RasterizationError(Exception):
35 | "Exception type for rasterization errors"
36 |
37 | def writeImageFunc(data, otherArgs):
38 | """
39 | Called from pylidar.lidarprocessor. Calls the nominated
40 | function on the data.
41 | """
42 | # get data for each file
43 | if otherArgs.atype == POINT:
44 | dataList = [input.getPointsByBins(colNames=otherArgs.attributes)
45 | for input in data.inList]
46 | else:
47 | dataList = [input.getPulsesByBins(colNames=otherArgs.attributes)
48 | for input in data.inList]
49 |
50 | # stack it so we can analyse the whole thing
51 | dataStack = numpy.ma.vstack(dataList)
52 |
53 | # create output
54 | nLayers = len(otherArgs.attributes)
55 | (maxPts, nRows, nCols) = dataStack.shape
56 | outStack = numpy.empty((nLayers, nRows, nCols), dtype=numpy.float64)
57 | if maxPts > 0:
58 | # a layer per attribute
59 | nIdx = 0
60 | for attribute in otherArgs.attributes:
61 | attributeData = dataStack[attribute]
62 | attributeDataFunc = otherArgs.func(attributeData, axis=0)
63 | outStack[nIdx] = attributeDataFunc
64 | # Need to manually put in the 'background' value. Masked arrays are dangerous.
65 | outStack[nIdx][attributeDataFunc.mask] = otherArgs.background
66 | nIdx += 1
67 | else:
68 | outStack.fill(otherArgs.background)
69 |
70 | data.imageOut.setData(outStack)
71 |
72 | def rasterize(infiles, outfile, attributes, function=DEFAULT_FUNCTION,
73 | atype=DEFAULT_ATTRIBUTE, background=0, binSize=None, extraModule=None,
74 | quiet=False, footprint=None, windowSize=None, driverName=None, driverOptions=None):
75 | """
76 | Apply the given function to the list of input files and create
77 | an output raster file. attributes is a list of attributes to run
78 | the function on. The function name must contain a module
79 | name and the specified function must take a masked array, plus
80 | the 'axis' parameter. atype should be a string containing
81 | either POINT|PULSE.
82 | background is the background raster value to use. binSize is the bin size
83 | to use which defaults to that of the spatial indices used.
84 | extraModule should be a string with an extra module to import - use
85 | this for modules other than numpy that are needed by your function.
86 | quiet means no progress etc
87 | footprint specifies the footprint type
88 | """
89 | dataFiles = lidarprocessor.DataFiles()
90 | dataFiles.inList = [lidarprocessor.LidarFile(fname, lidarprocessor.READ)
91 | for fname in infiles]
92 | dataFiles.imageOut = lidarprocessor.ImageFile(outfile, lidarprocessor.CREATE)
93 | dataFiles.imageOut.setRasterIgnore(background)
94 |
95 | if driverName is not None:
96 | dataFiles.imageOut.setRasterDriver(driverName)
97 |
98 | if driverOptions is not None:
99 | dataFiles.imageOut.setRasterDriverOptions(driverOptions)
100 |
101 | # import any other modules required
102 | globalsDict = globals()
103 | if extraModule is not None:
104 | globalsDict[extraModule] = importlib.import_module(extraModule)
105 |
106 | controls = lidarprocessor.Controls()
107 | controls.setSpatialProcessing(True)
108 | if not quiet:
109 | progress = cuiprogress.GDALProgressBar()
110 | controls.setProgress(progress)
111 |
112 | if binSize is not None:
113 | controls.setReferenceResolution(binSize)
114 |
115 | if footprint is not None:
116 | controls.setFootprint(footprint)
117 |
118 | if windowSize is not None:
119 | controls.setWindowSize(windowSize)
120 |
121 | otherArgs = lidarprocessor.OtherArgs()
122 | # reference to the function to call
123 | otherArgs.func = eval(function, globalsDict)
124 | otherArgs.attributes = attributes
125 | otherArgs.background = background
126 | atype = atype.upper()
127 | if atype == 'POINT':
128 | otherArgs.atype = POINT
129 | elif atype == 'PULSE':
130 | otherArgs.atype = PULSE
131 | else:
132 | msg = 'Unsupported type %s' % atype
133 | raise RasterizationError(msg)
134 |
135 | lidarprocessor.doProcessing(writeImageFunc, dataFiles, controls=controls,
136 | otherArgs=otherArgs)
137 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Module for doing translation between lidar formats
4 | """
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/ascii2spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between ASCII and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from pylidar.lidarformats import spdv4
27 | from rios import cuiprogress
28 | from osgeo import osr
29 |
30 | from . import translatecommon
31 |
32 | def transFunc(data, otherArgs):
33 | """
34 | Called from lidarprocessor. Does the actual conversion to SPD V4
35 | """
36 | pulses = data.input1.getPulses()
37 | points = data.input1.getPointsByPulse()
38 |
39 | # set scaling
40 | if data.info.isFirstBlock():
41 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
42 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
43 |
44 | # check the range
45 | translatecommon.checkRange(otherArgs.expectRange, points, pulses)
46 | # any constant columns
47 | points, pulses, waveforms = translatecommon.addConstCols(otherArgs.constCols,
48 | points, pulses)
49 |
50 | data.output1.setPoints(points)
51 | data.output1.setPulses(pulses)
52 |
53 | def translate(info, infile, outfile, colTypes, pulseCols=None, expectRange=None,
54 | scaling=None, classificationTranslation=None, nullVals=None,
55 | constCols=None):
56 | """
57 | Main function which does the work.
58 |
59 | * Info is a fileinfo object for the input file.
60 | * infile and outfile are paths to the input and output files respectively.
61 | * expectRange is a list of tuples with (type, varname, min, max).
62 | * scaling is a list of tuples with (type, varname, gain, offset).
63 | * colTypes is a list of name and data type tuples for every column
64 | * pulseCols is a list of strings defining the pulse columns
65 | * classificationTranslation is a list of tuples specifying how to translate
66 | between the codes within the files and the
67 | lidarprocessor.CLASSIFICATION_* ones. First element of tuple is file
68 | number, second the lidarprocessor code.
69 | * nullVals is a list of tuples with (type, varname, value)
70 | * constCols is a list of tupes with (type, varname, dtype, value)
71 | """
72 | scalingsDict = translatecommon.overRideDefaultScalings(scaling)
73 |
74 | # set up the variables
75 | dataFiles = lidarprocessor.DataFiles()
76 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
77 |
78 | # convert from strings to numpy dtypes
79 | numpyColTypes = []
80 | for name, typeString in colTypes:
81 | numpydtype = translatecommon.STRING_TO_DTYPE[typeString.upper()]
82 | numpyColTypes.append((name, numpydtype))
83 |
84 | dataFiles.input1.setLiDARDriverOption('COL_TYPES', numpyColTypes)
85 | if pulseCols is not None:
86 | dataFiles.input1.setLiDARDriverOption('PULSE_COLS', pulseCols)
87 |
88 | if classificationTranslation is not None:
89 | dataFiles.input1.setLiDARDriverOption('CLASSIFICATION_CODES',
90 | classificationTranslation)
91 |
92 | controls = lidarprocessor.Controls()
93 | progress = cuiprogress.GDALProgressBar()
94 | controls.setProgress(progress)
95 | controls.setSpatialProcessing(False)
96 |
97 | otherArgs = lidarprocessor.OtherArgs()
98 | otherArgs.scaling = scalingsDict
99 | otherArgs.expectRange = expectRange
100 | otherArgs.nullVals = nullVals
101 | otherArgs.constCols = constCols
102 |
103 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
104 | dataFiles.output1.setLiDARDriver('SPDV4')
105 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
106 |
107 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
108 | otherArgs=otherArgs)
109 |
110 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/lvisbin2spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between LVIS Binary and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from pylidar.lidarformats import spdv4
27 | from pylidar.lidarformats import lvisbin
28 | from rios import cuiprogress
29 |
30 | from . import translatecommon
31 |
32 | def transFunc(data, otherArgs):
33 | """
34 | Called from lidarprocessor. Does the actual conversion to SPD V4
35 | """
36 | pulses = data.input1.getPulses()
37 | points = data.input1.getPointsByPulse()
38 | waveformInfo = data.input1.getWaveformInfo()
39 | revc = data.input1.getReceived()
40 |
41 | # set scaling and write header
42 | if data.info.isFirstBlock():
43 |
44 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
45 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
46 |
47 | # check the range
48 | translatecommon.checkRange(otherArgs.expectRange, points, pulses,
49 | waveformInfo)
50 | # any constant columns
51 | points, pulses, waveformInfo = translatecommon.addConstCols(otherArgs.constCols,
52 | points, pulses, waveformInfo)
53 |
54 | data.output1.setPoints(points)
55 | data.output1.setPulses(pulses)
56 | if waveformInfo is not None and waveformInfo.size > 0:
57 | data.output1.setWaveformInfo(waveformInfo)
58 | if revc is not None and revc.size > 0:
59 | data.output1.setReceived(revc)
60 |
61 | def translate(info, infile, outfile, expectRange=None,
62 | scaling=None, nullVals=None, constCols=None):
63 | """
64 | Main function which does the work.
65 |
66 | * Info is a fileinfo object for the input file.
67 | * infile and outfile are paths to the input and output files respectively.
68 | * expectRange is a list of tuples with (type, varname, min, max).
69 | * scaling is a list of tuples with (type, varname, dtype, gain, offset).
70 | * nullVals is a list of tuples with (type, varname, value)
71 | * constCols is a list of tupes with (type, varname, dtype, value)
72 |
73 | """
74 | scalingsDict = translatecommon.overRideDefaultScalings(scaling)
75 |
76 | # set up the variables
77 | dataFiles = lidarprocessor.DataFiles()
78 |
79 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
80 |
81 | controls = lidarprocessor.Controls()
82 | progress = cuiprogress.GDALProgressBar()
83 | controls.setProgress(progress)
84 |
85 | otherArgs = lidarprocessor.OtherArgs()
86 | otherArgs.scaling = scalingsDict
87 | otherArgs.expectRange = expectRange
88 | otherArgs.nullVals = nullVals
89 | otherArgs.constCols = constCols
90 |
91 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
92 | dataFiles.output1.setLiDARDriver('SPDV4')
93 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
94 |
95 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
96 | otherArgs=otherArgs)
97 |
98 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/lvishdf52spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between LVIS HDF5 and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from pylidar.lidarformats import spdv4
27 | from pylidar.lidarformats import lvisbin
28 | from rios import cuiprogress
29 |
30 | from . import translatecommon
31 |
32 | def transFunc(data, otherArgs):
33 | """
34 | Called from lidarprocessor. Does the actual conversion to SPD V4
35 | """
36 | pulses = data.input1.getPulses()
37 | points = data.input1.getPointsByPulse()
38 | waveformInfo = data.input1.getWaveformInfo()
39 | revc = data.input1.getReceived()
40 |
41 | # set scaling and write header
42 | if data.info.isFirstBlock():
43 |
44 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
45 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
46 |
47 | # check the range
48 | translatecommon.checkRange(otherArgs.expectRange, points, pulses,
49 | waveformInfo)
50 | # any constant columns
51 | points, pulses, waveformInfo = translatecommon.addConstCols(otherArgs.constCols,
52 | points, pulses, waveformInfo)
53 |
54 | data.output1.setPoints(points)
55 | data.output1.setPulses(pulses)
56 | if waveformInfo is not None and waveformInfo.size > 0:
57 | data.output1.setWaveformInfo(waveformInfo)
58 | if revc is not None and revc.size > 0:
59 | data.output1.setReceived(revc)
60 |
61 | def translate(info, infile, outfile, expectRange=None,
62 | scaling=None, nullVals=None, constCols=None):
63 | """
64 | Main function which does the work.
65 |
66 | * Info is a fileinfo object for the input file.
67 | * infile and outfile are paths to the input and output files respectively.
68 | * expectRange is a list of tuples with (type, varname, min, max).
69 | * scaling is a list of tuples with (type, varname, dtype, gain, offset).
70 | * nullVals is a list of tuples with (type, varname, value)
71 | * constCols is a list of tupes with (type, varname, dtype, value)
72 |
73 | """
74 | scalingsDict = translatecommon.overRideDefaultScalings(scaling)
75 |
76 | # set up the variables
77 | dataFiles = lidarprocessor.DataFiles()
78 |
79 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
80 |
81 | controls = lidarprocessor.Controls()
82 | progress = cuiprogress.GDALProgressBar()
83 | controls.setProgress(progress)
84 |
85 | otherArgs = lidarprocessor.OtherArgs()
86 | otherArgs.scaling = scalingsDict
87 | otherArgs.expectRange = expectRange
88 | otherArgs.nullVals = nullVals
89 | otherArgs.constCols = constCols
90 |
91 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
92 | dataFiles.output1.setLiDARDriver('SPDV4')
93 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
94 |
95 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
96 | otherArgs=otherArgs)
97 |
98 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/pulsewaves2spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between PulseWaves and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from pylidar.lidarformats import spdv4
27 | from pylidar.lidarformats import lvisbin
28 | from rios import cuiprogress
29 |
30 | from . import translatecommon
31 |
32 | def transFunc(data, otherArgs):
33 | """
34 | Called from lidarprocessor. Does the actual conversion to SPD V4
35 | """
36 | pulses = data.input1.getPulses()
37 | points = data.input1.getPointsByPulse()
38 | waveformInfo = data.input1.getWaveformInfo()
39 | revc = data.input1.getReceived()
40 | trans = data.input1.getTransmitted()
41 |
42 | # set scaling and write header
43 | if data.info.isFirstBlock():
44 |
45 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
46 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
47 |
48 | # check the range
49 | translatecommon.checkRange(otherArgs.expectRange, points, pulses,
50 | waveformInfo)
51 | # any constant columns
52 | points, pulses, waveformInfo = translatecommon.addConstCols(otherArgs.constCols,
53 | points, pulses, waveformInfo)
54 |
55 | data.output1.setPoints(points)
56 | data.output1.setPulses(pulses)
57 | if waveformInfo is not None and waveformInfo.size > 0:
58 | data.output1.setWaveformInfo(waveformInfo)
59 | if revc is not None and revc.size > 0:
60 | data.output1.setReceived(revc)
61 | if trans is not None and trans.size > 0:
62 | data.output1.setTransmitted(trans)
63 |
64 | def translate(info, infile, outfile, expectRange=None,
65 | scaling=None, nullVals=None, constCols=None):
66 | """
67 | Main function which does the work.
68 |
69 | * Info is a fileinfo object for the input file.
70 | * infile and outfile are paths to the input and output files respectively.
71 | * expectRange is a list of tuples with (type, varname, min, max).
72 | * scaling is a list of tuples with (type, varname, dtype, gain, offset).
73 | * nullVals is a list of tuples with (type, varname, value)
74 | * constCols is a list of tupes with (type, varname, dtype, value)
75 |
76 | """
77 | scalingsDict = translatecommon.overRideDefaultScalings(scaling)
78 |
79 | # set up the variables
80 | dataFiles = lidarprocessor.DataFiles()
81 |
82 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
83 |
84 | controls = lidarprocessor.Controls()
85 | progress = cuiprogress.GDALProgressBar()
86 | controls.setProgress(progress)
87 |
88 | otherArgs = lidarprocessor.OtherArgs()
89 | otherArgs.scaling = scalingsDict
90 | otherArgs.expectRange = expectRange
91 | otherArgs.nullVals = nullVals
92 | otherArgs.constCols = constCols
93 |
94 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
95 | dataFiles.output1.setLiDARDriver('SPDV4')
96 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
97 |
98 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
99 | otherArgs=otherArgs)
100 |
101 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/rieglrdb2spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between Riegl RDB and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import copy
24 | import json
25 | import numpy
26 | from osgeo import osr
27 | from pylidar import lidarprocessor
28 | from pylidar.lidarformats import generic
29 | from pylidar.lidarformats import spdv4
30 | from rios import cuiprogress
31 |
32 | from . import translatecommon
33 |
34 | def transFunc(data, otherArgs):
35 | """
36 | Called from translate(). Does the actual conversion to SPD V4
37 | """
38 | pulses = data.input1.getPulses()
39 | points = data.input1.getPointsByPulse()
40 |
41 | if points is not None:
42 | data.output1.translateFieldNames(data.input1, points,
43 | lidarprocessor.ARRAY_TYPE_POINTS)
44 | if pulses is not None:
45 | data.output1.translateFieldNames(data.input1, pulses,
46 | lidarprocessor.ARRAY_TYPE_PULSES)
47 |
48 | # set scaling and write header
49 | if data.info.isFirstBlock():
50 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
51 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
52 | rieglInfo = otherArgs.rieglInfo
53 |
54 | # TODO: is there likely to be only one gemetry?
55 | # We go for the first one
56 | beamGeom = list(rieglInfo["riegl.scan_pattern"].keys())[0]
57 | data.output1.setHeaderValue("PULSE_ANGULAR_SPACING_SCANLINE",
58 | rieglInfo["riegl.scan_pattern"][beamGeom]['phi_increment'])
59 | data.output1.setHeaderValue("PULSE_ANGULAR_SPACING_SCANLINE_IDX",
60 | rieglInfo["riegl.scan_pattern"][beamGeom]['theta_increment'])
61 | data.output1.setHeaderValue("SENSOR_BEAM_EXIT_DIAMETER",
62 | rieglInfo["riegl.beam_geometry"]['beam_exit_diameter'])
63 | data.output1.setHeaderValue("SENSOR_BEAM_DIVERGENCE",
64 | rieglInfo["riegl.beam_geometry"]['beam_divergence'])
65 |
66 | data.output1.setHeaderValue("PULSE_INDEX_METHOD", 0) # first return
67 |
68 | if otherArgs.epsg is not None:
69 | sr = osr.SpatialReference()
70 | sr.ImportFromEPSG(otherArgs.epsg)
71 | data.output1.setHeaderValue('SPATIAL_REFERENCE', sr.ExportToWkt())
72 | elif otherArgs.wkt is not None:
73 | data.output1.setHeaderValue('SPATIAL_REFERENCE', otherArgs.wkt)
74 |
75 | # check the range
76 | translatecommon.checkRange(otherArgs.expectRange, points, pulses)
77 | # any constant columns
78 | points, pulses, waveformInfo = translatecommon.addConstCols(otherArgs.constCols,
79 | points, pulses)
80 |
81 | data.output1.setPulses(pulses)
82 | if points is not None:
83 | data.output1.setPoints(points)
84 |
85 | def translate(info, infile, outfile, expectRange=None, scalings=None,
86 | nullVals=None, constCols=None, epsg=None, wkt=None):
87 | """
88 | Main function which does the work.
89 |
90 | * Info is a fileinfo object for the input file.
91 | * infile and outfile are paths to the input and output files respectively.
92 | * expectRange is a list of tuples with (type, varname, min, max).
93 | * scaling is a list of tuples with (type, varname, gain, offset).
94 | * nullVals is a list of tuples with (type, varname, value)
95 | * constCols is a list of tupes with (type, varname, dtype, value)
96 | """
97 | scalingsDict = translatecommon.overRideDefaultScalings(scalings)
98 |
99 | # set up the variables
100 | dataFiles = lidarprocessor.DataFiles()
101 |
102 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
103 |
104 | controls = lidarprocessor.Controls()
105 | progress = cuiprogress.GDALProgressBar()
106 | controls.setProgress(progress)
107 | controls.setSpatialProcessing(False)
108 |
109 | otherArgs = lidarprocessor.OtherArgs()
110 | # and the header so we don't collect it again
111 | otherArgs.rieglInfo = info.header
112 | # also need the default/overriden scaling
113 | otherArgs.scaling = scalingsDict
114 | # expected range of the data
115 | otherArgs.expectRange = expectRange
116 | # null values
117 | otherArgs.nullVals = nullVals
118 | # constant columns
119 | otherArgs.constCols = constCols
120 | otherArgs.epsg = epsg
121 | otherArgs.wkt = wkt
122 |
123 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
124 | dataFiles.output1.setLiDARDriver('SPDV4')
125 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
126 |
127 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
128 | otherArgs=otherArgs)
129 |
130 |
131 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/spdv32spdv4.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between SPDV3 and SPDV4 formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import copy
24 | import numpy
25 | from pylidar import lidarprocessor
26 | from pylidar.lidarformats import generic
27 | from pylidar.lidarformats import spdv4
28 | from rios import cuiprogress
29 |
30 | from . import translatecommon
31 |
32 | def transFunc(data, otherArgs):
33 | """
34 | Called from lidarprocessor. Does the actual conversion to SPD V4
35 | """
36 | pulses = data.input1.getPulses()
37 | points = data.input1.getPointsByPulse()
38 | waveformInfo = data.input1.getWaveformInfo()
39 | revc = data.input1.getReceived()
40 | trans = data.input1.getTransmitted()
41 |
42 | data.output1.translateFieldNames(data.input1, points,
43 | lidarprocessor.ARRAY_TYPE_POINTS)
44 | data.output1.translateFieldNames(data.input1, pulses,
45 | lidarprocessor.ARRAY_TYPE_PULSES)
46 |
47 | # work out scaling
48 | if data.info.isFirstBlock():
49 | translatecommon.setOutputScaling(otherArgs.scaling, data.output1)
50 | translatecommon.setOutputNull(otherArgs.nullVals, data.output1)
51 |
52 | # copy the index type accross - we can assume these values
53 | # are the same (at the moment)
54 | indexType = data.input1.getHeaderValue('INDEX_TYPE')
55 | data.output1.setHeaderValue('INDEX_TYPE', indexType)
56 |
57 | # check the range
58 | translatecommon.checkRange(otherArgs.expectRange, points, pulses,
59 | waveformInfo)
60 | # any constant columns
61 | points, pulses, waveformInfo = translatecommon.addConstCols(otherArgs.constCols,
62 | points, pulses, waveformInfo)
63 |
64 | data.output1.setPoints(points)
65 | data.output1.setPulses(pulses)
66 | data.output1.setWaveformInfo(waveformInfo)
67 | data.output1.setReceived(revc)
68 | data.output1.setTransmitted(trans)
69 |
70 | def translate(info, infile, outfile, expectRange=None, spatial=False,
71 | extent=None, scaling=None, nullVals=None, constCols=None):
72 | """
73 | Main function which does the work.
74 |
75 | * Info is a fileinfo object for the input file.
76 | * infile and outfile are paths to the input and output files respectively.
77 | * expectRange is a list of tuples with (type, varname, min, max).
78 | * spatial is True or False - dictates whether we are processing spatially or not.
79 | If True then spatial index will be created on the output file on the fly.
80 | * extent is a tuple of values specifying the extent to work with.
81 | xmin ymin xmax ymax
82 | * scaling is a list of tuples with (type, varname, gain, offset).
83 | * nullVals is a list of tuples with (type, varname, value)
84 | * constCols is a list of tupes with (type, varname, dtype, value)
85 | """
86 | scalingsDict = translatecommon.overRideDefaultScalings(scaling)
87 |
88 | # first we need to determine if the file is spatial or not
89 | if spatial and not info.hasSpatialIndex:
90 | msg = "Spatial processing requested but file does not have spatial index"
91 | raise generic.LiDARInvalidSetting(msg)
92 |
93 | if extent is not None and not spatial:
94 | msg = 'Extent can only be set when processing spatially'
95 | raise generic.LiDARInvalidSetting(msg)
96 |
97 | dataFiles = lidarprocessor.DataFiles()
98 |
99 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
100 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
101 | dataFiles.output1.setLiDARDriver('SPDV4')
102 | dataFiles.output1.setLiDARDriverOption('SCALING_BUT_NO_DATA_WARNING', False)
103 |
104 | controls = lidarprocessor.Controls()
105 | progress = cuiprogress.GDALProgressBar()
106 | controls.setProgress(progress)
107 | controls.setSpatialProcessing(spatial)
108 |
109 | if extent is not None:
110 | extent = [float(x) for x in extent]
111 | binSize = info.header['BIN_SIZE']
112 | pixgrid = pixelgrid.PixelGridDefn(xMin=extent[0], yMin=extent[1],
113 | xMax=extent[2], yMax=extent[3], xRes=binSize, yRes=binSize)
114 | controls.setReferencePixgrid(pixgrid)
115 | controls.setFootprint(lidarprocessor.BOUNDS_FROM_REFERENCE)
116 |
117 | otherArgs = lidarprocessor.OtherArgs()
118 | otherArgs.scaling = scalingsDict
119 | otherArgs.expectRange = expectRange
120 | otherArgs.nullVals = nullVals
121 | otherArgs.constCols = constCols
122 |
123 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls,
124 | otherArgs=otherArgs)
125 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/spdv42las.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between SPDV4 and LAS formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from pylidar.lidarformats import las
27 | from rios import cuiprogress
28 |
29 | def setOutputScaling(points, indata, outdata):
30 | """
31 | Sets the output scaling for las. Tries to copy scaling accross.
32 | """
33 | for colName in points.dtype.fields:
34 | try:
35 | gain, offset = indata.getScaling(colName,
36 | lidarprocessor.ARRAY_TYPE_POINTS)
37 | except generic.LiDARArrayColumnError:
38 | # no scaling
39 | continue
40 |
41 | indtype = indata.getNativeDataType(colName,
42 | lidarprocessor.ARRAY_TYPE_POINTS)
43 | ininfo = numpy.iinfo(indtype)
44 |
45 | try:
46 | outdtype = outdata.getNativeDataType(colName,
47 | lidarprocessor.ARRAY_TYPE_POINTS)
48 | except generic.LiDARArrayColumnError:
49 | # OK so it wasn't one of the compulsory fields,
50 | # set to same type as in input file
51 | outdtype = indtype
52 | outdata.setNativeDataType(colName,
53 | lidarprocessor.ARRAY_TYPE_POINTS, outdtype)
54 |
55 | if numpy.issubdtype(outdtype, numpy.floating):
56 | # no scaling required
57 | continue
58 |
59 | outinfo = numpy.iinfo(outdtype)
60 | maxVal = offset + ((ininfo.max - ininfo.min) * gain)
61 | # adjust gain
62 | # assume min always 0. Not currect in the las case since
63 | # X, Y and Z are I32 which seems a bit weird so keep it all positive
64 | gain = (maxVal - offset) / outinfo.max
65 |
66 | if colName == "Y" and gain < 0:
67 | # we need to do another fiddle since las is strict
68 | # in its min and max for Y
69 | # not sure if this should be in the driver...
70 | gain = abs(gain)
71 | offset = maxVal
72 |
73 | try:
74 | outdata.setScaling(colName, lidarprocessor.ARRAY_TYPE_POINTS,
75 | gain, offset)
76 | except generic.LiDARArrayColumnError:
77 | # failure should mean that scaling cannot be set for this column
78 | # (should be an essential field).
79 | pass
80 |
81 | def transFunc(data):
82 | """
83 | Called from pylidar. Does the actual conversion to las
84 | """
85 | pulses = data.input1.getPulses()
86 | points = data.input1.getPointsByPulse()
87 | waveformInfo = data.input1.getWaveformInfo()
88 | revc = data.input1.getReceived()
89 |
90 | # TODO: this does not appear to be implemented for writing LAS
91 | # not sure if it should be. LAS driver seems to know the SPDV4 names
92 | # of things, so perhaps not relevant.
93 | #if points is not None:
94 | # data.output1.translateFieldNames(data.input1, points,
95 | # lidarprocessor.ARRAY_TYPE_POINTS)
96 | #if pulses is not None:
97 | # data.output1.translateFieldNames(data.input1, pulses,
98 | # lidarprocessor.ARRAY_TYPE_PULSES)
99 |
100 | # set scaling
101 | if data.info.isFirstBlock():
102 | # scaling only appears to relevant for points. I think.
103 | setOutputScaling(points, data.input1, data.output1)
104 |
105 | data.output1.setPoints(points)
106 | data.output1.setPulses(pulses)
107 | if waveformInfo is not None:
108 | data.output1.setWaveformInfo(waveformInfo)
109 | if revc is not None:
110 | data.output1.setReceived(revc)
111 |
112 | def translate(info, infile, outfile, spatial=False, extent=None):
113 | """
114 | Does the translation between SPD V4 and .las format files.
115 |
116 | * Info is a fileinfo object for the input file.
117 | * infile and outfile are paths to the input and output files respectively.
118 | * spatial is True or False - dictates whether we are processing spatially or not.
119 | If True then spatial index will be created on the output file on the fly.
120 | * extent is a tuple of values specifying the extent to work with.
121 | xmin ymin xmax ymax
122 |
123 | Currently does not take any command line scaling options so LAS scaling
124 | will be the same as the SPDV4 input file scaling. Not sure if this is
125 | a problem or not...
126 | """
127 | # first we need to determine if the file is spatial or not
128 | if spatial and not info.has_Spatial_Index:
129 | msg = "Spatial processing requested but file does not have spatial index"
130 | raise generic.LiDARInvalidSetting(msg)
131 |
132 | # get the waveform info
133 | print('Getting waveform description')
134 | try:
135 | wavePacketDescr = las.getWavePacketDescriptions(infile)
136 | except generic.LiDARInvalidData:
137 | wavePacketDescr = None
138 |
139 | # set up the variables
140 | dataFiles = lidarprocessor.DataFiles()
141 |
142 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
143 |
144 | controls = lidarprocessor.Controls()
145 | progress = cuiprogress.GDALProgressBar()
146 | controls.setProgress(progress)
147 | controls.setSpatialProcessing(spatial)
148 |
149 | if extent is not None:
150 | extent = [float(x) for x in extent]
151 | binSize = info.header['BIN_SIZE']
152 | pixgrid = pixelgrid.PixelGridDefn(xMin=extent[0], yMin=extent[1],
153 | xMax=extent[2], yMax=extent[3], xRes=binSize, yRes=binSize)
154 | controls.setReferencePixgrid(pixgrid)
155 | controls.setFootprint(lidarprocessor.BOUNDS_FROM_REFERENCE)
156 |
157 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
158 | dataFiles.output1.setLiDARDriver('LAS')
159 | if wavePacketDescr is not None:
160 | dataFiles.output1.setLiDARDriverOption('WAVEFORM_DESCR',
161 | wavePacketDescr)
162 |
163 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls)
164 |
--------------------------------------------------------------------------------
/pylidar/toolbox/translate/spdv42pulsewaves.py:
--------------------------------------------------------------------------------
1 | """
2 | Handles conversion between SPDV4 and PulseWaves formats
3 | """
4 |
5 | # This file is part of PyLidar
6 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function, division
22 |
23 | import numpy
24 | from pylidar import lidarprocessor
25 | from pylidar.lidarformats import generic
26 | from rios import cuiprogress
27 |
28 | def transFunc(data):
29 | """
30 | Called from pylidar. Does the actual conversion to pulsewaves
31 | """
32 | pulses = data.input1.getPulses()
33 | points = data.input1.getPointsByPulse()
34 | waveformInfo = data.input1.getWaveformInfo()
35 | revc = data.input1.getReceived()
36 | trans = data.input1.getTransmitted()
37 |
38 | data.output1.setPoints(points)
39 | data.output1.setPulses(pulses)
40 | if waveformInfo is not None:
41 | data.output1.setWaveformInfo(waveformInfo)
42 | if revc is not None:
43 | data.output1.setReceived(revc)
44 | if trans is not None:
45 | data.output1.setTransmitted(trans)
46 |
47 | def translate(info, infile, outfile):
48 | """
49 | Does the translation between SPD V4 and PulseWaves format files.
50 |
51 | * Info is a fileinfo object for the input file.
52 | * infile and outfile are paths to the input and output files respectively.
53 |
54 | """
55 | # set up the variables
56 | dataFiles = lidarprocessor.DataFiles()
57 |
58 | dataFiles.input1 = lidarprocessor.LidarFile(infile, lidarprocessor.READ)
59 |
60 | controls = lidarprocessor.Controls()
61 | progress = cuiprogress.GDALProgressBar()
62 | controls.setProgress(progress)
63 |
64 | dataFiles.output1 = lidarprocessor.LidarFile(outfile, lidarprocessor.CREATE)
65 | dataFiles.output1.setLiDARDriver('PulseWaves')
66 |
67 | lidarprocessor.doProcessing(transFunc, dataFiles, controls=controls)
68 |
--------------------------------------------------------------------------------
/pylidar/toolbox/visualisation.py:
--------------------------------------------------------------------------------
1 | """
2 | Functions which can be used to help with the visualisation of the point clouds
3 | """
4 | # This file is part of PyLidar
5 | # Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | from __future__ import print_function, division
21 |
22 | import os
23 | import numpy
24 |
25 | havepylidarviewer = True
26 | try:
27 | import pylidarviewer
28 | except ImportError as pylidarviewerErr:
29 | havepylidarviewer = False
30 |
31 | haveMatPlotLib = True
32 | try:
33 | import matplotlib.colors as clr
34 | import matplotlib.cm as cm
35 | except ImportError as pltErr:
36 | haveMatPlotLib = False
37 |
38 | class VisualisationError(Exception):
39 | "A Visualisation error has occured"
40 |
41 | def rescaleRGB(r, g, b, bit8=True):
42 | """
43 | A function which rescales the RGB arrays to a range of 0-1. Where
44 | bit8 is True the arrays will just be divided by 255 otherwise a
45 | min-max rescaling will be applied independently to each array.
46 | """
47 |
48 | if bit8:
49 | r = r/255
50 | g = g/255
51 | b = b/255
52 | else:
53 | rMin = numpy.min(r)
54 | rMax = numpy.max(r)
55 | r = (r-rMin)/(rMax-rMin)
56 |
57 | gMin = numpy.min(g)
58 | gMax = numpy.max(g)
59 | g = (g-gMin)/(gMax-gMin)
60 |
61 | bMin = numpy.min(b)
62 | bMax = numpy.max(b)
63 | b = (b-bMin)/(bMax-bMin)
64 | return r,g,b
65 |
66 | def getClassColoursDict():
67 | colourDict = {}
68 |
69 | colourDict[0] = [1,1,1,1] # Unknown
70 | colourDict[1] = [0,0,0,1] # Unclassified
71 | colourDict[2] = [0,0,0,1] # Created
72 | colourDict[3] = [1,0,0,2] # Ground
73 | colourDict[4] = [0,1,0,1] # Low Veg
74 | colourDict[5] = [0,0.803921569,0,1] # Med Veg
75 | colourDict[6] = [0,0.501960784,0,1] # High Veg
76 | colourDict[7] = [0.545098039,0.352941176,0,1] # Building
77 | colourDict[8] = [0,0,1,1] # Water
78 | colourDict[9] = [0.545098039,0.270588235,0.003921569,1] # Trunk
79 | colourDict[10] = [0,0.803921569,0,1] # Foliage
80 | colourDict[11] = [0.803921569,0.2,0.2,1] # Branch
81 | colourDict[12] = [0.721568627,0.721568627,0.721568627,1] # Wall
82 |
83 | return colourDict
84 |
85 | def colourByClass(classArr, colourDict=None):
86 | """
87 | A function which returns RGB and point size arrays given an input
88 | array of numerical classes. Where colourDict has been specified then
89 | the default colours (specified in getClassColoursDict()) can be overiden.
90 | """
91 | if colourDict == None:
92 | colourDict = getClassColoursDict()
93 |
94 | classPres = numpy.unique(classArr)
95 |
96 | r = numpy.zeros_like(classArr, dtype=numpy.float)
97 | g = numpy.zeros_like(classArr, dtype=numpy.float)
98 | b = numpy.zeros_like(classArr, dtype=numpy.float)
99 | s = numpy.ones_like(classArr, dtype=numpy.float)
100 |
101 |
102 | for classVal in classPres:
103 | r[classArr==classVal] = colourDict[classVal][0]
104 | g[classArr==classVal] = colourDict[classVal][1]
105 | b[classArr==classVal] = colourDict[classVal][2]
106 | s[classArr==classVal] = colourDict[classVal][3]
107 |
108 | return r,g,b,s
109 |
110 |
111 | def createRGB4Param(data, stretch='linear', colourMap='Spectral'):
112 | """
113 | A function to take a data column (e.g., intensity) and colour into a
114 | set of rgb arrays for visualisation. colourMap is a matplotlib colour map
115 | (see http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps) for colouring
116 | the input data. stretch options are 'linear' or 'stddev'
117 | """
118 | # Check matplotlib is available
119 | if not haveMatPlotLib:
120 | msg = "The matplotlib module is required for this function could not be imported\n\t" + str(pltErr)
121 | raise VisualisationError(msg)
122 |
123 | if stretch == 'stddev':
124 | min = numpy.min(data)
125 | max = numpy.max(data)
126 | stddev = numpy.std(data)
127 | mean = numpy.mean(data)
128 | minData = mean - (2 * mean)
129 | if minData < min:
130 | minData = min
131 | maxData = mean + (2 * mean)
132 | if maxData > max:
133 | maxData = max
134 |
135 | nData = numpy.zeros_like(data, dtype=numpy.float)
136 | nData = (data - minData)/(maxData - minData)
137 | nData[nData < 0] = 0
138 | nData[nData > 1] = 1
139 |
140 | else:
141 | norm = clr.Normalize(numpy.min(data), numpy.max(data))
142 | nData = norm(data)
143 |
144 | my_cmap = cm.get_cmap(colourMap)
145 | rgba = my_cmap(nData)
146 |
147 | r = rgba[:,0:1].flatten()
148 | g = rgba[:,1:2].flatten()
149 | b = rgba[:,2:3].flatten()
150 |
151 | return r,g,b
152 |
153 | def displayPointCloud(x, y, z, r, g, b, s):
154 | """
155 | Display the point cloud in 3D where arrays (all of the same length) are
156 | provided for the X, Y, Z position of the points and then the RGB (range 0-1)
157 | values to colour the points and then the point sizes (s).
158 |
159 | X and Y values are centred around 0,0 and then X, Y, Z values are rescale
160 | before display
161 | """
162 | # Check if pylidarviewer is available
163 | if not havepylidarviewer:
164 | msg = "The pylidarviewer module is required for this function could not be imported\n\t" + str(pylidarviewerErr)
165 | raise VisualisationError(msg)
166 |
167 | x = x - numpy.min(x)
168 | y = y - numpy.min(y)
169 | z = z - numpy.min(z)
170 |
171 | medianX = numpy.median(x)
172 | medianY = numpy.median(y)
173 |
174 | maxX = numpy.max(x)
175 | maxY = numpy.max(y)
176 | maxZ = numpy.max(z)
177 |
178 | maxVal = maxX
179 | if maxY > maxVal:
180 | maxVal = maxY
181 | if maxZ > maxVal:
182 | maxVal = maxZ
183 |
184 | x = (x-medianX)/maxVal
185 | y = (y-medianY)/maxVal
186 | z = z/maxVal
187 |
188 | pylidarviewer.DisplayPointCloud(x, y, z, r, g, b, s)
189 |
190 |
--------------------------------------------------------------------------------
/src/pylidar.h:
--------------------------------------------------------------------------------
1 | /*
2 | * pylidar.h
3 | *
4 | *
5 | * This file is part of PyLidar
6 | * Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | *
8 | * This program is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * This program is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | *
21 | */
22 |
23 | /*
24 | A collection of C functions for dealing with numpy structured
25 | arrays from C/C++.
26 | */
27 |
28 | #ifndef PYLIDAR_H
29 | #define PYLIDAR_H
30 |
31 | #include
32 | #include "numpy/arrayobject.h"
33 |
34 | #ifdef __cplusplus
35 | extern "C" {
36 | #endif
37 |
38 | /* call first - preferably in the init() of your module
39 | this sets up the connection to numpy */
40 | #if PY_MAJOR_VERSION >= 3
41 | PyObject *pylidar_init(void);
42 | #else
43 | void pylidar_init(void);
44 | #endif
45 | /* print error - used internally */
46 | void pylidar_error(char *errstr, ...);
47 |
48 | /* Helper function to get information about a named field within an array
49 | pass null for params you not interested in */
50 | int pylidar_getFieldDescr(PyArrayObject *pArray, const char *pszName, int *pnOffset, char *pcKind, int *pnSize, int *pnLength);
51 |
52 | /* return a field as a npy_int64 array. Caller to delete */
53 | npy_int64 *pylidar_getFieldAsInt64(PyArrayObject *pArray, const char *pszName);
54 |
55 | /* return a field as a double array. Caller to delete */
56 | double *pylidar_getFieldAsFloat64(PyArrayObject *pArray, const char *pszName);
57 |
58 | /* return a tuple containing the strings in data. data should be NULL terminated */
59 | PyObject *pylidar_stringArrayToTuple(const char *data[]);
60 |
61 |
62 | /* structure for defining a numpy structured array from a C one
63 | create using CREATE_FIELD_DEFN below */
64 | typedef struct
65 | {
66 | const char *pszName;
67 | char cKind; /* 'i' for signed int, 'u' for unsigned int, 'f' for float */
68 | int nSize;
69 | int nOffset;
70 | int nStructTotalSize;
71 | char bIgnore; /* set to 1 to not report this field to numpy */
72 | } SpylidarFieldDefn;
73 |
74 | /* workaround for gcc 4.7.4 (maybe other versions?) where offsetof isn't recognised from
75 | las.cpp (but is from riegl.cpp). */
76 | #ifdef __GNUC__
77 | #define CREATE_FIELD_DEFN(STRUCT, FIELD, KIND) \
78 | {#FIELD, KIND, sizeof(((STRUCT*)0)->FIELD), __builtin_offsetof(STRUCT, FIELD), sizeof(STRUCT), 0}
79 | #else
80 | #define CREATE_FIELD_DEFN(STRUCT, FIELD, KIND) \
81 | {#FIELD, KIND, sizeof(((STRUCT*)0)->FIELD), offsetof(STRUCT, FIELD), sizeof(STRUCT), 0}
82 | #endif
83 |
84 | /*
85 | Here is an example of use:
86 | Say you had a structure like this:
87 | typedef struct {
88 | double x,
89 | double y,
90 | int count
91 | } SMyStruct;
92 |
93 | Create an array of structures defining the fields like this:
94 | static SpylidarFieldDefn fields[] = {
95 | CREATE_FIELD_DEFN(SMyStruct, x, 'f'),
96 | CREATE_FIELD_DEFN(SMyStruct, y, 'f'),
97 | CREATE_FIELD_DEFN(SMyStruct, count, 'i'),
98 | {NULL} // Sentinel
99 | };
100 |
101 | Kind is one of the following (see http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html)
102 | 'b' boolean
103 | 'i' (signed) integer
104 | 'u' unsigned integer
105 | 'f' floating-point
106 | 'c' complex-floating point
107 | 'O' (Python) objects
108 | 'S', 'a' (byte-)string
109 | 'U' Unicode
110 | 'V' raw data (void)
111 |
112 | */
113 | /* Wrap an existing C array of structures and return as a numpy array */
114 | /* Python will free data when finished */
115 | /* Use PyArray_malloc to obtain the memory */
116 | PyArrayObject *pylidar_structArrayToNumpy(void *pStructArray, npy_intp nElems, SpylidarFieldDefn *pDefn);
117 |
118 | /* Returns NULL on failure, but doesn't set exception state */
119 | /* On success, a new reference is returned */
120 | PyArray_Descr *pylidar_getDtypeForField(SpylidarFieldDefn *pDefn, const char *pszFieldname);
121 |
122 | /* Set the ignore state of a named field in pDefn */
123 | int pylidar_setIgnore(SpylidarFieldDefn *pDefn, const char *pszFieldname, char bIgnore);
124 |
125 | #ifdef __cplusplus
126 | } /* extern C */
127 | #endif
128 |
129 | #endif /*PYLIDAR_H*/
130 |
131 |
--------------------------------------------------------------------------------
/src/pylmatrix.h:
--------------------------------------------------------------------------------
1 | /*
2 | * matrix.h
3 | *
4 | *
5 | * This file is part of PyLidar
6 | * Copyright (C) 2015 John Armston, Pete Bunting, Neil Flood, Sam Gillingham
7 | *
8 | * This program is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * This program is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | *
21 | */
22 |
23 | #ifndef PYLMATRIX_H
24 | #define PYLMATRIX_H
25 |
26 | // This class implements simple matrix addition and
27 | // multiplication.
28 | // Linking in BLAS etc seemed overkill for the tiny (4x4)
29 | // matrix sizes used in the C++ driver parts of PyLidar
30 | // hence this small (and probably inefficient) code.
31 |
32 | #include
33 | #include
34 | #include
35 |
36 | namespace pylidar
37 | {
38 |
39 | template
40 | class CMatrix
41 | {
42 | public:
43 | CMatrix(const CMatrix &other)
44 | {
45 | m_nRows = other.m_nRows;
46 | m_nCols = other.m_nCols;
47 | m_pData = (T*)malloc(sizeof(T) * m_nRows * m_nCols);
48 | memcpy(m_pData, other.m_pData, sizeof(T) * m_nRows * m_nCols);
49 | }
50 | CMatrix(int nRows, int nCols)
51 | {
52 | m_nRows = nRows;
53 | m_nCols = nCols;
54 | m_pData = (T*)malloc(sizeof(T) * nRows * nCols);
55 | }
56 | // assumed type matches T - no checking happens
57 | // apart from a rough check on size
58 | CMatrix(PyArrayObject *pNumpyArray)
59 | {
60 | if( PyArray_NDIM(pNumpyArray) != 2)
61 | {
62 | throw std::length_error("numpy array must be 2 dimensional");
63 | }
64 | if( PyArray_ITEMSIZE(pNumpyArray) != sizeof(T) )
65 | {
66 | throw std::invalid_argument("numpy array elements not same size as matrix type");
67 | }
68 |
69 | m_nRows = PyArray_DIM(pNumpyArray, 0);
70 | m_nCols = PyArray_DIM(pNumpyArray, 1);
71 | m_pData = (T*)malloc(sizeof(T) * m_nRows * m_nCols);
72 | T *p;
73 | for(int r = 0; r < m_nRows; ++r)
74 | {
75 | for(int c = 0; c < m_nCols; ++c)
76 | {
77 | p = (T*)PyArray_GETPTR2(pNumpyArray, r, c);
78 | set(r, c, *p);
79 | }
80 | }
81 | }
82 | ~CMatrix()
83 | {
84 | free(m_pData);
85 | }
86 |
87 | T get(int row, int col) const
88 | {
89 | T *p = (m_pData + (row * m_nCols)) + col;
90 | return *p;
91 | }
92 | void set(int row, int col, T v)
93 | {
94 | T *p = (m_pData + (row * m_nCols)) + col;
95 | *p = v;
96 | }
97 |
98 | CMatrix add(CMatrix &other) const
99 | {
100 | if( (m_nRows != other.m_nRows) || (m_nCols != other.m_nCols) )
101 | {
102 | throw std::length_error("matrices must be same size");
103 | }
104 |
105 | CMatrix result(m_nRows, m_nCols);
106 | T v;
107 | for(int r = 0; r < m_nRows; ++r)
108 | {
109 | for(int c = 0; c < m_nCols; ++c)
110 | {
111 | v = get(r, c) + other.get(r, c);
112 | result.set(r, c, v);
113 | }
114 | }
115 |
116 | return result;
117 | }
118 |
119 | CMatrix multiply(CMatrix &other) const
120 | {
121 | if( m_nCols != other.m_nRows )
122 | {
123 | throw std::length_error("number of cols in a must equal number of rows in b");
124 | }
125 |
126 | CMatrix result(m_nRows, other.m_nCols);
127 | T v;
128 | for(int r = 0; r < m_nRows; ++r)
129 | {
130 | for(int c = 0; c < other.m_nCols; ++c)
131 | {
132 | v = 0;
133 | for(int k = 0; k < m_nCols; ++k)
134 | {
135 | v += get(r, k) * other.get(k, c);
136 | }
137 | result.set(r, c, v);
138 | }
139 | }
140 |
141 | return result;
142 | }
143 |
144 | // return a new numpy array with the contents
145 | // of this object. typenum should correspond to T.
146 | PyArrayObject *getAsNumpyArray(int typenum=NPY_FLOAT) const
147 | {
148 | npy_intp dims[2];
149 | dims[0] = m_nRows;
150 | dims[1] = m_nCols;
151 | PyArrayObject *pNumpyArray = (PyArrayObject*)PyArray_SimpleNew(2, dims, typenum);
152 | T *p;
153 | for(int r = 0; r < m_nRows; ++r)
154 | {
155 | for(int c = 0; c < m_nCols; ++c)
156 | {
157 | p = (T*)PyArray_GETPTR2(pNumpyArray, r, c);
158 | *p = get(r, c);
159 | }
160 | }
161 | return pNumpyArray;
162 | }
163 |
164 | int getNumRows() const { return m_nRows; }
165 | int getNumCols() const { return m_nCols; }
166 |
167 | private:
168 | int m_nRows;
169 | int m_nCols;
170 | T *m_pData;
171 | };
172 |
173 | } // namespace pylidar
174 |
175 | #endif //PYLMATRIX_H
176 |
--------------------------------------------------------------------------------