Skip to content

Commit be3c417

Browse files
some updates - making progress towards a simplified working setup
1 parent ccb8bf1 commit be3c417

11 files changed

Lines changed: 1466 additions & 84 deletions

File tree

predicators/envs/spot_env.py

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from predicators.spot_utils.perception.object_detection import \
2626
AprilTagObjectDetectionID, KnownStaticObjectDetectionID, \
2727
LanguageObjectDetectionID, ObjectDetectionID, _query_detic_sam, \
28-
detect_objects, visualize_all_artifacts
28+
_query_vlm, detect_objects, visualize_all_artifacts
2929
from predicators.spot_utils.perception.object_specific_grasp_selection import \
3030
brush_prompt, bucket_prompt, football_prompt, train_toy_prompt
3131
from predicators.spot_utils.perception.perception_structs import RGBDImage, \
@@ -630,6 +630,12 @@ def step(self, action: Action) -> Observation:
630630
else:
631631
assert action_name == "MoveToReachObject"
632632
obj_pose = self._last_known_object_poses[target_obj]
633+
logging.warning(
634+
f"MoveToReachObject using last known pose for "
635+
f"'{target_obj.name}': "
636+
f"({obj_pose.x:.3f}, {obj_pose.y:.3f}, "
637+
f"{obj_pose.z:.3f}). This pose may be STALE "
638+
f"if the object was not re-detected recently.")
633639
obj_position = math_helpers.Vec3(x=obj_pose.x,
634640
y=obj_pose.y,
635641
z=obj_pose.z)
@@ -734,6 +740,19 @@ def _build_realworld_observation(
734740
if (self._detection_id_to_obj[det_id].type.name == "movable")
735741
# or self._detection_id_to_obj[det_id].name == "clear_plastic_container")
736742
}
743+
for obj, pose in all_objects_in_view.items():
744+
old_pose = self._last_known_object_poses.get(obj)
745+
if old_pose is not None:
746+
dist = np.sqrt((pose.x - old_pose.x)**2 +
747+
(pose.y - old_pose.y)**2 +
748+
(pose.z - old_pose.z)**2)
749+
if dist > 0.1:
750+
logging.warning(
751+
f"Object '{obj.name}' pose changed significantly: "
752+
f"old=({old_pose.x:.3f}, {old_pose.y:.3f}, "
753+
f"{old_pose.z:.3f}) -> "
754+
f"new=({pose.x:.3f}, {pose.y:.3f}, {pose.z:.3f}) "
755+
f"(dist={dist:.3f}m)")
737756
self._last_known_object_poses.update(all_objects_in_view)
738757
objects_in_hand_view = set(self._detection_id_to_obj[det_id]
739758
for det_id in hand_detections)
@@ -1157,37 +1176,52 @@ def _actively_construct_initial_object_views(
11571176
self) -> Tuple[Dict[Object, math_helpers.SE3Pose], Dict[str, Any]]:
11581177
assert self._robot is not None
11591178
assert self._localizer is not None
1160-
# stow_arm(self._robot)
1161-
# go_home(self._robot, self._localizer)
11621179
self._localizer.localize()
1163-
# detection_ids = self._detection_id_to_obj.keys()
1164-
# import ipdb; ipdb.set_trace()
1165-
# detections, artifacts = self._run_init_search_for_objects(
1166-
# set(detection_ids))
1167-
# stow_arm(self._robot)
1168-
# obj_to_se3_pose = {
1169-
# self._detection_id_to_obj[det_id]: val
1170-
# for (det_id, val) in detections.items()
1171-
# }
11721180

