# !/usr/bin/env python # Copyright 2025 The HuggingFace Inc. team. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig from lerobot.configs.types import FeatureType, PolicyFeature from lerobot.datasets.lerobot_dataset import LeRobotDataset from lerobot.policies.act.modeling_act import ACTPolicy from lerobot.policies.factory import make_pre_post_processors from lerobot.robots.so_follower import SO100Follower, SO100FollowerConfig from lerobot.robots.so_follower.pipelines import ( make_so10x_fk_observation_pipeline, make_so10x_ik_action_pipeline, ) from lerobot.scripts.lerobot_record import record_loop from lerobot.utils.control_utils import init_keyboard_listener from lerobot.utils.pipeline_utils import build_dataset_features from lerobot.utils.utils import log_say from lerobot.utils.visualization_utils import init_rerun NUM_EPISODES = 5 FPS = 30 EPISODE_TIME_SEC = 60 TASK_DESCRIPTION = "My task description" HF_MODEL_ID = "/" HF_DATASET_ID = "/" # NOTE: It is highly recommended to use the urdf in the SO-ARM100 repo: # https://github.com/TheRobotStudio/SO-ARM100/blob/main/Simulation/SO101/so101_new_calib.urdf URDF_PATH = "./SO101/so101_new_calib.urdf" def main(): # Create the robot configuration & robot camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)} robot_config = SO100FollowerConfig( port="/dev/tty.usbmodem58760434471", id="my_awesome_follower_arm", cameras=camera_config, use_degrees=True, ) robot = SO100Follower(robot_config) # Attach FK/IK pipelines so the robot works in EE space motor_names = list(robot.bus.motors.keys()) robot.set_output_pipeline(make_so10x_fk_observation_pipeline(URDF_PATH, motor_names)) robot.set_input_pipeline(make_so10x_ik_action_pipeline(URDF_PATH, motor_names)) # Create the dataset — obs auto-derived from FK pipeline, EE action spec explicit dataset = LeRobotDataset.create( repo_id=HF_DATASET_ID, fps=FPS, features=build_dataset_features( robot, use_videos=True, action_features={ f"ee.{k}": PolicyFeature(type=FeatureType.ACTION, shape=(1,)) for k in ["x", "y", "z", "wx", "wy", "wz", "gripper_pos"] }, ), robot_type=robot.name, use_videos=True, image_writer_threads=4, ) # Create policy policy = ACTPolicy.from_pretrained(HF_MODEL_ID) # Build Policy Processors preprocessor, postprocessor = make_pre_post_processors( policy_cfg=policy, pretrained_path=HF_MODEL_ID, dataset_stats=dataset.meta.stats, # The inference device is automatically set to match the detected hardware, overriding any previous device settings from training to ensure compatibility. preprocessor_overrides={"device_processor": {"device": str(policy.config.device)}}, ) # Connect the robot robot.connect() # Initialize the keyboard listener and rerun visualization listener, events = init_keyboard_listener() init_rerun(session_name="phone_so100_evaluate") try: if not robot.is_connected: raise ValueError("Robot is not connected!") print("Starting evaluate loop...") episode_idx = 0 for episode_idx in range(NUM_EPISODES): log_say(f"Running inference, recording eval episode {episode_idx + 1} of {NUM_EPISODES}") # Main record loop — pipelines applied internally by robot record_loop( robot=robot, events=events, fps=FPS, policy=policy, preprocessor=preprocessor, postprocessor=postprocessor, dataset=dataset, control_time_s=EPISODE_TIME_SEC, single_task=TASK_DESCRIPTION, display_data=True, ) # Reset the environment if not stopping or re-recording 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, control_time_s=EPISODE_TIME_SEC, single_task=TASK_DESCRIPTION, display_data=True, ) if events["rerecord_episode"]: log_say("Re-record episode") events["rerecord_episode"] = False events["exit_early"] = False dataset.clear_episode_buffer() continue # Save episode dataset.save_episode() episode_idx += 1 finally: # Clean up log_say("Stop recording") robot.disconnect() listener.stop() dataset.finalize() dataset.push_to_hub() if __name__ == "__main__": main()