Package: pdal
PDAL Python support allows you to process data with PDAL intoNumpyarrays.
It provides a PDAL extension module to control Python interaction with PDAL.
Additionally, you can use it to fetchschemaandmetadatafrom PDAL operations.InstallationPyPIPDAL Python support is installable via PyPI:pip install PDALGitHubThe repository for PDAL’s Python extension is available athttps://github.com/PDAL/pythonPython support released independently from PDAL itself as of PDAL 1.7.UsageSimpleGiven the following pipeline, which simply reads anASPRS LASfile and
sorts it by theXdimension:json="""
{
  "pipeline": [
    "1.2-with-color.las",
    {
        "type": "filters.sort",
        "dimension": "X"
    }
  ]
}"""importpdalpipeline=pdal.Pipeline(json)count=pipeline.execute()arrays=pipeline.arraysmetadata=pipeline.metadatalog=pipeline.logProgrammatic Pipeline ConstructionThe previous example specified the pipeline as a JSON string. Alternatively, a
pipeline can be constructed by creatingStageinstances and piping them
together. For example, the previous pipeline can be specified as:pipeline=pdal.Reader("1.2-with-color.las")|pdal.Filter.sort(dimension="X")Stage ObjectsA stage is an instance ofpdal.Reader,pdal.Filterorpdal.Writer.A stage can be instantiated by passing as keyword arguments the options
applicable to the respective PDAL stage. For more on PDAL stages and their
options, check the PDAL documentation onStage Objects.Thefilenameoption ofReadersandWritersas well as thetypeoption ofFilterscan be passed positionally as the first argument.Theinputsoption specifies a sequence of stages to be set as input to the
current stage. Each input can be either the string tag of another stage, or
theStageinstance itself.TheReader,FilterandWriterclasses come with static methods for
all the respective PDAL drivers. For example,pdal.Filter.head()is a
shortcut forpdal.Filter(type="filters.head"). These methods are
auto-generated by introspectingpdaland the available options are
included in each method’s docstring:>>> help(pdal.Filter.head)
Help on function head in module pdal.pipeline:

head(**kwargs)
    Return N points from beginning of the point cloud.

    user_data: User JSON
    log: Debug output filename
    option_file: File from which to read additional options
    where: Expression describing points to be passed to this filter
    where_merge='auto': If 'where' option is set, describes how skipped points should be merged with kept points in standard mode.
    count='10': Number of points to return from beginning.  If 'invert' is true, number of points to drop from the beginning.
    invert='false': If true, 'count' specifies the number of points to skip from the beginning.Pipeline ObjectsApdal.Pipelineinstance can be created from:a JSON string:Pipeline(json_string)a sequence ofStageinstances:Pipeline([stage1,stage2])a singleStagewith theStage.pipelinemethod:stage.pipeline()nothing:Pipeline()creates a pipeline with no stages.joiningStageand/or otherPipelineinstances together with the pipe
