Skip to content
This repository was archived by the owner on Dec 30, 2023. It is now read-only.

Commit 126b4cf

Browse files
committed
Merge pull request #31 from larsmans/io
New Pythonic pcl.{load,save} functions; PLY format support
2 parents 3832971 + bc0622b commit 126b4cf

File tree

12 files changed

+182
-102
lines changed

12 files changed

+182
-102
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
*.pcd
22

3-
pcl.cpp
4-
pcl.html
3+
pcl/_pcl.cpp
4+
pcl/_pcl.html
55

66
*.py[co]
77
*.so

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
pcl.so: pcl.pyx setup.py pcl_defs.pxd minipcl.cpp
1+
pcl/_pcl.so: pcl/_pcl.pyx setup.py pcl/pcl_defs.pxd pcl/minipcl.cpp
22
python setup.py build_ext --inplace
33

4-
test: pcl.so tests/test.py
4+
test: pcl/_pcl.so tests/test.py
55
nosetests -s
66

77
clean:
88
rm -rf build
9-
rm -f pcl.cpp pcl.so
9+
rm -f pcl/_pcl.cpp pcl/_pcl.so
1010

1111
doc: pcl.so conf.py readme.rst
1212
sphinx-build -b singlehtml -d build/doctrees . build/html

pcl/__init__.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# XXX do a more specific import!
2+
from ._pcl import *
3+
4+
5+
def load(path, format=None):
6+
"""Load pointcloud from path.
7+
8+
Currently supports PCD and PLY files.
9+
10+
Format should be "pcd", "ply", or None to infer from the pathname.
11+
"""
12+
format = _infer_format(path, format)
13+
p = PointCloud()
14+
try:
15+
loader = getattr(p, "_from_%s_file" % format)
16+
except AttributeError:
17+
raise ValueError("unknown file format %s" % format)
18+
if loader(path):
19+
raise IOError("error while loading pointcloud from %r (format=%r)"
20+
% (path, format))
21+
return p
22+
23+
24+
def save(cloud, path, format=None, binary=False):
25+
"""Save pointcloud to file.
26+
27+
Format should be "pcd", "ply", or None to infer from the pathname.
28+
"""
29+
format = _infer_format(path, format)
30+
try:
31+
dumper = getattr(cloud, "_to_%s_file" % format)
32+
except AttributeError:
33+
raise ValueError("unknown file format %s" % format)
34+
if dumper(path, binary):
35+
raise IOError("error while saving pointcloud to %r (format=%r)"
36+
% (path, format))
37+
38+
39+
def _infer_format(path, format):
40+
if format is not None:
41+
return format.lower()
42+
43+
for candidate in ["pcd", "ply"]:
44+
if path.endswith("." + candidate):
45+
return candidate
46+
47+
raise ValueError("Could not determine file format from pathname %s" % path)

pcl.pyx renamed to pcl/_pcl.pyx

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#cython: embedsignature=True
22

3+
from collections import Sequence
4+
import numbers
35
import numpy as np
46

57
cimport numpy as cnp
68

79
cimport pcl_defs as cpp
810

11+
cimport cython
912
from cython.operator import dereference as deref
1013
from libcpp.string cimport string
1114
from libcpp cimport bool
@@ -117,12 +120,31 @@ cdef class SegmentationNormal:
117120
mpcl_sacnormal_set_axis(deref(self.me),ax,ay,az)
118121

119122
cdef class PointCloud:
120-
"""
121-
Represents a class of points, supporting the PointXYZ type.
123+
"""Represents a cloud of points in 3-d space.
124+
125+
A point cloud can be initialized from either a NumPy ndarray of shape
126+
(n_points, 3), from a list of triples, or from an integer n to create an
127+
"empty" cloud of n points.
128+
129+
To load a point cloud from disk, use pcl.load.
122130
"""
123131
cdef cpp.PointCloud[cpp.PointXYZ] *thisptr
124-
def __cinit__(self):
132+
133+
def __cinit__(self, init=None):
125134
self.thisptr = new cpp.PointCloud[cpp.PointXYZ]()
135+
136+
if init is None:
137+
return
138+
elif isinstance(init, (numbers.Integral, np.integer)):
139+
self.resize(init)
140+
elif isinstance(init, np.ndarray):
141+
self.from_array(init)
142+
elif isinstance(init, Sequence):
143+
self.from_list(init)
144+
else:
145+
raise TypeError("Can't initialize a PointCloud from a %s"
146+
% type(init))
147+
126148
def __dealloc__(self):
127149
del self.thisptr
128150
property width:
@@ -138,6 +160,7 @@ cdef class PointCloud:
138160
""" property containing whether the cloud is dense or not """
139161
def __get__(self): return self.thisptr.is_dense
140162

163+
@cython.boundscheck(False)
141164
def from_array(self, cnp.ndarray[cnp.float32_t, ndim=2] arr not None):
142165
"""
143166
Fill this object from a 2D numpy array (float32)
@@ -149,43 +172,43 @@ cdef class PointCloud:
149172
self.thisptr.width = npts
150173
self.thisptr.height = 1
151174

