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

Commit 200dae6

Browse files
committed
get some exception safety, using Cython's built-in exception translation
Uses a hack to get around limitations in Cython, see pcl/indexing.hpp. See https://groups.google.com/forum/#!topic/cython-users/AAo7MQFLZyw for explanation. Also, we no longer need to use the at member when bounds checking is not needed: >>> import pcl >>> p = pcl.PointCloud() >>> %timeit p.from_array(a) 100000 loops, best of 3: 17.5 µs per loop This used to be 23.4 µs per loop.
1 parent 126b4cf commit 200dae6

File tree

5 files changed

+52
-10
lines changed

5 files changed

+52
-10
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
pcl/_pcl.so: pcl/_pcl.pyx setup.py pcl/pcl_defs.pxd pcl/minipcl.cpp
1+
pcl/_pcl.so: pcl/_pcl.pyx setup.py pcl/pcl_defs.pxd pcl/minipcl.cpp \
2+
pcl/indexing.hpp
23
python setup.py build_ext --inplace
34

45
test: pcl/_pcl.so tests/test.py

pcl/_pcl.pyx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ cdef class PointCloud:
174174

175175
cdef cpp.PointXYZ *p
176176
for i in range(npts):
177-
p = &self.thisptr.at(i)
177+
p = cpp.getptr(self.thisptr, i)
178178
p.x, p.y, p.z = arr[i, 0], arr[i, 1], arr[i, 2]
179179

180180
@cython.boundscheck(False)
@@ -190,7 +190,7 @@ cdef class PointCloud:
190190
result = np.empty((n, 3), dtype=np.float32)
191191

192192
for i in range(n):
193-
p = &self.thisptr.at(i)
193+
p = cpp.getptr(self.thisptr, i)
194194
result[i, 0] = p.x
195195
result[i, 1] = p.y
196196
result[i, 2] = p.z
@@ -206,8 +206,8 @@ cdef class PointCloud:
206206
self.resize(npts)
207207
self.thisptr.width = npts
208208
self.thisptr.height = 1
209-
for i,l in enumerate(_list):
210-
p = &self.thisptr.at(i)
209+
for i, l in enumerate(_list):
210+
p = cpp.getptr(self.thisptr, i)
211211
p.x, p.y, p.z = l
212212

213213
def to_list(self):
@@ -223,11 +223,11 @@ cdef class PointCloud:
223223
"""
224224
Return a point (3-tuple) at the given row/column
225225
"""
226-
cdef cpp.PointXYZ *p = &self.thisptr.at(row, col)
226+
cdef cpp.PointXYZ *p = cpp.getptr_at(self.thisptr, row, col)
227227
return p.x, p.y, p.z
228228

229229
def __getitem__(self, cnp.npy_intp idx):
230-
cdef cpp.PointXYZ *p = &self.thisptr.at(idx)
230+
cdef cpp.PointXYZ *p = cpp.getptr_at(self.thisptr, idx)
231231
return p.x, p.y, p.z
232232

233233
def from_file(self, char *f):

pcl/indexing.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace {
2+
// Workaround for a Cython bug in operator[] with templated types and
3+
// references. Let's hope the compiler optimizes these functions away.
4+
template <typename T>
5+
T *getptr(pcl::PointCloud<T> *pc, size_t i)
6+
{
7+
return &(*pc)[i];
8+
}
9+
10+
template <typename T>
11+
T *getptr_at(pcl::PointCloud<T> *pc, size_t i)
12+
{
13+
return &(pc->at(i));
14+
}
15+
16+
template <typename T>
17+
T *getptr_at(pcl::PointCloud<T> *pc, int i, int j)
18+
{
19+
return &(pc->at(i, j));
20+
}
21+
}

pcl/pcl_defs.pxd

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ cdef extern from "pcl/point_cloud.h" namespace "pcl":
1616
bool is_dense
1717
void resize(size_t) except +
1818
size_t size()
19-
T& operator[](size_t)
20-
T& at(size_t)
21-
T& at(int, int)
19+
#T& operator[](size_t)
20+
#T& at(size_t) except +
21+
#T& at(int, int) except +
2222
shared_ptr[PointCloud[T]] makeShared()
2323

24+
cdef extern from "indexing.hpp":
25+
# Use these instead of operator[] or at.
26+
PointXYZ *getptr(PointCloud[PointXYZ] *, size_t)
27+
PointXYZ *getptr_at(PointCloud[PointXYZ] *, size_t) except +
28+
PointXYZ *getptr_at(PointCloud[PointXYZ] *, int, int) except +
29+
2430
cdef extern from "pcl/point_types.h" namespace "pcl":
2531
cdef struct PointXYZ:
2632
PointXYZ()

tests/test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,20 @@ def testExtractNeg(self):
208208
self.assertNotEqual(self.p, p2)
209209
self.assertEqual(p2.size, self.p.size - 3)
210210

211+
class TestExceptions(unittest.TestCase):
212+
def setUp(self):
213+
self.p = pcl.PointCloud(np.arange(9, dtype=np.float32).reshape(3, 3))
214+
215+
def testIndex(self):
216+
self.assertRaises(IndexError, self.p.__getitem__, self.p.size)
217+
self.assertRaises(Exception, self.p.get_point, self.p.size, 1)
218+
219+
def testResize(self):
220+
# XXX MemoryError isn't actually the prettiest exception for a
221+
# negative argument. Don't hesitate to change this test to reflect
222+
# better exceptions.
223+
self.assertRaises(MemoryError, self.p.resize, -1)
224+
211225
class TestSegmenterNormal(unittest.TestCase):
212226

213227
def setUp(self):

0 commit comments

Comments
 (0)