Skip to content

Commit e34a207

Browse files
committed
Refactor replay onto existing replayer flow
1 parent e718d15 commit e34a207

6 files changed

Lines changed: 133 additions & 782 deletions

File tree

python/rcs/envs/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class ArmObsType(TQuatDictType, JointsDictType, TRPYDictType): ...
170170

171171
CartOrJointContType: TypeAlias = TQuatDictType | JointsDictType | TRPYDictType
172172
LimitedCartOrJointContType: TypeAlias = LimitedTQuatRelDictType | LimitedJointsRelDictType | LimitedTRPYRelDictType
173+
SimStateSpec: TypeAlias = dict[str, list[str] | list[int]]
173174

174175

175176
class ArmWithGripper(TQuatDictType, GripperDictType): ...
@@ -212,9 +213,9 @@ def __init__(self, sim: simulation.Sim, return_state=True) -> None:
212213
self.frame_rate = SimpleFrameRate(cfg.frequency, "MoJoCo Simulation Loop")
213214
self.main_greenlet: greenlet | None = None
214215
self.return_state = return_state
215-
self._replay_state: tuple[np.ndarray, int | None] | None = None
216+
self._replay_state: tuple[np.ndarray, SimStateSpec | None] | None = None
216217

217-
def set_replay_state(self, state: np.ndarray, spec: int | None = None):
218+
def set_replay_state(self, state: np.ndarray, spec: SimStateSpec | None = None):
218219
self._replay_state = (state, spec)
219220

220221
def step(self, action: dict[str, Any]) -> tuple[dict[str, Any], float, bool, bool, dict]:

python/rcs/envs/sim.py

Lines changed: 0 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
from typing import Any, cast
33

44
import gymnasium as gym
5-
import numpy as np
65
from rcs._core.common import RobotPlatform
7-
from rcs.envs.base import GripperWrapper
86
from rcs.envs.space_utils import ActObsInfoWrapper
97

10-
import rcs
118
from rcs import sim
129

