Skip to content

Commit 2449b3c

Browse files
committed
Merge branch 'user/adil-zouitine/2025-1-7-port-hil-serl-new' of github.com:huggingface/lerobot into user/adil-zouitine/2025-1-7-port-hil-serl-new
2 parents 6718003 + 36714a1 commit 2449b3c

File tree

13 files changed

+409
-0
lines changed

13 files changed

+409
-0
lines changed

examples/10_use_so100.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ python lerobot/scripts/find_motors_bus_port.py
9494

9595
#### b. Example outputs
9696

97+
#### b. Example outputs
98+
9799
Example output when identifying the leader arm's port (e.g., `/dev/tty.usbmodem575E0031751` on Mac, or possibly `/dev/ttyACM0` on Linux):
98100
```
99101
Finding all available ports for the MotorBus.
@@ -117,6 +119,8 @@ The port of this DynamixelMotorsBus is /dev/tty.usbmodem575E0032081
117119
Reconnect the usb cable.
118120
```
119121

122+
#### c. Troubleshooting
123+
On Linux, you might need to give access to the USB ports by running:
120124
#### c. Troubleshooting
121125
On Linux, you might need to give access to the USB ports by running:
122126
```bash
@@ -233,6 +237,7 @@ Follow the video for removing gears. You need to remove the gear for the motors
233237
Follow the video for adding the motor horn. For SO-100, you need to align the holes on the motor horn to the motor spline to be approximately 1:30, 4:30, 7:30 and 10:30.
234238
Try to avoid rotating the motor while doing so to keep position 2048 set during configuration. It is especially tricky for the leader motors as it is more sensible without the gears, but it's ok if it's a bit rotated.
235239

240+
## D. Assemble the arms
236241
## D. Assemble the arms
237242

238243
<details>
@@ -244,6 +249,7 @@ Try to avoid rotating the motor while doing so to keep position 2048 set during
244249

245250
Follow the video for assembling the arms. It is important to insert the cables into the motor that is being assembled before you assemble the motor into the arm! Inserting the cables beforehand is much easier than doing this afterward. The first arm should take a bit more than 1 hour to assemble, but once you get used to it, you can do it under 1 hour for the second arm.
246251

252+
## E. Calibrate
247253
## E. Calibrate
248254

249255
Next, you'll need to calibrate your SO-100 robot to ensure that the leader and follower arms have the same position values when they are in the same physical position. This calibration is essential because it allows a neural network trained on one SO-100 robot to work on another.
@@ -268,6 +274,8 @@ python lerobot/scripts/control_robot.py \
268274
--control.arms='["main_follower"]'
269275
```
270276

