👁️ Observation Space¶
The observation space defines the information that an agent perceives from the environment.
Building Observations¶
Basic Observation (Joint State)¶
def _get_obs(self):
qpos = self.data.qpos.copy()
qvel = self.data.qvel.copy()
return np.concatenate([qpos, qvel]).astype(np.float32)
Extended Observation (+ Sensors)¶
def _get_obs(self):
qpos = self.data.qpos.copy()
qvel = self.data.qvel.copy()
# IMU data
imu = self.query_sensor_data(["imu_acc", "imu_gyro"])
# End-effector pose (returns a nested dict: body_name -> {"xpos", "xmat", "xquat"})
result = self.get_body_xpos_xmat_xquat(["ee_link"])
ee_pos = result["ee_link"]["xpos"] # (3,) end-effector position
return np.concatenate([
qpos, qvel,
imu["imu_acc"], imu["imu_gyro"],
ee_pos
]).astype(np.float32)
Dictionary Observation (Multimodal)¶
def _get_obs(self):
return {
"proprio": np.concatenate([
self.data.qpos.copy(),
self.data.qvel.copy()
]).astype(np.float32),
"vision": self.get_camera_image("front_camera"), # Requires custom camera capture
"force": self.query_sensor_data(["ft_sensor"])["ft_sensor"],
}
Automatic Observation Space Inference¶
# Automatically inferred on first reset
obs = self._get_obs()
self.observation_space = self.generate_observation_space(obs)
# For numpy observations → spaces.Box
# For dict observations → spaces.Dict
Observation Normalization¶
class MyEnv(OrcaGymEulerEnv):
def __init__(self, ...):
super().__init__(...)
# Define normalization parameters
self._obs_mean = np.array([...]) # Task-dependent
self._obs_std = np.array([...])
def _get_obs(self):
raw_obs = np.concatenate([
self.data.qpos.copy(),
self.data.qvel.copy()
])
return ((raw_obs - self._obs_mean) / self._obs_std).astype(np.float32)
Observation Construction Best Practices¶
- Use
copy()to avoid subsequent updates overwriting data - Keep float32 for PyTorch/TensorFlow compatibility
- Fixed observation dimensions — shape must not change during inference
- Consider historical information — may need to stack multiple frames of observations