Skip to content

Commit 03e6c95

Browse files
authored
Feature/changes for multirobot (#18)
* start setting up vlc msgs * (WIP) message conversions * clean up and unittests * (WIP) multi-robot extension ROS node * (WIP) multi-robot extension ROS node * set up tf and exchanging robot info * set up node and launch file * initial minor bug fixes * more debugging * ignore ouroboros-ros tests * update has_image check * allow inter-robot lcs to bypass time filter * minor bug fixes and debugging prints * fix self->other loop closures and fix embedding bugs * finish self->other loop closures * add depth field to vlc image * fix bug from keypoint depths * update how we get keypoint depths and add options for query and match * working multi-robot * fix unittests for query * fix depth getting * remove multi-robot files * remove ouroboros msgs and any multi-robot changes * wrap body_T_cam get in loop * remove msg conversion tests for now
1 parent 70bf626 commit 03e6c95

File tree

9 files changed

+230
-95
lines changed

9 files changed

+230
-95
lines changed

extra/ouroboros_ros/src/ouroboros_ros/utils.py

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,81 @@
11
import numpy as np
22
import rospy
33
import tf2_ros
4+
from pose_graph_tools_msgs.msg import PoseGraphEdge
5+
from scipy.spatial.transform import Rotation as R
46

5-
from ouroboros import VlcPose
7+
import ouroboros as ob
68

79

8-
def get_tf_as_pose(tf_buffer, fixed_frame, body_frame, time=None):
10+
def build_robot_lc_message(
11+
key_from_ns,
12+
key_to_ns,
13+
robot_id,
14+
from_T_to,
15+
pose_cov,
16+
body_T_cam,
17+
):
18+
return build_lc_message(
19+
key_from_ns,
20+
key_to_ns,
21+
robot_id,
22+
robot_id,
23+
from_T_to,
24+
pose_cov,
25+
body_T_cam,
26+
body_T_cam,
27+
)
28+
29+
30+
def build_lc_message(
31+
key_from_ns,
32+
key_to_ns,
33+
robot_from,
34+
robot_to,
35+
from_T_to,
36+
pose_cov,
37+
robot_from_T_cam,
38+
robot_to_T_cam,
39+
):
40+
bodyfrom_T_bodyto = robot_from_T_cam @ from_T_to @ ob.invert_pose(robot_to_T_cam)
41+
relative_position = bodyfrom_T_bodyto[:3, 3]
42+
relative_orientation = R.from_matrix(bodyfrom_T_bodyto[:3, :3])
43+
44+
lc_edge = PoseGraphEdge()
45+
lc_edge.header.stamp = rospy.Time.now()
46+
lc_edge.key_from = key_from_ns
47+
lc_edge.key_to = key_to_ns
48+
lc_edge.robot_from = robot_from
49+
lc_edge.robot_to = robot_to
50+
lc_edge.type = PoseGraphEdge.LOOPCLOSE
51+
lc_edge.pose.position.x = relative_position[0]
52+
lc_edge.pose.position.y = relative_position[1]
53+
lc_edge.pose.position.z = relative_position[2]
54+
q = relative_orientation.as_quat()
55+
lc_edge.pose.orientation.x = q[0]
56+
lc_edge.pose.orientation.y = q[1]
57+
lc_edge.pose.orientation.z = q[2]
58+
lc_edge.pose.orientation.w = q[3]
59+
60+
lc_edge.covariance = pose_cov.flatten()
61+
return lc_edge
62+
63+
64+
def get_tf_as_pose(tf_buffer, fixed_frame, body_frame, time=None, timeout=1.0):
965
if time is None:
1066
time = rospy.Time()
1167
try:
12-
trans = tf_buffer.lookup_transform(fixed_frame, body_frame, time)
68+
trans = tf_buffer.lookup_transform(
69+
fixed_frame, body_frame, time, rospy.Duration(timeout)
70+
)
1371
except (
1472
tf2_ros.LookupException,
1573
tf2_ros.ConnectivityException,
1674
tf2_ros.ExtrapolationException,
17-
):
18-
rospy.logwarn(" Could not transform %s from %s ", fixed_frame, body_frame)
75+
) as e:
76+
rospy.logwarn(
77+
" Could not transform %s from %s: %s ", fixed_frame, body_frame, str(e)
78+
)
1979
return
2080

