00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 '''
00035 Functions for working with PointCloud2.
00036 '''
00037
00038 __docformat__ = "restructuredtext en"
00039
00040
00041 import numpy as np
00042 from sensor_msgs.msg import PointCloud2, PointField
00043
00044
00045
00046 DUMMY_FIELD_PREFIX = '__'
00047
00048
00049 type_mappings = [(PointField.INT8, np.dtype('int8')), (PointField.UINT8, np.dtype('uint8')), (PointField.INT16, np.dtype('int16')),
00050 (PointField.UINT16, np.dtype('uint16')), (PointField.INT32, np.dtype('int32')), (PointField.UINT32, np.dtype('uint32')),
00051 (PointField.FLOAT32, np.dtype('float32')), (PointField.FLOAT64, np.dtype('float64'))]
00052 pftype_to_nptype = dict(type_mappings)
00053 nptype_to_pftype = dict((nptype, pftype) for pftype, nptype in type_mappings)
00054
00055
00056 pftype_sizes = {PointField.INT8: 1, PointField.UINT8: 1, PointField.INT16: 2, PointField.UINT16: 2,
00057 PointField.INT32: 4, PointField.UINT32: 4, PointField.FLOAT32: 4, PointField.FLOAT64: 8}
00058
00059 def pointcloud2_to_dtype(cloud_msg):
00060 '''Convert a list of PointFields to a numpy record datatype.
00061 '''
00062 offset = 0
00063 np_dtype_list = []
00064 for f in cloud_msg.fields:
00065 while offset < f.offset:
00066
00067 np_dtype_list.append(('%s%d' % (DUMMY_FIELD_PREFIX, offset), np.uint8))
00068 offset += 1
00069 np_dtype_list.append((f.name, pftype_to_nptype[f.datatype]))
00070 offset += pftype_sizes[f.datatype]
00071
00072
00073 while offset < cloud_msg.point_step:
00074 np_dtype_list.append(('%s%d' % (DUMMY_FIELD_PREFIX, offset), np.uint8))
00075 offset += 1
00076
00077 return np_dtype_list
00078
00079 def arr_to_fields(cloud_arr):
00080 '''Convert a numpy record datatype into a list of PointFields.
00081 '''
00082 fields = []
00083 for field_name in cloud_arr.dtype.names:
00084 np_field_type, field_offset = cloud_arr.dtype.fields[field_name]
00085 pf = PointField()
00086 pf.name = field_name
00087 pf.datatype = nptype_to_pftype[np_field_type]
00088 pf.offset = field_offset
00089 pf.count = 1
00090 fields.append(pf)
00091 return fields
00092
00093 def pointcloud2_to_array(cloud_msg, split_rgb=False):
00094 ''' Converts a rospy PointCloud2 message to a numpy recordarray
00095
00096 Reshapes the returned array to have shape (height, width), even if the height is 1.
00097
00098 The reason for using np.fromstring rather than struct.unpack is speed... especially
00099 for large point clouds, this will be <much> faster.
00100 '''
00101
00102 dtype_list = pointcloud2_to_dtype(cloud_msg)
00103
00104
00105 cloud_arr = np.fromstring(cloud_msg.data, dtype_list)
00106
00107
00108 cloud_arr = cloud_arr[
00109 [fname for fname, _type in dtype_list if not (fname[:len(DUMMY_FIELD_PREFIX)] == DUMMY_FIELD_PREFIX)]]
00110
00111 if split_rgb:
00112 cloud_arr = split_rgb_field(cloud_arr)
00113
00114 return np.reshape(cloud_arr, (cloud_msg.height, cloud_msg.width))
00115
00116 def array_to_pointcloud2(cloud_arr, stamp=None, frame_id=None, merge_rgb=False):
00117 '''Converts a numpy record array to a sensor_msgs.msg.PointCloud2.
00118 '''
00119 if merge_rgb:
00120 cloud_arr = merge_rgb_fields(cloud_arr)
00121
00122
00123 cloud_arr = np.atleast_2d(cloud_arr)
00124
00125 cloud_msg = PointCloud2()
00126
00127 if stamp is not None:
00128 cloud_msg.header.stamp = stamp
00129 if frame_id is not None:
00130 cloud_msg.header.frame_id = frame_id
00131 cloud_msg.height = cloud_arr.shape[0]
00132 cloud_msg.width = cloud_arr.shape[1]
00133 cloud_msg.fields = arr_to_fields(cloud_arr)
00134 cloud_msg.is_bigendian = False
00135 cloud_msg.point_step = cloud_arr.dtype.itemsize
00136 cloud_msg.row_step = cloud_msg.point_step*cloud_arr.shape[1]
00137 cloud_msg.is_dense = all([np.isfinite(cloud_arr[fname]).all() for fname in cloud_arr.dtype.names])
00138 cloud_msg.data = cloud_arr.tostring()
00139 return cloud_msg
00140
00141 def merge_rgb_fields(cloud_arr):
00142 '''Takes an array with named np.uint8 fields 'r', 'g', and 'b', and returns an array in
00143 which they have been merged into a single np.float32 'rgb' field. The first byte of this
00144 field is the 'r' uint8, the second is the 'g', uint8, and the third is the 'b' uint8.
00145
00146 This is the way that pcl likes to handle RGB colors for some reason.
00147 '''
00148 r = np.asarray(cloud_arr['r'], dtype=np.uint32)
00149 g = np.asarray(cloud_arr['g'], dtype=np.uint32)
00150 b = np.asarray(cloud_arr['b'], dtype=np.uint32)
00151 rgb_arr = np.array((r << 16) | (g << 8) | (b << 0), dtype=np.uint32)
00152
00153
00154
00155 rgb_arr.dtype = np.float32
00156
00157
00158 new_dtype = []
00159 for field_name in cloud_arr.dtype.names:
00160 field_type, field_offset = cloud_arr.dtype.fields[field_name]
00161 if field_name not in ('r', 'g', 'b'):
00162 new_dtype.append((field_name, field_type))
00163 new_dtype.append(('rgb', np.float32))
00164 new_cloud_arr = np.zeros(cloud_arr.shape, new_dtype)
00165
00166
00167 for field_name in new_cloud_arr.dtype.names:
00168 if field_name == 'rgb':
00169 new_cloud_arr[field_name] = rgb_arr
00170 else:
00171 new_cloud_arr[field_name] = cloud_arr[field_name]
00172
00173 return new_cloud_arr
00174
00175 def split_rgb_field(cloud_arr):
00176 '''Takes an array with a named 'rgb' float32 field, and returns an array in which
00177 this has been split into 3 uint 8 fields: 'r', 'g', and 'b'.
00178
00179 (pcl stores rgb in packed 32 bit floats)
00180 '''
00181 rgb_arr = cloud_arr['rgb'].copy()
00182 rgb_arr.dtype = np.uint32
00183 r = np.asarray((rgb_arr >> 16) & 255, dtype=np.uint8)
00184 g = np.asarray((rgb_arr >> 8) & 255, dtype=np.uint8)
00185 b = np.asarray(rgb_arr & 255, dtype=np.uint8)
00186
00187 # create a new array, without rgb, but with r, g, and b fields
00188 new_dtype = []
00189 for field_name in cloud_arr.dtype.names:
00190 field_type, field_offset = cloud_arr.dtype.fields[field_name]
00191 if not field_name == 'rgb':
00192 new_dtype.append((field_name, field_type))
00193 new_dtype.append(('r', np.uint8))
00194 new_dtype.append(('g', np.uint8))
00195 new_dtype.append(('b', np.uint8))
00196 new_cloud_arr = np.zeros(cloud_arr.shape, new_dtype)
00197
00198
00199 for field_name in new_cloud_arr.dtype.names:
00200 if field_name == 'r':
00201 new_cloud_arr[field_name] = r
00202 elif field_name == 'g':
00203 new_cloud_arr[field_name] = g
00204 elif field_name == 'b':
00205 new_cloud_arr[field_name] = b
00206 else:
00207 new_cloud_arr[field_name] = cloud_arr[field_name]
00208 return new_cloud_arr
00209
00210 def get_xyz_points(cloud_array, remove_nans=True, dtype=np.float):
00211 '''Pulls out x, y, and z columns from the cloud recordarray, and returns
00212 a 3xN matrix.
00213 '''
00214
00215 if remove_nans:
00216 mask = np.isfinite(cloud_array['x']) & np.isfinite(cloud_array['y']) & np.isfinite(cloud_array['z'])
00217 cloud_array = cloud_array[mask]
00218
00219
00220 points = np.zeros(list(cloud_array.shape) + [3], dtype=dtype)
00221 points[...,0] = cloud_array['x']
00222 points[...,1] = cloud_array['y']
00223 points[...,2] = cloud_array['z']
00224
00225 return points
00226
00227 def pointcloud2_to_xyz_array(cloud_msg, remove_nans=True):
00228 return get_xyz_points(pointcloud2_to_array(cloud_msg), remove_nans=remove_nans)