3333 Predicate , Action , GoalDescription , EnvironmentTask , STRIPSOperator , \
3434 VLMPredicate , VLMGroundAtom , Observation , Type , SpotActionExtraInfo , \
3535 LiftedAtom , Variable
36+ from predicators .perception .perception_monitor import PerceptionDecision , \
37+ PerceptionMonitor
3638from predicators .spot_utils .perception .object_detection import \
3739 AprilTagObjectDetectionID , KnownStaticObjectDetectionID , \
3840 LanguageObjectDetectionID , ObjectDetectionID , detect_objects_from_language , \
7577_obj_name_to_sim_obj : Dict [str , pbrspot .body .Body ] = {}
7678
7779
80+ def _is_hand_view_action (action_name : str ) -> bool :
81+ """Detect whether the operator causes a hand-view observation."""
82+ return "HandView" in action_name
83+
84+
7885@dataclass (frozen = True )
7986class _SpotObservation :
8087 """An observation for a SpotEnv."""
@@ -213,7 +220,8 @@ def get_robot(
213220 lease_client .take ()
214221 lease_keepalive = LeaseKeepAlive (lease_client ,
215222 must_acquire = True ,
216- return_at_exit = True )
223+ return_at_exit = True ,
224+ warnings = False )
217225 localizer = None
218226 assert path .exists ()
219227 if use_localizer :
@@ -331,6 +339,7 @@ def __init__(self, use_gui: bool = True) -> None:
331339 self ._cached_realworld_observation : Optional [_SpotObservation ] = None
332340 self ._last_vlm_pointing : Optional [
333341 vlm_pointing .VLMPointingResult ] = None
342+ self ._perception_monitor = PerceptionMonitor ()
334343
335344 def _initialize_pybullet (self ) -> None :
336345 # First, check if we have any connections to pybullet already,
@@ -515,7 +524,7 @@ def _maybe_trigger_vlm_pointing(self, action_name: str,
515524 action_objs : Sequence [Object ],
516525 observation : _SpotObservation ) -> None :
517526 """Optionally compute a hand-camera grasp point via Gemini pointing."""
518- if not CFG .spot_use_object_pointing :
527+ if not CFG .spot_use_vlm_pointing :
519528 self ._last_vlm_pointing = None
520529 return
521530
@@ -777,6 +786,11 @@ def reset(self, train_or_test: str, task_idx: int) -> Observation:
777786 while True :
778787 try :
779788 self ._lease_client .take ()
789+ if CFG .spot_enable_manual_reset_teleop and \
790+ not CFG .spot_run_dry :
791+ logging .info (
792+ "[SpotEnv] Manual reset enabled; skipping automatic"
793+ " stow/go-home motions before initial observation." )
780794 self ._current_task = self ._actively_construct_env_task ()
781795 break
782796 except RetryableRpcError as e :
@@ -790,6 +804,8 @@ def reset(self, train_or_test: str, task_idx: int) -> Observation:
790804 self ._cached_realworld_observation = self ._current_observation
791805 else :
792806 self ._cached_realworld_observation = None
807+ self ._perception_monitor .reset (
808+ initial_scan_done = self ._cached_realworld_observation is not None )
793809 self ._current_task_goal_reached = False
794810 self ._last_action = None
795811
@@ -900,96 +916,104 @@ def step(self, action: Action) -> Observation:
900916 logging .warning ("WARNING: the following retryable error "
901917 f"was encountered. Trying again.\n { e } " )
902918
903- observe_only_refresh = CFG .spot_perception_refresh_observe_only
904- force_skip_perception = observe_only_refresh and not is_info_action
905- cached_available = self ._cached_realworld_observation is not None
906- skip_perception = False
919+ decision = self ._perception_monitor .decide (
920+ has_cached_observation = self ._cached_realworld_observation
921+ is not None ,
922+ is_information_gathering = is_info_action ,
923+ is_hand_view_action = _is_hand_view_action (action_name ))
924+ logging .debug ("[SpotEnv] Perception decision: %s" ,
925+ decision .reason )
907926
908- if force_skip_perception :
909- if cached_available :
910- skip_perception = True
911- else :
912- logging .debug (
913- "[SpotEnv] Observe-only perception requested but no "
914- "cached observation is available; running perception "
915- "once to seed cache." )
916- elif CFG .spot_skip_perception_for_manipulation and \
917- not is_info_action and cached_available :
918- skip_perception = True
919-
920- if skip_perception :
927+ if decision .refresh_full :
928+ while True :
929+ try :
930+ next_obs = self ._build_realworld_observation (
931+ next_nonpercept , curr_obs = obs )
932+ self ._perception_monitor .mark_refresh_complete ()
933+ self ._cached_realworld_observation = next_obs
934+ break
935+ except RetryableRpcError as e :
936+ logging .warning (
937+ "WARNING: the following retryable error "
938+ f"was encountered. Trying again.\n { e } " )
939+ elif decision .reuse_cache and \
940+ self ._cached_realworld_observation is not None :
921941 next_obs = self ._reuse_cached_observation (next_nonpercept )
922- self ._cached_realworld_observation = next_obs
923942 else :
924- # Get the new observation. Again, automatically retry if needed.
943+ logging .debug (
944+ "[SpotEnv] No cached observation available; running "
945+ "perception to seed cache." )
925946 while True :
926947 try :
927948 next_obs = self ._build_realworld_observation (
928949 next_nonpercept , curr_obs = obs )
950+ self ._perception_monitor .mark_refresh_complete ()
951+ self ._cached_realworld_observation = next_obs
929952 break
930953 except RetryableRpcError as e :
931954 logging .warning (
932955 "WARNING: the following retryable error "
933956 f"was encountered. Trying again.\n { e } " )
934957
958+ if decision .trigger_pointing :
935959 self ._maybe_trigger_vlm_pointing (action_name , action_objs ,
936960 next_obs )
937961
938- # Very hacky optimization to force viewing/reaching to work.
939- # NOTE: enter the check if "move to" "object"; make sure op names in such pattern are always needed to check
940- if CFG .spot_enable_interactive_checks and \
941- "MoveTo" in action_name and "Object" in action_name :
962+ # Very hacky optimization to force viewing/reaching to work.
963+ # NOTE: enter the check if "move to" "object"; make sure op names in such pattern are always needed to check
964+ if CFG .spot_enable_interactive_checks and \
965+ "MoveTo" in action_name and "Object" in action_name :
966+ logging .warning (
967+ f"Entering object detection check with action_name: { action_name } " )
968+ _ , target_obj = action_objs
969+ # Retry if each of the types of moving failed in their own way.
970+ if action_name == "MoveToHandViewObject" or "Hand" in action_name :
971+ need_retry = target_obj not in \
972+ next_obs .objects_in_hand_view
973+ elif action_name == "MoveToBodyViewObject" :
974+ need_retry = target_obj not in \
975+ next_obs .objects_in_any_view_except_back
976+ elif action_name == "MoveToReachObject" :
977+ obj_pose = self ._last_known_object_poses [target_obj ]
978+ obj_position = math_helpers .Vec3 (x = obj_pose .x ,
979+ y = obj_pose .y ,
980+ z = obj_pose .z )
981+ need_retry = not _obj_reachable_from_spot_pose (
982+ next_obs .robot_pos , obj_position )
983+ else :
984+ need_retry = False
985+ logging .info ("WARNING: object detection check not "
986+ "implemented for this action: %s" ,
987+ action_name )
988+ if need_retry :
942989 logging .warning (
943- f"Entering object detection check with action_name: { action_name } " )
944- _ , target_obj = action_objs
945- # Retry if each of the types of moving failed in their own way.
946- if action_name == "MoveToHandViewObject" or "Hand" in action_name :
947- need_retry = target_obj not in \
948- next_obs .objects_in_hand_view
949- elif action_name == "MoveToBodyViewObject" :
950- need_retry = target_obj not in \
951- next_obs .objects_in_any_view_except_back
952- elif action_name == "MoveToReachObject" :
953- obj_pose = self ._last_known_object_poses [target_obj ]
954- obj_position = math_helpers .Vec3 (x = obj_pose .x ,
955- y = obj_pose .y ,
956- z = obj_pose .z )
957- need_retry = not _obj_reachable_from_spot_pose (
958- next_obs .robot_pos , obj_position )
959- else :
960- need_retry = False
961- logging .info ("WARNING: object detection check not "
962- "implemented for this action: %s" ,
963- action_name )
964- if need_retry :
965- logging .warning (
966- "WARNING: retrying %s because %s was not "
967- "seen/reached." , action_name , target_obj )
968- prompt = (
969- "Hit 'c' to have the robot do a random movement "
970- "or take control and move the robot accordingly. "
971- "Hit the 'Enter' key when you're done!\n " )
972- user_pref = input (prompt )
973- assert self ._lease_client is not None
974- self ._lease_client .take ()
975- angle = 0.0
976- if user_pref == "c" :
977- # Do a small random movement to get a new view.
978- assert isinstance (action_fn_args [1 ],
979- math_helpers .SE2Pose )
980- angle = self ._noise_rng .uniform (- np .pi / 6 ,
981- np .pi / 6 )
982- rel_pose = math_helpers .SE2Pose (0 , 0 , angle )
983- assert isinstance (action_fn_args , tuple )
984- new_action_args = action_fn_args [0 :1 ] + (rel_pose , ) + \
985- action_fn_args [2 :]
986-
987- new_action = utils .create_spot_env_action (
988- SpotActionExtraInfo (action_name , action_objs ,
989- action_fn , new_action_args ,
990- sim_action_fn ,
991- sim_action_args ))
992- return self .step (new_action )
990+ "WARNING: retrying %s because %s was not "
991+ "seen/reached." , action_name , target_obj )
992+ prompt = (
993+ "Hit 'c' to have the robot do a random movement "
994+ "or take control and move the robot accordingly. "
995+ "Hit the 'Enter' key when you're done!\n " )
996+ user_pref = input (prompt )
997+ assert self ._lease_client is not None
998+ self ._lease_client .take ()
999+ angle = 0.0
1000+ if user_pref == "c" :
1001+ # Do a small random movement to get a new view.
1002+ assert isinstance (action_fn_args [1 ],
1003+ math_helpers .SE2Pose )
1004+ angle = self ._noise_rng .uniform (- np .pi / 6 ,
1005+ np .pi / 6 )
1006+ rel_pose = math_helpers .SE2Pose (0 , 0 , angle )
1007+ assert isinstance (action_fn_args , tuple )
1008+ new_action_args = action_fn_args [0 :1 ] + (rel_pose , ) + \
1009+ action_fn_args [2 :]
1010+
1011+ new_action = utils .create_spot_env_action (
1012+ SpotActionExtraInfo (action_name , action_objs ,
1013+ action_fn , new_action_args ,
1014+ sim_action_fn ,
1015+ sim_action_args ))
1016+ return self .step (new_action )
9931017 self ._allow_incidental_discovery = False
9941018
9951019 self ._current_observation = next_obs
@@ -1464,13 +1488,22 @@ def _actively_construct_initial_object_views(
14641488 bool or None ]]:
14651489 assert self ._robot is not None
14661490 assert self ._localizer is not None
1467- stow_arm (self ._robot )
1468- go_home (self ._robot , self ._localizer )
1491+ skip_motion = (CFG .spot_enable_manual_reset_teleop
1492+ and not CFG .spot_run_dry )
1493+ if skip_motion :
1494+ logging .info (
1495+ "[SpotEnv] Manual reset enabled; skipping automatic stow/go_home"
1496+ " during initial observation. Ensure the robot is already in a"
1497+ " safe pose before continuing." )
1498+ else :
1499+ stow_arm (self ._robot )
1500+ go_home (self ._robot , self ._localizer )
14691501 self ._localizer .localize ()
14701502 detection_ids = self ._detection_id_to_obj .keys ()
14711503 detections , vlm_atom_dict = self ._run_init_search_for_objects (
14721504 set (detection_ids ))
1473- stow_arm (self ._robot )
1505+ if not skip_motion :
1506+ stow_arm (self ._robot )
14741507 obj_to_se3_pose = {
14751508 self ._detection_id_to_obj [det_id ]: val
14761509 for (det_id , val ) in detections .items ()
@@ -1485,12 +1518,18 @@ def _run_init_search_for_objects(
14851518 """Have the hand look down from high up at first."""
14861519 assert self ._robot is not None
14871520 assert self ._localizer is not None
1488- hand_pose = math_helpers .SE3Pose (x = 0.80 ,
1489- y = 0.0 ,
1490- z = 0.75 ,
1491- rot = math_helpers .Quat .from_pitch (
1492- np .pi / 3 ))
1493- move_hand_to_relative_pose (self ._robot , hand_pose )
1521+ skip_motion = (CFG .spot_enable_manual_reset_teleop
1522+ and not CFG .spot_run_dry )
1523+ if skip_motion :
1524+ logging .info ("[SpotEnv] Manual reset enabled; keeping operator-set"
1525+ " hand pose for initial scan." )
1526+ else :
1527+ hand_pose = math_helpers .SE3Pose (x = 0.80 ,
1528+ y = 0.0 ,
1529+ z = 0.75 ,
1530+ rot = math_helpers .Quat .from_pitch (
1531+ np .pi / 3 ))
1532+ move_hand_to_relative_pose (self ._robot , hand_pose )
14941533 # Input VLM predicates (to filter task-relevant ones) and objects
14951534 # Obtain detections and additionally VLM ground atoms
14961535 detections , artifacts , vlm_atom_dict = init_search_for_objects (
@@ -5007,7 +5046,7 @@ def _detection_id_to_obj(self) -> Dict[ObjectDetectionID, Object]:
50075046 # NOTE: cup is container type
50085047 objects_to_detect = [
50095048 ("cardboard_box" , "cardboard_box" , _container_type ),
5010- ("cup" , "orange_cup " , _container_type ),
5049+ ("cup" , "green_cup " , _container_type ),
50115050 ]
50125051
50135052 # Add detection object prompt and save object identifier
0 commit comments