operator (|):stage1 | stage2stage1 | pipeline1pipeline1 | stage1pipeline1 | pipeline2Every application of the pipe operator creates a newPipelineinstance. To
update an existingPipelineuse the respective in-place pipe operator (|=):# update pipeline in-placepipeline=pdal.Pipeline()pipeline|=stagepipeline|=pipeline2Reading using Numpy ArraysThe following more complex scenario demonstrates the full cycling between
PDAL and Python:Read a small testfile from GitHub into a Numpy arrayFilters the array with Numpy for IntensityPass the filtered array to PDAL to be filtered againWrite the final filtered array to a LAS file and aTileDBarray
via theTileDB-PDAL integrationusing theTileDB writer pluginimportpdaldata="https://github.com/PDAL/PDAL/blob/master/test/data/las/1.2-with-color.las?raw=true"pipeline=pdal.Reader.las(filename=data).pipeline()print(pipeline.execute())# 1065 points# Get the data from the first array# [array([(637012.24, 849028.31, 431.66, 143, 1,# 1, 1, 0, 1,  -9., 132, 7326, 245380.78254963,  68,  77,  88),# dtype=[('X', '<f8'), ('Y', '<f8'), ('Z', '<f8'), ('Intensity', '<u2'),# ('ReturnNumber', 'u1'), ('NumberOfReturns', 'u1'), ('ScanDirectionFlag', 'u1'),# ('EdgeOfFlightLine', 'u1'), ('Classification', 'u1'), ('ScanAngleRank', '<f4'),# ('UserData', 'u1'), ('PointSourceId', '<u2'),# ('GpsTime', '<f8'), ('Red', '<u2'), ('Green', '<u2'), ('Blue', '<u2')])arr=pipeline.arrays[0]# Filter out entries that have intensity < 50intensity=arr[arr["Intensity"]>30]print(len(intensity))# 704 points# Now use pdal to clamp points that have intensity 100 <= v < 300pipeline=pdal.Filter.range(limits="Intensity[100:300)").pipeline(intensity)print(pipeline.execute())# 387 pointsclamped=pipeline.arrays[0]# Write our intensity data to a LAS file and a TileDB array. For TileDB it is# recommended to use Hilbert ordering by default with geospatial point cloud data,# which requires specifying a domain extent. This can be determined automatically# from a stats filter that computes statistics about each dimension (min, max, etc.).pipeline=pdal.Writer.las(filename="clamped.las",offset_x="auto",offset_y="auto",offset_z="auto",scale_x=0.01,scale_y=0.01,scale_z=0.01,).pipeline(clamped)pipeline|=pdal.Filter.stats()|pdal.Writer.tiledb(array_name="clamped")print(pipeline.execute())# 387 points# Dump the TileDB array schemaimporttiledbwithtiledb.open("clamped")asa:print(a.schema)Executing Streamable PipelinesStreamable pipelines (pipelines that consist exclusively of streamable PDAL
stages) can be executed in streaming mode viaPipeline.iterator(). This
returns an iterator object that yields Numpy arrays of up tochunk_sizesize
(default=10000) at a time.importpdalpipeline=pdal.Reader("test/data/autzen-utm.las")|pdal.Filter.range(limits="Intensity[80:120)")forarrayinpipeline.iterator(chunk_size=500):print(len(array))# or to concatenate all arrays into one# full_array = np.concatenate(list(pipeline))Pipeline.iterator()also takes an optionalprefetchparameter (default=0)
to allow prefetching up to to this number of arrays in parallel and buffering
them until they are yielded to the caller.If you just want to execute a streamable pipeline in streaming mode and don’t
need to access the data points (typically when the pipeline has Writer stage(s)),
you can use thePipeline.execute_streaming(chunk_size)method instead. This
is functionally equivalent tosum(map(len,pipeline.iterator(chunk_size)))but more efficient as it avoids allocating and filling any arrays in memory.Accessing Mesh DataSome PDAL stages (for instancefilters.delaunay) create TIN type mesh data.This data can be accessed in Python using thePipeline.meshesproperty, which returns anumpy.ndarrayof shape (1,n) where n is the number of Triangles in the mesh.If the PointView contains no mesh data, then n = 0.Each Triangle is a tuple(A,B,C)where A, B and C are indices into the PointView identifying the point that is the vertex for the Triangle.Meshio IntegrationThe meshes property provides the face data but is not easy to use as a mesh. Therefore, we have provided optional Integration
into theMeshiolibrary.Thepdal.Pipelineclass provides theget_meshio(idx: int)->meshio.Meshmethod. This
method creates aMeshobject from thePointViewarray and mesh properties.NoteThe meshio integration requires that meshio is installed (e.g.pip install meshio). If it is not, then the method fails with an informative RuntimeError.Simple use of the functionality could be as follows:importpdal...pl=pdal.Pipeline(pipeline)pl.execute()mesh=pl.get_meshio(0)mesh.write('test.obj')Advanced Mesh Use CaseUSE-CASE : Take a LiDAR map, create a mesh from the ground points, split into tiles and store the tiles in PostGIS.NoteLikePipeline.arrays,Pipeline.meshesreturns a list ofnumpy.ndarrayto provide for the case where the output from a Pipeline is multiple PointViews(example using 1.2-with-color.las and not doing the ground classification for clarity)importpdalimportpsycopg2importiopl=(pdal.Reader(".../python/test/data/1.2-with-color.las")|pdal.Filter.splitter(length=1000)|pdal.Filter.delaunay())pl.execute()conn=psycopg(%CONNNECTION_STRING%)buffer=io.StringIOforidxinrange(len(pl.meshes)):m=pl.get_meshio(idx)ifm:m.write(buffer,file_format="wkt")withconn.cursor()ascurr:curr.execute("INSERT INTO %table-name% (mesh) VALUES (ST_GeomFromEWKT(%(ewkt)s)",{"ewkt":buffer.getvalue()})conn.commit()conn.close()buffer.close()RequirementsPDAL 2.5+Python >=3.9Pybind11 (egpip install pybind11[global])Numpy (egpip install numpy)scikit-build (egpip install scikit-build)Changes3.2.3Do not build and include wheels in distro3.2.2Implement move ctor to satisfy MSVC 2019https://github.com/PDAL/python/commit/667f56bd0ee465f55a14636986e80b0a9cefcf143.2.1implement #129, add pandas DataFrame i/o for convenience by @hobu inhttps://github.com/PDAL/python/pull/130harden getMetadata and related calls from getting non-utf-8 ‘json’  by @hobu
inhttps://github.com/PDAL/python/pull/140ignore DataFrame test if not GeoPandas, give up on Python 3.7 builds by @hobu
inhttps://github.com/PDAL/python/pull/1373.2.0PDAL base library 2.4.0+ is requiredCMake project name updated to pdal-pythonsrswkt2property added to allow fetching of SRS infopip builds require cmake >= 3.11CMAKE_CXX_STANDARD set to c++17 to match PDAL 2.4.xDriver and optionsactuallyuses the library instead of
shelling out topdalapplication :)_get_json renamed to toJSON and made publicFix #119, ‘json’ optional kwarg put back for nowDEVELOPMENT_COMPONENT in CMake FindPython skipped on OSXMake sure ‘type’ gets set when serializing to JSON3.1.0Breaking change– pipeline.metadata now returns a dictionary from
json.loads instead of a string.pipeline.quickinfo will fetch the PDAL preview() information for a data source.
You can use this to fetch header or other information without reading data.https://github.com/PDAL/python/pull/109PDAL driver and option collection now uses the PDAL library directly rather
than shelling out to the pdal commandhttps://github.com/PDAL/python/pull/107Pipelines now support pickling for use with things like Daskhttps://github.com/PDAL/python/pull/1103.0.0Pythonic pipeline creationhttps://github.com/PDAL/python/pull/91Support streaming pipeline executionhttps://github.com/PDAL/python/pull/94Replace Cython with PyBind11https://github.com/PDAL/python/pull/102Remove pdal.pio modulehttps://github.com/PDAL/python/pull/101Move readers.numpy and filters.python to separate repositoryhttps://github.com/PDAL/python/pull/104Miscellaneous refactorings and cleanups2.3.5Fix memory leakhttps://github.com/PDAL/python/pull/74Handle metadata with invalid unicode by erroringhttps://github.com/PDAL/python/pull/742.3.0PDAL Python support 2.3.0 requires PDAL 2.1+. Older PDAL base libraries
likely will not work.Python support built using scikit-buildreaders.numpy and filters.python are installed along with the extension.Pipeline can take in a list of arrays that are passed to readers.numpyreaders.numpy now supports functions that return arrays. Seehttps://pdal.io/stages/readers.numpy.htmlfor more detail.2.0.0PDAL Python extension is now in its own repository on its own release
schedule athttps://github.com/PDAL/pythonExtension now builds and works under PDAL OSGeo4W64 on Windows.