175+
cdef cpp.PointXYZ *p
152176
for i in range(npts):
153-
self.thisptr.at(i).x = arr[i,0]
154-
self.thisptr.at(i).y = arr[i,1]
155-
self.thisptr.at(i).z = arr[i,2]
177+
p = &self.thisptr.at(i)
178+
p.x, p.y, p.z = arr[i, 0], arr[i, 1], arr[i, 2]
156179

180+
@cython.boundscheck(False)
157181
def to_array(self):
158182
"""
159183
Return this object as a 2D numpy array (float32)
160184
"""
161185
cdef float x,y,z
162186
cdef cnp.npy_intp n = self.thisptr.size()
163-
cdef cnp.ndarray[float, ndim=2] result = np.empty([n,3], dtype=np.float32)
187+
cdef cnp.ndarray[cnp.float32_t, ndim=2, mode="c"] result
188+
cdef cpp.PointXYZ *p
189+
190+
result = np.empty((n, 3), dtype=np.float32)
164191

165192
for i in range(n):
166-
x = self.thisptr.at(i).x
167-
y = self.thisptr.at(i).y
168-
z = self.thisptr.at(i).z
169-
result[i,0] = x
170-
result[i,1] = y
171-
result[i,2] = z
193+
p = &self.thisptr.at(i)
194+
result[i, 0] = p.x
195+
result[i, 1] = p.y
196+
result[i, 2] = p.z
172197
return result
173198

174199
def from_list(self, _list):
175200
"""
176201
Fill this pointcloud from a list of 3-tuples
177202
"""
178-
assert len(_list)
179-
assert len(_list[0]) == 3
180-
181203
cdef Py_ssize_t npts = len(_list)
204+
cdef cpp.PointXYZ *p
205+
182206
self.resize(npts)
183207
self.thisptr.width = npts
184208
self.thisptr.height = 1
185209
for i,l in enumerate(_list):
186-
self.thisptr.at(i).x = l[0]
187-
self.thisptr.at(i).y = l[1]
188-
self.thisptr.at(i).z = l[2]
210+
p = &self.thisptr.at(i)
211+
p.x, p.y, p.z = l
189212

190213
def to_list(self):
191214
"""
@@ -200,46 +223,54 @@ cdef class PointCloud:
200223
"""
201224
Return a point (3-tuple) at the given row/column
202225
"""
203-
#grr.... the following doesnt compile to valid
204-
#cython.. so just take the perf hit
205-
#cdef PointXYZ &p = self.thisptr.at(x,y)
206-
cdef x = self.thisptr.at(row,col).x
207-
cdef y = self.thisptr.at(row,col).y
208-
cdef z = self.thisptr.at(row,col).z
209-
return x,y,z
226+
cdef cpp.PointXYZ *p = &self.thisptr.at(row, col)
227+
return p.x, p.y, p.z
210228

211229
def __getitem__(self, cnp.npy_intp idx):
212-
cdef x = self.thisptr.at(idx).x
213-
cdef y = self.thisptr.at(idx).y
214-
cdef z = self.thisptr.at(idx).z
215-
return x,y,z
230+
cdef cpp.PointXYZ *p = &self.thisptr.at(idx)
231+
return p.x, p.y, p.z
216232

217233
def from_file(self, char *f):
218234
"""
219235
Fill this pointcloud from a file (a local path).
220236
Only pcd files supported currently.
237+
238+
Deprecated; use pcl.load instead.
221239
"""
240+
return self._from_pcd_file(f)
241+
242+
def _from_pcd_file(self, const char *s):
243+
cdef int error = 0
244+
with nogil:
245+
ok = cpp.loadPCDFile(string(s), deref(self.thisptr))
246+
return error
247+
248+
def _from_ply_file(self, const char *s):
222249
cdef int ok = 0
223-
cdef string s = string(f)
224-
if f.endswith(".pcd"):
225-
ok = cpp.loadPCDFile(s, deref(self.thisptr))
226-
else:
227-
raise ValueError("Incorrect file extension (must be .pcd)")
228-
return ok
250+
with nogil:
251+
error = cpp.loadPLYFile(string(s), deref(self.thisptr))
252+
return error
229253

230-
def to_file(self, char *f, bool ascii=True):
231-
"""
232-
Save this pointcloud to a local file.
233-
Only saving to binary or ascii pcd is supported
254+
def to_file(self, const char *fname, bool ascii=True):
255+
"""Save pointcloud to a file in PCD format.
256+
257+
Deprecated: use pcl.save instead.
234258
"""
235-
cdef bool binary = not ascii
236-
cdef int ok = 0
259+
return self._to_pcd_file(fname, not ascii)
260+
261+
def _to_pcd_file(self, const char *f, bool binary=False):
262+
cdef int error = 0
237263
cdef string s = string(f)
238-
if f.endswith(".pcd"):
239-
ok = cpp.savePCDFile(s, deref(self.thisptr), binary)
240-
else:
241-
raise ValueError("Incorrect file extension (must be .pcd)")
242-
return ok
264+
with nogil:
265+
error = cpp.savePCDFile(s, deref(self.thisptr), binary)
266+
return error
267+
268+
def _to_ply_file(self, const char *f, bool binary=False):
269+
cdef int error = 0
270+
cdef string s = string(f)
271+
with nogil:
272+
error = cpp.savePLYFile(s, deref(self.thisptr), binary)
273+
return error
243274

