Instructions to use hchen657Naza/PWM with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use hchen657Naza/PWM with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("hchen657Naza/PWM", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Download PWM_code/RISE_aux/deploy/piper_deploy.py from hchen657Naza/PWM: direct link, hf CLI and curl.
- Browser
- Download file 43.6 kB
-
https://huggingface.co/hchen657Naza/PWM/resolve/main/PWM_code/RISE_aux/deploy/piper_deploy.py
- Command line
-
hf download hf://hchen657Naza/PWM/PWM_code/RISE_aux/deploy/piper_deploy.py
-
curl -L -o piper_deploy.py https://huggingface.co/hchen657Naza/PWM/resolve/main/PWM_code/RISE_aux/deploy/piper_deploy.py
43.6 kB
| # -- coding: UTF-8 | |
| """ | |
| #!/usr/bin/python3 | |
| """ | |
| import argparse | |
| import threading | |
| import time | |
| from collections import deque | |
| import cv2 | |
| import numpy as np | |
| import rospy | |
| import torch | |
| from cv_bridge import CvBridge | |
| from geometry_msgs.msg import Twist | |
| from nav_msgs.msg import Odometry | |
| from openpi_client import image_tools, websocket_client_policy | |
| from piper_msgs.msg import PosCmd | |
| from sensor_msgs.msg import Image, JointState | |
| from std_msgs.msg import Header | |
| import signal | |
| import sys | |
| import os | |
| import threading | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| CAMERA_NAMES = ["cam_high", "cam_right_wrist", "cam_left_wrist"] | |
| stream_buffer = None # type: StreamActionBuffer | |
| observation_window = None | |
| lang_embeddings = "" | |
| RIGHT_OFFSET = 0.003 | |
| published_actions_history = [] # list[np.ndarray(shape=(14,))] | |
| observed_qpos_history = [] # list[np.ndarray(shape=(14,))] | |
| publish_step_global = 0 | |
| inferred_chunks = [] # list[dict(start_step:int, chunk:np.ndarray[chunk,14])] | |
| inferred_chunks_lock = threading.Lock() | |
| shutdown_event = threading.Event() | |
| def inference_fn_non_blocking_fast(args, config, policy, ros_operator): | |
| """ | |
| Non-blocking inference thread: no rate limiting. | |
| - Pack the latest observation into a payload and call infer; | |
| - As soon as an actions chunk is received, push it to stream_buffer; | |
| - After success or failure, immediately start the next inference (unless ROS is shutting down). | |
| """ | |
| global stream_buffer | |
| global observation_window | |
| global lang_embeddings | |
| rate = rospy.Rate(getattr(args, "inference_rate", 4)) | |
| while not rospy.is_shutdown(): | |
| try: | |
| time1 = time.time() | |
| # 1) Get latest observation (non-blocking) | |
| update_observation_window(args, config, ros_operator) | |
| print("Get Observation Time", time.time() - time1, "s") | |
| time1 = time.time() | |
| latest_obs = observation_window[-1] | |
| imgs = [ | |
| latest_obs["images"][config["camera_names"][0]], | |
| latest_obs["images"][config["camera_names"][1]], | |
| latest_obs["images"][config["camera_names"][2]], | |
| ] | |
| # BGR->RGB and pad/resize to model input size | |
| imgs = [cv2.cvtColor(im, cv2.COLOR_BGR2RGB) for im in imgs] | |
| imgs = image_tools.resize_with_pad(np.array(imgs), 224, 224) | |
| proprio = latest_obs["qpos"] | |
| # 2) Build payload (field names match your infer API) | |
| payload = { | |
| "state": proprio, | |
| "images": { | |
| "top_head": imgs[0].transpose(2, 0, 1), # CHW | |
| "hand_right": imgs[1].transpose(2, 0, 1), | |
| "hand_left": imgs[2].transpose(2, 0, 1), | |
| }, | |
| "prompt": lang_embeddings, | |
| } | |
| # 3) Infer (block until current chunk is received, then start next immediately) | |
| actions = policy.infer(payload)["actions"] | |
| print("Inference Time", time.time() - time1, "s") | |
| time1 = time.time() | |
| # 4) Push to parallel buffer (consumed by main control loop / temporal smoothing) | |
| if actions is not None and len(actions) > 0: | |
| max_k = int(getattr(args, "latency_k", 0)) | |
| min_m = int(getattr(args, "min_smooth_steps", 8)) | |
| stream_buffer.integrate_new_chunk(actions, max_k=max_k, min_m=min_m) | |
| elif actions is None: | |
| print("actions is None") | |
| elif len(actions) == 0: | |
| print("len(actions) == 0") | |
| # 5) Continue to next round immediately (no sleep, no rate limit) | |
| print("Append Buffer Time", time.time() - time1, "s") | |
| time1 = time.time() | |
| # Throttle inference thread | |
| try: | |
| rate.sleep() | |
| except rospy.ROSInterruptException: | |
| pass | |
| except Exception as e: | |
| # Log and continue to next round so thread does not die; minimal yield to avoid saturating one core | |
| rospy.logwarn(f"[inference_fn_non_blocking_fast] {e}") | |
| try: | |
| rate.sleep() | |
| except Exception: | |
| try: | |
| time.sleep(0.001) | |
| except Exception: | |
| pass | |
| continue | |
| class StreamActionBuffer: | |
| """ | |
| Maintains a queue of action chunks; each chunk is a deque([action0, action1, ...]). | |
| - New inferred chunks are appended from the right; | |
| - For each published step, popleft() the leftmost action from each chunk; | |
| - Empty chunks are dropped. | |
| """ | |
| def __init__(self, max_chunks=10, decay_alpha=0.25, state_dim=14, smooth_method="temporal"): | |
| self.chunks = deque() # Kept for backward compatibility | |
| self.max_chunks = max_chunks | |
| self.lock = threading.Lock() | |
| self.decay_alpha = float(decay_alpha) # Smoothing strength (exponential weight) | |
| self.state_dim = state_dim | |
| self.smooth_method = smooth_method | |
| self.cur_chunk = deque() # Current sequence to publish (after smoothing) | |
| self.k = 0 # Published step count (for latency trimming) | |
| self.last_action = None # Last successfully popped action | |
| def push_chunk(self, actions_chunk: np.ndarray): | |
| """Legacy interface (no longer used).""" | |
| with self.lock: | |
| if actions_chunk is None or len(actions_chunk) == 0: | |
| return | |
| dq = deque([a.copy() for a in actions_chunk], maxlen=None) | |
| self.chunks.append(dq) | |
| while len(self.chunks) > self.max_chunks: | |
| self.chunks.popleft() | |
| def integrate_new_chunk(self, actions_chunk: np.ndarray, max_k: int, min_m: int = 8): | |
| """ | |
| Integrate a new inference chunk: | |
| 1) Trim the front of the new chunk by current k and max_k (latency compensation). | |
| 2) If there is an existing chunk (cur_chunk), apply temporal smoothing on the overlap: | |
| - Overlap: first element 100% old / 0% new, last element 0% old / 100% new. | |
| - Extra tail from the new chunk is appended. | |
| 3) Reset k=0 as the new current execution sequence. | |
| """ | |
| with self.lock: | |
| if actions_chunk is None or len(actions_chunk) == 0: | |
| return | |
| max_k = max(0, int(max_k)) | |
| min_m = max(1, int(min_m)) | |
| drop_n = min(self.k, max_k) | |
| if drop_n >= len(actions_chunk): | |
| # Entire chunk trimmed; skip this update | |
| return | |
| new_chunk = [a.copy() for a in actions_chunk[drop_n:]] | |
| # Build old sequence: if empty but last_action exists, extend with last_action to min_m steps; | |
| # if non-empty and len < m, pad tail to min_m; if both empty, take new sequence as-is | |
| if len(self.cur_chunk) == 0 and self.last_action is not None: | |
| old_list = [np.asarray(self.last_action, dtype=float).copy() for _ in range(min_m)] | |
| self.last_action = None | |
| else: | |
| old_list = list(self.cur_chunk) | |
| if len(old_list) > 0 and len(old_list) < min_m: | |
| tail = np.asarray(old_list[-1], dtype=float).copy() | |
| old_list.extend([tail.copy() for _ in range(min_m - len(old_list))]) | |
| elif len(old_list) == 0: | |
| self.cur_chunk = deque(new_chunk, maxlen=None) | |
| self.k = 0 | |
| return | |
| new_list = list(new_chunk) | |
| # Overlap length = min of remaining old length and new length | |
| overlap_len = min(len(old_list), len(new_list)) | |
| if overlap_len <= 0: | |
| # No overlap; use new sequence as-is | |
| self.cur_chunk = deque(new_list, maxlen=None) | |
| self.k = 0 | |
| return | |
| # If old sequence is longer than new, trim old tail | |
| if len(old_list) > len(new_list): | |
| old_list = old_list[:len(new_list)] | |
| overlap_len = len(new_list) | |
| # Linear weights: first element 100% old, last element 0% old | |
| if overlap_len == 1: | |
| w_old = np.array([1.0], dtype=float) | |
| else: | |
| w_old = np.linspace(1.0, 0.0, overlap_len, dtype=float) | |
| w_new = 1.0 - w_old | |
| # Smooth the overlap region | |
| smoothed = [ | |
| (w_old[i] * np.asarray(old_list[i], dtype=float) + | |
| w_new[i] * np.asarray(new_list[i], dtype=float)) | |
| for i in range(overlap_len) | |
| ] | |
| # Append the extra tail from the new sequence | |
| combined = smoothed + new_list[overlap_len:] | |
| self.cur_chunk = deque([a.copy() for a in combined], maxlen=None) | |
| self.k = 0 | |
| def pop_left_step_from_all(self): | |
| """Legacy interface (no longer used).""" | |
| with self.lock: | |
| if len(self.cur_chunk) > 0: | |
| self.cur_chunk.popleft() | |
| def has_any(self): | |
| with self.lock: | |
| return len(self.cur_chunk) > 0 | |
| def pop_next_action(self) -> np.ndarray | None: | |
| """Pop and return the next action to publish; k += 1.""" | |
| with self.lock: | |
| if len(self.cur_chunk) == 0: | |
| return None | |
| # If about to pop the last element, save it as last_action | |
| if len(self.cur_chunk) == 1: | |
| self.last_action = np.asarray(self.cur_chunk[0], dtype=float).copy() | |
| act = np.asarray(self.cur_chunk.popleft(), dtype=float) | |
| self.k += 1 | |
| return act | |
| def temporal_smooth_action_at_index(self, idx_from_left: int) -> np.ndarray | None: | |
| """Legacy interface (no longer used).""" | |
| with self.lock: | |
| if len(self.cur_chunk) == 0: | |
| return None | |
| return np.asarray(self.cur_chunk[0], dtype=float) | |
| def delta_eef_smooth_action_at_index(self, idx_from_left: int) -> np.ndarray | None: | |
| # TODO: implement this | |
| pass | |
| def start_inference_thread(args, config, policy, ros_operator): | |
| inference_thread = threading.Thread(target=inference_fn_non_blocking_fast, args=(args, config, policy, ros_operator)) | |
| inference_thread.daemon = True | |
| inference_thread.start() | |
| def _on_sigint(signum, frame): | |
| # Set shutdown event so main loop can flush and save | |
| try: | |
| shutdown_event.set() | |
| except Exception: | |
| pass | |
| # Notify ROS to shut down | |
| try: | |
| rospy.signal_shutdown("SIGINT") | |
| except Exception: | |
| pass | |
| # Do not exit immediately in signal handler; let main flow clean up | |
| def set_seed(seed): | |
| torch.manual_seed(seed) | |
| np.random.seed(seed) | |
| # Interpolate the actions to make the robot move smoothly | |
| def interpolate_action(args, prev_action, cur_action): | |
| steps = np.concatenate((np.array(args.arm_steps_length), np.array(args.arm_steps_length)), axis=0) | |
| diff = np.abs(cur_action - prev_action) | |
| step = np.ceil(diff / steps).astype(int) | |
| step = np.max(step) | |
| if step <= 1: | |
| return cur_action[np.newaxis, :] | |
| new_actions = np.linspace(prev_action, cur_action, step + 1) | |
| return new_actions[1:] | |
| def get_config(args): | |
| config = { | |
| "episode_len": args.max_publish_step, | |
| "state_dim": 14, | |
| "chunk_size": args.chunk_size, | |
| "camera_names": CAMERA_NAMES, | |
| } | |
| return config | |
| def get_ros_observation(args, ros_operator): | |
| rate = rospy.Rate(args.publish_rate) | |
| print_flag = True | |
| time3 = time.time() | |
| while True and not rospy.is_shutdown(): | |
| result = ros_operator.get_frame() | |
| if time.time() - time3 > 0.01: | |
| print("Get Frame Time is too long", time.time() - time3, "s") | |
| if not result: | |
| if print_flag: | |
| print("syn fail when get_ros_observation") | |
| print_flag = False | |
| rate.sleep() | |
| continue | |
| print_flag = True | |
| ( | |
| img_front, | |
| img_left, | |
| img_right, | |
| img_front_depth, | |
| img_left_depth, | |
| img_right_depth, | |
| puppet_arm_left, | |
| puppet_arm_right, | |
| robot_base, | |
| ) = result | |
| return (img_front, img_left, img_right, puppet_arm_left, puppet_arm_right) | |
| def update_observation_window(args, config, ros_operator): | |
| # JPEG transformation | |
| # Align with training | |
| def jpeg_mapping(img): | |
| img = cv2.imencode(".jpg", img)[1].tobytes() | |
| img = cv2.imdecode(np.frombuffer(img, np.uint8), cv2.IMREAD_COLOR) | |
| return img | |
| global observation_window | |
| if observation_window is None: | |
| observation_window = deque(maxlen=2) | |
| # Append the first dummy image | |
| observation_window.append( | |
| { | |
| "qpos": None, | |
| "images": { | |
| config["camera_names"][0]: None, | |
| config["camera_names"][1]: None, | |
| config["camera_names"][2]: None, | |
| }, | |
| } | |
| ) | |
| img_front, img_left, img_right, puppet_arm_left, puppet_arm_right = get_ros_observation(args, ros_operator) | |
| img_front = jpeg_mapping(img_front) | |
| img_left = jpeg_mapping(img_left) | |
| img_right = jpeg_mapping(img_right) | |
| qpos = np.concatenate( | |
| (np.array(puppet_arm_left.position), np.array(puppet_arm_right.position)), | |
| axis=0, | |
| ) | |
| observation_window.append( | |
| { | |
| "qpos": qpos, | |
| "images": { | |
| config["camera_names"][0]: img_front, | |
| config["camera_names"][1]: img_right, | |
| config["camera_names"][2]: img_left, | |
| }, | |
| } | |
| ) | |
| def inference_fn(args, config, policy): | |
| global observation_window | |
| global lang_embeddings | |
| while True and not rospy.is_shutdown(): | |
| # fetch images in sequence [front, right, left] | |
| image_arrs = [ | |
| observation_window[-1]["images"][config["camera_names"][0]], | |
| observation_window[-1]["images"][config["camera_names"][1]], | |
| observation_window[-1]["images"][config["camera_names"][2]], | |
| ] | |
| # convert bgr ro rgb | |
| image_arrs = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in image_arrs] | |
| image_arrs = image_tools.resize_with_pad(np.array(image_arrs), 224, 224) | |
| # get last qpos in shape [14, ] | |
| proprio = observation_window[-1]["qpos"] | |
| payload = { | |
| "state": proprio, | |
| "images": { | |
| "top_head": image_arrs[0].transpose(2, 0, 1), | |
| "hand_right": image_arrs[1].transpose(2, 0, 1), | |
| "hand_left": image_arrs[2].transpose(2, 0, 1), | |
| }, | |
| "prompt": lang_embeddings, | |
| } | |
| time1 = time.time() | |
| # actions shaped as [64, 14] in format [left, right] | |
| actions = policy.infer(payload)["actions"] | |
| print(f"Model inference time: {(time.time() - time1)*1000:.3f} ms") | |
| return actions | |
| def model_inference(args, config, ros_operator): | |
| global lang_embeddings | |
| global stream_buffer | |
| # Load client | |
| policy = websocket_client_policy.WebsocketClientPolicy( | |
| args.host, | |
| args.port, | |
| ) | |
| print(f"Server metadata: {policy.get_server_metadata()}") | |
| max_publish_step = config["episode_len"] | |
| chunk_size = config["chunk_size"] | |
| # Initialize position of the puppet arm | |
| left0 = [0, 0.32, -0.36, 0, 0.24, 0, 0.07] | |
| right0 = [0, 0.32, -0.36, 0, 0.24, 0, 0.07] | |
| ros_operator.puppet_arm_publish_continuous(left0, right0) | |
| input("Press enter to continue") | |
| ros_operator.puppet_arm_publish_continuous(left0, right0) | |
| # Warmup: run one blocking inference (result discarded) | |
| try: | |
| update_observation_window(args, config, ros_operator) | |
| latest_obs = observation_window[-1] | |
| image_arrs = [ | |
| latest_obs["images"][config["camera_names"][0]], | |
| latest_obs["images"][config["camera_names"][1]], | |
| latest_obs["images"][config["camera_names"][2]], | |
| ] | |
| image_arrs = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in image_arrs] | |
| image_arrs = image_tools.resize_with_pad(np.array(image_arrs), 224, 224) | |
| proprio = latest_obs["qpos"] | |
| payload = { | |
| "state": proprio, | |
| "images": { | |
| "top_head": image_arrs[0].transpose(2, 0, 1), | |
| "hand_right": image_arrs[1].transpose(2, 0, 1), | |
| "hand_left": image_arrs[2].transpose(2, 0, 1), | |
| }, | |
| "prompt": lang_embeddings, | |
| } | |
| try: | |
| _ = policy.infer(payload) | |
| except Exception as e: | |
| rospy.logwarn(f"[startup_warmup_infer] {e}") | |
| except Exception as e: | |
| rospy.logwarn(f"[startup_warmup_prep] {e}") | |
| # Initialize the previous action to be the initial robot state | |
| pre_action = np.zeros(config["state_dim"]) | |
| action = None | |
| # Persistent accumulated joint correction offsets (first 6 joints) | |
| corr_left_q6 = np.zeros(6, dtype=float) | |
| corr_right_q6 = np.zeros(6, dtype=float) | |
| with torch.inference_mode(): | |
| while True and not rospy.is_shutdown(): | |
| t = 0 | |
| rate = rospy.Rate(args.publish_rate) | |
| action_buffer = np.zeros([chunk_size, config["state_dim"]]) | |
| while t < max_publish_step and not rospy.is_shutdown() and not shutdown_event.is_set(): | |
| if shutdown_event.is_set(): | |
| break | |
| if args.use_temporal_smoothing: | |
| if stream_buffer is None: | |
| stream_buffer = StreamActionBuffer( | |
| max_chunks=args.buffer_max_chunks, | |
| decay_alpha=args.exp_decay_alpha, | |
| state_dim=config["state_dim"], | |
| smooth_method="temporal", | |
| ) | |
| start_inference_thread(args, config, policy, ros_operator) | |
| act = stream_buffer.pop_next_action() | |
| if act is not None: | |
| if args.ctrl_type == "joint": | |
| left_action = act[:7].copy() | |
| right_action = act[7:14].copy() | |
| left_action[6] = max(0.0, left_action[6]-RIGHT_OFFSET) | |
| right_action[6] = max(0.0, right_action[6]-RIGHT_OFFSET) | |
| ros_operator.puppet_arm_publish(left_action, right_action) | |
| published_actions_history.append(np.concatenate([left_action, right_action], axis=0).astype(float)) | |
| else: | |
| print("Make sure ctrl_type is joint") | |
| else: | |
| print("act is None") | |
| time.sleep(0.001) | |
| continue | |
| print("Published Step", t) | |
| try: | |
| publish_step_global = len(published_actions_history) | |
| except Exception: | |
| pass | |
| rate.sleep() | |
| t += 1 | |
| else: | |
| print("Make sure touse temporal smoothing") | |
| if shutdown_event.is_set(): | |
| break | |
| class RosOperator: | |
| def __init__(self, args): | |
| self.communication_thread = None # Communication thread | |
| self.communication_flag = False # Flag to control communication thread | |
| self.lock = threading.Lock() # Synchronization between threads | |
| self.robot_base_deque = None | |
| self.puppet_arm_right_deque = None | |
| self.puppet_arm_left_deque = None | |
| self.img_front_deque = None | |
| self.img_right_deque = None | |
| self.img_left_deque = None | |
| self.img_front_depth_deque = None | |
| self.img_right_depth_deque = None | |
| self.img_left_depth_deque = None | |
| self.bridge = None | |
| self.puppet_arm_left_publisher = None | |
| self.puppet_arm_right_publisher = None | |
| self.endpose_left_publisher = None | |
| self.endpose_right_publisher = None | |
| self.robot_base_publisher = None | |
| self.puppet_arm_publish_thread = None | |
| self.puppet_arm_publish_lock = None | |
| self.args = args | |
| self.init() | |
| self.init_ros() | |
| def init(self): | |
| self.bridge = CvBridge() | |
| self.img_left_deque = deque() | |
| self.img_right_deque = deque() | |
| self.img_front_deque = deque() | |
| self.img_left_depth_deque = deque() | |
| self.img_right_depth_deque = deque() | |
| self.img_front_depth_deque = deque() | |
| self.puppet_arm_left_deque = deque() | |
| self.puppet_arm_right_deque = deque() | |
| self.robot_base_deque = deque() | |
| self.puppet_arm_publish_lock = threading.Lock() | |
| self.puppet_arm_publish_lock.acquire() | |
| def puppet_arm_publish(self, left, right): | |
| joint_state_msg = JointState() | |
| joint_state_msg.header = Header() | |
| joint_state_msg.header.stamp = rospy.Time.now() | |
| joint_state_msg.name = [ | |
| "joint0", | |
| "joint1", | |
| "joint2", | |
| "joint3", | |
| "joint4", | |
| "joint5", | |
| "joint6", | |
| ] | |
| joint_state_msg.position = left | |
| self.puppet_arm_left_publisher.publish(joint_state_msg) | |
| joint_state_msg.position = right | |
| self.puppet_arm_right_publisher.publish(joint_state_msg) | |
| def endpose_publish(self, left, right): | |
| endpose_msg = PosCmd() | |
| endpose_msg.x, endpose_msg.y, endpose_msg.z = left[:3] | |
| endpose_msg.roll, endpose_msg.pitch, endpose_msg.yaw = left[3:6] | |
| endpose_msg.gripper = left[6] | |
| self.endpose_left_publisher.publish(endpose_msg) | |
| endpose_msg.x, endpose_msg.y, endpose_msg.z = right[:3] | |
| endpose_msg.roll, endpose_msg.pitch, endpose_msg.yaw = right[3:6] | |
| endpose_msg.gripper = right[6] | |
| self.endpose_right_publisher.publish(endpose_msg) | |
| def robot_base_publish(self, vel): | |
| vel_msg = Twist() | |
| vel_msg.linear.x = vel[0] | |
| vel_msg.linear.y = 0 | |
| vel_msg.linear.z = 0 | |
| vel_msg.angular.x = 0 | |
| vel_msg.angular.y = 0 | |
| vel_msg.angular.z = vel[1] | |
| self.robot_base_publisher.publish(vel_msg) | |
| def puppet_arm_publish_continuous(self, left, right): | |
| rate = rospy.Rate(self.args.publish_rate) | |
| left_arm = None | |
| right_arm = None | |
| while True and not rospy.is_shutdown(): | |
| if len(self.puppet_arm_left_deque) != 0: | |
| left_arm = list(self.puppet_arm_left_deque[-1].position) | |
| if len(self.puppet_arm_right_deque) != 0: | |
| right_arm = list(self.puppet_arm_right_deque[-1].position) | |
| if left_arm is None or right_arm is None: | |
| rate.sleep() | |
| continue | |
| else: | |
| break | |
| left_symbol = [1 if left[i] - left_arm[i] > 0 else -1 for i in range(len(left))] | |
| right_symbol = [1 if right[i] - right_arm[i] > 0 else -1 for i in range(len(right))] | |
| flag = True | |
| step = 0 | |
| while flag and not rospy.is_shutdown(): | |
| if self.puppet_arm_publish_lock.acquire(False): | |
| return | |
| left_diff = [abs(left[i] - left_arm[i]) for i in range(len(left))] | |
| right_diff = [abs(right[i] - right_arm[i]) for i in range(len(right))] | |
| flag = False | |
| for i in range(len(left)): | |
| if left_diff[i] < self.args.arm_steps_length[i]: | |
| left_arm[i] = left[i] | |
| else: | |
| left_arm[i] += left_symbol[i] * self.args.arm_steps_length[i] | |
| flag = True | |
| for i in range(len(right)): | |
| if right_diff[i] < self.args.arm_steps_length[i]: | |
| right_arm[i] = right[i] | |
| else: | |
| right_arm[i] += right_symbol[i] * self.args.arm_steps_length[i] | |
| flag = True | |
| joint_state_msg = JointState() | |
| joint_state_msg.header = Header() | |
| joint_state_msg.header.stamp = rospy.Time.now() | |
| joint_state_msg.name = [ | |
| "joint0", | |
| "joint1", | |
| "joint2", | |
| "joint3", | |
| "joint4", | |
| "joint5", | |
| "joint6", | |
| ] | |
| joint_state_msg.position = left_arm | |
| self.puppet_arm_left_publisher.publish(joint_state_msg) | |
| joint_state_msg.position = right_arm | |
| self.puppet_arm_right_publisher.publish(joint_state_msg) | |
| step += 1 | |
| print("puppet_arm_publish_continuous:", step) | |
| rate.sleep() | |
| def puppet_arm_publish_linear(self, left, right): | |
| num_step = 100 | |
| rate = rospy.Rate(200) | |
| left_arm = None | |
| right_arm = None | |
| while True and not rospy.is_shutdown(): | |
| if len(self.puppet_arm_left_deque) != 0: | |
| left_arm = list(self.puppet_arm_left_deque[-1].position) | |
| if len(self.puppet_arm_right_deque) != 0: | |
| right_arm = list(self.puppet_arm_right_deque[-1].position) | |
| if left_arm is None or right_arm is None: | |
| rate.sleep() | |
| continue | |
| else: | |
| break | |
| traj_left_list = np.linspace(left_arm, left, num_step) | |
| traj_right_list = np.linspace(right_arm, right, num_step) | |
| for i in range(len(traj_left_list)): | |
| traj_left = traj_left_list[i] | |
| traj_right = traj_right_list[i] | |
| traj_left[-1] = left[-1] | |
| traj_right[-1] = right[-1] | |
| joint_state_msg = JointState() | |
| joint_state_msg.header = Header() | |
| joint_state_msg.header.stamp = rospy.Time.now() | |
| joint_state_msg.name = [ | |
| "joint0", | |
| "joint1", | |
| "joint2", | |
| "joint3", | |
| "joint4", | |
| "joint5", | |
| "joint6", | |
| ] | |
| joint_state_msg.position = traj_left | |
| self.puppet_arm_left_publisher.publish(joint_state_msg) | |
| joint_state_msg.position = traj_right | |
| self.puppet_arm_right_publisher.publish(joint_state_msg) | |
| rate.sleep() | |
| def puppet_arm_publish_continuous_thread(self, left, right): | |
| if self.puppet_arm_publish_thread is not None: | |
| self.puppet_arm_publish_lock.release() | |
| self.puppet_arm_publish_thread.join() | |
| self.puppet_arm_publish_lock.acquire(False) | |
| self.puppet_arm_publish_thread = None | |
| self.puppet_arm_publish_thread = threading.Thread(target=self.puppet_arm_publish_continuous, args=(left, right)) | |
| self.puppet_arm_publish_thread.start() | |
| def get_frame(self): | |
| if ( | |
| len(self.img_left_deque) == 0 | |
| or len(self.img_right_deque) == 0 | |
| or len(self.img_front_deque) == 0 | |
| or ( | |
| self.args.use_depth_image | |
| and ( | |
| len(self.img_left_depth_deque) == 0 | |
| or len(self.img_right_depth_deque) == 0 | |
| or len(self.img_front_depth_deque) == 0 | |
| ) | |
| ) | |
| ): | |
| return False | |
| if self.args.use_depth_image: | |
| frame_time = min( | |
| [ | |
| self.img_left_deque[-1].header.stamp.to_sec(), | |
| self.img_right_deque[-1].header.stamp.to_sec(), | |
| self.img_front_deque[-1].header.stamp.to_sec(), | |
| self.img_left_depth_deque[-1].header.stamp.to_sec(), | |
| self.img_right_depth_deque[-1].header.stamp.to_sec(), | |
| self.img_front_depth_deque[-1].header.stamp.to_sec(), | |
| ] | |
| ) | |
| else: | |
| frame_time = min( | |
| [ | |
| self.img_left_deque[-1].header.stamp.to_sec(), | |
| self.img_right_deque[-1].header.stamp.to_sec(), | |
| self.img_front_deque[-1].header.stamp.to_sec(), | |
| ] | |
| ) | |
| if len(self.img_left_deque) == 0 or self.img_left_deque[-1].header.stamp.to_sec() < frame_time: | |
| return False | |
| if len(self.img_right_deque) == 0 or self.img_right_deque[-1].header.stamp.to_sec() < frame_time: | |
| return False | |
| if len(self.img_front_deque) == 0 or self.img_front_deque[-1].header.stamp.to_sec() < frame_time: | |
| return False | |
| if len(self.puppet_arm_left_deque) == 0 or self.puppet_arm_left_deque[-1].header.stamp.to_sec() < frame_time: | |
| return False | |
| if len(self.puppet_arm_right_deque) == 0 or self.puppet_arm_right_deque[-1].header.stamp.to_sec() < frame_time: | |
| return False | |
| if self.args.use_depth_image and ( | |
| len(self.img_left_depth_deque) == 0 or self.img_left_depth_deque[-1].header.stamp.to_sec() < frame_time | |
| ): | |
| return False | |
| if self.args.use_depth_image and ( | |
| len(self.img_right_depth_deque) == 0 or self.img_right_depth_deque[-1].header.stamp.to_sec() < frame_time | |
| ): | |
| return False | |
| if self.args.use_depth_image and ( | |
| len(self.img_front_depth_deque) == 0 or self.img_front_depth_deque[-1].header.stamp.to_sec() < frame_time | |
| ): | |
| return False | |
| if self.args.use_robot_base and ( | |
| len(self.robot_base_deque) == 0 or self.robot_base_deque[-1].header.stamp.to_sec() < frame_time | |
| ): | |
| return False | |
| while self.img_left_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_left_deque.popleft() | |
| img_left = self.bridge.imgmsg_to_cv2(self.img_left_deque.popleft(), "passthrough") | |
| while self.img_right_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_right_deque.popleft() | |
| img_right = self.bridge.imgmsg_to_cv2(self.img_right_deque.popleft(), "passthrough") | |
| while self.img_front_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_front_deque.popleft() | |
| img_front = self.bridge.imgmsg_to_cv2(self.img_front_deque.popleft(), "passthrough") | |
| while self.puppet_arm_left_deque[0].header.stamp.to_sec() < frame_time: | |
| self.puppet_arm_left_deque.popleft() | |
| puppet_arm_left = self.puppet_arm_left_deque.popleft() | |
| while self.puppet_arm_right_deque[0].header.stamp.to_sec() < frame_time: | |
| self.puppet_arm_right_deque.popleft() | |
| puppet_arm_right = self.puppet_arm_right_deque.popleft() | |
| img_left_depth = None | |
| if self.args.use_depth_image: | |
| while self.img_left_depth_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_left_depth_deque.popleft() | |
| img_left_depth = self.bridge.imgmsg_to_cv2(self.img_left_depth_deque.popleft(), "passthrough") | |
| img_right_depth = None | |
| if self.args.use_depth_image: | |
| while self.img_right_depth_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_right_depth_deque.popleft() | |
| img_right_depth = self.bridge.imgmsg_to_cv2(self.img_right_depth_deque.popleft(), "passthrough") | |
| img_front_depth = None | |
| if self.args.use_depth_image: | |
| while self.img_front_depth_deque[0].header.stamp.to_sec() < frame_time: | |
| self.img_front_depth_deque.popleft() | |
| img_front_depth = self.bridge.imgmsg_to_cv2(self.img_front_depth_deque.popleft(), "passthrough") | |
| robot_base = None | |
| if self.args.use_robot_base: | |
| while self.robot_base_deque[0].header.stamp.to_sec() < frame_time: | |
| self.robot_base_deque.popleft() | |
| robot_base = self.robot_base_deque.popleft() | |
| return ( | |
| img_front, | |
| img_left, | |
| img_right, | |
| img_front_depth, | |
| img_left_depth, | |
| img_right_depth, | |
| puppet_arm_left, | |
| puppet_arm_right, | |
| robot_base, | |
| ) | |
| def img_left_callback(self, msg): | |
| if len(self.img_left_deque) >= 2000: | |
| self.img_left_deque.popleft() | |
| self.img_left_deque.append(msg) | |
| def img_right_callback(self, msg): | |
| if len(self.img_right_deque) >= 2000: | |
| self.img_right_deque.popleft() | |
| self.img_right_deque.append(msg) | |
| def img_front_callback(self, msg): | |
| if len(self.img_front_deque) >= 2000: | |
| self.img_front_deque.popleft() | |
| self.img_front_deque.append(msg) | |
| def img_left_depth_callback(self, msg): | |
| if len(self.img_left_depth_deque) >= 2000: | |
| self.img_left_depth_deque.popleft() | |
| self.img_left_depth_deque.append(msg) | |
| def img_right_depth_callback(self, msg): | |
| if len(self.img_right_depth_deque) >= 2000: | |
| self.img_right_depth_deque.popleft() | |
| self.img_right_depth_deque.append(msg) | |
| def img_front_depth_callback(self, msg): | |
| if len(self.img_front_depth_deque) >= 2000: | |
| self.img_front_depth_deque.popleft() | |
| self.img_front_depth_deque.append(msg) | |
| def puppet_arm_left_callback(self, msg): | |
| if len(self.puppet_arm_left_deque) >= 2000: | |
| self.puppet_arm_left_deque.popleft() | |
| self.puppet_arm_left_deque.append(msg) | |
| def puppet_arm_right_callback(self, msg): | |
| if len(self.puppet_arm_right_deque) >= 2000: | |
| self.puppet_arm_right_deque.popleft() | |
| self.puppet_arm_right_deque.append(msg) | |
| def robot_base_callback(self, msg): | |
| if len(self.robot_base_deque) >= 2000: | |
| self.robot_base_deque.popleft() | |
| self.robot_base_deque.append(msg) | |
| def init_ros(self): | |
| rospy.init_node("joint_state_publisher", anonymous=True) | |
| rospy.Subscriber( | |
| self.args.img_left_topic, | |
| Image, | |
| self.img_left_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.img_right_topic, | |
| Image, | |
| self.img_right_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.img_front_topic, | |
| Image, | |
| self.img_front_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| if self.args.use_depth_image: | |
| rospy.Subscriber( | |
| self.args.img_left_depth_topic, | |
| Image, | |
| self.img_left_depth_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.img_right_depth_topic, | |
| Image, | |
| self.img_right_depth_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.img_front_depth_topic, | |
| Image, | |
| self.img_front_depth_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.puppet_arm_left_topic, | |
| JointState, | |
| self.puppet_arm_left_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.puppet_arm_right_topic, | |
| JointState, | |
| self.puppet_arm_right_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| rospy.Subscriber( | |
| self.args.robot_base_topic, | |
| Odometry, | |
| self.robot_base_callback, | |
| queue_size=1000, | |
| tcp_nodelay=True, | |
| ) | |
| self.puppet_arm_left_publisher = rospy.Publisher(self.args.puppet_arm_left_cmd_topic, JointState, queue_size=10) | |
| self.puppet_arm_right_publisher = rospy.Publisher( | |
| self.args.puppet_arm_right_cmd_topic, JointState, queue_size=10 | |
| ) | |
| self.endpose_left_publisher = rospy.Publisher(self.args.endpose_left_cmd_topic, PosCmd, queue_size=10) | |
| self.endpose_right_publisher = rospy.Publisher(self.args.endpose_right_cmd_topic, PosCmd, queue_size=10) | |
| self.robot_base_publisher = rospy.Publisher(self.args.robot_base_cmd_topic, Twist, queue_size=10) | |
| def get_arguments(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--max_publish_step", | |
| action="store", | |
| type=int, | |
| help="Maximum number of action publishing steps", | |
| default=10000, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--seed", | |
| action="store", | |
| type=int, | |
| help="Random seed", | |
| default=None, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_front_topic", | |
| action="store", | |
| type=str, | |
| help="img_front_topic", | |
| default="/camera_f/color/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_left_topic", | |
| action="store", | |
| type=str, | |
| help="img_left_topic", | |
| default="/camera_l/color/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_right_topic", | |
| action="store", | |
| type=str, | |
| help="img_right_topic", | |
| default="/camera_r/color/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_front_depth_topic", | |
| action="store", | |
| type=str, | |
| help="img_front_depth_topic", | |
| default="/camera_f/depth/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_left_depth_topic", | |
| action="store", | |
| type=str, | |
| help="img_left_depth_topic", | |
| default="/camera_l/depth/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--img_right_depth_topic", | |
| action="store", | |
| type=str, | |
| help="img_right_depth_topic", | |
| default="/camera_r/depth/image_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--puppet_arm_left_cmd_topic", | |
| action="store", | |
| type=str, | |
| help="puppet_arm_left_cmd_topic", | |
| default="/master/joint_left", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--puppet_arm_right_cmd_topic", | |
| action="store", | |
| type=str, | |
| help="puppet_arm_right_cmd_topic", | |
| default="/master/joint_right", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--puppet_arm_left_topic", | |
| action="store", | |
| type=str, | |
| help="puppet_arm_left_topic", | |
| default="/puppet/joint_left", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--puppet_arm_right_topic", | |
| action="store", | |
| type=str, | |
| help="puppet_arm_right_topic", | |
| default="/puppet/joint_right", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--endpose_left_cmd_topic", | |
| action="store", | |
| type=str, | |
| help="endpose_left_cmd_topic", | |
| default="/pos_cmd_left", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--endpose_right_cmd_topic", | |
| action="store", | |
| type=str, | |
| help="endpose_right_cmd_topic", | |
| default="/pos_cmd_right", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--robot_base_topic", | |
| action="store", | |
| type=str, | |
| help="robot_base_topic", | |
| default="/odom_raw", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--robot_base_cmd_topic", | |
| action="store", | |
| type=str, | |
| help="robot_base_topic", | |
| default="/cmd_vel", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--use_robot_base", | |
| action="store_true", | |
| help="Whether to use the robot base to move around", | |
| default=False, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--publish_rate", | |
| action="store", | |
| type=int, | |
| help="The rate at which to publish the actions", | |
| default=30, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--chunk_size", | |
| action="store", | |
| type=int, | |
| help="Action chunk size", | |
| default=50, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--arm_steps_length", | |
| action="store", | |
| type=float, | |
| help="The maximum change allowed for each joint per timestep", | |
| default=[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.2], | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--use_actions_interpolation", | |
| action="store_true", | |
| help="Whether to interpolate the actions if the difference is too large", | |
| default=False, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--use_depth_image", | |
| action="store_true", | |
| help="Whether to use depth images", | |
| default=False, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--host", | |
| action="store", | |
| type=str, | |
| help="Websocket server host", | |
| default="localhost", | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--port", | |
| action="store", | |
| type=int, | |
| help="Websocket server port", | |
| default=8000, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--ctrl_type", | |
| type=str, | |
| choices=["joint", "eef"], | |
| help="Control type for the robot arm", | |
| default="joint", | |
| ) | |
| parser.add_argument( | |
| "--use_temporal_smoothing", | |
| action="store_true", | |
| help="Enable non-blocking communication and control execution", | |
| default=False, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--latency_k", | |
| type=int, | |
| help="Max Latency in steps", | |
| default=8, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--inference_rate", | |
| type=float, | |
| help="Inference loop rate (Hz)", | |
| default=3.0, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--min_smooth_steps", | |
| type=int, | |
| help="Minimum smoothing steps m", | |
| default=8, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--buffer_max_chunks", | |
| type=int, | |
| help="Maximum number of chunks in the buffer", | |
| default=10, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--exp_decay_alpha", | |
| type=float, | |
| help="Exponential decay alpha", | |
| default=0.25, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--use_delta_eef_smoothing", | |
| action="store_true", | |
| help="Whether to use delta eef smoothing", | |
| default=False, | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| "--lang_embeddings", | |
| type=str, | |
| help="Language instruction prompt sent to the policy", | |
| default="Pick and sort bricks on the conveyor.", | |
| required=False, | |
| ) | |
| args = parser.parse_args() | |
| return args | |
| def main(): | |
| global lang_embeddings | |
| args = get_arguments() | |
| lang_embeddings = args.lang_embeddings | |
| ros_operator = RosOperator(args) | |
| if args.seed is not None: | |
| set_seed(args.seed) | |
| config = get_config(args) | |
| # Register SIGINT handler so Ctrl+C triggers save/flush | |
| signal.signal(signal.SIGINT, _on_sigint) | |
| try: | |
| model_inference(args, config, ros_operator) | |
| except KeyboardInterrupt: | |
| pass | |
| if __name__ == "__main__": | |
| main() |