Skip to content

Managed environments actions / observations descriptions #2730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions scripts/environments/export_IODescriptors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Script to an environment with random action agent."""

"""Launch Isaac Sim Simulator first."""

import argparse

from isaaclab.app import AppLauncher

# add argparse arguments
parser = argparse.ArgumentParser(description="Random agent for Isaac Lab environments.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
args_cli.headless = True

# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app

"""Rest everything follows."""

import gymnasium as gym
import torch

import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils import parse_env_cfg

# PLACEHOLDER: Extension template (do not remove this comment)


def main():
"""Random actions agent with Isaac Lab environment."""
# create environment configuration
env_cfg = parse_env_cfg(args_cli.task, device=args_cli.device, num_envs=1, use_fabric=True)
# create environment
env = gym.make(args_cli.task, cfg=env_cfg)

# print info (this is vectorized environment)
print(f"[INFO]: Gym observation space: {env.observation_space}")
print(f"[INFO]: Gym action space: {env.action_space}")
# reset environment
env.reset()

outs = env.unwrapped.get_IO_descriptors
out = outs["observations"]
out_actions = outs["actions"]
# Make a yaml file with the output
import yaml

name = args_cli.task.lower().replace("-", "_")
name = name.replace(" ", "_")

with open(f"{name}_IO_descriptors.yaml", "w") as f:
yaml.safe_dump(outs, f)

for k, v in out_actions.items():
print(f"--- Action term: {k} ---")
for k1, v1 in v.items():
print(f"{k1}: {v1}")

for k, v in out.items():
print(f"--- Obs term: {k} ---")
for k1, v1 in v.items():
print(f"{k1}: {v1}")
env.step(torch.zeros(env.action_space.shape, device=env.unwrapped.device))
env.close()


if __name__ == "__main__":
# run the main function
main()
# close sim app
simulation_app.close()
12 changes: 12 additions & 0 deletions source/isaaclab/isaaclab/envs/manager_based_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ def device(self):
"""The device on which the environment is running."""
return self.sim.device

@property
def get_IO_descriptors(self):
"""Get the IO descriptors for the environment.

Returns:
A dictionary with keys as the group names and values as the IO descriptors.
"""
return {
"observations": self.observation_manager.get_IO_descriptors,
"actions": self.action_manager.get_IO_descriptors,
}

"""
Operations - Setup.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import actions_cfg

Expand Down Expand Up @@ -111,6 +112,15 @@ def raw_actions(self) -> torch.Tensor:
def processed_actions(self) -> torch.Tensor:
return self._processed_actions

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "JointAction"
self._IO_descriptor.joint_names = self._joint_names
return self._IO_descriptor

"""
Operations.
"""
Expand Down
20 changes: 20 additions & 0 deletions source/isaaclab/isaaclab/envs/mdp/actions/joint_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import actions_cfg

Expand Down Expand Up @@ -123,6 +124,25 @@ def raw_actions(self) -> torch.Tensor:
def processed_actions(self) -> torch.Tensor:
return self._processed_actions

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "JointAction"
self._IO_descriptor.joint_names = self._joint_names
self._IO_descriptor.scale = self._scale
# This seems to be always [4xNum_joints] IDK why. Need to check.
if isinstance(self._offset, torch.Tensor):
self._IO_descriptor.offset = self._offset[0].detach().cpu().numpy().tolist()
else:
self._IO_descriptor.offset = self._offset
if self.cfg.clip is not None:
self._IO_descriptor.clip = self._clip
else:
self._IO_descriptor.clip = None
return self._IO_descriptor

"""
Operations.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import actions_cfg

Expand Down Expand Up @@ -105,6 +106,25 @@ def raw_actions(self) -> torch.Tensor:
def processed_actions(self) -> torch.Tensor:
return self._processed_actions

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "JointAction"
self._IO_descriptor.joint_names = self._joint_names
self._IO_descriptor.scale = self._scale
# This seems to be always [4xNum_joints] IDK why. Need to check.
if isinstance(self._offset, torch.Tensor):
self._IO_descriptor.offset = self._offset[0].detach().cpu().numpy().tolist()
else:
self._IO_descriptor.offset = self._offset
if self.cfg.clip is not None:
self._IO_descriptor.clip = self._clip
else:
self._IO_descriptor.clip = None
return self._IO_descriptor

"""
Operations.
"""
Expand Down Expand Up @@ -195,6 +215,20 @@ def __init__(self, cfg: actions_cfg.EMAJointPositionToLimitsActionCfg, env: Mana
# initialize the previous targets
self._prev_applied_actions = torch.zeros_like(self.processed_actions)

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
if isinstance(self._alpha, float):
self._IO_descriptor.alpha = self._alpha
elif isinstance(self._alpha, torch.Tensor):
self._IO_descriptor.alpha = self._alpha[0].detach().cpu().numpy().tolist()
else:
raise ValueError(
f"Unsupported moving average weight type: {type(self._alpha)}. Supported types are float and"
" torch.Tensor."
)
return self._IO_descriptor

def reset(self, env_ids: Sequence[int] | None = None) -> None:
# check if specific environment ids are provided
if env_ids is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import actions_cfg

Expand Down Expand Up @@ -134,6 +135,21 @@ def raw_actions(self) -> torch.Tensor:
def processed_actions(self) -> torch.Tensor:
return self._processed_actions

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "non holonomic actions"
self._IO_descriptor.scale = self._scale
self._IO_descriptor.offset = self._offset
self._IO_descriptor.clip = self._clip
self._IO_descriptor.body_name = self._body_name
self._IO_descriptor.x_joint_name = self._joint_names[0]
self._IO_descriptor.y_joint_name = self._joint_names[1]
self._IO_descriptor.yaw_joint_name = self._joint_names[2]
return self._IO_descriptor

"""
Operations.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import pink_actions_cfg

Expand Down Expand Up @@ -130,6 +131,17 @@ def processed_actions(self) -> torch.Tensor:
"""Get the processed actions tensor."""
return self._processed_actions

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "PinkInverseKinematicsAction"
self._IO_descriptor.pink_controller_joint_names = self._pink_controlled_joint_names
self._IO_descriptor.hand_joint_names = self._hand_joint_names
self._IO_descriptor.extras["controller_cfg"] = self.cfg.controller.__dict__
return self._IO_descriptor

# """
# Operations.
# """
Expand Down
40 changes: 40 additions & 0 deletions source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
from isaaclab.envs.utils.io_descriptors import GenericActionIODescriptor

from . import actions_cfg

Expand Down Expand Up @@ -148,6 +149,23 @@ def jacobian_b(self) -> torch.Tensor:
jacobian[:, 3:, :] = torch.bmm(base_rot_matrix, jacobian[:, 3:, :])
return jacobian

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "TaskSpaceAction"
self._IO_descriptor.body_name = self._body_name
self._IO_descriptor.joint_names = self._joint_names
self._IO_descriptor.scale = self._scale
if self.cfg.clip is not None:
self._IO_descriptor.clip = self.cfg.clip
else:
self._IO_descriptor.clip = None
self._IO_descriptor.extras["controller_cfg"] = self.cfg.controller.__dict__
self._IO_descriptor.extras["body_offset"] = self.cfg.body_offset.__dict__
return self._IO_descriptor

"""
Operations.
"""
Expand Down Expand Up @@ -409,6 +427,28 @@ def jacobian_b(self) -> torch.Tensor:
jacobian[:, 3:, :] = torch.bmm(base_rot_matrix, jacobian[:, 3:, :])
return jacobian

@property
def IO_descriptor(self) -> GenericActionIODescriptor:
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "TaskSpaceAction"
self._IO_descriptor.body_name = self._ee_body_name
self._IO_descriptor.joint_names = self._joint_names
self._IO_descriptor.position_scale = self.cfg.position_scale
self._IO_descriptor.orientation_scale = self.cfg.orientation_scale
self._IO_descriptor.wrench_scale = self.cfg.wrench_scale
self._IO_descriptor.stiffness_scale = self.cfg.stiffness_scale
self._IO_descriptor.damping_ratio_scale = self.cfg.damping_ratio_scale
self._IO_descriptor.nullspace_joint_pos_target = self.cfg.nullspace_joint_pos_target
if self.cfg.clip is not None:
self._IO_descriptor.clip = self.cfg.clip
else:
self._IO_descriptor.clip = None
self._IO_descriptor.extras["controller_cfg"] = self.cfg.controller_cfg.__dict__
self._IO_descriptor.extras["body_offset"] = self.cfg.body_offset.__dict__
return self._IO_descriptor

"""
Operations.
"""
Expand Down
Loading