Skip to content

Commit cfe6391

Browse files
authored
Refactor PDAL Python support based on scikit-build and remove it from main PDAL library
2 parents 548b704 + a0ced8a commit cfe6391

23 files changed

+309
-72
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
pdal/libpdalpython.cpp
22
*.pyc
3+
_skbuild/*
4+
.vscode/*
35
__pycache__
46
build/*
57
PDAL.egg-info/*

README.rst

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,39 @@
22
PDAL
33
================================================================================
44

5-
The PDAL Python extension allows you to process data with PDAL into `Numpy`_
6-
arrays. Additionally, you can use it to fetch `schema`_ and `metadata`_ from
5+
PDAL Python support allows you to process data with PDAL into `Numpy`_
6+
arrays. It supports embedding Python in PDAL pipelines with the `readers.numpy <https://pdal.io/stages/readers.numpy.html>`__
7+
and `filters.python <https://pdal.io/stages/filters.python.html>`__ stages, and it provides a PDAL extension module to control
8+
Python interaction with PDAL.
9+
10+
Additionally, you can use it to fetch `schema`_ and `metadata`_ from
711
PDAL operations.
812

13+
Installation
14+
--------------------------------------------------------------------------------
15+
16+
PyPI
17+
................................................................................
18+
19+
PDAL Python support is installable via PyPI:
20+
21+
.. code-block::
22+
23+
pip install PDAL
24+
25+
GitHub
26+
................................................................................
27+
928
The repository for PDAL's Python extension is available at https://github.com/PDAL/python
1029

11-
It is released independently from PDAL itself as of PDAL 1.7.
30+
Python support released independently from PDAL itself as of PDAL 1.7.
1231

1332
Usage
1433
--------------------------------------------------------------------------------
1534

35+
Simple
36+
................................................................................
37+
1638
Given the following pipeline, which simply reads an `ASPRS LAS`_ file and
1739
sorts it by the ``X`` dimension:
1840

@@ -34,30 +56,107 @@ sorts it by the ``X`` dimension:
3456
3557
import pdal
3658
pipeline = pdal.Pipeline(json)
37-
pipeline.validate() # check if our JSON and options were good
38-
pipeline.loglevel = 8 #really noisy
3959
count = pipeline.execute()
4060
arrays = pipeline.arrays
4161
metadata = pipeline.metadata
4262
log = pipeline.log
4363
64+
Reading using Numpy Arrays
65+
................................................................................
66+
67+
The following more complex scenario demonstrates the full cycling between
68+
PDAL and Python:
69+
70+
* Read a small testfile from GitHub into a Numpy array
71+
* Filters those arrays with Numpy for Intensity
72+
* Pass the filtered array to PDAL to be filtered again
73+
* Write the filtered array to an LAS file.
74+
75+
.. code-block:: python
76+
77+
data = "https://github.com/PDAL/PDAL/blob/master/test/data/las/1.2-with-color.las?raw=true"
78+
79+
80+
json = """
81+
{
82+
"pipeline": [
83+
{
84+
"type": "readers.las",
85+
"filename": "%s"
86+
}
87+
]
88+
}"""
89+
90+
import pdal
91+
import numpy as np
92+
pipeline = pdal.Pipeline(json % data)
93+
count = pipeline.execute()
94+
95+
# get the data from the first array
96+
# [array([(637012.24, 849028.31, 431.66, 143, 1, 1, 1, 0, 1, -9., 132, 7326, 245380.78254963, 68, 77, 88),
97+
# 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')])
98+
99+
arr = pipeline.arrays[0]
100+
print (len(arr)) # 1065 points
101+
102+
103+
# Filter out entries that have intensity < 50
104+
intensity = arr[arr['Intensity'] > 30]
105+
print (len(intensity)) # 704 points
106+
107+
108+
# Now use pdal to clamp points that have intensity
109+
# 100 <= v < 300, and there are 387
110+
clamp =u"""{
111+
"pipeline":[
112+
{
113+
"type":"filters.range",
114+
"limits":"Intensity[100:300)"
115+
}
116+
]
117+
}"""
118+
119+
p = pdal.Pipeline(clamp, [intensity])
120+
count = p.execute()
121+
clamped = p.arrays[0]
122+
print (count)
123+
124+
# Write our intensity data to an LAS file
125+
output =u"""{
126+
"pipeline":[
127+
{
128+
"type":"writers.las",
129+
"filename":"clamped.las",
130+
"offset_x":"auto",
131+
"offset_y":"auto",
132+
"offset_z":"auto",
133+
"scale_x":0.01,
134+
"scale_y":0.01,
135+
"scale_z":0.01
136+
}
137+
]
138+
}"""
139+
140+
p = pdal.Pipeline(output, [clamped])
141+
count = p.execute()
142+
print (count)
143+
144+
145+
44146
45147
.. _`Numpy`: http://www.numpy.org/
46148
.. _`schema`: http://www.pdal.io/dimensions.html
47149
.. _`metadata`: http://www.pdal.io/development/metadata.html
48150

49151

50-
.. image:: https://travis-ci.org/PDAL/python.svg?branch=master
51-
:target: https://travis-ci.org/PDAL/python
52-
53152
.. image:: https://ci.appveyor.com/api/projects/status/of4kecyahpo8892d
54153
:target: https://ci.appveyor.com/project/hobu/python/
55154

56155
Requirements
57156
================================================================================
58157

59-
* PDAL 1.7+
60-
* Python >=2.7 (including Python 3.x)
158+
* PDAL 2.1+
159+
* Python >=3.6
61160
* Cython (eg :code:`pip install cython`)
62161
* Packaging (eg :code:`pip install packaging`)
63162

pdal/CMakeLists.txt

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ include(${PROJECT_SOURCE_DIR}/macros.cmake NO_POLICY_SCOPE)
77
set(CMAKE_CXX_STANDARD 11)
88
set(CMAKE_CXX_STANDARD_REQUIRED ON)
99
set(CMAKE_CXX_EXTENSIONS OFF)
10-
set(CMAKE_BUILD_TYPE "Debug")
10+
set(CMAKE_BUILD_TYPE "Release")
1111

1212
enable_testing()
1313

1414
# Python-finding settings
1515
set(Python3_FIND_STRATEGY "LOCATION")
16-
set(Python3_FIND_REGISTRY "LAST")
16+
set(Python3_FIND_REGISTRY "LAST")
1717
set(Python3_FIND_FRAMEWORK "LAST")
1818
find_package(Python3 COMPONENTS Interpreter Development NumPy REQUIRED)
1919

@@ -33,24 +33,24 @@ execute_process(
3333
OUTPUT_STRIP_TRAILING_WHITESPACE
3434
)
3535

36-
execute_process(
37-
COMMAND
38-
${Python3_EXECUTABLE} -c "from distutils import sysconfig; print(sysconfig.get_config_var('LDSHARED').split(' ', 1)[1])"
39-
OUTPUT_VARIABLE PYTHON_LDSHARED
40-
OUTPUT_STRIP_TRAILING_WHITESPACE
41-
)
42-
43-
if (NOT Py_ENABLE_SHARED)
44-
message(STATUS "Python ${Python3_EXECUTABLE} is statically linked")
45-
if (APPLE)
46-
# conda gives us -bundle, which isn't valid
47-
message(STATUS "Removing extra -bundle argument from sysconfig.get_config_var('LDSHARED')")
48-
string(REPLACE "-bundle" "" PYTHON_LDSHARED "${PYTHON_LDSHARED}")
49-
string(STRIP ${PYTHON_LDSHARED} PYTHON_LDSHARED)
50-
endif()
51-
# set(Python3_LIBRARIES ${PYTHON_LDSHARED})
52-
message(STATUS "Setting Python3_LIBRARIES to '${Python3_LIBRARIES}' due to static Python")
53-
endif()
36+
#execute_process(
37+
# COMMAND
38+
# ${Python3_EXECUTABLE} -c "from distutils import sysconfig; print(sysconfig.get_config_var('LDSHARED').split(' ', 1)[1])"
39+
# OUTPUT_VARIABLE PYTHON_LDSHARED
40+
# OUTPUT_STRIP_TRAILING_WHITESPACE
41+
#)
42+
43+
#if (NOT Py_ENABLE_SHARED)
44+
# message(STATUS "Python ${Python3_EXECUTABLE} is statically linked")
45+
# if (APPLE)
46+
# # conda gives us -bundle, which isn't valid
47+
# message(STATUS "Removing extra -bundle argument from sysconfig.get_config_var('LDSHARED')")
48+
# string(REPLACE "-bundle" "" PYTHON_LDSHARED "${PYTHON_LDSHARED}")
49+
# string(STRIP ${PYTHON_LDSHARED} PYTHON_LDSHARED)
50+
# endif()
51+
# # set(Python3_LIBRARIES ${PYTHON_LDSHARED})
52+
# message(STATUS "Setting Python3_LIBRARIES to '${Python3_LIBRARIES}' due to static Python")
53+
#endif()
5454

5555
set(EXTENSION_SRC
5656
PyArray.cpp
@@ -76,15 +76,15 @@ python_extension_module(${extension})
7676
install(TARGETS ${extension} LIBRARY DESTINATION ${PROJECT_NAME})
7777

7878
PDAL_PYTHON_ADD_PLUGIN(numpy_reader reader numpy
79-
FILES
79+
FILES
8080
./io/NumpyReader.cpp
8181
./io/NumpyReader.hpp
8282
./plang/Invocation.cpp
8383
./plang/Environment.cpp
8484
./plang/Redirector.cpp
8585
./plang/Script.cpp
86-
LINK_WITH
87-
${PDAL_LIBRARIES}
86+
LINK_WITH
87+
${PDAL_LIBRARIES}
8888
${Python3_LIBRARIES}
8989
${CMAKE_DL_LIBS}
9090
SYSTEM_INCLUDES
@@ -122,15 +122,15 @@ add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
122122
EXCLUDE_FROM_ALL)
123123

124124
PDAL_PYTHON_ADD_PLUGIN(python_filter filter python
125-
FILES
125+
FILES
126126
./filters/PythonFilter.cpp
127127
./filters/PythonFilter.hpp
128128
./plang/Invocation.cpp
129129
./plang/Environment.cpp
130130
./plang/Redirector.cpp
131131
./plang/Script.cpp
132-
LINK_WITH
133-
${PDAL_LIBRARIES}
132+
LINK_WITH
133+
${PDAL_LIBRARIES}
134134
${Python3_LIBRARIES}
135135
${CMAKE_DL_LIBS}
136136
SYSTEM_INCLUDES
@@ -145,8 +145,12 @@ PDAL_PYTHON_ADD_TEST(pdal_io_numpy_test
145145
FILES
146146
./test/NumpyReaderTest.cpp
147147
./test/Support.cpp
148+
./plang/Invocation.cpp
149+
./plang/Environment.cpp
150+
./plang/Redirector.cpp
151+
./plang/Script.cpp
148152
LINK_WITH
149-
${numpy_reader}
153+
${numpy_reader}
150154
${Python3_LIBRARIES}
151155
${PDAL_LIBRARIES}
152156
${CMAKE_DL_LIBS}
@@ -160,8 +164,10 @@ PDAL_PYTHON_ADD_TEST(pdal_filters_python_test
160164
FILES
161165
./test/PythonFilterTest.cpp
162166
./test/Support.cpp
163-
./plang/Invocation.cpp
167+
./plang/Invocation.cpp
164168
./plang/Environment.cpp
169+
./plang/Redirector.cpp
170+
./plang/Script.cpp
165171
LINK_WITH
166172
${python_filter}
167173
${Python3_LIBRARIES}

pdal/test/PythonFilterTest.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <pdal/StageFactory.hpp>
3939
#include <pdal/io/FauxReader.hpp>
4040
#include <pdal/filters/StatsFilter.hpp>
41+
#include <pdal/util/FileUtils.hpp>
4142

4243
#include "../plang/Invocation.hpp"
4344
#include "../plang/Environment.hpp"
@@ -138,7 +139,7 @@ TEST_F(PythonFilterTest, pipelineJSON)
138139
PipelineManager manager;
139140

140141
manager.readPipeline(
141-
Support::configuredpath("programmable-update-y-dims.json"));
142+
Support::datapath("programmable-update-y-dims.json"));
142143
manager.execute();
143144
PointViewSet viewSet = manager.views();
144145
EXPECT_EQ(viewSet.size(), 1u);
@@ -569,7 +570,7 @@ TEST_F(PredicateFilterTest, PredicateFilterTest_PipelineJSON)
569570
{
570571
PipelineManager mgr;
571572

572-
mgr.readPipeline(Support::configuredpath("from-module.json"));
573+
mgr.readPipeline(Support::datapath("from-module.json"));
573574
point_count_t cnt = mgr.execute();
574575
EXPECT_EQ(cnt, 1u);
575576
}
@@ -578,7 +579,7 @@ TEST_F(PredicateFilterTest, PredicateFilterTest_EmbedJSON)
578579
{
579580
PipelineManager mgr;
580581

581-
mgr.readPipeline(Support::configuredpath("predicate-embed.json"));
582+
mgr.readPipeline(Support::datapath("predicate-embed.json"));
582583
point_count_t cnt = mgr.execute();
583584
EXPECT_EQ(cnt, 1u);
584585
}
@@ -780,6 +781,9 @@ TEST(PLangTest, log)
780781
{
781782
// verify we can redirect the stdout inside the python script
782783

784+
std::string logfile("mylog_three.txt");
785+
// std::string logfile(Support::temppath("mylog_three.txt"));
786+
783787
Options reader_opts;
784788
{
785789
BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
@@ -791,7 +795,7 @@ TEST(PLangTest, log)
791795
reader_opts.add(opt2);
792796
reader_opts.add(opt3);
793797

794-
Option optlog("log", Support::temppath("mylog_three.txt"));
798+
Option optlog("log", logfile);
795799
reader_opts.add(optlog);
796800
}
797801

@@ -810,7 +814,7 @@ TEST(PLangTest, log)
810814
);
811815
const Option module("module", "xModule");
812816
const Option function("function", "xfunc");
813-
xfilter_opts.add("log", Support::temppath("mylog_three.txt"));
817+
xfilter_opts.add("log", logfile);
814818
xfilter_opts.add(source);
815819
xfilter_opts.add(module);
816820
xfilter_opts.add(function);
@@ -834,16 +838,15 @@ TEST(PLangTest, log)
834838
EXPECT_EQ(view->size(), 750u);
835839
}
836840

837-
bool ok = Support::compare_text_files(
838-
Support::temppath("mylog_three.txt"),
841+
bool ok = Support::compare_text_files(logfile,
839842
Support::datapath("logs/log_py.txt"));
840843

841844
// TODO: fails on Windows
842845
// unknown file: error: C++ exception with description "pdalboost::filesystem::remove:
843846
// The process cannot access the file because it is being used by another process:
844847
// "C:/projects/pdal/test/data/../temp/mylog_three.txt"" thrown in the test body.
845-
//if (ok)
846-
// FileUtils::deleteFile(Support::temppath("mylog_three.txt"));
848+
if (ok)
849+
FileUtils::deleteFile(Support::temppath("mylog_three.txt"));
847850

848851
EXPECT_TRUE(ok);
849852
}
@@ -1110,7 +1113,7 @@ static void run_pipeline(std::string const& pipeline)
11101113
const std::string cmd = "pdal pipeline";
11111114

11121115
std::string output;
1113-
std::string file(Support::configuredpath(pipeline));
1116+
std::string file(Support::datapath(pipeline));
11141117
int stat = pdal::Utils::run_shell_command(cmd + " " + file, output);
11151118
EXPECT_EQ(0, stat);
11161119
if (stat)

pdal/test/data/1.2-with-color.las

35.6 KB
Binary file not shown.

pdal/test/data/autzen-utm.las

36.5 KB
Binary file not shown.

pdal/test/data/from-module.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"pipeline":[
3-
"./1.2-with-color.las",
3+
"./pdal/test/data/1.2-with-color.las",
44
{
55
"type": "filters.python",
6-
"script": "./test1.py",
6+
"script": "./pdal/test/data/test1.py",
77
"module": "anything",
88
"function": "fff"
99
}

pdal/test/data/logs/log_py.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Testing log output through python script.

0 commit comments

Comments
 (0)