|
| 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