Skip to content

Commit 0cf43a6

Browse files
authored
Feature/multirobot ros (#21)
* add vlc info service * add multirobot server ros node * change initial service to discovery topic * resolve embedding and service name in info msg
1 parent 32e6394 commit 0cf43a6

File tree

4 files changed

+283
-0
lines changed

4 files changed

+283
-0
lines changed

Diff for: extra/ouroboros_msgs/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_message_files(
1515
SparkImageMsg.msg
1616
VlcImageMetadataMsg.msg
1717
VlcImageMsg.msg
18+
VlcInfoMsg.msg
1819
)
1920

2021
add_service_files(

Diff for: extra/ouroboros_msgs/msg/VlcInfoMsg.msg

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
uint8 robot_id
2+
sensor_msgs/CameraInfo camera_info
3+
geometry_msgs/PoseStamped body_T_cam
4+
5+
string embedding_topic
6+
string keypoints_service
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env python3
2+
import rospy
3+
from ouroboros_ros.vlc_multirobot_server_ros import VlcMultirobotServerRos
4+
5+
if __name__ == "__main__":
6+
rospy.init_node("vlc_server_node")
7+
node = VlcMultirobotServerRos()
8+
node.run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/env python3
2+
from dataclasses import dataclass
3+
4+
import rospy
5+
from ouroboros_msgs.msg import VlcImageMsg, VlcInfoMsg
6+
from ouroboros_msgs.srv import (
7+
VlcKeypointQuery,
8+
VlcKeypointQueryResponse,
9+
)
10+
from pose_graph_tools_msgs.msg import PoseGraph
11+
from sensor_msgs.msg import CameraInfo
12+
13+
import ouroboros as ob
14+
from ouroboros_ros.conversions import (
15+
vlc_image_from_msg,
16+
vlc_image_to_msg,
17+
vlc_pose_from_msg,
18+
vlc_pose_to_msg,
19+
)
20+
from ouroboros_ros.utils import build_lc_message, parse_camera_info
21+
from ouroboros_ros.vlc_server_ros import VlcServerRos
22+
23+
24+
@dataclass
25+
class RobotInfo:
26+
session_id: str = None
27+
camera_config: ob.config.Config = None
28+
body_T_cam: ob.VlcPose = None
29+
30+
@classmethod
31+
def from_info_msg(cls, resp: VlcInfoMsg):
32+
camera_config = parse_camera_info(resp.camera_info)
33+
body_T_cam = vlc_pose_from_msg(resp.body_T_cam)
34+
return cls(
35+
session_id=None,
36+
camera_config=camera_config,
37+
body_T_cam=body_T_cam,
38+
)
39+
40+
41+
class VlcMultirobotServerRos(VlcServerRos):
42+
def __init__(self):
43+
super().__init__()
44+
45+
self.track_new_uuids = []
46+
47+
# Spin up robot info server
48+
self.clients = rospy.get_param("~clients")
49+
50+
# Publish embeddings
51+
self.embedding_timer = rospy.Timer(
52+
rospy.Duration(5), self.embedding_timer_callback
53+
)
54+
self.embedding_publisher = rospy.Publisher(
55+
"vlc_embedding", VlcImageMsg, queue_size=10
56+
)
57+
58+
# Keypoint request server
59+
self.keypoint_server = rospy.Service(
60+
"vlc_keypoints_request",
61+
VlcKeypointQuery,
62+
self.process_keypoints_request,
63+
)
64+
65+
self.embedding_subscribers = []
66+
self.keypoint_clients = {}
67+
68+
# Publish info as discovery
69+
self.info_timer = rospy.Timer(rospy.Duration(5), self.info_timer_callback)
70+
self.info_publisher = rospy.Publisher("/vlc_info", VlcInfoMsg)
71+
72+
# Subscribe to other robots' infos as discovery
73+
self.info_subscriber = rospy.Subscriber(
74+
"/vlc_info", VlcInfoMsg, self.vlc_info_callback
75+
)
76+
77+
self.robot_infos = {}
78+
self.uuid_map = {}
79+
self.session_robot_map = {}
80+
81+
def info_timer_callback(self, event):
82+
# NOTE(Yun) maybe should terminate this? But there's a case where a new server shows up
83+
info_msg = VlcInfoMsg()
84+
info_msg.robot_id = self.robot_id
85+
camera_info = CameraInfo()
86+
camera_info.K = self.camera_config.K.flatten()
87+
info_msg.camera_info = camera_info
88+
info_msg.body_T_cam = vlc_pose_to_msg(self.body_T_cam)
89+
90+
info_msg.embedding_topic = rospy.resolve_name("vlc_embedding")
91+
info_msg.keypoints_service = rospy.resolve_name("vlc_keypoints_request")
92+
self.info_publisher.publish(info_msg)
93+
94+
def vlc_info_callback(self, info_msg):
95+
# Note(Yun) alternatively define server(s) in msg
96+
if info_msg.robot_id not in self.clients:
97+
# Not handling this robot
98+
return
99+
100+
if info_msg.robot_id in self.robot_infos:
101+
# Already initialized
102+
return
103+
104+
self.robot_infos[info_msg.robot_id] = RobotInfo.from_info_msg(info_msg)
105+
# Assign session_id
106+
self.robot_infos[
107+
info_msg.robot_id
108+
].session_id = self.vlc_server.register_camera(
109+
info_msg.robot_id,
110+
self.robot_infos[info_msg.robot_id].camera_config,
111+
rospy.Time.now().to_nsec(),
112+
)
113+
self.session_robot_map[self.robot_infos[info_msg.robot_id].session_id] = (
114+
info_msg.robot_id
115+
)
116+
# Subscribe to embeddings
117+
self.embedding_subscribers.append(
118+
rospy.Subscriber(
119+
info_msg.embedding_topic,
120+
VlcImageMsg,
121+
self.client_embedding_callback,
122+
callback_args=info_msg.robot_id,
123+
)
124+
)
125+
# Keypoint request client
126+
self.keypoint_clients[info_msg.robot_id] = rospy.ServiceProxy(
127+
info_msg.keypoints_service, VlcKeypointQuery
128+
)
129+
130+
def process_new_frame(self, image, stamp_ns, hint_pose):
131+
# Need a different one due to sometimes needing to request keypoints
132+
new_uuid = self.vlc_server.add_frame(
133+
self.session_id,
134+
image,
135+
stamp_ns,
136+
pose_hint=hint_pose,
137+
)
138+
self.track_new_uuids.append(new_uuid)
139+
140+
# Find match using the embeddings.
141+
image_match = self.vlc_server.find_match(new_uuid, stamp_ns)
142+
143+
if image_match is None:
144+
return new_uuid, None
145+
146+
match_uuid = image_match.metadata.image_uuid
147+
148+
interrobot = self.session_id != image_match.metadata.session_id
149+
if image_match.keypoints is None and interrobot:
150+
remapped_match_uuid = self.uuid_map[match_uuid]
151+
# Request keypoint and descriptors for match
152+
robot_id = self.session_robot_map[image_match.metadata.session_id]
153+
response = self.keypoint_clients[robot_id](remapped_match_uuid)
154+
vlc_response = vlc_image_from_msg(response.vlc_image)
155+
self.vlc_server.update_keypoints_decriptors(
156+
match_uuid, vlc_response.keypoints, vlc_response.descriptors
157+
)
158+
self.vlc_server.update_keypoint_depths(
159+
match_uuid, vlc_response.keypoint_depths
160+
)
161+
162+
elif not interrobot:
163+
self.vlc_server.compute_keypoints_descriptors(
164+
match_uuid, compute_depths=True
165+
)
166+
167+
# Compute self keypoints and descriptors
168+
self.vlc_server.compute_keypoints_descriptors(new_uuid, compute_depths=True)
169+
170+
lc_list = self.vlc_server.compute_loop_closure_pose(
171+
self.session_id, new_uuid, image_match.metadata.image_uuid, stamp_ns
172+
)
173+
174+
return new_uuid, lc_list
175+
176+
def embedding_timer_callback(self, event):
177+
while len(self.track_new_uuids) > 0:
178+
new_uuid = self.track_new_uuids.pop(0)
179+
vlc_img_msg = vlc_image_to_msg(
180+
self.vlc_server.get_image(new_uuid), convert_image=False
181+
)
182+
self.embedding_publisher.publish(vlc_img_msg)
183+
184+
def client_embedding_callback(self, vlc_img_msg, robot_id):
185+
# Add image
186+
vlc_image = vlc_image_from_msg(vlc_img_msg)
187+
vlc_stamp = vlc_img_msg.header.stamp.to_nsec()
188+
new_uuid = self.vlc_server.add_embedding_no_image(
189+
self.robot_infos[robot_id].session_id,
190+
vlc_image.embedding,
191+
vlc_stamp,
192+
pose_hint=vlc_image.pose_hint,
193+
)
194+
self.uuid_map[new_uuid] = vlc_image.metadata.image_uuid
195+
196+
# Find match
197+
matched_img = self.vlc_server.find_match(
198+
new_uuid, vlc_stamp, search_sessions=[self.session_id]
199+
)
200+
if matched_img is None:
201+
return
202+
203+
rospy.logwarn(f"Found match between robots {robot_id} and {self.robot_id}")
204+
205+
# Request keypoints / descriptors
206+
response = self.keypoint_clients[robot_id](vlc_image.metadata.image_uuid)
207+
vlc_response = vlc_image_from_msg(response.vlc_image)
208+
self.vlc_server.update_keypoints_decriptors(
209+
new_uuid, vlc_response.keypoints, vlc_response.descriptors
210+
)
211+
self.vlc_server.update_keypoint_depths(new_uuid, vlc_response.keypoint_depths)
212+
213+
# Detect loop closures
214+
self.vlc_server.compute_keypoints_descriptors(
215+
matched_img.metadata.image_uuid, compute_depths=True
216+
)
217+
lc_list = self.vlc_server.compute_loop_closure_pose(
218+
self.robot_infos[robot_id].session_id,
219+
new_uuid,
220+
matched_img.metadata.image_uuid,
221+
vlc_stamp,
222+
)
223+
224+
if lc_list is None:
225+
return
226+
227+
rospy.logwarn(f"Found lc between robots {robot_id} and {self.robot_id}")
228+
229+
pose_cov_mat = self.build_pose_cov_mat()
230+
pg = PoseGraph()
231+
pg.header.stamp = rospy.Time.now()
232+
for lc in lc_list:
233+
if not lc.is_metric:
234+
rospy.logwarn("Skipping non-metric loop closure.")
235+
continue
236+
237+
from_time_ns, to_time_ns = self.vlc_server.get_lc_times(lc.metadata.lc_uuid)
238+
239+
lc_edge = build_lc_message(
240+
from_time_ns,
241+
to_time_ns,
242+
robot_id,
243+
self.robot_id,
244+
lc.f_T_t,
245+
pose_cov_mat,
246+
self.robot_infos[robot_id].body_T_cam.as_matrix(),
247+
self.body_T_cam.as_matrix(),
248+
)
249+
250+
pg.edges.append(lc_edge)
251+
self.loop_closure_delayed_queue.append(
252+
(rospy.Time.now().to_sec() + self.lc_send_delay_s, pg)
253+
)
254+
255+
def process_keypoints_request(self, request):
256+
if not self.vlc_server.has_image(request.image_uuid):
257+
rospy.logwarn(f"Image ID {request.image_uuid} not found!")
258+
return VlcKeypointQueryResponse()
259+
260+
self.vlc_server.compute_keypoints_descriptors(
261+
request.image_uuid, compute_depths=True
262+
)
263+
vlc_img = self.vlc_server.get_image(request.image_uuid)
264+
return VlcKeypointQueryResponse(
265+
vlc_image=vlc_image_to_msg(
266+
vlc_img, convert_image=False, convert_embedding=False
267+
)
268+
)

0 commit comments

Comments
 (0)