1173-
obj_to_se3_pose = get_known_movable_objects()
1174-
obj_to_se3_pose.update(get_known_immovable_objects())
1175-
# Remap object types using _detection_id_to_obj so that e.g.
1176-
# short_round_coffee_table gets _table_type instead of _immovable_type.
1177-
det_id_to_obj = self._detection_id_to_obj
1178-
name_to_remapped_obj = {o.name: o for o in det_id_to_obj.values()}
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+
11791196
obj_to_se3_pose = {
1180-
name_to_remapped_obj.get(o.name, o): pose
1181-
for o, pose in obj_to_se3_pose.items()
1197+
self._detection_id_to_obj[det_id]: val
1198+
for (det_id, val) in detections.items()
11821199
}
1200+
1201+
# For any movable objects not detected, fall back to known poses
1202+
# from the config map file with a warning.
1203+
known_movable = get_known_movable_objects()
1204+
det_id_to_obj = self._detection_id_to_obj
1205+
name_to_remapped_obj = {o.name: o for o in det_id_to_obj.values()}
1206+
for o, pose in known_movable.items():
1207+
remapped = name_to_remapped_obj.get(o.name, o)
1208+
if remapped not in obj_to_se3_pose:
1209+
logging.warning(
1210+
f"Object '{o.name}' not detected by perception. "
1211+
f"Falling back to known pose from config map: "
1212+
f"({pose.x:.3f}, {pose.y:.3f}, {pose.z:.3f}). "
1213+
f"THIS POSE MAY BE INCORRECT!")
1214+
obj_to_se3_pose[remapped] = pose
1215+
1216+
# Always use known poses for immovable objects.
1217+
known_immovable = get_known_immovable_objects()
1218+
for o, pose in known_immovable.items():
1219+
remapped = name_to_remapped_obj.get(o.name, o)
1220+
if remapped not in obj_to_se3_pose:
1221+
obj_to_se3_pose[remapped] = pose
1222+
11831223
self._last_known_object_poses.update(obj_to_se3_pose)
1184-
# Move the robot into a good place to construct the initial state
1185-
# by running VLM predicates.
1186-
prompt = "Finished initial search for objects. Take control of the robot and move it into a good initial location for constructing the initial state of the task. Press 'Enter' when done!"
1187-
_ = input(prompt)
1188-
assert self._lease_client is not None
1189-
self._lease_client.take()
1190-
return obj_to_se3_pose, {}
1224+
return obj_to_se3_pose, artifacts
11911225

