pointclouds.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 #
00033 # Author: Jon Binney
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 # prefix to the names of dummy fields we add to get byte alignment correct. this needs to not
00045 # clash with any actual field names
00046 DUMMY_FIELD_PREFIX = '__'
00047 
00048 # mappings between PointField types and numpy types
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 # sizes (in bytes) of PointField types
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             # might be extra padding between fields
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     # might be extra padding between points
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 # is this ever more than one?
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     # construct a numpy record type equivalent to the point type of this cloud
00102     dtype_list = pointcloud2_to_dtype(cloud_msg)
00103 
00104     # parse the cloud into an array
00105     cloud_arr = np.fromstring(cloud_msg.data, dtype_list)
00106 
00107     # remove the dummy fields that were added
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     # make it 2d (even if height will be 1)
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 # assumption
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     # not sure if there is a better way to do this. i'm changing the type of the array
00154     # from uint32 to float32, but i don't want any conversion to take place -jdb
00155     rgb_arr.dtype = np.float32
00156 
00157     # create a new array, without r, g, and b, but with rgb float32 field
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     # fill in the new array
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     # fill in the new array
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     # remove crap points
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     # pull out x, y, and z values
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)


python_msg_conversions
Author(s): Jonathan Binney
autogenerated on Thu Jan 2 2014 11:37:16