1- from cython.operator cimport dereference as deref
21from libc.stdint cimport uint8_t
32from libcpp cimport bool
43from libcpp.string cimport string
5- from cpython cimport bool as PyBool
4+ from cython.operator cimport dereference as deref
5+ import sys
66import numpy as np
77cimport numpy as np
8- import warnings
98
109from pcl._boost.smart_ptr cimport make_shared
1110from .ros import ros_exist, ros_error
1211if ros_exist: from pcl.ros cimport from_msg_cloud, to_msg_cloud
1312from pcl.common.conversions cimport toPCLPointCloud2, fromPCLPointCloud2
1413from 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
1714from 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
6765cdef 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))
0 commit comments