2181
current_pos = np.array(
@@ -36,5 +96,14 @@ def get_tf_as_pose(tf_buffer, fixed_frame, body_frame, time=None):
3696
)
3797

3898
time_ns = trans.header.stamp.to_nsec()
39-
vlc_pose = VlcPose(time_ns=time_ns, position=current_pos, rotation=current_rot)
99+
vlc_pose = ob.VlcPose(time_ns=time_ns, position=current_pos, rotation=current_rot)
40100
return vlc_pose
101+
102+
103+
def parse_camera_info(info_msg):
104+
K = np.array(info_msg.K).reshape((3, 3))
105+
fx = K[0, 0]
106+
fy = K[1, 1]
107+
cx = K[0, 2]
108+
cy = K[1, 2]
109+
return ob.PinholeCamera(fx=fx, fy=fy, cx=cx, cy=cy)

extra/ouroboros_ros/src/ouroboros_ros/vlc_server_ros.py

Lines changed: 41 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
import numpy as np
99
import rospy
1010
import tf2_ros
11-
from pose_graph_tools_msgs.msg import PoseGraph, PoseGraphEdge
12-
from scipy.spatial.transform import Rotation as R
11+
from pose_graph_tools_msgs.msg import PoseGraph
1312
from sensor_msgs.msg import CameraInfo, Image
1413

1514
import ouroboros as ob
1615
from ouroboros.utils.plotting_utils import plt_fast_pause
17-
from ouroboros_ros.utils import get_tf_as_pose
16+
from ouroboros_ros.utils import (
17+
build_robot_lc_message,
18+
get_tf_as_pose,
19+
parse_camera_info,
20+
)
1821

1922

2023
def update_plot(line, pts, images_to_pose):
@@ -56,38 +59,6 @@ def get_query_and_est_match_position(lc, image_to_pose, body_T_cam):
5659
return query_position, est_match_position
5760

5861

59-
def build_lc_message(
60-
key_from_ns,
61-
key_to_ns,
62-
robot_id,
63-
from_T_to,
64-
pose_cov,
65-
body_T_cam,
66-
):
67-
bodyfrom_T_bodyto = body_T_cam @ from_T_to @ ob.invert_pose(body_T_cam)
68-
relative_position = bodyfrom_T_bodyto[:3, 3]
69-
relative_orientation = R.from_matrix(bodyfrom_T_bodyto[:3, :3])
70-
71-
lc_edge = PoseGraphEdge()
72-
lc_edge.header.stamp = rospy.Time.now()
73-
lc_edge.key_from = key_from_ns
74-
lc_edge.key_to = key_to_ns
75-
lc_edge.robot_from = 0
76-
lc_edge.robot_to = 0
77-
lc_edge.type = PoseGraphEdge.LOOPCLOSE
78-
lc_edge.pose.position.x = relative_position[0]
79-
lc_edge.pose.position.y = relative_position[1]
80-
lc_edge.pose.position.z = relative_position[2]
81-
q = relative_orientation.as_quat()
82-
lc_edge.pose.orientation.x = q[0]
83-
lc_edge.pose.orientation.y = q[1]
84-
lc_edge.pose.orientation.z = q[2]
85-
lc_edge.pose.orientation.w = q[3]
86-
87-
lc_edge.covariance = pose_cov.flatten()
88-
return lc_edge
89-
90-
9162
class VlcServerRos:
9263
def __init__(self):
9364
self.tf_buffer = tf2_ros.Buffer()
@@ -111,13 +82,13 @@ def __init__(self):
11182
server_config = ob.VlcServerConfig.load(config_path)
11283
self.vlc_server = ob.VlcServer(
11384
server_config,
114-
robot_id=0,
85+
robot_id=self.robot_id,
11586
)
11687

