2019-11-07 15:02:32 -05:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
2019-11-07 15:24:02 -05:00
|
|
|
# Similar to map_assembler node, this minimal python example shows how
|
|
|
|
|
# to reconstruct the obstacle map by subscribing only to
|
2019-11-07 15:02:32 -05:00
|
|
|
# graph and latest data added to map (for constant network bandwidth usage).
|
|
|
|
|
|
|
|
|
|
import rospy
|
|
|
|
|
from sets import Set
|
|
|
|
|
|
|
|
|
|
import message_filters
|
2023-02-22 23:11:49 -08:00
|
|
|
from rtabmap_msgs.msg import MapGraph
|
2019-11-07 15:02:32 -05:00
|
|
|
from sensor_msgs.msg import PointCloud2
|
|
|
|
|
from geometry_msgs.msg import Pose
|
|
|
|
|
from geometry_msgs.msg import TransformStamped
|
|
|
|
|
from tf2_sensor_msgs.tf2_sensor_msgs import do_transform_cloud
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
posesDict = {}
|
|
|
|
|
cloudsDict = {}
|
|
|
|
|
assembledCloud = PointCloud2()
|
|
|
|
|
pub = rospy.Publisher('assembled_local_grids', PointCloud2, queue_size=10)
|
|
|
|
|
|
|
|
|
|
def callback(graph, cloud):
|
|
|
|
|
global assembledCloud
|
|
|
|
|
global posesDict
|
|
|
|
|
global cloudsDict
|
|
|
|
|
global pub
|
|
|
|
|
|
|
|
|
|
begin = rospy.get_time()
|
|
|
|
|
|
|
|
|
|
nodeId = graph.posesId[-1]
|
|
|
|
|
pose = graph.poses[-1]
|
|
|
|
|
size = cloud.width
|
2019-11-07 15:24:02 -05:00
|
|
|
|
2019-11-07 15:02:32 -05:00
|
|
|
posesDict[nodeId] = pose
|
|
|
|
|
cloudsDict[nodeId] = cloud
|
2019-11-07 15:24:02 -05:00
|
|
|
|
|
|
|
|
# Update pose of our buffered clouds.
|
2019-11-07 15:02:32 -05:00
|
|
|
# Check also if the clouds have moved because of a loop closure. If so, we have to update the rendering.
|
|
|
|
|
maxDiff = 0
|
|
|
|
|
for i in range(0,len(graph.posesId)):
|
|
|
|
|
if graph.posesId[i] in posesDict:
|
|
|
|
|
currentPose = posesDict[graph.posesId[i]].position
|
|
|
|
|
newPose = graph.poses[i].position
|
|
|
|
|
diff = max([abs(currentPose.x-newPose.x), abs(currentPose.y-newPose.y), abs(currentPose.z-newPose.z)])
|
2019-11-07 15:24:02 -05:00
|
|
|
if maxDiff < diff:
|
2019-11-07 15:02:32 -05:00
|
|
|
maxDiff = diff
|
|
|
|
|
else:
|
|
|
|
|
rospy.loginfo("Old node %d not found in cache, creating an empty cloud.", graph.posesId[i])
|
|
|
|
|
posesDict[graph.posesId[i]] = graph.poses[i]
|
|
|
|
|
cloudsDict[graph.posesId[i]] = PointCloud2()
|
2019-11-07 15:24:02 -05:00
|
|
|
|
2019-11-07 15:02:32 -05:00
|
|
|
# If we don't move, some nodes would be removed from the graph, so remove them from our buffered clouds.
|
|
|
|
|
newGraph = Set(graph.posesId)
|
|
|
|
|
totalPoints = 0
|
|
|
|
|
for p in posesDict.keys():
|
|
|
|
|
if p not in newGraph:
|
|
|
|
|
posesDict.pop(p)
|
|
|
|
|
cloudsDict.pop(p)
|
|
|
|
|
else:
|
|
|
|
|
totalPoints = totalPoints + cloudsDict[p].width
|
2019-11-07 15:24:02 -05:00
|
|
|
|
|
|
|
|
if maxDiff > 0.1:
|
2019-11-07 15:02:32 -05:00
|
|
|
# if any node moved more than 10 cm, request an update of the assembled map so far
|
|
|
|
|
newAssembledCloud = PointCloud2()
|
|
|
|
|
rospy.loginfo("Map has been optimized! maxDiff=%.3fm, re-updating the whole map...", maxDiff)
|
|
|
|
|
for i in range(0,len(graph.posesId)):
|
|
|
|
|
posesDict[graph.posesId[i]] = graph.poses[i]
|
|
|
|
|
t = TransformStamped()
|
|
|
|
|
p = posesDict[graph.posesId[i]]
|
|
|
|
|
t.transform.translation = p.position
|
|
|
|
|
t.transform.rotation = p.orientation
|
|
|
|
|
transformedCloud = do_transform_cloud(cloudsDict[graph.posesId[i]], t)
|
|
|
|
|
if i==0:
|
|
|
|
|
newAssembledCloud = transformedCloud
|
|
|
|
|
else:
|
|
|
|
|
newAssembledCloud.data = newAssembledCloud.data + transformedCloud.data
|
|
|
|
|
newAssembledCloud.width = newAssembledCloud.width + transformedCloud.width
|
|
|
|
|
newAssembledCloud.row_step = newAssembledCloud.row_step + transformedCloud.row_step
|
|
|
|
|
assembledCloud = newAssembledCloud
|
|
|
|
|
else:
|
|
|
|
|
t = TransformStamped()
|
|
|
|
|
t.transform.translation = pose.position
|
|
|
|
|
t.transform.rotation = pose.orientation
|
|
|
|
|
transformedCloud = do_transform_cloud(cloud, t)
|
|
|
|
|
# just concatenate new cloud to current assembled map
|
|
|
|
|
if assembledCloud.width == 0:
|
|
|
|
|
assembledCloud = transformedCloud
|
|
|
|
|
else:
|
|
|
|
|
# Adding only the difference would be more efficient
|
|
|
|
|
assembledCloud.data = assembledCloud.data + transformedCloud.data
|
|
|
|
|
assembledCloud.width = assembledCloud.width + transformedCloud.width
|
|
|
|
|
assembledCloud.row_step = assembledCloud.row_step + transformedCloud.row_step
|
2019-11-07 15:24:02 -05:00
|
|
|
|
2019-11-07 15:02:32 -05:00
|
|
|
updateTime = rospy.get_time() - begin
|
2019-11-07 15:24:02 -05:00
|
|
|
|
|
|
|
|
rospy.loginfo("Received node %d (%d pts) at xyz=%.2f %.2f %.2f, q_xyzw=%.2f %.2f %.2f %.2f (Map: Nodes=%d Points=%d Assembled=%d Update=%.0fms)",
|
|
|
|
|
nodeId, size,
|
|
|
|
|
pose.position.x, pose.position.y, pose.position.z,
|
|
|
|
|
pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w,
|
|
|
|
|
len(cloudsDict), totalPoints, assembledCloud.width, updateTime*1000)
|
|
|
|
|
|
2019-11-07 15:02:32 -05:00
|
|
|
assembledCloud.header = graph.header
|
|
|
|
|
pub.publish(assembledCloud)
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
rospy.init_node('assemble_local_grids', anonymous=True)
|
|
|
|
|
graph_sub = message_filters.Subscriber('rtabmap/mapGraph', MapGraph)
|
|
|
|
|
cloud_sub = message_filters.Subscriber('rtabmap/local_grid_obstacle', PointCloud2)
|
|
|
|
|
|
|
|
|
|
ts = message_filters.TimeSynchronizer([graph_sub, cloud_sub], 2)
|
|
|
|
|
ts.registerCallback(callback)
|
|
|
|
|
rospy.spin()
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
try:
|
|
|
|
|
main()
|
|
|
|
|
except rospy.ROSInterruptException:
|
|
|
|
|
pass
|