277+
#### b. Manual calibration of leader arm
278+
Follow step 6 of the [assembly video](https://youtu.be/FioA2oeFZ5I?t=724) which illustrates the manual calibration. You will need to move the leader arm to these positions sequentially:
271279
#### b. Manual calibration of leader arm
272280
Follow step 6 of the [assembly video](https://youtu.be/FioA2oeFZ5I?t=724) which illustrates the manual calibration. You will need to move the leader arm to these positions sequentially:
273281

@@ -284,6 +292,7 @@ python lerobot/scripts/control_robot.py \
284292
--control.arms='["main_leader"]'
285293
```
286294

295+
## F. Teleoperate
287296
## F. Teleoperate
288297

289298
**Simple teleop**
@@ -296,6 +305,7 @@ python lerobot/scripts/control_robot.py \
296305
```
297306

298307

308+
#### a. Teleop with displaying cameras
299309
#### a. Teleop with displaying cameras
300310
Follow [this guide to setup your cameras](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#c-add-your-cameras-with-opencvcamera). Then you will be able to display the cameras on your computer while you are teleoperating by running the following code. This is useful to prepare your setup before recording your first dataset.
301311
```bash
@@ -304,6 +314,7 @@ python lerobot/scripts/control_robot.py \
304314
--control.type=teleoperate
305315
```
306316

317+
## G. Record a dataset
307318
## G. Record a dataset
308319

309320
Once you're familiar with teleoperation, you can record your first dataset with SO-100.
@@ -337,6 +348,7 @@ python lerobot/scripts/control_robot.py \
337348

338349
Note: You can resume recording by adding `--control.resume=true`. Also if you didn't push your dataset yet, add `--control.local_files_only=true`.
339350

351+
## H. Visualize a dataset
340352
## H. Visualize a dataset
341353

342354
If you uploaded your dataset to the hub with `--control.push_to_hub=true`, you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id given by:
@@ -351,6 +363,7 @@ python lerobot/scripts/visualize_dataset_html.py \
351363
--local-files-only 1
352364
```
353365

366+
## I. Replay an episode
354367
## I. Replay an episode
355368

356369
Now try to replay the first episode on your robot:
@@ -365,6 +378,7 @@ python lerobot/scripts/control_robot.py \
365378

366379
Note: If you didn't push your dataset yet, add `--control.local_files_only=true`.
367380

381+
## J. Train a policy
368382
## J. Train a policy
369383

370384
To train a policy to control your robot, use the [`python lerobot/scripts/train.py`](../lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
@@ -388,6 +402,7 @@ Let's explain it:
388402

389403
Training should take several hours. You will find checkpoints in `outputs/train/act_so100_test/checkpoints`.
390404

405+
## K. Evaluate your policy
391406
## K. Evaluate your policy
392407

393408
You can use the `record` function from [`lerobot/scripts/control_robot.py`](../lerobot/scripts/control_robot.py) but with a policy checkpoint as input. For instance, run this command to record 10 evaluation episodes:
@@ -411,6 +426,7 @@ As you can see, it's almost the same command as previously used to record your t
411426
1. There is an additional `--control.policy.path` argument which indicates the path to your policy checkpoint with (e.g. `outputs/train/eval_act_so100_test/checkpoints/last/pretrained_model`). You can also use the model repository if you uploaded a model checkpoint to the hub (e.g. `${HF_USER}/act_so100_test`).
412427
2. The name of dataset begins by `eval` to reflect that you are running inference (e.g. `${HF_USER}/eval_act_so100_test`).
413428

429+
## L. More Information
414430
## L. More Information
415431

416432
Follow this [previous tutorial](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#4-train-a-policy-on-your-data) for a more in-depth tutorial on controlling real robots with LeRobot.

examples/advanced/2_calculate_validation_loss.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
import torch
1414

15+
from lerobot.common.datasets.lerobot_dataset import (
16+
LeRobotDataset,
17+
LeRobotDatasetMetadata,
18+
)
1519
from lerobot.common.datasets.lerobot_dataset import (
1620
LeRobotDataset,
1721
LeRobotDatasetMetadata,

lerobot/common/datasets/transforms.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def __init__(
6161
raise ValueError(
6262
f"n_subset should be in the interval [1, {len(transforms)}]"
6363
)
64+
raise ValueError(
65+
f"n_subset should be in the interval [1, {len(transforms)}]"
66+
)
6467

6568
self.transforms = transforms
6669
total = sum(p)
@@ -124,6 +127,9 @@ def _check_input(self, sharpness):
124127
raise ValueError(
125128
"If sharpness is a single number, it must be non negative."
126129
)
130+
raise ValueError(
131+
"If sharpness is a single number, it must be non negative."
132+
)
127133
sharpness = [1.0 - sharpness, 1.0 + sharpness]
128134
sharpness[0] = max(sharpness[0], 0.0)
129135
elif isinstance(sharpness, collections.abc.Sequence) and len(sharpness) == 2:
@@ -132,11 +138,17 @@ def _check_input(self, sharpness):
132138
raise TypeError(
133139
f"{sharpness=} should be a single number or a sequence with length 2."
134140
)
141+
raise TypeError(
142+
f"{sharpness=} should be a single number or a sequence with length 2."
143+
)
135144

136145
if not 0.0 <= sharpness[0] <= sharpness[1]:
137146
raise ValueError(
138147
f"sharpnesss values should be between (0., inf), but got {sharpness}."
139148
)
149+
raise ValueError(
150+
f"sharpnesss values should be between (0., inf), but got {sharpness}."
151+
)
140152

141153
return float(sharpness[0]), float(sharpness[1])
142154

lerobot/common/datasets/v2/batch_convert_dataset_v1_to_v2.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@
121121
"single_task": "Pick up the candy and unwrap it.",
122122
**ALOHA_STATIC_INFO,
123123
},
124+
"aloha_static_candy": {
125+
"single_task": "Pick up the candy and unwrap it.",
126+
**ALOHA_STATIC_INFO,
127+
},
124128
"aloha_static_coffee": {
125129
"single_task": "Place the coffee capsule inside the capsule container, then place the cup onto the center of the cup tray, then push the 'Hot Water' and 'Travel Mug' buttons.",
126130
**ALOHA_STATIC_INFO,
@@ -162,11 +166,21 @@
162166
**ALOHA_STATIC_INFO,
163167
},
164168
"aloha_static_vinh_cup": {
169+
"single_task": "Pick up the plastic cup with the right arm, then pop its lid open with the left arm.",
165170
"single_task": "Pick up the plastic cup with the right arm, then pop its lid open with the left arm.",
166171
**ALOHA_STATIC_INFO,
167172
},
168173
"aloha_static_vinh_cup_left": {
169174
"single_task": "Pick up the plastic cup with the left arm, then pop its lid open with the right arm.",
175+
"single_task": "Pick up the plastic cup with the left arm, then pop its lid open with the right arm.",
176+
**ALOHA_STATIC_INFO,
177+
},
178+
"aloha_static_ziploc_slide": {
179+
"single_task": "Slide open the ziploc bag.",
180+
**ALOHA_STATIC_INFO,
181+
},
182+
"aloha_sim_insertion_scripted": {
183+
"single_task": "Insert the peg into the socket.",
170184
**ALOHA_STATIC_INFO,
171185
},
172186
"aloha_static_ziploc_slide": {
@@ -185,6 +199,10 @@
185199
"single_task": "Insert the peg into the socket.",
186200
**ALOHA_STATIC_INFO,
187201
},
202+
"aloha_sim_insertion_human": {
203+
"single_task": "Insert the peg into the socket.",
204+
**ALOHA_STATIC_INFO,
205+
},
188206
"aloha_sim_insertion_human_image": {
189207
"single_task": "Insert the peg into the socket.",
190208
**ALOHA_STATIC_INFO,
@@ -213,11 +231,23 @@
213231
"single_task": "Push the T-shaped block onto the T-shaped target.",
214232
**PUSHT_INFO,
215233
},
234+
"pusht": {
235+
"single_task": "Push the T-shaped block onto the T-shaped target.",
236+
**PUSHT_INFO,
237+
},
238+
"pusht_image": {
239+
"single_task": "Push the T-shaped block onto the T-shaped target.",
240+
**PUSHT_INFO,
241+
},
216242
"unitreeh1_fold_clothes": {"single_task": "Fold the sweatshirt.", **UNITREEH_INFO},
217243
"unitreeh1_rearrange_objects": {
218244
"single_task": "Put the object into the bin.",
219245
**UNITREEH_INFO,
220246
},
247+
"unitreeh1_rearrange_objects": {
248+
"single_task": "Put the object into the bin.",
249+
**UNITREEH_INFO,
250+
},
221251
"unitreeh1_two_robot_greeting": {
222252
"single_task": "Greet the other robot with a high five.",
223253
**UNITREEH_INFO,
@@ -239,6 +269,18 @@
239269
"single_task": "Pick up the cube and lift it.",
240270
**XARM_INFO,
241271
},
272+
"xarm_lift_medium_image": {
273+
"single_task": "Pick up the cube and lift it.",
274+
**XARM_INFO,
275+
},
276+
"xarm_lift_medium_replay": {
277+
"single_task": "Pick up the cube and lift it.",
278+
**XARM_INFO,
279+
},
280+
"xarm_lift_medium_replay_image": {
281+
"single_task": "Pick up the cube and lift it.",
282+
**XARM_INFO,
283+
},
242284
"xarm_push_medium": {"single_task": "Push the cube onto the target.", **XARM_INFO},
243285
"xarm_push_medium_image": {
244286
"single_task": "Push the cube onto the target.",
@@ -252,6 +294,18 @@
252294
"single_task": "Push the cube onto the target.",
253295
**XARM_INFO,
254296
},
297+
"xarm_push_medium_image": {
298+
"single_task": "Push the cube onto the target.",
299+
**XARM_INFO,
300+
},
301+
"xarm_push_medium_replay": {
302+
"single_task": "Push the cube onto the target.",
303+
**XARM_INFO,
304+
},
305+
"xarm_push_medium_replay_image": {
306+
"single_task": "Push the cube onto the target.",
307+
**XARM_INFO,
308+
},
255309
"umi_cup_in_the_wild": {
256310
"single_task": "Put the cup on the plate.",
257311
"license": "apache-2.0",

lerobot/common/envs/factory.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# limitations under the License.
1616
import importlib
1717
from collections import deque
18+
from collections import deque
1819

1920
import gymnasium as gym
2021

@@ -164,3 +165,99 @@ def _get_obs(self, observation):
164165
ret["state"] = observation
165166
ret["pixels"] = images
166167
return ret
168+
169+
170+
def make_maniskill_env(
171+
cfg: DictConfig, n_envs: int | None = None
172+
) -> gym.vector.VectorEnv | None:
173+
"""Make ManiSkill3 gym environment"""
174+
from mani_skill.vector.wrappers.gymnasium import ManiSkillVectorEnv
175+
176+
env = gym.make(
177+
cfg.env.task,
178+
obs_mode=cfg.env.obs,
179+
control_mode=cfg.env.control_mode,
180+
render_mode=cfg.env.render_mode,
181+
sensor_configs=dict(width=cfg.env.image_size, height=cfg.env.image_size),
182+
num_envs=n_envs,
183+
)
184+
# cfg.env_cfg.control_mode = cfg.eval_env_cfg.control_mode = env.control_mode
185+
env = ManiSkillVectorEnv(env, ignore_terminations=True)
186+
# state should have the size of 25
187+
# env = ConvertToLeRobotEnv(env, n_envs)
188+
# env = PixelWrapper(cfg, env, n_envs)
189+
env._max_episode_steps = env.max_episode_steps = (
190+
50 # gym_utils.find_max_episode_steps_value(env)
191+
)
192+
env.unwrapped.metadata["render_fps"] = 20
193+
194+
return env
195+
196+
197+
class PixelWrapper(gym.Wrapper):
198+
"""
199+
Wrapper for pixel observations. Works with Maniskill vectorized environments
200+
"""
201+
202+
def __init__(self, cfg, env, num_envs, num_frames=3):
203+
super().__init__(env)
204+
self.cfg = cfg
205+
self.env = env
206+
self.observation_space = gym.spaces.Box(
207+
low=0,
208+
high=255,
209+
shape=(num_envs, num_frames * 3, cfg.env.render_size, cfg.env.render_size),
210+
dtype=np.uint8,
211+
)
212+
self._frames = deque([], maxlen=num_frames)
213+
self._render_size = cfg.env.render_size
214+
215+
def _get_obs(self, obs):
216+
frame = obs["sensor_data"]["base_camera"]["rgb"].cpu().permute(0, 3, 1, 2)
217+
self._frames.append(frame)
218+
return {
219+
"pixels": torch.from_numpy(np.concatenate(self._frames, axis=1)).to(
220+
self.env.device
221+
)
222+
}
223+
224+
def reset(self, seed):
225+
obs, info = self.env.reset() # (seed=seed)
226+
for _ in range(self._frames.maxlen):
227+
obs_frames = self._get_obs(obs)
228+
return obs_frames, info
229+
230+
def step(self, action):
231+
obs, reward, terminated, truncated, info = self.env.step(action)
232+
return self._get_obs(obs), reward, terminated, truncated, info
233+
234+
235+
# TODO: Remove this
236+
class ConvertToLeRobotEnv(gym.Wrapper):
237+
def __init__(self, env, num_envs):
238+
super().__init__(env)
239+
240+
def reset(self, seed=None, options=None):
241+
obs, info = self.env.reset(seed=seed, options={})
242+
return self._get_obs(obs), info
243+
244+
def step(self, action):
245+
obs, reward, terminated, truncated, info = self.env.step(action)
246+
return self._get_obs(obs), reward, terminated, truncated, info
247+
248+
def _get_obs(self, observation):
249+
sensor_data = observation.pop("sensor_data")
250+
del observation["sensor_param"]
251+
images = []
252+
for cam_data in sensor_data.values():
253+
images.append(cam_data["rgb"])
254+
255+
images = torch.concat(images, axis=-1)
256+
# flatten the rest of the data which should just be state data
257+
observation = common.flatten_state_dict(
258+
observation, use_torch=True, device=self.base_env.device
259+
)
260+
ret = dict()
261+
ret["state"] = observation
262+
ret["pixels"] = images
263+
return ret

0 commit comments

Comments
 (0)