Skip to content
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

Humanoids pr #1165

Merged
merged 18 commits into from
Mar 1, 2023
Merged
66 changes: 61 additions & 5 deletions examples/interactive_play.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ def step_env(env, action_name, action_args):


def get_input_vel_ctlr(
skip_pygame, arm_action, env, not_block_input, agent_to_control
skip_pygame,
arm_action,
env,
not_block_input,
agent_to_control,
use_joint_control,
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
):
if skip_pygame:
return step_env(env, "empty", {}), None, False
Expand All @@ -98,10 +103,15 @@ def get_input_vel_ctlr(
else:
agent_k = ""
arm_action_name = f"{agent_k}arm_action"
base_action_name = f"{agent_k}base_velocity"
arm_key = "arm_action"
grip_key = "grip_action"
base_key = "base_vel"

if use_joint_control:
base_action_name = f"{agent_k}humanjoint_action"
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
base_key = "human_joints_trans"
else:
base_action_name = f"{agent_k}base_velocity"
arm_key = "arm_action"
grip_key = "grip_action"
base_key = "base_vel"

if arm_action_name in env.action_space.spaces:
arm_action_space = env.action_space.spaces[arm_action_name].spaces[
Expand Down Expand Up @@ -252,6 +262,42 @@ def get_input_vel_ctlr(
logger.info("[play.py]: Snapping")
magic_grasp = 1

if use_joint_control:
# Add random noise to human arms but keep global transform
(
new_joints,
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
root_trans,
) = env._sim.articulated_agent.get_joint_transform()
num_joints = len(new_joints) // 4
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
root_trans = np.array(root_trans)
new_joints_quat = [
mn.Quaternion(
mn.Vector3(new_joints[(4 * index) : (4 * index + 3)]),
new_joints[4 * index + 3],
)
for index in range(num_joints)
]
rotated_joints_quat = []
for index, joint_quat in enumerate(new_joints_quat):
random_vec = np.random.rand(3)
random_angle = np.random.rand() * 360 / 60
rotation_quat = mn.Quaternion.rotation(
mn.Rad(random_angle), mn.Vector3(random_vec).normalized()
)
new_quat = joint_quat
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
if index > 10:
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
new_quat = joint_quat * rotation_quat
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
rotated_joints_quat.append(new_quat)
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
new_joints = np.concatenate(
[
np.array(list(quat.vector) + [quat.scalar])
for quat in rotated_joints_quat
]
)
base_action = np.concatenate(
[new_joints.reshape(-1), root_trans.transpose().reshape(-1)]
)

if keys[pygame.K_PERIOD]:
# Print the current position of the articulated agent, useful for debugging.
pos = [
Expand Down Expand Up @@ -434,6 +480,7 @@ def play_env(env, args, config):
env,
not free_cam.is_free_cam_mode,
agent_to_control,
args.use_joint_control,
)

if not args.no_render and keys[pygame.K_c]:
Expand Down Expand Up @@ -612,12 +659,21 @@ def has_pygame():
action="store_true",
help="If specified, does not add the inverse kinematics end-effector control.",
)

parser.add_argument(
"--use-joint-control",
action="store_true",
default=False,
help="Control joints of articulated agent.",
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
)

parser.add_argument(
"--gfx",
action="store_true",
default=False,
help="Save a GFX replay file.",
)

xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
parser.add_argument("--load-actions", type=str, default=None)
parser.add_argument("--cfg", type=str, default=DEFAULT_CFG)
parser.add_argument(
Expand Down
2 changes: 1 addition & 1 deletion habitat-lab/habitat/articulated_agents/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Robot Design
==============================

Habitat supports different kinds of agents that can interact in the environment, including robots or humanoids. These agents are represented as articulated objects, consisting of multiple rigid parts (links) connected by joints to perform rotational or translational motion. This readme file details the design of these articulated agent modules, which robots inherit from. We provide the implementation of different robots under the `robots` folder.
Habitat supports different kinds of agents that can interact in the environment, including robots or humanoids. These agents are represented as articulated objects, consisting of multiple rigid parts (links) connected by joints to perform rotational or translational motion. This readme file details the design of these articulated agent modules, which robots and humanoids inherit from. We provide the implementation of different robots under the `robots` folder, and the implementation of different humanoids under `humanoids`.

---

Expand Down
19 changes: 17 additions & 2 deletions habitat-lab/habitat/articulated_agents/articulated_agent_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@ def __init__(
sim: Simulator,
limit_robo_joints: bool = True,
fixed_based: bool = True,
maintain_link_order=False,
base_type="mobile",
sim_obj=None,
**kwargs,
):
r"""Constructor"""
r"""Constructor
:param params: The parameter of the base articulated agent.
:param urdf_path: The path to the articulated agent's URDF file.
:param sim: The simulator.
:param limit_robo_joints: If true, joint limits of articulated agent are always
enforced.
:param fixed_base: If the articulated agent's base is fixed or not.
:param maintain_link_order: Whether to to preserve the order of
links parsed from URDF files as link indices. Needed for
compatibility with PyBullet.
:param sim_obj: Pointer to the simulated object
"""
assert base_type in [
"mobile",
"leg",
Expand All @@ -42,6 +54,7 @@ def __init__(
self._limit_robo_joints = limit_robo_joints
self._base_type = base_type
self.sim_obj = sim_obj
self._maintain_link_order = maintain_link_order
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved

# NOTE: the follow members cache static info for improved efficiency over querying the API
# maps joint ids to motor settings for convenience
Expand Down Expand Up @@ -71,7 +84,9 @@ def reconfigure(self) -> None:
if self.sim_obj is None or not self.sim_obj.is_alive:
ao_mgr = self._sim.get_articulated_object_manager()
self.sim_obj = ao_mgr.add_articulated_object_from_urdf(
self.urdf_path, fixed_base=self._fixed_base
self.urdf_path,
fixed_base=self._fixed_base,
maintain_link_order=self._maintain_link_order,
)
# set correct gains for wheels
if (
Expand Down
35 changes: 35 additions & 0 deletions habitat-lab/habitat/articulated_agents/humanoids/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Humanoid Design
==============================

Habitat also supports humanoid avatars. These avatars are represented as articulated objects connected via joints with rotational motion. The current version only includes a kinematic humanoid, which can be controlled by specifying the joint rotations and object transformation.

You can download the humanoid avatar by running:

```
python -m habitat_sim.utils.datasets_download --uids humanoid_data --data-path data/
```

From the home directory.

---

## Humanoid Component Design

1. **Humanoid**
- `kinematic_humanoid.py` is built upon `mobile_manipulator.py` and can be controlled by setting the joint and object transforms, via the function `set_joint_transform`.

1. **Mobile Manipulator Parameters**
- Parameters such as the camera's transformation, end-effector position, control gain, and more are specified in each robot class (e.g., here, `kinematic_humanoid.py`).

1. **Humanoid Articulated Object**

The humanoid is represented as an articulated object made out of capsules and spheres. It contains 20 links and 19 joints, and is designed to match the morphology of the SMPL skeleton, as referenced [here](https://files.is.tue.mpg.de/black/talks/SMPL-made-simple-FAQs.pdf). Note that the current model does not include the hand or foot joint, hence the 20 links instead of 24, and that the root joint is represented via the object transform, hence the 19 joints.


## Testing the Humanoid

You can test a simple demo where the humanoid moves their arms at random using:

```
python examples/interactive_play.py --never-end --disable-inverse-kinematics --use-joint-control --cfg benchmark/rearrange/play_human.yaml
```
13 changes: 13 additions & 0 deletions habitat-lab/habitat/articulated_agents/humanoids/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python3

# Copyright (c) Meta Platforms, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from habitat.articulated_agents.humanoids.kinematic_humanoid import (
KinematicHumanoid,
)

__all__ = [
"KinematicHumanoid",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright (c) Meta Platforms, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import List

import magnum as mn
import numpy as np

import habitat_sim
from habitat.articulated_agents.mobile_manipulator import (
ArticulatedAgentCameraParams,
MobileManipulator,
MobileManipulatorParams,
)


class KinematicHumanoid(MobileManipulator):
def _get_humanoid_params(self):
return MobileManipulatorParams(
arm_joints=[], # For now we do not add arm_joints
gripper_joints=[],
wheel_joints=None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! Do we expect that humans have wheels?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but I found I could not ignore this parameter, even if it was optional. I think that in order to be able to ignore it, we need to set a default argument to wheels, here of None. I can do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm mobile_manipulator.py was probably designed with Fetch in mind, so I wouldn't change the default to None. Why not set it as False here instead of None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have here a check of wheel_joints existing as a param or being None, this is why I was proposing to set the default to None. If we set it as False, or [], we would need to change all these checks as well. I can do that if you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the type of this variable? This should indicate what the no wheel option should be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional[List[int]]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I faced the same issue before, my solution at that time was to use inheritance, which is not the best way to do this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on how you used inheritance? I think the best would be to make it List[int] (no optional) with a default argument [] and whenever we are checking this param, we replace the None/False checks fro empty list.

arm_init_params=None,
gripper_init_params=None,
gripper_closed_state=np.array([]),
gripper_open_state=np.array([]),
gripper_state_eps=None,
wheel_mtr_pos_gain=None,
wheel_mtr_vel_gain=None,
wheel_mtr_max_impulse=None,
ee_offset=[mn.Vector3(), mn.Vector3()],
ee_links=[15, 9],
ee_constraint=np.zeros((2, 2, 3)),
cameras={
"robot_head": ArticulatedAgentCameraParams(
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
cam_offset_pos=mn.Vector3(0.0, 0.5, 0.25),
cam_look_at_pos=mn.Vector3(0.0, 0.5, 0.75),
attached_link_id=-1,
),
"robot_third": ArticulatedAgentCameraParams(
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
cam_offset_pos=mn.Vector3(-1.2, 2.0, -1.2),
cam_look_at_pos=mn.Vector3(1, 0.0, 0.75),
attached_link_id=-1,
),
},
arm_mtr_pos_gain=0.3,
arm_mtr_vel_gain=0.3,
arm_mtr_max_impulse=10.0,
base_offset=mn.Vector3(0, -0.9, 0),
base_link_names={
"base_link",
},
ee_count=2,
)

def __init__(
self, urdf_path, sim, limit_robo_joints=False, fixed_base=False
):
super().__init__(
self._get_humanoid_params(),
urdf_path,
sim,
limit_robo_joints,
fixed_base,
maintain_link_order=True,
)

def reconfigure(self) -> None:
"""Instantiates the human in the scene. Loads the URDF, sets initial state of parameters, joints, motors, etc..."""
super().reconfigure()
self.sim_obj.motion_type = habitat_sim.physics.MotionType.KINEMATIC

def set_joint_transform(
self, joint_list: List[float], transform: mn.Matrix4
) -> None:
"""Sets the joints and base transform of the humanoid"""
# TODO: should this go into articulated agent?
self.sim_obj.joint_positions = joint_list
self.sim_obj.transformation = transform

def get_joint_transform(self):
"""Returns the joints and base transform of the humanoid"""
# TODO: should this go into articulated agent?
return self.sim_obj.joint_positions, self.sim_obj.transformation
7 changes: 6 additions & 1 deletion habitat-lab/habitat/articulated_agents/manipulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(
limit_robo_joints: bool = True,
fixed_based: bool = True,
sim_obj=None,
maintain_link_order=False,
akshararai marked this conversation as resolved.
Show resolved Hide resolved
**kwargs,
):
r"""Constructor"""
Expand All @@ -38,6 +39,7 @@ def __init__(
self._limit_robo_joints = limit_robo_joints
self._fixed_base = fixed_based
self.sim_obj = sim_obj
self._maintain_link_order = maintain_link_order
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved

# Adapt Manipulator params to support multiple end effector indices
# NOTE: the follow members cache static info for improved efficiency over querying the API
Expand Down Expand Up @@ -78,7 +80,9 @@ def reconfigure(self) -> None:
if self.sim_obj is None or not self.sim_obj.is_alive:
ao_mgr = self._sim.get_articulated_object_manager()
self.sim_obj = ao_mgr.add_articulated_object_from_urdf(
self.urdf_path, fixed_base=self._fixed_base
self.urdf_path,
fixed_base=self._fixed_base,
maintain_link_order=self._maintain_link_order,
)
if self._limit_robo_joints:
# automatic joint limit clamping after each call to sim.step_physics()
Expand Down Expand Up @@ -177,6 +181,7 @@ def update(self) -> None:
sens_obj.node.transformation = (
orthonormalize_rotation_shear(cam_transform)
)

if self._fix_joint_values is not None:
self.arm_joint_pos = self._fix_joint_values

Expand Down
17 changes: 11 additions & 6 deletions habitat-lab/habitat/articulated_agents/mobile_manipulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ class MobileManipulatorParams:
arm_mtr_vel_gain: float
arm_mtr_max_impulse: float

wheel_mtr_pos_gain: float
wheel_mtr_vel_gain: float
wheel_mtr_max_impulse: float
wheel_mtr_pos_gain: Optional[float]
wheel_mtr_vel_gain: Optional[float]
wheel_mtr_max_impulse: Optional[float]

base_offset: mn.Vector3
base_link_names: Set[str]
Expand All @@ -112,15 +112,19 @@ def __init__(
sim: Simulator,
limit_robo_joints: bool = True,
fixed_base: bool = True,
maintain_link_order: bool = False,
base_type="mobile",
):
r"""Constructor
:param params: The parameter of the manipulator robot.
:param urdf_path: The path to the robot's URDF file.
:param params: The parameter of the manipulator articulated agent.
:param urdf_path: The path to the agent's URDF file.
:param sim: The simulator.
:param limit_robo_joints: If true, joint limits of robot are always
:param limit_robo_joints: If true, joint limits of agent are always
enforced.
:param fixed_base: If the robot's base is fixed or not.
:param maintain_link_order: Whether to to preserve the order of
links parsed from URDF files as link indices. Needed for
compatibility with PyBullet.
:param base_type: The base type
"""
# instantiate a manipulator
Expand All @@ -141,6 +145,7 @@ def __init__(
fixed_based=fixed_base,
sim_obj=self.sim_obj,
base_type=base_type,
maintain_link_order=maintain_link_order,
xavierpuigf marked this conversation as resolved.
Show resolved Hide resolved
)

def reconfigure(self) -> None:
Expand Down
Loading