11921226
def _run_init_search_for_objects(
11931227
self, detection_ids: Set[ObjectDetectionID]
@@ -1235,7 +1269,7 @@ def _generate_goal_description(self) -> GoalDescription:
12351269
###############################################################################
12361270

12371271
## Constants
1238-
HANDEMPTY_GRIPPER_THRESHOLD = 2.5 # made public for use in perceiver
1272+
HANDEMPTY_GRIPPER_THRESHOLD = 4.5 # made public for use in perceiver
12391273
_ONTOP_Z_THRESHOLD = 0.2
12401274
_INSIDE_Z_THRESHOLD = 0.3
12411275
_ONTOP_SURFACE_BUFFER = 0.48
@@ -2791,8 +2825,12 @@ def detect_objects(
27912825
assert isinstance(
27922826
obj_id, LanguageObjectDetectionID
27932827
), "Only LanguageObjectDetectionIDs are supported."
2794-
object_id_to_img_detections = _query_detic_sam(
2795-
object_ids, rgbd_images) # type: ignore
2828+
if CFG.spot_use_vlm_detection:
2829+
object_id_to_img_detections = _query_vlm(
2830+
object_ids, rgbd_images) # type: ignore
2831+
else:
2832+
object_id_to_img_detections = _query_detic_sam(
2833+
object_ids, rgbd_images) # type: ignore
27962834
# This ^ is currently a mapping of object_id -> camera_name ->
27972835
# SegmentedBoundingBox.
27982836
# We want to do our annotations by camera image, so let's turn this
@@ -4417,9 +4455,9 @@ def _detection_id_to_obj(self) -> Dict[ObjectDetectionID, Object]:
44174455
if obj.name == "short_round_coffee_table":
44184456
table_obj = Object("short_round_coffee_table", _table_type)
44194457
detection_id_to_obj[stat_detection_id] = table_obj
4420-
# if obj.name == "child_play_table":
4421-
# table_obj = Object("child_play_table", _table_type)
4422-
# detection_id_to_obj[stat_detection_id] = table_obj
4458+
elif obj.name == "childs_play_table":
4459+
table_obj = Object("childs_play_table", _table_type)
4460+
detection_id_to_obj[stat_detection_id] = table_obj
44234461
else:
44244462
detection_id_to_obj[stat_detection_id] = obj
44254463

predicators/ground_truth_models/spot_env/options.py

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -596,49 +596,68 @@ def _move_to_reach_and_wipe_surface_policy(name: str, state: State,
596596
objects: Sequence[Object],
597597
params: Array) -> Action:
598598

599+
# =========================================================================
600+
# WIPE IMPLEMENTATION TOGGLE
601+
# Set to True -> use iPhone-based online wiping (VLM + iPhone depth)
602+
# Set to False -> use old hardcoded wiping (spot_wipe_table)
603+
USE_IPHONE_WIPE = True
604+
# =========================================================================
605+
599606
def _fn() -> None:
600607
robot, localizer, _ = get_robot()
601608
target_pose = math_helpers.SE2Pose(params[0], params[1], params[2])
602609
navigate_to_absolute_pose(robot, localizer, target_pose)
603-
# #######################
604-
# # NOTE: just for testing -> ask for the eraser!
605-
# # Move the hand to the side.
606-
# hand_side_pose = math_helpers.SE3Pose(x=0.80,
607-
# y=0.0,
608-
# z=0.25,
609-
# rot=math_helpers.Quat.from_yaw(
610-
# -np.pi / 2))
611-
# move_hand_to_relative_pose(robot, hand_side_pose)
612-
# # Ask for the eraser.
613-
# open_gripper(robot)
614-
# # Press any key, instead of just enter. Useful for remote control.
615-
# msg = "Put the brush in the robot's gripper, then press any key"
616-
# utils.wait_for_any_button_press(msg)
617-
# close_gripper(robot)
618-
# ###################
619-
# NOTE: these parameters hardcoded for a particular child_play_table
620-
# object njk is experimenting with. Please swap out depending on the
621-
# actual object you have
622-
# start_pose = math_helpers.SE3Pose(x=0.8,
623-
# y=-0.35,
624-
# z=-0.08,
625-
# rot=math_helpers.Quat.from_pitch(
626-
# np.pi / 2))
627-
start_pose = math_helpers.SE3Pose(x=0.8,
628-
y=-0.1,
629-
z=-0.1,
630-
rot=math_helpers.Quat.from_pitch(
631-
np.pi / 2))
632-
end_pose = math_helpers.SE3Pose(x=0.65,
633-
y=0.0,
634-
z=0.55,
635-
rot=math_helpers.Quat.from_pitch(
636-
np.pi / 2))
637-
rel_dx, rel_dy, delta_dx, delta_dy, num_wipes, duration_per_stroke = params[
638-
3:9]
639-
wipe_multiple_strokes(robot, start_pose, end_pose,
640-
rel_dx, rel_dy, (delta_dx, delta_dy),
641-
int(num_wipes), duration_per_stroke, int(2))
610+
611+
if USE_IPHONE_WIPE:
612+
# --- NEW: iPhone-based online wiping ---
613+
# Uses VLM (Gemini) to detect the spill via iPhone depth camera,
614+
# then computes wipe params from the bounding box automatically.
615+
# Requires iPhone streaming to be running.
616+
# Lazy import to avoid pulling in open3d/rerun/etc at module load.
617+
from predicators.spot_utils.skills.spot_wipe_online_iphone import \
618+
wipe_online
619+
wipe_online(robot)
620+
else:
621+
# --- OLD: Hardcoded wiping (spot_wipe_table) ---
622+
# #######################
623+
# # NOTE: just for testing -> ask for the eraser!
624+
# # Move the hand to the side.
625+
# hand_side_pose = math_helpers.SE3Pose(x=0.80,
626+
# y=0.0,
627+
# z=0.25,
628+
# rot=math_helpers.Quat.from_yaw(
629+
# -np.pi / 2))
630+
# move_hand_to_relative_pose(robot, hand_side_pose)
631+
# # Ask for the eraser.
632+
# open_gripper(robot)
633+
# # Press any key, instead of just enter. Useful for remote control.
634+
# msg = "Put the brush in the robot's gripper, then press any key"
635+
# utils.wait_for_any_button_press(msg)
636+
# close_gripper(robot)
637+
# ###################
638+
# NOTE: these parameters hardcoded for a particular child_play_table
639+
# object njk is experimenting with. Please swap out depending on the
640+
# actual object you have
641+
# start_pose = math_helpers.SE3Pose(x=0.8,
642+
# y=-0.35,
643+
# z=-0.08,
644+
# rot=math_helpers.Quat.from_pitch(
645+
# np.pi / 2))
646+
start_pose = math_helpers.SE3Pose(x=0.8,
647+
y=-0.1,
648+
z=-0.1,
649+
rot=math_helpers.Quat.from_pitch(
650+
np.pi / 2))
651+
end_pose = math_helpers.SE3Pose(x=0.65,
652+
y=0.0,
653+
z=0.55,
654+
rot=math_helpers.Quat.from_pitch(
655+
np.pi / 2))
656+
rel_dx, rel_dy, delta_dx, delta_dy, num_wipes, \
657+
duration_per_stroke = params[3:9]
658+
wipe_multiple_strokes(robot, start_pose, end_pose,
659+
rel_dx, rel_dy, (delta_dx, delta_dy),
660+
int(num_wipes), duration_per_stroke, int(2))
642661

643662
# Note simulation fn and args not implemented yet.
644663
action_extra_info = SpotActionExtraInfo(name, objects, _fn, tuple(), None,

predicators/perception/spot_perceiver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,8 +697,8 @@ def _create_goal(self, state: State,
697697
clear_trash_can = Object("clear_plastic_container", _trash_can_type)
698698
cardboard_trash_can = Object("cardboard_box_bin", _trash_can_type)
699699
apple = Object("apple", _movable_object_type)
700-
table = Object("short_round_coffee_table", _table_type)
701700
# table = Object("short_round_coffee_table", _table_type)
701+
table = Object("childs_play_table", _table_type)
702702
eraser = Object("pink_furry_eraser", _movable_object_type)
703703
recycling_bin = Object("recycling_bin", _trash_can_type)
704704
soda = Object("soda_can", _movable_object_type)

predicators/pretrained_model_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def _sample_completions(
310310
response = self._client.models.generate_content(
311311
model=self._model_name,
312312
contents=[prompt] + imgs,
313-
config=config)
313+
config=config)
314314
return [response.text]
315315

316316
def get_id(self) -> str:

predicators/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ class GlobalSettings:
179179
spot_robot_ip = "invalid-IP-address"
180180
spot_fiducial_size = 44.45
181181
spot_vision_detection_threshold = 0.5
182+
spot_use_vlm_detection = True
183+
spot_vlm_detection_default_score = 0.9
182184
spot_perception_outdir = "spot_perception_outputs"
183185
spot_render_perception_outputs = True
184186
spot_graph_nav_map = "floor8-sweeping"

predicators/spot_utils/graph_nav_maps/b45-621-cleanup/metadata.yaml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ known-immovable-objects:
3131
x: 1.4
3232
y: 0.5
3333
z: -0.5
34-
short_round_coffee_table:
34+
# short_round_coffee_table:
35+
# x: 1.65
36+
# y: -0.24
37+
# z: 0.14
38+
childs_play_table:
3539
x: 1.65
3640
y: -0.24
3741
z: 0.14
@@ -42,7 +46,12 @@ known-movable-objects:
4246
# y: 0.3
4347
# z: 0.20
4448
# object_type: "movable"
45-
pink_furry_eraser:
49+
# pink_furry_eraser:
50+
# x: 2.5
51+
# y: -0.5
52+
# z: 0.20
53+
# object_type: "movable"
54+
green_and_blue_furry_eraser:
4655
x: 2.5
4756
y: -0.5
4857
z: 0.20
@@ -130,6 +139,13 @@ static-object-features:
130139
width: 0.1
131140
placeable: 1
132141
is_sweeper: 0
142+
green_and_blue_furry_eraser:
143+
shape: 1
144+
height: 0.1
145+
length: 0.1
146+
width: 0.1
147+
placeable: 1
148+
is_sweeper: 0
133149
soda_can:
134150
shape: 2
135151
height: 0.1
@@ -151,15 +167,24 @@ static-object-features:
151167
length: 0.79
152168
width: 0.62
153169
flat_top_surface: 1
170+
childs_play_table:
171+
shape: 2
172+
height: 0.1
173+
length: 0.35
174+
width: 0.62
175+
flat_top_surface: 1
154176

155177
# Helpful for static objects that are up against a wall, for example.
156178
approach_angle_bounds:
157179
short_round_coffee_table: [-0.1, 0.1]
180+
childs_play_table: [-0.1, 0.1]
158181
clear_plastic_container: [1.47, 1.67]
159182
recycling_bin: [-0.1, 0.1]
160183
pink_furry_eraser: [-1.67, -1.47]
184+
green_and_blue_furry_eraser: [-1.67, -1.47]
161185
soda_can: [-1.67, -1.47]
162186

163187
# Wiping skill params.
164188
wipe_location:
165-
short_round_coffee_table: [2.48, 0.205, -2.96856]
189+
short_round_coffee_table: [2.48, 0.205, -2.96856]
190+
childs_play_table: [2.48, 0.205, -2.96856]

0 commit comments

Comments
 (0)