| from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig |
| from lerobot.cameras.opencv.camera_opencv import OpenCVCamera |
| from lerobot.datasets.lerobot_dataset import LeRobotDataset |
| from lerobot.datasets.utils import hw_to_dataset_features |
| from lerobot.robots.so101_follower import SO101Follower, SO101FollowerConfig |
| from lerobot.teleoperators.so101_leader.config_so101_leader import SO101LeaderConfig |
| from lerobot.teleoperators.so101_leader.so101_leader import SO101Leader |
| from lerobot.utils.control_utils import init_keyboard_listener |
| from lerobot.utils.utils import log_say |
| from lerobot.utils.visualization_utils import init_rerun |
| from lerobot.scripts.lerobot_record import record_loop |
| from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraConfig |
| from lerobot.cameras.configs import ColorMode, Cv2Rotation |
| from lerobot.processor import make_default_processors |
|
|
| import json |
| import argparse |
| import time |
| import sys, select |
|
|
| curr_time = time.time() |
| TOP_CAM_INDEX = 4 |
| WRIST_CAM_INDEX = 6 |
|
|
|
|
| def main(args): |
| dataset_name = args.dataset_name |
| dataset_name = dataset_name + str(curr_time) |
|
|
|
|
| fps = args.fps |
| episode_time_sec = args.episode_time_sec |
| reset_time_sec = args.reset_time_sec |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| camera_config = { |
| "top": OpenCVCameraConfig(index_or_path=TOP_CAM_INDEX, width=640, height=480, fps=30), |
| "wrist": OpenCVCameraConfig(index_or_path=WRIST_CAM_INDEX, width=640, height=480, fps=30), |
| } |
|
|
|
|
| |
| |
|
|
| robot_config = SO101FollowerConfig( |
| port="/dev/ttyACM2", id="so101_follower_arm", cameras=camera_config |
| ) |
|
|
| |
| teleop_config = SO101LeaderConfig(port="/dev/ttyACM1", id="so101_leader_arm") |
| robot = SO101Follower(robot_config) |
| teleop = SO101Leader(teleop_config) |
| teleop_action_processor, robot_action_processor, robot_observation_processor = make_default_processors() |
|
|
| |
| action_features = hw_to_dataset_features(robot.action_features, "action") |
| obs_features = hw_to_dataset_features(robot.observation_features, "observation") |
| dataset_features = {**action_features, **obs_features} |
|
|
|
|
|
|
|
|
|
|
| num_episodes = args.num_episodes |
|
|
| dataset = LeRobotDataset.create( |
| repo_id="HenryZhang/" + dataset_name, |
| fps=fps, |
| features=dataset_features, |
| robot_type=robot.name, |
| use_videos=True, |
| image_writer_threads=2, |
| ) |
|
|
| _, events = init_keyboard_listener() |
| init_rerun(session_name="recording") |
|
|
| with open(args.ann_json_path, "r") as f: |
| annotations = json.load(f) |
|
|
| log_say("Connecting robot and teleop...") |
| robot.connect() |
| teleop.connect() |
|
|
| try: |
| for TASK_DESCRIPTION in annotations: |
| log_say(f"Resetting the arm, Starting task: {TASK_DESCRIPTION}. Setting the environment accordingly. Press ENTER to continue.") |
|
|
| |
| while True: |
| teleop_action = teleop.get_action() |
| robot.send_action(teleop_action) |
| time.sleep(0.01) |
|
|
| |
| if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: |
| input() |
| break |
|
|
| episode_idx = 0 |
| while episode_idx < num_episodes and not events["stop_recording"]: |
| log_say(f"Recording episode {episode_idx + 1} of {num_episodes}") |
|
|
| record_loop( |
| robot=robot, |
| events=events, |
| fps=fps, |
| teleop=teleop, |
| dataset=dataset, |
| control_time_s=episode_time_sec, |
| single_task=TASK_DESCRIPTION, |
| display_data=True, |
| teleop_action_processor=teleop_action_processor, |
| robot_action_processor=robot_action_processor, |
| robot_observation_processor=robot_observation_processor, |
| ) |
|
|
| |
| if not events["stop_recording"] and (episode_idx < num_episodes - 1 or events["rerecord_episode"]): |
| log_say("Reset the environment") |
| record_loop( |
| robot=robot, |
| events=events, |
| fps=fps, |
| teleop=teleop, |
| control_time_s=reset_time_sec, |
| single_task=TASK_DESCRIPTION, |
| display_data=True, |
| teleop_action_processor=teleop_action_processor, |
| robot_action_processor=robot_action_processor, |
| robot_observation_processor=robot_observation_processor, |
| ) |
|
|
| if events["rerecord_episode"]: |
| log_say("Re-recording episode") |
| events["rerecord_episode"] = False |
| events["exit_early"] = False |
| dataset.clear_episode_buffer() |
| continue |
|
|
| dataset.save_episode() |
| episode_idx += 1 |
|
|
| except KeyboardInterrupt: |
| log_say("❗ Ctrl-C detected — stopping early and disconnecting safely.") |
|
|
| finally: |
| log_say("Stopping recording and disconnecting devices...") |
| try: |
| robot.disconnect() |
| except: |
| pass |
| try: |
| teleop.disconnect() |
| except: |
| pass |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| base_path = "/home/koch/.cache/huggingface/lerobot/HenryZhang/" + dataset_name |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| print("please manually upload the dataset located at:", base_path) |
| print("command is:", f"hf upload HenryZhang/{dataset_name} . --repo-type=dataset --delete='*'") |
|
|
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
|
|
|
|
| if __name__ == "__main__": |
|
|
| parser = argparse.ArgumentParser(description="Record data for SO101 robot") |
| parser.add_argument( |
| "--ann_json_path", type=str, default="vla_tasks.json", help="Task annotation JSON" |
| ) |
| parser.add_argument( |
| "--num_episodes", type=int, default=40, help="Number of episodes to record" |
| ) |
| parser.add_argument( |
| "--dataset_name", type=str, default="RFM_fail", required=True, help="Name of the dataset to create" |
| ) |
| parser.add_argument( |
| "--fps", type=int, default=15, help="Frames per second for recording" |
| ) |
| parser.add_argument( |
| "--episode_time_sec", type=int, default=20, help="Run in headless mode without visualization" |
| ) |
| parser.add_argument( |
| "--reset_time_sec", type=int, default=7, help="Reset time between episodes" |
| ) |
| args = parser.parse_args() |
|
|
| main(args) |
| |
| |