117-
camera_config = self.get_camera_config_ros()
118-
print(f"camera config: {camera_config}")
88+
self.camera_config = self.get_camera_config_ros()
89+
print(f"camera config: {self.camera_config}")
11990
self.session_id = self.vlc_server.register_camera(
120-
0, camera_config, rospy.Time.now().to_nsec()
91+
self.robot_id, self.camera_config, rospy.Time.now().to_nsec()
12192
)
12293

12394
self.loop_rate = rospy.Rate(10)
@@ -145,6 +116,8 @@ def __init__(self):
145116
)
146117
self.image_depth_sub.registerCallback(self.image_depth_callback)
147118

119+
self.body_T_cam = self.get_body_T_cam_ros()
120+
148121
def get_camera_config_ros(self):
149122
rate = rospy.Rate(10)
150123
while not rospy.is_shutdown():
@@ -154,14 +127,29 @@ def get_camera_config_ros(self):
154127
rospy.logerr("Timed out waiting for camera info")
155128
rate.sleep()
156129
continue
157-
break
158-
K = np.array(info_msg.K).reshape((3, 3))
159-
fx = K[0, 0]
160-
fy = K[1, 1]
161-
cx = K[0, 2]
162-
cy = K[1, 2]
163-
pinhole = ob.PinholeCamera(fx=fx, fy=fy, cx=cx, cy=cy)
164-
return pinhole
130+
pinhole = parse_camera_info(info_msg)
131+
return pinhole
132+
return None
133+
134+
def get_body_T_cam_ros(self, max_retries=10, retry_delay=0.5):
135+
rate = rospy.Rate(10)
136+
while not rospy.is_shutdown():
137+
try:
138+
return get_tf_as_pose(
139+
self.tf_buffer, self.body_frame, self.camera_frame, rospy.Time.now()
140+
)
141+
except rospy.ROSException:
142+
rospy.logerr("Failed to get body_T_cam transform")
143+
rate.sleep()
144+
return None
145+
146+
def process_new_frame(self, image, stamp_ns, hint_pose):
147+
return self.vlc_server.add_and_query_frame(
148+
self.session_id,
149+
image,
150+
stamp_ns,
151+
pose_hint=hint_pose,
152+
)
165153

