├── .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 | --------------------------------------------------------------------------------