-
Notifications
You must be signed in to change notification settings - Fork 0
/
as_mapping_node.py
399 lines (300 loc) · 11.7 KB
/
as_mapping_node.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#!/usr/bin/env python
###############################################################################
#
# Autonomous Systems - 2019/2020 IST
#
# Mapping with Depth Camera ROS Node
# Authors:
# - Carolina Costa 84022
# - Francisco Melo, 84053
# - Raul Vaqueiro, 84175
# - Rodrigo Rego, 89213
#
#
#
###############################################################################
from as_mapping.my_ros_independent_class import my_generic_sum_function
import math
import numpy as np
# ROS python api with lots of handy ROS functions
import rospy
# sensor msg
from sensor_msgs.msg import LaserScan
# pose msg
from geometry_msgs.msg import PoseWithCovarianceStamped
# for Occupancy grid msgs
from nav_msgs.msg import OccupancyGrid
# for msgs time info
from std_msgs.msg import Header
# to Synchronize
import message_filters
# to measure blocks of code running time
import time
# for transformations
import tf
class occupancy_grid_mapping():
def __init__(self):
'''
Class constructor.
'''
rospy.init_node('mapping', anonymous=False)
# print message in terminal
rospy.loginfo('Occupancy Grid Mapping Started')
# Subscribe Laser msg topico /scan msg Object - LaserScan
rospy.Subscriber("/scan_depth", LaserScan,self.callbackLaser,queue_size=1)
# Subscribe pose msg topico /amcl_pose msg Object - PoseWithCovarianceStamped
rospy.Subscriber("/amcl_pose",PoseWithCovarianceStamped,self.callbackPose,queue_size=1)
# pose and laser flags
self.pose_msg_received = False
self.laser_msg_received = False
# Set ROS Node Rate
self.loop_rate = rospy.Rate(rospy.get_param('~loop_rate', 10.0))
# Laser Parameters initialization
self.max_range = None
self.min_range = None
self.angle_min = None
self.angle_max = None
self.angle_increment = None
self.ranges = None
# Pose Parameters initialization
self.cov = None
self.quartenion = None
self.pos_x = 0
self.pos_y = 0
# MAP publisher
self.map_pub = rospy.Publisher('/map_mapping',OccupancyGrid, queue_size=0)
# Map Parametes
self.width = 1500
self.height = 1500
self.resolution = 0.02
# Map Initialization
#log odds map
self.lt = np.zeros((self.width, self.height))
#probability map
self.pb = (np.ones((self.width, self.height))*(-1))
# Create occupancy grid msg object to publish
self.map = OccupancyGrid()
# Map message information
self.map.info.resolution=self.resolution
self.map.info.width=self.width
self.map.info.height=self.height
self.map.info.origin.position.x=-5
self.map.info.origin.position.y=-5
self.map.info.origin.position.z=0
self.map.info.origin.orientation.x=0
self.map.info.origin.orientation.y=0
self.map.info.origin.orientation.z=0
self.map.info.origin.orientation.w=0
# Map data must be a list -> Matrix to list conversion
for i in range (0,self.width):
for j in range(0,self.height):
self.map.data.append(self.pb[i][j])
#Publish initial map
self.map_pub.publish(self.map)
# Mapping constants
self.l0 = 0 # Default log probability
self.locc = np.log(0.73/(1-0.73))
self.lfree = np.log(0.27/(1-0.27))
self.alpha = 0.1 # Thickness of obstacles
self.beta = None # rad # Angular width of the sensor beam
self.zmax = None # meters # Max reading from the sensor
self.zmin = None
self.sensor_theta = None
# Map offset
self.offset = 20.0
def callbackPose(self, msg_pose):
'''
This function gets executed everytime a laser scan msg
receives a msg_laser object.
'''
# Time of pose msg
self.pose_t = msg_pose.header.stamp.secs
#sets flag of pose messaged received = true
self.pose_msg_received = True
# Pose Parameters
self.cov = msg_pose.pose.covariance
# Quartenion orientation
self.quartenion = [msg_pose.pose.pose.orientation.x, msg_pose.pose.pose.orientation.y, msg_pose.pose.pose.orientation.z, msg_pose.pose.pose.orientation.w]
# 2D Position
self.pos_x = msg_pose.pose.pose.position.x
self.pos_y = msg_pose.pose.pose.position.y
def callbackLaser(self, msg_laser):
'''
This function gets executed everytime a amcl pose msg receives
a msg_pose object.
'''
self.laser_t = msg_laser.header.stamp.secs
self.laser_msg_received = True
# Laser Parameters
self.max_range = msg_laser.range_max
self.min_range = msg_laser.range_min
self.angle_min = msg_laser.angle_min
self.angle_max = msg_laser.angle_max
self.angle_increment = msg_laser.angle_increment
self.ranges = msg_laser.ranges
self.beta = self.angle_max - self.angle_min # rad # Angular width of the sensor beam
self.zmax = self.max_range
self.zmin = self.min_range
#angular spacing between beams
self.sensor_theta=np.linspace(self.angle_min, self.angle_max,len(self.ranges))
def mapping(self):
'''
Mappin method.
'''
#counts the amount of time spent in mapping algorithm
############################################
start = time.time()
###########################################
# Robot 2D Pose (x,y, yaw)
(roll, pitch, yaw) = tf.transformations.euler_from_quaternion(self.quartenion)
x = (self.pos_x + self.offset)*(1/self.resolution)
y = (self.pos_y + self.offset)*(1/self.resolution)
xt = [x, y, yaw]
zt = self.ranges # Set of measurements
self.occupancy_grid_mapping(xt, zt)
self.pb = 100*(1 - (1 / (1 + np.exp(self.lt)))) # log odds -> probability
#celss outisde cone have 50% probability of being occupied
outside_pos = self.pb == 50
# cell outside the laser cone are marked unknown, in this case, -1
self.pb[outside_pos] = -1
occupied_thr = (self.pb > 50) # occupied cell treshold
free_thr = (self.pb <= 50) & (self.pb >=0) # free cell treshold
self.pb[occupied_thr] = 100 # assings 100% probability to map occupied cells
self.pb[free_thr] = 0 # assings 0% probability to map occupied cells
self.map.data = [] # map data list intialization
# converts map matrix to a list to publish purposes
map_data = np.reshape(self.pb,(1, self.width*self.height))
self.map.data = map_data.tolist()[0]
##########################################
end = time.time()
time_taken = end - start
print('Time: ',time_taken)
##########################################
# publishes map_mapping topic to ROS
self.map_pub.publish(self.map)
def occupancy_grid_mapping(self, xt, zt): # z - mesurement | xt = <x,y,theta>
'''
Occupancy grid algorithm method.
'''
for i in range(len(zt)): # for all measurements
zi = zt[i]
if not np.isnan(zi) and zi <= self.zmax and zi>=self.zmin:
theta_k = self.sensor_theta[i]
x0 = xt[0] # x pos of the robot
y0 = xt[1] # y pos of the robot
theta = xt[2] + theta_k; #orientation of the robot + beam angle
if theta > np.pi:
theta -= 2 * np.pi
elif theta < -np.pi:
theta += 2 * np.pi
# Position of the obstacle
x1 = x0 + ((zi) * math.cos(theta)*(1/self.resolution))
y1 = y0 + ((zi) *math.sin(theta)*(1/self.resolution))
# Position of the obstacle + object thickness alpha
x2 = x0 + ((zi + self.alpha)*math.cos(theta)*(1/self.resolution))
y2 = y0 +((zi + self.alpha)*math.sin(theta)*(1/self.resolution))
x0 = int(round(xt[0]))
y0 = int(round(xt[1]))
x1 = int(round(x1))
y1 = int(round(y1))
x2 = int(round(x2))
y2 = int(round(y2))
# Bresenham’s Line Algorithm
# Cells in the line from x0,y0 to x1,x2 are marked free
self.plotLine(x0, y0, x1, y1, self.lfree)
# Cells in the line from x1,y1 to x2,y2 are marked occupied
self.plotLine(x1, y1, x2, y2, self.locc)
def plotLine(self, x0, y0, x1, y1, lupdate):
'''
Plots a line in a Matrix. Is the slope is positive it calls the
plotLineLow otherwise it calls the polotLineHigh.
'''
if abs(y1 - y0) < abs(x1 - x0):
if x0 > x1:
self.plotLineLow(x1, y1, x0, y0, lupdate)
else:
self.plotLineLow(x0, y0, x1, y1, lupdate)
else:
if y0 > y1:
self.plotLineHigh(x1, y1, x0, y0, lupdate)
else:
self.plotLineHigh(x0, y0, x1, y1, lupdate)
def plotLineLow(self, x0, y0, x1, y1, lupdate):
'''
For gradients between 0 and 1 by checking whether y needs to
increase or decrease. Draws a line in the matrix.
'''
dx = x1 - x0
dy = y1 - y0
xi = 1
yi = 1
if dx < 0:
xi = -1
if dy < 0:
yi = -1
dy = -dy
D = 2*dy - dx
y = y0
# Updates the cells in the line from the log odds matrix
for x in range(x0, x1+1, xi):
self.lt[x][y] +=lupdate- self.l0
if D > 0:
y = y + yi
D = D - 2*dx
D = D + 2*dy
def plotLineHigh(self, x0, y0, x1, y1, lupdate):
'''
For gradients between 0 and 1 by checking whether x needs to increase
or decrease . Draws a line in a matrix.
'''
dx = x1 - x0
dy = y1 - y0
xi = 1
yi = 1
if dx < 0:
xi = -1
dx = -dx
if dy < 0:
yi = 1
D = 2*dx - dy
x = x0
# Updates the cells in the line from the log odds matrix
for y in range(y0, y1+1, yi):
self.lt[x][y] += lupdate - self.l0
if D > 0:
x = x + xi
D = D - 2*dy
D = D + 2*dx
def compare_timestamps(self):
'''
Ensure that no big time difference exists between msgs for sync purposes.
'''
diff = abs(self.pose_t- self.laser_t)
print(diff)
if diff >=1: # ensure difference betwen msg times is less than 1
return False
else:
return True
def run_behavior(self):
'''
Algorithm Master.
'''
while not rospy.is_shutdown():
if self.pose_msg_received == True: #checks for pose msgs
rospy.loginfo('pose msg received!')
if self.laser_msg_received == True: #checks for laser msgs
rospy.loginfo('laser msg received!')
# lower flags
self.laser_msg_received = False
self.pose_msg_received = False
# compare timestamps
if self.compare_timestamps(): # Check if msgs are in sync
self.mapping() # Runs the mapping algorithm
else:
rospy.logwarn('pose and laser msgs are not in sync')
def main():
mapp = occupancy_grid_mapping()
mapp.run_behavior()
print 'success'
if __name__ == '__main__':
main()