166154
def image_depth_callback(self, img_msg, depth_msg):
167155
if not (
@@ -199,17 +187,11 @@ def image_depth_callback(self, img_msg, depth_msg):
199187
print("Cannot get current pose, skipping frame!")
200188
return
201189

202-
body_T_cam = get_tf_as_pose(
203-
self.tf_buffer, self.body_frame, self.camera_frame, img_msg.header.stamp
204-
).as_matrix()
205-
206190
spark_image = ob.SparkImage(rgb=color_image, depth=depth_image)
207-
image_uuid, loop_closures = self.vlc_server.add_and_query_frame(
208-
self.session_id,
209-
spark_image,
210-
img_msg.header.stamp.to_nsec(),
211-
pose_hint=hint_pose,
191+
image_uuid, loop_closures = self.process_new_frame(
192+
spark_image, img_msg.header.stamp.to_nsec(), hint_pose
212193
)
194+
213195
with self.image_pose_lock:
214196
self.images_to_pose[image_uuid] = hint_pose
215197

@@ -223,7 +205,7 @@ def image_depth_callback(self, img_msg, depth_msg):
223205
if self.show_plots:
224206
with self.image_pose_lock:
225207
query_pos, match_pos = get_query_and_est_match_position(
226-
lc, self.images_to_pose, body_T_cam
208+
lc, self.images_to_pose, self.body_T_cam.as_matrix()
227209
)
228210
true_match_pos = self.images_to_pose[lc.to_image_uuid].position
229211
if not lc.is_metric:
@@ -239,13 +221,13 @@ def image_depth_callback(self, img_msg, depth_msg):
239221

240222
from_time_ns, to_time_ns = self.vlc_server.get_lc_times(lc.metadata.lc_uuid)
241223

242-
lc_edge = build_lc_message(
224+
lc_edge = build_robot_lc_message(
243225
from_time_ns,
244226
to_time_ns,
245227
self.robot_id,
246228
lc.f_T_t,
247229
pose_cov_mat,
248-
body_T_cam,
230+
self.body_T_cam.as_matrix(),
249231
)
250232

251233
pg.edges.append(lc_edge)

src/ouroboros/pose_recovery.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ def from_image(
7474
cls, cam: PinholeCamera, img: VlcImage, indices: Optional[np.ndarray] = None
7575
):
7676
"""Get undistorted geometry from keypoints."""
77-
depths = img.get_feature_depths()
77+
if img.keypoint_depths is None:
78+
depths = img.get_feature_depths()
79+
else:
80+
depths = img.keypoint_depths
81+
7882
keypoints = img.keypoints
7983
if indices is not None:
8084
keypoints = keypoints[indices, :]

src/ouroboros/vlc_db/vlc_db.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def add_image(
4141
session_id, image_timestamp, image, pose_hint
4242
)
4343

44+
def has_image(self, image_uuid: str) -> bool:
45+
return self._image_table.has_image(image_uuid)
46+
4447
def get_image(self, image_uuid: str) -> VlcImage:
4548
img = self._image_table.get_image(image_uuid)
4649
if img is None:
@@ -94,8 +97,10 @@ def query_embeddings_max_time(
9497
self,
9598
embedding: np.ndarray,
9699
k: int,
100+
sessions_to_time_filter: [str],
97101
max_time: Union[float, int, datetime],
98102
similarity_metric: Union[str, callable] = "ip",
103+
search_sessions: Optional[List[str]] = None,
99104
) -> ([VlcImage], [float]):
100105
"""Query image embeddings to find the k closest vectors with timestamp older than max_time."""
101106

@@ -105,11 +110,24 @@ def query_embeddings_max_time(
105110
# this to be more efficient and not iterate through the full set of
106111
# vectors
107112

113+
def session_filter(_, vlc_image, similarity):
114+
if search_sessions is None:
115+
return True
116+
return vlc_image.metadata.session_id in search_sessions
117+
108118
def time_filter(_, vlc_image, similarity):
109-
return vlc_image.metadata.epoch_ns < max_time
119+
return (
120+
vlc_image.metadata.epoch_ns < max_time
121+
or vlc_image.metadata.session_id not in sessions_to_time_filter
122+
)
123+
124+
def combined_filter(_, vlc_image, similarity):
125+
return time_filter(_, vlc_image, similarity) and session_filter(
126+
_, vlc_image, similarity
127+
)
110128

111129
ret = self.batch_query_embeddings_filter(
112-
np.array([embedding]), k, time_filter, similarity_metric
130+
np.array([embedding]), k, combined_filter, similarity_metric
113131
)
114132
matches = [t[2] for t in ret[0]]
115133
similarities = [t[0] for t in ret[0]]
@@ -210,6 +228,10 @@ def update_keypoints(self, image_uuid: str, keypoints, descriptors=None):
210228
)
211229
return self.get_image(image_uuid)
212230

231+
def update_keypoint_depths(self, image_uuid: str, keypoint_depths):
232+
self._image_table.update_keypoint_depths(image_uuid, keypoint_depths)
233+
return self.get_image(image_uuid)
234+
213235
def get_keypoints(self, image_uuid: str):
214236
return self._image_table.get_keypoints(image_uuid)
215237

src/ouroboros/vlc_db/vlc_image.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class VlcImage:
1919
image: SparkImage
2020
embedding: np.ndarray = None
2121
keypoints: np.ndarray = None
22+
keypoint_depths: np.ndarray = None
2223
descriptors: np.ndarray = None
2324
pose_hint: VlcPose = None
2425

@@ -39,7 +40,7 @@ def get_feature_depths(self):
3940
if self.keypoints is None:
4041
return None
4142

42-
if self.image.depth is None:
43+
if self.image is None or self.image.depth is None:
4344
return None
4445

4546
# NOTE(nathan) this is ugly, but:

0 commit comments

Comments
 (0)