Skip to content

Commit c60e59f

Browse files
committed
Merge pull request strawlab#61 from larsmans/buffer-api
Buffer api: easier NumPy interop
2 parents 71afdb5 + 14f674a commit c60e59f

File tree

4 files changed

+80
-7
lines changed

4 files changed

+80
-7
lines changed

pcl/_pcl.pxd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ cimport pcl_defs as cpp
77
cdef class PointCloud:
88
cdef cpp.PointCloudPtr_t thisptr_shared
99

10+
# Buffer protocol support.
11+
cdef Py_ssize_t _shape[2]
12+
cdef Py_ssize_t _view_count
13+
1014
cdef inline cpp.PointCloud[cpp.PointXYZ] *thisptr(self) nogil:
1115
# Shortcut to get raw pointer to underlying PointCloud<PointXYZ>.
1216
return self.thisptr_shared.get()

pcl/_pcl.pyx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ cimport pcl_defs as cpp
1010

1111
cimport cython
1212
from cython.operator import dereference as deref
13+
14+
from cpython cimport Py_buffer
15+
1316
from libcpp.string cimport string
1417
from libcpp cimport bool
1518
from libcpp.vector cimport vector
@@ -125,6 +128,20 @@ cdef class SegmentationNormal:
125128
def set_axis(self, double ax, double ay, double az):
126129
mpcl_sacnormal_set_axis(deref(self.me),ax,ay,az)
127130

131+
132+
# Empirically determine strides, for buffer support.
133+
# XXX Is there a more elegant way to get these?
134+
cdef Py_ssize_t _strides[2]
135+
cdef PointCloud _pc_tmp = PointCloud(np.array([[1, 2, 3],
136+
[4, 5, 6]], dtype=np.float32))
137+
cdef cpp.PointCloud[cpp.PointXYZ] *p = _pc_tmp.thisptr()
138+
_strides[0] = ( <Py_ssize_t><void *>cpp.getptr(p, 1)
139+
- <Py_ssize_t><void *>cpp.getptr(p, 0))
140+
_strides[1] = ( <Py_ssize_t><void *>&(cpp.getptr(p, 0).y)
141+
- <Py_ssize_t><void *>&(cpp.getptr(p, 0).x))
142+
_pc_tmp = None
143+
144+
128145
cdef class PointCloud:
129146
"""Represents a cloud of points in 3-d space.
130147
@@ -137,6 +154,8 @@ cdef class PointCloud:
137154
def __cinit__(self, init=None):
138155
cdef PointCloud other
139156

157+
self._view_count = 0
158+
140159
sp_assign(self.thisptr_shared, new cpp.PointCloud[cpp.PointXYZ]())
141160

142161
if init is None:
@@ -170,6 +189,32 @@ cdef class PointCloud:
170189
def __repr__(self):
171190
return "<PointCloud of %d points>" % self.size
172191

192+
# Buffer protocol support. Taking a view locks the pointcloud for
193+
# resizing, because that can move it around in memory.
194+
def __getbuffer__(self, Py_buffer *buffer, int flags):
195+
# TODO parse flags
196+
cdef Py_ssize_t npoints = self.thisptr().size()
197+
198+
if self._view_count == 0:
199+
self._view_count += 1
200+
self._shape[0] = npoints
201+
self._shape[1] = 3
202+
203+
buffer.buf = <char *>&(cpp.getptr_at(self.thisptr(), 0).x)
204+
buffer.format = 'f'
205+
buffer.internal = NULL
206+
buffer.itemsize = sizeof(float)
207+
buffer.len = npoints * 3 * sizeof(float)
208+
buffer.ndim = 2
209+
buffer.obj = self
210+
buffer.readonly = 0
211+
buffer.shape = self._shape
212+
buffer.strides = _strides
213+
buffer.suboffsets = NULL
214+
215+
def __releasebuffer__(self, Py_buffer *buffer):
216+
self._view_count -= 1
217+
173218
# Pickle support. XXX this copies the entire pointcloud; it would be nice
174219
# to have an asarray member that returns a view, or even better, implement
175220
# the buffer protocol (https://docs.python.org/c-api/buffer.html).
@@ -246,6 +291,9 @@ cdef class PointCloud:
246291
return self.to_array().tolist()
247292

248293
def resize(self, cnp.npy_intp x):
294+
if self._view_count > 0:
295+
raise ValueError("can't resize PointCloud while there are"
296+
" arrays/memoryviews referencing it")
249297
self.thisptr().resize(x)
250298

251299
def get_point(self, cnp.npy_intp row, cnp.npy_intp col):

readme.rst

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ point types
2929
* registration (ICP, GICP, ICP_NL)
3030

3131
The code tries to follow the Point Cloud API, and also provides helper function
32-
for interacting with numpy. For example (from tests/test.py)
32+
for interacting with NumPy. For example (from tests/test.py)
3333

3434
.. code-block:: python
3535
@@ -52,6 +52,20 @@ or, for smoothing
5252
fil.set_std_dev_mul_thresh (1.0)
5353
fil.filter().to_file("inliers.pcd")
5454
55+
Point clouds can be viewed as NumPy arrays, so modifying them is possible
56+
using all the familiar NumPy functionality:
57+
58+
.. code-block:: python
59+
60+
import numpy as np
61+
import pcl
62+
p = pcl.PointCloud(10) # "empty" point cloud
63+
a = np.asarray(p) # NumPy view on the cloud
64+
a[:] = 0 # fill with zeros
65+
print(p[3]) # prints (0.0, 0.0, 0.0)
66+
a[:, 0] = 1 # set x coordinates to 1
67+
print(p[3]) # prints (1.0, 0.0, 0.0)
68+
5569
More samples can be found in the `examples directory <https://github.com/strawlab/python-pcl/tree/master/examples>`_,
5670
and in the `unit tests <https://github.com/strawlab/python-pcl/blob/master/tests/test.py>`_.
5771

@@ -60,11 +74,11 @@ This work was supported by `Strawlab <http://strawlab.org/>`_.
6074
Requirements
6175
------------
6276

63-
This release has been tested on Ubuntu 13.10 with
77+
This release has been tested on Linux Mint 17 with
6478

65-
* Python 2.7.5
66-
* pcl 1.7.1
67-
* Cython 0.21
79+
* Python 2.7.6
80+
* pcl 1.7.2
81+
* Cython 0.21.2
6882

6983
and CentOS 6.5 with
7084

tests/test.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pcl
88
import numpy as np
99

10-
from numpy.testing import assert_array_equal
10+
from numpy.testing import assert_array_almost_equal, assert_array_equal
1111

1212

1313
_data = [(i,2*i,3*i+0.2) for i in range(5)]
@@ -38,7 +38,7 @@ def setUp(self):
3838
self.a = np.array(np.mat(_DATA, dtype=np.float32))
3939
self.p = pcl.PointCloud(self.a)
4040

41-
def testFromNumy(self):
41+
def testFromNumpy(self):
4242
for i,d in enumerate(_data):
4343
pt = self.p[i]
4444
assert np.allclose(pt, _data[i])
@@ -47,6 +47,13 @@ def testToNumpy(self):
4747
a = self.p.to_array()
4848
self.assertTrue(np.alltrue(a == self.a))
4949

50+
def test_asarray(self):
51+
p = pcl.PointCloud(self.p) # copy
52+
old0 = p[0]
53+
a = np.asarray(p) # view
54+
a[:] += 6
55+
assert_array_almost_equal(p[0], a[0])
56+
5057
def test_pickle(self):
5158
"""Test pickle support."""
5259
# In this testcase because picking reduces to pickling NumPy arrays.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy