Skip to content

Commit 47fcd29

Browse files
committed
Implement initializers
Purge
1 parent 5b7f37b commit 47fcd29

18 files changed

+182
-112
lines changed

.vscode/settings.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ Nevertheless, this library focuses on simplicity, readability and accessibility,
1515

1616
------------------------
1717

18+
## Installation
19+
20+
- Installation from PyPI: `pip install pypcl`
21+
- Installation from source: `python setup.py install`
22+
- Installation from source in-place: `python setup.py develop`
23+
24+
------------------------
25+
1826
## Notes
19-
The cython doesn't support a template technique which is similar to ["covariant"](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)) in its template support, so the code which need this technique is not wrapped or header-provided as stated above.
27+
The cython doesn't support a template technique similar to ["covariant"](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)) in its template support, so the code which need this technique is not wrapped or header-provided as stated above.
2028

2129
The library is under heavy construction, thus do not use it in productive codes. However, playing with it is totally welcome now, and it will be great to receive your issues, suggestions and pull requests!~

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
- [ ] Add conversion between numpy array and Eigen objects
44
- [ ] Migrate original implementations of pypcl
55
- [ ] Fix TODOs in the code
6+
- [ ] Instantiate template classes to given point-type (e.g. PointXYZ)
7+
- [ ] Support organized point cloud

pcl/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ add_library(Visualizer MODULE ${Visualizer})
2929
target_link_libraries(Visualizer ${PCL_LIBRARIES})
3030
python_extension_module(Visualizer)
3131
install(TARGETS Visualizer LIBRARY DESTINATION pcl)
32+
33+
add_subdirectory(io)

pcl/PointCloud.pyx

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
from cython.operator cimport dereference as deref
21
from libc.stdint cimport uint8_t
32
from libcpp cimport bool
43
from libcpp.string cimport string
5-
from cpython cimport bool as PyBool
4+
from cython.operator cimport dereference as deref
5+
import sys
66
import numpy as np
77
cimport numpy as np
8-
import warnings
98

109
from pcl._boost.smart_ptr cimport make_shared
1110
from .ros import ros_exist, ros_error
1211
if ros_exist: from pcl.ros cimport from_msg_cloud, to_msg_cloud
1312
from pcl.common.conversions cimport toPCLPointCloud2, fromPCLPointCloud2
1413
from pcl.common.point_cloud cimport PointCloud as cPC
15-
from pcl.io.pcd_io cimport loadPCDFile, savePCDFile
16-
from pcl.io.ply_io cimport loadPLYFile, savePLYFile
1714
from pcl.PointField cimport PointField, _FIELD_TYPE_MAPPING
1815

1916
# XXX: unordered_map[string, vector[(string, uint8_t, uint32_t)]]
@@ -52,31 +49,32 @@ cdef string _check_dtype_compatible(np.dtype dtype):
5249
tuple data_fields
5350
tuple data_dtypes
5451

55-
for name, typetuple in _POINT_TYPE_MAPPING:
56-
builtin_fields = tuple(name for name,_,_ in typetuple)
57-
builtin_dtypes = tuple(_FIELD_TYPE_MAPPING[typeid] for _,typeid,_ in typetuple)
52+
for name, typetuple in _POINT_TYPE_MAPPING.items():
53+
builtin_fields = tuple(name.decode('ascii') for name,_,_ in typetuple)
54+
builtin_dtypes = tuple(_FIELD_TYPE_MAPPING[typeid][0] for _,typeid,_ in typetuple)
5855
data_fields = dtype.names
59-
data_dtypes = tuple(_parse_single_dtype(dtype[name]).encode('ascii') for name in dtype.names)
56+
data_dtypes = tuple(_parse_single_dtype(dtype[name]) for name in dtype.names)
6057
if builtin_fields == data_fields and builtin_dtypes == data_dtypes:
6158
return name
6259
return b""
6360

64-
cdef bool _is_not_record_array(np.ndarray array):
65-
return array.dtype.isbuiltin
61+
cdef inline bool _is_not_record_array(np.ndarray array):
62+
# https://github.com/numpy/numpy/blob/master/numpy/core/_dtype.py#L107
63+
return array.dtype.fields is None
6664

6765
cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
6866
def __cinit__(self):
6967
self._origin = Vector4f.Zero()
7068
self._orientation = Quaternionf.Identity()
7169

72-
def __init__(self, data=None, point_type=None, copy=False):
70+
def __init__(self, data=None, point_type='XYZ'):
7371
"""
7472
Initialize a point cloud
7573
7674
# Parameters
7775
- data: point cloud data, can be a PointCloud or a numpy array. If the dimension of input
7876
is 3, then it will be considered as dense point cloud with dimensions (height,
79-
width, data)
77+
width, data). The input data will always be copied.
8078
- point_type: if normal numpy array is given, the type of point should be specified
8179
if you are using other PCL components on it. The valid point_types are:
8280
- Intensity
@@ -107,24 +105,33 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
107105
from_msg_cloud(data, deref(self._ptr))
108106
initialized = True
109107
if isinstance(data, PointCloud):
110-
# TODO: support non-copy instantiation
111-
self._ptr = (<PointCloud>data)._ptr
108+
self._ptr = make_shared[PCLPointCloud2]()
109+
self.ptr()[0] = (<PointCloud>data).ptr()[0] # copy struct
112110
self._origin = (<PointCloud>data)._origin
113111
self._orientation = (<PointCloud>data)._orientation
114112
self._ptype = (<PointCloud>data)._ptype
115113
initialized = True
116114
if isinstance(data, (list, tuple)):
117-
# TODO: support different dtype
118-
data = np.ndarray(data)
115+
if point_type == None:
116+
raise ValueError('Point type should be specified when normal array is inputed')
117+
self._ptype = point_type.upper().encode('ascii')
118+
if <bytes>(self._ptype) not in _POINT_TYPE_MAPPING:
119+
raise TypeError('Unsupported point type!')
120+
121+
ndtype = []
122+
byte_order = '>' if sys.byteorder == 'big' else '<'
123+
for name, typeid, count in _POINT_TYPE_MAPPING[self._ptype]:
124+
ndtype.append((name.decode('ascii'),
125+
str(count) + byte_order + _FIELD_TYPE_MAPPING[typeid][0]))
126+
data = np.array(data, dtype=ndtype)
119127
if isinstance(data, np.ndarray):
128+
self._ptr = make_shared[PCLPointCloud2]()
120129
# matrix order detection
121130
if not data.flags.c_contiguous:
122131
data = data.ascontiguousarray()
123-
if copy: warnings.warn('The matrix order is inconsistent, data copy is forced!')
124132

125-
# TODO: support copying instantiation
126133
# datatype interpreting
127-
if _is_not_record_array(data): # normal array
134+
if _is_not_record_array(data): # [normal array]
128135
# data shape interpreting
129136
if len(data.shape) < 2 or len(data.shape) > 3:
130137
raise ValueError("Unrecognized input data shape")
@@ -143,7 +150,7 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
143150
raise ValueError('Point type should be specified when normal array is inputed')
144151
self._ptype = point_type.upper().encode('ascii')
145152
if <bytes>(self._ptype) not in _POINT_TYPE_MAPPING:
146-
warnings.warn('Unsupported point type!')
153+
raise TypeError('Unsupported point type!')
147154
else:
148155
# field consensus check
149156
if data.shape[-1] != len(_POINT_TYPE_MAPPING[self._ptype]):
@@ -156,7 +163,14 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
156163
raise ValueError('Input data is not consistent with what point type requires: '
157164
+ _FIELD_TYPE_MAPPING[typeid_list[0]][0])
158165

159-
else: # record array
166+
# byteorder intepreting
167+
if (data.dtype.byteorder == '<' and self.ptr().is_bigendian) or\
168+
(data.dtype.byteorder == '>' and not self.ptr().is_bigendian):
169+
ndtype = data.dtype
170+
ndtype.byteorder = '>' if self.ptr().is_bigendian else '<'
171+
data = data.astype(ndtype)
172+
173+
else: # [record array]
160174
# data shape interpreting
161175
if len(data.shape) < 1 or len(data.shape) > 2:
162176
raise ValueError("Unrecognized input data shape")
@@ -171,11 +185,23 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
171185
self.ptr().is_dense = True
172186

173187
# data field interpreting
174-
self._ptype = _check_dtype_compatible(data.dtype)
175-
if self._ptype.empty():
176-
raise ValueError("Inconsistent input numpy array dtype!")
188+
if self._ptype.empty(): # not previously defined
189+
self._ptype = _check_dtype_compatible(data.dtype)
190+
if self._ptype.empty():
191+
raise ValueError("Inconsistent input numpy array dtype!")
177192
self.ptr().point_step = data.strides[-1]
178193

194+
# byteorder intepreting
195+
ndtype = {'names':[], 'formats':[]}
196+
for subname in data.dtype.names:
197+
subtype = data.dtype[subname]
198+
if (subtype.byteorder == '<' and self.ptr().is_bigendian) or\
199+
(subtype.byteorder == '>' and not self.ptr().is_bigendian):
200+
subtype.byetorder = '>' if self.ptr().is_bigendian else '<'
201+
ndtype['names'].append(subname)
202+
ndtype['formats'].append(subtype)
203+
data = data.astype(ndtype)
204+
179205
# data strides calculation
180206
self.ptr().point_step = 0
181207
for name, typeid, count in _POINT_TYPE_MAPPING[self._ptype]:
@@ -189,14 +215,10 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
189215
self.ptr().row_step = self.ptr().point_step * self.ptr().width
190216

191217
if not _is_not_record_array(data):
192-
assert self.ptr().point_step != data.strides[-1]
218+
assert self.ptr().point_step - data.strides[-1] == 0
193219

194220
# data interpreting
195-
# FIXME: generate warning if byte order is not consistent
196-
if self.ptr().is_bigendian:
197-
self.ptr().data = data.view('>u1').reshape(-1)
198-
else:
199-
self.ptr().data = data.view('<u1').reshape(-1)
221+
self.ptr().data = data.view('B').ravel()
200222

201223
initialized = True
202224
if data is None:
@@ -216,6 +238,10 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
216238
'''The fields of the point cloud'''
217239
def __get__(self):
218240
return [PointField.wrap(field) for field in self.ptr().fields]
241+
property names:
242+
'''The name of the point cloud fields'''
243+
def __get__(self):
244+
return [field.name.decode('ascii') for field in self.ptr().fields]
219245
property sensor_orientation:
220246
''' Sensor acquisition pose (rotation). '''
221247
def __get__(self):
@@ -261,27 +287,42 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
261287
def __get__(self):
262288
cdef np.dtype struct = np.dtype([('b', 'u1'), ('g', 'u1'), ('r', 'u1'), ('a', 'u1')])
263289
return self.to_ndarray()['rgba'].view(struct)
290+
property is_organized:
291+
'''
292+
Get whether the point cloud is organized
293+
'''
264294

265295
def __len__(self):
266296
return self.ptr().width * self.ptr().height
267-
268297
def __repr__(self):
269298
return "<PointCloud of %d points>" % len(self)
270-
271-
def __iter__(self):
299+
def __reduce__(self):
272300
raise NotImplementedError()
273-
301+
def __iter__(self):
302+
return iter(self.to_ndarray())
274303
def __contains__(self, item):
275-
raise NotImplementedError()
304+
# for the field names, use 'names' property for instead.
305+
return item in self.to_ndarray()
276306

277-
def __reduce__(self):
307+
def __getitem__(self, indices):
308+
raise NotImplementedError()
309+
def __setitem__(self, indices, value):
310+
raise NotImplementedError()
311+
def __delitem__(self, indices):
278312
raise NotImplementedError()
279313

280314
def __add__(self, item):
281315
raise NotImplementedError()
316+
def __eq__(self, target):
317+
raise NotImplementedError()
318+
282319
def __array__(self, *_):
283320
'''support conversion to ndarray'''
284321
return self.to_ndarray()
322+
def __getbuffer__(self, Py_buffer *buffer, int flags):
323+
raise NotImplementedError()
324+
def __releasebuffer__(self, Py_buffer *buffer):
325+
raise NotImplementedError()
285326

286327
cdef PCLPointCloud2* ptr(self):
287328
return self._ptr.get()
@@ -290,7 +331,7 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
290331
cdef uint8_t[:] mem_view = <uint8_t[:self.ptr().data.size()]>mem_ptr
291332
cdef np.ndarray arr_raw = np.asarray(mem_view)
292333
cdef str byte_order = '>' if self.ptr().is_bigendian else '<'
293-
cdef list dtype = [(field.name, str(field.count) + byte_order + field.datatype)
334+
cdef list dtype = [(field.name, str(field.count) + byte_order + field.npdtype)
294335
for field in self.fields]
295336
cdef np.ndarray arr_view = arr_raw.view(dtype)
296337
return arr_view
@@ -308,34 +349,3 @@ cdef public class PointCloud[object CyPointCloud, type CyPointCloud_py]:
308349
Disorganize the point cloud. The function can act as updating function
309350
'''
310351
raise NotImplementedError()
311-
312-
cdef str _nonzero_error_msg = "Function {0} returned {1}, please check stderr output!"
313-
314-
# TODO: Inference point type from fields
315-
cpdef PointCloud load_pcd(str path):
316-
cdef PointCloud cloud = PointCloud()
317-
cdef int retval = loadPCDFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation)
318-
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("loadPCDFile", retval))
319-
return cloud
320-
321-
cpdef void save_pcd(str path, PointCloud cloud, PyBool binary=False):
322-
cdef int retval = savePCDFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation, binary)
323-
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("savePCDFile", retval))
324-
325-
# TODO: Inference point type from fields
326-
def load_ply(str path, type return_type=PointCloud):
327-
cdef:
328-
PointCloud cloud
329-
int retval
330-
331-
if return_type == PointCloud:
332-
cloud = PointCloud()
333-
retval = loadPLYFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation)
334-
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("loadPLYFile", retval))
335-
return cloud
336-
else:
337-
raise TypeError("Unsupported return type!")
338-
339-
cpdef void save_ply(str path, PointCloud cloud, PyBool binary=False, PyBool use_camera=True):
340-
cdef int retval = savePLYFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation, binary, use_camera)
341-
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("savePLYFile", retval))

pcl/PointField.pyx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,25 @@ cdef class PointField:
2222

2323
property name:
2424
def __get__(self): return self.base.name.decode('ascii')
25+
# def __set__(self, str value): self.base.name = value.encode('ascii')
2526

2627
property count:
2728
def __get__(self): return self.base.count
29+
# def __set__(self, int value): self.base.count = value
2830

2931
property offset:
3032
def __get__(self): return self.base.offset
33+
# def __set__(self, int value): self.base.offset = value
3134

3235
property datatype:
36+
def __get__(self): return self.base.datatype
37+
# def __set__(self, unsigned char value): self.base.datatype = value
38+
39+
property npdtype:
3340
def __get__(self): return _FIELD_TYPE_MAPPING[self.base.datatype][0]
3441

3542
def __repr__(self):
36-
return "<PointField {0} : {1}>".format(self.name, self.datatype)
43+
return "<PointField {0} : {1}>".format(self.name, self.npdtype)
3744

3845
def to_msg(self):
3946
if not ros_exist: raise ros_error

pcl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from .PointField import *
22
from .PointCloud import *
33
from .Visualizer import *
4+
5+
from .io import *

pcl/io/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_cython_target(_io CXX)
2+
add_library(_io MODULE ${_io})
3+
target_link_libraries(_io ${PCL_LIBRARIES})
4+
python_extension_module(_io)
5+
install(TARGETS _io LIBRARY DESTINATION pcl/io)

pcl/io/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from ._io import *

pcl/io/_io.pyx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from cython.operator cimport dereference as deref
2+
from libcpp cimport bool
3+
4+
from pcl.PointCloud cimport PointCloud
5+
from pcl.io.pcd_io cimport loadPCDFile, savePCDFile
6+
from pcl.io.ply_io cimport loadPLYFile, savePLYFile
7+
8+
cdef str _nonzero_error_msg = "Function {0} returned {1}, please check stderr output!"
9+
10+
# TODO: Inference point type from fields
11+
cpdef PointCloud load_pcd(str path):
12+
cdef PointCloud cloud = PointCloud()
13+
cdef int retval = loadPCDFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation)
14+
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("loadPCDFile", retval))
15+
return cloud
16+
17+
cpdef void save_pcd(str path, PointCloud cloud, bool binary=False):
18+
cdef int retval = savePCDFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation, binary)
19+
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("savePCDFile", retval))
20+
21+
# TODO: Inference point type from fields
22+
def load_ply(str path, type return_type=PointCloud):
23+
cdef:
24+
PointCloud cloud
25+
int retval
26+
27+
if return_type == PointCloud:
28+
cloud = PointCloud()
29+
retval = loadPLYFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation)
30+
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("loadPLYFile", retval))
31+
return cloud
32+
else:
33+
raise TypeError("Unsupported return type!")
34+
35+
cpdef void save_ply(str path, PointCloud cloud, bool binary=False, bool use_camera=True):
36+
cdef int retval = savePLYFile(path.encode('ascii'), deref(cloud._ptr), cloud._origin, cloud._orientation, binary, use_camera)
37+
if retval != 0: raise RuntimeError(_nonzero_error_msg.format("savePLYFile", retval))

0 commit comments

Comments
 (0)