1310
logger = logging.getLogger(__name__)
@@ -43,36 +40,6 @@ def reset(
4340
return super().reset(seed=seed, options=options)
4441

4542

46-
class SimStateObservationWrapper(ActObsInfoWrapper):
47-
STATE_KEY = "sim_state"
48-
STATE_SPEC_KEY = "sim_state_spec"
49-
STATE_SIZE_KEY = "sim_state_size"
50-
51-
def __init__(self, env):
52-
super().__init__(env)
53-
assert self.env.get_wrapper_attr("PLATFORM") == RobotPlatform.SIMULATION, "Base environment must be simulation."
54-
self.sim = cast(sim.Sim, self.get_wrapper_attr("sim"))
55-
self._state_spec = self.sim.get_state_spec()
56-
self._include_state_spec_in_next_step = True
57-
58-
def observation(self, observation: dict[str, Any], info: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
59-
observation = dict(observation)
60-
sim_state = self.sim.get_state()
61-
observation[self.STATE_KEY] = sim_state
62-
observation[self.STATE_SIZE_KEY] = sim_state.shape[0]
63-
if self._include_state_spec_in_next_step:
64-
observation[self.STATE_SPEC_KEY] = self._state_spec
65-
self._include_state_spec_in_next_step = False
66-
return observation, info
67-
68-
def reset(
69-
self, *, seed: int | None = None, options: dict[str, Any] | None = None
70-
) -> tuple[dict[str, Any], dict[str, Any]]:
71-
obs, info = super().reset(seed=seed, options=options)
72-
self._include_state_spec_in_next_step = True
73-
return obs, info
74-
75-
7643
class GripperWrapperSim(ActObsInfoWrapper):
7744
def __init__(self, env):
7845
super().__init__(env)
@@ -127,187 +94,6 @@ def observation(self, observation: dict[str, Any], info: dict[str, Any]) -> tupl
12794
return observation, info
12895

12996

130-
class RandomObjectPos(gym.Wrapper):
131-
"""
132-
Wrapper to randomly re-place an object in the lab environments.
133-
Given the object's joint name and initial pose, its x, y coordinates are randomized, while z remains fixed.
134-
If include_rotation is true, the object's z-axis rotation (yaw) is also randomized.
135-
136-
Args:
137-
env (gym.Env): The environment to wrap.
138-
simulation (sim.Sim): The simulation instance.
139-
joint_name (str): The name of the free joint attached to the object to manipulate.
140-
init_object_pose (rcs.common.Pose): The initial pose of the object.
141-
include_rotation (bool): Whether to include rotation in the randomization.
142-
"""
143-
144-
def __init__(
145-
self,
146-
env: gym.Env,
147-
joint_name: str,
148-
init_object_pose: rcs.common.Pose,
149-
include_position: bool = True,
150-
include_rotation: bool = False,
151-
x_scale: float = 0.2,
152-
y_scale: float = 0.2,
153-
x_offset: float = 0.1,
154-
y_offset: float = 0.1,
155-
):
156-
super().__init__(env)
157-
self.joint_name = joint_name
158-
self.init_object_pose = init_object_pose
159-
self.include_position = include_position
160-
self.include_rotation = include_rotation
161-
self.x_scale = x_scale
162-
self.y_scale = y_scale
163-
self.x_offset = x_offset
164-
self.y_offset = y_offset
165-
166-
def reset(
167-
self, *, seed: int | None = None, options: dict[str, Any] | None = None
168-
) -> tuple[dict[str, Any], dict[str, Any]]:
169-
if options is not None and "RandomObjectPos.init_object_pose" in options:
170-
assert isinstance(
171-
options["RandomObjectPos.init_object_pose"], rcs.common.Pose
172-
), "RandomObjectPos.init_object_pose must be a rcs.common.Pose"
173-
174-
self.init_object_pose = options["RandomObjectPos.init_object_pose"]
175-
print("Got random object pos!\n", self.init_object_pose)
176-
del options["RandomObjectPos.init_object_pose"]
177-
obs, info = super().reset(seed=seed, options=options)
178-
179-
pos_z = self.init_object_pose.translation()[2]
180-
if self.include_position:
181-
pos_x = self.init_object_pose.translation()[0] + np.random.random() * self.x_scale + self.x_offset
182-
pos_y = self.init_object_pose.translation()[1] + np.random.random() * self.y_scale + self.y_offset
183-
else:
184-
pos_x = self.init_object_pose.translation()[0]
185-
pos_y = self.init_object_pose.translation()[1]
186-
187-
quat = self.init_object_pose.rotation_q() # xyzw format
188-
if self.include_rotation:
189-
random_z_rotation = (np.random.random() - 0.5) * (0.7071068 * 2)
190-
self.get_wrapper_attr("sim").data.joint(self.joint_name).qpos = [
191-
pos_x,
192-
pos_y,
193-
pos_z,
194-
quat[3] + random_z_rotation,
195-
quat[0],
196-
quat[1],
197-
quat[2] + random_z_rotation,
198-
]
199-
else:
200-
self.get_wrapper_attr("sim").data.joint(self.joint_name).qpos = [
201-
pos_x,
202-
pos_y,
203-
pos_z,
204-
quat[3],
205-
quat[0],
206-
quat[1],
207-
quat[2],
208-
]
209-
210-
return obs, info
211-
212-
213-
class RandomCubePos(gym.Wrapper):
214-
"""Wrapper to randomly place cube in the lab environments.
215-
216-
Works only for single robot
217-
"""
218-
219-
def __init__(self, env: gym.Env, include_rotation: bool = False, cube_joint_name="box_joint"):
220-
super().__init__(env)
221-
self.include_rotation = include_rotation
222-
self.cube_joint_name = cube_joint_name
223-
224-
def reset(
225-
self, *, seed: int | None = None, options: dict[str, Any] | None = None
226-
) -> tuple[dict[str, Any], dict[str, Any]]:
227-
obs, info = super().reset(seed=seed, options=options)
228-
229-
iso_cube = np.array([0.498, 0.0, 0.226])
230-
iso_cube_pose = rcs.common.Pose(translation=np.array(iso_cube), rpy_vector=np.array([0, 0, 0])) # type: ignore
231-
iso_cube = self.get_wrapper_attr("robot").to_pose_in_world_coordinates(iso_cube_pose).translation()
232-
pos_z = 0.0288
233-
pos_x = iso_cube[0] + np.random.random() * 0.2 - 0.1
234-
pos_y = iso_cube[1] + np.random.random() * 0.2 - 0.1
235-
236-
if self.include_rotation:
237-
self.get_wrapper_attr("sim").data.joint(self.cube_joint_name).qpos = [
238-
pos_x,
239-
pos_y,
240-
pos_z,
241-
2 * np.random.random() - 1,
242-
0,
243-
0,
244-
1,
245-
]
246-
else:
247-
self.get_wrapper_attr("sim").data.joint(self.cube_joint_name).qpos = [pos_x, pos_y, pos_z, 0, 0, 0, 1]
248-
249-
return obs, info
250-
251-
252-
class PickCubeSuccessWrapper(gym.Wrapper):
253-
"""
254-
Wrapper to check if the cube is successfully picked up in the FR3SimplePickUpSim environment.
255-
Cube must be lifted 10 cm above the robot base.
256-
Computes a reward between 0 and 1 based on:
257-
- TCP to object distance
258-
- cube z height
259-
- whether the arm is standing still once the task is solved.
260-
"""
261-
262-
def __init__(self, env, cube_geom_name="box_geom"):
263-
super().__init__(env)
264-
assert isinstance(self.get_wrapper_attr("robot"), sim.SimRobot), "Robot must be a sim.SimRobot instance."
265-
self._robot = cast(sim.SimRobot, self.get_wrapper_attr("robot"))
266-
self.sim = self.env.get_wrapper_attr("sim")
267-
self.cube_geom_name = cube_geom_name
268-
self.home_pose = self._robot.get_cartesian_position()
269-
self._gripper_closing = 0
270-
self._gripper = self.get_wrapper_attr("gripper")
271-
272-
def step(self, action: dict[str, Any]): # type: ignore
273-
obs, reward, _, truncated, info = super().step(action)
274-
if (
275-
self._gripper.get_normalized_width() > 0.01
276-
and self._gripper.get_normalized_width() < 0.99
277-
and obs["gripper"] == GripperWrapper.BINARY_GRIPPER_CLOSED
278-
):
279-
self._gripper_closing += 1
280-
else:
281-
self._gripper_closing = 0
282-
cube_pose = rcs.common.Pose(translation=self.sim.data.geom(self.cube_geom_name).xpos)
283-
cube_pose = self._robot.to_pose_in_robot_coordinates(cube_pose)
284-
tcp_to_obj_dist = np.linalg.norm(cube_pose.translation() - self._robot.get_cartesian_position().translation())
285-
obj_to_goal_dist = 0.10 - min(cube_pose.translation()[-1], 0.10)
286-
obj_to_goal_dist = np.linalg.norm(cube_pose.translation() - self.home_pose.translation())
287-
# NOTE: 4 depends on the time passing between each step.
288-
is_grasped = (
289-
self._gripper_closing >= 4 # gripper is closing since more than 4 steps
290-
and obs["gripper"] == GripperWrapper.BINARY_GRIPPER_CLOSED # command is still close
291-
and tcp_to_obj_dist <= 0.01 # tcp to cube center is max 1cm
292-
)
293-
success = obj_to_goal_dist <= 0.022 and info["is_grasped"]
294-
movement = np.linalg.norm(self.sim.data.qvel)
295-
296-
reaching_reward = 1 - np.tanh(5 * tcp_to_obj_dist)
297-
place_reward = 1 - np.tanh(5 * obj_to_goal_dist)
298-
static_reward = 1 - np.tanh(5 * movement)
299-
info["is_grasped"] = is_grasped
300-
info["success"] = success
301-
reward = reaching_reward + place_reward * is_grasped + static_reward * success
302-
reward /= 3 # type: ignore
303-
return obs, reward, success, truncated, info
304-
305-
def reset(self, *, seed: int | None = None, options: dict[str, Any] | None = None):
306-
obs, info = super().reset()
307-
self.home_pose = self._robot.get_cartesian_position()
308-
return obs, info
309-
310-
31197
class DigitalTwin(gym.Wrapper):
31298
def __init__(self, env, twin_env):
31399
super().__init__(env)

python/rcs/sim/replayer.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@
99
import rcs.envs.configs as env_configs
1010
import rcs.envs.tasks as env_tasks
1111
from rcs._core.sim import SimConfig
12-
from rcs.envs.base import RelativeTo, SimEnv
12+
from rcs.envs.base import RelativeTo, SimEnv, SimStateSpec
1313
from rcs.envs.scenes import SimEnvCreator
1414
from rcs.envs.storage_wrapper import StorageWrapper
1515

1616

17+
def _normalize_sim_state_spec(value: Any) -> SimStateSpec:
18+
return {
19+
"joint_names": [str(item) for item in value["joint_names"]],
20+
"joint_types": [int(item) for item in value["joint_types"]],
21+
"qpos_sizes": [int(item) for item in value["qpos_sizes"]],
22+
"qvel_sizes": [int(item) for item in value["qvel_sizes"]],
23+
}
24+
25+
1726
@dataclass(frozen=True)
1827
class RecordedSimStep:
1928
step: int
@@ -38,13 +47,13 @@ def sim_state(self) -> np.ndarray:
3847
raise KeyError(msg)
3948

4049
@property
41-
def sim_state_spec(self) -> int | None:
50+
def sim_state_spec(self) -> SimStateSpec | None:
4251
if SimEnv.STATE_SPEC_KEY in self.info:
43-
return int(self.info[SimEnv.STATE_SPEC_KEY])
52+
return _normalize_sim_state_spec(self.info[SimEnv.STATE_SPEC_KEY])
4453

4554
for value in self.info.values():
4655
if isinstance(value, dict) and SimEnv.STATE_SPEC_KEY in value:
47-
return int(value[SimEnv.STATE_SPEC_KEY])
56+
return _normalize_sim_state_spec(value[SimEnv.STATE_SPEC_KEY])
4857

4958
return None
5059

0 commit comments

Comments
 (0)