244275
def make_segmenter(self):
245276
"""
@@ -608,13 +639,13 @@ cdef class OctreePointCloudSearch(OctreePointCloud):
608639

609640
def __dealloc__(self):
610641
del self.me
611-
612-
"""
613-
Search for all neighbors of query point that are within a given radius.
614-
615-
Returns: (k_indices, k_sqr_distances)
616-
"""
642+
617643
def radius_search (self, point, double radius, unsigned int max_nn = 0):
644+
"""
645+
Search for all neighbors of query point that are within a given radius.
646+
647+
Returns: (k_indices, k_sqr_distances)
648+
"""
618649
cdef vector[int] k_indices
619650
cdef vector[float] k_sqr_distances
620651
if max_nn > 0:
File renamed without changes.
File renamed without changes.

pcl_defs.pxd renamed to pcl/pcl_defs.pxd

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from libc.stddef cimport size_t
2+
13
from libcpp.vector cimport vector
24
from libcpp.string cimport string
35
from libcpp cimport bool
@@ -7,15 +9,15 @@ from vector cimport vector as vector2
79

810
cdef extern from "pcl/point_cloud.h" namespace "pcl":
911
cdef cppclass PointCloud[T]:
10-
PointCloud()
11-
PointCloud(int, int)
12+
PointCloud() except +
13+
PointCloud(unsigned int, unsigned int) except +
1214
unsigned int width
1315
unsigned int height
1416
bool is_dense
15-
void resize(int)
16-
int size()
17-
T& operator[](int)
18-
T& at(int)
17+
void resize(size_t) except +
18+
size_t size()
19+
T& operator[](size_t)
20+
T& at(size_t)
1921
T& at(int, int)
2022
shared_ptr[PointCloud[T]] makeShared()
2123

@@ -120,8 +122,15 @@ ctypedef PointIndices PointIndices_t
120122
ctypedef shared_ptr[PointIndices] PointIndicesPtr_t
121123

122124
cdef extern from "pcl/io/pcd_io.h" namespace "pcl::io":
123-
int loadPCDFile (string file_name, PointCloud[PointXYZ] cloud)
124-
int savePCDFile (string file_name, PointCloud[PointXYZ] cloud, bool binary_mode)
125+
int load(string file_name, PointCloud[PointXYZ] &cloud) nogil
126+
int loadPCDFile(string file_name, PointCloud[PointXYZ] &cloud) nogil
127+
int savePCDFile(string file_name, PointCloud[PointXYZ] &cloud,
128+
bool binary_mode) nogil
129+
130+
cdef extern from "pcl/io/ply_io.h" namespace "pcl::io":
131+
int loadPLYFile(string file_name, PointCloud[PointXYZ] &cloud) nogil
132+
int savePLYFile(string file_name, PointCloud[PointXYZ] &cloud,
133+
bool binary_mode) nogil
125134

126135
#http://dev.pointclouds.org/issues/624
127136
#cdef extern from "pcl/io/ply_io.h" namespace "pcl::io":
@@ -200,4 +209,3 @@ cdef extern from "pcl/kdtree/kdtree_flann.h" namespace "pcl":
200209
int, int, vector[int], vector[float])
201210

202211
ctypedef KdTreeFLANN[PointXYZ] KdTreeFLANN_t
203-
File renamed without changes.
File renamed without changes.

readme.rst

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ for interacting with numpy. For example (from tests/test.py)
3434
3535
import pcl
3636
import numpy as np
37-
p = pcl.PointCloud()
38-
p.from_array(np.array([[1,2,3],[3,4,5]], dtype=np.float32))
37+
p = pcl.PointCloud(np.array([[1, 2, 3], [3, 4, 5]], dtype=np.float32))
3938
seg = p.make_segmenter()
4039
seg.set_model_type(pcl.SACMODEL_PLANE)
4140
seg.set_method_type(pcl.SAC_RANSAC)
@@ -46,8 +45,7 @@ or, for smoothing
4645
.. code-block:: python
4746
4847
import pcl
49-
p = pcl.PointCloud()
50-
p.from_file("C/table_scene_lms400.pcd")
48+
p = pcl.load("C/table_scene_lms400.pcd")
5149
fil = p.make_statistical_outlier_filter()
5250
fil.set_mean_k (50)
5351
fil.set_std_dev_mul_thresh (1.0)
@@ -76,8 +74,8 @@ and CentOS 6.5 with
7674
A note about types
7775
------------------
7876

79-
Point Cloud is a heavily templated API, and consequently mapping this into python
80-
using Cython is challenging.
77+
Point Cloud is a heavily templated API, and consequently mapping this into
78+
Python using Cython is challenging.
8179

8280
It is written in Cython, and implements enough hard bits of the API
8381
(from Cythons perspective, i.e the template/smart_ptr bits) to

0 commit comments

Comments
 (0)