1+ from elevation_mapping_cupy import ElevationMap
2+ from elevation_mapping_cupy import Parameter
3+
4+ # General
5+ import os
6+ import numpy as np
7+
8+ # ROS
9+ import rospy
10+ import ros_numpy
11+ from tf .transformations import quaternion_matrix
12+ import tf2_ros
13+ import rospkg
14+
15+ from sensor_msgs .msg import PointCloud2
16+ from grid_map_msgs .msg import GridMap
17+ from std_msgs .msg import Float32MultiArray
18+ from std_msgs .msg import MultiArrayLayout as MAL
19+ from std_msgs .msg import MultiArrayDimension as MAD
20+
21+ class ElevationMapWrapper ():
22+ def __init__ (self ):
23+ rospack = rospkg .RosPack ()
24+ self .root = rospack .get_path ("elevation_mapping_cupy" )
25+ weight_file = os .path .join (self .root , "config/weights.dat" )
26+ plugin_config_file = os .path .join (self .root , "config/plugin_config.yaml" )
27+ self .param = Parameter ( use_chainer = False , weight_file = weight_file , plugin_config_file = plugin_config_file )
28+ self .node_name = "elevation_mapping"
29+ # ROS
30+ self .initalize_ros ()
31+ self .register_subscribers ()
32+ self .initalize_elevation_mapping ()
33+ self .register_publishers ()
34+ self .register_timers ()
35+
36+ def initalize_elevation_mapping (self ):
37+ self .param .update ()
38+ self ._pointcloud_process_counter = 0
39+ self ._map = ElevationMap (self .param )
40+ self ._map_data = np .zeros ((self ._map .cell_n - 2 , self ._map .cell_n - 2 ), dtype = np .float32 )
41+ self ._map_q = None
42+ self ._map_t = None
43+
44+ def initalize_ros (self ):
45+ rospy .init_node (self .node_name , anonymous = False )
46+ self ._tf_buffer = tf2_ros .Buffer ()
47+ self ._listener = tf2_ros .TransformListener (self ._tf_buffer )
48+ self .get_ros_params ()
49+
50+ def register_subscribers (self ):
51+ pointcloud_subs = {}
52+ additional = []
53+ fusion = []
54+ for key , config in self .subscribers .items ():
55+ pointcloud_subs [key ] = rospy .Subscriber (config ["topic_name" ], PointCloud2 , self .pointcloud_callback , (config ["channels" ], config ["fusion" ]))
56+ for chan ,fus in zip (config ["channels" ],config ["fusion" ]):
57+ if chan not in additional :
58+ additional .append (chan )
59+ fusion .append (fus )
60+ self .param .additional_layers = additional
61+ self .param .fusion_algorithms = fusion
62+ self .dtype = [
63+ ("x" , np .float32 ),
64+ ("y" , np .float32 ),
65+ ("z" , np .float32 ),
66+ ]
67+ for chan in config ["channels" ]:
68+ self .dtype .append ((chan , np .float32 ))
69+
70+ def register_publishers (self ):
71+ # TODO publishers
72+ self ._publishers = {}
73+ self ._publishers_timers = []
74+ for k , v in self .publishers .items ():
75+ print (f"Register Publisher: { k } " )
76+ self ._publishers [k ] = rospy .Publisher (f"/{ self .node_name } /{ k } " , GridMap , queue_size = 10 )
77+ self ._publishers_timers .append ( rospy .Timer (rospy .Duration (1 / v ["fps" ]), lambda x : self .publish_map (x , k )))
78+
79+ def publish_map (self , t , k ):
80+ print (k )
81+ if self ._map_q is None :
82+ return
83+
84+ gm = GridMap ()
85+ gm .info .header .frame_id = self .map_frame
86+ gm .info .header .stamp = rospy .Time .now ()
87+ gm .info .header .seq = 0
88+ gm .info .resolution = self ._map .resolution
89+ gm .info .length_x = self ._map .map_length
90+ gm .info .length_y = self ._map .map_length
91+ gm .info .pose .position .x = self ._map_t .x
92+ gm .info .pose .position .y = self ._map_t .y
93+ gm .info .pose .position .z = self ._map_t .z
94+ gm .info .pose .orientation .w = 1 # self._map_q.w
95+ gm .info .pose .orientation .x = 0 # self._map_q.x
96+ gm .info .pose .orientation .y = 0 # self._map_q.y
97+ gm .info .pose .orientation .z = 0 # self._map_q.z
98+ gm .layers = self .publishers [k ]["layers" ]
99+ gm .basic_layers = self .publishers [k ]["basic_layers" ]
100+
101+ # arr.layout.dim = [int(gm.info.length_x/gm.info.resolution), int(gm.info.length_y/gm.info.resolution)]
102+
103+ for i , layer in enumerate (gm .layers ):
104+ self ._map .get_map_with_name_ref (layer , self ._map_data )
105+ # self._map_data = np.nan_to_num(self._map_data, False, 0)
106+ data = self ._map_data .copy ()
107+ arr = Float32MultiArray ()
108+ arr .layout = MAL ()
109+ N = self ._map_data .shape [0 ]
110+ arr .layout .dim .append ( MAD (label = "column_index" ,size = N ,stride = int (N * N )))
111+ arr .layout .dim .append ( MAD (label = "row_index" ,size = N ,stride = N ))
112+ arr .data = tuple (np .ascontiguousarray (data ).reshape (- 1 ))
113+ gm .data .append ( arr )
114+
115+ self ._publishers [k ].publish ( gm )
116+
117+ def register_timers (self ):
118+ self .timer_variance = rospy .Timer (rospy .Duration (1 / self .update_variance_fps ), self .update_variance )
119+ self .timer_pose = rospy .Timer (rospy .Duration (1 / self .update_pose_fps ), self .update_pose )
120+ self .timer_time = rospy .Timer (rospy .Duration (self .time_interval ), self .update_time )
121+
122+ def pointcloud_callback (self , msg , config ):
123+ # convert pcd into numpy array
124+ channels = ['x' ,'y' ,'z' ]+ config [0 ]
125+ fusion = config [1 ]
126+ points = ros_numpy .numpify (msg )
127+ pts = np .empty ((points .shape [0 ],0 ))
128+ for ch in channels :
129+ pts = np .append (pts ,np .expand_dims (points [ch ],1 ),axis = 1 )
130+
131+ # get pose of pointcloud
132+ t = rospy .Time (secs = msg .header .stamp .secs , nsecs = msg .header .stamp .nsecs )
133+ try :
134+ transform = self ._tf_buffer .lookup_transform (self .map_frame , msg .header .frame_id , t , rospy .Duration (1.0 ))
135+ except (tf2_ros .LookupException , tf2_ros .ConnectivityException , tf2_ros .ExtrapolationException ) as e :
136+ print (e )
137+ return
138+
139+ t = transform .transform .translation
140+ t = np .array ( [t .x , t .y , t .z ] )
141+ q = transform .transform .rotation
142+ R = quaternion_matrix ([q .w , q .x , q .y , q .z ])[:3 ,:3 ]
143+
144+ # process pointcloud
145+ self ._map .input (pts , channels , R , t , 0 , 0 )
146+ self ._pointcloud_process_counter += 1
147+ print (self ._pointcloud_process_counter )
148+
149+ def update_pose (self , t ):
150+ # get pose of base
151+ t = rospy .Time .now ()
152+ try :
153+ transform = self ._tf_buffer .lookup_transform (self .map_frame , self .base_frame , t , rospy .Duration (1.0 ))
154+ except (tf2_ros .LookupException , tf2_ros .ConnectivityException , tf2_ros .ExtrapolationException ) as e :
155+ print (e )
156+ return
157+ t = transform .transform .translation
158+ trans = np .array ( [t .x , t .y , t .z ] )
159+ q = transform .transform .rotation
160+ rot = quaternion_matrix ([q .w , q .x , q .y , q .z ])[:3 ,:3 ]
161+ self ._map .move_to (trans , rot )
162+
163+ self ._map_t = t
164+ self ._map_q = q
165+
166+ def update_variance (self , t ):
167+ self ._map .update_variance ()
168+
169+ def update_time (self , t ):
170+ self ._map .update_time ()
171+
172+ def get_ros_params (self ):
173+ # TODO fix this here when later launching with launch-file
174+ # This is currently {p} elevation_mapping")
175+ para = os .path .join (self .root , "config/parameters.yaml" )
176+ sens = os .path .join (self .root , "config/sensor_parameter.yaml" )
177+ os .system (f"rosparam delete /{ self .node_name } " )
178+ os .system (f"rosparam load { para } elevation_mapping" )
179+ os .system (f"rosparam load { sens } elevation_mapping" )
180+ self .subscribers = rospy .get_param ("~subscribers" )
181+ self .publishers = rospy .get_param ("~publishers" )
182+ self .initialize_frame_id = rospy .get_param ("~initialize_frame_id" , "base" )
183+ self .initialize_tf_offset = rospy .get_param ("~initialize_tf_offset" , 0.0 )
184+ self .pose_topic = rospy .get_param ("~pose_topic" , "pose" )
185+ self .map_frame = rospy .get_param ("~map_frame" , "map" )
186+ self .base_frame = rospy .get_param ("~base_frame" , "base" )
187+ self .corrected_map_frame = rospy .get_param ("~corrected_map_frame" , "corrected_map" )
188+ self .initialize_method = rospy .get_param ("~initialize_method" , "cubic" )
189+ self .position_lowpass_alpha = rospy .get_param ("~position_lowpass_alpha" , 0.2 )
190+ self .orientation_lowpass_alpha = rospy .get_param ("~orientation_lowpass_alpha" , 0.2 )
191+ self .recordable_fps = rospy .get_param ("~recordable_fps" , 3.0 )
192+ self .update_variance_fps = rospy .get_param ("~update_variance_fps" , 1.0 )
193+ self .time_interval = rospy .get_param ("~time_interval" , 0.1 )
194+ self .update_pose_fps = rospy .get_param ("~update_pose_fps" , 10.0 )
195+ self .initialize_tf_grid_size = rospy .get_param ("~initialize_tf_grid_size" , 0.5 )
196+ self .map_acquire_fps = rospy .get_param ("~map_acquire_fps" , 5.0 )
197+ self .publish_statistics_fps = rospy .get_param ("~publish_statistics_fps" , 1.0 )
198+ self .enable_pointcloud_publishing = rospy .get_param ("~enable_pointcloud_publishing" , False )
199+ self .enable_normal_arrow_publishing = rospy .get_param ("~enable_normal_arrow_publishing" , False )
200+ self .enable_drift_corrected_TF_publishing = rospy .get_param ("~enable_drift_corrected_TF_publishing" , False )
201+ self .use_initializer_at_start = rospy .get_param ("~use_initializer_at_start" , False )
202+
203+ if __name__ == '__main__' :
204+ emw = ElevationMapWrapper ()
205+
206+ while not rospy .is_shutdown ():
207+ try :
208+ rospy .spin ()
209+ except rospy .ROSInterruptException :
210+ print ("Error" )
0 commit comments