Skip to content

Commit 5a709fb

Browse files
yayay! mapping works!
1 parent 1a13d85 commit 5a709fb

8 files changed

Lines changed: 298 additions & 70 deletions

File tree

predicators/approaches/bilevel_planning_approach.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ def _solve(self, task: Task, timeout: int) -> Callable[[State], Action]:
7474

7575
for act in nsrt_plan:
7676
print(act)
77-
import ipdb
78-
ipdb.set_trace()
77+
import ipdb; ipdb.set_trace()
7978

8079
# Run full bilevel planning.
8180
else:

predicators/envs/spot_env.py

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
from predicators.spot_utils.skills.spot_navigation import go_home, \
4040
navigate_to_absolute_pose
4141
from predicators.spot_utils.skills.spot_stow_arm import stow_arm
42-
from predicators.spot_utils.spot_localization import SpotLocalizer
42+
from predicators.spot_utils.spot_localization import OdomLocalizer, \
43+
SpotLocalizer
4344
from predicators.spot_utils.utils import _base_object_type, _broom_type, \
4445
_container_type, _cup_type, _dustpan_type, _immovable_object_type, \
4546
_juicer_type, _movable_object_type, _robot_type, _table_type, \
@@ -176,7 +177,6 @@ def get_robot(
176177
return None, None, None
177178
setup_logging(False)
178179
hostname = CFG.spot_robot_ip
179-
path = get_graph_nav_dir()
180180
sdk = create_standard_sdk("PredicatorsClient-")
181181
robot = sdk.create_robot(hostname)
182182
authenticate(robot)
@@ -187,9 +187,14 @@ def get_robot(
187187
must_acquire=True,
188188
return_at_exit=True)
189189
localizer = None
190-
assert path.exists()
191190
if use_localizer:
192-
localizer = SpotLocalizer(robot, path, lease_client, lease_keepalive)
191+
if CFG.spot_mapless_mode:
192+
localizer = OdomLocalizer(robot)
193+
else:
194+
path = get_graph_nav_dir()
195+
assert path.exists()
196+
localizer = SpotLocalizer(robot, path, lease_client,
197+
lease_keepalive)
193198
return robot, localizer, lease_client
194199

195200

@@ -1178,25 +1183,28 @@ def _actively_construct_initial_object_views(
11781183
assert self._localizer is not None
11791184
self._localizer.localize()
11801185

1181-
# Capture images from the current position and run detection
1182-
# without moving the robot.
1183-
# rgbds = capture_images(self._robot, self._localizer)
1184-
# detection_ids = set(self._detection_id_to_obj.keys())
1185-
# detections, artifacts = detect_objects(
1186-
# detection_ids, rgbds, self._allowed_regions)
1187-
1188-
# if CFG.spot_render_perception_outputs:
1189-
# outdir = Path(CFG.spot_perception_outdir)
1190-
# time_str = time.strftime("%Y%m%d-%H%M%S")
1191-
# detections_outfile = outdir / f"detections_{time_str}.png"
1192-
# no_detections_outfile = outdir / f"no_detections_{time_str}.png"
1193-
# visualize_all_artifacts(artifacts, detections_outfile,
1194-
# no_detections_outfile)
1195-
1196-
# Detection is currently disabled (commented out above), so default
1197-
# to empty so the fallback to known poses below still works.
1198-
detections: Dict[ObjectDetectionID, math_helpers.SE3Pose] = {}
1199-
artifacts: Dict[str, Any] = {}
1186+
if CFG.spot_mapless_mode:
1187+
# In mapless mode, we must actually run object detection since
1188+
# there are no metadata poses to fall back on. Use the init
1189+
# search procedure which spins the robot to find objects.
1190+
detection_ids = set(self._detection_id_to_obj.keys())
1191+
# Filter out KnownStaticObjectDetectionIDs (e.g. floor) since
1192+
# those don't need searching.
1193+
search_ids = {
1194+
d for d in detection_ids
1195+
if not isinstance(d, KnownStaticObjectDetectionID)
1196+
}
1197+
detections, artifacts = self._run_init_search_for_objects(
1198+
search_ids)
1199+
# Also include known static objects (e.g. floor).
1200+
for d in detection_ids:
1201+
if isinstance(d, KnownStaticObjectDetectionID):
1202+
detections[d] = d.pose
1203+
else:
1204+
# Detection is currently disabled for map-based mode; fall back
1205+
# to known poses from metadata below.
1206+
detections = {}
1207+
artifacts = {}
12001208

12011209
obj_to_se3_pose = {
12021210
self._detection_id_to_obj[det_id]: val
@@ -4422,49 +4430,50 @@ def get_name(cls) -> str:
44224430
@property
44234431
def _detection_id_to_obj(self) -> Dict[ObjectDetectionID, Object]:
44244432
detection_id_to_obj: Dict[ObjectDetectionID, Object] = {}
4425-
# detection_id_to_obj[LanguageObjectDetectionID(
4426-
# "bottle/clear_cup/clear_trashcan")] = Object(
4427-
# "clear_plastic_container", _trash_can_type)
4428-
# detection_id_to_obj[LanguageObjectDetectionID(
4429-
# "apple/red_ball")] = Object("apple", _movable_object_type)
4430-
# detection_id_to_obj[LanguageObjectDetectionID(
4431-
# "soda_can")] = Object(
4432-
# "soda_can", _movable_object_type)
4433-
# detection_id_to_obj[LanguageObjectDetectionID(
4434-
# "fluffy_toy/flower_arrangement")] = Object(
4435-
# "fluffy_green_toy_eraser", _movable_object_type)
4436-
# detection_id_to_obj[LanguageObjectDetectionID(
4437-
# "blue_block")] = Object("blue_block", _movable_object_type)
4438-
4439-
# detection_id_to_obj[LanguageObjectDetectionID(
4440-
# "cardboard_box")] = Object("cardboard_box_bin", _trash_can_type)
4441-
# detection_id_to_obj[LanguageObjectDetectionID(
4442-
# "blue_coffee_cup")] = Object("blue_coffee_cup",
4443-
# _movable_object_type)
4444-
for obj, pose in get_known_movable_objects().items():
4445-
if obj.name == "pink_furry_eraser":
4446-
eraser_obj = Object("pink_furry_eraser",
4447-
_movable_object_type)
4448-
detection_id = LanguageObjectDetectionID("toy/flower_arrangement")
4449-
detection_id_to_obj[detection_id] = eraser_obj
4450-
elif obj.name == "clear_plastic_container":
4451-
container_obj = Object("clear_plastic_container",
4452-
_trash_can_type)
4453-
detection_id = LanguageObjectDetectionID("bottle/clear_cup/clear_trashcan")
4454-
detection_id_to_obj[detection_id] = container_obj
4455-
else:
4456-
detection_id = LanguageObjectDetectionID(obj.name)
4457-
detection_id_to_obj[detection_id] = obj
4458-
for obj, pose in get_known_immovable_objects().items():
4459-
stat_detection_id = KnownStaticObjectDetectionID(obj.name, pose)
4460-
if obj.name == "short_round_coffee_table":
4461-
table_obj = Object("short_round_coffee_table", _table_type)
4462-
detection_id_to_obj[stat_detection_id] = table_obj
4463-
elif obj.name == "childs_play_table":
4464-
table_obj = Object("childs_play_table", _table_type)
4465-
detection_id_to_obj[stat_detection_id] = table_obj
4466-
else:
4433+
4434+
if CFG.spot_mapless_mode:
4435+
# In mapless mode, hardcode the objects to detect via
4436+
# language rather than relying on metadata.
4437+
detection_id_to_obj[LanguageObjectDetectionID(
4438+
"green_and_blue_furry_eraser")] = Object(
4439+
"green_and_blue_furry_eraser", _movable_object_type)
4440+
detection_id_to_obj[LanguageObjectDetectionID(
4441+
"childs_play_table")] = Object(
4442+
"childs_play_table", _table_type)
4443+
# Floor is always a known static object.
4444+
for obj, pose in get_known_immovable_objects().items():
4445+
stat_detection_id = KnownStaticObjectDetectionID(
4446+
obj.name, pose)
44674447
detection_id_to_obj[stat_detection_id] = obj
4448+
else:
4449+
for obj, pose in get_known_movable_objects().items():
4450+
if obj.name == "pink_furry_eraser":
4451+
eraser_obj = Object("pink_furry_eraser",
4452+
_movable_object_type)
4453+
detection_id = LanguageObjectDetectionID(
4454+
"toy/flower_arrangement")
4455+
detection_id_to_obj[detection_id] = eraser_obj
4456+
elif obj.name == "clear_plastic_container":
4457+
container_obj = Object("clear_plastic_container",
4458+
_trash_can_type)
4459+
detection_id = LanguageObjectDetectionID(
4460+
"bottle/clear_cup/clear_trashcan")
4461+
detection_id_to_obj[detection_id] = container_obj
4462+
else:
4463+
detection_id = LanguageObjectDetectionID(obj.name)
4464+
detection_id_to_obj[detection_id] = obj
4465+
for obj, pose in get_known_immovable_objects().items():
4466+
stat_detection_id = KnownStaticObjectDetectionID(
4467+
obj.name, pose)
4468+
if obj.name == "short_round_coffee_table":
4469+
table_obj = Object("short_round_coffee_table",
4470+
_table_type)
4471+
detection_id_to_obj[stat_detection_id] = table_obj
4472+
elif obj.name == "childs_play_table":
4473+
table_obj = Object("childs_play_table", _table_type)
4474+
detection_id_to_obj[stat_detection_id] = table_obj
4475+
else:
4476+
detection_id_to_obj[stat_detection_id] = obj
44684477

44694478
return detection_id_to_obj
44704479

predicators/ground_truth_models/spot_env/nsrts.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,27 @@ def _move_and_wipe_table_sampler(state: State, goal: Set[GroundAtom],
284284
rng: np.random.Generator,
285285
objs: Sequence[Object]) -> Array:
286286
target_obj = objs[1]
287-
move_sample_params = load_spot_metadata()["wipe_location"][target_obj.name]
287+
if CFG.spot_mapless_mode:
288+
# In mapless mode, derive the wipe approach pose from the
289+
# detected table pose and robot pose in the state.
290+
robot_obj = objs[0]
291+
table_x = state.get(target_obj, "x")
292+
table_y = state.get(target_obj, "y")
293+
robot_x = state.get(robot_obj, "x")
294+
robot_y = state.get(robot_obj, "y")
295+
# Angle from table to robot (i.e., the direction to stand back).
296+
angle_table_to_robot = np.arctan2(robot_y - table_y,
297+
robot_x - table_x)
298+
# Stand ~0.85m from the table center, along that direction.
299+
standoff = 0.85
300+
stand_x = table_x + standoff * np.cos(angle_table_to_robot)
301+
stand_y = table_y + standoff * np.sin(angle_table_to_robot)
302+
# Face toward the table (opposite direction).
303+
facing_angle = angle_table_to_robot + np.pi
304+
move_sample_params = [stand_x, stand_y, facing_angle]
305+
else:
306+
move_sample_params = load_spot_metadata()["wipe_location"][
307+
target_obj.name]
288308
# Hardcoded params; probably need to change in the future.
289309
rel_dx = 0.0
290310
# # Params for child play table:

predicators/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class GlobalSettings:
184184
spot_perception_outdir = "spot_perception_outputs"
185185
spot_render_perception_outputs = True
186186
spot_graph_nav_map = "floor8-sweeping"
187+
spot_mapless_mode = False
187188
spot_grasp_stow_volume_threshold = 0.074
188189
spot_run_dry = False
189190
spot_use_perfect_samplers = False # for debugging

predicators/spot_utils/perception/object_detection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def _get_detection_score(img_detections: Dict[str, SegmentedBoundingBox],
244244
continue
245245
# If the detected pose is outside the allowed bounds, skip.
246246
pose_xy = np.array([pose.x, pose.y])
247-
if allowed_regions is not None:
247+
if allowed_regions is not None and len(allowed_regions) > 0:
248248
in_allowed_region = False
249249
for region in allowed_regions:
250250
if region.find_simplex(pose_xy).item() >= 0:

predicators/spot_utils/spot_localization.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,39 @@ def localize(self,
172172
return None
173173

174174

175+
class OdomLocalizer:
176+
"""Uses the odom frame as the world frame. No GraphNav map required.
177+
178+
Drop-in replacement for SpotLocalizer for short sessions in small
179+
areas where odometry drift is acceptable.
180+
"""
181+
182+
def __init__(self, robot: Robot) -> None:
183+
self._robot = robot
184+
self._robot.time_sync.wait_for_sync()
185+
self._robot_pose = math_helpers.SE3Pose(0, 0, 0, math_helpers.Quat())
186+
self.localize()
187+
188+
def get_last_robot_pose(self) -> math_helpers.SE3Pose:
189+
"""Get the last estimated robot pose in the odom frame."""
190+
return self._robot_pose
191+
192+
def get_world_tform_odom(self) -> math_helpers.SE3Pose:
193+
"""Identity transform since world frame IS the odom frame."""
194+
return math_helpers.SE3Pose(0, 0, 0, math_helpers.Quat())
195+
196+
def localize(self,
197+
num_retries: int = 10,
198+
retry_wait_time: float = 1.0) -> None:
199+
"""Read odom_tform_body from robot state."""
200+
robot_state = get_robot_state(self._robot)
201+
odom_tform_body = get_odom_tform_body(
202+
robot_state.kinematic_state.transforms_snapshot)
203+
self._robot_pose = math_helpers.SE3Pose.from_proto(
204+
odom_tform_body.to_proto())
205+
return None
206+
207+
175208
if __name__ == "__main__":
176209
# Run this file alone to test manually.
177210
# Make sure to pass in --spot_robot_ip.

predicators/spot_utils/utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,59 @@ def get_graph_nav_dir() -> Path:
195195
return upload_dir / CFG.spot_graph_nav_map
196196

197197

198+
def _get_mapless_static_object_features(env_name: str) -> Dict:
199+
"""Return static object features for mapless mode, per environment."""
200+
# Floor is always present.
201+
feats: Dict = {
202+
"floor": {
203+
"shape": 1,
204+
"height": 0.0001,
205+
"length": 10000000,
206+
"width": 10000000,
207+
"flat_top_surface": 1,
208+
},
209+
}
210+
if env_name == "spot_vlm_table_wiping_invented_predicates_env":
211+
feats["childs_play_table"] = {
212+
"shape": 2,
213+
"height": 0.1,
214+
"length": 0.35,
215+
"width": 0.62,
216+
"flat_top_surface": 1,
217+
}
218+
feats["green_and_blue_furry_eraser"] = {
219+
"shape": 1,
220+
"height": 0.1,
221+
"length": 0.1,
222+
"width": 0.1,
223+
"placeable": 1,
224+
"is_sweeper": 0,
225+
}
226+
return feats
227+
228+
198229
def load_spot_metadata() -> Dict:
199-
"""Load from the YAML config."""
230+
"""Load from the YAML config.
231+
232+
In mapless mode, returns sensible defaults so that no map directory
233+
or metadata.yaml file is required.
234+
"""
235+
from predicators.settings import CFG # pylint: disable=import-outside-toplevel
236+
if CFG.spot_mapless_mode:
237+
return {
238+
"allowed-regions": {},
239+
"known-immovable-objects": {
240+
# The floor always exists with a sensible default pose.
241+
# z=-0.5 is roughly where the floor sits relative to the
242+
# robot's body frame origin.
243+
"floor": {"x": 0.0, "y": 0.0, "z": -0.5},
244+
},
245+
"known-movable-objects": {},
246+
"static-object-features": _get_mapless_static_object_features(
247+
CFG.env),
248+
"spot-home-pose": {"x": 0.0, "y": 0.0, "angle": 0.0},
249+
"april-tag-offsets": {},
250+
}
200251
config_filepath = get_graph_nav_dir() / "metadata.yaml"
201252
with open(config_filepath, "r", encoding="utf-8") as f:
202253
config = yaml.safe_load(f)

0 commit comments

Comments
 (0)